From 3c225809b95f9b58d9bb8104535345d57e357b94 Mon Sep 17 00:00:00 2001 From: Eugene Mikhantyev Date: Sun, 3 May 2026 21:50:14 +0100 Subject: [PATCH] =?UTF-8?q?fix(pin=5Flocator):=20apply=20lib=E2=86=92scree?= =?UTF-8?q?n=20Y-flip=20to=20get=5Fpin=5Fangle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #145 restored the Y-axis flip in WireDragger.pin_world_xy so pin coordinates now match the schematic (Y-down) frame instead of the library (Y-up) frame. PinLocator.get_pin_angle was the companion to that transform but never received the matching fix: it was returning the library-frame angle (with mirror handling but no Y-flip), so angles came out 180° off along the Y axis. This was masked before PR #145 because pin_world_xy was wrong in the same direction — both functions skipped the Y-flip, so callers that compared pin endpoints to angles saw a self-consistent picture. Once pin_world_xy was corrected the inconsistency surfaced. Apply the same lib→screen Y-flip (negate angle) after the mirror handling and before the symbol-rotation add, matching pin_world_xy's order: mirror in lib space → Y-flip → rotate → translate (no translate for angles since angles are translation-invariant). Fixes the 24 parametrized cases in tests/test_get_pin_angle.py::test_get_pin_angle_matches_geometric_expectation (pin × mirror × rotation matrix). The test derives its expected value from pin_world_xy itself, making it the canonical geometric oracle. test_pin_locator_y_flip and test_move_with_wire_preservation continue to pass. Co-Authored-By: Claude Opus 4.7 --- python/commands/pin_locator.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python/commands/pin_locator.py b/python/commands/pin_locator.py index 5b664b4..5b1c2ae 100644 --- a/python/commands/pin_locator.py +++ b/python/commands/pin_locator.py @@ -287,6 +287,15 @@ class PinLocator: if mirror_y: pin_def_angle = (-pin_def_angle) % 360 + # Library symbols are Y-up; the schematic is Y-down. Match the + # lib→screen Y-flip applied by WireDragger.pin_world_xy (mirror in + # lib space → Y-flip → rotate → translate). For an angle this + # negates the Y component, i.e. negates the angle. Without this + # step pin angles are 180° off along the Y axis; before PR #145 + # this was masked because pin_world_xy was missing the same flip, + # so the two were "wrong in the same direction" and consistent. + pin_def_angle = (-pin_def_angle) % 360 + absolute_angle = (pin_def_angle + symbol_rotation) % 360 return absolute_angle