MoleditPy 4.9.0__tar.gz → 4.9.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-4.9.0 → moleditpy-4.9.2}/PKG-INFO +1 -1
- {moleditpy-4.9.0 → moleditpy-4.9.2}/pyproject.toml +1 -1
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/MoleditPy.egg-info/PKG-INFO +1 -1
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/core/mol_geometry.py +2 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/core/molecular_data.py +2 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/main.py +2 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/plugins/plugin_interface.py +4 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/plugins/plugin_manager.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/plugins/plugin_manager_window.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/about_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/align_plane_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/alignment_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/analysis_window.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/angle_dialog.py +2 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/app_state.py +2 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/atom_item.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/atom_picking.py +3 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/base_picking_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/bond_item.py +2 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/bond_length_dialog.py +2 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/calculation_worker.py +5 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/chain_mixin.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/color_settings_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/compute_logic.py +10 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/constrained_optimization_dialog.py +5 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/custom_interactor_style.py +12 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/custom_qt_interactor.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/dialog_3d_picking_mixin.py +2 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/dialog_logic.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/dihedral_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/edit_3d_logic.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/edit_actions_logic.py +5 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/export_logic.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/geometry_base_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/io_logic.py +74 -24
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/main_window.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/main_window_init.py +6 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/mirror_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/molecular_scene_handler.py +9 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/molecule_scene.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/move_group_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/move_selected_atoms_dialog.py +2 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/periodic_table_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/planarize_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/plugin_menu_manager.py +6 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/preview_molecule.py +2 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/settings_dialog.py +4 -1
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/settings_2d_cleanup_tab.py +9 -2
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/settings_2d_tab.py +8 -2
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/settings_3d_tabs.py +17 -4
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/settings_other_tab.py +15 -4
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/settings_tab_base.py +22 -3
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/string_importers.py +3 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/template_preview_item.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/template_preview_view.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/translation_dialog.py +5 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/ui_manager.py +2 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/user_template_dialog.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/view_3d_logic.py +9 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/zoomable_view.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/utils/constants.py +1 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/LICENSE +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/README.md +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/setup.cfg +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/MoleditPy.egg-info/SOURCES.txt +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/MoleditPy.egg-info/entry_points.txt +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/MoleditPy.egg-info/requires.txt +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/MoleditPy.egg-info/top_level.txt +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/__init__.py +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/__main__.py +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/assets/file_icon.ico +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/assets/icon.icns +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/assets/icon.ico +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/assets/icon.png +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/core/__init__.py +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/plugins/__init__.py +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/__init__.py +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/__init__.py +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/utils/__init__.py +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/utils/default_settings.py +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/utils/hit_radius.py +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/utils/sip_isdeleted_safe.py +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/utils/suppress_log.py +0 -0
- {moleditpy-4.9.0 → moleditpy-4.9.2}/src/moleditpy/utils/system_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 4.9.
|
|
3
|
+
Version: 4.9.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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 4.9.
|
|
3
|
+
Version: 4.9.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
|
|
@@ -723,6 +723,7 @@ def resolve_2d_overlaps(
|
|
|
723
723
|
parent = {aid: aid for aid in atom_ids}
|
|
724
724
|
|
|
725
725
|
def find_set(aid: int) -> int:
|
|
726
|
+
"""Find representative root for an element in disjoint-set."""
|
|
726
727
|
# Iterative path compression avoids recursion limits
|
|
727
728
|
root = aid
|
|
728
729
|
while parent[root] != root:
|
|
@@ -732,6 +733,7 @@ def resolve_2d_overlaps(
|
|
|
732
733
|
return root
|
|
733
734
|
|
|
734
735
|
def unite_sets(aid1: int, aid2: int) -> None:
|
|
736
|
+
"""Unite two sets in disjoint-set forest."""
|
|
735
737
|
root1 = find_set(aid1)
|
|
736
738
|
root2 = find_set(aid2)
|
|
737
739
|
if root1 != root2:
|
|
@@ -126,6 +126,7 @@ class MolecularData:
|
|
|
126
126
|
next_atom_id: int
|
|
127
127
|
|
|
128
128
|
def __init__(self) -> None:
|
|
129
|
+
"""Initialize MolecularData structure with atoms, bonds, and coordinates."""
|
|
129
130
|
self.atoms = {}
|
|
130
131
|
self.bonds = {}
|
|
131
132
|
self.next_atom_id = 0
|
|
@@ -342,6 +343,7 @@ class MolecularData:
|
|
|
342
343
|
|
|
343
344
|
# Helper: Pick neighbors prioritizing heavy atoms
|
|
344
345
|
def pick_preferred_neighbor(atom: Chem.Atom, exclude_idx: int) -> Optional[int]:
|
|
346
|
+
"""Select preferred neighbor atom for geometry alignment or reference."""
|
|
345
347
|
for nbr in atom.GetNeighbors():
|
|
346
348
|
if nbr.GetIdx() == exclude_idx:
|
|
347
349
|
continue
|
|
@@ -77,6 +77,7 @@ class _ErrorDialogHandler(logging.Handler):
|
|
|
77
77
|
_DEDUP_WINDOW_S = 10.0
|
|
78
78
|
|
|
79
79
|
def __init__(self, log_path: Optional[str] = None) -> None:
|
|
80
|
+
"""Initialize GUI error dialog logging handler with dedup tracking."""
|
|
80
81
|
super().__init__(level=logging.ERROR)
|
|
81
82
|
# signature -> monotonic timestamp of its last shown dialog.
|
|
82
83
|
self._last_shown: Dict[str, float] = {}
|
|
@@ -85,6 +86,7 @@ class _ErrorDialogHandler(logging.Handler):
|
|
|
85
86
|
self._log_path = log_path
|
|
86
87
|
|
|
87
88
|
def emit(self, record: logging.LogRecord) -> None:
|
|
89
|
+
"""Emit error dialog for unhandled error log record with deduplication."""
|
|
88
90
|
try:
|
|
89
91
|
if getattr(record, "no_dialog", False):
|
|
90
92
|
return
|
|
@@ -21,6 +21,7 @@ class PluginContext:
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
def __init__(self, manager: Any, plugin_name: str) -> None:
|
|
24
|
+
"""Initialize plugin execution context with host reference."""
|
|
24
25
|
self._manager = manager
|
|
25
26
|
self._plugin_name = plugin_name
|
|
26
27
|
|
|
@@ -206,6 +207,7 @@ class PluginContext:
|
|
|
206
207
|
|
|
207
208
|
@current_mol.setter
|
|
208
209
|
def current_mol(self, mol: Any) -> None:
|
|
210
|
+
"""Set active RDKit molecule and redraw 3D view."""
|
|
209
211
|
mw = self.get_main_window()
|
|
210
212
|
if mw and hasattr(mw, "view_3d_manager"):
|
|
211
213
|
mw.view_3d_manager.current_mol = mol
|
|
@@ -218,6 +220,7 @@ class PluginContext:
|
|
|
218
220
|
|
|
219
221
|
@current_molecule.setter
|
|
220
222
|
def current_molecule(self, mol: Any) -> None:
|
|
223
|
+
"""Set active molecule via current_mol setter."""
|
|
221
224
|
self.current_mol = mol
|
|
222
225
|
|
|
223
226
|
@property
|
|
@@ -607,6 +610,7 @@ class Plugin3DController:
|
|
|
607
610
|
"""Helper to manipulate the 3D scene."""
|
|
608
611
|
|
|
609
612
|
def __init__(self, main_window: Any) -> None:
|
|
613
|
+
"""Initialize Plugin3DController with main window reference."""
|
|
610
614
|
self._mw = main_window
|
|
611
615
|
|
|
612
616
|
def _get_v3d(self) -> Optional[Any]:
|
|
@@ -37,6 +37,7 @@ class PluginManagerWindow(QDialog):
|
|
|
37
37
|
"""Dialog for browsing, installing, and removing plugins."""
|
|
38
38
|
|
|
39
39
|
def __init__(self, plugin_manager: Any, parent: Optional[QWidget] = None) -> None:
|
|
40
|
+
"""Initialize plugin management dialog window."""
|
|
40
41
|
super().__init__(parent)
|
|
41
42
|
self.btn_remove: Any = None
|
|
42
43
|
self.table: Any = None
|
|
@@ -33,6 +33,7 @@ class AboutDialog(QDialog):
|
|
|
33
33
|
"""Dialog showing application version, icon, and project links."""
|
|
34
34
|
|
|
35
35
|
def __init__(self, main_window: Any, parent: Optional[QWidget] = None) -> None:
|
|
36
|
+
"""Initialize About dialog with version and author info."""
|
|
36
37
|
super().__init__(parent)
|
|
37
38
|
self.image_label: Any = None
|
|
38
39
|
self.main_window = main_window
|
|
@@ -45,6 +45,7 @@ class AlignPlaneDialog(BasePickingDialog):
|
|
|
45
45
|
preselected_atoms: Optional[Sequence[int]] = None,
|
|
46
46
|
parent: Optional[QWidget] = None,
|
|
47
47
|
) -> None:
|
|
48
|
+
"""Initialize align plane dialog with target geometry settings."""
|
|
48
49
|
super().__init__(mol, main_window, parent)
|
|
49
50
|
self.plane = plane
|
|
50
51
|
self._selected_atoms = SelectionList()
|
|
@@ -49,6 +49,7 @@ class AlignmentDialog(Dialog3DPickingMixin, QDialog):
|
|
|
49
49
|
preselected_atoms: Optional[Sequence[int]] = None,
|
|
50
50
|
parent: Optional[QWidget] = None,
|
|
51
51
|
) -> None:
|
|
52
|
+
"""Initialize alignment dialog with reference axis settings."""
|
|
52
53
|
QDialog.__init__(self, parent)
|
|
53
54
|
Dialog3DPickingMixin.__init__(self)
|
|
54
55
|
self.mol = mol
|
|
@@ -41,6 +41,7 @@ class AnalysisWindow(QDialog):
|
|
|
41
41
|
parent: Optional[QWidget] = None,
|
|
42
42
|
is_xyz_derived: bool = False,
|
|
43
43
|
) -> None:
|
|
44
|
+
"""Initialize analysis window for molecular properties."""
|
|
44
45
|
super().__init__(parent)
|
|
45
46
|
self.mol = mol
|
|
46
47
|
self.is_xyz_derived = is_xyz_derived # Flag indicating if derived from XYZ
|
|
@@ -51,6 +51,7 @@ class AngleDialog(GeometryBaseDialog):
|
|
|
51
51
|
preselected_atoms: Optional[Sequence[int]] = None,
|
|
52
52
|
parent: Optional[QWidget] = None,
|
|
53
53
|
) -> None:
|
|
54
|
+
"""Initialize bond angle adjustment dialog."""
|
|
54
55
|
super().__init__(mol, main_window, parent)
|
|
55
56
|
self._baseline_positions: Any = None
|
|
56
57
|
self._snapshot_positions = None
|
|
@@ -384,6 +385,7 @@ class AngleDialog(GeometryBaseDialog):
|
|
|
384
385
|
self._push_undo()
|
|
385
386
|
|
|
386
387
|
def _is_selection_complete(self) -> bool:
|
|
388
|
+
"""Check whether required three atoms have been picked."""
|
|
387
389
|
return (
|
|
388
390
|
self.atom1_idx is not None
|
|
389
391
|
and self.atom2_idx is not None
|
|
@@ -74,6 +74,7 @@ class StateManager:
|
|
|
74
74
|
"""Manages serialized undo/redo state and document save state for MainWindow."""
|
|
75
75
|
|
|
76
76
|
def __init__(self, host: MainWindow) -> None:
|
|
77
|
+
"""Initialize StateManager with reference to host window."""
|
|
77
78
|
self.host = host
|
|
78
79
|
self.data: MolecularData # Dynamically assigned in main_window_init.py
|
|
79
80
|
self.has_unsaved_changes = False
|
|
@@ -139,6 +140,7 @@ class StateManager:
|
|
|
139
140
|
file_version_str = loaded_data.get("version", "0.0.0")
|
|
140
141
|
|
|
141
142
|
def parse_v(v_str: str) -> Tuple[int, ...]:
|
|
143
|
+
"""Parse version string into tuple of integers."""
|
|
142
144
|
if not v_str or not isinstance(v_str, str):
|
|
143
145
|
return (0, 0, 0)
|
|
144
146
|
try:
|
|
@@ -78,6 +78,7 @@ class AtomItem(QGraphicsItem):
|
|
|
78
78
|
def __init__(
|
|
79
79
|
self, atom_id: int, symbol: str, pos: QPointF, charge: int = 0, radical: int = 0
|
|
80
80
|
) -> None:
|
|
81
|
+
"""Initialize 2D atom graphics item."""
|
|
81
82
|
super().__init__()
|
|
82
83
|
self.atom_id: int = atom_id
|
|
83
84
|
self.symbol: str = symbol
|
|
@@ -21,6 +21,7 @@ from ..utils.constants import VDW_DISPLAY_RADII, pt
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def _world_to_display(renderer: Any, pos: Any) -> Optional[tuple[float, float, float]]:
|
|
24
|
+
"""Project 3D world coordinates to 2D display viewport pixels."""
|
|
24
25
|
try:
|
|
25
26
|
renderer.SetWorldPoint(float(pos[0]), float(pos[1]), float(pos[2]), 1.0)
|
|
26
27
|
renderer.WorldToDisplay()
|
|
@@ -31,6 +32,7 @@ def _world_to_display(renderer: Any, pos: Any) -> Optional[tuple[float, float, f
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def _atom_world_radius(view_3d_manager: Any, mol: Any, atom_idx: int) -> float:
|
|
35
|
+
"""Calculate effective atom sphere radius in 3D world units."""
|
|
34
36
|
try:
|
|
35
37
|
atom = mol.GetAtomWithIdx(int(atom_idx))
|
|
36
38
|
symbol = atom.GetSymbol()
|
|
@@ -68,6 +70,7 @@ def _atom_world_radius(view_3d_manager: Any, mol: Any, atom_idx: int) -> float:
|
|
|
68
70
|
def _projected_radius_px(
|
|
69
71
|
renderer: Any, center: Any, world_radius: float
|
|
70
72
|
) -> Optional[float]:
|
|
73
|
+
"""Calculate projected atom radius on screen in display pixels."""
|
|
71
74
|
center_display = _world_to_display(renderer, center)
|
|
72
75
|
if center_display is None:
|
|
73
76
|
return None
|
|
@@ -57,6 +57,7 @@ class BasePickingDialog(Dialog3DPickingMixin, QDialog):
|
|
|
57
57
|
main_window: "MainWindow",
|
|
58
58
|
parent: Optional[QWidget] = None,
|
|
59
59
|
) -> None:
|
|
60
|
+
"""Initialize base atom picking dialog."""
|
|
60
61
|
QDialog.__init__(self, parent)
|
|
61
62
|
Dialog3DPickingMixin.__init__(self)
|
|
62
63
|
self.mol = mol
|
|
@@ -120,6 +120,7 @@ class BondItem(QGraphicsItem):
|
|
|
120
120
|
"""Shorten inner ring double bonds based on the ring angle at both atoms."""
|
|
121
121
|
|
|
122
122
|
def angle_between(v1: QPointF, v2: QPointF) -> Optional[float]:
|
|
123
|
+
"""Calculate smaller unsigned angle between two 2D vectors in radians."""
|
|
123
124
|
len1 = math.hypot(v1.x(), v1.y())
|
|
124
125
|
len2 = math.hypot(v2.x(), v2.y())
|
|
125
126
|
if len1 <= 1e-6 or len2 <= 1e-6:
|
|
@@ -158,6 +159,7 @@ class BondItem(QGraphicsItem):
|
|
|
158
159
|
order: int = 1,
|
|
159
160
|
stereo: int = 0,
|
|
160
161
|
) -> None:
|
|
162
|
+
"""Initialize 2D bond graphics item between two atoms."""
|
|
161
163
|
super().__init__()
|
|
162
164
|
# Validate input parameters
|
|
163
165
|
if atom1_item is None or atom2_item is None:
|
|
@@ -48,6 +48,7 @@ class BondLengthDialog(GeometryBaseDialog):
|
|
|
48
48
|
preselected_atoms: Optional[Sequence[int]] = None,
|
|
49
49
|
parent: Optional[QWidget] = None,
|
|
50
50
|
) -> None:
|
|
51
|
+
"""Initialize bond length adjustment dialog."""
|
|
51
52
|
super().__init__(mol, main_window, parent)
|
|
52
53
|
self._baseline_positions: Any = None
|
|
53
54
|
self._snapshot_positions = None
|
|
@@ -325,6 +326,7 @@ class BondLengthDialog(GeometryBaseDialog):
|
|
|
325
326
|
self._push_undo()
|
|
326
327
|
|
|
327
328
|
def _is_selection_complete(self) -> bool:
|
|
329
|
+
"""Check whether required two atoms have been picked."""
|
|
328
330
|
return self.atom1_idx is not None and self.atom2_idx is not None
|
|
329
331
|
|
|
330
332
|
def apply_geometry_update(self, new_distance: float) -> None: # pylint: disable=arguments-renamed
|
|
@@ -808,6 +808,7 @@ class CalculationWorker(QObject):
|
|
|
808
808
|
start_work = pyqtSignal(str, object)
|
|
809
809
|
|
|
810
810
|
def __init__(self, parent: Optional[QObject] = None) -> None:
|
|
811
|
+
"""Initialize background calculation worker thread."""
|
|
811
812
|
super().__init__(parent)
|
|
812
813
|
self.halt_ids: Optional[Set[Any]] = None
|
|
813
814
|
self.halt_all: bool = False
|
|
@@ -822,6 +823,7 @@ class CalculationWorker(QObject):
|
|
|
822
823
|
w_id = options.get("worker_id")
|
|
823
824
|
|
|
824
825
|
def _check_halted() -> bool:
|
|
826
|
+
"""Check if current calculation worker has been requested to halt."""
|
|
825
827
|
h_ids = getattr(self, "halt_ids", None)
|
|
826
828
|
if self.halt_all:
|
|
827
829
|
return True # type: ignore[return-value]
|
|
@@ -836,18 +838,21 @@ class CalculationWorker(QObject):
|
|
|
836
838
|
# pylint: enable=unsupported-membership-test
|
|
837
839
|
|
|
838
840
|
def _safe_status(msg: str) -> None:
|
|
841
|
+
"""Emit calculation status update checking halt state."""
|
|
839
842
|
if _check_halted():
|
|
840
843
|
raise WorkerHaltError("Halted")
|
|
841
844
|
with suppress_log(AttributeError, RuntimeError):
|
|
842
845
|
self.status_update.emit(msg)
|
|
843
846
|
|
|
844
847
|
def _safe_finished(payload: Any) -> None:
|
|
848
|
+
"""Emit calculation finished signal checking halt state."""
|
|
845
849
|
if _check_halted():
|
|
846
850
|
raise WorkerHaltError("Halted")
|
|
847
851
|
with suppress_log(AttributeError, RuntimeError, TypeError):
|
|
848
852
|
self.finished.emit(payload)
|
|
849
853
|
|
|
850
854
|
def _safe_error(msg: str) -> None:
|
|
855
|
+
"""Emit calculation error signal handling graceful halt state."""
|
|
851
856
|
# If we're already halting, don't raise another error
|
|
852
857
|
if msg == "Halted":
|
|
853
858
|
with suppress_log(AttributeError, RuntimeError, TypeError):
|
|
@@ -38,6 +38,7 @@ class ChainMixin:
|
|
|
38
38
|
template_preview: Any
|
|
39
39
|
|
|
40
40
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
41
|
+
"""Initialize aliphatic chain drawing tool mixin."""
|
|
41
42
|
self.chain_anchor: Optional[QPointF] = None
|
|
42
43
|
self.chain_start_atom: Optional[AtomItem] = None
|
|
43
44
|
self.chain_end_atom: Optional[AtomItem] = None
|
|
@@ -37,6 +37,7 @@ class ColorSettingsDialog(QDialog):
|
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
39
|
def __init__(self, current_settings: Any, parent: Optional[QWidget] = None) -> None:
|
|
40
|
+
"""Initialize CPK element color customization dialog."""
|
|
40
41
|
super().__init__(parent)
|
|
41
42
|
self.setWindowTitle("CPK Colors")
|
|
42
43
|
self.parent_window: Any = parent
|
|
@@ -48,6 +48,7 @@ class ComputeManager:
|
|
|
48
48
|
"""Independent manager for molecular computations, ported from MainWindowCompute mixin."""
|
|
49
49
|
|
|
50
50
|
def __init__(self, host: MainWindow) -> None:
|
|
51
|
+
"""Initialize ComputeManager with reference to host window."""
|
|
51
52
|
self._calculating_text_actor: Any = None
|
|
52
53
|
self.original_atom_properties: Dict[int, int] = {}
|
|
53
54
|
self.host = host
|
|
@@ -180,6 +181,7 @@ class ComputeManager:
|
|
|
180
181
|
menu.exec(self.host.init_manager.convert_button.mapToGlobal(pos)) # type: ignore[union-attr]
|
|
181
182
|
|
|
182
183
|
def _trigger_conversion_with_temp_mode(self, mode_key: str) -> None:
|
|
184
|
+
"""Trigger 2D to 3D conversion with temporary mode override."""
|
|
183
185
|
# Store mode as instance attribute so it reaches trigger_conversion
|
|
184
186
|
# without passing a kwarg — plugins may wrap trigger_conversion without
|
|
185
187
|
# forwarding **kwargs, which would cause a TypeError.
|
|
@@ -203,6 +205,7 @@ class ComputeManager:
|
|
|
203
205
|
menu.exec(self.host.init_manager.optimize_3d_button.mapToGlobal(pos)) # type: ignore[union-attr]
|
|
204
206
|
|
|
205
207
|
def _trigger_optimize_with_temp_method(self, method_key: str) -> None:
|
|
208
|
+
"""Trigger 3D optimization with temporary method override."""
|
|
206
209
|
QTimer.singleShot(0, lambda: self.optimize_3d_structure(method_key))
|
|
207
210
|
|
|
208
211
|
def trigger_conversion(
|
|
@@ -429,6 +432,7 @@ class ComputeManager:
|
|
|
429
432
|
self.host.update_status_message(f"Process completed ({label}).")
|
|
430
433
|
|
|
431
434
|
def _prepare_rdkit_mol_for_conversion(self) -> Optional[Chem.Mol]:
|
|
435
|
+
"""Prepare and sanitize RDKit molecule for 3D conversion."""
|
|
432
436
|
mol = self.host.state_manager.data.to_rdkit_mol(use_2d_stereo=False)
|
|
433
437
|
if not mol or mol.GetNumAtoms() == 0:
|
|
434
438
|
self.check_chemistry_problems_fallback()
|
|
@@ -457,6 +461,7 @@ class ComputeManager:
|
|
|
457
461
|
def _handle_chemistry_problems(
|
|
458
462
|
self, mol: Chem.Mol, problems: Sequence[Any]
|
|
459
463
|
) -> None:
|
|
464
|
+
"""Handle chemical sanitization issues before conversion."""
|
|
460
465
|
self.host.init_manager.scene.clear_all_problem_flags()
|
|
461
466
|
msg = f"Error: {len(problems)} chemistry problem(s) found (e.g., hypervalency). Fix the 2D layout before converting."
|
|
462
467
|
self.host.statusBar().showMessage(msg) # type: ignore[union-attr]
|
|
@@ -498,6 +503,7 @@ class ComputeManager:
|
|
|
498
503
|
return None
|
|
499
504
|
|
|
500
505
|
def _setup_mol_block_for_worker(self, mol: Chem.Mol) -> str:
|
|
506
|
+
"""Construct molecule block data for background worker."""
|
|
501
507
|
ez_block = self._ez_consistent_mol_block()
|
|
502
508
|
if ez_block:
|
|
503
509
|
return ez_block
|
|
@@ -511,6 +517,7 @@ class ComputeManager:
|
|
|
511
517
|
def _start_calculation_worker(
|
|
512
518
|
self, mol_block: str, options: Dict[str, Any], run_id: int
|
|
513
519
|
) -> None:
|
|
520
|
+
"""Start background thread calculation worker."""
|
|
514
521
|
thread = QThread()
|
|
515
522
|
worker = CalculationWorker()
|
|
516
523
|
worker.halt_ids = self.halt_ids
|
|
@@ -518,6 +525,7 @@ class ComputeManager:
|
|
|
518
525
|
worker.status_update.connect(self.host.ui_manager.update_status_bar)
|
|
519
526
|
|
|
520
527
|
def _cleanup() -> None:
|
|
528
|
+
"""Clean up calculation worker thread references."""
|
|
521
529
|
thread.quit()
|
|
522
530
|
thread.finished.connect(thread.deleteLater)
|
|
523
531
|
worker.deleteLater()
|
|
@@ -525,12 +533,14 @@ class ComputeManager:
|
|
|
525
533
|
self._active_calc_threads.remove(thread)
|
|
526
534
|
|
|
527
535
|
def _on_finished(result: Any) -> None:
|
|
536
|
+
"""Handle successful background calculation completion."""
|
|
528
537
|
try:
|
|
529
538
|
self.on_calculation_finished(result)
|
|
530
539
|
finally:
|
|
531
540
|
_cleanup()
|
|
532
541
|
|
|
533
542
|
def _on_error(msg: Any) -> None:
|
|
543
|
+
"""Handle background calculation failure."""
|
|
534
544
|
try:
|
|
535
545
|
self.on_calculation_error(msg)
|
|
536
546
|
finally:
|
|
@@ -40,6 +40,7 @@ class ConstrainedOptimizationThread(QThread):
|
|
|
40
40
|
error_occurred = pyqtSignal(str)
|
|
41
41
|
|
|
42
42
|
def __init__(self, ff: Any, max_iters: int = 20000, parent: Any = None) -> None:
|
|
43
|
+
"""Initialize constrained optimization worker thread."""
|
|
43
44
|
super().__init__(parent)
|
|
44
45
|
self.ff = ff
|
|
45
46
|
self.max_iters = max_iters
|
|
@@ -57,6 +58,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
57
58
|
"""Dialog for constrained optimization."""
|
|
58
59
|
|
|
59
60
|
def __init__(self, mol: Any, main_window: Any, parent: Any = None) -> None:
|
|
61
|
+
"""Initialize constrained force field optimization dialog."""
|
|
60
62
|
QDialog.__init__(self, parent)
|
|
61
63
|
Dialog3DPickingMixin.__init__(self)
|
|
62
64
|
self.mol = mol
|
|
@@ -251,6 +253,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
251
253
|
layout.addLayout(main_buttons)
|
|
252
254
|
|
|
253
255
|
def on_atom_picked(self, atom_idx: int) -> None:
|
|
256
|
+
"""Handle atom selection event from 3D viewport."""
|
|
254
257
|
if atom_idx in self.selected_atoms:
|
|
255
258
|
self.selected_atoms.remove(atom_idx)
|
|
256
259
|
else:
|
|
@@ -603,6 +606,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
603
606
|
self.optimize_button.setEnabled(True)
|
|
604
607
|
|
|
605
608
|
def _on_optimization_error(self, err_msg: str) -> None:
|
|
609
|
+
"""Handle optimization thread failure callback."""
|
|
606
610
|
if self._closed:
|
|
607
611
|
logging.warning(
|
|
608
612
|
"Constrained optimization failed after dialog close: %s", err_msg
|
|
@@ -615,6 +619,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
615
619
|
QMessageBox.critical(self, "Error", f"Optimization error: {err_msg}")
|
|
616
620
|
|
|
617
621
|
def _on_optimization_finished(self, ff_name: str, conf: Any) -> None:
|
|
622
|
+
"""Handle optimization thread completion callback."""
|
|
618
623
|
if self._closed:
|
|
619
624
|
logging.info("Discarding constrained optimization result after close.")
|
|
620
625
|
return
|
|
@@ -72,6 +72,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
72
72
|
"""VTK interactor style extending trackball-camera with 3D atom drag and measurement."""
|
|
73
73
|
|
|
74
74
|
def __init__(self, main_window: Any = None, **kwargs: Any) -> None:
|
|
75
|
+
"""Initialize custom PyVista interactor style with mouse handlers."""
|
|
75
76
|
super().__init__(**kwargs)
|
|
76
77
|
self.main_window = main_window
|
|
77
78
|
# Custom state flags
|
|
@@ -416,6 +417,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
416
417
|
if type(move_group_dialog).__name__ == "MoveSelectedAtomsDialog":
|
|
417
418
|
# For MoveSelectedAtomsDialog, we toggle ONLY the clicked atom, no BFS!
|
|
418
419
|
def _deferred_toggle() -> None:
|
|
420
|
+
"""Toggle atom selection state in deferred callback."""
|
|
419
421
|
try:
|
|
420
422
|
move_group_dialog.on_atom_picked(clicked_atom_idx)
|
|
421
423
|
except (AttributeError, RuntimeError):
|
|
@@ -477,6 +479,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
477
479
|
move_group_dialog.selected_atoms.add(clicked_atom_idx)
|
|
478
480
|
|
|
479
481
|
def _deferred_move_group_update() -> None:
|
|
482
|
+
"""Update selected group translation in deferred callback."""
|
|
480
483
|
try:
|
|
481
484
|
move_group_dialog.show_atom_labels()
|
|
482
485
|
move_group_dialog.update_display()
|
|
@@ -527,6 +530,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
527
530
|
if True:
|
|
528
531
|
|
|
529
532
|
def _deferred_measure() -> None:
|
|
533
|
+
"""Measure distance between picked atoms in deferred callback."""
|
|
530
534
|
try:
|
|
531
535
|
mw.edit_3d_manager.handle_measurement_atom_selection(
|
|
532
536
|
closest_atom_idx
|
|
@@ -846,6 +850,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
846
850
|
_rt_mol = mw.view_3d_manager.current_mol
|
|
847
851
|
|
|
848
852
|
def _deferred_rt_atom() -> None:
|
|
853
|
+
"""Execute deferred real-time atom drag update."""
|
|
849
854
|
try:
|
|
850
855
|
mw.view_3d_manager.draw_molecule_3d(_rt_mol)
|
|
851
856
|
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
@@ -906,6 +911,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
906
911
|
_rt_dlg = move_group_dialog
|
|
907
912
|
|
|
908
913
|
def _deferred_rt_grp() -> None:
|
|
914
|
+
"""Execute deferred real-time group translation update."""
|
|
909
915
|
try:
|
|
910
916
|
mw.view_3d_manager.draw_molecule_3d(_rt_mol)
|
|
911
917
|
_rt_dlg.show_atom_labels()
|
|
@@ -1054,6 +1060,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
1054
1060
|
_rt_dlg = move_group_dialog
|
|
1055
1061
|
|
|
1056
1062
|
def _deferred_rt_rot() -> None:
|
|
1063
|
+
"""Execute deferred real-time group rotation update."""
|
|
1057
1064
|
try:
|
|
1058
1065
|
mw.view_3d_manager.draw_molecule_3d(_rt_mol)
|
|
1059
1066
|
_rt_dlg.show_atom_labels()
|
|
@@ -1161,6 +1168,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
1161
1168
|
_grp_dlg = move_group_dialog
|
|
1162
1169
|
|
|
1163
1170
|
def _deferred_group_redraw() -> None:
|
|
1171
|
+
"""Redraw moving group geometry in deferred callback."""
|
|
1164
1172
|
mw.view_3d_manager.draw_molecule_3d(_grp_mol)
|
|
1165
1173
|
mw.view_3d_manager.update_chiral_labels()
|
|
1166
1174
|
_grp_dlg.show_atom_labels()
|
|
@@ -1186,6 +1194,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
1186
1194
|
if not self._mouse_moved_during_drag and self._mouse_press_pos is not None:
|
|
1187
1195
|
# Background click: deselect
|
|
1188
1196
|
def _deferred_clear_move_group() -> None:
|
|
1197
|
+
"""Clear active move group selection in deferred callback."""
|
|
1189
1198
|
try:
|
|
1190
1199
|
move_group_dialog.group_atoms.clear()
|
|
1191
1200
|
move_group_dialog.selected_atoms.clear()
|
|
@@ -1205,6 +1214,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
1205
1214
|
):
|
|
1206
1215
|
# Background click -> clear selection
|
|
1207
1216
|
def _deferred_clear_measurement() -> None:
|
|
1217
|
+
"""Clear active measurement lines in deferred callback."""
|
|
1208
1218
|
try:
|
|
1209
1219
|
mw.edit_3d_manager.clear_measurement_selection()
|
|
1210
1220
|
except (AttributeError, RuntimeError):
|
|
@@ -1314,6 +1324,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
1314
1324
|
_atom_mol = mw.view_3d_manager.current_mol
|
|
1315
1325
|
|
|
1316
1326
|
def _deferred_atom_redraw() -> None:
|
|
1327
|
+
"""Redraw moved atom geometry in deferred callback."""
|
|
1317
1328
|
try:
|
|
1318
1329
|
mw.view_3d_manager.draw_molecule_3d(_atom_mol)
|
|
1319
1330
|
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
@@ -1340,6 +1351,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
1340
1351
|
]
|
|
1341
1352
|
|
|
1342
1353
|
def _deferred_updates() -> None:
|
|
1354
|
+
"""Process pending deferred viewport updates."""
|
|
1343
1355
|
for fn in _update_calls:
|
|
1344
1356
|
try:
|
|
1345
1357
|
fn()
|
|
@@ -86,6 +86,7 @@ class Dialog3DPickingMixin(_MixinBase):
|
|
|
86
86
|
self.main_window.picking_consumed = True
|
|
87
87
|
|
|
88
88
|
def _deferred_pick() -> None:
|
|
89
|
+
"""Process deferred atom pick event in 3D viewport."""
|
|
89
90
|
try:
|
|
90
91
|
self.on_atom_picked(int(closest_atom_idx))
|
|
91
92
|
except (AttributeError, RuntimeError):
|
|
@@ -140,6 +141,7 @@ class Dialog3DPickingMixin(_MixinBase):
|
|
|
140
141
|
if hasattr(self, "clear_selection"):
|
|
141
142
|
|
|
142
143
|
def _deferred_clear() -> None:
|
|
144
|
+
"""Clear deferred picking state in 3D viewport."""
|
|
143
145
|
try:
|
|
144
146
|
self.clear_selection()
|
|
145
147
|
except (AttributeError, RuntimeError):
|
|
@@ -51,6 +51,7 @@ class DihedralDialog(GeometryBaseDialog):
|
|
|
51
51
|
preselected_atoms: Optional[Sequence[int]] = None,
|
|
52
52
|
parent: Optional[QWidget] = None,
|
|
53
53
|
) -> None:
|
|
54
|
+
"""Initialize dihedral angle adjustment dialog."""
|
|
54
55
|
super().__init__(mol, main_window, parent)
|
|
55
56
|
self._baseline_positions: Any = None
|
|
56
57
|
self._snapshot_positions = None
|
|
@@ -39,6 +39,7 @@ class Edit3DManager:
|
|
|
39
39
|
"""Independent manager for 3D editing logic, ported from MainWindowEdit3d mixin."""
|
|
40
40
|
|
|
41
41
|
def __init__(self, host: Any) -> None:
|
|
42
|
+
"""Initialize 3D geometric transformation and editing manager."""
|
|
42
43
|
self.host = host
|
|
43
44
|
# State variables previously held by mixin
|
|
44
45
|
self.measurement_mode = False
|
|
@@ -75,6 +75,7 @@ class Rotate2DDialog(QDialog):
|
|
|
75
75
|
def __init__(
|
|
76
76
|
self, parent: Optional[QWidget] = None, initial_angle: float = 0
|
|
77
77
|
) -> None:
|
|
78
|
+
"""Initialize 2D structure rotation dialog."""
|
|
78
79
|
super().__init__(parent)
|
|
79
80
|
self.setWindowTitle("Rotate 2D")
|
|
80
81
|
self.setFixedWidth(300)
|
|
@@ -122,6 +123,7 @@ class EditActionsManager:
|
|
|
122
123
|
"""Independent manager for molecular editing actions, ported from MainWindowEditActions mixin."""
|
|
123
124
|
|
|
124
125
|
def __init__(self, host: MainWindow) -> None:
|
|
126
|
+
"""Initialize EditActionsManager for clipboard and editing tools."""
|
|
125
127
|
self.dragged_atom_info = None
|
|
126
128
|
self.host = host
|
|
127
129
|
# State variables previously held by mixin
|
|
@@ -665,6 +667,7 @@ class EditActionsManager:
|
|
|
665
667
|
|
|
666
668
|
# Helper: determine bond_stereo for hydrogen
|
|
667
669
|
def _choose_stereo(i: int) -> int:
|
|
670
|
+
"""Determine stereo wedge or dash representation for bond."""
|
|
668
671
|
# 0: plain, 1: wedge, 2: dash, 3: plain, 4+: all plain
|
|
669
672
|
if i == 0:
|
|
670
673
|
return 0
|
|
@@ -1122,6 +1125,7 @@ class EditActionsManager:
|
|
|
1122
1125
|
problem_map = self._detect_chemistry_problems(mol)
|
|
1123
1126
|
|
|
1124
1127
|
def _ui_closure() -> None:
|
|
1128
|
+
"""Execute UI update closure callback."""
|
|
1125
1129
|
self._apply_ui_h_counts(h_count_map, problem_map, my_token)
|
|
1126
1130
|
|
|
1127
1131
|
try:
|
|
@@ -1265,6 +1269,7 @@ class EditActionsManager:
|
|
|
1265
1269
|
from moleditpy.core.mol_geometry import resolve_2d_overlaps
|
|
1266
1270
|
|
|
1267
1271
|
def has_bond_check(id1: int, id2: int) -> bool:
|
|
1272
|
+
"""Check if bond already exists between atom pair."""
|
|
1268
1273
|
item1 = self.host.init_manager.scene.atom_items.get(id1)
|
|
1269
1274
|
item2 = self.host.init_manager.scene.atom_items.get(id2)
|
|
1270
1275
|
return (
|
|
@@ -35,6 +35,7 @@ class ExportManager:
|
|
|
35
35
|
"""Independent manager for export logic, ported from MainWindowExport mixin."""
|
|
36
36
|
|
|
37
37
|
def __init__(self, host: Any) -> None:
|
|
38
|
+
"""Initialize ExportManager for file and scene exports."""
|
|
38
39
|
self.host = host
|
|
39
40
|
|
|
40
41
|
def _get_default_basename(self) -> str:
|