Files
periscope/tests/test_placement_pack.py
T
micheleandCursor 8d2b85600f Rebrand Pinscope to Periscope across product and codebase.
Rename the core package to periscopex, update UI/docs/Docker/deploy defaults to periscope.michelebigi.it, and keep legacy version/storage key aliases so existing projects keep working.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 20:02:04 +02:00

110 lines
3.2 KiB
Python

"""Layout F2 placement_pack — gated; no invented millimetres."""
from __future__ import annotations
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 = Path(__file__).resolve().parents[1] / "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