Files
metabuilder/pcbgenerator/tests/test_demo_script.py
2026-01-21 17:18:12 +00:00

145 lines
5.1 KiB
Python

import sys
from pathlib import Path
import zipfile
import pytest
EXPECTED_DIR = Path(__file__).resolve().parent / "expected"
# Add repository root to path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from boardforge import PCB, Layer
def test_demo_script_equivalent(tmp_path):
base = ROOT
font_path = base / "fonts" / "RobotoMono.ttf"
svg_path = base / "graphics" / "torch.svg"
board = PCB(width=80, height=60)
board.set_layer_stack([
Layer.TOP_COPPER.value,
Layer.BOTTOM_COPPER.value,
Layer.TOP_SILK.value,
Layer.BOTTOM_SILK.value,
])
bat = board.add_component("CR2032", ref="BT1", at=(40, 10))
bat.add_pin("VCC", dx=0, dy=0)
bat.add_pin("GND", dx=10, dy=0)
bat.add_pad("VCC", dx=0, dy=0, w=2, h=2)
bat.add_pad("GND", dx=10, dy=0, w=2, h=2)
sw = board.add_component("SWITCH", ref="SW1", at=(40, 20))
sw.add_pin("A", dx=-2, dy=0)
sw.add_pin("B", dx=2, dy=0)
sw.add_pad("A", dx=-2, dy=0, w=1.8, h=1.8)
sw.add_pad("B", dx=2, dy=0, w=1.8, h=1.8)
resistor_positions = [(20, 35), (40, 35), (60, 35)]
resistors = []
for i, pos in enumerate(resistor_positions):
r = board.add_component("RESISTOR", ref=f"R{i+1}", at=pos)
r.add_pin("A", dx=-3, dy=0)
r.add_pin("B", dx=3, dy=0)
r.add_pad("A", dx=-3, dy=0, w=1.6, h=1.6)
r.add_pad("B", dx=3, dy=0, w=1.6, h=1.6)
resistors.append(r)
led_positions = [
(20, 50, 45),
(40, 50, 0),
(60, 50, -45),
]
leds = []
for i, (x, y, ang) in enumerate(led_positions):
d = board.add_component("LED", ref=f"D{i+1}", at=(x, y), rotation=ang)
d.add_pin("A", dx=0, dy=-2)
d.add_pin("K", dx=0, dy=2)
d.add_pad("A", dx=0, dy=-2, w=1.6, h=1.6)
d.add_pad("K", dx=0, dy=2, w=1.6, h=1.6)
leds.append(d)
board.route_trace("BT1:VCC", "SW1:A")
for r in resistors:
board.route_trace("SW1:B", f"{r.ref}:A")
for r, d in zip(resistors, leds):
board.route_trace(f"{r.ref}:B", f"{d.ref}:A")
for d in leds:
board.route_trace(f"{d.ref}:K", "BT1:GND")
for c in [bat, sw] + resistors + leds:
board.add_text_ttf(
c.ref,
font_path=str(font_path),
at=(c.at[0]-4, c.at[1]-5),
size=1.2,
layer=Layer.TOP_SILK.value,
)
if svg_path.exists():
board.add_svg_graphic(str(svg_path), layer=Layer.TOP_SILK.value, scale=1.2, at=(5, 5))
board.save_svg_previews(tmp_path)
zip_path = tmp_path / "demo_output.zip"
board.export_gerbers(zip_path)
assert zip_path.exists()
with zipfile.ZipFile(zip_path) as z:
names = set(z.namelist())
assert {"GTL.gbr", "GBL.gbr", "GTO.gbr", "GBO.gbr"}.issubset(names)
assert {"preview_top.svg", "preview_bottom.svg", "preview_top.png", "preview_bottom.png"}.issubset(names)
gtl_data = z.read("GTL.gbr").decode()
gbl_data = z.read("GBL.gbr").decode()
gto_data = z.read("GTO.gbr").decode()
gbo_data = z.read("GBO.gbr").decode()
top_png = z.read("preview_top.png") if "preview_top.png" in names else b""
expected_gtl = (EXPECTED_DIR / "demo_GTL.gbr").read_text()
expected_gbl = (EXPECTED_DIR / "demo_GBL.gbr").read_text()
expected_gto = (EXPECTED_DIR / "demo_GTO.gbr").read_text()
expected_gbo = (EXPECTED_DIR / "demo_GBO.gbr").read_text()
if top_png:
from io import BytesIO
from PIL import Image
with Image.open(BytesIO(top_png)) as img:
assert img.size == (board.width * 10, board.height * 10)
assert img.getpixel((30, 30)) == (93, 34, 146, 255)
# verify pad colours and rings
for comp in [bat, sw] + resistors + leds:
for pad in comp.pads:
px = int(pad.x * 10)
py = int(pad.y * 10)
assert img.getpixel((px, py)) == (255, 193, 0, 255)
if abs(pad.w - pad.h) <= 0.1:
ring_r = int((int(pad.w * 10) + 6) // 2)
ring_px = px + ring_r - 2
if ring_px < img.width:
assert img.getpixel((ring_px, py)) == (255, 236, 128, 255)
# verify silkscreen text renders in white
for comp in [bat, sw] + resistors + leds:
tx = int((comp.at[0] - 4) * 10)
ty = int((comp.at[1] - 5) * 10)
found_white = False
for dx in range(-10, 11):
for dy in range(-10, 11):
x = tx + dx
y = ty + dy
if 0 <= x < img.width and 0 <= y < img.height:
if img.getpixel((x, y)) == (255, 255, 255, 255):
found_white = True
break
if found_white:
break
assert found_white
assert gtl_data == expected_gtl
assert gbl_data == expected_gbl
assert gto_data == expected_gto
assert gbo_data == expected_gbo