[AutoTool] infer port info automatically
This commit is contained in:
parent
cf3f72b828
commit
fe4e2f760b
4 changed files with 434 additions and 43 deletions
|
|
@ -158,7 +158,7 @@ from masque.builder import AutoTool
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool()
|
AutoTool()
|
||||||
.add_straight('m1wire', make_straight, 'input')
|
.add_straight(make_straight, 'm1wire', 'input')
|
||||||
.add_bend(lib.abstract('bend'), 'input', 'output', clockwise=True)
|
.add_bend(lib.abstract('bend'), 'input', 'output', clockwise=True)
|
||||||
.add_transition(lib.abstract('via'), 'top', 'bottom')
|
.add_transition(lib.abstract('via'), 'top', 'bottom')
|
||||||
)
|
)
|
||||||
|
|
@ -167,7 +167,7 @@ tool = (
|
||||||
The key differences are:
|
The key differences are:
|
||||||
|
|
||||||
- `BasicTool` -> `AutoTool`
|
- `BasicTool` -> `AutoTool`
|
||||||
- `straight=(fn, in_name, out_name)` -> `add_straight(ptype, fn, in_name)`
|
- `straight=(fn, in_name, out_name)` -> `add_straight(fn, ptype, in_name)`
|
||||||
- `bend=(abstract, in_name, out_name)` -> `add_bend(abstract, in_name, out_name)`
|
- `bend=(abstract, in_name, out_name)` -> `add_bend(abstract, in_name, out_name)`
|
||||||
- transitions are registered with `add_transition(abstract, external_port, internal_port)`
|
- transitions are registered with `add_transition(abstract, external_port, internal_port)`
|
||||||
- transitions are bidirectional by default; pass `one_way=True` to inhibit the reverse adapter
|
- transitions are bidirectional by default; pass `one_way=True` to inhibit the reverse adapter
|
||||||
|
|
|
||||||
|
|
@ -809,17 +809,150 @@ class AutoTool(Tool):
|
||||||
repr = False,
|
repr = False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sample_positive_parameter(parameter_domain: tuple[float, float], route_name: str) -> float:
|
||||||
|
"""Choose a finite positive value for generator metadata inference."""
|
||||||
|
lower, upper = (float(parameter_domain[0]), float(parameter_domain[1]))
|
||||||
|
if lower > upper or (lower == upper and lower <= 0):
|
||||||
|
raise BuildError(f'{route_name} inference requires a positive in-domain sample')
|
||||||
|
if lower == upper:
|
||||||
|
return lower
|
||||||
|
sample_lower = max(lower, 0.0)
|
||||||
|
preferred = sample_lower if sample_lower > 0 else 1.0
|
||||||
|
if numpy.isfinite(upper):
|
||||||
|
if upper <= sample_lower:
|
||||||
|
raise BuildError(f'{route_name} inference requires a positive in-domain sample')
|
||||||
|
return preferred if preferred < upper else (sample_lower + upper) / 2
|
||||||
|
return preferred
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generated_pattern(fn: GeneratedPrimitiveFn, parameter: float) -> Pattern:
|
||||||
|
generated = fn(parameter)
|
||||||
|
return generated if isinstance(generated, Pattern) else generated.top_pattern()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _two_port_names(pattern: Pattern, route_name: str) -> tuple[str, str]:
|
||||||
|
port_names = tuple(pattern.ports.keys())
|
||||||
|
if len(port_names) != 2:
|
||||||
|
raise BuildError(f'{route_name} inference requires a generated example with exactly two ports')
|
||||||
|
return port_names
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _resolve_equivalent_ptype(
|
||||||
|
in_port: Port,
|
||||||
|
out_port: Port,
|
||||||
|
ptype: str | None,
|
||||||
|
route_name: str,
|
||||||
|
) -> str | None:
|
||||||
|
if not ptypes_compatible(in_port.ptype, out_port.ptype):
|
||||||
|
raise BuildError(f'{route_name} inference requires equivalent port ptypes')
|
||||||
|
if ptype is not None:
|
||||||
|
if not ptypes_compatible(in_port.ptype, ptype) or not ptypes_compatible(out_port.ptype, ptype):
|
||||||
|
raise BuildError(f'{route_name} ptype does not match generated example ports')
|
||||||
|
return ptype
|
||||||
|
return in_port.ptype if in_port.ptype not in (None, 'unk') else out_port.ptype
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _measure_opposite_ports(
|
||||||
|
in_port: Port,
|
||||||
|
out_port: Port,
|
||||||
|
route_name: str,
|
||||||
|
) -> tuple[NDArray[numpy.float64], float]:
|
||||||
|
dxy, angle = in_port.measure_travel(out_port)
|
||||||
|
if angle is None:
|
||||||
|
raise BuildError(f'{route_name} inference requires generated ports with rotations')
|
||||||
|
normalized_angle = angle % (2 * pi)
|
||||||
|
if not (numpy.isclose(normalized_angle, pi)):
|
||||||
|
raise BuildError(f'{route_name} inference requires opposite generated port rotations')
|
||||||
|
return dxy, angle
|
||||||
|
|
||||||
|
def _infer_straight_metadata(
|
||||||
|
self,
|
||||||
|
fn: GeneratedPrimitiveFn,
|
||||||
|
length_range: tuple[float, float],
|
||||||
|
ptype: str | None,
|
||||||
|
in_port_name: str | None,
|
||||||
|
) -> tuple[str | None, str]:
|
||||||
|
if ptype is not None and in_port_name is not None:
|
||||||
|
return ptype, in_port_name
|
||||||
|
|
||||||
|
sample = self._sample_positive_parameter(length_range, 'straight')
|
||||||
|
pattern = self._generated_pattern(fn, sample)
|
||||||
|
first_name, second_name = self._two_port_names(pattern, 'straight')
|
||||||
|
candidate_names = (
|
||||||
|
(in_port_name, second_name if in_port_name == first_name else first_name),
|
||||||
|
) if in_port_name is not None else (
|
||||||
|
(first_name, second_name),
|
||||||
|
(second_name, first_name),
|
||||||
|
)
|
||||||
|
for candidate_in, candidate_out in candidate_names:
|
||||||
|
if candidate_in not in pattern.ports or candidate_out not in pattern.ports:
|
||||||
|
continue
|
||||||
|
in_port = pattern.ports[candidate_in]
|
||||||
|
out_port = pattern.ports[candidate_out]
|
||||||
|
dxy, _angle = self._measure_opposite_ports(in_port, out_port, 'straight')
|
||||||
|
if dxy[0] > 0 and numpy.isclose(dxy[1], 0):
|
||||||
|
return (
|
||||||
|
self._resolve_equivalent_ptype(in_port, out_port, ptype, 'straight'),
|
||||||
|
candidate_in,
|
||||||
|
)
|
||||||
|
raise BuildError('straight inference requires an equivalent two-port straight example')
|
||||||
|
|
||||||
|
def _infer_sbend_metadata(
|
||||||
|
self,
|
||||||
|
fn: GeneratedPrimitiveFn,
|
||||||
|
jog_range: tuple[float, float],
|
||||||
|
ptype: str | None,
|
||||||
|
in_port_name: str | None,
|
||||||
|
out_port_name: str | None,
|
||||||
|
) -> tuple[str | None, str, str]:
|
||||||
|
if ptype is not None and in_port_name is not None and out_port_name is not None:
|
||||||
|
return ptype, in_port_name, out_port_name
|
||||||
|
|
||||||
|
sample = self._sample_positive_parameter(jog_range, 'S-bend')
|
||||||
|
pattern = self._generated_pattern(fn, sample)
|
||||||
|
first_name, second_name = self._two_port_names(pattern, 'S-bend')
|
||||||
|
if in_port_name is not None and out_port_name is None:
|
||||||
|
out_port_name = second_name if in_port_name == first_name else first_name
|
||||||
|
elif in_port_name is None and out_port_name is not None:
|
||||||
|
in_port_name = second_name if out_port_name == first_name else first_name
|
||||||
|
|
||||||
|
candidate_names = (
|
||||||
|
(in_port_name, out_port_name),
|
||||||
|
) if in_port_name is not None and out_port_name is not None else (
|
||||||
|
(first_name, second_name),
|
||||||
|
(second_name, first_name),
|
||||||
|
)
|
||||||
|
for candidate_in, candidate_out in candidate_names:
|
||||||
|
if candidate_in not in pattern.ports or candidate_out not in pattern.ports:
|
||||||
|
continue
|
||||||
|
in_port = pattern.ports[candidate_in]
|
||||||
|
out_port = pattern.ports[candidate_out]
|
||||||
|
dxy, _angle = self._measure_opposite_ports(in_port, out_port, 'S-bend')
|
||||||
|
if dxy[0] > 0 and numpy.isclose(dxy[1], sample):
|
||||||
|
return (
|
||||||
|
self._resolve_equivalent_ptype(in_port, out_port, ptype, 'S-bend'),
|
||||||
|
candidate_in,
|
||||||
|
candidate_out,
|
||||||
|
)
|
||||||
|
raise BuildError('S-bend inference requires an equivalent two-port S-bend example')
|
||||||
|
|
||||||
def add_straight(
|
def add_straight(
|
||||||
self,
|
self,
|
||||||
ptype: str,
|
|
||||||
fn: GeneratedPrimitiveFn,
|
fn: GeneratedPrimitiveFn,
|
||||||
in_port_name: str,
|
ptype: str | None = None,
|
||||||
|
in_port_name: str | None = None,
|
||||||
*,
|
*,
|
||||||
length_range: tuple[float, float] = (0, numpy.inf),
|
length_range: tuple[float, float] = (0, numpy.inf),
|
||||||
) -> Self:
|
) -> Self:
|
||||||
"""
|
"""
|
||||||
Register a generated straight primitive.
|
Register a generated straight primitive.
|
||||||
|
|
||||||
|
If `ptype` or `in_port_name` is omitted, one in-domain example is
|
||||||
|
generated and the missing metadata is inferred from an equivalent
|
||||||
|
two-port straight.
|
||||||
"""
|
"""
|
||||||
|
ptype, in_port_name = self._infer_straight_metadata(fn, length_range, ptype, in_port_name)
|
||||||
priority_bias = len(self._straight_offers) * BUILTIN_PRIORITY_STEP
|
priority_bias = len(self._straight_offers) * BUILTIN_PRIORITY_STEP
|
||||||
|
|
||||||
def data_at(length: float) -> AutoTool.GeneratedData:
|
def data_at(length: float) -> AutoTool.GeneratedData:
|
||||||
|
|
@ -837,26 +970,43 @@ class AutoTool(Tool):
|
||||||
def add_bend(
|
def add_bend(
|
||||||
self,
|
self,
|
||||||
abstract: Abstract,
|
abstract: Abstract,
|
||||||
in_port_name: str,
|
in_port_name: str | None = None,
|
||||||
out_port_name: str,
|
out_port_name: str | None = None,
|
||||||
*,
|
*,
|
||||||
clockwise: bool = True,
|
clockwise: bool | None = None,
|
||||||
mirror: bool = True,
|
mirror: bool = True,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
"""
|
"""
|
||||||
Register a reusable L-bend primitive.
|
Register a reusable L-bend primitive.
|
||||||
|
|
||||||
|
If the bend has exactly two ports, port names may be omitted. The bend
|
||||||
|
direction is inferred from the selected port orientations; `clockwise`,
|
||||||
|
when provided, is checked against that inferred direction.
|
||||||
"""
|
"""
|
||||||
|
if (in_port_name is None) != (out_port_name is None):
|
||||||
|
raise BuildError('Bend port names must be provided together')
|
||||||
|
if in_port_name is None or out_port_name is None:
|
||||||
|
port_names = tuple(abstract.ports.keys())
|
||||||
|
if len(port_names) != 2:
|
||||||
|
raise BuildError(f'Bend port names are required for {len(port_names)}-port abstracts')
|
||||||
|
in_port_name, out_port_name = port_names
|
||||||
|
|
||||||
priority_bias = len(self._bend_offers[0]) * BUILTIN_PRIORITY_STEP
|
priority_bias = len(self._bend_offers[0]) * BUILTIN_PRIORITY_STEP
|
||||||
in_port = abstract.ports[in_port_name]
|
in_port = abstract.ports[in_port_name]
|
||||||
out_port = abstract.ports[out_port_name]
|
out_port = abstract.ports[out_port_name]
|
||||||
out_ptype = out_port.ptype
|
out_ptype = out_port.ptype
|
||||||
|
source_clockwise = self._bend_clockwise(in_port, out_port)
|
||||||
|
if clockwise is not None and bool(clockwise) != source_clockwise:
|
||||||
|
raise BuildError('Bend clockwise argument does not match port orientations')
|
||||||
|
|
||||||
for ccw in (False, True):
|
for ccw in (False, True):
|
||||||
bend_dxy, bend_angle = self._bend2dxy(in_port, out_port, ccw)
|
target_clockwise = not bool(ccw)
|
||||||
|
bend_dxy, bend_angle = self._bend2dxy(in_port, out_port, source_clockwise, target_clockwise)
|
||||||
bend_dx = float(bend_dxy[0])
|
bend_dx = float(bend_dxy[0])
|
||||||
bend_dy = float(bend_dxy[1])
|
bend_dy = float(bend_dxy[1])
|
||||||
mirrored = mirror and (ccw == clockwise)
|
source_matches_target = source_clockwise == target_clockwise
|
||||||
port_name = in_port_name if (mirror or ccw != clockwise) else out_port_name
|
mirrored = mirror and not source_matches_target
|
||||||
|
port_name = in_port_name if (mirror or source_matches_target) else out_port_name
|
||||||
reusable_data = self.ReusableData(abstract, port_name, mirrored)
|
reusable_data = self.ReusableData(abstract, port_name, mirrored)
|
||||||
endpoint = Port((bend_dx, bend_dy), rotation=bend_angle, ptype=out_ptype)
|
endpoint = Port((bend_dx, bend_dy), rotation=bend_angle, ptype=out_ptype)
|
||||||
|
|
||||||
|
|
@ -873,10 +1023,10 @@ class AutoTool(Tool):
|
||||||
|
|
||||||
def add_sbend(
|
def add_sbend(
|
||||||
self,
|
self,
|
||||||
ptype: str,
|
|
||||||
fn: GeneratedPrimitiveFn,
|
fn: GeneratedPrimitiveFn,
|
||||||
in_port_name: str,
|
ptype: str | None = None,
|
||||||
out_port_name: str,
|
in_port_name: str | None = None,
|
||||||
|
out_port_name: str | None = None,
|
||||||
*,
|
*,
|
||||||
jog_range: tuple[float, float] = (0, numpy.inf),
|
jog_range: tuple[float, float] = (0, numpy.inf),
|
||||||
endpoint: GeneratedEndpointFn | None = None,
|
endpoint: GeneratedEndpointFn | None = None,
|
||||||
|
|
@ -887,7 +1037,17 @@ class AutoTool(Tool):
|
||||||
`endpoint`, when supplied, describes the generated S-bend output port
|
`endpoint`, when supplied, describes the generated S-bend output port
|
||||||
directly during planning and avoids instantiating `fn()` inside
|
directly during planning and avoids instantiating `fn()` inside
|
||||||
`endpoint_at()`.
|
`endpoint_at()`.
|
||||||
|
|
||||||
|
If `ptype` or port names are omitted, one in-domain example is generated
|
||||||
|
and the missing metadata is inferred from an equivalent two-port S-bend.
|
||||||
"""
|
"""
|
||||||
|
ptype, in_port_name, out_port_name = self._infer_sbend_metadata(
|
||||||
|
fn,
|
||||||
|
jog_range,
|
||||||
|
ptype,
|
||||||
|
in_port_name,
|
||||||
|
out_port_name,
|
||||||
|
)
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
def endpoint_at(jog: float) -> Port:
|
def endpoint_at(jog: float) -> Port:
|
||||||
jog_magnitude = abs(jog)
|
jog_magnitude = abs(jog)
|
||||||
|
|
@ -971,24 +1131,55 @@ class AutoTool(Tool):
|
||||||
def add_transition(
|
def add_transition(
|
||||||
self,
|
self,
|
||||||
abstract: Abstract,
|
abstract: Abstract,
|
||||||
their_port_name: str,
|
their_port_name: str | None = None,
|
||||||
our_port_name: str,
|
our_port_name: str | None = None,
|
||||||
*,
|
*,
|
||||||
one_way: bool = False,
|
one_way: bool = False,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
"""
|
"""
|
||||||
Register a reusable port-type transition and expose it as router-visible adapter offers.
|
Register a reusable port-type transition and expose it as router-visible adapter offers.
|
||||||
|
|
||||||
|
If the transition has exactly two ports and is bidirectional, port names
|
||||||
|
may be omitted.
|
||||||
"""
|
"""
|
||||||
|
if (their_port_name is None) != (our_port_name is None):
|
||||||
|
raise BuildError('Transition port names must be provided together')
|
||||||
|
if their_port_name is None or our_port_name is None:
|
||||||
|
if one_way:
|
||||||
|
raise BuildError('one-way transitions require explicit port names')
|
||||||
|
port_names = tuple(abstract.ports.keys())
|
||||||
|
if len(port_names) != 2:
|
||||||
|
raise BuildError(f'Transition port names are required for {len(port_names)}-port abstracts')
|
||||||
|
their_port_name, our_port_name = port_names
|
||||||
|
|
||||||
self._add_transition_direction(abstract, their_port_name, our_port_name)
|
self._add_transition_direction(abstract, their_port_name, our_port_name)
|
||||||
if not one_way:
|
if not one_way:
|
||||||
self._add_transition_direction(abstract, our_port_name, their_port_name)
|
self._add_transition_direction(abstract, our_port_name, their_port_name)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _bend2dxy(in_port: Port, out_port: Port, ccw: bool) -> tuple[NDArray[numpy.float64], float]:
|
def _bend_clockwise(in_port: Port, out_port: Port) -> bool:
|
||||||
|
"""Return true when the selected reusable bend port order turns clockwise."""
|
||||||
|
_bend_dxy, bend_angle = in_port.measure_travel(out_port)
|
||||||
|
if bend_angle is None:
|
||||||
|
raise BuildError('Bend primitive output port must have a 90-degree rotation from its input port')
|
||||||
|
normalized_angle = bend_angle % (2 * pi)
|
||||||
|
if numpy.isclose(normalized_angle, pi / 2):
|
||||||
|
return True
|
||||||
|
if numpy.isclose(normalized_angle, 3 * pi / 2):
|
||||||
|
return False
|
||||||
|
raise BuildError('Bend primitive output port must have a 90-degree rotation from its input port')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _bend2dxy(
|
||||||
|
in_port: Port,
|
||||||
|
out_port: Port,
|
||||||
|
source_clockwise: bool,
|
||||||
|
target_clockwise: bool,
|
||||||
|
) -> tuple[NDArray[numpy.float64], float]:
|
||||||
bend_dxy, bend_angle = in_port.measure_travel(out_port)
|
bend_dxy, bend_angle = in_port.measure_travel(out_port)
|
||||||
assert bend_angle is not None
|
assert bend_angle is not None
|
||||||
if ccw:
|
if source_clockwise != target_clockwise:
|
||||||
bend_dxy[1] *= -1
|
bend_dxy[1] *= -1
|
||||||
bend_angle *= -1
|
bend_angle *= -1
|
||||||
return bend_dxy, bend_angle
|
return bend_dxy, bend_angle
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,8 @@ def autotool_setup() -> tuple[Pather, AutoTool, Library]:
|
||||||
tool_m1 = (
|
tool_m1 = (
|
||||||
AutoTool(bbox_library=lib)
|
AutoTool(bbox_library=lib)
|
||||||
.add_straight(
|
.add_straight(
|
||||||
"wire_m1",
|
|
||||||
lambda length: _make_transition_straight(length, ptype="wire_m1"),
|
lambda length: _make_transition_straight(length, ptype="wire_m1"),
|
||||||
|
"wire_m1",
|
||||||
"in",
|
"in",
|
||||||
)
|
)
|
||||||
.add_transition(via_abs, "m2", "m1")
|
.add_transition(via_abs, "m2", "m1")
|
||||||
|
|
@ -149,8 +149,8 @@ def test_pather_straight_topology_allows_transition_offset_cancellation() -> Non
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool(bbox_library=lib)
|
AutoTool(bbox_library=lib)
|
||||||
.add_straight(
|
.add_straight(
|
||||||
"core",
|
|
||||||
lambda length: _make_transition_straight(length, ptype="core"),
|
lambda length: _make_transition_straight(length, ptype="core"),
|
||||||
|
"core",
|
||||||
"in",
|
"in",
|
||||||
length_range=(0, 1e8),
|
length_range=(0, 1e8),
|
||||||
)
|
)
|
||||||
|
|
@ -199,6 +199,33 @@ def test_autotool_add_transition_dedupes_bidirectional_adapter_offers() -> None:
|
||||||
assert core_data.port_name == "CORE"
|
assert core_data.port_name == "CORE"
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_transition_infers_two_port_bidirectional_transition() -> None:
|
||||||
|
lib = Library()
|
||||||
|
|
||||||
|
trans_pat = Pattern()
|
||||||
|
trans_pat.ports["EXT"] = Port((0, 0), 0, ptype="ext")
|
||||||
|
trans_pat.ports["CORE"] = Port((2, 0), pi, ptype="core")
|
||||||
|
lib["trans"] = trans_pat
|
||||||
|
|
||||||
|
tool = AutoTool(bbox_library=lib)
|
||||||
|
tool.add_transition(lib.abstract("trans"))
|
||||||
|
|
||||||
|
ext_offers = tool.primitive_offers("straight", in_ptype="ext")
|
||||||
|
core_offers = tool.primitive_offers("straight", in_ptype="core")
|
||||||
|
|
||||||
|
assert len(ext_offers) == 1
|
||||||
|
assert len(core_offers) == 1
|
||||||
|
|
||||||
|
ext_port, ext_data = commit_offer(ext_offers[0], 2)
|
||||||
|
core_port, core_data = commit_offer(core_offers[0], 2)
|
||||||
|
assert_allclose(ext_port.offset, [2, 0])
|
||||||
|
assert_allclose(core_port.offset, [2, 0])
|
||||||
|
assert isinstance(ext_data, AutoTool.ReusableData)
|
||||||
|
assert isinstance(core_data, AutoTool.ReusableData)
|
||||||
|
assert ext_data.port_name == "EXT"
|
||||||
|
assert core_data.port_name == "CORE"
|
||||||
|
|
||||||
|
|
||||||
def test_autotool_add_transition_one_way_inhibits_reverse_adapter_offer() -> None:
|
def test_autotool_add_transition_one_way_inhibits_reverse_adapter_offer() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
|
|
||||||
|
|
@ -222,6 +249,43 @@ def test_autotool_add_transition_one_way_inhibits_reverse_adapter_offer() -> Non
|
||||||
assert ext_data.port_name == "EXT"
|
assert ext_data.port_name == "EXT"
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_transition_requires_explicit_names_for_one_way() -> None:
|
||||||
|
lib = Library()
|
||||||
|
|
||||||
|
trans_pat = Pattern()
|
||||||
|
trans_pat.ports["EXT"] = Port((0, 0), 0, ptype="ext")
|
||||||
|
trans_pat.ports["CORE"] = Port((2, 0), pi, ptype="core")
|
||||||
|
lib["trans"] = trans_pat
|
||||||
|
|
||||||
|
with pytest.raises(BuildError, match='one-way transitions require explicit port names'):
|
||||||
|
AutoTool().add_transition(lib.abstract("trans"), one_way=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_transition_rejects_partial_port_names() -> None:
|
||||||
|
lib = Library()
|
||||||
|
|
||||||
|
trans_pat = Pattern()
|
||||||
|
trans_pat.ports["EXT"] = Port((0, 0), 0, ptype="ext")
|
||||||
|
trans_pat.ports["CORE"] = Port((2, 0), pi, ptype="core")
|
||||||
|
lib["trans"] = trans_pat
|
||||||
|
|
||||||
|
with pytest.raises(BuildError, match='Transition port names must be provided together'):
|
||||||
|
AutoTool().add_transition(lib.abstract("trans"), "EXT")
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_transition_requires_explicit_names_for_non_two_port_abstract() -> None:
|
||||||
|
lib = Library()
|
||||||
|
|
||||||
|
trans_pat = Pattern()
|
||||||
|
trans_pat.ports["EXT"] = Port((0, 0), 0, ptype="ext")
|
||||||
|
trans_pat.ports["CORE"] = Port((2, 0), pi, ptype="core")
|
||||||
|
trans_pat.ports["TAP"] = Port((1, 1), pi, ptype="tap")
|
||||||
|
lib["trans"] = trans_pat
|
||||||
|
|
||||||
|
with pytest.raises(BuildError, match='Transition port names are required for 3-port abstracts'):
|
||||||
|
AutoTool().add_transition(lib.abstract("trans"))
|
||||||
|
|
||||||
|
|
||||||
def make_straight(length: float, width: float = 2, ptype: str = "wire") -> Pattern:
|
def make_straight(length: float, width: float = 2, ptype: str = "wire") -> Pattern:
|
||||||
pat = Pattern()
|
pat = Pattern()
|
||||||
pat.rect((1, 0), xmin=0, xmax=length, yctr=0, ly=width)
|
pat.rect((1, 0), xmin=0, xmax=length, yctr=0, ly=width)
|
||||||
|
|
@ -264,8 +328,8 @@ def multi_bend_tool() -> tuple[AutoTool, Library]:
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool(bbox_library=lib)
|
AutoTool(bbox_library=lib)
|
||||||
.add_straight("wire", make_straight, "A", length_range=(0, 10))
|
.add_straight(make_straight, "wire", "A", length_range=(0, 10))
|
||||||
.add_straight("wire", lambda length: make_straight(length, width=4), "A", length_range=(10, 1e8))
|
.add_straight(lambda length: make_straight(length, width=4), "wire", "A", length_range=(10, 1e8))
|
||||||
.add_bend(b1_abs, "A", "B", clockwise=True, mirror=True)
|
.add_bend(b1_abs, "A", "B", clockwise=True, mirror=True)
|
||||||
.add_bend(b2_abs, "A", "B", clockwise=True, mirror=True)
|
.add_bend(b2_abs, "A", "B", clockwise=True, mirror=True)
|
||||||
)
|
)
|
||||||
|
|
@ -287,8 +351,8 @@ def asymmetric_transition_tool() -> AutoTool:
|
||||||
|
|
||||||
return (
|
return (
|
||||||
AutoTool(bbox_library=lib)
|
AutoTool(bbox_library=lib)
|
||||||
.add_straight("core", lambda length: make_straight(length, ptype="core"), "A", length_range=(0, 3))
|
.add_straight(lambda length: make_straight(length, ptype="core"), "core", "A", length_range=(0, 3))
|
||||||
.add_straight("mid", lambda length: make_straight(length, ptype="mid"), "A", length_range=(0, 1e8))
|
.add_straight(lambda length: make_straight(length, ptype="mid"), "mid", "A", length_range=(0, 1e8))
|
||||||
.add_bend(lib.abstract("core_bend"), "in", "out", clockwise=True, mirror=True)
|
.add_bend(lib.abstract("core_bend"), "in", "out", clockwise=True, mirror=True)
|
||||||
.add_transition(lib.abstract("core_mid"), "MID", "CORE")
|
.add_transition(lib.abstract("core_mid"), "MID", "CORE")
|
||||||
)
|
)
|
||||||
|
|
@ -310,8 +374,8 @@ def wildcard_transition_tool() -> tuple[AutoTool, Library]:
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool(bbox_library=lib)
|
AutoTool(bbox_library=lib)
|
||||||
.add_straight("core", lambda length: make_straight(length, ptype="core"), "A", length_range=(0, 1e8))
|
.add_straight(lambda length: make_straight(length, ptype="core"), "core", "A", length_range=(0, 1e8))
|
||||||
.add_sbend("core", make_core_sbend, "A", "B", jog_range=(-1e8, 1e8))
|
.add_sbend(make_core_sbend, "core", "A", "B", jog_range=(-1e8, 1e8))
|
||||||
.add_transition(lib.abstract("wild_core"), "WILD", "CORE")
|
.add_transition(lib.abstract("wild_core"), "WILD", "CORE")
|
||||||
)
|
)
|
||||||
return tool, lib
|
return tool, lib
|
||||||
|
|
@ -363,11 +427,67 @@ def make_sbend_tool(jog_range: tuple[float, float]) -> AutoTool:
|
||||||
|
|
||||||
return (
|
return (
|
||||||
AutoTool()
|
AutoTool()
|
||||||
.add_straight("core", make_straight, "A", length_range=(0, 1e8))
|
.add_straight(make_straight, "core", "A", length_range=(0, 1e8))
|
||||||
.add_sbend("core", make_sbend, "A", "B", jog_range=jog_range)
|
.add_sbend(make_sbend, "core", "A", "B", jog_range=jog_range)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_straight_infers_metadata_from_generated_example() -> None:
|
||||||
|
calls: list[float] = []
|
||||||
|
|
||||||
|
def make_counted_straight(length: float) -> Pattern:
|
||||||
|
calls.append(length)
|
||||||
|
return make_straight(length, ptype="core")
|
||||||
|
|
||||||
|
tool = AutoTool().add_straight(make_counted_straight, length_range=(0, 10))
|
||||||
|
|
||||||
|
assert calls == [1]
|
||||||
|
offer = tool.primitive_offers("straight", in_ptype="core")[0]
|
||||||
|
out_port, data = commit_offer(offer, 7)
|
||||||
|
assert out_port.ptype == "core"
|
||||||
|
assert isinstance(data, AutoTool.GeneratedData)
|
||||||
|
assert data.port_name == "A"
|
||||||
|
assert data.parameter == 7
|
||||||
|
assert calls == [1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_straight_explicit_metadata_does_not_sample_generator() -> None:
|
||||||
|
calls: list[float] = []
|
||||||
|
|
||||||
|
def make_counted_straight(length: float) -> Pattern:
|
||||||
|
calls.append(length)
|
||||||
|
return make_straight(length, ptype="core")
|
||||||
|
|
||||||
|
tool = AutoTool().add_straight(make_counted_straight, "core", "A", length_range=(0, 10))
|
||||||
|
|
||||||
|
assert calls == []
|
||||||
|
offer = tool.primitive_offers("straight", in_ptype="core")[0]
|
||||||
|
_out_port, data = commit_offer(offer, 7)
|
||||||
|
assert isinstance(data, AutoTool.GeneratedData)
|
||||||
|
assert data.port_name == "A"
|
||||||
|
assert calls == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_sbend_infers_metadata_from_generated_example() -> None:
|
||||||
|
calls: list[float] = []
|
||||||
|
|
||||||
|
def make_counted_sbend(jog: float) -> Pattern:
|
||||||
|
calls.append(jog)
|
||||||
|
return make_sbend(jog, ptype="core")
|
||||||
|
|
||||||
|
tool = AutoTool().add_sbend(make_counted_sbend, jog_range=(0, 10))
|
||||||
|
|
||||||
|
assert calls == [1]
|
||||||
|
offer = tool.primitive_offers("s", in_ptype="core")[0]
|
||||||
|
data = offer.commit(4)
|
||||||
|
assert offer.in_ptype == "core"
|
||||||
|
assert offer.out_ptype == "core"
|
||||||
|
assert isinstance(data, AutoTool.GeneratedData)
|
||||||
|
assert data.port_name == "A"
|
||||||
|
assert data.parameter == 4
|
||||||
|
assert calls == [1]
|
||||||
|
|
||||||
|
|
||||||
def test_autotool_sbend_custom_endpoint_avoids_generator_during_planning() -> None:
|
def test_autotool_sbend_custom_endpoint_avoids_generator_during_planning() -> None:
|
||||||
calls = 0
|
calls = 0
|
||||||
|
|
||||||
|
|
@ -380,8 +500,8 @@ def test_autotool_sbend_custom_endpoint_avoids_generator_during_planning() -> No
|
||||||
return Port((20, jog), rotation=pi, ptype="core")
|
return Port((20, jog), rotation=pi, ptype="core")
|
||||||
|
|
||||||
tool = AutoTool().add_sbend(
|
tool = AutoTool().add_sbend(
|
||||||
"core",
|
|
||||||
make_counted_sbend,
|
make_counted_sbend,
|
||||||
|
"core",
|
||||||
"A",
|
"A",
|
||||||
"B",
|
"B",
|
||||||
jog_range=(0, 1e8),
|
jog_range=(0, 1e8),
|
||||||
|
|
@ -510,6 +630,86 @@ def test_autotool_l_offer_selection_uses_primitive_cost_and_domains(
|
||||||
assert large_bend_offer.parameter_domain == (5, 5)
|
assert large_bend_offer.parameter_domain == (5, 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_bend_infers_two_port_clockwise_bend() -> None:
|
||||||
|
lib = Library()
|
||||||
|
lib["bend"] = make_bend(2, ptype="wire", clockwise=True)
|
||||||
|
|
||||||
|
tool = AutoTool(bbox_library=lib).add_bend(lib.abstract("bend"))
|
||||||
|
|
||||||
|
_cw_offer, cw_port, cw_data = selected_offer(tool, "bend", 2, in_ptype="wire", ccw=False)
|
||||||
|
_ccw_offer, ccw_port, ccw_data = selected_offer(tool, "bend", 2, in_ptype="wire", ccw=True)
|
||||||
|
assert_allclose(cw_port.offset, [2, -2])
|
||||||
|
assert_allclose(ccw_port.offset, [2, 2])
|
||||||
|
assert isinstance(cw_data, AutoTool.ReusableData)
|
||||||
|
assert isinstance(ccw_data, AutoTool.ReusableData)
|
||||||
|
assert cw_data.port_name == "A"
|
||||||
|
assert not cw_data.mirrored
|
||||||
|
assert ccw_data.port_name == "A"
|
||||||
|
assert ccw_data.mirrored
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_bend_infers_two_port_counterclockwise_bend() -> None:
|
||||||
|
lib = Library()
|
||||||
|
lib["bend"] = make_bend(2, ptype="wire", clockwise=False)
|
||||||
|
|
||||||
|
tool = AutoTool(bbox_library=lib).add_bend(lib.abstract("bend"))
|
||||||
|
|
||||||
|
_cw_offer, cw_port, cw_data = selected_offer(tool, "bend", 2, in_ptype="wire", ccw=False)
|
||||||
|
_ccw_offer, ccw_port, ccw_data = selected_offer(tool, "bend", 2, in_ptype="wire", ccw=True)
|
||||||
|
assert_allclose(cw_port.offset, [2, -2])
|
||||||
|
assert_allclose(ccw_port.offset, [2, 2])
|
||||||
|
assert isinstance(cw_data, AutoTool.ReusableData)
|
||||||
|
assert isinstance(ccw_data, AutoTool.ReusableData)
|
||||||
|
assert cw_data.port_name == "A"
|
||||||
|
assert cw_data.mirrored
|
||||||
|
assert ccw_data.port_name == "A"
|
||||||
|
assert not ccw_data.mirrored
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_bend_inferred_names_allow_rotational_reuse_without_mirror() -> None:
|
||||||
|
lib = Library()
|
||||||
|
lib["bend"] = make_bend(2, ptype="wire", clockwise=True)
|
||||||
|
|
||||||
|
tool = AutoTool(bbox_library=lib).add_bend(lib.abstract("bend"), mirror=False)
|
||||||
|
|
||||||
|
cw_offer, _cw_port, cw_data = selected_offer(tool, "bend", 2, in_ptype="wire", ccw=False)
|
||||||
|
ccw_offer, _ccw_port, ccw_data = selected_offer(tool, "bend", 2, in_ptype="wire", ccw=True)
|
||||||
|
assert isinstance(cw_data, AutoTool.ReusableData)
|
||||||
|
assert isinstance(ccw_data, AutoTool.ReusableData)
|
||||||
|
assert cw_data.port_name == "A"
|
||||||
|
assert not cw_data.mirrored
|
||||||
|
assert ccw_data.port_name == "B"
|
||||||
|
assert not ccw_data.mirrored
|
||||||
|
assert_rendered_offer_endpoint_matches_plan(tool, cw_offer, 2, "wire")
|
||||||
|
assert_rendered_offer_endpoint_matches_plan(tool, ccw_offer, 2, "wire")
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_bend_rejects_clockwise_mismatch() -> None:
|
||||||
|
lib = Library()
|
||||||
|
lib["bend"] = make_bend(2, ptype="wire", clockwise=True)
|
||||||
|
|
||||||
|
with pytest.raises(BuildError, match='Bend clockwise argument does not match port orientations'):
|
||||||
|
AutoTool().add_bend(lib.abstract("bend"), "A", "B", clockwise=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_bend_rejects_partial_port_names() -> None:
|
||||||
|
lib = Library()
|
||||||
|
lib["bend"] = make_bend(2, ptype="wire", clockwise=True)
|
||||||
|
|
||||||
|
with pytest.raises(BuildError, match='Bend port names must be provided together'):
|
||||||
|
AutoTool().add_bend(lib.abstract("bend"), "A")
|
||||||
|
|
||||||
|
|
||||||
|
def test_autotool_add_bend_requires_explicit_names_for_non_two_port_abstract() -> None:
|
||||||
|
lib = Library()
|
||||||
|
bend = make_bend(2, ptype="wire", clockwise=True)
|
||||||
|
bend.ports["TAP"] = Port((1, 1), pi, ptype="wire")
|
||||||
|
lib["bend"] = bend
|
||||||
|
|
||||||
|
with pytest.raises(BuildError, match='Bend port names are required for 3-port abstracts'):
|
||||||
|
AutoTool().add_bend(lib.abstract("bend"))
|
||||||
|
|
||||||
|
|
||||||
def test_autotool_l_offer_bbox_matches_rendered_primitive(multi_bend_tool: tuple[AutoTool, Library]) -> None:
|
def test_autotool_l_offer_bbox_matches_rendered_primitive(multi_bend_tool: tuple[AutoTool, Library]) -> None:
|
||||||
tool, lib = multi_bend_tool
|
tool, lib = multi_bend_tool
|
||||||
offer, _out_port, _data = selected_offer(tool, "bend", 2, ccw=True)
|
offer, _out_port, _data = selected_offer(tool, "bend", 2, ccw=True)
|
||||||
|
|
@ -551,7 +751,7 @@ def test_autotool_transition_offer_bbox_matches_rendered_primitive() -> None:
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool(bbox_library=lib)
|
AutoTool(bbox_library=lib)
|
||||||
.add_straight("core", lambda length: make_straight(length, ptype="core"), "A", length_range=(0, 1e8))
|
.add_straight(lambda length: make_straight(length, ptype="core"), "core", "A", length_range=(0, 1e8))
|
||||||
.add_bend(lib.abstract("core_bend"), "A", "B", clockwise=True, mirror=True)
|
.add_bend(lib.abstract("core_bend"), "A", "B", clockwise=True, mirror=True)
|
||||||
.add_transition(lib.abstract("out_trans"), "EXT", "CORE")
|
.add_transition(lib.abstract("out_trans"), "EXT", "CORE")
|
||||||
)
|
)
|
||||||
|
|
@ -596,8 +796,8 @@ def test_autotool_s_offer_bbox_matches_rendered_sbend() -> None:
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool(bbox_library=lib)
|
AutoTool(bbox_library=lib)
|
||||||
.add_straight("core", make_straight, "A", length_range=(1, 1e8))
|
.add_straight(make_straight, "core", "A", length_range=(1, 1e8))
|
||||||
.add_sbend("core", make_wide_sbend, "A", "B", jog_range=(0, 1e8))
|
.add_sbend(make_wide_sbend, "core", "A", "B", jog_range=(0, 1e8))
|
||||||
.add_transition(lib.abstract("xin"), "EXT", "CORE")
|
.add_transition(lib.abstract("xin"), "EXT", "CORE")
|
||||||
)
|
)
|
||||||
offer, _out_port, _data = selected_offer(tool, "s", 4, length=15, in_ptype="core")
|
offer, _out_port, _data = selected_offer(tool, "s", 4, length=15, in_ptype="core")
|
||||||
|
|
@ -657,8 +857,8 @@ def test_autotool_sbend_registration_order_sets_priority() -> None:
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool()
|
AutoTool()
|
||||||
.add_sbend("core", first_sbend, "A", "B", jog_range=(0, 1e8))
|
.add_sbend(first_sbend, "core", "A", "B", jog_range=(0, 1e8))
|
||||||
.add_sbend("core", second_sbend, "A", "B", jog_range=(0, 1e8))
|
.add_sbend(second_sbend, "core", "A", "B", jog_range=(0, 1e8))
|
||||||
)
|
)
|
||||||
|
|
||||||
_offer, out_port, data = selected_offer(tool, "s", 4, in_ptype="core")
|
_offer, out_port, data = selected_offer(tool, "s", 4, in_ptype="core")
|
||||||
|
|
@ -813,7 +1013,7 @@ def test_autotool_generated_primitives_do_not_capture_route_kwargs() -> None:
|
||||||
markers.append(marker)
|
markers.append(marker)
|
||||||
return make_straight(length, ptype="wire")
|
return make_straight(length, ptype="wire")
|
||||||
|
|
||||||
tool = AutoTool().add_straight("wire", make_marked_straight, "A", length_range=(0, 1e8))
|
tool = AutoTool().add_straight(make_marked_straight, "wire", "A", length_range=(0, 1e8))
|
||||||
p = Pather(Library(), tools=tool, render='deferred')
|
p = Pather(Library(), tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), 0, ptype="wire")
|
p.ports["A"] = Port((0, 0), 0, ptype="wire")
|
||||||
|
|
||||||
|
|
@ -839,7 +1039,7 @@ def test_autotool_bend_offer_supports_requested_output_transition(ccw: bool) ->
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool(bbox_library=lib)
|
AutoTool(bbox_library=lib)
|
||||||
.add_straight("core", lambda length: make_straight(length, ptype="core"), "A", length_range=(0, 1e8))
|
.add_straight(lambda length: make_straight(length, ptype="core"), "core", "A", length_range=(0, 1e8))
|
||||||
.add_bend(lib.abstract("core_bend"), "A", "B", clockwise=True, mirror=True)
|
.add_bend(lib.abstract("core_bend"), "A", "B", clockwise=True, mirror=True)
|
||||||
.add_transition(lib.abstract("out_trans"), "EXT", "CORE")
|
.add_transition(lib.abstract("out_trans"), "EXT", "CORE")
|
||||||
)
|
)
|
||||||
|
|
@ -873,7 +1073,7 @@ def test_autotool_bend_offer_supports_bend_input_transition(ccw: bool) -> None:
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool(bbox_library=lib)
|
AutoTool(bbox_library=lib)
|
||||||
.add_straight("core", lambda length: make_straight(length, ptype="core"), "A", length_range=(0, 1e8))
|
.add_straight(lambda length: make_straight(length, ptype="core"), "core", "A", length_range=(0, 1e8))
|
||||||
.add_bend(lib.abstract("mid_bend"), "A", "B", clockwise=True, mirror=True)
|
.add_bend(lib.abstract("mid_bend"), "A", "B", clockwise=True, mirror=True)
|
||||||
.add_transition(lib.abstract("bend_trans"), "MID", "CORE")
|
.add_transition(lib.abstract("bend_trans"), "MID", "CORE")
|
||||||
)
|
)
|
||||||
|
|
@ -906,7 +1106,7 @@ def test_pather_accepts_bend_offer_with_zero_lateral_endpoint() -> None:
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool(bbox_library=lib)
|
AutoTool(bbox_library=lib)
|
||||||
.add_straight("core", lambda length: make_straight(length, ptype="core"), "A", length_range=(0, 1e8))
|
.add_straight(lambda length: make_straight(length, ptype="core"), "core", "A", length_range=(0, 1e8))
|
||||||
.add_bend(lib.abstract("mid_bend"), "A", "B", clockwise=True, mirror=True)
|
.add_bend(lib.abstract("mid_bend"), "A", "B", clockwise=True, mirror=True)
|
||||||
.add_transition(lib.abstract("bend_trans"), "MID", "CORE")
|
.add_transition(lib.abstract("bend_trans"), "MID", "CORE")
|
||||||
)
|
)
|
||||||
|
|
@ -945,7 +1145,7 @@ def test_autotool_bend_offer_supports_bend_and_output_transitions(ccw: bool) ->
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool(bbox_library=lib)
|
AutoTool(bbox_library=lib)
|
||||||
.add_straight("core", lambda length: make_straight(length, ptype="core"), "A", length_range=(0, 1e8))
|
.add_straight(lambda length: make_straight(length, ptype="core"), "core", "A", length_range=(0, 1e8))
|
||||||
.add_bend(lib.abstract("mid_bend"), "A", "B", clockwise=True, mirror=True)
|
.add_bend(lib.abstract("mid_bend"), "A", "B", clockwise=True, mirror=True)
|
||||||
.add_transition(lib.abstract("bend_trans"), "MID", "CORE")
|
.add_transition(lib.abstract("bend_trans"), "MID", "CORE")
|
||||||
.add_transition(lib.abstract("out_trans"), "EXT", "MID")
|
.add_transition(lib.abstract("out_trans"), "EXT", "MID")
|
||||||
|
|
@ -988,8 +1188,8 @@ def test_pather_autotool_pure_sbend_with_transition_dx() -> None:
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool()
|
AutoTool()
|
||||||
.add_straight("core", make_core_straight, "A", length_range=(1, 1e8))
|
.add_straight(make_core_straight, "core", "A", length_range=(1, 1e8))
|
||||||
.add_sbend("core", make_core_sbend, "A", "B", jog_range=(0, 1e8))
|
.add_sbend(make_core_sbend, "core", "A", "B", jog_range=(0, 1e8))
|
||||||
.add_transition(lib.abstract("xin"), "EXT", "CORE")
|
.add_transition(lib.abstract("xin"), "EXT", "CORE")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ def multi_bend_tool() -> tuple[AutoTool, Library]:
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool()
|
AutoTool()
|
||||||
.add_straight("wire", make_straight, "A", length_range=(0, 10))
|
.add_straight(make_straight, "wire", "A", length_range=(0, 10))
|
||||||
.add_straight("wire", lambda length: make_straight(length, width=4), "A", length_range=(10, 1e8))
|
.add_straight(lambda length: make_straight(length, width=4), "wire", "A", length_range=(10, 1e8))
|
||||||
.add_bend(b1_abs, "A", "B", clockwise=True, mirror=True)
|
.add_bend(b1_abs, "A", "B", clockwise=True, mirror=True)
|
||||||
.add_bend(b2_abs, "A", "B", clockwise=True, mirror=True)
|
.add_bend(b2_abs, "A", "B", clockwise=True, mirror=True)
|
||||||
)
|
)
|
||||||
|
|
@ -66,7 +66,7 @@ def test_autotool_uturn() -> None:
|
||||||
|
|
||||||
tool = (
|
tool = (
|
||||||
AutoTool()
|
AutoTool()
|
||||||
.add_straight('wire', make_straight, 'in')
|
.add_straight(make_straight, 'wire', 'in')
|
||||||
.add_bend(lib.abstract('bend'), 'in', 'out', clockwise=True)
|
.add_bend(lib.abstract('bend'), 'in', 'out', clockwise=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue