[AutoTool] rework two-L routing to avoid some bugs with incorrect transitions
This commit is contained in:
parent
d95ddbb6b9
commit
950d144ead
2 changed files with 119 additions and 48 deletions
|
|
@ -791,39 +791,32 @@ class AutoTool(Tool, metaclass=ABCMeta):
|
|||
Solve for a path consisting of two L-bends connected by a straight segment.
|
||||
Used for both U-turns (ccw1 == ccw2) and S-bends (ccw1 != ccw2).
|
||||
"""
|
||||
for plan1 in self._iter_l_plans(ccw1, in_ptype, None):
|
||||
for plan2 in self._iter_l_plans(ccw2, plan1.out_ptype, out_ptype):
|
||||
# Solving for:
|
||||
# X = L1_total +/- R2_actual = length
|
||||
# Y = R1_actual + L2_straight + overhead_mid + overhead_b2 + L3_total = jog
|
||||
|
||||
# Sign for overhead_y2 depends on whether it's a U-turn or S-bend
|
||||
is_u = bool(ccw1) == bool(ccw2)
|
||||
# U-turn: X = L1_total - R2 = length => L1_total = length + R2
|
||||
# S-bend: X = L1_total + R2 = length => L1_total = length - R2
|
||||
l1_total = length + (abs(plan2.overhead_y) if is_u else -abs(plan2.overhead_y))
|
||||
l1_straight = l1_total - plan1.overhead_x
|
||||
out_rot = 0 if is_u else pi
|
||||
|
||||
for plan1 in self._iter_l_plans(ccw1, in_ptype, None):
|
||||
rot_mid = rotation_matrix_2d(pi + plan1.bend_angle)
|
||||
mid_axis = rot_mid @ numpy.array((1.0, 0.0))
|
||||
if not numpy.isclose(mid_axis[0], 0) or numpy.isclose(mid_axis[1], 0):
|
||||
continue
|
||||
|
||||
if plan1.straight.length_range[0] <= l1_straight < plan1.straight.length_range[1]:
|
||||
for straight_mid in self.straights:
|
||||
# overhead_mid accounts for the transition from bend1 to straight_mid
|
||||
mid_ptype_pair = (plan1.out_ptype, straight_mid.ptype)
|
||||
mid_trans = self.transitions.get(mid_ptype_pair, None)
|
||||
mid_trans_dxy = self._itransition2dxy(mid_trans)
|
||||
|
||||
# b_trans2 accounts for the transition from straight_mid to bend2
|
||||
b2_trans = None
|
||||
if plan2.bend is not None and plan2.bend.in_port.ptype != straight_mid.ptype:
|
||||
b2_trans = self.transitions.get((plan2.bend.in_port.ptype, straight_mid.ptype), None)
|
||||
b2_trans_dxy = self._itransition2dxy(b2_trans)
|
||||
for plan2 in self._iter_l_plans(ccw2, straight_mid.ptype, out_ptype):
|
||||
fixed_dxy = numpy.array((plan1.overhead_x, plan1.overhead_y))
|
||||
fixed_dxy += rot_mid @ (
|
||||
mid_trans_dxy
|
||||
+ numpy.array((plan2.overhead_x, plan2.overhead_y))
|
||||
)
|
||||
|
||||
l2_straight = abs(jog) - abs(plan1.overhead_y) - plan2.overhead_x - mid_trans_dxy[0] - b2_trans_dxy[0]
|
||||
l1_straight = length - fixed_dxy[0]
|
||||
l2_straight = (jog - fixed_dxy[1]) / mid_axis[1]
|
||||
|
||||
if straight_mid.length_range[0] <= l2_straight < straight_mid.length_range[1]:
|
||||
# Found a solution!
|
||||
# For plan2, we assume l3_straight = 0.
|
||||
# We need to verify if l3=0 is valid for plan2.straight.
|
||||
if plan1.straight.length_range[0] <= l1_straight < plan1.straight.length_range[1] \
|
||||
and straight_mid.length_range[0] <= l2_straight < straight_mid.length_range[1]:
|
||||
l3_straight = 0
|
||||
if plan2.straight.length_range[0] <= l3_straight < plan2.straight.length_range[1]:
|
||||
ldata0 = self.LData(
|
||||
|
|
@ -832,12 +825,10 @@ class AutoTool(Tool, metaclass=ABCMeta):
|
|||
)
|
||||
ldata1 = self.LData(
|
||||
l3_straight, plan2.straight, kwargs, ccw2, plan2.bend,
|
||||
b2_trans, None, plan2.out_trans,
|
||||
plan2.in_trans, plan2.b_trans, plan2.out_trans,
|
||||
)
|
||||
|
||||
data = self.UData(ldata0, ldata1, straight_mid, l2_straight, mid_trans)
|
||||
# out_port is at (length, jog) rot pi (for S-bend) or 0 (for U-turn) relative to input
|
||||
out_rot = 0 if is_u else pi
|
||||
out_port = Port((length, jog), rotation=out_rot, ptype=plan2.out_ptype)
|
||||
return out_port, data
|
||||
raise BuildError(f"Failed to find a valid double-L configuration for {length=}, {jog=}")
|
||||
|
|
|
|||
|
|
@ -60,6 +60,60 @@ def multi_bend_tool():
|
|||
)
|
||||
return tool, lib
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def asymmetric_transition_tool() -> AutoTool:
|
||||
lib = Library()
|
||||
|
||||
bend_pat = Pattern()
|
||||
bend_pat.ports["in"] = Port((0, 0), 0, ptype="core")
|
||||
bend_pat.ports["out"] = Port((2, -2), pi / 2, ptype="core")
|
||||
lib["core_bend"] = bend_pat
|
||||
|
||||
trans_pat = Pattern()
|
||||
trans_pat.ports["CORE"] = Port((0, 0), 0, ptype="core")
|
||||
trans_pat.ports["MID"] = Port((3, 1), pi, ptype="mid")
|
||||
lib["core_mid"] = trans_pat
|
||||
|
||||
return AutoTool(
|
||||
straights=[
|
||||
AutoTool.Straight(
|
||||
ptype="core",
|
||||
fn=lambda length: make_straight(length, ptype="core"),
|
||||
in_port_name="A",
|
||||
out_port_name="B",
|
||||
length_range=(0, 3),
|
||||
),
|
||||
AutoTool.Straight(
|
||||
ptype="mid",
|
||||
fn=lambda length: make_straight(length, ptype="mid"),
|
||||
in_port_name="A",
|
||||
out_port_name="B",
|
||||
length_range=(0, 1e8),
|
||||
),
|
||||
],
|
||||
bends=[
|
||||
AutoTool.Bend(lib.abstract("core_bend"), "in", "out", clockwise=True, mirror=True),
|
||||
],
|
||||
sbends=[],
|
||||
transitions={
|
||||
("mid", "core"): AutoTool.Transition(lib.abstract("core_mid"), "MID", "CORE"),
|
||||
},
|
||||
default_out_ptype="core",
|
||||
).add_complementary_transitions()
|
||||
|
||||
|
||||
def assert_trace_matches_plan(plan_port: Port, tree: Library, port_names: tuple[str, str] = ("A", "B")) -> None:
|
||||
pat = tree.top_pattern()
|
||||
out_port = pat[port_names[1]]
|
||||
dxy, rot = pat[port_names[0]].measure_travel(out_port)
|
||||
assert_allclose(dxy, plan_port.offset)
|
||||
assert rot is not None
|
||||
assert plan_port.rotation is not None
|
||||
assert_allclose(rot, plan_port.rotation)
|
||||
assert out_port.ptype == plan_port.ptype
|
||||
|
||||
|
||||
def test_autotool_planL_selection(multi_bend_tool) -> None:
|
||||
tool, _ = multi_bend_tool
|
||||
|
||||
|
|
@ -93,6 +147,19 @@ def test_autotool_planU_consistency(multi_bend_tool) -> None:
|
|||
assert data.ldata1.straight_length == 0
|
||||
assert data.ldata1.bend.abstract.name == "b1"
|
||||
|
||||
|
||||
def test_autotool_traceU_matches_plan_with_asymmetric_transition(asymmetric_transition_tool: AutoTool) -> None:
|
||||
tool = asymmetric_transition_tool
|
||||
|
||||
plan_port, data = tool.planU(12, length=0, in_ptype="core")
|
||||
|
||||
assert data.ldata1.in_transition is not None
|
||||
assert data.ldata1.b_transition is not None
|
||||
|
||||
tree = tool.traceU(12, length=0, in_ptype="core")
|
||||
assert_trace_matches_plan(plan_port, tree)
|
||||
|
||||
|
||||
def test_autotool_planS_double_L(multi_bend_tool) -> None:
|
||||
tool, lib = multi_bend_tool
|
||||
|
||||
|
|
@ -109,6 +176,19 @@ def test_autotool_planS_double_L(multi_bend_tool) -> None:
|
|||
assert data.l2_length == 6
|
||||
|
||||
|
||||
def test_autotool_traceS_double_l_matches_plan_with_asymmetric_transition(asymmetric_transition_tool: AutoTool) -> None:
|
||||
tool = asymmetric_transition_tool
|
||||
|
||||
plan_port, data = tool.planS(4, 10, in_ptype="core")
|
||||
|
||||
assert isinstance(data, AutoTool.UData)
|
||||
assert data.ldata1.in_transition is not None
|
||||
assert data.ldata1.b_transition is not None
|
||||
|
||||
tree = tool.traceS(4, 10, in_ptype="core")
|
||||
assert_trace_matches_plan(plan_port, tree)
|
||||
|
||||
|
||||
def test_autotool_planS_pure_sbend_with_transition_dx() -> None:
|
||||
lib = Library()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue