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:
dan 2026-09-11 21:33:59 +02:00
parent 047ea60fa5
commit 607f5e0c57
7 changed files with 114 additions and 31 deletions

View file

@ -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: