MoleditPy-linux 4.1.2__tar.gz → 4.1.4__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 (81) hide show
  1. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/PKG-INFO +1 -1
  2. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/pyproject.toml +1 -1
  3. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/MoleditPy_linux.egg-info/PKG-INFO +1 -1
  4. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/__init__.py +0 -2
  5. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/plugins/plugin_interface.py +3 -5
  6. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/plugins/plugin_manager.py +11 -2
  7. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/plugins/plugin_manager_window.py +2 -3
  8. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/main_window.py +1 -2
  9. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/move_selected_atoms_dialog.py +101 -0
  10. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/view_3d_logic.py +1 -1
  11. moleditpy_linux-4.1.4/src/moleditpy_linux/utils/__init__.py +11 -0
  12. moleditpy_linux-4.1.2/src/moleditpy_linux/core/__init__.py +0 -0
  13. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/LICENSE +0 -0
  14. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/README.md +0 -0
  15. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/setup.cfg +0 -0
  16. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/MoleditPy_linux.egg-info/SOURCES.txt +0 -0
  17. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
  18. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
  19. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
  20. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
  21. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/__main__.py +0 -0
  22. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/assets/file_icon.ico +0 -0
  23. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/assets/icon.icns +0 -0
  24. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/assets/icon.ico +0 -0
  25. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/assets/icon.png +0 -0
  26. {moleditpy_linux-4.1.2/src/moleditpy_linux/plugins → moleditpy_linux-4.1.4/src/moleditpy_linux/core}/__init__.py +0 -0
  27. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/core/mol_geometry.py +0 -0
  28. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/core/molecular_data.py +0 -0
  29. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/main.py +0 -0
  30. {moleditpy_linux-4.1.2/src/moleditpy_linux/ui/settings_tabs → moleditpy_linux-4.1.4/src/moleditpy_linux/plugins}/__init__.py +0 -0
  31. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/__init__.py +0 -0
  32. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/about_dialog.py +0 -0
  33. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
  34. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
  35. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/analysis_window.py +0 -0
  36. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/angle_dialog.py +0 -0
  37. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/app_state.py +0 -0
  38. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/atom_item.py +0 -0
  39. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/atom_picking.py +0 -0
  40. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/base_picking_dialog.py +0 -0
  41. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/bond_item.py +0 -0
  42. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/bond_length_dialog.py +0 -0
  43. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/calculation_worker.py +0 -0
  44. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
  45. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/compute_logic.py +0 -0
  46. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +0 -0
  47. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/custom_interactor_style.py +0 -0
  48. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/custom_qt_interactor.py +0 -0
  49. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +0 -0
  50. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/dialog_logic.py +0 -0
  51. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/dihedral_dialog.py +0 -0
  52. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/edit_3d_logic.py +0 -0
  53. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/edit_actions_logic.py +0 -0
  54. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/export_logic.py +0 -0
  55. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
  56. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/io_logic.py +0 -0
  57. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/main_window_init.py +0 -0
  58. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
  59. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/molecular_scene_handler.py +0 -0
  60. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/molecule_scene.py +0 -0
  61. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/move_group_dialog.py +0 -0
  62. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
  63. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/planarize_dialog.py +0 -0
  64. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/plugin_menu_manager.py +0 -0
  65. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
  66. {moleditpy_linux-4.1.2/src/moleditpy_linux/utils → moleditpy_linux-4.1.4/src/moleditpy_linux/ui/settings_tabs}/__init__.py +0 -0
  67. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +0 -0
  68. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +0 -0
  69. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -0
  70. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -0
  71. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/string_importers.py +0 -0
  72. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/template_preview_item.py +0 -0
  73. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
  74. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/translation_dialog.py +0 -0
  75. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/ui_manager.py +0 -0
  76. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/user_template_dialog.py +0 -0
  77. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
  78. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/utils/constants.py +0 -0
  79. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/utils/default_settings.py +0 -0
  80. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
  81. {moleditpy_linux-4.1.2 → moleditpy_linux-4.1.4}/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.1.2
3
+ Version: 4.1.4
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.1.2"
8
+ version = "4.1.4"
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.1.2
3
+ Version: 4.1.4
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
@@ -8,8 +8,6 @@ Author: Hiromichi Yokoyama
8
8
  License: GPL-3.0 license
9
9
  Repo: https://github.com/HiroYokoyama/python_molecular_editor
10
10
  DOI: 10.5281/zenodo.17268532
11
-
12
- Top-level package for moleditpy_linux.
13
11
  """
14
12
 
15
13
  import importlib.util # noqa: F401
@@ -190,11 +190,9 @@ class PluginContext:
190
190
  Get or set the current molecule (RDKit Mol object). Shortcut for current_molecule.
191
191
  """
192
192
  mw = self.get_main_window()
193
- return (
194
- mw.view_3d_manager.current_mol
195
- if mw and hasattr(mw, "view_3d_manager")
196
- else None
197
- )
193
+ if mw and hasattr(mw, "view_3d_manager"):
194
+ return mw.view_3d_manager.current_mol
195
+ return None
198
196
 
199
197
  @current_mol.setter
200
198
  def current_mol(self, mol: Any) -> None:
@@ -206,9 +206,10 @@ class PluginManager:
206
206
  shutil.copy2(file_path, dest_path)
207
207
  msg = f"Installed {filename}"
208
208
 
209
- # Reload plugins after install
209
+ # Reload plugins after install and rebuild UI immediately
210
210
  if self.main_window:
211
211
  self.discover_plugins(self.main_window)
212
+ self.rebuild_plugin_menus()
212
213
  return True, msg
213
214
  except (
214
215
  AttributeError,
@@ -315,6 +316,13 @@ class PluginManager:
315
316
  stub.__package__ = parent_name
316
317
  sys.modules[parent_name] = stub
317
318
 
319
+ # Purge stale cache so package submodules reload from disk on re-exec
320
+ for _cached_key in list(sys.modules.keys()):
321
+ if _cached_key == unique_module_name or _cached_key.startswith(
322
+ unique_module_name + "."
323
+ ):
324
+ del sys.modules[_cached_key]
325
+
318
326
  spec = importlib.util.spec_from_file_location(
319
327
  unique_module_name,
320
328
  filepath,
@@ -588,7 +596,8 @@ class PluginManager:
588
596
  selected_atom_ids.add(item.atom_id)
589
597
 
590
598
  # Now map these editor IDs to RDKit indices
591
- mol = getattr(self.main_window, "current_mol", None)
599
+ v3d = getattr(self.main_window, "view_3d_manager", None)
600
+ mol = v3d.current_mol if v3d else None
592
601
  if mol and selected_atom_ids:
593
602
  for i in range(mol.GetNumAtoms()):
594
603
  atom = mol.GetAtomWithIdx(i)
@@ -156,13 +156,12 @@ class PluginManagerWindow(QDialog):
156
156
  self.btn_remove.setEnabled(has_selection)
157
157
 
158
158
  def on_reload(self, silent: bool = False) -> None:
159
- """Reload all plugins from disk and refresh the table."""
160
- # Trigger reload in main manager
159
+ """Reload all plugins from disk, rebuild main-window UI, and refresh the table."""
161
160
  if self.plugin_manager.main_window:
162
161
  self.plugin_manager.discover_plugins(self.plugin_manager.main_window)
162
+ self.plugin_manager.rebuild_plugin_menus()
163
163
  self.refresh_plugin_list()
164
164
 
165
- # For immediate feedback:
166
165
  if not silent:
167
166
  QMessageBox.information(self, "Reloaded", "Plugins have been reloaded.")
168
167
  else:
@@ -19,6 +19,7 @@ if TYPE_CHECKING:
19
19
  from rdkit import Chem
20
20
 
21
21
  # PyQt6 Modules
22
+ import copy
22
23
  from PyQt6.QtCore import pyqtSignal
23
24
  from PyQt6.QtWidgets import QMainWindow, QMessageBox
24
25
 
@@ -254,8 +255,6 @@ class MainWindow(QMainWindow):
254
255
 
255
256
  def save_state_snapshot(self) -> None:
256
257
  """Create a deep copy snapshot of the current state for undo/redo comparison."""
257
- import copy
258
-
259
258
  try:
260
259
  self.state_manager.saved_state = copy.deepcopy(
261
260
  self.state_manager.get_current_state()
@@ -56,6 +56,8 @@ class MoveSelectedAtomsDialog(BasePickingDialog):
56
56
  self.mouse_moved_during_drag: bool = False
57
57
  self._consume_next_left_release: bool = False
58
58
  self.highlight_actor: Optional[pv.Actor] = None
59
+ self.original_style: Optional[Any] = None
60
+ self._click_press_pos: Optional[Any] = None
59
61
 
60
62
  self.widgets: dict[str, Any] = {}
61
63
 
@@ -211,6 +213,13 @@ class MoveSelectedAtomsDialog(BasePickingDialog):
211
213
  def _init_buttons_ui(self, layout: QVBoxLayout) -> None:
212
214
  """Initialize bottom buttons."""
213
215
  button_layout = QHBoxLayout()
216
+
217
+ box_select_btn = QPushButton("Box Selection: OFF")
218
+ box_select_btn.setCheckable(True)
219
+ box_select_btn.clicked.connect(self.toggle_box_selection)
220
+ self.widgets["box_select_btn"] = box_select_btn
221
+ button_layout.addWidget(box_select_btn)
222
+
214
223
  clear_btn = QPushButton("Clear Selection")
215
224
  clear_btn.clicked.connect(self.clear_selection)
216
225
  self.widgets["clear_button"] = clear_btn
@@ -238,6 +247,24 @@ class MoveSelectedAtomsDialog(BasePickingDialog):
238
247
 
239
248
  e_type = event.type()
240
249
 
250
+ btn = self.widgets.get("box_select_btn")
251
+ box_selection_on = btn is not None and btn.isChecked()
252
+
253
+ if e_type == QEvent.Type.MouseButtonPress:
254
+ self._click_press_pos = getattr(event, "pos", lambda: None)()
255
+ elif e_type == QEvent.Type.MouseButtonRelease:
256
+ if hasattr(self, "_click_press_pos") and self._click_press_pos is not None:
257
+ pos = getattr(event, "pos", lambda: None)()
258
+ if pos:
259
+ diff = pos - self._click_press_pos
260
+ if diff.manhattanLength() < 3:
261
+ if box_selection_on:
262
+ self.clear_selection()
263
+ self._click_press_pos = None
264
+
265
+ if box_selection_on:
266
+ return False
267
+
241
268
  if e_type == QEvent.Type.MouseButtonDblClick:
242
269
  # Ignore double clicks and reset state
243
270
  self.is_dragging_group = False
@@ -645,3 +672,77 @@ class MoveSelectedAtomsDialog(BasePickingDialog):
645
672
  self.update_display()
646
673
  self.is_dragging_group = False
647
674
  self.drag_start_pos = None
675
+
676
+ def toggle_box_selection(self, checked: bool) -> None:
677
+ """Toggle Box Selection mode using PyVista's rectangle picker."""
678
+ plotter = self.main_window.view_3d_manager.plotter
679
+ if plotter is None or plotter.interactor is None:
680
+ return
681
+
682
+ btn = self.widgets["box_select_btn"]
683
+ if checked:
684
+ btn.setText("Box Selection: ON")
685
+ # Save original style if not saved
686
+ if self.original_style is None:
687
+ self.original_style = plotter.interactor.GetInteractorStyle()
688
+
689
+ # Using PyVista's built-in picking which reliably draws the box correctly
690
+ plotter.enable_rectangle_picking(
691
+ callback=self.on_rectangle_picked,
692
+ show_message=False,
693
+ start=True,
694
+ color="red",
695
+ )
696
+ else:
697
+ btn.setText("Box Selection: OFF")
698
+ plotter.disable_picking()
699
+ # Restore original style
700
+ if self.original_style is not None:
701
+ plotter.interactor.SetInteractorStyle(self.original_style)
702
+
703
+ def on_rectangle_picked(self, selection: Any) -> None:
704
+ """Handle PyVista rectangle picking callback."""
705
+ if not hasattr(selection, "viewport"):
706
+ return
707
+
708
+ x0, y0, x1, y1 = selection.viewport
709
+ x_min = min(x0, x1)
710
+ x_max = max(x0, x1)
711
+ y_min = min(y0, y1)
712
+ y_max = max(y0, y1)
713
+
714
+ # If the drag box is small, treat it as a single click to clear selection
715
+ if abs(x_max - x_min) < 15 and abs(y_max - y_min) < 15:
716
+ self.clear_selection()
717
+ return
718
+
719
+ plotter = self.main_window.view_3d_manager.plotter
720
+ if plotter is None:
721
+ return
722
+ renderer = plotter.renderer
723
+
724
+ positions = self.main_window.view_3d_manager.atom_positions_3d
725
+ if positions is not None:
726
+ added = False
727
+ for atom_idx, pos in enumerate(positions):
728
+ renderer.SetWorldPoint(float(pos[0]), float(pos[1]), float(pos[2]), 1.0)
729
+ renderer.WorldToDisplay()
730
+ display = renderer.GetDisplayPoint()
731
+
732
+ if x_min <= display[0] <= x_max and y_min <= display[1] <= y_max:
733
+ if atom_idx not in self.selected_atoms:
734
+ self.selected_atoms.add(atom_idx)
735
+ added = True
736
+
737
+ if added:
738
+ self.show_atom_labels()
739
+ self.update_display()
740
+
741
+ def reject(self) -> None:
742
+ """Clean up when dialog closes."""
743
+ # Ensure we restore the interactor style if it was left in Box Selection mode
744
+ btn = self.widgets.get("box_select_btn")
745
+ if btn and btn.isChecked():
746
+ btn.setChecked(False)
747
+ self.toggle_box_selection(False)
748
+ super().reject()
@@ -2,7 +2,7 @@
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
4
  """
5
- MoleditPy  EA Python-based molecular editing software
5
+ MoleditPy — A Python-based molecular editing software
6
6
 
7
7
  Author: Hiromichi Yokoyama
8
8
  License: GPL-3.0 license
@@ -0,0 +1,11 @@
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
+ """
File without changes