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 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 19:45:39 +03:00
co-authored by Claude Opus 5.5
parent 96a703c5d5
commit fd22eb314d
2 changed files with 29 additions and 14 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it.
import configparser import configparser
import os 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") PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
DEFAULT_GRID_SIZE = 10 DEFAULT_GRID_SIZE = 10
DEFAULT_ADD_TIMESTAMP = True DEFAULT_ADD_TIMESTAMP = True
+28 -13
View File
@@ -33,6 +33,10 @@ WIRE_LANE_GAP = 6
# knobs, the same way a link leaving the sheet is. # knobs, the same way a link leaving the sheet is.
WIRE_STANDOFF_MAX = 60 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 # 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 # 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 # 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 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): def channel_candidates(src_box, dst_box, boxes, low, high):
"""Vertical channel positions to try, best first. """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 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. 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: for bx1, _, bx2, _ in boxes:
ordered.append(bx2 + WIRE_HUG) ordered.append(bx2 + WIRE_HUG)
ordered.append(bx1 - 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 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. """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 `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 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 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 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)) floor = max(WIRE_MIN_CLEAR, int(math.ceil(min_offset)))
if max_offset > WIRE_HUG: hug = max(WIRE_HUG, floor)
offsets += list(range(WIRE_HUG + 1, int(max_offset) + 1)) offsets = list(range(hug, floor - 1, -1))
if max_offset > hug:
offsets += list(range(hug + 1, int(max_offset) + 1))
for offset in offsets: for offset in offsets:
lane = edge_x + side * offset 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) start = (src_box[2], src_y)
end = (dst_box[0], dst_y) end = (dst_box[0], dst_y)
leave = (start[0] + LINK_STUB, src_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]) 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) 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 continue
in_x = hug_lane(dst_box[0], -1, min(base_cy, dst_y), max(base_cy, dst_y), 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), 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: if in_x is None:
continue continue
@@ -1205,12 +1220,12 @@ def wire_demand_by_boundary(column_of_comp):
def column_gap_for(run_count): def column_gap_for(run_count):
"""Clear space a column gap needs to carry `run_count` vertical wire runs. """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 Lanes start WIRE_MIN_CLEAR out from the source box and arrive_turn() in front of
n of them span 2 * WIRE_MIN_CLEAR + (n - 1) * WIRE_LANE_GAP. One or two runs already the target box, and stand WIRE_LANE_GAP apart, so n of them span
fit inside the plain TIDY_GAP -- at 10 and 20 across a 30px gap, both still hugging WIRE_MIN_CLEAR + arrive_turn() + (n - 1) * WIRE_LANE_GAP.
a box -- which is why the great majority of gaps stay at exactly TIDY_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): def plan_columns(columns, column_keys, demand, scale):