[AutoTool] add U-bend
This commit is contained in:
parent
8a45c6d8d6
commit
1070815730
2 changed files with 167 additions and 1 deletions
|
|
@ -264,6 +264,7 @@ class Tool:
|
|||
self,
|
||||
jog: float,
|
||||
*,
|
||||
length: float = 0,
|
||||
in_ptype: str | None = None,
|
||||
out_ptype: str | None = None,
|
||||
port_names: tuple[str, str] = ('A', 'B'),
|
||||
|
|
@ -597,6 +598,14 @@ class AutoTool(Tool, metaclass=ABCMeta):
|
|||
b_transition: 'AutoTool.Transition | None'
|
||||
out_transition: 'AutoTool.Transition | None'
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class UData:
|
||||
""" Data for planU """
|
||||
ldata0: 'AutoTool.LData'
|
||||
ldata1: 'AutoTool.LData'
|
||||
straight2: 'AutoTool.Straight'
|
||||
l2_length: float
|
||||
|
||||
straights: list[Straight]
|
||||
""" List of straight-generators to choose from, in order of priority """
|
||||
|
||||
|
|
@ -942,6 +951,113 @@ class AutoTool(Tool, metaclass=ABCMeta):
|
|||
self._renderS(data=data, tree=tree, port_names=port_names, gen_kwargs=kwargs)
|
||||
return tree
|
||||
|
||||
def planU(
|
||||
self,
|
||||
jog: float,
|
||||
*,
|
||||
length: float = 0,
|
||||
in_ptype: str | None = None,
|
||||
out_ptype: str | None = None,
|
||||
**kwargs,
|
||||
) -> tuple[Port, UData]:
|
||||
ccw = jog > 0
|
||||
kwargs_no_out = kwargs | {'out_ptype': None}
|
||||
|
||||
# Use loops to find a combination of straights and bends that fits
|
||||
success = False
|
||||
for _straight1 in self.straights:
|
||||
for _bend1 in self.bends:
|
||||
for straight2 in self.straights:
|
||||
for _bend2 in self.bends:
|
||||
try:
|
||||
# We need to know R1 and R2 to calculate the lengths.
|
||||
# Use large dummy lengths to probe the bends.
|
||||
p_probe1, _ = self.planL(ccw, 1e9, in_ptype=in_ptype, **kwargs_no_out)
|
||||
R1 = abs(Port((0, 0), 0).measure_travel(p_probe1)[0][1])
|
||||
p_probe2, _ = self.planL(ccw, 1e9, in_ptype=p_probe1.ptype, out_ptype=out_ptype, **kwargs)
|
||||
R2 = abs(Port((0, 0), 0).measure_travel(p_probe2)[0][1])
|
||||
|
||||
# Final x will be: x = l1_straight + R1 - R2
|
||||
# We want final x = length. So: l1_straight = length - R1 + R2
|
||||
# Total length for planL(0) is l1 = l1_straight + R1 = length + R2
|
||||
l1 = length + R2
|
||||
|
||||
# Final y will be: y = R1 + l2_straight + R2 = abs(jog)
|
||||
# So: l2_straight = abs(jog) - R1 - R2
|
||||
l2_length = abs(jog) - R1 - R2
|
||||
|
||||
if l2_length >= straight2.length_range[0] and l2_length < straight2.length_range[1]:
|
||||
p0, ldata0 = self.planL(ccw, l1, in_ptype=in_ptype, **kwargs_no_out)
|
||||
# For the second bend, we want straight length = 0.
|
||||
# Total length for planL(1) is l2 = 0 + R2 = R2.
|
||||
p1, ldata1 = self.planL(ccw, R2, in_ptype=p0.ptype, out_ptype=out_ptype, **kwargs)
|
||||
|
||||
success = True
|
||||
break
|
||||
except BuildError:
|
||||
continue
|
||||
if success:
|
||||
break
|
||||
if success:
|
||||
break
|
||||
if success:
|
||||
break
|
||||
|
||||
if not success:
|
||||
raise BuildError(f"AutoTool failed to plan U-turn with {jog=}, {length=}")
|
||||
|
||||
data = self.UData(ldata0, ldata1, straight2, l2_length)
|
||||
# Final port is at (length, jog) rot pi relative to input
|
||||
out_port = Port((length, jog), rotation=pi, ptype=p1.ptype)
|
||||
return out_port, data
|
||||
|
||||
def _renderU(
|
||||
self,
|
||||
data: UData,
|
||||
tree: ILibrary,
|
||||
port_names: tuple[str, str],
|
||||
gen_kwargs: dict[str, Any],
|
||||
) -> ILibrary:
|
||||
pat = tree.top_pattern()
|
||||
# 1. First L-bend
|
||||
self._renderL(data.ldata0, tree, port_names, gen_kwargs)
|
||||
# 2. Connecting straight
|
||||
if not numpy.isclose(data.l2_length, 0):
|
||||
s2_pat_or_tree = data.straight2.fn(data.l2_length, **(gen_kwargs | data.ldata0.straight_kwargs))
|
||||
pmap = {port_names[1]: data.straight2.in_port_name}
|
||||
if isinstance(s2_pat_or_tree, Pattern):
|
||||
pat.plug(s2_pat_or_tree, pmap, append=True)
|
||||
else:
|
||||
s2_tree = s2_pat_or_tree
|
||||
top = s2_tree.top()
|
||||
s2_tree.flatten(top, dangling_ok=True)
|
||||
pat.plug(s2_tree[top], pmap, append=True)
|
||||
# 3. Second L-bend
|
||||
self._renderL(data.ldata1, tree, port_names, gen_kwargs)
|
||||
return tree
|
||||
|
||||
def pathU(
|
||||
self,
|
||||
jog: float,
|
||||
*,
|
||||
length: float = 0,
|
||||
in_ptype: str | None = None,
|
||||
out_ptype: str | None = None,
|
||||
port_names: tuple[str, str] = ('A', 'B'),
|
||||
**kwargs,
|
||||
) -> Library:
|
||||
_out_port, data = self.planU(
|
||||
jog,
|
||||
length = length,
|
||||
in_ptype = in_ptype,
|
||||
out_ptype = out_ptype,
|
||||
**kwargs,
|
||||
)
|
||||
tree, pat = Library.mktree(SINGLE_USE_PREFIX + 'pathU')
|
||||
pat.add_port_pair(names=port_names, ptype='unk' if in_ptype is None else in_ptype)
|
||||
self._renderU(data=data, tree=tree, port_names=port_names, gen_kwargs=kwargs)
|
||||
return tree
|
||||
|
||||
def render(
|
||||
self,
|
||||
batch: Sequence[RenderStep],
|
||||
|
|
@ -959,6 +1075,8 @@ class AutoTool(Tool, metaclass=ABCMeta):
|
|||
self._renderL(data=step.data, tree=tree, port_names=port_names, straight_kwargs=kwargs)
|
||||
elif step.opcode == 'S':
|
||||
self._renderS(data=step.data, tree=tree, port_names=port_names, gen_kwargs=kwargs)
|
||||
elif step.opcode == 'U':
|
||||
self._renderU(data=step.data, tree=tree, port_names=port_names, gen_kwargs=kwargs)
|
||||
return tree
|
||||
|
||||
|
||||
|
|
@ -1086,6 +1204,7 @@ class PathTool(Tool, metaclass=ABCMeta):
|
|||
port_rot = step.start_port.rotation
|
||||
# Masque convention: Port rotation points INTO the device.
|
||||
# So the direction of travel for the path is AWAY from the port, i.e., port_rot + pi.
|
||||
assert port_rot is not None
|
||||
|
||||
if step.opcode == 'L':
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import numpy
|
||||
from numpy import pi
|
||||
from masque import Pather, RenderPather, Library, Port
|
||||
from masque import Pather, RenderPather, Library, Pattern, Port
|
||||
from masque.builder.tools import PathTool
|
||||
|
||||
def test_pather_trace_basic() -> None:
|
||||
|
|
@ -25,6 +25,7 @@ def test_pather_trace_basic() -> None:
|
|||
# (-5000, -500) rot pi - pi/2 = pi/2
|
||||
# Add to start: (-10000, -500) rot pi/2
|
||||
assert numpy.allclose(p.pattern.ports['A'].offset, (-10000, -500))
|
||||
assert p.pattern.ports['A'].rotation is not None
|
||||
assert numpy.isclose(p.pattern.ports['A'].rotation, pi/2)
|
||||
|
||||
def test_pather_trace_to() -> None:
|
||||
|
|
@ -151,8 +152,54 @@ def test_renderpather_uturn_fallback() -> None:
|
|||
assert rp.paths['A'][1].opcode == 'L'
|
||||
|
||||
rp.render()
|
||||
assert rp.pattern.ports['A'].rotation is not None
|
||||
assert numpy.isclose(rp.pattern.ports['A'].rotation, pi)
|
||||
|
||||
def test_autotool_uturn() -> None:
|
||||
from masque.builder.tools import AutoTool
|
||||
lib = Library()
|
||||
|
||||
# Setup AutoTool with a simple straight and a bend
|
||||
def make_straight(length: float) -> Pattern:
|
||||
pat = Pattern()
|
||||
pat.rect(layer='M1', xmin=0, xmax=length, yctr=0, ly=1000)
|
||||
pat.ports['in'] = Port((0, 0), 0)
|
||||
pat.ports['out'] = Port((length, 0), pi)
|
||||
return pat
|
||||
|
||||
bend_pat = Pattern()
|
||||
bend_pat.polygon(layer='M1', vertices=[(0, -500), (0, 500), (1000, -500)])
|
||||
bend_pat.ports['in'] = Port((0, 0), 0)
|
||||
bend_pat.ports['out'] = Port((500, -500), pi/2)
|
||||
lib['bend'] = bend_pat
|
||||
|
||||
tool = AutoTool(
|
||||
straights=[AutoTool.Straight(ptype='wire', fn=make_straight, in_port_name='in', out_port_name='out')],
|
||||
bends=[AutoTool.Bend(abstract=lib.abstract('bend'), in_port_name='in', out_port_name='out', clockwise=True)],
|
||||
sbends=[],
|
||||
transitions={},
|
||||
default_out_ptype='wire'
|
||||
)
|
||||
|
||||
p = Pather(lib, tools=tool)
|
||||
p.pattern.ports['A'] = Port((0, 0), 0)
|
||||
|
||||
# CW U-turn (jog < 0)
|
||||
# R = 500. jog = -2000. length = 1000.
|
||||
# p0 = planL(length=1000) -> out at (1000, -500) rot pi/2
|
||||
# R2 = 500.
|
||||
# l2_length = abs(-2000) - abs(-500) - 500 = 1000.
|
||||
p.at('A').uturn(offset=-2000, length=1000)
|
||||
|
||||
# Final port should be at (-1000, 2000) rot pi
|
||||
# Start: (0,0) rot 0. Wire direction is rot + pi = pi (West, -x).
|
||||
# Tool planU returns (length, jog) = (1000, -2000) relative to (0,0) rot 0.
|
||||
# Rotation of pi transforms (1000, -2000) to (-1000, 2000).
|
||||
# Final rotation: 0 + pi = pi.
|
||||
assert numpy.allclose(p.pattern.ports['A'].offset, (-1000, 2000))
|
||||
assert p.pattern.ports['A'].rotation is not None
|
||||
assert numpy.isclose(p.pattern.ports['A'].rotation, pi)
|
||||
|
||||
def test_pather_trace_into() -> None:
|
||||
lib = Library()
|
||||
tool = PathTool(layer='M1', width=1000)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue