Add IFA/meander/stub antenna templates with SVG and KiCad export.

Progetta now returns a parametric radiator geometry (segments + preview + .kicad_mod) so the layout can be replicated without inventing EM results.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-13 19:27:06 +02:00
co-authored by Cursor
parent 1d3cf12482
commit 556306bf4d
9 changed files with 529 additions and 8 deletions
+76
View File
@@ -177,3 +177,79 @@ def test_design_recipe_ready_with_ant_and_stackup():
assert recipe.radiator.length_mm_suggest is not None
assert recipe.zone is not None
assert recipe.zone.bbox_mm is not None
assert recipe.geometry is not None
assert recipe.geometry.template == "ifa"
assert recipe.geometry.fit in ("ok", "scaled")
assert len(recipe.geometry.segments) >= 2
assert recipe.geometry.svg and "<svg" in recipe.geometry.svg
assert recipe.geometry.kicad_mod and '(footprint "' in recipe.geometry.kicad_mod
assert "fp_line" in recipe.geometry.kicad_mod
def test_geometry_templates_produce_export():
from backend.pinscopex.antenna_geometry import build_geometry
for tmpl in ("ifa", "meander", "stub"):
geo = build_geometry(tmpl, f0_mhz=2440.0, w_mm=0.4, er=4.5)
assert geo.fit == "ok"
assert geo.total_length_mm is not None and geo.total_length_mm > 10
assert geo.length_ideal_mm is not None
assert geo.total_length_mm >= geo.length_ideal_mm * 0.9
assert geo.svg and "path" in geo.svg
assert geo.kicad_mod and '(pad "1"' in geo.kicad_mod
assert len(geo.segments) >= 1
def test_geometry_overflow_tiny_zone():
from backend.pinscopex.antenna_geometry import build_geometry
geo = build_geometry(
"ifa",
f0_mhz=2440.0,
w_mm=0.4,
er=4.5,
zone_bbox_mm=(0.0, 0.0, 2.0, 1.0),
feed_xy=(0.0, 0.0),
)
assert geo.fit == "overflow"
assert not geo.segments
def test_design_recipe_meander_template():
g = DesignGraph(
components={
"ANT1": Component(
reference="ANT1", value="feed", footprint="",
component_type=ComponentType.CONNECTOR,
pins={"1": "ANT_FEED"},
),
},
nets={
"ANT_FEED": Net(
name="ANT_FEED", net_type=NetType.SIGNAL,
pins=[PinConnection(component_ref="ANT1", pin_number="1")],
),
},
)
layout = LayoutGraph(
footprints={
"ANT1": LayoutFootprint(
reference="ANT1", x=0.0, y=0.0, layer="F.Cu",
pads=[LayoutPad(number="1", x=0.0, y=0.0, net="ANT_FEED")],
),
},
nets={"ANT_FEED": 1},
stackup=LayoutStackup(
copper_layers=["F.Cu", "B.Cu"],
dielectrics=[LayoutDielectric(name="FR4", er=4.5, height_mm=0.2)],
copper_thickness_mm=0.035,
),
)
recipe = build_design_recipe(
g, layout, f0_mhz=2440.0, template="meander",
)
assert recipe.status == "ready"
assert recipe.geometry is not None
assert recipe.geometry.template == "meander"
assert recipe.geometry.fit == "ok"
assert recipe.geometry.kicad_mod is not None