Use the vendored Hammerstad-Jensen/Cohn engine for microstrip, stripline, and coupled-diff advice. Skip OpenEMS/pcbnew and refuse CPWG rather than inventing a number. Co-authored-by: Cursor <cursoragent@cursor.com>
76 lines
3.1 KiB
Python
76 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
from impedancefinder import planes
|
|
from impedancefinder.model import Point2D, SamplePoint, ZonePolygon
|
|
|
|
from .conftest import rect
|
|
|
|
|
|
def _sample_at(x_mm: float, width_mm: float = 0.2) -> SamplePoint:
|
|
return SamplePoint(
|
|
net="SIG", layer="F.Cu", distance_along_net_mm=x_mm, position=Point2D(x_mm, 0), width_mm=width_mm
|
|
)
|
|
|
|
|
|
def test_full_coverage_has_no_flags(full_ground_plane):
|
|
sample = _sample_at(2.0)
|
|
coverage = planes.coverage_at(sample, (full_ground_plane,), "In1.Cu")
|
|
assert coverage.is_covered
|
|
assert planes._flags_for(coverage, sample.width_mm) == ()
|
|
|
|
|
|
def test_void_directly_under_trace_flags_broken(split_ground_plane):
|
|
sample = _sample_at(5.0) # inside the 4..6mm gap
|
|
coverage = planes.coverage_at(sample, (split_ground_plane,), "In1.Cu")
|
|
assert not coverage.is_covered
|
|
assert planes._flags_for(coverage, sample.width_mm) == ("plane_broken",)
|
|
|
|
|
|
def test_void_near_but_not_under_trace_flags_proximity_only(split_ground_plane):
|
|
sample = _sample_at(3.9, width_mm=0.5) # covered, close to the gap edge at x=4
|
|
coverage = planes.coverage_at(sample, (split_ground_plane,), "In1.Cu")
|
|
assert coverage.is_covered
|
|
assert planes._flags_for(coverage, sample.width_mm) == ("plane_split_nearby",)
|
|
|
|
|
|
def test_far_from_void_has_no_proximity_flag(split_ground_plane):
|
|
sample = _sample_at(0.0)
|
|
coverage = planes.coverage_at(sample, (split_ground_plane,), "In1.Cu")
|
|
assert coverage.is_covered
|
|
assert planes._flags_for(coverage, sample.width_mm) == ()
|
|
|
|
|
|
def test_missing_plane_layer_reports_uncovered_not_a_crash():
|
|
sample = _sample_at(0.0)
|
|
coverage = planes.coverage_at(sample, (), "In1.Cu")
|
|
assert not coverage.is_covered
|
|
assert coverage.distance_to_void_mm is None
|
|
|
|
|
|
def test_resolve_reference_planes_outer_layer_has_only_below(stackup_4layer, full_ground_plane):
|
|
sample = _sample_at(2.0)
|
|
context = planes.resolve_reference_planes(stackup_4layer, (full_ground_plane,), sample)
|
|
assert context.above is None
|
|
assert context.below is not None
|
|
assert context.reference_plane_count == 1
|
|
|
|
|
|
def test_resolve_reference_planes_inner_layer_has_both(stackup_4layer):
|
|
top_plane = ZonePolygon(net="GND", layer="F.Cu", outlines_mm=(rect(-5, -5, 50, 5),))
|
|
bottom_plane = ZonePolygon(net="GND", layer="In2.Cu", outlines_mm=(rect(-5, -5, 50, 5),))
|
|
sample = SamplePoint(
|
|
net="SIG", layer="In1.Cu", distance_along_net_mm=0, position=Point2D(0, 0), width_mm=0.15
|
|
)
|
|
context = planes.resolve_reference_planes(stackup_4layer, (top_plane, bottom_plane), sample)
|
|
assert context.above is not None and context.above.is_covered
|
|
assert context.below is not None and context.below.is_covered
|
|
assert context.reference_plane_count == 2
|
|
|
|
|
|
def test_exclude_net_ignores_the_traces_own_copper():
|
|
own_net_pour = ZonePolygon(net="SIG", layer="F.Cu", outlines_mm=(rect(-5, -5, 50, 5),))
|
|
sample = _sample_at(0.0)
|
|
coverage = planes.coverage_at(sample, (own_net_pour,), "F.Cu", exclude_net="SIG")
|
|
assert not coverage.is_covered
|
|
assert coverage.distance_to_void_mm is None
|