-
Notifications
You must be signed in to change notification settings - Fork 23
Check PCB trace maximum lengths during routing DRC #174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| import type { | ||
| AnyCircuitElement, | ||
| PcbTrace, | ||
| PcbTraceRoutePoint, | ||
| PcbTraceTooLongWarning, | ||
| SourceTrace, | ||
| } from "circuit-json" | ||
|
|
||
| const DEFAULT_VIA_LENGTH_MM = 1.6 | ||
|
|
||
| const getRoutePointPosition = (routePoint: PcbTraceRoutePoint) => | ||
| routePoint.route_type === "through_pad" | ||
| ? routePoint.start | ||
| : { x: routePoint.x, y: routePoint.y } | ||
|
|
||
| const getPcbTraceLength = (pcbTrace: PcbTrace): number => { | ||
| if (pcbTrace.trace_length !== undefined) return pcbTrace.trace_length | ||
|
|
||
| let traceLength = 0 | ||
|
|
||
| for ( | ||
| let routePointIndex = 0; | ||
| routePointIndex < pcbTrace.route.length; | ||
| routePointIndex++ | ||
| ) { | ||
| const routePoint = pcbTrace.route[routePointIndex] | ||
| if (!routePoint) continue | ||
|
|
||
| if (routePoint.route_type === "via") { | ||
| traceLength += DEFAULT_VIA_LENGTH_MM | ||
| continue | ||
| } | ||
|
|
||
| const nextRoutePoint = pcbTrace.route[routePointIndex + 1] | ||
| if (!nextRoutePoint) continue | ||
|
|
||
| const routePointPosition = getRoutePointPosition(routePoint) | ||
| const nextRoutePointPosition = getRoutePointPosition(nextRoutePoint) | ||
| traceLength += Math.hypot( | ||
| nextRoutePointPosition.x - routePointPosition.x, | ||
| nextRoutePointPosition.y - routePointPosition.y, | ||
| ) | ||
| } | ||
|
|
||
| return traceLength | ||
| } | ||
|
|
||
| export const checkPcbTraceLengths = ( | ||
| circuitJson: AnyCircuitElement[], | ||
| ): PcbTraceTooLongWarning[] => { | ||
| const sourceTraces = circuitJson.filter( | ||
| (element): element is SourceTrace => element.type === "source_trace", | ||
| ) | ||
| const pcbTraces = circuitJson.filter( | ||
| (element): element is PcbTrace => element.type === "pcb_trace", | ||
| ) | ||
|
|
||
| const sourceTracesById = new Map( | ||
| sourceTraces.map((sourceTrace) => [ | ||
| sourceTrace.source_trace_id, | ||
| sourceTrace, | ||
| ]), | ||
| ) | ||
| const warnings: PcbTraceTooLongWarning[] = [] | ||
|
|
||
| for (const pcbTrace of pcbTraces) { | ||
| if (!pcbTrace.source_trace_id) continue | ||
|
|
||
| const sourceTrace = sourceTracesById.get(pcbTrace.source_trace_id) | ||
| if (!sourceTrace) continue | ||
|
|
||
| const maximumTraceLength = sourceTrace.max_length | ||
| if (typeof maximumTraceLength !== "number") continue | ||
|
|
||
| const actualTraceLength = getPcbTraceLength(pcbTrace) | ||
| if (actualTraceLength <= maximumTraceLength) continue | ||
|
|
||
| warnings.push({ | ||
| type: "pcb_trace_too_long_warning", | ||
| pcb_trace_too_long_warning_id: `pcb_trace_too_long_warning_${pcbTrace.pcb_trace_id}`, | ||
| warning_type: "pcb_trace_too_long_warning", | ||
| message: `PCB trace is ${actualTraceLength.toFixed(2)}mm long, exceeding the ${maximumTraceLength}mm maximum`, | ||
| pcb_trace_id: pcbTrace.pcb_trace_id, | ||
| source_trace_id: sourceTrace.source_trace_id, | ||
| source_net_id: sourceTrace.connected_source_net_ids[0], | ||
| actual_trace_length: actualTraceLength, | ||
| maximum_trace_length: maximumTraceLength, | ||
| subcircuit_id: pcbTrace.subcircuit_id ?? sourceTrace.subcircuit_id, | ||
| }) | ||
| } | ||
|
|
||
| return warnings | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import { expect, test } from "bun:test" | ||
| import type { AnyCircuitElement } from "circuit-json" | ||
| import { checkPcbTraceLengths, runAllRoutingChecks } from "../.." | ||
|
|
||
| const circuitJson = [ | ||
| { | ||
| type: "source_trace", | ||
| source_trace_id: "overlength_source_trace", | ||
| connected_source_port_ids: [], | ||
| connected_source_net_ids: ["signal_net"], | ||
| max_length: 10, | ||
| subcircuit_id: "board", | ||
| }, | ||
| { | ||
| type: "source_trace", | ||
| source_trace_id: "short_source_trace", | ||
| connected_source_port_ids: [], | ||
| connected_source_net_ids: [], | ||
| max_length: 10, | ||
| }, | ||
| { | ||
| type: "source_trace", | ||
| source_trace_id: "unconstrained_source_trace", | ||
| connected_source_port_ids: [], | ||
| connected_source_net_ids: [], | ||
| max_length: null, | ||
| }, | ||
| { | ||
| type: "pcb_trace", | ||
| pcb_trace_id: "overlength_pcb_trace", | ||
| source_trace_id: "overlength_source_trace", | ||
| subcircuit_id: "board", | ||
| route: [ | ||
| { route_type: "wire", x: 0, y: 0, width: 0.15, layer: "top" }, | ||
| { route_type: "wire", x: 12, y: 0, width: 0.15, layer: "top" }, | ||
| ], | ||
| }, | ||
| { | ||
| type: "pcb_trace", | ||
| pcb_trace_id: "short_pcb_trace", | ||
| source_trace_id: "short_source_trace", | ||
| trace_length: 8, | ||
| route: [], | ||
| }, | ||
| { | ||
| type: "pcb_trace", | ||
| pcb_trace_id: "unconstrained_pcb_trace", | ||
| source_trace_id: "unconstrained_source_trace", | ||
| trace_length: 20, | ||
| route: [], | ||
| }, | ||
| ] as AnyCircuitElement[] | ||
|
|
||
| test("warns when a PCB trace exceeds its source trace maximum length", () => { | ||
| expect(checkPcbTraceLengths(circuitJson)).toEqual([ | ||
| { | ||
| type: "pcb_trace_too_long_warning", | ||
| pcb_trace_too_long_warning_id: | ||
| "pcb_trace_too_long_warning_overlength_pcb_trace", | ||
| warning_type: "pcb_trace_too_long_warning", | ||
| message: "PCB trace is 12.00mm long, exceeding the 10mm maximum", | ||
| pcb_trace_id: "overlength_pcb_trace", | ||
| source_trace_id: "overlength_source_trace", | ||
| source_net_id: "signal_net", | ||
| actual_trace_length: 12, | ||
| maximum_trace_length: 10, | ||
| subcircuit_id: "board", | ||
| }, | ||
| ]) | ||
| }) | ||
|
|
||
| test("is included in the routing check pipeline", async () => { | ||
| const results = await runAllRoutingChecks(circuitJson) | ||
|
|
||
| expect( | ||
| results.filter((result) => result.type === "pcb_trace_too_long_warning"), | ||
| ).toEqual(checkPcbTraceLengths(circuitJson)) | ||
| }) | ||
|
Comment on lines
+54
to
+78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file contains two Spotted by Graphite (based on custom rule: Custom rule) |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical bug: The
continuestatement skips calculating the distance from the via to the next route point. This causes traces with vias to report shorter lengths than actual, potentially missing length violations.Impact: Traces containing vias will have their lengths underreported by the distance between the via and the next route point, causing the check to miss traces that actually exceed their maximum length.
Fix: Remove the
continuestatement and let the code fall through to calculate the distance to the next point:Spotted by Graphite

Is this helpful? React 👍 or 👎 to let us know.