From fd22eb314d0aa17ae14c8c846d8de2d18a59a808 Mon Sep 17 00:00:00 2001 From: Arda Aydin Date: Tue, 6 Oct 2026 19:45:39 +0300 Subject: [PATCH] Wiresheet: make the last wire turn 1.5 grid before the target arrow The final vertical run into an input slot now stands at least 1.5 * GRID_SIZE in front of the box, so the arrowhead sits on a straight run instead of on the bend. Column gaps in tidy layout widen to match. Co-Authored-By: Claude Opus 5.5 --- app_state.py | 2 +- wiresheet.py | 41 ++++++++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/app_state.py b/app_state.py index b4d5648..9c9c6ba 100644 --- a/app_state.py +++ b/app_state.py @@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it. import configparser import os -VERSION = "0.0.0.148" +VERSION = "0.0.0.149" PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties") DEFAULT_GRID_SIZE = 10 DEFAULT_ADD_TIMESTAMP = True diff --git a/wiresheet.py b/wiresheet.py index b1b305d..fab6f61 100644 --- a/wiresheet.py +++ b/wiresheet.py @@ -33,6 +33,10 @@ WIRE_LANE_GAP = 6 # knobs, the same way a link leaving the sheet is. WIRE_STANDOFF_MAX = 60 +# The last turn before a target slot is kept this many grid squares back from the box, +# so the arrowhead always sits on a straight run of its own instead of on the bend. +ARRIVE_TURN_GRIDS = 1.5 + # Two different wires running horizontally at the same height are indistinguishable, so # a free horizontal lane is nudged until it clears any other wire's by this much. The # short stubs joining a slot cannot move -- they have to meet their pin -- so a fan-out @@ -315,6 +319,11 @@ def vertical_run_clearance(cx, y1, y2, boxes): return nearest +def arrive_turn(): + """How far in front of a target box the wire makes its last turn, at 1:1.""" + return max(WIRE_HUG, ARRIVE_TURN_GRIDS * app_state.GRID_SIZE) + + def channel_candidates(src_box, dst_box, boxes, low, high): """Vertical channel positions to try, best first. @@ -322,7 +331,7 @@ def channel_candidates(src_box, dst_box, boxes, low, high): that a vertical line stays within WIRE_HUG of a component. The midpoint of the gap is kept only as a last resort, for when every hug position is blocked. """ - ordered = [src_box[2] + WIRE_HUG, dst_box[0] - WIRE_HUG] + ordered = [src_box[2] + WIRE_HUG, dst_box[0] - arrive_turn()] for bx1, _, bx2, _ in boxes: ordered.append(bx2 + WIRE_HUG) ordered.append(bx1 - WIRE_HUG) @@ -362,18 +371,22 @@ def free_lane(cx, run_lo, run_hi, boxes, taken, low, high, max_steps=8): return None -def hug_lane(edge_x, side, run_lo, run_hi, boxes, claimed, max_offset=WIRE_HUG): +def hug_lane(edge_x, side, run_lo, run_hi, boxes, claimed, max_offset=WIRE_HUG, + min_offset=WIRE_MIN_CLEAR): """Lane for a run pinned to one box edge, starting WIRE_MIN_CLEAR out from it. `side` is +1 for a run leaving a box's right edge, -1 for one entering its left edge. Offsets inside the hug band are offered first, furthest out of the band first so the run keeps clear of its own box; only when every one of those is taken does it reach past WIRE_HUG, out to `max_offset`. Returns None when nothing up to - that limit clears both the boxes and the runs already placed. + that limit clears both the boxes and the runs already placed. `min_offset` raises + the floor, and the hug position with it, for a run that must stand further off. """ - offsets = list(range(WIRE_HUG, WIRE_MIN_CLEAR - 1, -1)) - if max_offset > WIRE_HUG: - offsets += list(range(WIRE_HUG + 1, int(max_offset) + 1)) + floor = max(WIRE_MIN_CLEAR, int(math.ceil(min_offset))) + hug = max(WIRE_HUG, floor) + offsets = list(range(hug, floor - 1, -1)) + if max_offset > hug: + offsets += list(range(hug + 1, int(max_offset) + 1)) for offset in offsets: lane = edge_x + side * offset @@ -437,7 +450,8 @@ def route_link_path(src_box, src_y, dst_box, dst_y, boxes, taken=None, start = (src_box[2], src_y) end = (dst_box[0], dst_y) leave = (start[0] + LINK_STUB, src_y) - arrive = (end[0] - LINK_STUB, dst_y) + # The last turn stands arrive_turn() in front of the target, never closer. + arrive = (end[0] - max(LINK_STUB, arrive_turn()), dst_y) low, high = min(leave[0], arrive[0]), max(leave[0], arrive[0]) run_lo, run_hi = min(src_y, dst_y), max(src_y, dst_y) @@ -509,7 +523,8 @@ def route_link_path(src_box, src_y, dst_box, dst_y, boxes, taken=None, continue in_x = hug_lane(dst_box[0], -1, min(base_cy, dst_y), max(base_cy, dst_y), boxes, claimed + [(out_x, min(src_y, base_cy), - max(src_y, base_cy))], max_offset) + max(src_y, base_cy))], max_offset, + min_offset=arrive_turn()) if in_x is None: continue @@ -1205,12 +1220,12 @@ def wire_demand_by_boundary(column_of_comp): def column_gap_for(run_count): """Clear space a column gap needs to carry `run_count` vertical wire runs. - Lanes start WIRE_MIN_CLEAR in from either box edge and stand WIRE_LANE_GAP apart, so - n of them span 2 * WIRE_MIN_CLEAR + (n - 1) * WIRE_LANE_GAP. One or two runs already - fit inside the plain TIDY_GAP -- at 10 and 20 across a 30px gap, both still hugging - a box -- which is why the great majority of gaps stay at exactly TIDY_GAP. + Lanes start WIRE_MIN_CLEAR out from the source box and arrive_turn() in front of + the target box, and stand WIRE_LANE_GAP apart, so n of them span + WIRE_MIN_CLEAR + arrive_turn() + (n - 1) * WIRE_LANE_GAP. """ - return max(TIDY_GAP, 2 * WIRE_MIN_CLEAR + max(0, run_count - 1) * WIRE_LANE_GAP) + return max(TIDY_GAP, WIRE_MIN_CLEAR + arrive_turn() + + max(0, run_count - 1) * WIRE_LANE_GAP) def plan_columns(columns, column_keys, demand, scale):