New modules: - patterns/ library: decoupling bank, pull resistor, crystal oscillator placement with power symbol attachment and grid math helpers - tools/batch.py: atomic file-based batch operations with dry_run - tools/power_symbols.py: add_power_symbol with auto #PWR refs - tools/schematic_patterns.py: MCP wrappers for pattern library - tools/schematic_edit.py: modify/remove components, title blocks, annotations - resources/schematic.py: schematic data resources 43 new tests (99 total), lint clean.
163 lines
5.4 KiB
Python
163 lines
5.4 KiB
Python
"""Unit tests for the geometry helpers in mckicad.patterns._geometry.
|
|
|
|
These tests do NOT require kicad-sch-api — they test pure math and
|
|
string matching functions.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
class TestSnapToGrid:
|
|
"""Tests for snap_to_grid()."""
|
|
|
|
def test_exact_grid_point(self):
|
|
from mckicad.patterns._geometry import snap_to_grid
|
|
|
|
assert snap_to_grid(2.54) == 2.54
|
|
assert snap_to_grid(5.08) == 5.08
|
|
assert snap_to_grid(0.0) == 0.0
|
|
|
|
def test_snap_up(self):
|
|
from mckicad.patterns._geometry import snap_to_grid
|
|
|
|
# 2.0 is closer to 2.54 than 0.0 with grid=2.54
|
|
assert snap_to_grid(2.0) == 2.54
|
|
|
|
def test_snap_down(self):
|
|
from mckicad.patterns._geometry import snap_to_grid
|
|
|
|
# 1.0 is closer to 0.0 than 2.54
|
|
assert snap_to_grid(1.0) == 0.0
|
|
|
|
def test_negative_values(self):
|
|
from mckicad.patterns._geometry import snap_to_grid
|
|
|
|
assert snap_to_grid(-2.54) == -2.54
|
|
assert snap_to_grid(-1.0) == 0.0
|
|
|
|
def test_custom_grid(self):
|
|
from mckicad.patterns._geometry import snap_to_grid
|
|
|
|
# Fine grid (1.27mm)
|
|
assert snap_to_grid(1.0, grid=1.27) == 1.27
|
|
assert snap_to_grid(0.5, grid=1.27) == 0.0
|
|
|
|
def test_large_values(self):
|
|
from mckicad.patterns._geometry import snap_to_grid
|
|
|
|
result = snap_to_grid(100.0)
|
|
assert result % 2.54 == pytest.approx(0.0, abs=0.001)
|
|
|
|
|
|
class TestGridPositions:
|
|
"""Tests for grid_positions()."""
|
|
|
|
def test_single_position(self):
|
|
from mckicad.patterns._geometry import grid_positions
|
|
|
|
positions = grid_positions(100, 100, 1)
|
|
assert len(positions) == 1
|
|
# snap_to_grid(100) = round(100/2.54)*2.54 = 39*2.54 = 99.06
|
|
assert positions[0] == (pytest.approx(99.06, abs=0.01), pytest.approx(99.06, abs=0.01))
|
|
|
|
def test_row_layout(self):
|
|
from mckicad.patterns._geometry import grid_positions
|
|
|
|
positions = grid_positions(0, 0, 3, cols=6, h_spacing=10.0)
|
|
assert len(positions) == 3
|
|
# All should be on same row (same y)
|
|
ys = [p[1] for p in positions]
|
|
assert all(y == ys[0] for y in ys)
|
|
|
|
def test_wraps_to_next_row(self):
|
|
from mckicad.patterns._geometry import grid_positions
|
|
|
|
positions = grid_positions(0, 0, 4, cols=2, h_spacing=10.0, v_spacing=15.0)
|
|
assert len(positions) == 4
|
|
# First two on row 0, next two on row 1
|
|
assert positions[0][1] == positions[1][1] # same row
|
|
assert positions[2][1] == positions[3][1] # same row
|
|
assert positions[2][1] != positions[0][1] # different rows
|
|
|
|
def test_zero_count(self):
|
|
from mckicad.patterns._geometry import grid_positions
|
|
|
|
positions = grid_positions(100, 100, 0)
|
|
assert positions == []
|
|
|
|
def test_all_positions_grid_aligned(self):
|
|
from mckicad.patterns._geometry import grid_positions, snap_to_grid
|
|
|
|
positions = grid_positions(100, 100, 12, cols=4)
|
|
for x, y in positions:
|
|
# Verify each coordinate is unchanged by snap_to_grid
|
|
# (i.e., already on the grid)
|
|
assert snap_to_grid(x) == pytest.approx(x, abs=0.001)
|
|
assert snap_to_grid(y) == pytest.approx(y, abs=0.001)
|
|
|
|
|
|
class TestIsGroundNet:
|
|
"""Tests for is_ground_net()."""
|
|
|
|
def test_standard_grounds(self):
|
|
from mckicad.patterns._geometry import is_ground_net
|
|
|
|
assert is_ground_net("GND") is True
|
|
assert is_ground_net("GNDA") is True
|
|
assert is_ground_net("GNDD") is True
|
|
assert is_ground_net("VSS") is True
|
|
assert is_ground_net("VSSA") is True
|
|
|
|
def test_extended_grounds(self):
|
|
from mckicad.patterns._geometry import is_ground_net
|
|
|
|
assert is_ground_net("AGND") is True
|
|
assert is_ground_net("DGND") is True
|
|
assert is_ground_net("PGND") is True
|
|
assert is_ground_net("SGND") is True
|
|
|
|
def test_case_insensitive(self):
|
|
from mckicad.patterns._geometry import is_ground_net
|
|
|
|
assert is_ground_net("gnd") is True
|
|
assert is_ground_net("Gnd") is True
|
|
assert is_ground_net("vss") is True
|
|
|
|
def test_not_ground(self):
|
|
from mckicad.patterns._geometry import is_ground_net
|
|
|
|
assert is_ground_net("VCC") is False
|
|
assert is_ground_net("+3V3") is False
|
|
assert is_ground_net("+5V") is False
|
|
assert is_ground_net("SPI_CLK") is False
|
|
assert is_ground_net("") is False
|
|
|
|
def test_whitespace_stripped(self):
|
|
from mckicad.patterns._geometry import is_ground_net
|
|
|
|
assert is_ground_net(" GND ") is True
|
|
|
|
|
|
class TestResolvePowerLibId:
|
|
"""Tests for resolve_power_lib_id()."""
|
|
|
|
def test_known_symbols(self):
|
|
from mckicad.patterns._geometry import resolve_power_lib_id
|
|
|
|
assert resolve_power_lib_id("GND") == "power:GND"
|
|
assert resolve_power_lib_id("VCC") == "power:VCC"
|
|
assert resolve_power_lib_id("+5V") == "power:+5V"
|
|
assert resolve_power_lib_id("+3V3") == "power:+3V3"
|
|
|
|
def test_case_insensitive_lookup(self):
|
|
from mckicad.patterns._geometry import resolve_power_lib_id
|
|
|
|
assert resolve_power_lib_id("gnd") == "power:GND"
|
|
assert resolve_power_lib_id("vcc") == "power:VCC"
|
|
|
|
def test_unknown_falls_back_to_power_prefix(self):
|
|
from mckicad.patterns._geometry import resolve_power_lib_id
|
|
|
|
assert resolve_power_lib_id("+1V8") == "power:+1V8"
|
|
assert resolve_power_lib_id("VDDIO") == "power:VDDIO"
|