diff --git a/lib/components/normal-components/Board/Board.ts b/lib/components/normal-components/Board/Board.ts index 053dd6f80e..cdd94b6abe 100644 --- a/lib/components/normal-components/Board/Board.ts +++ b/lib/components/normal-components/Board/Board.ts @@ -252,6 +252,35 @@ export class Board } } + /** + * Via hole/pad diameters (mm) required by this board's design rules: + * minViaHoleDiameter/minViaPadDiameter, then the board's pcbStyle via size, + * then JLCPCB's minimums. These are recorded on pcb_board and are the + * default size for vias placed without an explicit or styled size. + */ + _getResolvedMinViaDiameters(): { holeDiameter: number; padDiameter: number } { + const subcircuitProps = this.getSubcircuit()._parsedProps + const pcbStyle = this.getInheritedMergedProperty("pcbStyle") + const styledViaDimensions = getViaDiameterDefaults(pcbStyle) + const configuredViaHoleDiameter = + subcircuitProps.minViaHoleDiameter ?? + (pcbStyle?.viaHoleDiameter !== undefined + ? styledViaDimensions.holeDiameter + : undefined) + return { + holeDiameter: + configuredViaHoleDiameter ?? jlcMinTolerances.min_via_hole_diameter!, + padDiameter: + subcircuitProps.minViaPadDiameter ?? + (pcbStyle?.viaPadDiameter !== undefined + ? styledViaDimensions.padDiameter + : configuredViaHoleDiameter !== undefined + ? configuredViaHoleDiameter + + DEFAULT_VIA_PAD_DIAMETER_OVER_HOLE_DIAMETER_MM + : jlcMinTolerances.min_via_pad_diameter!), + } + } + doInitialPcbBoardAutoSize(): void { if (this.root?.pcbDisabled) return if (!this.pcb_board_id) return @@ -584,23 +613,10 @@ export class Board } } const subcircuitProps = this.getSubcircuit()._parsedProps - const pcbStyle = this.getInheritedMergedProperty("pcbStyle") - const styledViaDimensions = getViaDiameterDefaults(pcbStyle) - const configuredViaHoleDiameter = - subcircuitProps.minViaHoleDiameter ?? - (pcbStyle?.viaHoleDiameter !== undefined - ? styledViaDimensions.holeDiameter - : undefined) - const resolvedMinViaHoleDiameter = - configuredViaHoleDiameter ?? jlcMinTolerances.min_via_hole_diameter - const resolvedMinViaPadDiameter = - subcircuitProps.minViaPadDiameter ?? - (pcbStyle?.viaPadDiameter !== undefined - ? styledViaDimensions.padDiameter - : configuredViaHoleDiameter !== undefined - ? configuredViaHoleDiameter + - DEFAULT_VIA_PAD_DIAMETER_OVER_HOLE_DIAMETER_MM - : jlcMinTolerances.min_via_pad_diameter) + const { + holeDiameter: resolvedMinViaHoleDiameter, + padDiameter: resolvedMinViaPadDiameter, + } = this._getResolvedMinViaDiameters() const pcb_board = db.pcb_board.insert({ source_board_id: this.source_board_id, subcircuit_id: this.subcircuit_id ?? undefined, diff --git a/lib/components/normal-components/Board/BoardI.ts b/lib/components/normal-components/Board/BoardI.ts index 14212cecfa..23fe3d4c3f 100644 --- a/lib/components/normal-components/Board/BoardI.ts +++ b/lib/components/normal-components/Board/BoardI.ts @@ -9,5 +9,6 @@ export interface BoardI { _generatedStitchingViaIds?: Set allLayers: ReadonlyArray _getBoardCalcVariables(): Record + _getResolvedMinViaDiameters?(): { holeDiameter: number; padDiameter: number } pcb_board_id?: string | null } diff --git a/lib/components/primitive-components/PcbTrace.ts b/lib/components/primitive-components/PcbTrace.ts index b2194a2931..a7413df0e6 100644 --- a/lib/components/primitive-components/PcbTrace.ts +++ b/lib/components/primitive-components/PcbTrace.ts @@ -2,7 +2,7 @@ import { type PcbTraceRoutePoint, pcb_trace_route_point } from "circuit-json" import { applyToPoint } from "transformation-matrix" import { z } from "zod" import { getViaSpanLayers } from "lib/utils/getViaSpanLayers" -import { getViaDiameterDefaults } from "lib/utils/pcbStyle/getViaDiameterDefaults" +import { getViaDiameterDefaultsForComponent } from "lib/utils/pcbStyle/getViaDiameterDefaults" import { PrimitiveComponent } from "../base-components/PrimitiveComponent" export const pcbTraceProps = z.object({ @@ -87,9 +87,8 @@ export class PcbTrace extends PrimitiveComponent { }) this.pcb_trace_id = pcb_trace.pcb_trace_id - const { holeDiameter, padDiameter } = getViaDiameterDefaults( - this.getInheritedMergedProperty("pcbStyle"), - ) + const { holeDiameter, padDiameter } = + getViaDiameterDefaultsForComponent(this) for (const [routeIndex, point] of transformedRoute.entries()) { if (point.route_type !== "via") continue if (this._materializedRouteViaIndices.has(routeIndex)) continue diff --git a/lib/components/primitive-components/Trace/Trace_doInitialPcbManualTraceRender.ts b/lib/components/primitive-components/Trace/Trace_doInitialPcbManualTraceRender.ts index 533adac500..1025353ae5 100644 --- a/lib/components/primitive-components/Trace/Trace_doInitialPcbManualTraceRender.ts +++ b/lib/components/primitive-components/Trace/Trace_doInitialPcbManualTraceRender.ts @@ -10,7 +10,7 @@ import type { Port } from "../Port" import type { Trace } from "./Trace" import { applyToPoint, identity } from "transformation-matrix" import { clipTraceEndAtPad } from "../../../utils/trace-clipping/clipTraceEndAtPad" -import { getViaDiameterDefaults } from "../../../utils/pcbStyle/getViaDiameterDefaults" +import { getViaDiameterDefaultsForComponent } from "../../../utils/pcbStyle/getViaDiameterDefaults" import type { ManualPcbPathPoint } from "lib/utils/pcbTraceRouteToPcbPath" import { TraceConnectionError } from "lib/errors" import { getPcbSelectorErrorForTracePort } from "./getPcbSelectorErrorForTracePort" @@ -188,8 +188,8 @@ export function Trace_doInitialPcbManualTraceRender(trace: Trace) { subcircuit_id: subcircuit?.subcircuit_id ?? undefined, pcb_group_id: trace.getGroup()?.pcb_group_id ?? undefined, }) - const pcbStyle = trace.getInheritedMergedProperty("pcbStyle") - const { holeDiameter, padDiameter } = getViaDiameterDefaults(pcbStyle) + const { holeDiameter, padDiameter } = + getViaDiameterDefaultsForComponent(trace) for (let index = 0; index < transformedRoute.length; index++) { const point = transformedRoute[index] if (point.route_type === "via") { @@ -485,8 +485,8 @@ export function Trace_doInitialPcbManualTraceRender(trace: Trace) { const subcircuitConnectivityMapKey = trace.subcircuit_connectivity_map_key ?? db.source_trace.get(trace.source_trace_id!)?.subcircuit_connectivity_map_key - const pcbStyle = trace.getInheritedMergedProperty("pcbStyle") - const { holeDiameter, padDiameter } = getViaDiameterDefaults(pcbStyle) + const { holeDiameter, padDiameter } = + getViaDiameterDefaultsForComponent(trace) for (const point of route) { if (point.route_type === "via") { const fromLayer = point.from_layer as LayerRef diff --git a/lib/components/primitive-components/Trace/Trace_doInitialPcbTraceRender.ts b/lib/components/primitive-components/Trace/Trace_doInitialPcbTraceRender.ts index 889bc99775..c9e1d06f0d 100644 --- a/lib/components/primitive-components/Trace/Trace_doInitialPcbTraceRender.ts +++ b/lib/components/primitive-components/Trace/Trace_doInitialPcbTraceRender.ts @@ -20,7 +20,7 @@ import { import { getObstaclesFromCircuitJson } from "lib/utils/obstacles/getObstaclesFromCircuitJson" import { pairs } from "lib/utils/pairs" import { getRoutePointPosition } from "lib/utils/pcb-trace-route-point-utils" -import { getViaDiameterDefaults } from "lib/utils/pcbStyle/getViaDiameterDefaults" +import { getViaDiameterDefaultsForComponent } from "lib/utils/pcbStyle/getViaDiameterDefaults" import { reversePcbTraceRoute } from "lib/utils/reverse-pcb-trace-route" import { tryNow } from "lib/utils/try-now" import type { Port } from "../Port" @@ -510,8 +510,8 @@ export function Trace_doInitialPcbTraceRender(trace: Trace) { const mergedRoute = mergeRoutes(routes) const traceLength = getTraceLength(mergedRoute) - const pcbStyle = trace.getInheritedMergedProperty("pcbStyle") - const { holeDiameter, padDiameter } = getViaDiameterDefaults(pcbStyle) + const { holeDiameter, padDiameter } = + getViaDiameterDefaultsForComponent(trace) const pcb_trace = db.pcb_trace.insert({ route: getCircuitJsonPcbTraceRoute( mergedRoute as PcbTraceRoutePointWithSrjMetadata[], diff --git a/lib/components/primitive-components/Trace/render-pcb-paths.ts b/lib/components/primitive-components/Trace/render-pcb-paths.ts index 8c370ff98f..bca7f0b7ad 100644 --- a/lib/components/primitive-components/Trace/render-pcb-paths.ts +++ b/lib/components/primitive-components/Trace/render-pcb-paths.ts @@ -1,7 +1,7 @@ import type { PcbPath } from "@tscircuit/props" import type { LayerRef } from "circuit-json" import { getViaSpanLayers } from "lib/utils/getViaSpanLayers" -import { getViaDiameterDefaults } from "../../../utils/pcbStyle/getViaDiameterDefaults" +import { getViaDiameterDefaultsForComponent } from "../../../utils/pcbStyle/getViaDiameterDefaults" import type { Port } from "../Port" import { getRouteForPcbPath } from "./get-route-for-pcb-path" import { insertPcbPathViaContactErrors } from "./insert-pcb-path-via-contact-errors" @@ -47,8 +47,8 @@ export const renderPcbPaths = ({ return } - const pcbStyle = trace.getInheritedMergedProperty("pcbStyle") - const { holeDiameter, padDiameter } = getViaDiameterDefaults(pcbStyle) + const { holeDiameter, padDiameter } = + getViaDiameterDefaultsForComponent(trace) const connectivityMapKey = trace.subcircuit_connectivity_map_key ?? db.source_trace.get(trace.source_trace_id!)?.subcircuit_connectivity_map_key diff --git a/lib/components/primitive-components/Via.ts b/lib/components/primitive-components/Via.ts index 1b0c115fc6..1fe44033b2 100644 --- a/lib/components/primitive-components/Via.ts +++ b/lib/components/primitive-components/Via.ts @@ -1,8 +1,8 @@ -import { type PcbStyle, viaProps } from "@tscircuit/props" +import { viaProps } from "@tscircuit/props" import type { LayerRef, PcbVia } from "circuit-json" import { createNetsFromProps } from "lib/utils/components/createNetsFromProps" import { getViaSpanLayers } from "lib/utils/getViaSpanLayers" -import { getViaDiameterDefaultsWithOverrides } from "lib/utils/pcbStyle/getViaDiameterDefaults" +import { getViaDiameterDefaultsForComponent } from "lib/utils/pcbStyle/getViaDiameterDefaults" import { getViaTenting } from "lib/utils/getViaTenting" import { z } from "zod" import { PrimitiveComponent } from "../base-components/PrimitiveComponent" @@ -36,21 +36,15 @@ export class Via extends PrimitiveComponent { return this._getLayers() } - private _getResolvedViaDiameters(pcbStyle?: PcbStyle) { - return getViaDiameterDefaultsWithOverrides( - { - holeDiameter: this._parsedProps.holeDiameter, - padDiameter: this._parsedProps.outerDiameter, - }, - pcbStyle, - ) + private _getResolvedViaDiameters() { + return getViaDiameterDefaultsForComponent(this, { + holeDiameter: this._parsedProps.holeDiameter, + padDiameter: this._parsedProps.outerDiameter, + }) } getPcbSize(): { width: number; height: number } { - const pcbStyle = this.getInheritedMergedProperty("pcbStyle") as - | PcbStyle - | undefined - const { padDiameter } = this._getResolvedViaDiameters(pcbStyle) + const { padDiameter } = this._getResolvedViaDiameters() return { width: padDiameter, height: padDiameter } } @@ -158,10 +152,7 @@ export class Via extends PrimitiveComponent { doInitialPcbComponentRender(): void { if (this.root?.pcbDisabled) return const { db } = this.root! - const pcbStyle = this.getInheritedMergedProperty("pcbStyle") as - | PcbStyle - | undefined - const { padDiameter } = this._getResolvedViaDiameters(pcbStyle) + const { padDiameter } = this._getResolvedViaDiameters() const position = this._getGlobalPcbPositionBeforeLayout() const subcircuit = this.getSubcircuit() const pcb_component = db.pcb_component.insert({ @@ -264,11 +255,7 @@ export class Via extends PrimitiveComponent { doInitialPcbPrimitiveRender(): void { if (this.root?.pcbDisabled) return const { db } = this.root! - const pcbStyle = this.getInheritedMergedProperty("pcbStyle") as - | PcbStyle - | undefined - const { holeDiameter, padDiameter } = - this._getResolvedViaDiameters(pcbStyle) + const { holeDiameter, padDiameter } = this._getResolvedViaDiameters() const position = this._getGlobalPcbPositionBeforeLayout() const subcircuit = this.getSubcircuit() const connectedNetOrTrace = this._getConnectedNetOrTrace() diff --git a/lib/utils/pcbStyle/getViaDiameterDefaults.ts b/lib/utils/pcbStyle/getViaDiameterDefaults.ts index 350fa2eaae..f815ddff2d 100644 --- a/lib/utils/pcbStyle/getViaDiameterDefaults.ts +++ b/lib/utils/pcbStyle/getViaDiameterDefaults.ts @@ -1,4 +1,5 @@ import type { PcbStyle } from "@tscircuit/props" +import type { PrimitiveComponent } from "lib/components/base-components/PrimitiveComponent/PrimitiveComponent" const DEFAULT_VIA_HOLE_DIAMETER = 0.2 const DEFAULT_VIA_PAD_DIAMETER = 0.3 @@ -13,15 +14,26 @@ const parseDistance = ( return Number.isFinite(parsed) ? parsed : fallback } -export const getViaDiameterDefaults = (pcbStyle?: PcbStyle) => { +/** Via drill (hole) and copper pad diameters, in mm */ +type ViaDiameters = { holeDiameter: number; padDiameter: number } + +/** + * Default via size when a via doesn't specify one. A pcbStyle via size wins, + * then the board's resolved via minimums (so hand-placed vias match the vias + * the autorouter creates), then the global defaults. + */ +export const getViaDiameterDefaults = ( + pcbStyle?: PcbStyle, + boardMinViaDiameters?: ViaDiameters, +): ViaDiameters => { return { holeDiameter: parseDistance( pcbStyle?.viaHoleDiameter, - DEFAULT_VIA_HOLE_DIAMETER, + boardMinViaDiameters?.holeDiameter ?? DEFAULT_VIA_HOLE_DIAMETER, ), padDiameter: parseDistance( pcbStyle?.viaPadDiameter, - DEFAULT_VIA_PAD_DIAMETER, + boardMinViaDiameters?.padDiameter ?? DEFAULT_VIA_PAD_DIAMETER, ), } } @@ -29,10 +41,29 @@ export const getViaDiameterDefaults = (pcbStyle?: PcbStyle) => { export const getViaDiameterDefaultsWithOverrides = ( overrides: { holeDiameter?: number; padDiameter?: number }, pcbStyle?: PcbStyle, + boardMinViaDiameters?: ViaDiameters, ) => { - const defaults = getViaDiameterDefaults(pcbStyle) + const defaults = getViaDiameterDefaults(pcbStyle, boardMinViaDiameters) return { holeDiameter: overrides.holeDiameter ?? defaults.holeDiameter, padDiameter: overrides.padDiameter ?? defaults.padDiameter, } } + +/** + * Default via size for a via created by `component` (a , a trace's + * pcbPath, a route...), using its inherited pcbStyle and the + * minimums of the board it is on. + */ +export const getViaDiameterDefaultsForComponent = ( + component: Pick< + PrimitiveComponent, + "getInheritedMergedProperty" | "_getBoard" + >, + overrides: { holeDiameter?: number; padDiameter?: number } = {}, +) => + getViaDiameterDefaultsWithOverrides( + overrides, + component.getInheritedMergedProperty("pcbStyle"), + component._getBoard()?._getResolvedMinViaDiameters?.(), + ) diff --git a/tests/components/primitive-components/__snapshots__/via-board-minimum-diameters1-pcb.snap.svg b/tests/components/primitive-components/__snapshots__/via-board-minimum-diameters1-pcb.snap.svg new file mode 100644 index 0000000000..e4372881b1 --- /dev/null +++ b/tests/components/primitive-components/__snapshots__/via-board-minimum-diameters1-pcb.snap.svg @@ -0,0 +1 @@ +board minVia 0.3/0.6mm keeps explicit and styled sizesexplicit 0.4/0.8pcbStyle 0.35/0.7route 0.45/0.9 \ No newline at end of file diff --git a/tests/components/primitive-components/__snapshots__/via-board-minimum-diameters2-pcb.snap.svg b/tests/components/primitive-components/__snapshots__/via-board-minimum-diameters2-pcb.snap.svg new file mode 100644 index 0000000000..ded106ae1a --- /dev/null +++ b/tests/components/primitive-components/__snapshots__/via-board-minimum-diameters2-pcb.snap.svg @@ -0,0 +1 @@ +minViaHoleDiameter 0.3mm: via 0.3mm drill / 0.45mm pad \ No newline at end of file diff --git a/tests/components/primitive-components/__snapshots__/via-board-minimum-diameters3-pcb.snap.svg b/tests/components/primitive-components/__snapshots__/via-board-minimum-diameters3-pcb.snap.svg new file mode 100644 index 0000000000..d85ecca35e --- /dev/null +++ b/tests/components/primitive-components/__snapshots__/via-board-minimum-diameters3-pcb.snap.svg @@ -0,0 +1 @@ +no board via minimums: default 0.2mm drill / 0.3mm pad \ No newline at end of file diff --git a/tests/components/primitive-components/via-board-minimum-diameters1.test.tsx b/tests/components/primitive-components/via-board-minimum-diameters1.test.tsx new file mode 100644 index 0000000000..da4b977097 --- /dev/null +++ b/tests/components/primitive-components/via-board-minimum-diameters1.test.tsx @@ -0,0 +1,76 @@ +import { expect, test } from "bun:test" +import type { PcbTraceRoutePoint } from "circuit-json" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +const explicitlySizedRoute = [ + { route_type: "wire", x: 4, y: 0, width: 0.15, layer: "top" }, + { + route_type: "via", + x: 6, + y: 0, + from_layer: "top", + to_layer: "bottom", + hole_diameter: 0.45, + outer_diameter: 0.9, + }, + { route_type: "wire", x: 8, y: 0, width: 0.15, layer: "bottom" }, +] satisfies PcbTraceRoutePoint[] + +test("explicit and pcbStyle via sizes still win over the board's via minimums", async () => { + const { circuit } = getTestFixture() + + circuit.add( + + + + + + + + + + + , + ) + + await circuit.renderUntilSettled() + + expect( + circuit.db.pcb_via + .list() + .map((via) => [via.hole_diameter, via.outer_diameter]), + ).toEqual([ + [0.4, 0.8], + [0.35, 0.7], + [0.45, 0.9], + ]) + expect(circuit).toMatchPcbSnapshot(import.meta.path) +}) diff --git a/tests/components/primitive-components/via-board-minimum-diameters2.test.tsx b/tests/components/primitive-components/via-board-minimum-diameters2.test.tsx new file mode 100644 index 0000000000..ddb44c61a2 --- /dev/null +++ b/tests/components/primitive-components/via-board-minimum-diameters2.test.tsx @@ -0,0 +1,28 @@ +import { expect, test } from "bun:test" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("a board with only minViaHoleDiameter sizes vias like its pcb_board minimums", async () => { + const { circuit } = getTestFixture() + + circuit.add( + + + + , + ) + + await circuit.renderUntilSettled() + + const board = circuit.db.pcb_board.list()[0] + const [via] = circuit.db.pcb_via.list() + expect([via.hole_diameter, via.outer_diameter]).toEqual([ + board.min_via_hole_diameter!, + board.min_via_pad_diameter!, + ]) + expect(circuit).toMatchPcbSnapshot(import.meta.path) +}) diff --git a/tests/components/primitive-components/via-board-minimum-diameters3.test.tsx b/tests/components/primitive-components/via-board-minimum-diameters3.test.tsx new file mode 100644 index 0000000000..c0db9ce8a9 --- /dev/null +++ b/tests/components/primitive-components/via-board-minimum-diameters3.test.tsx @@ -0,0 +1,24 @@ +import { expect, test } from "bun:test" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("boards without via minimums keep the default 0.2/0.3mm via", async () => { + const { circuit } = getTestFixture() + + circuit.add( + + + + , + ) + + await circuit.renderUntilSettled() + + const [via] = circuit.db.pcb_via.list() + expect([via.hole_diameter, via.outer_diameter]).toEqual([0.2, 0.3]) + expect(circuit).toMatchPcbSnapshot(import.meta.path) +}) diff --git a/tests/repros/__snapshots__/repro5559-hand-placed-vias-board-minimums-pcb.snap.svg b/tests/repros/__snapshots__/repro5559-hand-placed-vias-board-minimums-pcb.snap.svg new file mode 100644 index 0000000000..265007c19d --- /dev/null +++ b/tests/repros/__snapshots__/repro5559-hand-placed-vias-board-minimums-pcb.snap.svg @@ -0,0 +1 @@ +R1R2board minVia 0.3/0.6mm: all vias 0.3/0.6mm<via>pcbPath viaspcbtrace via \ No newline at end of file diff --git a/tests/repros/repro5559-hand-placed-vias-board-minimums.test.tsx b/tests/repros/repro5559-hand-placed-vias-board-minimums.test.tsx new file mode 100644 index 0000000000..4e5d988578 --- /dev/null +++ b/tests/repros/repro5559-hand-placed-vias-board-minimums.test.tsx @@ -0,0 +1,67 @@ +import { expect, test } from "bun:test" +import type { PcbTraceRoutePoint } from "circuit-json" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +const pcbtraceRoute = [ + { route_type: "wire", x: -6, y: -3, width: 0.15, layer: "top" }, + { route_type: "wire", x: -4, y: -3, width: 0.15, layer: "top" }, + { route_type: "via", x: -4, y: -3, from_layer: "top", to_layer: "bottom" }, + { route_type: "wire", x: -4, y: -3, width: 0.15, layer: "bottom" }, + { route_type: "wire", x: -2, y: -3, width: 0.15, layer: "bottom" }, +] satisfies PcbTraceRoutePoint[] + +test("repro5559: pcbPath, pcbtrace and vias default to the board's via minimums", async () => { + const { circuit } = getTestFixture() + + circuit.add( + + + + + + + + + + + , + ) + + await circuit.renderUntilSettled() + + expect( + circuit.db.pcb_via + .list() + .map((via) => [via.hole_diameter, via.outer_diameter]), + ).toEqual([ + [0.3, 0.6], + [0.3, 0.6], + [0.3, 0.6], + [0.3, 0.6], + ]) + expect(circuit).toMatchPcbSnapshot(import.meta.path) +})