Files
periscope/vendor/impedancefinder/report.py
micheleandCursor 6fc2ac583d Integrate ImpedenceFinder closed-form Z0 into the project Impedance tab.
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>
2026-09-10 23:14:57 +02:00

122 lines
4.1 KiB
Python

"""Profile assembly and export.
build_profile is pure. to_csv is this module's impure edge (writes a file).
gerber2ems export/import now lives in gerber2ems_export.py, not here (see
docs/field-solver-export-plan.md for why it grew into its own module and
what was verified against the real installed tool). to_rf2dfieldsolver
below is still a stub -- RF2DFieldSolver is GUI-only with no batch mode
(verified against its main.cpp), so an automated export-and-read-back loop
isn't possible for it the way it is for gerber2ems; see the plan doc for
the full comparison.
"""
from __future__ import annotations
import csv
from impedancefinder import net_analysis
from impedancefinder.model import BoardData, ImpedanceSample, NetProfile, NetSummary
from impedancefinder.net_analysis import NetAnalysisResult
_CSV_COLUMNS = (
"distance_along_net_mm",
"x_mm",
"y_mm",
"layer",
"width_mm",
"topology",
"z0_ohms",
"flags",
)
_SUMMARY_CSV_COLUMNS = (
"net_name",
"length_mm",
"branch_count",
"is_differential",
"partner_net_name",
"topologies",
"z0_min_ohms",
"z0_max_ohms",
"z0_avg_ohms",
"flags",
)
def build_profile(net_name: str, samples: tuple[ImpedanceSample, ...]) -> NetProfile:
return NetProfile(net_name=net_name, samples=samples)
def to_csv(profile: NetProfile, path: str) -> None:
with open(path, "w", newline="", encoding="utf-8") as handle:
writer = csv.writer(handle)
writer.writerow(_CSV_COLUMNS)
for sample in profile.samples:
writer.writerow(_csv_row(sample))
def _csv_row(sample: ImpedanceSample) -> tuple:
return (
sample.distance_along_net_mm,
sample.position.x_mm,
sample.position.y_mm,
sample.layer,
sample.width_mm,
sample.topology.name,
"" if sample.z0_ohms is None else sample.z0_ohms,
";".join(sample.flags),
)
def summarize_net(net_name: str, board_data: BoardData, result: NetAnalysisResult) -> NetSummary:
"""Collapse one net's per-sample analysis into a single report row —
used by board_report.py for a batch of nets, one closed-form pass each,
no openEMS involved."""
z0_values = tuple(sample.z0_ohms for sample in result.samples if sample.z0_ohms is not None)
return NetSummary(
net_name=net_name,
length_mm=net_analysis.net_length_mm(board_data, net_name),
branch_count=result.branch_count,
is_differential=result.is_differential,
partner_net_name=result.partner_net_name,
topologies=_unique_in_order(sample.topology.name for sample in result.samples),
z0_min_ohms=min(z0_values) if z0_values else None,
z0_max_ohms=max(z0_values) if z0_values else None,
z0_avg_ohms=sum(z0_values) / len(z0_values) if z0_values else None,
flags=_unique_in_order(flag for sample in result.samples for flag in sample.flags),
)
def _unique_in_order(values) -> tuple[str, ...]:
return tuple(dict.fromkeys(values))
def summaries_to_csv(summaries: tuple[NetSummary, ...], path: str) -> None:
with open(path, "w", newline="", encoding="utf-8") as handle:
writer = csv.writer(handle)
writer.writerow(_SUMMARY_CSV_COLUMNS)
for summary in summaries:
writer.writerow(_summary_csv_row(summary))
def _summary_csv_row(summary: NetSummary) -> tuple:
return (
summary.net_name,
summary.length_mm,
summary.branch_count,
summary.is_differential,
summary.partner_net_name or "",
";".join(summary.topologies),
"" if summary.z0_min_ohms is None else summary.z0_min_ohms,
"" if summary.z0_max_ohms is None else summary.z0_max_ohms,
"" if summary.z0_avg_ohms is None else summary.z0_avg_ohms,
";".join(summary.flags),
)
def to_rf2dfieldsolver(profile: NetProfile, path: str) -> None:
"""Export a 2D cross-section for RF2DFieldSolver at a flagged region.
Not implemented yet — see the module docstring and
docs/field-solver-export-plan.md."""
raise NotImplementedError("RF2DFieldSolver export is not implemented yet")