From a70b8d3b7b09fbc701bde88df52cdbf34e8ad2a9 Mon Sep 17 00:00:00 2001 From: Arseniy Terekhin Date: Tue, 8 Oct 2024 11:48:45 +0300 Subject: [PATCH] feat: shift key enables size/angle snapping With SHIFT key: - Rectangles become squares - Lines snap to 15deg - Points snap to integer coordinates --- gmc/__init__.py | 2 +- gmc/markup_objects/line.py | 13 +++---------- gmc/markup_objects/point.py | 6 ++++-- gmc/markup_objects/polygon.py | 13 +++++++++++-- gmc/markup_objects/quadrangle.py | 20 ++++++++++++-------- gmc/markup_objects/rect.py | 12 +++++++++++- pyproject.toml | 2 +- 7 files changed, 43 insertions(+), 25 deletions(-) diff --git a/gmc/__init__.py b/gmc/__init__.py index a06ff4e..3a384fb 100644 --- a/gmc/__init__.py +++ b/gmc/__init__.py @@ -1 +1 @@ -__version__ = "1.5.3" +__version__ = "1.5.4" diff --git a/gmc/markup_objects/line.py b/gmc/markup_objects/line.py index 9a8e0ac..bf4fe1f 100644 --- a/gmc/markup_objects/line.py +++ b/gmc/markup_objects/line.py @@ -2,7 +2,7 @@ from ..views.image_view import ImageView from ..settings import settings -from .polygon import MarkupPolygon, UndoPolygonCreate +from .polygon import MarkupPolygon, EditableMarkupPolygon, UndoPolygonCreate Qt = QtCore.Qt @@ -52,11 +52,7 @@ def mouse_press(self, event: QtGui.QMouseEvent, view: ImageView) -> bool: view.scene().addItem(self) return True - def mouse_move(self, event: QtGui.QMouseEvent, view: ImageView) -> bool: - self._polygon[1] = view.mapToScene(event.pos()) - self.update() - self.on_change_polygon(self._polygon) - return True + mouse_move = EditableMarkupPolygon.mouse_move def mouse_release(self, event: QtGui.QMouseEvent, view: ImageView) -> bool: self.mouse_move(event, view) @@ -73,7 +69,4 @@ def mouse_release(self, event: QtGui.QMouseEvent, view: ImageView) -> bool: view.set_mouse_press(self.prevent_event) return True - def cancel(self, view: ImageView): - view.unset_all_events() - view.scene().removeItem(self) - view.set_mouse_press(self.mouse_press) # start over + cancel = EditableMarkupPolygon.cancel diff --git a/gmc/markup_objects/point.py b/gmc/markup_objects/point.py index 7bd3d42..ebefc4b 100644 --- a/gmc/markup_objects/point.py +++ b/gmc/markup_objects/point.py @@ -70,9 +70,11 @@ def on_change_point(self, _): pass # for overriding def mouse_press(self, event: QtGui.QMouseEvent, view: ImageView) -> bool: - self._create_pos = view.mapToScene(event.pos()) + pos = view.mapToScene(event.pos()) + if QtGui.QGuiApplication.keyboardModifiers() == Qt.Modifier.SHIFT: + pos = QPointF(round(pos.x(), round(pos.y()))) scene = view.scene() - scene.undo_stack.push(UndoPointCreate(scene, self, self._create_pos)) + scene.undo_stack.push(UndoPointCreate(scene, self, pos)) view.set_mouse_move(self.mouse_move) view.set_mouse_release(self.mouse_release) view.set_mouse_press(None) diff --git a/gmc/markup_objects/polygon.py b/gmc/markup_objects/polygon.py index 1496ec2..58f27a1 100644 --- a/gmc/markup_objects/polygon.py +++ b/gmc/markup_objects/polygon.py @@ -4,7 +4,7 @@ from ..views.image_view import ImageView from . import MarkupObjectMeta from .moveable_diamond import MoveableDiamond -from math import hypot +from math import hypot, atan2, degrees, radians, sin, cos from typing import Any, Callable from PyQt5.QtCore import Qt, QPointF, QCoreApplication, QRectF @@ -239,7 +239,16 @@ def mouse_doubleclick( return True def mouse_move(self, event: QtGui.QMouseEvent, view: ImageView) -> bool: - self._polygon[-1] = view.mapToScene(event.pos()) + pt = view.mapToScene(event.pos()) + if QtGui.QGuiApplication.keyboardModifiers() == Qt.ShiftModifier: + step_deg = 15 + a: QPointF = self._polygon[-2] + dx, dy = pt.x() - a.x(), pt.y() - a.y() + angle = degrees(atan2(dx, dy)) + step_deg * 0.5 + d = hypot(dx, dy) + fixed_angle = radians(angle - angle % step_deg) + pt = a + QPointF(d * sin(fixed_angle), d * cos(fixed_angle)) + self._polygon[-1] = pt self.update() self.on_change_polygon(self._polygon) return True diff --git a/gmc/markup_objects/quadrangle.py b/gmc/markup_objects/quadrangle.py index d7a9667..ef763e3 100644 --- a/gmc/markup_objects/quadrangle.py +++ b/gmc/markup_objects/quadrangle.py @@ -1,3 +1,4 @@ +from math import copysign from PyQt5 import QtGui, QtCore, QtWidgets from ..markup_objects.moveable_diamond import MoveableDiamond from ..views.image_view import ImageView @@ -72,14 +73,17 @@ def mouse_move_sequential( def mouse_move(self, event: QtGui.QMouseEvent, view: ImageView) -> bool: p0 = self._polygon.at(0) p2 = view.mapToScene(event.pos()) - polygon = QtGui.QPolygonF( - [ - p0, - QtCore.QPointF(p2.x(), p0.y()), - p2, - QtCore.QPointF(p0.x(), p2.y()), - ] - ) + if QtGui.QGuiApplication.keyboardModifiers() != Qt.ShiftModifier: + p1 = QtCore.QPointF(p2.x(), p0.y()) + p3 = QtCore.QPointF(p0.x(), p2.y()) + else: + w, h = p2.x() - p0.x(), p2.y() - p0.y() + size = max(abs(w), abs(h)) + w, h = copysign(size, w), copysign(size, h) + p1 = QtCore.QPointF(p0.x() + w, p0.y()) + p2 = QtCore.QPointF(p0.x() + w, p0.y() + h) + p3 = QtCore.QPointF(p0.x(), p0.y() + h) + polygon = QtGui.QPolygonF((p0, p1, p2, p3)) self._polygon = polygon self.update() self.on_change_polygon(polygon) diff --git a/gmc/markup_objects/rect.py b/gmc/markup_objects/rect.py index dd58442..a01fbbc 100644 --- a/gmc/markup_objects/rect.py +++ b/gmc/markup_objects/rect.py @@ -1,4 +1,5 @@ from typing import Any +from math import copysign from PyQt5 import QtGui, QtWidgets from PyQt5.QtCore import Qt, QRectF, QPointF, QSizeF, QElapsedTimer @@ -158,7 +159,16 @@ def mouse_press(self, event: QtGui.QMouseEvent, view: ImageView) -> bool: return True # if not 'return True', back objects will be selected def mouse_move(self, event: QtGui.QMouseEvent, view: ImageView) -> bool: - self._rect.setBottomRight(view.mapToScene(event.pos())) + if QtGui.QGuiApplication.keyboardModifiers() != Qt.ShiftModifier: + self._rect.setBottomRight(view.mapToScene(event.pos())) + else: + rc = self._rect + rc.setBottomRight(view.mapToScene(event.pos())) + w, h = rc.width(), rc.height() + size = max(abs(w), abs(h)) + rc.setWidth(copysign(size, w)) + rc.setHeight(copysign(size, h)) + self._rect = rc self.update() self.on_change_rect(self._rect) return True diff --git a/pyproject.toml b/pyproject.toml index 68e66a2..a9a3d85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "run-gmc" -version = "1.5.3" +version = "1.5.4" authors = [ { name="Arseniy Terekhin", email="senyai@gmail.com" }, ]