[Pather/RenderPather/PathTool] Add updated pather tests

This commit is contained in:
jan 2026-03-06 23:09:59 -08:00
commit 9d6fb985d8

View file

@ -0,0 +1,183 @@
import numpy
from numpy import pi
from masque import Pather, RenderPather, Library, Port
from masque.builder.tools import PathTool
def test_pather_trace_basic() -> None:
lib = Library()
tool = PathTool(layer='M1', width=1000)
p = Pather(lib, tools=tool)
# Port rotation 0 points in +x (INTO device).
# To extend it, we move in -x direction.
p.pattern.ports['A'] = Port((0, 0), rotation=0)
# Trace single port
p.at('A').trace(None, 5000)
assert numpy.allclose(p.pattern.ports['A'].offset, (-5000, 0))
# Trace with bend
p.at('A').trace(True, 5000) # CCW bend
# Port was at (-5000, 0) rot 0.
# New wire starts at (-5000, 0) rot 0.
# Output port of wire before rotation: (5000, 500) rot -pi/2
# Rotate by pi (since dev port rot is 0 and tool port rot is 0):
# (-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 numpy.isclose(p.pattern.ports['A'].rotation, pi/2)
def test_pather_trace_to() -> None:
lib = Library()
tool = PathTool(layer='M1', width=1000)
p = Pather(lib, tools=tool)
p.pattern.ports['A'] = Port((0, 0), rotation=0)
# Trace to x=-10000
p.at('A').trace_to(None, x=-10000)
assert numpy.allclose(p.pattern.ports['A'].offset, (-10000, 0))
# Trace to position=-20000
p.at('A').trace_to(None, p=-20000)
assert numpy.allclose(p.pattern.ports['A'].offset, (-20000, 0))
def test_pather_bundle_trace() -> None:
lib = Library()
tool = PathTool(layer='M1', width=1000)
p = Pather(lib, tools=tool)
p.pattern.ports['A'] = Port((0, 0), rotation=0)
p.pattern.ports['B'] = Port((0, 2000), rotation=0)
# Straight bundle - all should align to same x
p.at(['A', 'B']).straight(xmin=-10000)
assert numpy.isclose(p.pattern.ports['A'].offset[0], -10000)
assert numpy.isclose(p.pattern.ports['B'].offset[0], -10000)
# Bundle with bend
p.at(['A', 'B']).ccw(xmin=-20000, spacing=2000)
# Traveling in -x direction. CCW turn turns towards -y.
# A is at y=0, B is at y=2000.
# Rotation center is at y = -R.
# A is closer to center than B. So A is inner, B is outer.
# xmin is coordinate of innermost bend (A).
assert numpy.isclose(p.pattern.ports['A'].offset[0], -20000)
# B's bend is further out (more negative x)
assert numpy.isclose(p.pattern.ports['B'].offset[0], -22000)
def test_pather_each_bound() -> None:
lib = Library()
tool = PathTool(layer='M1', width=1000)
p = Pather(lib, tools=tool)
p.pattern.ports['A'] = Port((0, 0), rotation=0)
p.pattern.ports['B'] = Port((-1000, 2000), rotation=0)
# Each should move by 5000 (towards -x)
p.at(['A', 'B']).trace(None, each=5000)
assert numpy.allclose(p.pattern.ports['A'].offset, (-5000, 0))
assert numpy.allclose(p.pattern.ports['B'].offset, (-6000, 2000))
def test_selection_management() -> None:
lib = Library()
p = Pather(lib)
p.pattern.ports['A'] = Port((0, 0), rotation=0)
p.pattern.ports['B'] = Port((0, 0), rotation=0)
pp = p.at('A')
assert pp.ports == ['A']
pp.select('B')
assert pp.ports == ['A', 'B']
pp.deselect('A')
assert pp.ports == ['B']
pp.select(['A'])
assert pp.ports == ['B', 'A']
pp.drop()
assert 'A' not in p.pattern.ports
assert 'B' not in p.pattern.ports
assert pp.ports == []
def test_mark_fork() -> None:
lib = Library()
p = Pather(lib)
p.pattern.ports['A'] = Port((100, 200), rotation=1)
pp = p.at('A')
pp.mark('B')
assert 'B' in p.pattern.ports
assert numpy.allclose(p.pattern.ports['B'].offset, (100, 200))
assert p.pattern.ports['B'].rotation == 1
assert pp.ports == ['A'] # mark keeps current selection
pp.fork('C')
assert 'C' in p.pattern.ports
assert pp.ports == ['C'] # fork switches to new name
def test_rename() -> None:
lib = Library()
p = Pather(lib)
p.pattern.ports['A'] = Port((0, 0), rotation=0)
p.at('A').rename('B')
assert 'A' not in p.pattern.ports
assert 'B' in p.pattern.ports
p.pattern.ports['C'] = Port((0, 0), rotation=0)
pp = p.at(['B', 'C'])
pp.rename({'B': 'D', 'C': 'E'})
assert 'B' not in p.pattern.ports
assert 'C' not in p.pattern.ports
assert 'D' in p.pattern.ports
assert 'E' in p.pattern.ports
assert set(pp.ports) == {'D', 'E'}
def test_renderpather_uturn_fallback() -> None:
lib = Library()
tool = PathTool(layer='M1', width=1000)
rp = RenderPather(lib, tools=tool)
rp.pattern.ports['A'] = Port((0, 0), rotation=0)
# PathTool doesn't implement planU, so it should fall back to two planL calls
rp.at('A').uturn(offset=10000, length=5000)
# Two steps should be added
assert len(rp.paths['A']) == 2
assert rp.paths['A'][0].opcode == 'L'
assert rp.paths['A'][1].opcode == 'L'
rp.render()
assert numpy.isclose(rp.pattern.ports['A'].rotation, pi)
def test_pather_trace_into() -> None:
lib = Library()
tool = PathTool(layer='M1', width=1000)
p = Pather(lib, tools=tool)
# 1. Straight connector
p.pattern.ports['A'] = Port((0, 0), rotation=0)
p.pattern.ports['B'] = Port((-10000, 0), rotation=pi)
p.at('A').trace_into('B', plug_destination=False)
assert 'B' in p.pattern.ports
assert 'A' in p.pattern.ports
assert numpy.allclose(p.pattern.ports['A'].offset, (-10000, 0))
# 2. Single bend
p.pattern.ports['C'] = Port((0, 0), rotation=0)
p.pattern.ports['D'] = Port((-5000, 5000), rotation=pi/2)
p.at('C').trace_into('D', plug_destination=False)
assert 'D' in p.pattern.ports
assert 'C' in p.pattern.ports
assert numpy.allclose(p.pattern.ports['C'].offset, (-5000, 5000))
# 3. Jog (S-bend)
p.pattern.ports['E'] = Port((0, 0), rotation=0)
p.pattern.ports['F'] = Port((-10000, 2000), rotation=pi)
p.at('E').trace_into('F', plug_destination=False)
assert 'F' in p.pattern.ports
assert 'E' in p.pattern.ports
assert numpy.allclose(p.pattern.ports['E'].offset, (-10000, 2000))