[PortPather] add default_spacing (.at(..., spacing=...))
This commit is contained in:
parent
833f5dd159
commit
d36a1a53e6
3 changed files with 105 additions and 10 deletions
|
|
@ -54,9 +54,10 @@ def main() -> None:
|
|||
(rpather.at(['GND', 'VCC'])
|
||||
.trace(True, xmax=-10_000, spacing=5_000) # Move West to -10k, turn South
|
||||
.retool(M1_tool) # Retools both GND and VCC
|
||||
.trace(True, emax=50_000, spacing=1_200) # Turn East, moves 50um extension
|
||||
.trace(False, emin=1_000, spacing=1_200) # U-turn back South
|
||||
.trace(False, emin=2_000, spacing=4_500) # U-turn back West
|
||||
.set_spacing(1_200) # Default bundle spacing for later bends
|
||||
.trace(True, emax=50_000) # Turn East, moves 50um extension
|
||||
.trace(False, emin=1_000) # U-turn back South
|
||||
.trace(False, emin=2_000, spacing=4_500) # U-turn back West, overriding the default spacing
|
||||
)
|
||||
|
||||
# Retool VCC back to M2 and move both to x=-28k
|
||||
|
|
|
|||
|
|
@ -1173,30 +1173,50 @@ class Pather(PortList):
|
|||
self.pattern.flatten(self.library)
|
||||
return self
|
||||
|
||||
def at(self, portspec: str | Iterable[str]) -> 'PortPather':
|
||||
return PortPather(portspec, self)
|
||||
def at(
|
||||
self,
|
||||
portspec: str | Iterable[str],
|
||||
*,
|
||||
spacing: float | ArrayLike | None = None,
|
||||
) -> 'PortPather':
|
||||
return PortPather(portspec, self, default_spacing=spacing)
|
||||
|
||||
|
||||
class PortPather:
|
||||
""" Port state manager for fluent pathing. """
|
||||
def __init__(self, ports: str | Iterable[str], pather: Pather) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
ports: str | Iterable[str],
|
||||
pather: Pather,
|
||||
*,
|
||||
default_spacing: float | ArrayLike | None = None,
|
||||
) -> None:
|
||||
self.ports = [ports] if isinstance(ports, str) else list(ports)
|
||||
self.pather = pather
|
||||
self.default_spacing = default_spacing
|
||||
|
||||
def retool(self, tool: Tool) -> Self:
|
||||
self.pather.retool(tool, self.ports)
|
||||
return self
|
||||
|
||||
def set_spacing(self, spacing: float | ArrayLike | None) -> Self:
|
||||
self.default_spacing = spacing
|
||||
return self
|
||||
|
||||
@contextmanager
|
||||
def toolctx(self, tool: Tool) -> Iterator[Self]:
|
||||
with self.pather.toolctx(tool, keys=self.ports):
|
||||
yield self
|
||||
|
||||
def trace(self, ccw: SupportsBool | None, length: float | None = None, **kw: Any) -> Self:
|
||||
if 'spacing' not in kw and self.default_spacing is not None and len(self.ports) > 1 and ccw is not None:
|
||||
kw['spacing'] = self.default_spacing
|
||||
self.pather.trace(self.ports, ccw, length, **kw)
|
||||
return self
|
||||
|
||||
def trace_to(self, ccw: SupportsBool | None, **kw: Any) -> Self:
|
||||
if 'spacing' not in kw and self.default_spacing is not None and len(self.ports) > 1 and ccw is not None:
|
||||
kw['spacing'] = self.default_spacing
|
||||
self.pather.trace_to(self.ports, ccw, **kw)
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import numpy
|
||||
from numpy import pi
|
||||
from numpy.testing import assert_allclose, assert_equal
|
||||
|
||||
from masque import Pather, Library, Pattern, Port
|
||||
from masque.builder.tools import PathTool, Tool
|
||||
from masque.error import BuildError, PortError, PatternError
|
||||
from masque.builder.tools import PathTool
|
||||
from masque.error import BuildError, PortError
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -117,6 +115,82 @@ def test_pather_bundle_trace() -> None:
|
|||
assert numpy.isclose(p.pattern.ports['A'].offset[0], -20000)
|
||||
assert numpy.isclose(p.pattern.ports['B'].offset[0], -22000)
|
||||
|
||||
def test_portpather_default_spacing_matches_explicit_spacing() -> None:
|
||||
lib_default = Library()
|
||||
tool_default = PathTool(layer='M1', width=1000)
|
||||
p_default = Pather(lib_default, tools=tool_default, auto_render=False)
|
||||
p_default.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||
p_default.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||
|
||||
lib_explicit = Library()
|
||||
tool_explicit = PathTool(layer='M1', width=1000)
|
||||
p_explicit = Pather(lib_explicit, tools=tool_explicit, auto_render=False)
|
||||
p_explicit.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||
p_explicit.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||
|
||||
p_default.at(['A', 'B'], spacing=2000).ccw(xmin=-20000)
|
||||
p_explicit.at(['A', 'B']).ccw(xmin=-20000, spacing=2000)
|
||||
|
||||
assert_allclose(p_default.pattern.ports['A'].offset, p_explicit.pattern.ports['A'].offset)
|
||||
assert_allclose(p_default.pattern.ports['B'].offset, p_explicit.pattern.ports['B'].offset)
|
||||
|
||||
def test_portpather_default_spacing_reused_and_overridden() -> None:
|
||||
p_default = Pather(Library(), tools=PathTool(layer='M1', width=1000), auto_render=False)
|
||||
p_default.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||
p_default.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||
|
||||
p_explicit = Pather(Library(), tools=PathTool(layer='M1', width=1000), auto_render=False)
|
||||
p_explicit.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||
p_explicit.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||
|
||||
pp_default = p_default.at(['A', 'B'], spacing=2000)
|
||||
pp_default.ccw(xmin=-20000)
|
||||
pp_default.cw(emin=1000)
|
||||
p_explicit.at(['A', 'B']).ccw(xmin=-20000, spacing=2000).cw(emin=1000, spacing=2000)
|
||||
|
||||
assert_allclose(p_default.pattern.ports['A'].offset, p_explicit.pattern.ports['A'].offset)
|
||||
assert_allclose(p_default.pattern.ports['B'].offset, p_explicit.pattern.ports['B'].offset)
|
||||
|
||||
p_override = Pather(Library(), tools=PathTool(layer='M1', width=1000), auto_render=False)
|
||||
p_override.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||
p_override.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||
|
||||
p_override_explicit = Pather(Library(), tools=PathTool(layer='M1', width=1000), auto_render=False)
|
||||
p_override_explicit.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||
p_override_explicit.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||
|
||||
p_override.at(['A', 'B'], spacing=2000).ccw(xmin=-20000, spacing=3000)
|
||||
p_override_explicit.at(['A', 'B']).ccw(xmin=-20000, spacing=3000)
|
||||
|
||||
assert_allclose(p_override.pattern.ports['A'].offset, p_override_explicit.pattern.ports['A'].offset)
|
||||
assert_allclose(p_override.pattern.ports['B'].offset, p_override_explicit.pattern.ports['B'].offset)
|
||||
|
||||
def test_portpather_default_spacing_not_injected_for_straight_bundle() -> None:
|
||||
lib = Library()
|
||||
tool = PathTool(layer='M1', width=1000)
|
||||
p = Pather(lib, tools=tool, auto_render=False)
|
||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||
p.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||
|
||||
p.at(['A', 'B'], spacing=2000).straight(xmin=-10000)
|
||||
|
||||
assert numpy.isclose(p.pattern.ports['A'].offset[0], -10000)
|
||||
assert numpy.isclose(p.pattern.ports['B'].offset[0], -10000)
|
||||
|
||||
def test_portpather_default_spacing_vector_revalidated_after_selection_change() -> None:
|
||||
lib = Library()
|
||||
tool = PathTool(layer='M1', width=1000)
|
||||
p = Pather(lib, tools=tool, auto_render=False)
|
||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||
p.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||
p.pattern.ports['C'] = Port((0, 4000), rotation=0)
|
||||
|
||||
pp = p.at(['A', 'B', 'C'], spacing=[2000, 3000])
|
||||
pp.deselect('C')
|
||||
|
||||
with pytest.raises(BuildError, match='spacing must be scalar or have length 1'):
|
||||
pp.ccw(xmin=-20000)
|
||||
|
||||
def test_pather_each_bound() -> None:
|
||||
lib = Library()
|
||||
tool = PathTool(layer='M1', width=1000)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue