MoleditPy 3.0.0a6__tar.gz → 3.0.0a8__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 (78) hide show
  1. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/PKG-INFO +1 -1
  2. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/pyproject.toml +1 -1
  3. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/MoleditPy.egg-info/PKG-INFO +1 -1
  4. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/plugins/plugin_interface.py +5 -5
  5. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/plugins/plugin_manager_window.py +56 -17
  6. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/io_logic.py +36 -24
  7. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/main_window.py +7 -6
  8. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/ui_manager.py +14 -11
  9. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/utils/constants.py +1 -1
  10. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/LICENSE +0 -0
  11. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/README.md +0 -0
  12. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/setup.cfg +0 -0
  13. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/MoleditPy.egg-info/SOURCES.txt +0 -0
  14. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
  15. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/MoleditPy.egg-info/entry_points.txt +0 -0
  16. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/MoleditPy.egg-info/requires.txt +0 -0
  17. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/MoleditPy.egg-info/top_level.txt +0 -0
  18. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/__init__.py +0 -0
  19. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/__main__.py +0 -0
  20. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/assets/file_icon.ico +0 -0
  21. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/assets/icon.icns +0 -0
  22. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/assets/icon.ico +0 -0
  23. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/assets/icon.png +0 -0
  24. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/core/__init__.py +0 -0
  25. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/core/mol_geometry.py +0 -0
  26. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/core/molecular_data.py +0 -0
  27. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/main.py +0 -0
  28. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/plugins/__init__.py +0 -0
  29. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/plugins/plugin_manager.py +0 -0
  30. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/__init__.py +0 -0
  31. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/about_dialog.py +0 -0
  32. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/align_plane_dialog.py +0 -0
  33. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/alignment_dialog.py +0 -0
  34. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/analysis_window.py +0 -0
  35. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/angle_dialog.py +0 -0
  36. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/app_state.py +0 -0
  37. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/atom_item.py +0 -0
  38. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/base_picking_dialog.py +0 -0
  39. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/bond_item.py +0 -0
  40. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/bond_length_dialog.py +0 -0
  41. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/calculation_worker.py +0 -0
  42. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/color_settings_dialog.py +0 -0
  43. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/compute_logic.py +0 -0
  44. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/constrained_optimization_dialog.py +0 -0
  45. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/custom_interactor_style.py +0 -0
  46. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/custom_qt_interactor.py +0 -0
  47. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/dialog_3d_picking_mixin.py +0 -0
  48. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/dialog_logic.py +0 -0
  49. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/dihedral_dialog.py +0 -0
  50. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/edit_3d_logic.py +0 -0
  51. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/edit_actions_logic.py +0 -0
  52. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/export_logic.py +0 -0
  53. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/geometry_base_dialog.py +0 -0
  54. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/main_window_init.py +0 -0
  55. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/mirror_dialog.py +0 -0
  56. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/molecular_scene_handler.py +0 -0
  57. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/molecule_scene.py +0 -0
  58. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/move_group_dialog.py +0 -0
  59. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/periodic_table_dialog.py +0 -0
  60. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/planarize_dialog.py +0 -0
  61. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/settings_dialog.py +0 -0
  62. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/settings_tabs/__init__.py +0 -0
  63. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/settings_tabs/settings_2d_tab.py +0 -0
  64. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/settings_tabs/settings_3d_tabs.py +0 -0
  65. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/settings_tabs/settings_other_tab.py +0 -0
  66. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/settings_tabs/settings_tab_base.py +0 -0
  67. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/sip_isdeleted_safe.py +0 -0
  68. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/string_importers.py +0 -0
  69. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/template_preview_item.py +0 -0
  70. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/template_preview_view.py +0 -0
  71. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/translation_dialog.py +0 -0
  72. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/user_template_dialog.py +0 -0
  73. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/view_3d_logic.py +0 -0
  74. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/ui/zoomable_view.py +0 -0
  75. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/utils/__init__.py +0 -0
  76. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/utils/default_settings.py +0 -0
  77. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/utils/sip_isdeleted_safe.py +0 -0
  78. {moleditpy-3.0.0a6 → moleditpy-3.0.0a8}/src/moleditpy/utils/system_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy
3
- Version: 3.0.0a6
3
+ Version: 3.0.0a8
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"
7
7
 
8
- version = "3.0.0a6"
8
+ version = "3.0.0a8"
9
9
 
10
10
  license = {file = "LICENSE"}
11
11
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy
3
- Version: 3.0.0a6
3
+ Version: 3.0.0a8
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
@@ -179,13 +179,13 @@ class PluginContext:
179
179
  Get or set the current molecule (RDKit Mol object). Shortcut for current_molecule.
180
180
  """
181
181
  mw = self.get_main_window()
182
- return mw.current_mol if mw else None
182
+ return mw.view_3d_manager.current_mol if mw and hasattr(mw, "view_3d_manager") else None
183
183
 
184
184
  @current_mol.setter
185
185
  def current_mol(self, mol: Any):
186
186
  mw = self.get_main_window()
187
- if mw:
188
- mw.current_mol = mol
187
+ if mw and hasattr(mw, "view_3d_manager"):
188
+ mw.view_3d_manager.current_mol = mol
189
189
  if hasattr(mw.view_3d_manager, "draw_molecule_3d"):
190
190
  mw.view_3d_manager.draw_molecule_3d(mol)
191
191
 
@@ -204,7 +204,7 @@ class PluginContext:
204
204
  Returns the PyVista plotter from the MainWindow.
205
205
  """
206
206
  mw = self.get_main_window()
207
- return mw.plotter if mw else None
207
+ return mw.view_3d_manager.plotter if mw and hasattr(mw, "view_3d_manager") else None
208
208
 
209
209
  @property
210
210
  def scene(self) -> Any:
@@ -212,7 +212,7 @@ class PluginContext:
212
212
  Returns the 2D MoleculeScene from the MainWindow.
213
213
  """
214
214
  mw = self.get_main_window()
215
- return mw.scene if mw else None
215
+ return mw.init_manager.scene if mw and hasattr(mw, "init_manager") else None
216
216
 
217
217
  def add_export_action(self, label: str, callback: Callable):
218
218
  """
@@ -10,8 +10,9 @@ Repo: https://github.com/HiroYokoyama/python_molecular_editor
10
10
  DOI: 10.5281/zenodo.17268532
11
11
  """
12
12
 
13
- import os
14
- import shutil
13
+ import os
14
+ import shutil
15
+ import hashlib
15
16
 
16
17
  from PyQt6.QtCore import Qt, QUrl
17
18
  from PyQt6.QtGui import QDesktopServices, QDragEnterEvent, QDropEvent
@@ -257,9 +258,10 @@ class PluginManagerWindow(QDialog):
257
258
  is_valid = True
258
259
  is_folder = True
259
260
 
260
- if is_valid:
261
- # Extract info and confirm
262
- info = {
261
+ if is_valid:
262
+ sha256_value = self._compute_sha256(file_path)
263
+ # Extract info and confirm
264
+ info = {
263
265
  "name": os.path.basename(file_path),
264
266
  "version": "Unknown",
265
267
  "author": "Unknown",
@@ -279,14 +281,15 @@ class PluginManagerWindow(QDialog):
279
281
  elif file_path.endswith(".py"):
280
282
  info = self.plugin_manager.get_plugin_info_safe(file_path)
281
283
 
282
- msg = (
283
- f"Do you want to install this plugin?\n\n"
284
- f"Name: {info['name']}\n"
285
- f"Author: {info['author']}\n"
286
- f"Version: {info['version']}\n"
287
- f"Description: {info['description']}\n\n"
288
- f"File: {os.path.basename(file_path)}"
289
- )
284
+ msg = (
285
+ f"Do you want to install this plugin?\n\n"
286
+ f"Name: {info['name']}\n"
287
+ f"Author: {info['author']}\n"
288
+ f"Version: {info['version']}\n"
289
+ f"Description: {info['description']}\n\n"
290
+ f"File: {os.path.basename(file_path)}\n"
291
+ f"SHA-256: {sha256_value}"
292
+ )
290
293
 
291
294
  reply = QMessageBox.question(
292
295
  self,
@@ -307,7 +310,43 @@ class PluginManagerWindow(QDialog):
307
310
  summary = ""
308
311
  if files_installed:
309
312
  summary += "Installed:\n" + "\n".join(files_installed) + "\n\n"
310
- if errors:
311
- summary += "Errors:\n" + "\n".join(errors)
312
-
313
- QMessageBox.information(self, "Plugin Installation", summary)
313
+ if errors:
314
+ summary += "Errors:\n" + "\n".join(errors)
315
+
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"
@@ -175,32 +175,44 @@ class IOManager:
175
175
  _set_prop(final_mol, "_xyz_skip_checks", 1)
176
176
  else:
177
177
  final_mol = None
178
- while True:
179
- prompt_fn = getattr(self, "prompt_for_charge", None)
180
- if callable(prompt_fn):
181
- result = prompt_fn()
182
- if isinstance(result, tuple) and len(result) == 3:
183
- charge_val, ok, skip_flag = result
178
+ settings = getattr(self.host.init_manager, "settings", {}) or {}
179
+ # First try with charge 0 (per user's 'first try with 0 then ask' requirement)
180
+ # but only if "Always ask" is not explicitly enabled in settings.
181
+ if not settings.get("always_ask_charge", False):
182
+ try:
183
+ final_mol = _process(0, use_rd_determine=True)
184
+ except (RuntimeError, ValueError, TypeError):
185
+ final_mol = None
186
+
187
+ # If still no final_mol (because always_ask is True, or charge 0 failed)
188
+ if final_mol is None:
189
+ while True:
190
+ prompt_fn = getattr(self, "prompt_for_charge", None)
191
+ if callable(prompt_fn):
192
+ result = prompt_fn()
193
+ if isinstance(result, tuple) and len(result) == 3:
194
+ charge_val, ok, skip_flag = result
195
+ else:
196
+ charge_val, ok, skip_flag = 0, True, False
184
197
  else:
185
198
  charge_val, ok, skip_flag = 0, True, False
186
- else:
187
- charge_val, ok, skip_flag = 0, True, False
188
-
189
- if not ok:
190
- return None
191
- if skip_flag:
192
- final_mol = _process(0, use_rd_determine=False)
193
- _set_prop(final_mol, "_xyz_skip_checks", 1)
194
- break
195
- try:
196
- final_mol = _process(charge_val, use_rd_determine=True)
197
- break
198
- except (RuntimeError, ValueError, TypeError) as e:
199
- self.host.statusBar().showMessage(
200
- f"Chemistry failed for charge {charge_val}: {e}. Try a different charge or skip."
201
- )
202
- if not callable(prompt_fn):
203
- raise e
199
+
200
+ if not ok:
201
+ return None
202
+ if skip_flag:
203
+ final_mol = _process(0, use_rd_determine=False)
204
+ _set_prop(final_mol, "_xyz_skip_checks", 1)
205
+ break
206
+ try:
207
+ final_mol = _process(charge_val, use_rd_determine=True)
208
+ break
209
+ except (RuntimeError, ValueError, TypeError) as e:
210
+ if hasattr(self.host, "statusBar") and self.host.statusBar():
211
+ self.host.statusBar().showMessage(
212
+ f"Chemistry failed for charge {charge_val}: {e}. Try a different charge or skip."
213
+ )
214
+ if not callable(prompt_fn):
215
+ raise e
204
216
 
205
217
  if final_mol:
206
218
  final_mol._xyz_atom_data = atoms_data
@@ -86,14 +86,15 @@ class MainWindow(QMainWindow):
86
86
  def plotter(self):
87
87
  return self.view_3d_manager.plotter
88
88
 
89
+ @property
90
+ def data(self):
91
+ return self.state_manager.data
92
+
89
93
  @property
90
94
  def scene(self):
91
95
  return self.init_manager.scene
92
96
 
93
- def dragEnterEvent(self, event):
94
- """Delegate drag enter event to UI manager."""
95
- self.ui_manager.dragEnterEvent(event)
97
+ def draw_molecule_3d(self, mol):
98
+ self.current_mol = mol
99
+ self.view_3d_manager.draw_molecule_3d(mol)
96
100
 
97
- def dropEvent(self, event):
98
- """Delegate drop event to UI manager."""
99
- self.ui_manager.dropEvent(event)
@@ -200,11 +200,20 @@ class UIManager(QObject):
200
200
  ):
201
201
  self.host.init_manager.view_2d.setFocus()
202
202
 
203
- # Handle Window Close via event filter
204
- if obj is self.host and event.type() == QEvent.Type.Close:
205
- if not self.handle_close_event(event):
206
- event.ignore()
207
- return True # Stop propagation
203
+ if obj is self.host:
204
+ # Handle Drag and Drop via event filter
205
+ if event.type() == QEvent.Type.DragEnter:
206
+ self.handle_drag_enter_event(event)
207
+ return True
208
+ if event.type() == QEvent.Type.Drop:
209
+ self.handle_drop_event(event)
210
+ return True
211
+
212
+ # Handle Window Close via event filter
213
+ if event.type() == QEvent.Type.Close:
214
+ if not self.handle_close_event(event):
215
+ event.ignore()
216
+ return True # Stop propagation
208
217
 
209
218
  return super().eventFilter(obj, event)
210
219
 
@@ -310,9 +319,6 @@ class UIManager(QObject):
310
319
  self.host.view_3d_manager.plotter.interactor.SetInteractorStyle(style)
311
320
  self.host.view_3d_manager.plotter.interactor.Initialize()
312
321
 
313
- def dragEnterEvent(self, event):
314
- """Handle drag enter event."""
315
- self.handle_drag_enter_event(event)
316
322
 
317
323
  def handle_drag_enter_event(self, event):
318
324
  """Internal handler for drag enter event (bypasses PyQt type checks in tests)."""
@@ -338,9 +344,6 @@ class UIManager(QObject):
338
344
 
339
345
  event.ignore()
340
346
 
341
- def dropEvent(self, event):
342
- """Handle file drop event."""
343
- self.handle_drop_event(event)
344
347
 
345
348
  def handle_drop_event(self, event):
346
349
  """Internal handler for file drop event (bypasses PyQt type checks in tests)."""
@@ -16,7 +16,7 @@ from PyQt6.QtGui import QColor, QFont
16
16
  from rdkit import Chem
17
17
 
18
18
  # Version
19
- VERSION = "3.0.0a6"
19
+ VERSION = "3.0.0a8"
20
20
 
21
21
  ATOM_RADIUS = 18
22
22
  BOND_OFFSET = 3.5
File without changes
File without changes
File without changes