feat: add explicit left/right orientation with swap button
Fixes mirrored slice exports by making orientation explicit: - PhotoSlice.swapped flag persists a user left/right swap - swap_left_right() mirrors [TL,TR,BR,BL] -> [TR,TL,BL,BR] - normalize_top_left() honors the flag so squarize/reset_rotation no longer overwrite manual orientation - Swap L/R toolbar button wired through canvas state + controller - Color-code left (orange) vs right (cyan) corners and edges
This commit is contained in:
parent
047ea60fa5
commit
607f5e0c57
7 changed files with 114 additions and 31 deletions
|
|
@ -28,14 +28,22 @@ class CanvasRenderMixin:
|
|||
b = points[(i + 1) % 4]
|
||||
if i == 0:
|
||||
color = QColor("red")
|
||||
elif i == 1:
|
||||
color = QColor("green")
|
||||
elif i == 2:
|
||||
color = QColor("orange")
|
||||
else:
|
||||
color = QColor("blue") if s.locked else QColor("lightgreen")
|
||||
painter.setPen(QPen(color, 3))
|
||||
painter.drawLine(a, b)
|
||||
|
||||
painter.setPen(QPen(QColor("blue"), 2))
|
||||
painter.setBrush(QColor(128, 128, 128, 170))
|
||||
for p in points:
|
||||
# Corners are color-coded: left = orange, right = cyan.
|
||||
# Edge 0 (top, red) runs TL->TR, so corners 0 and 3 are the left
|
||||
# side and corners 1 and 2 are the right side.
|
||||
corner_colors = (QColor("orange"), QColor("cyan"), QColor("cyan"), QColor("orange"))
|
||||
for p, color in zip(points, corner_colors):
|
||||
painter.setPen(QPen(QColor("blue"), 2))
|
||||
painter.setBrush(color)
|
||||
painter.drawRect(QRectF(p.x() - 5, p.y() - 5, 10, 10))
|
||||
|
||||
center = np.mean(s.bbox, axis=0)
|
||||
|
|
|
|||
|
|
@ -192,6 +192,15 @@ class CanvasStateMixin:
|
|||
self.update()
|
||||
return True
|
||||
|
||||
# swap_active_slice_left_right: function definition for ScanSlicer behavior.
|
||||
def swap_active_slice_left_right(self):
|
||||
active_slice = self.get_active_slice()
|
||||
if active_slice is None:
|
||||
return False
|
||||
active_slice.swap_left_right()
|
||||
self.update()
|
||||
return True
|
||||
|
||||
# reset_active_slice_rotation: function definition for ScanSlicer behavior.
|
||||
def reset_active_slice_rotation(self):
|
||||
active_slice = self.get_active_slice()
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class ScanSlicerCanvasActionsMixin:
|
|||
self.active_box_lock_button.setEnabled(False)
|
||||
self.active_box_squarize_button.setEnabled(False)
|
||||
self.active_box_reset_rotation_button.setEnabled(False)
|
||||
self.active_box_swap_button.setEnabled(False)
|
||||
self.active_box_remove_button.setEnabled(False)
|
||||
self.active_box_lock_button.blockSignals(True)
|
||||
self.active_box_lock_button.setChecked(False)
|
||||
|
|
@ -38,6 +39,7 @@ class ScanSlicerCanvasActionsMixin:
|
|||
self.active_box_lock_button.setEnabled(True)
|
||||
self.active_box_squarize_button.setEnabled(True)
|
||||
self.active_box_reset_rotation_button.setEnabled(True)
|
||||
self.active_box_swap_button.setEnabled(True)
|
||||
self.active_box_remove_button.setEnabled(True)
|
||||
self.active_box_lock_button.blockSignals(True)
|
||||
self.active_box_lock_button.setChecked(active_slice.locked)
|
||||
|
|
@ -69,6 +71,13 @@ class ScanSlicerCanvasActionsMixin:
|
|||
return
|
||||
self.update_statusbar("Active box squarized.")
|
||||
|
||||
# swap_active_box_left_right: function definition for ScanSlicer behavior.
|
||||
def swap_active_box_left_right(self):
|
||||
if not self.slicing_canvas.swap_active_slice_left_right():
|
||||
self.update_statusbar("No active box selected.")
|
||||
return
|
||||
self.update_statusbar("Active box left/right swapped.")
|
||||
|
||||
# reset_active_box_rotation: function definition for ScanSlicer behavior.
|
||||
def reset_active_box_rotation(self):
|
||||
if not self.slicing_canvas.reset_active_slice_rotation():
|
||||
|
|
|
|||
|
|
@ -230,6 +230,14 @@ class QtScanSlicer(QMainWindow, ScanSlicerUiBuilderMixin, ScanSlicerControllerMi
|
|||
"NzRMMjEgMTYiLz48cGF0aCBkPSJNMTYgMTZoNXY1Ii8+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMSIv"
|
||||
"Pjwvc3ZnPg=="
|
||||
)
|
||||
SWAP_LR_ICON_DATA_URL = (
|
||||
"data:image/svg+xml;base64,"
|
||||
"PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIg"
|
||||
"dmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdp"
|
||||
"ZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9"
|
||||
"Ik04IDMgNCA3bDQgNCIvPjxwYXRoIGQ9Ik00IDdoMTYiLz48cGF0aCBkPSJtMTYgMjEgNC00LTQtNCIvPjxw"
|
||||
"YXRoIGQ9Ik0yMCAxN0g0Ii8+PC9zdmc+"
|
||||
)
|
||||
RESET_ROTATION_ICON_DATA_URL = (
|
||||
"data:image/svg+xml;base64,"
|
||||
"PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIg"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ class PhotoSlice:
|
|||
self.locked = False
|
||||
# Side-length handles are an explicit edit mode, enabled by squarize.
|
||||
self.side_handles_enabled = False
|
||||
# False = default winding, True = explicit left/right swap by the user.
|
||||
# Persists through squarize/reset_rotation so manual orientation wins.
|
||||
self.swapped = False
|
||||
|
||||
@property
|
||||
# bbox: function definition for ScanSlicer behavior.
|
||||
|
|
@ -49,8 +52,23 @@ class PhotoSlice:
|
|||
if horizontal_score(reversed_winding) > horizontal_score(ordered):
|
||||
ordered = reversed_winding
|
||||
|
||||
# A user-requested left/right swap overrides the geometric inference,
|
||||
# so manual orientation survives normalize_top_left().
|
||||
if getattr(self, "swapped", False):
|
||||
ordered = ordered[[1, 0, 3, 2]]
|
||||
|
||||
self.bbox = ordered
|
||||
|
||||
# swap_left_right: function definition for ScanSlicer behavior.
|
||||
def swap_left_right(self):
|
||||
# Mirror left/right: swap the two top corners and the two bottom
|
||||
# corners, keeping the top (edge 0->1) and bottom edges in place.
|
||||
# [TL, TR, BR, BL] <-> [TR, TL, BL, BR].
|
||||
self.swapped = not self.swapped
|
||||
self.bbox = self.bbox[[1, 0, 3, 2]]
|
||||
self._rect_cache = None
|
||||
self._rect_cache_tol = None
|
||||
|
||||
# toggle_locked: function definition for ScanSlicer behavior.
|
||||
def toggle_locked(self, locked=None):
|
||||
if locked is not None:
|
||||
|
|
|
|||
|
|
@ -123,6 +123,16 @@ class ScanSlicerUiPreviewMixin(ScanSlicerUiSharedMixin):
|
|||
)
|
||||
preview_toolbar_layout.addWidget(self.active_box_reset_rotation_button)
|
||||
|
||||
self.active_box_swap_button = self._create_toolbar_button(
|
||||
preview_toolbar,
|
||||
text="Swap L/R",
|
||||
tooltip="Swap left/right orientation of the active box",
|
||||
icon_data_url=self.SWAP_LR_ICON_DATA_URL,
|
||||
callback=self.swap_active_box_left_right,
|
||||
enabled=False,
|
||||
)
|
||||
preview_toolbar_layout.addWidget(self.active_box_swap_button)
|
||||
|
||||
self.add_box_button = self._create_toolbar_button(
|
||||
preview_toolbar,
|
||||
text="Add box",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue