[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,
|
self,
|
||||||
jog: float,
|
jog: float,
|
||||||
*,
|
*,
|
||||||
|
length: float = 0,
|
||||||
in_ptype: str | None = None,
|
in_ptype: str | None = None,
|
||||||
out_ptype: str | None = None,
|
out_ptype: str | None = None,
|
||||||
port_names: tuple[str, str] = ('A', 'B'),
|
port_names: tuple[str, str] = ('A', 'B'),
|
||||||
|
|
@ -597,6 +598,14 @@ class AutoTool(Tool, metaclass=ABCMeta):
|
||||||
b_transition: 'AutoTool.Transition | None'
|
b_transition: 'AutoTool.Transition | None'
|
||||||
out_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]
|
straights: list[Straight]
|
||||||
""" List of straight-generators to choose from, in order of priority """
|
""" 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)
|
self._renderS(data=data, tree=tree, port_names=port_names, gen_kwargs=kwargs)
|
||||||
return tree
|
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(
|
def render(
|
||||||
self,
|
self,
|
||||||
batch: Sequence[RenderStep],
|
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)
|
self._renderL(data=step.data, tree=tree, port_names=port_names, straight_kwargs=kwargs)
|
||||||
elif step.opcode == 'S':
|
elif step.opcode == 'S':
|
||||||
self._renderS(data=step.data, tree=tree, port_names=port_names, gen_kwargs=kwargs)
|
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
|
return tree
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1086,6 +1204,7 @@ class PathTool(Tool, metaclass=ABCMeta):
|
||||||
port_rot = step.start_port.rotation
|
port_rot = step.start_port.rotation
|
||||||
# Masque convention: Port rotation points INTO the device.
|
# 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.
|
# 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':
|
if step.opcode == 'L':
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import numpy
|
import numpy
|
||||||
from numpy import pi
|
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
|
from masque.builder.tools import PathTool
|
||||||
|
|
||||||
def test_pather_trace_basic() -> None:
|
def test_pather_trace_basic() -> None:
|
||||||
|
|
@ -25,6 +25,7 @@ def test_pather_trace_basic() -> None:
|
||||||
# (-5000, -500) rot pi - pi/2 = pi/2
|
# (-5000, -500) rot pi - pi/2 = pi/2
|
||||||
# Add to start: (-10000, -500) rot pi/2
|
# Add to start: (-10000, -500) rot pi/2
|
||||||
assert numpy.allclose(p.pattern.ports['A'].offset, (-10000, -500))
|
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)
|
assert numpy.isclose(p.pattern.ports['A'].rotation, pi/2)
|
||||||
|
|
||||||
def test_pather_trace_to() -> None:
|
def test_pather_trace_to() -> None:
|
||||||
|
|
@ -151,8 +152,54 @@ def test_renderpather_uturn_fallback() -> None:
|
||||||
assert rp.paths['A'][1].opcode == 'L'
|
assert rp.paths['A'][1].opcode == 'L'
|
||||||
|
|
||||||
rp.render()
|
rp.render()
|
||||||
|
assert rp.pattern.ports['A'].rotation is not None
|
||||||
assert numpy.isclose(rp.pattern.ports['A'].rotation, pi)
|
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:
|
def test_pather_trace_into() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue