Write SES coordinates in resolution units, as FreeRouting does
SES lengths and coordinates were written in the DSN's unit while the session declared (resolution um 10), so readers that follow FreeRouting's SesWriter (KiCad's importer, mckicad's ses_apply) placed every track at a tenth of its size. The JAR, run on the same DSN, writes F1 at 65000 where freeroute wrote 6500. Scale every length by the resolution factor: placements, via padstack shapes, wire widths and points, and vias. Rotations stay as they are. The router tests compared SES output against DSN pad positions and so had the same mistake built in; they now derive the expected values through ses_scale.
This commit is contained in:
+59
-31
@@ -44,6 +44,30 @@ def _iround(value: float) -> int:
|
||||
return math.floor(value + 0.5)
|
||||
|
||||
|
||||
# Millimetres per Specctra unit name.
|
||||
_UNIT_MM = {"inch": 25.4, "mil": 0.0254, "mm": 1.0, "cm": 10.0, "um": 0.001}
|
||||
|
||||
|
||||
def ses_scale(board: DsnBoard) -> float:
|
||||
"""Factor from DSN coordinates to SES coordinates.
|
||||
|
||||
DSN coordinates are in the DSN's ``(unit ...)``; SES coordinates are in
|
||||
resolution units, 1/value of the resolution unit, as FreeRouting's
|
||||
SesWriter writes them. For kicad-cli's ``(resolution um 10) (unit um)``
|
||||
that is x10: a pad at 6500 um in the DSN is 65000 in the SES.
|
||||
"""
|
||||
res = board.resolution
|
||||
try:
|
||||
return res.value * _UNIT_MM[board.unit] / _UNIT_MM[res.unit]
|
||||
except KeyError as exc:
|
||||
raise ValueError(f"unknown Specctra unit {exc.args[0]!r}") from None
|
||||
|
||||
|
||||
def _len(value: float, k: float) -> int:
|
||||
"""A DSN length or coordinate as an SES integer."""
|
||||
return _iround(value * k)
|
||||
|
||||
|
||||
def write_ses(
|
||||
board: DsnBoard,
|
||||
result: RoutingResult | None = None,
|
||||
@@ -100,9 +124,10 @@ def _write_session_scope(
|
||||
out.write("(base_design ")
|
||||
ident.write(design_name, out)
|
||||
out.write(")")
|
||||
_write_placement(out, ident, board)
|
||||
k = ses_scale(board)
|
||||
_write_placement(out, ident, board, k)
|
||||
_write_was_is(out)
|
||||
_write_routes(out, ident, board, result)
|
||||
_write_routes(out, ident, board, result, k)
|
||||
out.end_scope()
|
||||
|
||||
|
||||
@@ -111,7 +136,7 @@ def _write_resolution(out: IndentWriter, board: DsnBoard) -> None:
|
||||
out.write(f"(resolution {board.resolution.unit} {board.resolution.value})")
|
||||
|
||||
|
||||
def _write_placement(out: IndentWriter, ident: Identifier, board: DsnBoard) -> None:
|
||||
def _write_placement(out: IndentWriter, ident: Identifier, board: DsnBoard, k: float) -> None:
|
||||
out.start_scope()
|
||||
out.write("placement")
|
||||
_write_resolution(out, board)
|
||||
@@ -126,7 +151,7 @@ def _write_placement(out: IndentWriter, ident: Identifier, board: DsnBoard) -> N
|
||||
out.new_line()
|
||||
out.write("(place ")
|
||||
ident.write(place.name, out)
|
||||
out.write(f" {_iround(place.x)} {_iround(place.y)}")
|
||||
out.write(f" {_len(place.x, k)} {_len(place.y, k)}")
|
||||
out.write(" front " if place.is_front else " back ")
|
||||
out.write(str(_iround(place.rotation)))
|
||||
if place.position_fixed:
|
||||
@@ -145,14 +170,14 @@ def _write_was_is(out: IndentWriter) -> None:
|
||||
|
||||
|
||||
def _write_routes(
|
||||
out: IndentWriter, ident: Identifier, board: DsnBoard, result: RoutingResult
|
||||
out: IndentWriter, ident: Identifier, board: DsnBoard, result: RoutingResult, k: float
|
||||
) -> None:
|
||||
out.start_scope()
|
||||
out.write("routes ")
|
||||
_write_resolution(out, board)
|
||||
_write_parser(out, ident, board)
|
||||
_write_library_out(out, ident, board)
|
||||
_write_network_out(out, ident, result)
|
||||
_write_library_out(out, ident, board, k)
|
||||
_write_network_out(out, ident, result, k)
|
||||
out.end_scope()
|
||||
|
||||
|
||||
@@ -174,24 +199,24 @@ def _write_parser(out: IndentWriter, ident: Identifier, board: DsnBoard) -> None
|
||||
out.end_scope()
|
||||
|
||||
|
||||
def _write_library_out(out: IndentWriter, ident: Identifier, board: DsnBoard) -> None:
|
||||
def _write_library_out(out: IndentWriter, ident: Identifier, board: DsnBoard, k: float) -> None:
|
||||
out.start_scope()
|
||||
out.write("library_out ")
|
||||
for name in board.via_padstack_names:
|
||||
padstack = board.padstack(name)
|
||||
if padstack is not None:
|
||||
_write_padstack(out, ident, padstack)
|
||||
_write_padstack(out, ident, padstack, k)
|
||||
out.end_scope()
|
||||
|
||||
|
||||
def _write_padstack(out: IndentWriter, ident: Identifier, padstack) -> None:
|
||||
def _write_padstack(out: IndentWriter, ident: Identifier, padstack, k: float) -> None:
|
||||
out.start_scope()
|
||||
out.write("padstack ")
|
||||
ident.write(padstack.name, out)
|
||||
for shape in padstack.shapes:
|
||||
out.start_scope()
|
||||
out.write("shape")
|
||||
_write_shape_int(out, ident, shape)
|
||||
_write_shape_int(out, ident, shape, k)
|
||||
out.end_scope()
|
||||
if not padstack.attach_allowed:
|
||||
out.new_line()
|
||||
@@ -199,61 +224,63 @@ def _write_padstack(out: IndentWriter, ident: Identifier, padstack) -> None:
|
||||
out.end_scope()
|
||||
|
||||
|
||||
def _write_shape_int(out: IndentWriter, ident: Identifier, shape: Shape) -> None:
|
||||
def _write_shape_int(out: IndentWriter, ident: Identifier, shape: Shape, k: float) -> None:
|
||||
"""Write a padstack shape with integer coordinates (mirrors write_scope_int)."""
|
||||
out.start_scope()
|
||||
if isinstance(shape, Rectangle):
|
||||
out.write("rect ")
|
||||
ident.write(shape.layer, out)
|
||||
for c in shape.coords[:4]:
|
||||
out.write(f" {_iround(c)}")
|
||||
out.write(f" {_len(c, k)}")
|
||||
elif isinstance(shape, Circle):
|
||||
out.write("circle ")
|
||||
ident.write(shape.layer, out)
|
||||
out.write(f" {_iround(shape.diameter)}")
|
||||
out.write(f" {_len(shape.diameter, k)}")
|
||||
if shape.center_x or shape.center_y:
|
||||
out.write(f" {_iround(shape.center_x)} {_iround(shape.center_y)}")
|
||||
out.write(f" {_len(shape.center_x, k)} {_len(shape.center_y, k)}")
|
||||
elif isinstance(shape, Polygon):
|
||||
out.write("polygon ")
|
||||
ident.write(shape.layer, out)
|
||||
out.write(f" {_iround(shape.aperture_width)}")
|
||||
out.write(f" {_len(shape.aperture_width, k)}")
|
||||
for c in shape.coords:
|
||||
out.write(f" {_iround(c)}")
|
||||
out.write(f" {_len(c, k)}")
|
||||
elif isinstance(shape, Path):
|
||||
out.write("path ")
|
||||
ident.write(shape.layer, out)
|
||||
out.write(f" {_iround(shape.width)}")
|
||||
out.write(f" {_len(shape.width, k)}")
|
||||
for c in shape.coords:
|
||||
out.write(f" {_iround(c)}")
|
||||
out.write(f" {_len(c, k)}")
|
||||
else: # pragma: no cover - defensive: unknown shape type
|
||||
out.write("rect ")
|
||||
ident.write(shape.layer, out)
|
||||
out.end_scope()
|
||||
|
||||
|
||||
def _write_network_out(out: IndentWriter, ident: Identifier, result: RoutingResult) -> None:
|
||||
def _write_network_out(
|
||||
out: IndentWriter, ident: Identifier, result: RoutingResult, k: float
|
||||
) -> None:
|
||||
out.start_scope()
|
||||
out.write("network_out ")
|
||||
for net in result.routed_nets():
|
||||
_write_net(out, ident, net)
|
||||
_write_net(out, ident, net, k)
|
||||
out.end_scope()
|
||||
|
||||
|
||||
def _write_net(out: IndentWriter, ident: Identifier, net: RoutedNet) -> None:
|
||||
def _write_net(out: IndentWriter, ident: Identifier, net: RoutedNet, k: float) -> None:
|
||||
out.start_scope()
|
||||
out.write("net ")
|
||||
ident.write(net.name, out)
|
||||
for wire in net.wires:
|
||||
_write_wire(out, ident, wire)
|
||||
_write_wire(out, ident, wire, k)
|
||||
for via in net.vias:
|
||||
_write_via(out, ident, via)
|
||||
_write_via(out, ident, via, k)
|
||||
out.end_scope()
|
||||
|
||||
|
||||
def _write_wire(out: IndentWriter, ident: Identifier, wire: RoutedWire) -> None:
|
||||
def _write_wire(out: IndentWriter, ident: Identifier, wire: RoutedWire, k: float) -> None:
|
||||
out.start_scope()
|
||||
out.write("wire")
|
||||
_write_path(out, ident, wire.layer, wire.width, wire.coords)
|
||||
_write_path(out, ident, wire.layer, wire.width, wire.coords, k)
|
||||
if wire.fixed:
|
||||
out.new_line()
|
||||
out.write("(type fix)")
|
||||
@@ -266,26 +293,27 @@ def _write_path(
|
||||
layer: str,
|
||||
width: float,
|
||||
coords: list[float],
|
||||
k: float,
|
||||
) -> None:
|
||||
out.start_scope()
|
||||
out.write("path ")
|
||||
ident.write(layer, out)
|
||||
out.write(" ")
|
||||
out.write(str(_iround(width)))
|
||||
out.write(str(_len(width, k)))
|
||||
corner_count = len(coords) // 2
|
||||
for i in range(corner_count):
|
||||
out.new_line()
|
||||
out.write(str(_iround(coords[2 * i])))
|
||||
out.write(str(_len(coords[2 * i], k)))
|
||||
out.write(" ")
|
||||
out.write(str(_iround(coords[2 * i + 1])))
|
||||
out.write(str(_len(coords[2 * i + 1], k)))
|
||||
out.end_scope()
|
||||
|
||||
|
||||
def _write_via(out: IndentWriter, ident: Identifier, via: RoutedVia) -> None:
|
||||
def _write_via(out: IndentWriter, ident: Identifier, via: RoutedVia, k: float) -> None:
|
||||
out.start_scope()
|
||||
out.write("via ")
|
||||
ident.write(via.padstack, out)
|
||||
out.write(f" {_iround(via.x)} {_iround(via.y)}")
|
||||
out.write(f" {_len(via.x, k)} {_len(via.y, k)}")
|
||||
if via.fixed:
|
||||
out.new_line()
|
||||
out.write("(type fix)")
|
||||
|
||||
Reference in New Issue
Block a user