diff --git a/lib/check-traces-are-contiguous/check-traces-are-contiguous.ts b/lib/check-traces-are-contiguous/check-traces-are-contiguous.ts index b75e32f..3d5fcc5 100644 --- a/lib/check-traces-are-contiguous/check-traces-are-contiguous.ts +++ b/lib/check-traces-are-contiguous/check-traces-are-contiguous.ts @@ -52,7 +52,7 @@ function routePointConnectsToAnotherExpectedPort( const expectedPads = padMap.get(expectedPort.pcb_port_id) return ( expectedPads?.some((pad) => - isPointInPad({ x: point.x, y: point.y }, pad), + isPointInPad({ x: point.x, y: point.y }, pad, point.layer), ) ?? false ) }) @@ -273,13 +273,21 @@ function checkTracesAreContiguous( const isFirstPointConnected = firstPoint.route_type === "wire" && pads.some((pad) => - isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad), + isPointInPad( + { x: firstPoint.x, y: firstPoint.y }, + pad, + firstPoint.layer, + ), ) const isLastPointConnected = lastPoint.route_type === "wire" && pads.some((pad) => - isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad), + isPointInPad( + { x: lastPoint.x, y: lastPoint.y }, + pad, + lastPoint.layer, + ), ) if (!isFirstPointConnected && !isLastPointConnected) { @@ -326,7 +334,11 @@ function checkTracesAreContiguous( if ( firstPoint.route_type === "wire" && pads.some((pad) => - isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad), + isPointInPad( + { x: firstPoint.x, y: firstPoint.y }, + pad, + firstPoint.layer, + ), ) ) { firstConnectsToAnyPad = true @@ -334,7 +346,11 @@ function checkTracesAreContiguous( if ( lastPoint.route_type === "wire" && pads.some((pad) => - isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad), + isPointInPad( + { x: lastPoint.x, y: lastPoint.y }, + pad, + lastPoint.layer, + ), ) ) { lastConnectsToAnyPad = true diff --git a/lib/check-traces-are-contiguous/is-point-in-pad.ts b/lib/check-traces-are-contiguous/is-point-in-pad.ts index e786e83..4ec84c3 100644 --- a/lib/check-traces-are-contiguous/is-point-in-pad.ts +++ b/lib/check-traces-are-contiguous/is-point-in-pad.ts @@ -34,7 +34,18 @@ function isPointOnSegment( export function isPointInPad( point: { x: number; y: number }, pad: PcbSmtPad | PcbPlatedHole, + layer?: string, ): boolean { + // A trace endpoint only connects to a pad on the same layer (a top-layer + // wire can't land on a bottom-layer smt pad without a via). + if (layer !== undefined) { + const padIsOnLayer = + pad.type === "pcb_smtpad" + ? pad.layer === layer + : pad.layers.some((padLayer) => padLayer === layer) + if (!padIsOnLayer) return false + } + if (pad.type === "pcb_smtpad") { if (pad.shape === "circle") { return getDistanceBetweenPoints(point, pad) <= pad.radius diff --git a/tests/lib/__snapshots__/trace-endpoint-on-different-layer.snap.svg b/tests/lib/__snapshots__/trace-endpoint-on-different-layer.snap.svg index 41cbce8..baf4829 100644 --- a/tests/lib/__snapshots__/trace-endpoint-on-different-layer.snap.svg +++ b/tests/lib/__snapshots__/trace-endpoint-on-different-layer.snap.svg @@ -1 +1 @@ - \ No newline at end of file +Trace [trace[t1]] is missing a connection to smtpad[#pp2] \ No newline at end of file diff --git a/tests/lib/check-traces-are-contiguous.test.ts b/tests/lib/check-traces-are-contiguous.test.ts index 6af8b7e..4901ac3 100644 --- a/tests/lib/check-traces-are-contiguous.test.ts +++ b/tests/lib/check-traces-are-contiguous.test.ts @@ -410,148 +410,142 @@ test("still reports a source-trace branch that does not reach a required port", ) }) -test.failing( - "errors when a trace endpoint touches a pad on a different layer", - () => { - // A top-layer wire endpoint at (1,0) coincides with a BOTTOM-layer pad. - // They cannot connect without a via, so this should be flagged. - const circuitJson = [ - { - type: "source_trace", - source_trace_id: "trace_1", - connected_source_port_ids: ["port_1", "port_2"], - }, - { - type: "pcb_port", - pcb_port_id: "pp1", - source_port_id: "port_1", - x: 0, - y: 0, - layers: ["top"], - }, - { - type: "pcb_port", - pcb_port_id: "pp2", - source_port_id: "port_2", - x: 1, - y: 0, - layers: ["top"], - }, - { - type: "pcb_smtpad", - pcb_smtpad_id: "pad_1", - pcb_port_id: "pp1", - shape: "rect", - x: 0, - y: 0, - width: 0.5, - height: 0.5, - layer: "top", - }, - { - type: "pcb_smtpad", - pcb_smtpad_id: "pad_2", - pcb_port_id: "pp2", - shape: "rect", - x: 1, - y: 0, - width: 0.5, - height: 0.5, - layer: "bottom", - }, - { - type: "pcb_trace", - pcb_trace_id: "t1", - source_trace_id: "trace_1", - route: [ - { route_type: "wire", x: 0, y: 0, layer: "top", width: 0.2 }, - { route_type: "wire", x: 1, y: 0, layer: "top", width: 0.2 }, - ], - }, - ] as AnyCircuitElement[] +test("errors when a trace endpoint touches a pad on a different layer", () => { + // A top-layer wire endpoint at (1,0) coincides with a BOTTOM-layer pad. + // They cannot connect without a via, so this should be flagged. + const circuitJson = [ + { + type: "source_trace", + source_trace_id: "trace_1", + connected_source_port_ids: ["port_1", "port_2"], + }, + { + type: "pcb_port", + pcb_port_id: "pp1", + source_port_id: "port_1", + x: 0, + y: 0, + layers: ["top"], + }, + { + type: "pcb_port", + pcb_port_id: "pp2", + source_port_id: "port_2", + x: 1, + y: 0, + layers: ["top"], + }, + { + type: "pcb_smtpad", + pcb_smtpad_id: "pad_1", + pcb_port_id: "pp1", + shape: "rect", + x: 0, + y: 0, + width: 0.5, + height: 0.5, + layer: "top", + }, + { + type: "pcb_smtpad", + pcb_smtpad_id: "pad_2", + pcb_port_id: "pp2", + shape: "rect", + x: 1, + y: 0, + width: 0.5, + height: 0.5, + layer: "bottom", + }, + { + type: "pcb_trace", + pcb_trace_id: "t1", + source_trace_id: "trace_1", + route: [ + { route_type: "wire", x: 0, y: 0, layer: "top", width: 0.2 }, + { route_type: "wire", x: 1, y: 0, layer: "top", width: 0.2 }, + ], + }, + ] as AnyCircuitElement[] - const errors = checkTracesAreContiguous(circuitJson) - expect(errors.length).toBeGreaterThan(0) - }, -) + const errors = checkTracesAreContiguous(circuitJson) + expect(errors.length).toBeGreaterThan(0) +}) -test.failing( - "draws an error where a trace endpoint touches a pad on a different layer", - () => { - // Same scenario rendered with shouldDrawErrors: a top-layer wire ends on - // the BOTTOM-layer pad on the right. On unfixed code the check misses it, - // so no error is drawn; the fix makes the error marker appear. - const circuitJson = [ - { - type: "pcb_board", - pcb_board_id: "board_1", - center: { x: 0.5, y: 0 }, - width: 3, - height: 2, - thickness: 1.6, - num_layers: 2, - material: "fr4", - }, - { - type: "source_trace", - source_trace_id: "trace_1", - connected_source_port_ids: ["port_1", "port_2"], - }, - { - type: "pcb_port", - pcb_port_id: "pp1", - source_port_id: "port_1", - x: 0, - y: 0, - layers: ["top"], - }, - { - type: "pcb_port", - pcb_port_id: "pp2", - source_port_id: "port_2", - x: 1, - y: 0, - layers: ["top"], - }, - { - type: "pcb_smtpad", - pcb_smtpad_id: "pad_1", - pcb_port_id: "pp1", - shape: "rect", - x: 0, - y: 0, - width: 0.5, - height: 0.5, - layer: "top", - }, - { - type: "pcb_smtpad", - pcb_smtpad_id: "pad_2", - pcb_port_id: "pp2", - shape: "rect", - x: 1, - y: 0, - width: 0.5, - height: 0.5, - layer: "bottom", - }, - { - type: "pcb_trace", - pcb_trace_id: "t1", - source_trace_id: "trace_1", - route: [ - { route_type: "wire", x: 0, y: 0, layer: "top", width: 0.2 }, - { route_type: "wire", x: 1, y: 0, layer: "top", width: 0.2 }, - ], - }, - ] as AnyCircuitElement[] +test("draws an error where a trace endpoint touches a pad on a different layer", () => { + // Same scenario rendered with shouldDrawErrors: a top-layer wire ends on + // the BOTTOM-layer pad on the right. On unfixed code the check misses it, + // so no error is drawn; the fix makes the error marker appear. + const circuitJson = [ + { + type: "pcb_board", + pcb_board_id: "board_1", + center: { x: 0.5, y: 0 }, + width: 3, + height: 2, + thickness: 1.6, + num_layers: 2, + material: "fr4", + }, + { + type: "source_trace", + source_trace_id: "trace_1", + connected_source_port_ids: ["port_1", "port_2"], + }, + { + type: "pcb_port", + pcb_port_id: "pp1", + source_port_id: "port_1", + x: 0, + y: 0, + layers: ["top"], + }, + { + type: "pcb_port", + pcb_port_id: "pp2", + source_port_id: "port_2", + x: 1, + y: 0, + layers: ["top"], + }, + { + type: "pcb_smtpad", + pcb_smtpad_id: "pad_1", + pcb_port_id: "pp1", + shape: "rect", + x: 0, + y: 0, + width: 0.5, + height: 0.5, + layer: "top", + }, + { + type: "pcb_smtpad", + pcb_smtpad_id: "pad_2", + pcb_port_id: "pp2", + shape: "rect", + x: 1, + y: 0, + width: 0.5, + height: 0.5, + layer: "bottom", + }, + { + type: "pcb_trace", + pcb_trace_id: "t1", + source_trace_id: "trace_1", + route: [ + { route_type: "wire", x: 0, y: 0, layer: "top", width: 0.2 }, + { route_type: "wire", x: 1, y: 0, layer: "top", width: 0.2 }, + ], + }, + ] as AnyCircuitElement[] - const errors = checkTracesAreContiguous(circuitJson) - expect( - convertCircuitJsonToPcbSvg([...circuitJson, ...errors], { - shouldDrawErrors: true, - }), - ).toMatchSvgSnapshot(import.meta.path, "trace-endpoint-on-different-layer") - expect(errors.length).toBeGreaterThan(0) - }, -) + const errors = checkTracesAreContiguous(circuitJson) + expect( + convertCircuitJsonToPcbSvg([...circuitJson, ...errors], { + shouldDrawErrors: true, + }), + ).toMatchSvgSnapshot(import.meta.path, "trace-endpoint-on-different-layer") + expect(errors.length).toBeGreaterThan(0) +})