diff --git a/lib/components/normal-components/Board/Board.ts b/lib/components/normal-components/Board/Board.ts index 053dd6f80e..7dc17b4b74 100644 --- a/lib/components/normal-components/Board/Board.ts +++ b/lib/components/normal-components/Board/Board.ts @@ -1,5 +1,4 @@ -import { getReadablePcbFoldIssueMessage } from "lib/utils/cad/get-readable-pcb-fold-issue-message" -import { tryCreatePcbFold, type PcbFold } from "@tscircuit/flex-utils" +import { type PcbFold, tryCreatePcbFold } from "@tscircuit/flex-utils" import { jlcMinTolerances } from "@tscircuit/jlcpcb-manufacturing-specs" import { getBoundsFromPoints } from "@tscircuit/math-utils" import { boardProps } from "@tscircuit/props" @@ -9,10 +8,11 @@ import type { PcbBoard, PcbVia, } from "circuit-json" +import { getReadablePcbFoldIssueMessage } from "lib/utils/cad/get-readable-pcb-fold-issue-message" import { getBoardAvailableLayers } from "lib/utils/getViaSpanLayers" import { getViaTenting } from "lib/utils/getViaTenting" import { type Matrix, compose, translate } from "transformation-matrix" -import type { z } from "zod" +import { z } from "zod" import { getDescendantSubcircuitIds } from "../../../utils/autorouting/getAncestorSubcircuitIds" import { getBoardCenterFromAnchor } from "../../../utils/boards/get-board-center-from-anchor" import { inflateCircuitJson } from "../../../utils/circuit-json/inflate-circuit-json" @@ -20,19 +20,22 @@ import { getViaDiameterDefaults } from "../../../utils/pcbStyle/getViaDiameterDe import { NormalComponent } from "../../base-components/NormalComponent/NormalComponent" import type { RenderPhase } from "../../base-components/Renderable" import { Group } from "../../primitive-components/Group/Group" -import { resolveBoardMotorMount } from "../../primitive-components/resolve-board-motor-mount" import type { SubcircuitI } from "../../primitive-components/Group/Subcircuit/SubcircuitI" import { Subcircuit_doInitialRenderIsolatedSubcircuits } from "../../primitive-components/Group/Subcircuit/Subcircuit_doInitialRenderIsolatedSubcircuits" import { Subcircuit_getSubcircuitPropHash } from "../../primitive-components/Group/Subcircuit_getSubcircuitPropHash" -import { Board_updatePcbDesignRuleChecks } from "./Board_updatePcbDesignRuleChecks" +import { resolveBoardMotorMount } from "../../primitive-components/resolve-board-motor-mount" import type { BoardI } from "./BoardI" import { Board_doInitialPcbCopperPourCleanup } from "./Board_doInitialPcbCopperPourCleanup" import { Board_doInitialPcbPlacementDesignRuleChecks } from "./Board_doInitialPcbPlacementDesignRuleChecks" import { Board_doInitialPcbStackupRender } from "./Board_doInitialPcbStackupRender" +import { Board_updatePcbDesignRuleChecks } from "./Board_updatePcbDesignRuleChecks" import { BoardCastellatedHole } from "./board-castellated-hole" const MIN_EFFECTIVE_BORDER_RADIUS_MM = 0.01 const DEFAULT_VIA_PAD_DIAMETER_OVER_HOLE_DIAMETER_MM = 0.15 +const boardPropsWithStackup = boardProps.extend({ + jlcpcbStackup: z.string().optional(), +}) /** * Return board-local outline points in mm, centered at the origin in the @@ -106,7 +109,7 @@ const getRoundedRectOutline = ( } export class Board - extends Group + extends Group implements BoardI, SubcircuitI { pcbFold: PcbFold | undefined @@ -124,7 +127,7 @@ export class Board super.doInitialCadModelRender() } - constructor(props: z.input) { + constructor(props: z.input) { super(props) this._castellatedHoles = BoardCastellatedHole.fromBoardOutline( this._parsedProps.outline, @@ -176,7 +179,7 @@ export class Board get config() { return { componentName: "Board", - zodProps: boardProps, + zodProps: boardPropsWithStackup, } } diff --git a/lib/components/normal-components/Board/Board_doInitialPcbStackupRender.ts b/lib/components/normal-components/Board/Board_doInitialPcbStackupRender.ts index 400132fa86..f8535643b0 100644 --- a/lib/components/normal-components/Board/Board_doInitialPcbStackupRender.ts +++ b/lib/components/normal-components/Board/Board_doInitialPcbStackupRender.ts @@ -1,4 +1,7 @@ -import { getJlcStackup } from "@tscircuit/jlcpcb-manufacturing-specs" +import { + getJlcStackup, + jlcStackups, +} from "@tscircuit/jlcpcb-manufacturing-specs" import type { PcbBoard } from "circuit-json" import type { Board } from "./Board" @@ -17,14 +20,29 @@ export function Board_doInitialPcbStackupRender( element.pcb_board_id === importedPcbBoard?.pcb_board_id, ) let layers = importedStackup?.layers - if (!layers && board._parsedProps.fabricatorPreset) { - const { num_layers, thickness, material } = - importedPcbBoard ?? db.pcb_board.get(pcb_board_id)! - layers = getJlcStackup({ + const { fabricatorPreset, jlcpcbStackup } = board._parsedProps + if (!layers && (fabricatorPreset || jlcpcbStackup)) { + const { num_layers, - thickness: thickness ?? board.boardThickness, - material, - })?.layers + thickness, + material = "fr4", + } = importedPcbBoard ?? db.pcb_board.get(pcb_board_id)! + const resolvedThickness = thickness ?? board.boardThickness + layers = ( + jlcpcbStackup + ? jlcStackups.find( + (stackup) => + stackup.name === jlcpcbStackup && + stackup.num_layers === num_layers && + stackup.thickness === resolvedThickness && + stackup.material === material, + ) + : getJlcStackup({ + num_layers, + thickness: resolvedThickness, + material, + }) + )?.layers } if (!layers) return diff --git a/lib/fiber/intrinsic-jsx.ts b/lib/fiber/intrinsic-jsx.ts index 0e1abda9ba..c5273e5e14 100644 --- a/lib/fiber/intrinsic-jsx.ts +++ b/lib/fiber/intrinsic-jsx.ts @@ -10,7 +10,7 @@ export interface TscircuitElements { diode: Props.DiodeProps fuse: Props.FuseProps led: Props.LedProps - board: Props.BoardProps + board: Props.BoardProps & { jlcpcbStackup?: string } drccheck: Props.DrcCheckProps mountedboard: Props.MountedBoardProps panel: Props.PanelProps diff --git a/tests/components/normal-components/board-stackup-preset5.test.tsx b/tests/components/normal-components/board-stackup-preset5.test.tsx new file mode 100644 index 0000000000..e68975bb54 --- /dev/null +++ b/tests/components/normal-components/board-stackup-preset5.test.tsx @@ -0,0 +1,27 @@ +import { expect, test } from "bun:test" +import { jlc04101h1080Stackup } from "@tscircuit/jlcpcb-manufacturing-specs" +import { pcb_stackup } from "circuit-json" +import { getBoardStackupFixture } from "tests/fixtures/get-board-stackup-fixture" + +test("jlcpcbStackup selects JLC04101H-1080 for 4-layer 1.0mm FR-4 board", async () => { + const circuit = await getBoardStackupFixture({ + width: "100mm", + height: "100mm", + layers: 4, + thickness: 1, + material: "fr4", + fabricatorPreset: "jlcpcb_standard", + jlcpcbStackup: "JLC04101H-1080", + }) + const stackups = circuit.db.pcb_stackup.list() + expect(stackups).toHaveLength(1) + const stackup = stackups[0]! + expect(pcb_stackup.parse(stackup)).toEqual(stackup) + expect(stackup.layers).toEqual(jlc04101h1080Stackup.layers) + expect(stackup.layers[1]).toMatchObject({ + material_name: "1080 RC67%", + thickness: 0.0764, + relative_permittivity: 3.91, + }) + expect(circuit.db.pcb_placement_error.list()).toHaveLength(0) +}) diff --git a/tests/components/normal-components/board-stackup-preset6.test.tsx b/tests/components/normal-components/board-stackup-preset6.test.tsx new file mode 100644 index 0000000000..26c5e43cd5 --- /dev/null +++ b/tests/components/normal-components/board-stackup-preset6.test.tsx @@ -0,0 +1,28 @@ +import { expect, test } from "bun:test" +import { jlc04101h2116Stackup } from "@tscircuit/jlcpcb-manufacturing-specs" +import { pcb_stackup } from "circuit-json" +import { getBoardStackupFixture } from "tests/fixtures/get-board-stackup-fixture" + +test("jlcpcbStackup selects JLC04101H-2116 for 4-layer 1.0mm FR-4 board", async () => { + const circuit = await getBoardStackupFixture({ + width: "100mm", + height: "100mm", + layers: 4, + thickness: 1, + material: "fr4", + fabricatorPreset: "jlcpcb_standard", + jlcpcbStackup: "JLC04101H-2116", + }) + const stackups = circuit.db.pcb_stackup.list() + expect(stackups).toHaveLength(1) + const stackup = stackups[0]! + expect(pcb_stackup.parse(stackup)).toEqual(stackup) + expect(stackup.layers).toEqual(jlc04101h2116Stackup.layers) + expect(stackup.layers).toHaveLength(9) + expect(stackup.layers[1]).toMatchObject({ + material_name: "2116 RC54%", + thickness: 0.124, + relative_permittivity: 4.16, + }) + expect(circuit.db.pcb_placement_error.list()).toHaveLength(0) +}) diff --git a/tests/components/normal-components/board-stackup-preset7.test.tsx b/tests/components/normal-components/board-stackup-preset7.test.tsx new file mode 100644 index 0000000000..cae3c26e59 --- /dev/null +++ b/tests/components/normal-components/board-stackup-preset7.test.tsx @@ -0,0 +1,27 @@ +import { expect, test } from "bun:test" +import { jlc04101h3313Stackup } from "@tscircuit/jlcpcb-manufacturing-specs" +import { pcb_stackup } from "circuit-json" +import { getBoardStackupFixture } from "tests/fixtures/get-board-stackup-fixture" + +test("jlcpcbStackup selects JLC04101H-3313 for 4-layer 1.0mm FR-4 board", async () => { + const circuit = await getBoardStackupFixture({ + width: "100mm", + height: "100mm", + layers: 4, + thickness: 1, + material: "fr4", + fabricatorPreset: "jlcpcb_standard", + jlcpcbStackup: "JLC04101H-3313", + }) + const stackups = circuit.db.pcb_stackup.list() + expect(stackups).toHaveLength(1) + const stackup = stackups[0]! + expect(pcb_stackup.parse(stackup)).toEqual(stackup) + expect(stackup.layers).toEqual(jlc04101h3313Stackup.layers) + expect(stackup.layers[1]).toMatchObject({ + material_name: "3313 RC57%", + thickness: 0.0994, + relative_permittivity: 4.1, + }) + expect(circuit.db.pcb_placement_error.list()).toHaveLength(0) +}) diff --git a/tests/components/normal-components/board-stackup-preset8.test.tsx b/tests/components/normal-components/board-stackup-preset8.test.tsx new file mode 100644 index 0000000000..de67c37e80 --- /dev/null +++ b/tests/components/normal-components/board-stackup-preset8.test.tsx @@ -0,0 +1,27 @@ +import { expect, test } from "bun:test" +import { jlc04101h7628Stackup } from "@tscircuit/jlcpcb-manufacturing-specs" +import { pcb_stackup } from "circuit-json" +import { getBoardStackupFixture } from "tests/fixtures/get-board-stackup-fixture" + +test("jlcpcbStackup selects JLC04101H-7628 for 4-layer 1.0mm FR-4 board", async () => { + const circuit = await getBoardStackupFixture({ + width: "100mm", + height: "100mm", + layers: 4, + thickness: 1, + material: "fr4", + fabricatorPreset: "jlcpcb_standard", + jlcpcbStackup: "JLC04101H-7628", + }) + const stackups = circuit.db.pcb_stackup.list() + expect(stackups).toHaveLength(1) + const stackup = stackups[0]! + expect(pcb_stackup.parse(stackup)).toEqual(stackup) + expect(stackup.layers).toEqual(jlc04101h7628Stackup.layers) + expect(stackup.layers[1]).toMatchObject({ + material_name: "7628 RC49%", + thickness: 0.2104, + relative_permittivity: 4.4, + }) + expect(circuit.db.pcb_placement_error.list()).toHaveLength(0) +}) diff --git a/tests/components/normal-components/board-stackup-unsupported6.test.tsx b/tests/components/normal-components/board-stackup-unsupported6.test.tsx new file mode 100644 index 0000000000..ff33fda857 --- /dev/null +++ b/tests/components/normal-components/board-stackup-unsupported6.test.tsx @@ -0,0 +1,16 @@ +import { expect, test } from "bun:test" +import { getBoardStackupFixture } from "tests/fixtures/get-board-stackup-fixture" + +test("mismatched jlcpcbStackup construction for board thickness emits no stackup", async () => { + const circuit = await getBoardStackupFixture({ + width: 20, + height: 20, + fabricatorPreset: "jlcpcb_standard", + jlcpcbStackup: "JLC04161H-3313", + layers: 4, + thickness: 1, + material: "fr4", + }) + expect(circuit.db.pcb_stackup.list()).toHaveLength(0) + expect(circuit.db.pcb_placement_error.list()).toHaveLength(0) +}) diff --git a/tests/fixtures/get-board-stackup-fixture.tsx b/tests/fixtures/get-board-stackup-fixture.tsx index 8fc7680301..b034418dc8 100644 --- a/tests/fixtures/get-board-stackup-fixture.tsx +++ b/tests/fixtures/get-board-stackup-fixture.tsx @@ -3,7 +3,9 @@ import type { AnyCircuitElement, PcbBoard } from "circuit-json" import simpleCircuitJson from "tests/components/normal-components/assets/simple-circuit.json" import { getTestFixture } from "./get-test-fixture" -export const getBoardStackupFixture = async (boardProps: BoardProps) => { +export const getBoardStackupFixture = async ( + boardProps: BoardProps & { jlcpcbStackup?: string }, +) => { const { circuit } = getTestFixture() circuit.add() await circuit.renderUntilSettled()