Files
periscope/tests/test_placement_pack.py
T
michele c362ef8a56 Split native Periscope (periscope/src) from inherited PinScope (periscope/dependency).
Keep validate.py and the finding engine as-is. AGPL LICENSE stays at the repo root.
Docker overlays dependency then src. Do not delete the inherited tree.
2026-09-20 14:31:42 +02:00

112 lines
3.2 KiB
Python

"""Layout F2 placement_pack — gated; no invented millimetres."""
from __future__ import annotations
from tests.paths import SIMPLE_PROJECT, TAXONOMY
from pathlib import Path
from backend.periscopex.functional_groups import (
PlacementIcGroup,
PlacementSatellite,
FunctionalGroupsReport,
build_functional_groups,
)
from backend.periscopex.models import (
DesignGraph,
LayoutFootprint,
LayoutGraph,
LayoutPad,
)
from backend.periscopex.placement_pack import build_placement_pack
SIMPLE = SIMPLE_PROJECT
def _graph() -> DesignGraph:
return DesignGraph.model_validate_json(
(SIMPLE / "design_graph.json").read_text(encoding="utf-8"),
)
def test_simple_project_without_pcb_skips_pack():
plan = build_functional_groups(_graph())
pack = build_placement_pack(plan, None)
assert pack.status == "skipped"
assert pack.skip_reason == "no_pcb_footprints"
assert pack.placements == []
def test_layout_without_numeric_rules_skips():
plan = FunctionalGroupsReport(
groups=[
PlacementIcGroup(
ref="U1",
layout_rules=[{"kind": "decoupling_proximity", "pin": "5"}],
satellites=[
PlacementSatellite(
ref="C4",
component_type="capacitor",
role_hint="decoupling",
nets=["+3V3"],
),
],
),
],
)
layout = LayoutGraph(
footprints={
"U1": LayoutFootprint(
reference="U1", x=0, y=0, layer="F.Cu",
pads=[LayoutPad(number="5", x=0.0, y=0.0, net="+3V3")],
),
},
)
pack = build_placement_pack(plan, layout)
assert pack.status == "skipped"
assert pack.skip_reason == "no_numeric_layout_rules"
def test_numeric_rule_proposes_satellite_within_limit():
limit = 2.0
plan = FunctionalGroupsReport(
groups=[
PlacementIcGroup(
ref="U1",
layout_rules=[{
"kind": "decoupling_proximity",
"pin": "5",
"max_distance_mm": limit,
}],
satellites=[
PlacementSatellite(
ref="C4",
component_type="capacitor",
role_hint="decoupling",
nets=["+3V3"],
),
],
),
],
)
layout = LayoutGraph(
footprints={
"U1": LayoutFootprint(
reference="U1", x=0, y=0, layer="F.Cu",
pads=[LayoutPad(number="5", x=10.0, y=20.0, net="+3V3")],
),
},
)
pack = build_placement_pack(plan, layout)
assert pack.status == "packed"
assert pack.skip_reason is None
assert len(pack.placements) == 1
p = pack.placements[0]
assert p.ref == "C4"
assert p.anchor_ref == "U1"
assert p.max_distance_mm == limit
assert p.layer == "F.Cu"
dist = ((p.proposed_x - 10.0) ** 2 + (p.proposed_y - 20.0) ** 2) ** 0.5
assert dist <= limit + 1e-6
assert dist > 0