MoleditPy-linux 4.6.1__tar.gz → 4.7.1__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_linux-4.6.1 → moleditpy_linux-4.7.1}/PKG-INFO +1 -1
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/pyproject.toml +1 -1
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/MoleditPy_linux.egg-info/PKG-INFO +1 -1
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/MoleditPy_linux.egg-info/SOURCES.txt +1 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/plugins/plugin_interface.py +37 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/angle_dialog.py +4 -1
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/atom_item.py +29 -41
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/bond_item.py +17 -26
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/bond_length_dialog.py +4 -1
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/custom_interactor_style.py +8 -3
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/dihedral_dialog.py +4 -1
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/edit_3d_logic.py +1 -1
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/edit_actions_logic.py +1 -1
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/io_logic.py +36 -15
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/main_window_init.py +1 -1
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/molecular_scene_handler.py +5 -9
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +6 -5
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/zoomable_view.py +31 -1
- moleditpy_linux-4.7.1/src/moleditpy_linux/utils/hit_radius.py +32 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/LICENSE +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/README.md +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/setup.cfg +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/__init__.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/__main__.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/assets/file_icon.ico +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/assets/icon.icns +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/assets/icon.ico +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/assets/icon.png +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/core/__init__.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/core/mol_geometry.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/core/molecular_data.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/main.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/plugins/__init__.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/plugins/plugin_manager.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/plugins/plugin_manager_window.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/__init__.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/about_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/analysis_window.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/app_state.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/atom_picking.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/base_picking_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/calculation_worker.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/chain_mixin.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/compute_logic.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/custom_qt_interactor.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/dialog_logic.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/export_logic.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/main_window.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/molecule_scene.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/move_group_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/move_selected_atoms_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/planarize_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/plugin_menu_manager.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/string_importers.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/template_preview_item.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/translation_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/ui_manager.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/user_template_dialog.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/view_3d_logic.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/utils/__init__.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/utils/constants.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/utils/default_settings.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/utils/suppress_log.py +0 -0
- {moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/utils/system_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.7.1
|
|
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-linux
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.7.1
|
|
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
|
|
@@ -75,6 +75,7 @@ src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py
|
|
|
75
75
|
src/moleditpy_linux/utils/__init__.py
|
|
76
76
|
src/moleditpy_linux/utils/constants.py
|
|
77
77
|
src/moleditpy_linux/utils/default_settings.py
|
|
78
|
+
src/moleditpy_linux/utils/hit_radius.py
|
|
78
79
|
src/moleditpy_linux/utils/sip_isdeleted_safe.py
|
|
79
80
|
src/moleditpy_linux/utils/suppress_log.py
|
|
80
81
|
src/moleditpy_linux/utils/system_utils.py
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/plugins/plugin_interface.py
RENAMED
|
@@ -409,6 +409,43 @@ class PluginContext:
|
|
|
409
409
|
mw.init_manager.settings[namespaced] = value
|
|
410
410
|
mw.init_manager.settings_dirty = True
|
|
411
411
|
|
|
412
|
+
def get_current_file(self) -> Optional[str]:
|
|
413
|
+
"""
|
|
414
|
+
Path of the file the application currently has open, or None.
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
The path, or None when nothing is open.
|
|
418
|
+
"""
|
|
419
|
+
mw = self.get_main_window()
|
|
420
|
+
if mw and hasattr(mw, "init_manager"):
|
|
421
|
+
path = mw.init_manager.current_file_path
|
|
422
|
+
return str(path) if path else None
|
|
423
|
+
return None
|
|
424
|
+
|
|
425
|
+
def set_current_file(self, path: Optional[str]) -> None:
|
|
426
|
+
"""
|
|
427
|
+
Record *path* as the file the application has open and retitle its window.
|
|
428
|
+
|
|
429
|
+
Use this after a plugin file opener loads a file, so the window names it
|
|
430
|
+
the way it names a project. Assigning ``init_manager.current_file_path``
|
|
431
|
+
directly leaves the title showing the previous file: it is only rebuilt
|
|
432
|
+
on demand.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
path: File path to show, or None for "Untitled".
|
|
436
|
+
"""
|
|
437
|
+
mw = self.get_main_window()
|
|
438
|
+
if mw is None or not hasattr(mw, "init_manager"):
|
|
439
|
+
return
|
|
440
|
+
try:
|
|
441
|
+
mw.init_manager.current_file_path = path
|
|
442
|
+
if hasattr(mw, "state_manager"):
|
|
443
|
+
fn = getattr(mw.state_manager, "update_window_title", None)
|
|
444
|
+
if fn:
|
|
445
|
+
fn()
|
|
446
|
+
except (AttributeError, RuntimeError):
|
|
447
|
+
logging.debug("set_current_file failed", exc_info=True)
|
|
448
|
+
|
|
412
449
|
def mark_project_modified(self) -> None:
|
|
413
450
|
"""Mark the current project as having unsaved changes and update the window title."""
|
|
414
451
|
mw = self.get_main_window()
|
|
@@ -487,7 +487,10 @@ class AngleDialog(GeometryBaseDialog):
|
|
|
487
487
|
if abs(achieved - requested) < 0.5:
|
|
488
488
|
return
|
|
489
489
|
with suppress_log(AttributeError, RuntimeError, TypeError):
|
|
490
|
-
self.main_window.statusBar()
|
|
490
|
+
status_bar = self.main_window.statusBar()
|
|
491
|
+
if status_bar is None:
|
|
492
|
+
return
|
|
493
|
+
status_bar.showMessage(
|
|
491
494
|
f"Angle unchanged ({achieved:.2f} deg): the three atoms rotate "
|
|
492
495
|
"together, which happens for an angle inside a ring.",
|
|
493
496
|
5000,
|
|
@@ -37,6 +37,7 @@ from ..utils.constants import (
|
|
|
37
37
|
FONT_FAMILY,
|
|
38
38
|
FONT_WEIGHT_BOLD,
|
|
39
39
|
)
|
|
40
|
+
from ..utils.hit_radius import scene_hit_radius
|
|
40
41
|
from ..utils.sip_isdeleted_safe import sip_isdeleted_safe
|
|
41
42
|
|
|
42
43
|
if TYPE_CHECKING:
|
|
@@ -111,8 +112,8 @@ class AtomItem(QGraphicsItem):
|
|
|
111
112
|
font_italic = False
|
|
112
113
|
font_underline = False
|
|
113
114
|
|
|
114
|
-
scene = self.scene()
|
|
115
|
-
if scene is not None
|
|
115
|
+
scene: Any = self.scene()
|
|
116
|
+
if scene is not None:
|
|
116
117
|
font_size = scene.get_setting("atom_font_size_2d", 20)
|
|
117
118
|
font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
|
|
118
119
|
font_bold = scene.get_setting("atom_font_bold_2d", True)
|
|
@@ -133,15 +134,15 @@ class AtomItem(QGraphicsItem):
|
|
|
133
134
|
)
|
|
134
135
|
self.update()
|
|
135
136
|
|
|
136
|
-
def
|
|
137
|
-
"""
|
|
137
|
+
def visual_rect(self) -> QRectF:
|
|
138
|
+
"""Return the rectangle the atom draws into; highlights use this."""
|
|
138
139
|
font_size = 20
|
|
139
140
|
font_family = FONT_FAMILY
|
|
140
141
|
font_bold = True
|
|
141
142
|
font_italic = False
|
|
142
143
|
font_underline = False
|
|
143
|
-
scene = self.scene()
|
|
144
|
-
if scene is not None
|
|
144
|
+
scene: Any = self.scene()
|
|
145
|
+
if scene is not None:
|
|
145
146
|
font_size = scene.get_setting("atom_font_size_2d", 20)
|
|
146
147
|
font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
|
|
147
148
|
font_bold = scene.get_setting("atom_font_bold_2d", True)
|
|
@@ -251,6 +252,13 @@ class AtomItem(QGraphicsItem):
|
|
|
251
252
|
# 3. Add final margins for selection highlights, etc.
|
|
252
253
|
return full_visual_rect.adjusted(-3, -3, 3, 3)
|
|
253
254
|
|
|
255
|
+
def boundingRect(self) -> QRectF:
|
|
256
|
+
"""Return the drawn rect plus the hit shape, which Qt requires it to cover."""
|
|
257
|
+
hit_r = self.hit_radius()
|
|
258
|
+
return self.visual_rect().united(
|
|
259
|
+
QRectF(-hit_r, -hit_r, hit_r * 2.0, hit_r * 2.0)
|
|
260
|
+
)
|
|
261
|
+
|
|
254
262
|
def get_bg_ellipse_path(self) -> QPainterPath:
|
|
255
263
|
"""Return the elliptical background path used for bond endpoint clipping."""
|
|
256
264
|
path = QPainterPath()
|
|
@@ -262,8 +270,8 @@ class AtomItem(QGraphicsItem):
|
|
|
262
270
|
font_bold = True
|
|
263
271
|
font_italic = False
|
|
264
272
|
font_underline = False
|
|
265
|
-
scene = self.scene()
|
|
266
|
-
if scene is not None
|
|
273
|
+
scene: Any = self.scene()
|
|
274
|
+
if scene is not None:
|
|
267
275
|
font_size = scene.get_setting("atom_font_size_2d", 20)
|
|
268
276
|
font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
|
|
269
277
|
font_bold = scene.get_setting("atom_font_bold_2d", True)
|
|
@@ -330,33 +338,13 @@ class AtomItem(QGraphicsItem):
|
|
|
330
338
|
path.addEllipse(bg_rect)
|
|
331
339
|
return path
|
|
332
340
|
|
|
333
|
-
def
|
|
334
|
-
"""
|
|
335
|
-
|
|
336
|
-
The hit radius is derived from the ``bond_snapping_distance_2d``
|
|
337
|
-
setting so that hover highlight matches the snapping / keyboard
|
|
338
|
-
instant-switch zone exactly. The radius is expressed in screen
|
|
339
|
-
pixels and divided by the current view scale.
|
|
340
|
-
"""
|
|
341
|
-
scene = self.scene()
|
|
342
|
-
if not scene or not scene.views():
|
|
343
|
-
path = QPainterPath()
|
|
344
|
-
hit_r = max(4.0, ATOM_RADIUS - 6.0) * 2
|
|
345
|
-
path.addEllipse(QRectF(-hit_r, -hit_r, hit_r * 2.0, hit_r * 2.0))
|
|
346
|
-
return path
|
|
347
|
-
|
|
348
|
-
view = scene.views()[0]
|
|
349
|
-
scale = view.transform().m11()
|
|
350
|
-
if scale <= 0.0:
|
|
351
|
-
scale = 1.0
|
|
341
|
+
def hit_radius(self) -> float:
|
|
342
|
+
"""Return the hit/snap radius in scene units for the current zoom."""
|
|
343
|
+
return scene_hit_radius(self.scene(), fallback=max(4.0, ATOM_RADIUS - 6.0) * 2)
|
|
352
344
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
hit_px = scene.get_setting("bond_snapping_distance_2d", 14.0)
|
|
357
|
-
else:
|
|
358
|
-
hit_px = 14.0
|
|
359
|
-
scene_radius = hit_px / scale
|
|
345
|
+
def shape(self) -> QPainterPath:
|
|
346
|
+
"""Define the collision area: a fixed pixel radius, never the drawn label."""
|
|
347
|
+
scene_radius = self.hit_radius()
|
|
360
348
|
|
|
361
349
|
path = QPainterPath()
|
|
362
350
|
path.addEllipse(QPointF(0, 0), scene_radius, scene_radius)
|
|
@@ -374,11 +362,11 @@ class AtomItem(QGraphicsItem):
|
|
|
374
362
|
# Color logic: check if we should use bond color (uniform) or CPK (element-specific)
|
|
375
363
|
color = CPK_COLORS.get(self.symbol, CPK_COLORS["DEFAULT"])
|
|
376
364
|
# Use bond color if specified in settings
|
|
377
|
-
scene = self.scene()
|
|
378
|
-
if
|
|
379
|
-
self.symbol == "H" or scene.get_setting("atom_use_bond_color_2d", False)
|
|
365
|
+
scene: Any = self.scene()
|
|
366
|
+
if scene is not None and (
|
|
367
|
+
self.symbol == "H" or scene.get_setting("atom_use_bond_color_2d", False)
|
|
380
368
|
):
|
|
381
|
-
custom_color = scene.get_setting("bond_color_2d", "#222222")
|
|
369
|
+
custom_color = scene.get_setting("bond_color_2d", "#222222")
|
|
382
370
|
if isinstance(custom_color, str):
|
|
383
371
|
color = QColor(custom_color)
|
|
384
372
|
|
|
@@ -533,17 +521,17 @@ class AtomItem(QGraphicsItem):
|
|
|
533
521
|
if self.has_problem:
|
|
534
522
|
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
535
523
|
painter.setPen(QPen(QColor(255, 0, 0, 200), 4))
|
|
536
|
-
painter.drawRect(self.
|
|
524
|
+
painter.drawRect(self.visual_rect())
|
|
537
525
|
elif self.isSelected():
|
|
538
526
|
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
539
527
|
painter.setPen(QPen(QColor(0, 100, 255), 3))
|
|
540
|
-
painter.drawRect(self.
|
|
528
|
+
painter.drawRect(self.visual_rect())
|
|
541
529
|
if (not self.isSelected()) and getattr(self, "hovered", False):
|
|
542
530
|
pen = QPen(QColor(144, 238, 144, 200), 3)
|
|
543
531
|
pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
|
|
544
532
|
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
545
533
|
painter.setPen(pen)
|
|
546
|
-
painter.drawRect(self.
|
|
534
|
+
painter.drawRect(self.visual_rect())
|
|
547
535
|
|
|
548
536
|
def itemChange(self, change: QGraphicsItem.GraphicsItemChange, value: Any) -> Any:
|
|
549
537
|
"""Propagate position and scene changes to connected bond items."""
|
|
@@ -13,7 +13,7 @@ DOI: 10.5281/zenodo.17268532
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
import math
|
|
15
15
|
import logging
|
|
16
|
-
from typing import TYPE_CHECKING, Optional, Tuple, Union
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Optional, Tuple, Union
|
|
17
17
|
|
|
18
18
|
from PyQt6.QtCore import QLineF, QPointF, QRectF, Qt
|
|
19
19
|
from PyQt6.QtGui import (
|
|
@@ -43,6 +43,7 @@ from ..utils.constants import (
|
|
|
43
43
|
FONT_WEIGHT_BOLD,
|
|
44
44
|
HOVER_PEN_WIDTH,
|
|
45
45
|
)
|
|
46
|
+
from ..utils.hit_radius import scene_hit_radius
|
|
46
47
|
|
|
47
48
|
if TYPE_CHECKING:
|
|
48
49
|
from .atom_item import AtomItem
|
|
@@ -231,6 +232,10 @@ class BondItem(QGraphicsItem):
|
|
|
231
232
|
# return zero line to prevent downstream crashes.
|
|
232
233
|
return QLineF(0, 0, 0, 0)
|
|
233
234
|
|
|
235
|
+
def hit_radius(self) -> float:
|
|
236
|
+
"""Return the click/hover half-width in scene units for the current zoom."""
|
|
237
|
+
return scene_hit_radius(self.scene())
|
|
238
|
+
|
|
234
239
|
def boundingRect(self) -> QRectF:
|
|
235
240
|
"""Return the bounding rect encompassing all visual bond representations."""
|
|
236
241
|
try:
|
|
@@ -239,10 +244,10 @@ class BondItem(QGraphicsItem):
|
|
|
239
244
|
line = QLineF(0, 0, 0, 0)
|
|
240
245
|
|
|
241
246
|
# Get dynamic bond offset (spacing)
|
|
242
|
-
scene = self.scene()
|
|
247
|
+
scene: Any = self.scene()
|
|
243
248
|
bond_offset = 3.5
|
|
244
249
|
wedge_width = 6.0
|
|
245
|
-
if scene is not None
|
|
250
|
+
if scene is not None:
|
|
246
251
|
key = (
|
|
247
252
|
"bond_spacing_triple_2d"
|
|
248
253
|
if getattr(self, "order", 1) == 3
|
|
@@ -251,7 +256,9 @@ class BondItem(QGraphicsItem):
|
|
|
251
256
|
bond_offset = scene.get_setting(key, 3.5)
|
|
252
257
|
wedge_width = scene.get_setting("bond_wedge_width_2d", 6.0)
|
|
253
258
|
|
|
259
|
+
# Qt prunes clicks whose shape() escapes boundingRect().
|
|
254
260
|
extra = (getattr(self, "order", 1) - 1) * bond_offset + 50 + wedge_width
|
|
261
|
+
extra = max(extra, self.hit_radius() + 2.0)
|
|
255
262
|
rect = (
|
|
256
263
|
QRectF(line.p1(), line.p2())
|
|
257
264
|
.normalized()
|
|
@@ -262,7 +269,7 @@ class BondItem(QGraphicsItem):
|
|
|
262
269
|
if self.order == 2 and self.stereo in [3, 4]:
|
|
263
270
|
font_size = 20
|
|
264
271
|
font_family = FONT_FAMILY
|
|
265
|
-
if scene is not None
|
|
272
|
+
if scene is not None:
|
|
266
273
|
font_size = scene.get_setting("atom_font_size_2d", 20)
|
|
267
274
|
font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
|
|
268
275
|
|
|
@@ -292,25 +299,9 @@ class BondItem(QGraphicsItem):
|
|
|
292
299
|
path.moveTo(line.p1())
|
|
293
300
|
path.lineTo(line.p2())
|
|
294
301
|
|
|
295
|
-
#
|
|
296
|
-
scene = self.scene()
|
|
297
|
-
if scene and scene.views():
|
|
298
|
-
view = scene.views()[0]
|
|
299
|
-
scale = view.transform().m11()
|
|
300
|
-
if scale <= 0.0:
|
|
301
|
-
scale = 1.0
|
|
302
|
-
if hasattr(scene, "get_setting"):
|
|
303
|
-
hit_px = scene.get_setting("bond_snapping_distance_2d", 14.0)
|
|
304
|
-
else:
|
|
305
|
-
hit_px = 14.0
|
|
306
|
-
# width is diameter, so 2 * radius
|
|
307
|
-
scene_width = (hit_px * 2) / scale
|
|
308
|
-
else:
|
|
309
|
-
scene_width = 28.0
|
|
310
|
-
|
|
311
|
-
# Stroke it to give it some width
|
|
302
|
+
# Stroke it to give it some width (width is a diameter, so 2 * radius)
|
|
312
303
|
stroker = QPainterPathStroker()
|
|
313
|
-
stroker.setWidth(
|
|
304
|
+
stroker.setWidth(self.hit_radius() * 2.0)
|
|
314
305
|
path = stroker.createStroke(path)
|
|
315
306
|
|
|
316
307
|
# If there's an E/Z label, add its rect to the selection shape
|
|
@@ -362,8 +353,8 @@ class BondItem(QGraphicsItem):
|
|
|
362
353
|
bond_color = QColor("#222222")
|
|
363
354
|
|
|
364
355
|
try:
|
|
365
|
-
scene = self.scene()
|
|
366
|
-
if scene is not None
|
|
356
|
+
scene: Any = self.scene()
|
|
357
|
+
if scene is not None:
|
|
367
358
|
# Color
|
|
368
359
|
if self.isSelected():
|
|
369
360
|
bond_color = QColor("blue")
|
|
@@ -538,8 +529,8 @@ class BondItem(QGraphicsItem):
|
|
|
538
529
|
# --- Label Settings ---
|
|
539
530
|
font_size = 20
|
|
540
531
|
font_family = FONT_FAMILY
|
|
541
|
-
_sc = self.scene()
|
|
542
|
-
if _sc is not None
|
|
532
|
+
_sc: Any = self.scene()
|
|
533
|
+
if _sc is not None:
|
|
543
534
|
font_size = _sc.get_setting("atom_font_size_2d", 20)
|
|
544
535
|
font_family = _sc.get_setting(
|
|
545
536
|
"atom_font_family_2d", FONT_FAMILY
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/bond_length_dialog.py
RENAMED
|
@@ -441,7 +441,10 @@ class BondLengthDialog(GeometryBaseDialog):
|
|
|
441
441
|
if abs(worst_delta) < 1e-4:
|
|
442
442
|
return
|
|
443
443
|
with suppress_log(AttributeError, RuntimeError, TypeError):
|
|
444
|
-
self.main_window.statusBar()
|
|
444
|
+
status_bar = self.main_window.statusBar()
|
|
445
|
+
if status_bar is None:
|
|
446
|
+
return
|
|
447
|
+
status_bar.showMessage(
|
|
445
448
|
f"Bond {worst_label} also changed by {worst_delta:+.3f} A: a bond "
|
|
446
449
|
"inside a ring cannot be resized on its own.",
|
|
447
450
|
5000,
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/custom_interactor_style.py
RENAMED
|
@@ -51,8 +51,9 @@ _GROUP_ROTATION_RADIANS_PER_PIXEL = 0.008
|
|
|
51
51
|
def _rodrigues_matrix(axis: np.ndarray, angle: float) -> np.ndarray:
|
|
52
52
|
"""Return the rotation matrix for `angle` radians about `axis` (Rodrigues)."""
|
|
53
53
|
norm = float(np.linalg.norm(axis))
|
|
54
|
+
identity: np.ndarray = np.eye(3)
|
|
54
55
|
if norm < 1e-6 or abs(angle) < 1e-6:
|
|
55
|
-
return
|
|
56
|
+
return identity
|
|
56
57
|
unit = axis / norm
|
|
57
58
|
k_mat = np.array(
|
|
58
59
|
[
|
|
@@ -61,7 +62,10 @@ def _rodrigues_matrix(axis: np.ndarray, angle: float) -> np.ndarray:
|
|
|
61
62
|
[-unit[1], unit[0], 0.0],
|
|
62
63
|
]
|
|
63
64
|
)
|
|
64
|
-
|
|
65
|
+
rotation: np.ndarray = (
|
|
66
|
+
identity + np.sin(angle) * k_mat + (1.0 - np.cos(angle)) * (k_mat @ k_mat)
|
|
67
|
+
)
|
|
68
|
+
return rotation
|
|
65
69
|
|
|
66
70
|
|
|
67
71
|
class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
@@ -944,7 +948,8 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
944
948
|
speed = _GROUP_ROTATION_RADIANS_PER_PIXEL * self._rotation_sensitivity()
|
|
945
949
|
rot_y = _rodrigues_matrix(view_up, float(dx) * speed)
|
|
946
950
|
rot_x = _rodrigues_matrix(right, -float(dy) * speed)
|
|
947
|
-
|
|
951
|
+
combined: np.ndarray = rot_x @ rot_y
|
|
952
|
+
return combined
|
|
948
953
|
except (AttributeError, RuntimeError, TypeError, ValueError):
|
|
949
954
|
logging.debug("Suppressed non-critical error", exc_info=True)
|
|
950
955
|
return None
|
|
@@ -489,7 +489,10 @@ class DihedralDialog(GeometryBaseDialog):
|
|
|
489
489
|
if min(difference, 360.0 - difference) < 0.5:
|
|
490
490
|
return
|
|
491
491
|
with suppress_log(AttributeError, RuntimeError, TypeError):
|
|
492
|
-
self.main_window.statusBar()
|
|
492
|
+
status_bar = self.main_window.statusBar()
|
|
493
|
+
if status_bar is None:
|
|
494
|
+
return
|
|
495
|
+
status_bar.showMessage(
|
|
493
496
|
f"Dihedral unchanged ({achieved:.2f}°): the four atoms rotate "
|
|
494
497
|
"together, which happens for a bond inside a ring.",
|
|
495
498
|
5000,
|
|
@@ -242,7 +242,7 @@ class Edit3DManager:
|
|
|
242
242
|
|
|
243
243
|
# Position near top-right of atom
|
|
244
244
|
atom_pos = atom_item.pos()
|
|
245
|
-
atom_rect = atom_item.
|
|
245
|
+
atom_rect = atom_item.visual_rect()
|
|
246
246
|
label_pos = QPointF(
|
|
247
247
|
atom_pos.x() + atom_rect.width() / 4 + 2,
|
|
248
248
|
atom_pos.y() - atom_rect.height() / 4 - 8,
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/edit_actions_logic.py
RENAMED
|
@@ -1045,7 +1045,7 @@ class EditActionsManager:
|
|
|
1045
1045
|
# Only prepare a geometry change if the implicit H count
|
|
1046
1046
|
# changes (this may affect the item's bounding rect).
|
|
1047
1047
|
need_geometry = current != new_count
|
|
1048
|
-
if need_geometry
|
|
1048
|
+
if need_geometry:
|
|
1049
1049
|
item.prepareGeometryChange()
|
|
1050
1050
|
item.implicit_h_count = new_count
|
|
1051
1051
|
item.has_problem = bool(desired_prob)
|
|
@@ -495,22 +495,20 @@ class IOManager:
|
|
|
495
495
|
self.host.statusBar().showMessage("Error: Nothing to save.")
|
|
496
496
|
return
|
|
497
497
|
|
|
498
|
-
|
|
499
|
-
if
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
498
|
+
current_path = self.host.init_manager.current_file_path
|
|
499
|
+
if current_path and current_path.lower().endswith(".pmeraw"):
|
|
500
|
+
# Never write the legacy pickle back; Save As only emits .pmeprj.
|
|
501
|
+
self.host.update_status_message(
|
|
502
|
+
"Raw (.pmeraw) files are read-only — saving as .pmeprj instead."
|
|
503
|
+
)
|
|
504
|
+
self.save_project_as()
|
|
505
|
+
return
|
|
506
|
+
|
|
507
|
+
if current_path and current_path.lower().endswith(".pmeprj"):
|
|
503
508
|
try:
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
pickle.dump(save_data, f)
|
|
508
|
-
else:
|
|
509
|
-
json_data = self.host.state_manager.create_json_data()
|
|
510
|
-
with open(
|
|
511
|
-
self.host.init_manager.current_file_path, "w", encoding="utf-8"
|
|
512
|
-
) as f:
|
|
513
|
-
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
|
509
|
+
json_data = self.host.state_manager.create_json_data()
|
|
510
|
+
with open(current_path, "w", encoding="utf-8") as f:
|
|
511
|
+
json.dump(json_data, f, indent=2, ensure_ascii=False)
|
|
514
512
|
|
|
515
513
|
self.host.set_has_unsaved_changes(False)
|
|
516
514
|
self.host.state_manager.update_window_title()
|
|
@@ -1109,6 +1107,25 @@ class IOManager:
|
|
|
1109
1107
|
except (OSError, IOError, ValueError, RuntimeError, AttributeError) as e:
|
|
1110
1108
|
self.host.statusBar().showMessage(f"Error saving XYZ: {e}")
|
|
1111
1109
|
|
|
1110
|
+
def _confirm_pickle_load(self, file_path: str) -> bool:
|
|
1111
|
+
"""Ask before unpickling a .pmeraw file, which can run arbitrary code."""
|
|
1112
|
+
box = QMessageBox(self.host)
|
|
1113
|
+
box.setIcon(QMessageBox.Icon.Warning)
|
|
1114
|
+
box.setWindowTitle("Open Raw Project File?")
|
|
1115
|
+
box.setText("This file is in the legacy raw (pickle) format.")
|
|
1116
|
+
box.setInformativeText(
|
|
1117
|
+
f"{os.path.basename(file_path)}\n\n"
|
|
1118
|
+
"Raw project files can execute arbitrary code when opened. "
|
|
1119
|
+
"Only open .pmeraw files that you created yourself or received "
|
|
1120
|
+
"from a source you trust.\n\n"
|
|
1121
|
+
"The .pmeprj format is safe and is recommended instead."
|
|
1122
|
+
)
|
|
1123
|
+
open_button = box.addButton("Open", QMessageBox.ButtonRole.AcceptRole)
|
|
1124
|
+
cancel_button = box.addButton(QMessageBox.StandardButton.Cancel)
|
|
1125
|
+
box.setDefaultButton(cancel_button)
|
|
1126
|
+
box.exec()
|
|
1127
|
+
return box.clickedButton() is open_button
|
|
1128
|
+
|
|
1112
1129
|
def load_raw_data(self, file_path: Optional[str] = None) -> None:
|
|
1113
1130
|
"""Open a .pmeraw pickle project file."""
|
|
1114
1131
|
if not self.host.state_manager.check_unsaved_changes():
|
|
@@ -1129,6 +1146,10 @@ class IOManager:
|
|
|
1129
1146
|
if not file_path:
|
|
1130
1147
|
return
|
|
1131
1148
|
|
|
1149
|
+
if not self._confirm_pickle_load(file_path):
|
|
1150
|
+
self.host.update_status_message("Opening raw project file cancelled.")
|
|
1151
|
+
return
|
|
1152
|
+
|
|
1132
1153
|
if not self.host.edit_actions_manager.clear_all(skip_check=True):
|
|
1133
1154
|
return
|
|
1134
1155
|
|
|
@@ -223,7 +223,7 @@ class MainInitManager:
|
|
|
223
223
|
"""Resolve the atom-label font now, in the user's configured family."""
|
|
224
224
|
scene = getattr(self, "scene", None)
|
|
225
225
|
font = None
|
|
226
|
-
if scene is not None
|
|
226
|
+
if scene is not None:
|
|
227
227
|
family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
|
|
228
228
|
size = scene.get_setting("atom_font_size_2d", 20)
|
|
229
229
|
bold = scene.get_setting("atom_font_bold_2d", True)
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/molecular_scene_handler.py
RENAMED
|
@@ -1396,6 +1396,7 @@ class SceneQueryMixin:
|
|
|
1396
1396
|
items: Any
|
|
1397
1397
|
update_all_items: Any
|
|
1398
1398
|
window: Any
|
|
1399
|
+
views: Any
|
|
1399
1400
|
addItem: Any
|
|
1400
1401
|
removeItem: Any
|
|
1401
1402
|
|
|
@@ -1592,20 +1593,15 @@ class SceneQueryMixin:
|
|
|
1592
1593
|
def find_atom_near(self, pos: Any, tol: float = 14.0) -> Any:
|
|
1593
1594
|
"""Return the AtomItem within tolerance distance of pos, or None.
|
|
1594
1595
|
|
|
1595
|
-
``tol`` is
|
|
1596
|
-
current view scale so that the effective snap radius stays constant
|
|
1597
|
-
on screen regardless of zoom level — matching the behaviour of
|
|
1598
|
-
``AtomItem.shape()`` (hover highlight).
|
|
1596
|
+
``tol`` is in screen pixels, divided by the view scale to match shape().
|
|
1599
1597
|
"""
|
|
1600
1598
|
if pos is None:
|
|
1601
1599
|
return None
|
|
1602
1600
|
# Convert screen-pixel tolerance to scene units (zoom-aware).
|
|
1603
1601
|
if self.views():
|
|
1604
|
-
|
|
1605
|
-
if
|
|
1606
|
-
|
|
1607
|
-
if scale > 0:
|
|
1608
|
-
tol = tol / scale
|
|
1602
|
+
scale = self.views()[0].transform().m11()
|
|
1603
|
+
if scale > 0:
|
|
1604
|
+
tol = tol / scale
|
|
1609
1605
|
# Create a small search rectangle around the position
|
|
1610
1606
|
search_rect = QRectF(pos.x() - tol, pos.y() - tol, 2 * tol, 2 * tol)
|
|
1611
1607
|
nearby_items = self.items(search_rect)
|
|
@@ -190,10 +190,11 @@ class Settings2DTab(SettingsTabBase):
|
|
|
190
190
|
|
|
191
191
|
form_layout.addRow(self._create_separator())
|
|
192
192
|
|
|
193
|
-
# ---
|
|
194
|
-
form_layout.addRow(QLabel("<b>
|
|
193
|
+
# --- Atom Hit / Snap Settings ---
|
|
194
|
+
form_layout.addRow(QLabel("<b>Atom Hit / Snap Settings</b>"))
|
|
195
195
|
|
|
196
|
-
#
|
|
196
|
+
# Atom hit radius (also used for bond snapping) — internal key is
|
|
197
|
+
# kept as bond_snapping_distance_2d so existing settings files load.
|
|
197
198
|
self.bond_snapping_distance_2d_slider, self.bond_snapping_distance_2d_label = (
|
|
198
199
|
self._create_slider(5, 50, 1.0, is_int=True)
|
|
199
200
|
)
|
|
@@ -201,10 +202,10 @@ class Settings2DTab(SettingsTabBase):
|
|
|
201
202
|
"The distance in screen pixels (zoom-independent) within which:\n"
|
|
202
203
|
"• Drawing a bond snaps to an existing atom\n"
|
|
203
204
|
"• Keyboard shortcuts instantly edit the atom under cursor\n"
|
|
204
|
-
"• Atoms show hover highlight"
|
|
205
|
+
"• Atoms and bonds show hover highlight and respond to clicks"
|
|
205
206
|
)
|
|
206
207
|
form_layout.addRow(
|
|
207
|
-
"
|
|
208
|
+
"Atom Hit / Snap Distance (px):",
|
|
208
209
|
self._wrap_layout(
|
|
209
210
|
self.bond_snapping_distance_2d_slider,
|
|
210
211
|
self.bond_snapping_distance_2d_label,
|
|
@@ -14,7 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
|
|
15
15
|
from typing import Optional, cast
|
|
16
16
|
|
|
17
|
-
from PyQt6.QtCore import QEvent, QPointF, Qt
|
|
17
|
+
from PyQt6.QtCore import QEvent, QPointF, Qt, QTimer
|
|
18
18
|
from PyQt6.QtGui import QMouseEvent, QWheelEvent, QNativeGestureEvent
|
|
19
19
|
from PyQt6.QtWidgets import QGraphicsScene, QGraphicsView, QWidget
|
|
20
20
|
|
|
@@ -38,6 +38,14 @@ class ZoomableView(QGraphicsView):
|
|
|
38
38
|
self._pan_start_scroll_h = 0
|
|
39
39
|
self._pan_start_scroll_v = 0
|
|
40
40
|
|
|
41
|
+
# Coalesces re-indexing to once per zoom gesture; doing it per wheel
|
|
42
|
+
# tick dirties every item and costs ~40x the zoom step on a large
|
|
43
|
+
# structure.
|
|
44
|
+
self._geometry_timer = QTimer(self)
|
|
45
|
+
self._geometry_timer.setSingleShot(True)
|
|
46
|
+
self._geometry_timer.setInterval(50)
|
|
47
|
+
self._geometry_timer.timeout.connect(self._invalidate_item_geometry)
|
|
48
|
+
|
|
41
49
|
def wheelEvent(self, event: Optional[QWheelEvent]) -> None:
|
|
42
50
|
"""Event handler for mouse wheel rotation"""
|
|
43
51
|
if event is None:
|
|
@@ -123,6 +131,28 @@ class ZoomableView(QGraphicsView):
|
|
|
123
131
|
else:
|
|
124
132
|
super().mouseReleaseEvent(event)
|
|
125
133
|
|
|
134
|
+
def _invalidate_item_geometry(self) -> None:
|
|
135
|
+
"""Re-index items: their pixel-sized hit geometry changes with zoom."""
|
|
136
|
+
scene = self.scene()
|
|
137
|
+
if scene is None:
|
|
138
|
+
return
|
|
139
|
+
for item in scene.items():
|
|
140
|
+
# A wrapper deleted mid-iteration (scene teardown) raises.
|
|
141
|
+
try:
|
|
142
|
+
item.prepareGeometryChange()
|
|
143
|
+
except RuntimeError:
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
def scale(self, sx: float, sy: float) -> None:
|
|
147
|
+
"""Scale the view, then re-index items once the gesture settles."""
|
|
148
|
+
super().scale(sx, sy)
|
|
149
|
+
self._geometry_timer.start()
|
|
150
|
+
|
|
151
|
+
def resetTransform(self) -> None:
|
|
152
|
+
"""Reset the view transform and re-index items for the new scale."""
|
|
153
|
+
super().resetTransform()
|
|
154
|
+
self._geometry_timer.start()
|
|
155
|
+
|
|
126
156
|
def viewportEvent(self, event: Optional[QEvent]) -> bool:
|
|
127
157
|
"""Handle native gestures (like pinch zoom on trackpads)"""
|
|
128
158
|
if event is None:
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
MoleditPy — A Python-based molecular editing software
|
|
6
|
+
|
|
7
|
+
Author: Hiromichi Yokoyama
|
|
8
|
+
License: GPL-3.0 license
|
|
9
|
+
Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
10
|
+
DOI: 10.5281/zenodo.17268532
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
DEFAULT_HIT_PX = 14.0
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def scene_hit_radius(scene: Any, fallback: float = DEFAULT_HIT_PX) -> float:
|
|
19
|
+
"""Return the 2D hit/snap radius in scene units for the current zoom.
|
|
20
|
+
|
|
21
|
+
bond_snapping_distance_2d is in screen pixels, so it is divided by the view
|
|
22
|
+
scale; shape() and find_atom_near() share this to stay in step.
|
|
23
|
+
"""
|
|
24
|
+
if not scene or not scene.views():
|
|
25
|
+
return fallback
|
|
26
|
+
|
|
27
|
+
scale = scene.views()[0].transform().m11()
|
|
28
|
+
if scale <= 0.0:
|
|
29
|
+
scale = 1.0
|
|
30
|
+
|
|
31
|
+
hit_px = scene.get_setting("bond_snapping_distance_2d", DEFAULT_HIT_PX)
|
|
32
|
+
return float(hit_px / scale)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/MoleditPy_linux.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/MoleditPy_linux.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/plugins/plugin_manager.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/plugins/plugin_manager_window.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/align_plane_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/base_picking_dialog.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/calculation_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/color_settings_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/custom_qt_interactor.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/geometry_base_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/move_selected_atoms_dialog.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/periodic_table_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/plugin_menu_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/settings_tabs/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/template_preview_item.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/template_preview_view.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/translation_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/ui/user_template_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/utils/default_settings.py
RENAMED
|
File without changes
|
{moleditpy_linux-4.6.1 → moleditpy_linux-4.7.1}/src/moleditpy_linux/utils/sip_isdeleted_safe.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|