MoleditPy-linux 3.6.0__py3-none-any.whl → 3.6.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- moleditpy_linux/__init__.py +3 -4
- moleditpy_linux/ui/atom_picking.py +148 -1
- moleditpy_linux/ui/constrained_optimization_dialog.py +0 -3
- moleditpy_linux/ui/custom_interactor_style.py +9 -2
- moleditpy_linux/ui/edit_3d_logic.py +16 -20
- moleditpy_linux/ui/edit_actions_logic.py +3 -3
- moleditpy_linux/ui/molecular_scene_handler.py +23 -14
- moleditpy_linux/ui/molecule_scene.py +38 -26
- moleditpy_linux/ui/move_group_dialog.py +40 -18
- moleditpy_linux/ui/move_selected_atoms_dialog.py +206 -194
- moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +27 -0
- moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -1
- moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -1
- moleditpy_linux/utils/default_settings.py +1 -0
- {moleditpy_linux-3.6.0.dist-info → moleditpy_linux-3.6.2.dist-info}/METADATA +1 -1
- {moleditpy_linux-3.6.0.dist-info → moleditpy_linux-3.6.2.dist-info}/RECORD +20 -20
- {moleditpy_linux-3.6.0.dist-info → moleditpy_linux-3.6.2.dist-info}/WHEEL +0 -0
- {moleditpy_linux-3.6.0.dist-info → moleditpy_linux-3.6.2.dist-info}/entry_points.txt +0 -0
- {moleditpy_linux-3.6.0.dist-info → moleditpy_linux-3.6.2.dist-info}/licenses/LICENSE +0 -0
- {moleditpy_linux-3.6.0.dist-info → moleditpy_linux-3.6.2.dist-info}/top_level.txt +0 -0
moleditpy_linux/__init__.py
CHANGED
|
@@ -8,15 +8,14 @@ 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
11
|
|
|
13
|
-
|
|
12
|
+
Top-level package for moleditpy_linux.
|
|
13
|
+
"""
|
|
14
14
|
|
|
15
15
|
import importlib.util
|
|
16
|
+
from .utils.constants import VERSION as __version__ # noqa: F401
|
|
16
17
|
|
|
17
18
|
try:
|
|
18
19
|
OBABEL_AVAILABLE = False
|
|
19
20
|
except ImportError:
|
|
20
21
|
OBABEL_AVAILABLE = False
|
|
21
|
-
|
|
22
|
-
from .utils.constants import VERSION as __version__ # noqa: F401
|
|
@@ -13,6 +13,7 @@ DOI: 10.5281/zenodo.17268532
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
15
|
from typing import Any, Optional
|
|
16
|
+
import logging
|
|
16
17
|
|
|
17
18
|
import numpy as np
|
|
18
19
|
|
|
@@ -101,7 +102,7 @@ def _projected_radius_px(
|
|
|
101
102
|
return radius_px
|
|
102
103
|
|
|
103
104
|
|
|
104
|
-
def
|
|
105
|
+
def pick_atom_index_from_screen_sequential(
|
|
105
106
|
view_3d_manager: Any,
|
|
106
107
|
click_pos: tuple[int, int],
|
|
107
108
|
mol: Optional[Any] = None,
|
|
@@ -161,3 +162,149 @@ def pick_atom_index_from_screen(
|
|
|
161
162
|
best_score = score
|
|
162
163
|
|
|
163
164
|
return best_idx
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def pick_atom_index_from_screen_vectorized(
|
|
168
|
+
view_3d_manager: Any,
|
|
169
|
+
click_pos: tuple[int, int],
|
|
170
|
+
mol: Optional[Any] = None,
|
|
171
|
+
padding_px: float = 8.0,
|
|
172
|
+
min_radius_px: float = 14.0,
|
|
173
|
+
max_radius_px: float = 96.0,
|
|
174
|
+
) -> Optional[int]:
|
|
175
|
+
"""
|
|
176
|
+
Vectorized atom picking using the camera's projection matrix.
|
|
177
|
+
Eliminates the O(N) Python loop and VTK C++ boundary calls.
|
|
178
|
+
"""
|
|
179
|
+
try:
|
|
180
|
+
plotter = view_3d_manager.plotter
|
|
181
|
+
renderer = plotter.renderer
|
|
182
|
+
positions = view_3d_manager.atom_positions_3d
|
|
183
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
if positions is None or len(positions) == 0:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
positions_array = np.asarray(positions, dtype=float) # Shape: (N, 3)
|
|
191
|
+
except (TypeError, ValueError):
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
if positions_array.ndim != 2 or positions_array.shape[1] < 3:
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
if mol is None:
|
|
198
|
+
mol = getattr(view_3d_manager, "current_mol", None)
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
atom_count = int(mol.GetNumAtoms()) if mol is not None else len(positions_array)
|
|
202
|
+
except (AttributeError, RuntimeError, TypeError, ValueError):
|
|
203
|
+
atom_count = len(positions_array)
|
|
204
|
+
|
|
205
|
+
active_atoms = min(atom_count, len(positions_array))
|
|
206
|
+
if active_atoms == 0:
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
positions_array = positions_array[:active_atoms]
|
|
210
|
+
|
|
211
|
+
# 1. Retrieve the View-Projection (Composite) Matrix from the active camera
|
|
212
|
+
try:
|
|
213
|
+
camera = renderer.GetActiveCamera()
|
|
214
|
+
aspect_ratio = renderer.GetTiledAspectRatio()
|
|
215
|
+
# Get the 4x4 composite projection matrix (vtkMatrix4x4)
|
|
216
|
+
vtk_matrix = camera.GetCompositeProjectionTransformMatrix(aspect_ratio, -1, 1)
|
|
217
|
+
|
|
218
|
+
# Convert vtkMatrix4x4 to a NumPy 4x4 array
|
|
219
|
+
matrix = np.zeros((4, 4))
|
|
220
|
+
for i in range(4):
|
|
221
|
+
for j in range(4):
|
|
222
|
+
matrix[i, j] = vtk_matrix.GetElement(i, j)
|
|
223
|
+
except Exception:
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
# 2. Convert all N world coordinates to homogeneous coordinates (N, 4)
|
|
227
|
+
homogeneous_coords = np.hstack([positions_array, np.ones((active_atoms, 1))])
|
|
228
|
+
|
|
229
|
+
# 3. Apply Matrix Multiplication: (N, 4) x (4, 4).T -> Clip Space Coordinates
|
|
230
|
+
clip_coords = homogeneous_coords @ matrix.T
|
|
231
|
+
|
|
232
|
+
# 4. Perform Perspective Divide -> Normalized Device Coordinates (NDC)
|
|
233
|
+
w = clip_coords[:, 3:4]
|
|
234
|
+
w_copy = np.copy(w)
|
|
235
|
+
w_copy[np.abs(w_copy) < 1e-5] = 1.0
|
|
236
|
+
ndc_coords = clip_coords[:, :3] / w_copy
|
|
237
|
+
|
|
238
|
+
# 5. Transform NDC to Screen/Display Coordinates
|
|
239
|
+
try:
|
|
240
|
+
size = renderer.GetSize() # (width, height)
|
|
241
|
+
except Exception:
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
# VTK display space coordinates: X: [0, W], Y: [0, H]
|
|
245
|
+
display_coords = np.zeros((active_atoms, 2))
|
|
246
|
+
display_coords[:, 0] = (ndc_coords[:, 0] + 1.0) * 0.5 * size[0]
|
|
247
|
+
display_coords[:, 1] = (ndc_coords[:, 1] + 1.0) * 0.5 * size[1]
|
|
248
|
+
|
|
249
|
+
# 6. Vectorized Distance and Hit Radius Calculation
|
|
250
|
+
dx = display_coords[:, 0] - click_pos[0]
|
|
251
|
+
dy = display_coords[:, 1] - click_pos[1]
|
|
252
|
+
distances = np.hypot(dx, dy)
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
if camera.GetParallelProjection():
|
|
256
|
+
pixel_scale = size[1] / (2.0 * camera.GetParallelScale())
|
|
257
|
+
else:
|
|
258
|
+
view_angle_rad = np.radians(camera.GetViewAngle())
|
|
259
|
+
pixel_scale = size[1] / (
|
|
260
|
+
2.0 * np.abs(w.flatten()) * np.tan(view_angle_rad / 2.0)
|
|
261
|
+
)
|
|
262
|
+
except Exception:
|
|
263
|
+
pixel_scale = 20.0 # Safe fallback scale
|
|
264
|
+
|
|
265
|
+
# Pre-calculate world radii for all atoms
|
|
266
|
+
world_radii = np.array(
|
|
267
|
+
[_atom_world_radius(view_3d_manager, mol, idx) for idx in range(active_atoms)]
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
projected_radii = world_radii * pixel_scale
|
|
271
|
+
hit_radii = np.maximum(
|
|
272
|
+
min_radius_px, np.minimum(max_radius_px, projected_radii + padding_px)
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# 7. Mask out atoms that are further than their hit radius
|
|
276
|
+
valid_mask = distances <= hit_radii
|
|
277
|
+
if not np.any(valid_mask):
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
# 8. Score calculation and find the best index
|
|
281
|
+
# Tie-breaking logic: (ratio) + (distances * 1e-8)
|
|
282
|
+
scores = (distances / hit_radii) + (distances * 1e-8)
|
|
283
|
+
|
|
284
|
+
scores[~valid_mask] = np.inf
|
|
285
|
+
best_idx = int(np.argmin(scores))
|
|
286
|
+
|
|
287
|
+
return best_idx if scores[best_idx] != np.inf else None
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def pick_atom_index_from_screen(
|
|
291
|
+
view_3d_manager: Any,
|
|
292
|
+
click_pos: tuple[int, int],
|
|
293
|
+
mol: Optional[Any] = None,
|
|
294
|
+
padding_px: float = 8.0,
|
|
295
|
+
min_radius_px: float = 14.0,
|
|
296
|
+
max_radius_px: float = 96.0,
|
|
297
|
+
) -> Optional[int]:
|
|
298
|
+
"""Return the atom nearest a screen click, trying vectorized first, falling back to sequential."""
|
|
299
|
+
try:
|
|
300
|
+
best_idx = pick_atom_index_from_screen_vectorized(
|
|
301
|
+
view_3d_manager, click_pos, mol, padding_px, min_radius_px, max_radius_px
|
|
302
|
+
)
|
|
303
|
+
if best_idx is not None:
|
|
304
|
+
return best_idx
|
|
305
|
+
except Exception as e:
|
|
306
|
+
logging.debug("Vectorized picking failed, falling back to sequential: %s", e)
|
|
307
|
+
|
|
308
|
+
return pick_atom_index_from_screen_sequential(
|
|
309
|
+
view_3d_manager, click_pos, mol, padding_px, min_radius_px, max_radius_px
|
|
310
|
+
)
|
|
@@ -405,7 +405,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
405
405
|
dx = current_pos[0] - move_group_dialog._drag_start_pos[0]
|
|
406
406
|
dy = current_pos[1] - move_group_dialog._drag_start_pos[1]
|
|
407
407
|
|
|
408
|
-
if abs(dx) >
|
|
408
|
+
if abs(dx) > 5 or abs(dy) > 5:
|
|
409
409
|
move_group_dialog._mouse_moved = True
|
|
410
410
|
|
|
411
411
|
return # Disable camera rotation
|
|
@@ -423,7 +423,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
423
423
|
dx = current_pos[0] - move_group_dialog._rotation_start_pos[0]
|
|
424
424
|
dy = current_pos[1] - move_group_dialog._rotation_start_pos[1]
|
|
425
425
|
|
|
426
|
-
if abs(dx) >
|
|
426
|
+
if abs(dx) > 5 or abs(dy) > 5:
|
|
427
427
|
move_group_dialog._rotation_mouse_moved = True
|
|
428
428
|
|
|
429
429
|
return # Disable camera rotation
|
|
@@ -493,6 +493,13 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
493
493
|
if getattr(
|
|
494
494
|
move_group_dialog, "_is_dragging_group_vtk", False
|
|
495
495
|
) and not getattr(move_group_dialog, "_mouse_moved", False):
|
|
496
|
+
# No drag = click only -> toggle
|
|
497
|
+
clicked_atom = getattr(move_group_dialog, "_drag_atom_idx", None)
|
|
498
|
+
if clicked_atom is not None:
|
|
499
|
+
try:
|
|
500
|
+
move_group_dialog.on_atom_picked(clicked_atom)
|
|
501
|
+
except (AttributeError, RuntimeError, TypeError, ValueError) as e:
|
|
502
|
+
logging.error(f"Error in toggle: {e}")
|
|
496
503
|
# Reset if multi-clicked without drag
|
|
497
504
|
move_group_dialog._is_dragging_group_vtk = False
|
|
498
505
|
move_group_dialog._drag_start_pos = None
|
|
@@ -12,15 +12,25 @@ DOI: 10.5281/zenodo.17268532
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Mixin class separated from main_window.py
|
|
18
|
-
"""
|
|
15
|
+
# main_window_edit_3d.py
|
|
16
|
+
# Mixin class separated from main_window.py
|
|
19
17
|
|
|
20
18
|
import logging
|
|
21
19
|
from typing import Any, List, Optional
|
|
22
20
|
|
|
23
21
|
import numpy as np
|
|
22
|
+
import pyvista as pv
|
|
23
|
+
from PyQt6.QtCore import QPointF
|
|
24
|
+
from PyQt6.QtGui import QColor, QFont
|
|
25
|
+
from PyQt6.QtWidgets import QGraphicsTextItem
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
from PyQt6 import sip as _sip # type: ignore
|
|
29
|
+
|
|
30
|
+
_sip_isdeleted = getattr(_sip, "isdeleted", None)
|
|
31
|
+
except ImportError:
|
|
32
|
+
_sip = None # type: ignore[assignment]
|
|
33
|
+
_sip_isdeleted = None
|
|
24
34
|
|
|
25
35
|
try:
|
|
26
36
|
from .mol_geometry import (
|
|
@@ -41,20 +51,6 @@ try:
|
|
|
41
51
|
except ImportError:
|
|
42
52
|
from moleditpy_linux.utils.sip_isdeleted_safe import sip_isdeleted_safe
|
|
43
53
|
|
|
44
|
-
# PyQt6 Modules
|
|
45
|
-
import pyvista as pv
|
|
46
|
-
from PyQt6.QtCore import QPointF
|
|
47
|
-
from PyQt6.QtGui import QColor, QFont
|
|
48
|
-
from PyQt6.QtWidgets import QGraphicsTextItem
|
|
49
|
-
|
|
50
|
-
try:
|
|
51
|
-
from PyQt6 import sip as _sip # type: ignore
|
|
52
|
-
|
|
53
|
-
_sip_isdeleted = getattr(_sip, "isdeleted", None)
|
|
54
|
-
except ImportError:
|
|
55
|
-
_sip = None # type: ignore[assignment]
|
|
56
|
-
_sip_isdeleted = None
|
|
57
|
-
|
|
58
54
|
try:
|
|
59
55
|
# package relative imports (preferred when running as `python -m moleditpy`)
|
|
60
56
|
from .constants import VDW_RADII
|
|
@@ -169,8 +165,8 @@ class Edit3DManager:
|
|
|
169
165
|
return
|
|
170
166
|
|
|
171
167
|
# Prepare label positions and text
|
|
172
|
-
atom_indices = [
|
|
173
|
-
labels = [
|
|
168
|
+
atom_indices = [item[0] for item in self.measurement_labels]
|
|
169
|
+
labels = [item[1] for item in self.measurement_labels]
|
|
174
170
|
positions = []
|
|
175
171
|
texts = []
|
|
176
172
|
positions_3d = self.host.view_3d_manager.atom_positions_3d
|
|
@@ -1443,9 +1443,9 @@ class EditActionsManager:
|
|
|
1443
1443
|
pos_i = positions_i[k]
|
|
1444
1444
|
vdw_i = vdw_i_all[k]
|
|
1445
1445
|
|
|
1446
|
-
for
|
|
1447
|
-
pos_j = positions_j[
|
|
1448
|
-
vdw_j = vdw_j_all[
|
|
1446
|
+
for idx, _ in enumerate(frag_j["indices"]):
|
|
1447
|
+
pos_j = positions_j[idx]
|
|
1448
|
+
vdw_j = vdw_j_all[idx]
|
|
1449
1449
|
|
|
1450
1450
|
distance_vec = pos_i - pos_j
|
|
1451
1451
|
distance_sq = np.dot(
|
|
@@ -32,11 +32,10 @@ except ImportError:
|
|
|
32
32
|
from moleditpy_linux.utils.sip_isdeleted_safe import sip_isdeleted_safe
|
|
33
33
|
|
|
34
34
|
try:
|
|
35
|
-
from ..utils.constants import DEFAULT_BOND_LENGTH,
|
|
35
|
+
from ..utils.constants import DEFAULT_BOND_LENGTH, SUM_TOLERANCE
|
|
36
36
|
except ImportError:
|
|
37
37
|
from moleditpy_linux.utils.constants import (
|
|
38
38
|
DEFAULT_BOND_LENGTH,
|
|
39
|
-
SNAP_DISTANCE,
|
|
40
39
|
SUM_TOLERANCE,
|
|
41
40
|
)
|
|
42
41
|
|
|
@@ -724,7 +723,9 @@ class KeyboardMixin:
|
|
|
724
723
|
|
|
725
724
|
temp_line: Optional[QGraphicsLineItem]
|
|
726
725
|
|
|
727
|
-
def _calculate_new_atom_position(
|
|
726
|
+
def _calculate_new_atom_position(
|
|
727
|
+
self, start_atom: Any, bond_length: Any, target_order: int = 1
|
|
728
|
+
) -> Any:
|
|
728
729
|
"""
|
|
729
730
|
Calculate the position for a new atom based on the surroundings of start_atom.
|
|
730
731
|
Returns the offset QPointF.
|
|
@@ -752,8 +753,12 @@ class KeyboardMixin:
|
|
|
752
753
|
other_atom = bond.atom1 if bond.atom2 is start_atom else bond.atom2
|
|
753
754
|
existing_bond_vector = start_pos - other_atom.pos()
|
|
754
755
|
|
|
755
|
-
# Rotate 60° clockwise from existing bond
|
|
756
|
-
|
|
756
|
+
# Rotate 60° clockwise/anticlockwise from existing bond (or 0°/180° straight continuation for alkyne)
|
|
757
|
+
is_clockwise = getattr(self, "placement_direction_clockwise", True)
|
|
758
|
+
angle_deg = 60 if is_clockwise else -60
|
|
759
|
+
if target_order == 3 or getattr(bond, "order", 1) == 3:
|
|
760
|
+
angle_deg = 0
|
|
761
|
+
angle_rad = math.radians(angle_deg)
|
|
757
762
|
cos_a, sin_a = math.cos(angle_rad), math.sin(angle_rad)
|
|
758
763
|
vx, vy = existing_bond_vector.x(), existing_bond_vector.y()
|
|
759
764
|
new_vx, new_vy = vx * cos_a - vy * sin_a, vx * sin_a + vy * cos_a
|
|
@@ -820,9 +825,9 @@ class KeyboardMixin:
|
|
|
820
825
|
if key == Qt.Key.Key_4:
|
|
821
826
|
snap_dist = self.get_setting("template_snapping_distance_2d", 14.0)
|
|
822
827
|
item_at_cursor = self.find_atom_near(cursor_pos, tol=snap_dist)
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
item_at_cursor = self.find_atom_near(cursor_pos, tol=
|
|
828
|
+
else:
|
|
829
|
+
snap_dist = self.get_setting("bond_snapping_distance_2d", 14.0)
|
|
830
|
+
item_at_cursor = self.find_atom_near(cursor_pos, tol=snap_dist)
|
|
826
831
|
if item_at_cursor is None:
|
|
827
832
|
item_at_cursor = self.itemAt(cursor_pos, transform)
|
|
828
833
|
|
|
@@ -1160,7 +1165,7 @@ class KeyboardMixin:
|
|
|
1160
1165
|
start_pos = start_atom.pos()
|
|
1161
1166
|
bond_len = DEFAULT_BOND_LENGTH
|
|
1162
1167
|
new_pos_offset = self._calculate_new_atom_position(
|
|
1163
|
-
start_atom, bond_len
|
|
1168
|
+
start_atom, bond_len, target_order
|
|
1164
1169
|
)
|
|
1165
1170
|
|
|
1166
1171
|
# SNAP_DISTANCE is a module-level constant
|
|
@@ -1168,11 +1173,8 @@ class KeyboardMixin:
|
|
|
1168
1173
|
|
|
1169
1174
|
# Find nearby atom
|
|
1170
1175
|
near_atom = None
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
"template_fusing_distance_2d", SNAP_DISTANCE
|
|
1174
|
-
)
|
|
1175
|
-
near_atom = self.find_atom_near(target_pos, tol=fuse_dist)
|
|
1176
|
+
snap_dist = self.get_setting("bond_snapping_distance_2d", 14.0)
|
|
1177
|
+
near_atom = self.find_atom_near(target_pos, tol=snap_dist)
|
|
1176
1178
|
|
|
1177
1179
|
if near_atom and near_atom is not start_atom:
|
|
1178
1180
|
# Bond if exists
|
|
@@ -1193,6 +1195,13 @@ class KeyboardMixin:
|
|
|
1193
1195
|
bond_stereo=0,
|
|
1194
1196
|
)
|
|
1195
1197
|
|
|
1198
|
+
if target_order != 3:
|
|
1199
|
+
if hasattr(self, "placement_direction_clockwise"):
|
|
1200
|
+
self.placement_direction_clockwise = (
|
|
1201
|
+
not self.placement_direction_clockwise
|
|
1202
|
+
)
|
|
1203
|
+
else:
|
|
1204
|
+
self.placement_direction_clockwise = False
|
|
1196
1205
|
self.clearSelection()
|
|
1197
1206
|
self.update_all_items()
|
|
1198
1207
|
self.window.edit_actions_manager.push_undo_state()
|
|
@@ -177,6 +177,11 @@ class MoleculeScene(TemplateMixin, KeyboardMixin, SceneQueryMixin, QGraphicsScen
|
|
|
177
177
|
|
|
178
178
|
def mousePressEvent(self, event: Any) -> None:
|
|
179
179
|
self.press_pos = event.scenePos()
|
|
180
|
+
self.was_selected_on_press = False
|
|
181
|
+
if self.mode == "select" and event.button() == Qt.MouseButton.LeftButton:
|
|
182
|
+
item = self.itemAt(self.press_pos, self.views()[0].transform())
|
|
183
|
+
if isinstance(item, AtomItem) and item.isSelected():
|
|
184
|
+
self.was_selected_on_press = True
|
|
180
185
|
self.mouse_moved_since_press = False
|
|
181
186
|
self.data_changed_in_event = False
|
|
182
187
|
|
|
@@ -239,7 +244,7 @@ class MoleculeScene(TemplateMixin, KeyboardMixin, SceneQueryMixin, QGraphicsScen
|
|
|
239
244
|
if hasattr(item, "stereo") and item.stereo in [3, 4]:
|
|
240
245
|
item.set_stereo(0)
|
|
241
246
|
# Also update the data model
|
|
242
|
-
for
|
|
247
|
+
for bdata in self.data.bonds.values():
|
|
243
248
|
if bdata.get("item") is item:
|
|
244
249
|
bdata["stereo"] = 0
|
|
245
250
|
break
|
|
@@ -306,14 +311,14 @@ class MoleculeScene(TemplateMixin, KeyboardMixin, SceneQueryMixin, QGraphicsScen
|
|
|
306
311
|
event.accept()
|
|
307
312
|
|
|
308
313
|
item = None
|
|
309
|
-
if (
|
|
310
|
-
self.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
314
|
+
if self.mode.startswith("bond") and self.press_pos:
|
|
315
|
+
snap_dist = self.get_setting("bond_snapping_distance_2d", 14.0)
|
|
316
|
+
item = self.find_atom_near(self.press_pos, tol=snap_dist)
|
|
317
|
+
if item is None:
|
|
318
|
+
candidate = self.itemAt(self.press_pos, self.views()[0].transform())
|
|
319
|
+
if not isinstance(candidate, AtomItem):
|
|
320
|
+
item = candidate
|
|
321
|
+
else:
|
|
317
322
|
item = self.itemAt(self.press_pos, self.views()[0].transform())
|
|
318
323
|
|
|
319
324
|
if isinstance(item, AtomItem):
|
|
@@ -360,14 +365,9 @@ class MoleculeScene(TemplateMixin, KeyboardMixin, SceneQueryMixin, QGraphicsScen
|
|
|
360
365
|
end_point = current_pos
|
|
361
366
|
|
|
362
367
|
target_atom = None
|
|
363
|
-
if
|
|
364
|
-
|
|
365
|
-
target_atom = self.find_atom_near(current_pos, tol=
|
|
366
|
-
else:
|
|
367
|
-
for item in self.items(current_pos):
|
|
368
|
-
if isinstance(item, AtomItem):
|
|
369
|
-
target_atom = item
|
|
370
|
-
break
|
|
368
|
+
if current_pos:
|
|
369
|
+
snap_dist = self.get_setting("bond_snapping_distance_2d", 14.0)
|
|
370
|
+
target_atom = self.find_atom_near(current_pos, tol=snap_dist)
|
|
371
371
|
|
|
372
372
|
is_valid_snap_target = target_atom is not None and (
|
|
373
373
|
self.start_atom is None or target_atom is not self.start_atom
|
|
@@ -387,7 +387,7 @@ class MoleculeScene(TemplateMixin, KeyboardMixin, SceneQueryMixin, QGraphicsScen
|
|
|
387
387
|
|
|
388
388
|
end_pos = event.scenePos()
|
|
389
389
|
is_click = (
|
|
390
|
-
self.press_pos
|
|
390
|
+
self.press_pos is not None
|
|
391
391
|
and (end_pos - self.press_pos).manhattanLength()
|
|
392
392
|
< QApplication.startDragDistance()
|
|
393
393
|
)
|
|
@@ -557,11 +557,13 @@ class MoleculeScene(TemplateMixin, KeyboardMixin, SceneQueryMixin, QGraphicsScen
|
|
|
557
557
|
):
|
|
558
558
|
line = QLineF(self.start_atom.pos(), end_pos)
|
|
559
559
|
end_item = None
|
|
560
|
-
if
|
|
561
|
-
|
|
562
|
-
end_item = self.find_atom_near(end_pos, tol=
|
|
560
|
+
if end_pos:
|
|
561
|
+
snap_dist = self.get_setting("bond_snapping_distance_2d", 14.0)
|
|
562
|
+
end_item = self.find_atom_near(end_pos, tol=snap_dist)
|
|
563
563
|
if end_item is None:
|
|
564
|
-
|
|
564
|
+
candidate = self.itemAt(end_pos, self.views()[0].transform())
|
|
565
|
+
if not isinstance(candidate, AtomItem):
|
|
566
|
+
end_item = candidate
|
|
565
567
|
# Determine bond style to use
|
|
566
568
|
# In atom modes, set bond_order/stereo to None so create_bond uses defaults (1, 0)
|
|
567
569
|
# In bond_* modes, use current settings (self.bond_order/stereo)
|
|
@@ -611,11 +613,13 @@ class MoleculeScene(TemplateMixin, KeyboardMixin, SceneQueryMixin, QGraphicsScen
|
|
|
611
613
|
self.data_changed_in_event = True
|
|
612
614
|
else:
|
|
613
615
|
end_item = None
|
|
614
|
-
if
|
|
615
|
-
|
|
616
|
-
end_item = self.find_atom_near(end_pos, tol=
|
|
616
|
+
if end_pos:
|
|
617
|
+
snap_dist = self.get_setting("bond_snapping_distance_2d", 14.0)
|
|
618
|
+
end_item = self.find_atom_near(end_pos, tol=snap_dist)
|
|
617
619
|
if end_item is None:
|
|
618
|
-
|
|
620
|
+
candidate = self.itemAt(end_pos, self.views()[0].transform())
|
|
621
|
+
if not isinstance(candidate, AtomItem):
|
|
622
|
+
end_item = candidate
|
|
619
623
|
if isinstance(end_item, AtomItem):
|
|
620
624
|
start_id = self.create_atom(
|
|
621
625
|
self.current_atom_symbol, self.start_pos
|
|
@@ -642,6 +646,14 @@ class MoleculeScene(TemplateMixin, KeyboardMixin, SceneQueryMixin, QGraphicsScen
|
|
|
642
646
|
# 5. Other processing (Select mode, etc.)
|
|
643
647
|
else:
|
|
644
648
|
super().mouseReleaseEvent(event)
|
|
649
|
+
if (
|
|
650
|
+
self.mode == "select"
|
|
651
|
+
and is_click
|
|
652
|
+
and getattr(self, "was_selected_on_press", False)
|
|
653
|
+
):
|
|
654
|
+
released_item = self.itemAt(end_pos, self.views()[0].transform())
|
|
655
|
+
if isinstance(released_item, AtomItem):
|
|
656
|
+
released_item.setSelected(False)
|
|
645
657
|
|
|
646
658
|
# Safely check for moved objects
|
|
647
659
|
moved_atoms = []
|
|
@@ -288,10 +288,10 @@ class MoveGroupDialog(BasePickingDialog):
|
|
|
288
288
|
RuntimeError,
|
|
289
289
|
ValueError,
|
|
290
290
|
TypeError,
|
|
291
|
-
):
|
|
292
|
-
|
|
293
|
-
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
294
|
-
|
|
291
|
+
) as e:
|
|
292
|
+
logging.debug(f"Failed to set closed hand cursor: {e}")
|
|
293
|
+
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
294
|
+
logging.debug(f"Error initiating drag on move: {e}")
|
|
295
295
|
|
|
296
296
|
if not self.is_dragging_group:
|
|
297
297
|
return False
|
|
@@ -305,10 +305,10 @@ class MoveGroupDialog(BasePickingDialog):
|
|
|
305
305
|
current_pos = interactor.GetEventPosition()
|
|
306
306
|
dx = current_pos[0] - self.drag_start_pos[0]
|
|
307
307
|
dy = current_pos[1] - self.drag_start_pos[1]
|
|
308
|
-
if abs(dx) >
|
|
308
|
+
if abs(dx) > 5 or abs(dy) > 5:
|
|
309
309
|
self.mouse_moved_during_drag = True
|
|
310
|
-
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
311
|
-
|
|
310
|
+
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
311
|
+
logging.debug(f"Error tracking drag movement: {e}")
|
|
312
312
|
return True
|
|
313
313
|
|
|
314
314
|
# Hover handling
|
|
@@ -329,8 +329,8 @@ class MoveGroupDialog(BasePickingDialog):
|
|
|
329
329
|
plotter_ref.setCursor(Qt.CursorShape.OpenHandCursor)
|
|
330
330
|
else:
|
|
331
331
|
plotter_ref.setCursor(Qt.CursorShape.ArrowCursor)
|
|
332
|
-
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
333
|
-
|
|
332
|
+
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
333
|
+
logging.debug(f"Error updating hover cursor: {e}")
|
|
334
334
|
|
|
335
335
|
return False
|
|
336
336
|
|
|
@@ -373,16 +373,18 @@ class MoveGroupDialog(BasePickingDialog):
|
|
|
373
373
|
RuntimeError,
|
|
374
374
|
ValueError,
|
|
375
375
|
TypeError,
|
|
376
|
-
):
|
|
377
|
-
|
|
376
|
+
) as e:
|
|
377
|
+
logging.debug(
|
|
378
|
+
f"Failed to reset cursor to arrow: {e}"
|
|
379
|
+
)
|
|
378
380
|
return True
|
|
379
381
|
else:
|
|
380
382
|
logging.error(
|
|
381
383
|
"REPORT ERROR: Missing attribute 'clicked_atom_for_toggle' on self"
|
|
382
384
|
)
|
|
383
385
|
|
|
384
|
-
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
385
|
-
|
|
386
|
+
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
387
|
+
logging.debug(f"Error in mouse release handling: {e}")
|
|
386
388
|
finally:
|
|
387
389
|
self.is_dragging_group = False
|
|
388
390
|
self.drag_start_pos = None
|
|
@@ -392,8 +394,15 @@ class MoveGroupDialog(BasePickingDialog):
|
|
|
392
394
|
plotter_ptr = self.main_window.view_3d_manager.plotter
|
|
393
395
|
if plotter_ptr is not None:
|
|
394
396
|
plotter_ptr.setCursor(Qt.CursorShape.ArrowCursor)
|
|
395
|
-
except (
|
|
396
|
-
|
|
397
|
+
except (
|
|
398
|
+
AttributeError,
|
|
399
|
+
RuntimeError,
|
|
400
|
+
ValueError,
|
|
401
|
+
TypeError,
|
|
402
|
+
) as e:
|
|
403
|
+
logging.debug(
|
|
404
|
+
f"Failed to reset cursor in release finally: {e}"
|
|
405
|
+
)
|
|
397
406
|
|
|
398
407
|
return True
|
|
399
408
|
|
|
@@ -428,10 +437,11 @@ class MoveGroupDialog(BasePickingDialog):
|
|
|
428
437
|
# Toggle group
|
|
429
438
|
if visited.issubset(self.group_atoms):
|
|
430
439
|
self.group_atoms -= visited
|
|
440
|
+
if atom_idx in self.selected_atoms:
|
|
441
|
+
self.selected_atoms.remove(atom_idx)
|
|
431
442
|
else:
|
|
432
443
|
self.group_atoms |= visited
|
|
433
|
-
|
|
434
|
-
self.selected_atoms.add(atom_idx)
|
|
444
|
+
self.selected_atoms.add(atom_idx)
|
|
435
445
|
self.show_atom_labels()
|
|
436
446
|
self.update_display()
|
|
437
447
|
|
|
@@ -450,13 +460,18 @@ class MoveGroupDialog(BasePickingDialog):
|
|
|
450
460
|
|
|
451
461
|
def show_atom_labels(self) -> None:
|
|
452
462
|
"""Highlight atoms in the selected group."""
|
|
463
|
+
plotter = self.main_window.view_3d_manager.plotter
|
|
464
|
+
try:
|
|
465
|
+
cam = plotter.camera_position if plotter else None
|
|
466
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
467
|
+
cam = None
|
|
468
|
+
|
|
453
469
|
self.clear_atom_labels()
|
|
454
470
|
|
|
455
471
|
if not self.group_atoms:
|
|
456
472
|
return
|
|
457
473
|
|
|
458
474
|
selected_indices = list(self.group_atoms)
|
|
459
|
-
plotter = self.main_window.view_3d_manager.plotter
|
|
460
475
|
if self.main_window.view_3d_manager.atom_positions_3d is None:
|
|
461
476
|
logging.error("atom_positions_3d is None in update_atom_labels")
|
|
462
477
|
return
|
|
@@ -486,8 +501,15 @@ class MoveGroupDialog(BasePickingDialog):
|
|
|
486
501
|
opacity=0.3,
|
|
487
502
|
name="move_group_highlight",
|
|
488
503
|
pickable=False,
|
|
504
|
+
reset_camera=False,
|
|
489
505
|
)
|
|
490
506
|
|
|
507
|
+
if cam is not None:
|
|
508
|
+
try:
|
|
509
|
+
plotter.camera_position = cam
|
|
510
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
511
|
+
pass
|
|
512
|
+
|
|
491
513
|
plotter.render()
|
|
492
514
|
|
|
493
515
|
def clear_atom_labels(self) -> None:
|
|
@@ -53,17 +53,15 @@ class MoveSelectedAtomsDialog(BasePickingDialog):
|
|
|
53
53
|
self.selected_atoms.update(preselected_atoms)
|
|
54
54
|
|
|
55
55
|
self.clicked_atom_for_toggle: Optional[int] = None
|
|
56
|
+
# State for group movement (used by dialog's own event filter)
|
|
57
|
+
self.drag_atom_idx: Optional[int] = None
|
|
58
|
+
self.potential_drag: bool = False
|
|
59
|
+
self.is_dragging_group: bool = False
|
|
60
|
+
self.drag_start_pos: Optional[Any] = None
|
|
61
|
+
self.mouse_moved_during_drag: bool = False
|
|
62
|
+
self._consume_next_left_release: bool = False
|
|
56
63
|
self.highlight_actor: Optional[pv.Actor] = None
|
|
57
64
|
|
|
58
|
-
# Grouped states to comply with Pylint instance attribute limit
|
|
59
|
-
self.drag_state: dict[str, Any] = {
|
|
60
|
-
"drag_atom_idx": None,
|
|
61
|
-
"potential_drag": False,
|
|
62
|
-
"is_dragging_group": False,
|
|
63
|
-
"drag_start_pos": (0, 0),
|
|
64
|
-
"mouse_moved_during_drag": False,
|
|
65
|
-
"consume_next_left_release": False,
|
|
66
|
-
}
|
|
67
65
|
self.widgets: dict[str, Any] = {}
|
|
68
66
|
|
|
69
67
|
self.init_ui()
|
|
@@ -231,180 +229,6 @@ class MoveSelectedAtomsDialog(BasePickingDialog):
|
|
|
231
229
|
|
|
232
230
|
layout.addLayout(button_layout)
|
|
233
231
|
|
|
234
|
-
def _handle_double_click(self) -> bool:
|
|
235
|
-
"""Handle MouseButtonDblClick event."""
|
|
236
|
-
self.drag_state["is_dragging_group"] = False
|
|
237
|
-
self.drag_state["drag_start_pos"] = (0, 0)
|
|
238
|
-
self.drag_state["mouse_moved_during_drag"] = False
|
|
239
|
-
self.drag_state["potential_drag"] = False
|
|
240
|
-
self.clicked_atom_for_toggle = None
|
|
241
|
-
return False
|
|
242
|
-
|
|
243
|
-
def _handle_mouse_press(self, plotter: Any) -> bool:
|
|
244
|
-
"""Handle MouseButtonPress event for LeftButton."""
|
|
245
|
-
self.drag_state["is_dragging_group"] = False
|
|
246
|
-
self.drag_state["potential_drag"] = False
|
|
247
|
-
self.clicked_atom_for_toggle = None
|
|
248
|
-
|
|
249
|
-
if self.selected_atoms:
|
|
250
|
-
return False
|
|
251
|
-
|
|
252
|
-
try:
|
|
253
|
-
interactor = plotter.interactor
|
|
254
|
-
if interactor is None:
|
|
255
|
-
return False
|
|
256
|
-
click_pos = interactor.GetEventPosition()
|
|
257
|
-
|
|
258
|
-
clicked_atom_idx = pick_atom_index_from_screen(
|
|
259
|
-
self.main_window.view_3d_manager,
|
|
260
|
-
(int(click_pos[0]), int(click_pos[1])),
|
|
261
|
-
self.mol,
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
if clicked_atom_idx is not None:
|
|
265
|
-
if self.selected_atoms and clicked_atom_idx in self.selected_atoms:
|
|
266
|
-
self.drag_state["is_dragging_group"] = False
|
|
267
|
-
self.drag_state["drag_start_pos"] = click_pos
|
|
268
|
-
self.drag_state["drag_atom_idx"] = clicked_atom_idx
|
|
269
|
-
self.drag_state["mouse_moved_during_drag"] = False
|
|
270
|
-
self.drag_state["potential_drag"] = True
|
|
271
|
-
self.clicked_atom_for_toggle = clicked_atom_idx
|
|
272
|
-
return False
|
|
273
|
-
|
|
274
|
-
self.on_atom_picked(clicked_atom_idx)
|
|
275
|
-
self.drag_state["consume_next_left_release"] = True
|
|
276
|
-
return True
|
|
277
|
-
|
|
278
|
-
return False
|
|
279
|
-
|
|
280
|
-
except (AttributeError, RuntimeError, ValueError) as e:
|
|
281
|
-
logging.debug("Error in mouse press: %s", e)
|
|
282
|
-
return False
|
|
283
|
-
|
|
284
|
-
def _handle_potential_drag(self, plotter: Any) -> bool:
|
|
285
|
-
"""Handle potential drag checking and threshold transition."""
|
|
286
|
-
start_pos = self.drag_state["drag_start_pos"]
|
|
287
|
-
try:
|
|
288
|
-
interactor = plotter.interactor
|
|
289
|
-
if interactor is None:
|
|
290
|
-
return False
|
|
291
|
-
current_pos = interactor.GetEventPosition()
|
|
292
|
-
dx = current_pos[0] - start_pos[0]
|
|
293
|
-
dy = current_pos[1] - start_pos[1]
|
|
294
|
-
|
|
295
|
-
if abs(dx) > 5 or abs(dy) > 5:
|
|
296
|
-
self.drag_state["is_dragging_group"] = True
|
|
297
|
-
self.drag_state["potential_drag"] = False
|
|
298
|
-
try:
|
|
299
|
-
plotter.setCursor(Qt.CursorShape.ClosedHandCursor)
|
|
300
|
-
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
301
|
-
pass
|
|
302
|
-
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
303
|
-
pass
|
|
304
|
-
return self.drag_state["is_dragging_group"]
|
|
305
|
-
|
|
306
|
-
def _handle_actual_drag(self, plotter: Any) -> bool:
|
|
307
|
-
"""Update drag movement flags during active dragging."""
|
|
308
|
-
start_pos = self.drag_state["drag_start_pos"]
|
|
309
|
-
try:
|
|
310
|
-
interactor = plotter.interactor
|
|
311
|
-
if interactor is None:
|
|
312
|
-
return False
|
|
313
|
-
current_pos = interactor.GetEventPosition()
|
|
314
|
-
dx = current_pos[0] - start_pos[0]
|
|
315
|
-
dy = current_pos[1] - start_pos[1]
|
|
316
|
-
if abs(dx) > 2 or abs(dy) > 2:
|
|
317
|
-
self.drag_state["mouse_moved_during_drag"] = True
|
|
318
|
-
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
319
|
-
pass
|
|
320
|
-
return True
|
|
321
|
-
|
|
322
|
-
def _handle_hover_cursor(self, plotter: Any) -> bool:
|
|
323
|
-
"""Set appropriate hover cursor style over selected atoms."""
|
|
324
|
-
try:
|
|
325
|
-
interactor = plotter.interactor
|
|
326
|
-
if interactor is None:
|
|
327
|
-
return False
|
|
328
|
-
current_pos = interactor.GetEventPosition()
|
|
329
|
-
closest_atom_idx = pick_atom_index_from_screen(
|
|
330
|
-
self.main_window.view_3d_manager,
|
|
331
|
-
(int(current_pos[0]), int(current_pos[1])),
|
|
332
|
-
self.mol,
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
if closest_atom_idx in self.selected_atoms:
|
|
336
|
-
plotter.setCursor(Qt.CursorShape.OpenHandCursor)
|
|
337
|
-
else:
|
|
338
|
-
plotter.setCursor(Qt.CursorShape.ArrowCursor)
|
|
339
|
-
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
340
|
-
pass
|
|
341
|
-
return False
|
|
342
|
-
|
|
343
|
-
def _handle_mouse_move(self, plotter: Any) -> bool:
|
|
344
|
-
"""Handle MouseMove event."""
|
|
345
|
-
if (
|
|
346
|
-
self.drag_state["potential_drag"]
|
|
347
|
-
and not self.drag_state["is_dragging_group"]
|
|
348
|
-
):
|
|
349
|
-
if not self._handle_potential_drag(plotter):
|
|
350
|
-
return False
|
|
351
|
-
|
|
352
|
-
if self.drag_state["is_dragging_group"]:
|
|
353
|
-
return self._handle_actual_drag(plotter)
|
|
354
|
-
|
|
355
|
-
if self.selected_atoms:
|
|
356
|
-
return self._handle_hover_cursor(plotter)
|
|
357
|
-
|
|
358
|
-
return False
|
|
359
|
-
|
|
360
|
-
def _reset_drag_state(self) -> None:
|
|
361
|
-
"""Reset internal drag flags."""
|
|
362
|
-
self.drag_state["is_dragging_group"] = False
|
|
363
|
-
self.drag_state["drag_start_pos"] = (0, 0)
|
|
364
|
-
self.drag_state["mouse_moved_during_drag"] = False
|
|
365
|
-
self.drag_state["potential_drag"] = False
|
|
366
|
-
|
|
367
|
-
def _restore_arrow_cursor(self) -> None:
|
|
368
|
-
"""Restore standard arrow cursor in the 3D viewer."""
|
|
369
|
-
try:
|
|
370
|
-
plotter_ptr = self.main_window.view_3d_manager.plotter
|
|
371
|
-
if plotter_ptr is not None:
|
|
372
|
-
plotter_ptr.setCursor(Qt.CursorShape.ArrowCursor)
|
|
373
|
-
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
374
|
-
pass
|
|
375
|
-
|
|
376
|
-
def _handle_mouse_release(self) -> bool:
|
|
377
|
-
"""Handle MouseButtonRelease event for LeftButton."""
|
|
378
|
-
if self.drag_state["consume_next_left_release"]:
|
|
379
|
-
self.drag_state["consume_next_left_release"] = False
|
|
380
|
-
return True
|
|
381
|
-
|
|
382
|
-
is_drag_active = (
|
|
383
|
-
self.drag_state["potential_drag"] or self.drag_state["is_dragging_group"]
|
|
384
|
-
)
|
|
385
|
-
if not is_drag_active:
|
|
386
|
-
return False
|
|
387
|
-
|
|
388
|
-
has_moved = (
|
|
389
|
-
self.drag_state["is_dragging_group"]
|
|
390
|
-
and self.drag_state["mouse_moved_during_drag"]
|
|
391
|
-
)
|
|
392
|
-
clicked_atom = self.clicked_atom_for_toggle
|
|
393
|
-
|
|
394
|
-
if not has_moved and clicked_atom is not None:
|
|
395
|
-
self.clicked_atom_for_toggle = None
|
|
396
|
-
self._reset_drag_state()
|
|
397
|
-
try:
|
|
398
|
-
self.on_atom_picked(clicked_atom)
|
|
399
|
-
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
400
|
-
pass
|
|
401
|
-
self._restore_arrow_cursor()
|
|
402
|
-
return True
|
|
403
|
-
|
|
404
|
-
self._reset_drag_state()
|
|
405
|
-
self._restore_arrow_cursor()
|
|
406
|
-
return True
|
|
407
|
-
|
|
408
232
|
def eventFilter(self, obj: Any, event: Any) -> bool:
|
|
409
233
|
"""Mouse event handling in 3D view.
|
|
410
234
|
|
|
@@ -417,31 +241,207 @@ class MoveSelectedAtomsDialog(BasePickingDialog):
|
|
|
417
241
|
if obj != plotter.interactor:
|
|
418
242
|
return super().eventFilter(obj, event)
|
|
419
243
|
|
|
420
|
-
result = False
|
|
421
244
|
e_type = event.type()
|
|
422
245
|
|
|
423
246
|
if e_type == QEvent.Type.MouseButtonDblClick:
|
|
424
|
-
|
|
425
|
-
|
|
247
|
+
# Ignore double clicks and reset state
|
|
248
|
+
self.is_dragging_group = False
|
|
249
|
+
self.drag_start_pos = None
|
|
250
|
+
self.mouse_moved_during_drag = False
|
|
251
|
+
self.potential_drag = False
|
|
252
|
+
self.clicked_atom_for_toggle = None
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
if (
|
|
426
256
|
e_type == QEvent.Type.MouseButtonPress
|
|
427
257
|
and isinstance(event, QMouseEvent)
|
|
428
258
|
and event.button() == Qt.MouseButton.LeftButton
|
|
429
259
|
):
|
|
430
|
-
|
|
260
|
+
# Clean up previous state (triple-click countermeasure)
|
|
261
|
+
self.is_dragging_group = False
|
|
262
|
+
self.potential_drag = False
|
|
263
|
+
self.clicked_atom_for_toggle = None
|
|
264
|
+
# Delegate to CustomInteractorStyle if atoms are already selected
|
|
265
|
+
if self.selected_atoms:
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
# Mouse press handling
|
|
269
|
+
try:
|
|
270
|
+
interactor = plotter.interactor
|
|
271
|
+
if interactor is None:
|
|
272
|
+
return False
|
|
273
|
+
click_pos = interactor.GetEventPosition()
|
|
274
|
+
|
|
275
|
+
clicked_atom_idx = pick_atom_index_from_screen(
|
|
276
|
+
self.main_window.view_3d_manager,
|
|
277
|
+
(int(click_pos[0]), int(click_pos[1])),
|
|
278
|
+
self.mol,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Handle clicked atom
|
|
282
|
+
if clicked_atom_idx is not None:
|
|
283
|
+
if self.selected_atoms and clicked_atom_idx in self.selected_atoms:
|
|
284
|
+
# Atom within existing group - prepare for drag
|
|
285
|
+
self.is_dragging_group = False
|
|
286
|
+
self.drag_start_pos = click_pos
|
|
287
|
+
self.drag_atom_idx = clicked_atom_idx
|
|
288
|
+
self.mouse_moved_during_drag = False
|
|
289
|
+
self.potential_drag = True
|
|
290
|
+
self.clicked_atom_for_toggle = clicked_atom_idx
|
|
291
|
+
return False
|
|
292
|
+
else:
|
|
293
|
+
# Atom outside group - select
|
|
294
|
+
self.on_atom_picked(clicked_atom_idx)
|
|
295
|
+
self._consume_next_left_release = True
|
|
296
|
+
return True
|
|
297
|
+
else:
|
|
298
|
+
# Clicked outside atoms
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
except (AttributeError, RuntimeError, ValueError) as e:
|
|
302
|
+
logging.debug(f"Error in mouse press: {e}")
|
|
303
|
+
return False
|
|
304
|
+
|
|
431
305
|
elif e_type == QEvent.Type.MouseMove and isinstance(event, QMouseEvent):
|
|
432
|
-
|
|
306
|
+
# Mouse move handling
|
|
307
|
+
if (
|
|
308
|
+
getattr(self, "potential_drag", False)
|
|
309
|
+
and self.drag_start_pos
|
|
310
|
+
and not self.is_dragging_group
|
|
311
|
+
):
|
|
312
|
+
try:
|
|
313
|
+
plotter_ref = self.main_window.view_3d_manager.plotter
|
|
314
|
+
if plotter_ref is None or plotter_ref.interactor is None:
|
|
315
|
+
return False
|
|
316
|
+
interactor = plotter_ref.interactor
|
|
317
|
+
current_pos = interactor.GetEventPosition()
|
|
318
|
+
dx = current_pos[0] - self.drag_start_pos[0]
|
|
319
|
+
dy = current_pos[1] - self.drag_start_pos[1]
|
|
320
|
+
|
|
321
|
+
# Start drag if threshold is exceeded
|
|
322
|
+
drag_threshold = 5 # pixels
|
|
323
|
+
if abs(dx) > drag_threshold or abs(dy) > drag_threshold:
|
|
324
|
+
self.is_dragging_group = True
|
|
325
|
+
self.potential_drag = False
|
|
326
|
+
try:
|
|
327
|
+
plotter_ptr = self.main_window.view_3d_manager.plotter
|
|
328
|
+
if plotter_ptr is not None:
|
|
329
|
+
plotter_ptr.setCursor(Qt.CursorShape.ClosedHandCursor)
|
|
330
|
+
except (
|
|
331
|
+
AttributeError,
|
|
332
|
+
RuntimeError,
|
|
333
|
+
ValueError,
|
|
334
|
+
TypeError,
|
|
335
|
+
) as e:
|
|
336
|
+
logging.debug(f"Failed to set closed hand cursor: {e}")
|
|
337
|
+
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
338
|
+
logging.debug(f"Error initiating drag on move: {e}")
|
|
339
|
+
|
|
340
|
+
if not self.is_dragging_group:
|
|
341
|
+
return False
|
|
342
|
+
|
|
343
|
+
if self.is_dragging_group and self.drag_start_pos:
|
|
344
|
+
try:
|
|
345
|
+
plotter_ref = self.main_window.view_3d_manager.plotter
|
|
346
|
+
if plotter_ref is None or plotter_ref.interactor is None:
|
|
347
|
+
return False
|
|
348
|
+
interactor = plotter_ref.interactor
|
|
349
|
+
current_pos = interactor.GetEventPosition()
|
|
350
|
+
dx = current_pos[0] - self.drag_start_pos[0]
|
|
351
|
+
dy = current_pos[1] - self.drag_start_pos[1]
|
|
352
|
+
if abs(dx) > 5 or abs(dy) > 5:
|
|
353
|
+
self.mouse_moved_during_drag = True
|
|
354
|
+
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
355
|
+
logging.debug(f"Error tracking drag movement: {e}")
|
|
356
|
+
return True
|
|
357
|
+
|
|
358
|
+
# Hover handling
|
|
359
|
+
if self.selected_atoms:
|
|
360
|
+
try:
|
|
361
|
+
plotter_ref = self.main_window.view_3d_manager.plotter
|
|
362
|
+
if plotter_ref is None or plotter_ref.interactor is None:
|
|
363
|
+
return False
|
|
364
|
+
interactor = plotter_ref.interactor
|
|
365
|
+
current_pos = interactor.GetEventPosition()
|
|
366
|
+
closest_atom_idx = pick_atom_index_from_screen(
|
|
367
|
+
self.main_window.view_3d_manager,
|
|
368
|
+
(int(current_pos[0]), int(current_pos[1])),
|
|
369
|
+
self.mol,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
if closest_atom_idx in self.selected_atoms:
|
|
373
|
+
plotter_ref.setCursor(Qt.CursorShape.OpenHandCursor)
|
|
374
|
+
else:
|
|
375
|
+
plotter_ref.setCursor(Qt.CursorShape.ArrowCursor)
|
|
376
|
+
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
377
|
+
logging.debug(f"Error updating hover cursor: {e}")
|
|
378
|
+
|
|
379
|
+
return False
|
|
380
|
+
|
|
433
381
|
elif (
|
|
434
382
|
e_type == QEvent.Type.MouseButtonRelease
|
|
435
383
|
and isinstance(event, QMouseEvent)
|
|
436
384
|
and event.button() == Qt.MouseButton.LeftButton
|
|
437
385
|
):
|
|
438
|
-
|
|
386
|
+
if self._consume_next_left_release:
|
|
387
|
+
self._consume_next_left_release = False
|
|
388
|
+
return True
|
|
389
|
+
|
|
390
|
+
if getattr(self, "potential_drag", False) or (
|
|
391
|
+
self.is_dragging_group and self.drag_start_pos
|
|
392
|
+
):
|
|
393
|
+
try:
|
|
394
|
+
if not (self.is_dragging_group and self.mouse_moved_during_drag):
|
|
395
|
+
# Mouse move below threshold = simple click (toggle)
|
|
396
|
+
if self.clicked_atom_for_toggle is not None:
|
|
397
|
+
clicked_atom = self.clicked_atom_for_toggle
|
|
398
|
+
self.clicked_atom_for_toggle = None
|
|
399
|
+
self.is_dragging_group = False
|
|
400
|
+
self.drag_start_pos = None
|
|
401
|
+
self.mouse_moved_during_drag = False
|
|
402
|
+
self.potential_drag = False
|
|
403
|
+
if clicked_atom is not None:
|
|
404
|
+
self.on_atom_picked(clicked_atom)
|
|
405
|
+
try:
|
|
406
|
+
plotter_ptr = self.main_window.view_3d_manager.plotter
|
|
407
|
+
if plotter_ptr is not None:
|
|
408
|
+
plotter_ptr.setCursor(Qt.CursorShape.ArrowCursor)
|
|
409
|
+
except (
|
|
410
|
+
AttributeError,
|
|
411
|
+
RuntimeError,
|
|
412
|
+
ValueError,
|
|
413
|
+
TypeError,
|
|
414
|
+
) as e:
|
|
415
|
+
logging.debug(f"Failed to reset cursor to arrow: {e}")
|
|
416
|
+
return True
|
|
417
|
+
else:
|
|
418
|
+
logging.error(
|
|
419
|
+
"REPORT ERROR: Missing attribute 'clicked_atom_for_toggle' on self"
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
423
|
+
logging.debug(f"Error in mouse release handling: {e}")
|
|
424
|
+
finally:
|
|
425
|
+
self.is_dragging_group = False
|
|
426
|
+
self.drag_start_pos = None
|
|
427
|
+
self.mouse_moved_during_drag = False
|
|
428
|
+
self.potential_drag = False
|
|
429
|
+
try:
|
|
430
|
+
plotter_ptr = self.main_window.view_3d_manager.plotter
|
|
431
|
+
if plotter_ptr is not None:
|
|
432
|
+
plotter_ptr.setCursor(Qt.CursorShape.ArrowCursor)
|
|
433
|
+
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
434
|
+
logging.debug(f"Failed to reset cursor in release finally: {e}")
|
|
439
435
|
|
|
440
|
-
|
|
436
|
+
return True
|
|
437
|
+
|
|
438
|
+
return False
|
|
439
|
+
|
|
440
|
+
return super().eventFilter(obj, event)
|
|
441
441
|
|
|
442
442
|
def on_atom_picked(self, atom_idx: int) -> None:
|
|
443
443
|
"""Select or deselect the clicked atom."""
|
|
444
|
-
if self
|
|
444
|
+
if getattr(self, "is_dragging_group", False):
|
|
445
445
|
return
|
|
446
446
|
|
|
447
447
|
if atom_idx in self.selected_atoms:
|
|
@@ -471,13 +471,18 @@ class MoveSelectedAtomsDialog(BasePickingDialog):
|
|
|
471
471
|
|
|
472
472
|
def show_atom_labels(self) -> None:
|
|
473
473
|
"""Highlight selected atoms."""
|
|
474
|
+
plotter = self.main_window.view_3d_manager.plotter
|
|
475
|
+
try:
|
|
476
|
+
cam = plotter.camera_position if plotter else None
|
|
477
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
478
|
+
cam = None
|
|
479
|
+
|
|
474
480
|
self.clear_atom_labels()
|
|
475
481
|
|
|
476
482
|
if not self.selected_atoms:
|
|
477
483
|
return
|
|
478
484
|
|
|
479
485
|
selected_indices = list(self.selected_atoms)
|
|
480
|
-
plotter = self.main_window.view_3d_manager.plotter
|
|
481
486
|
if self.main_window.view_3d_manager.atom_positions_3d is None:
|
|
482
487
|
logging.error("atom_positions_3d is None in update_atom_labels")
|
|
483
488
|
return
|
|
@@ -507,8 +512,15 @@ class MoveSelectedAtomsDialog(BasePickingDialog):
|
|
|
507
512
|
opacity=0.3,
|
|
508
513
|
name="move_selected_atoms_highlight",
|
|
509
514
|
pickable=False,
|
|
515
|
+
reset_camera=False,
|
|
510
516
|
)
|
|
511
517
|
|
|
518
|
+
if cam is not None:
|
|
519
|
+
try:
|
|
520
|
+
plotter.camera_position = cam
|
|
521
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
522
|
+
pass
|
|
523
|
+
|
|
512
524
|
plotter.render()
|
|
513
525
|
|
|
514
526
|
def clear_atom_labels(self) -> None:
|
|
@@ -636,5 +648,5 @@ class MoveSelectedAtomsDialog(BasePickingDialog):
|
|
|
636
648
|
self.selected_atoms.clear()
|
|
637
649
|
self.clear_atom_labels()
|
|
638
650
|
self.update_display()
|
|
639
|
-
self.
|
|
640
|
-
self.
|
|
651
|
+
self.is_dragging_group = False
|
|
652
|
+
self.drag_start_pos = None
|
|
@@ -147,6 +147,26 @@ class Settings2DTab(SettingsTabBase):
|
|
|
147
147
|
|
|
148
148
|
form_layout.addRow(self._create_separator())
|
|
149
149
|
|
|
150
|
+
# --- Bond Snapping Settings ---
|
|
151
|
+
form_layout.addRow(QLabel("<b>Bond Snapping Settings</b>"))
|
|
152
|
+
|
|
153
|
+
# Bond Snapping Distance
|
|
154
|
+
self.bond_snapping_distance_2d_slider, self.bond_snapping_distance_2d_label = (
|
|
155
|
+
self._create_slider(5, 50, 1.0, is_int=True)
|
|
156
|
+
)
|
|
157
|
+
self.bond_snapping_distance_2d_slider.setToolTip(
|
|
158
|
+
"The distance in pixels within which drawing a bond will snap to an existing atom."
|
|
159
|
+
)
|
|
160
|
+
form_layout.addRow(
|
|
161
|
+
"Bond Snapping Distance (px):",
|
|
162
|
+
self._wrap_layout(
|
|
163
|
+
self.bond_snapping_distance_2d_slider,
|
|
164
|
+
self.bond_snapping_distance_2d_label,
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
form_layout.addRow(self._create_separator())
|
|
169
|
+
|
|
150
170
|
# --- Template Settings ---
|
|
151
171
|
form_layout.addRow(QLabel("<b>Template Settings</b>"))
|
|
152
172
|
|
|
@@ -270,6 +290,10 @@ class Settings2DTab(SettingsTabBase):
|
|
|
270
290
|
self.template_fusing_distance_2d_slider.setEnabled(fusing_enabled)
|
|
271
291
|
self.template_fusing_distance_2d_label.setEnabled(fusing_enabled)
|
|
272
292
|
|
|
293
|
+
self.bond_snapping_distance_2d_slider.setValue(
|
|
294
|
+
int(settings_dict.get("bond_snapping_distance_2d", 14.0))
|
|
295
|
+
)
|
|
296
|
+
|
|
273
297
|
self.template_fusing_distance_2d_slider.setValue(
|
|
274
298
|
int(settings_dict.get("template_fusing_distance_2d", 14.0))
|
|
275
299
|
)
|
|
@@ -290,6 +314,9 @@ class Settings2DTab(SettingsTabBase):
|
|
|
290
314
|
"atom_font_family_2d": self.atom_font_family_2d_combo.currentFont().family(),
|
|
291
315
|
"atom_font_size_2d": self.atom_font_size_2d_slider.value(),
|
|
292
316
|
"atom_use_bond_color_2d": self.atom_use_bond_color_2d_checkbox.isChecked(),
|
|
317
|
+
"bond_snapping_distance_2d": float(
|
|
318
|
+
self.bond_snapping_distance_2d_slider.value()
|
|
319
|
+
),
|
|
293
320
|
"template_fusing_enabled_2d": self.template_fusing_enabled_2d_checkbox.isChecked(),
|
|
294
321
|
"template_fusing_distance_2d": float(
|
|
295
322
|
self.template_fusing_distance_2d_slider.value()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 3.6.
|
|
3
|
+
Version: 3.6.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,4 +1,4 @@
|
|
|
1
|
-
moleditpy_linux/__init__.py,sha256=
|
|
1
|
+
moleditpy_linux/__init__.py,sha256=SBIBlrNkWvxt6fWR02XOA9A0Bokj_a5xhv7OZIaxhZ4,490
|
|
2
2
|
moleditpy_linux/__main__.py,sha256=VHCD-CCX6iKUbmUyRv2BGVXCjHWEaISpmjO7_yh9AaU,654
|
|
3
3
|
moleditpy_linux/main.py,sha256=jkew0v747koc4MTTlH6S3OfqkDI05cgsn9vWFZlJDQA,5444
|
|
4
4
|
moleditpy_linux/assets/file_icon.ico,sha256=yyVj084A7HuMNbV073cE_Ag3Ne405qgOP3Mia1ZqLpE,101632
|
|
@@ -20,31 +20,31 @@ moleditpy_linux/ui/analysis_window.py,sha256=iVYzxLpL78XS46lzRC0DpT3hwD6FS60su_K
|
|
|
20
20
|
moleditpy_linux/ui/angle_dialog.py,sha256=jEtq0BcMmaIq7iZA26j5w7n6DBwPZswcHuGhsj2nRUA,17803
|
|
21
21
|
moleditpy_linux/ui/app_state.py,sha256=aNU2dy2lbW17pkZOurORFYmIimu715DioSH33UZn2QQ,40162
|
|
22
22
|
moleditpy_linux/ui/atom_item.py,sha256=tN7paZGVVOLR2ppuI_qcYajiQ7YLH4mOhjzBYHeEN3w,21144
|
|
23
|
-
moleditpy_linux/ui/atom_picking.py,sha256=
|
|
23
|
+
moleditpy_linux/ui/atom_picking.py,sha256=HhJ8kH1zVF3DIzMG1Qymk44d8MwUqbg9xr4pQ6H0lHA,10391
|
|
24
24
|
moleditpy_linux/ui/base_picking_dialog.py,sha256=t4kdyKXgHj6IGbjL_bOJbFbIlaYkERPcxKeir1u-roA,5682
|
|
25
25
|
moleditpy_linux/ui/bond_item.py,sha256=sn3Vk1XblmjfDP1zzPS_mn_5byFA4xPR_-qoYWjKTe8,26588
|
|
26
26
|
moleditpy_linux/ui/bond_length_dialog.py,sha256=U7YQ6r75TueSA7lV_B9dUEnaEjOLEgkT1aW8KUItbtI,14962
|
|
27
27
|
moleditpy_linux/ui/calculation_worker.py,sha256=HJz5NkXfNS66OODxKkR-IfGUtv7hRG-mp4Wfapw6D7M,41125
|
|
28
28
|
moleditpy_linux/ui/color_settings_dialog.py,sha256=sV9w-fRh8dcRLBPwyNGkvATmOJXlvm8Em2yGpGHHb_k,16642
|
|
29
29
|
moleditpy_linux/ui/compute_logic.py,sha256=qxUXBKb3-AvE0FdtrgeyyO9Dc93sEVpPP5sAviewZ78,28425
|
|
30
|
-
moleditpy_linux/ui/constrained_optimization_dialog.py,sha256=
|
|
31
|
-
moleditpy_linux/ui/custom_interactor_style.py,sha256=
|
|
30
|
+
moleditpy_linux/ui/constrained_optimization_dialog.py,sha256=PNQN2d0nufI9s-N-60zG2HeKUuYzWFzNudYHb5y4KOQ,35042
|
|
31
|
+
moleditpy_linux/ui/custom_interactor_style.py,sha256=poOgprQeffKtdL7cvoxWVPgRefO0OAOkAja_NOag4Ms,42564
|
|
32
32
|
moleditpy_linux/ui/custom_qt_interactor.py,sha256=oAfCHIDVOJmLTdowrBiGsaCoQMJ-ZnQhpSkDLIHMe9U,3735
|
|
33
33
|
moleditpy_linux/ui/dialog_3d_picking_mixin.py,sha256=iJg9bZDj2wvOG178mwOus5Y-6HmSmIMCX0KyvOJ-l0U,9901
|
|
34
34
|
moleditpy_linux/ui/dialog_logic.py,sha256=nsQ7nhQrOH-DmM49rhu-S4dvmbQPDyXh17efJzjuC3k,21356
|
|
35
35
|
moleditpy_linux/ui/dihedral_dialog.py,sha256=ZDihYt32_3V_uJXsJ4u3qGxn3pASWPcl8BIY3nTBQ0Q,18103
|
|
36
|
-
moleditpy_linux/ui/edit_3d_logic.py,sha256=
|
|
37
|
-
moleditpy_linux/ui/edit_actions_logic.py,sha256=
|
|
36
|
+
moleditpy_linux/ui/edit_3d_logic.py,sha256=y-6GabTZzlLA0u3j8ugy7yHV3jA4HvWTVCAmgFEKwh4,19740
|
|
37
|
+
moleditpy_linux/ui/edit_actions_logic.py,sha256=KZZJOnAcN_wHwXX48z5j5zZPTO5v5f0hgikJXogbVvs,67671
|
|
38
38
|
moleditpy_linux/ui/export_logic.py,sha256=4irp1z_7PRAAME-2gJUB8zVnM8eXdmaiM3vMLpLqSYg,43422
|
|
39
39
|
moleditpy_linux/ui/geometry_base_dialog.py,sha256=eAlmZOPPXMKKBDWFiBN68-Zbp-EeqWrgXBf8q3ioxGY,4734
|
|
40
40
|
moleditpy_linux/ui/io_logic.py,sha256=aHjZdTmBY2e35NpIgvawdOAXmDRg-DcJf9025AdSDqg,43839
|
|
41
41
|
moleditpy_linux/ui/main_window.py,sha256=MIGs8M72OnT3TZG7RM8NeBfyInYysDlKzZqEoKUnSiE,4773
|
|
42
42
|
moleditpy_linux/ui/main_window_init.py,sha256=lSfW6EDtcVptS37OUEPidz5WNcq7FSzmG86G8ftfH-4,94196
|
|
43
43
|
moleditpy_linux/ui/mirror_dialog.py,sha256=iJriiS5U61tyw_7M_PUf3x1qAHh8ciBh751GH8O_BpQ,5075
|
|
44
|
-
moleditpy_linux/ui/molecular_scene_handler.py,sha256=
|
|
45
|
-
moleditpy_linux/ui/molecule_scene.py,sha256=
|
|
46
|
-
moleditpy_linux/ui/move_group_dialog.py,sha256=
|
|
47
|
-
moleditpy_linux/ui/move_selected_atoms_dialog.py,sha256=
|
|
44
|
+
moleditpy_linux/ui/molecular_scene_handler.py,sha256=TFmvgs10xuEI0PiRxWYvUlh_gBunUfQb1BPrIDF5CtA,69602
|
|
45
|
+
moleditpy_linux/ui/molecule_scene.py,sha256=qa-nWA653oEiJFJx7Gn-jBSHaPtJw4kq4KfK6pqJcQs,38524
|
|
46
|
+
moleditpy_linux/ui/move_group_dialog.py,sha256=1T8vxhPcSp5r71WE90bINe4lm0Q8XEvCUBlUcXhHF7w,26259
|
|
47
|
+
moleditpy_linux/ui/move_selected_atoms_dialog.py,sha256=samPLggErPkNQeZJ_zNbWKpbh3MFGVB6aHxg0waF5HI,25268
|
|
48
48
|
moleditpy_linux/ui/periodic_table_dialog.py,sha256=2fgAILjcjYNt-x9UCqq2VFgwDY8oX7aE4jMQvyyCMho,5847
|
|
49
49
|
moleditpy_linux/ui/planarize_dialog.py,sha256=F_xMk6Jcmq7PECKJfiKtQuRm1tRXLS7Se2h5wwpnD0w,7359
|
|
50
50
|
moleditpy_linux/ui/settings_dialog.py,sha256=WTexmB8HOmdA-CjHK5M6TcS7ZrJtywyq7qMM2SZzu-k,7673
|
|
@@ -57,18 +57,18 @@ moleditpy_linux/ui/user_template_dialog.py,sha256=lH3gf5XtmOMxdMHWFWNhxh-c9vQcb4
|
|
|
57
57
|
moleditpy_linux/ui/view_3d_logic.py,sha256=rO6svIUu7XurU6Vj3C7avAV5EvIAdrogevbOHXgBxIE,94668
|
|
58
58
|
moleditpy_linux/ui/zoomable_view.py,sha256=eCHAgIMRaNoz7qjnZjrJBdxAmDt2bESII_Kc6l-Mzjg,5803
|
|
59
59
|
moleditpy_linux/ui/settings_tabs/__init__.py,sha256=BEOaCz93HzgTe0FhBzSj6Sfk8MAWxcY9vCDGmOriJx4,267
|
|
60
|
-
moleditpy_linux/ui/settings_tabs/settings_2d_tab.py,sha256=
|
|
60
|
+
moleditpy_linux/ui/settings_tabs/settings_2d_tab.py,sha256=7AsEz9mIZA4DYg-eiLt1UeYX9zw-8_I12qO8GCCQQsE,12786
|
|
61
61
|
moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py,sha256=S2ydaYjtc9AGZu86mDaLDi1DC6hEI5mFtPvjwVpBVaY,11341
|
|
62
|
-
moleditpy_linux/ui/settings_tabs/settings_other_tab.py,sha256=
|
|
63
|
-
moleditpy_linux/ui/settings_tabs/settings_tab_base.py,sha256=
|
|
62
|
+
moleditpy_linux/ui/settings_tabs/settings_other_tab.py,sha256=NLJiuFi4fTA7YVjt7wdnx1qPOoL_Ur76veGFjAfexfI,5449
|
|
63
|
+
moleditpy_linux/ui/settings_tabs/settings_tab_base.py,sha256=pV9WdT16ZJTQGv74YIkFSQ79Iq1rerjM6G9svbYmIeY,3516
|
|
64
64
|
moleditpy_linux/utils/__init__.py,sha256=BEOaCz93HzgTe0FhBzSj6Sfk8MAWxcY9vCDGmOriJx4,267
|
|
65
65
|
moleditpy_linux/utils/constants.py,sha256=HsOqb9sAzRNujvBQwAeTWXS7fHfho46shmnSa3TuBDk,6633
|
|
66
|
-
moleditpy_linux/utils/default_settings.py,sha256=
|
|
66
|
+
moleditpy_linux/utils/default_settings.py,sha256=OxgIrdk-LCXBdhUPF_qzsbdsiM18kDWgP3seELyUgJU,2731
|
|
67
67
|
moleditpy_linux/utils/sip_isdeleted_safe.py,sha256=My6IJqDewbYY6SoRYNk6XwFUQ9_yihaR3Ym7EOETuAw,1189
|
|
68
68
|
moleditpy_linux/utils/system_utils.py,sha256=K5c9cJRgMFqtUtLefSu3w2hy2drgxNqfT200bSIFy2k,2325
|
|
69
|
-
moleditpy_linux-3.6.
|
|
70
|
-
moleditpy_linux-3.6.
|
|
71
|
-
moleditpy_linux-3.6.
|
|
72
|
-
moleditpy_linux-3.6.
|
|
73
|
-
moleditpy_linux-3.6.
|
|
74
|
-
moleditpy_linux-3.6.
|
|
69
|
+
moleditpy_linux-3.6.2.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
70
|
+
moleditpy_linux-3.6.2.dist-info/METADATA,sha256=Hvax71UUKc-KHdTtT9hmn0R_dF-kUWfEdmZqqQ0T4io,62978
|
|
71
|
+
moleditpy_linux-3.6.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
72
|
+
moleditpy_linux-3.6.2.dist-info/entry_points.txt,sha256=-OzipSi__yVwlimNtu3eiRP5t5UMg55Cs0udyhXYiyw,60
|
|
73
|
+
moleditpy_linux-3.6.2.dist-info/top_level.txt,sha256=qyqe-hDYL6CXyin9E5Me5rVl3PG84VqiOjf9bQvfJLs,16
|
|
74
|
+
moleditpy_linux-3.6.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|