circuit-json-to-tscircuit 0.0.53 → 0.0.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-YC65LHVL.js → chunk-NOSIW5HF.js} +39 -18
- package/dist/cli/main.js +2 -2
- package/dist/lib/index.js +1 -1
- package/lib/generate-footprint-tsx/convert-vias.ts +34 -0
- package/lib/generate-footprint-tsx.tsx +2 -0
- package/lib/get-board-using-template.ts +7 -2
- package/package.json +1 -1
- package/tests/board-center.test.tsx +55 -0
- package/tests/fixtures/create-ti-evm-roundtrip.ts +21 -1
- package/tests/support-pcb-vias.test.tsx +91 -0
- package/tests/ti-evms/__snapshots__/ti-drv8307evm-roundtrip-pcb-comparison.snap.svg +3 -3
- package/tests/ti-evms/__snapshots__/ti-lm251772evm-pd-roundtrip-pcb-comparison.snap.svg +4 -4
- package/tests/ti-evms/__snapshots__/ti-lm5155evm-fly-roundtrip-pcb-comparison.snap.svg +1 -1
- package/tests/ti-evms/__snapshots__/ti-lmg342x-bb-evm-roundtrip-pcb-comparison.snap.svg +1 -1
- package/tests/ti-evms/ti-drv8307evm-roundtrip.test.tsx +361 -1
- package/tests/ti-evms/ti-lm251772evm-pd-roundtrip.test.tsx +262 -1
- package/tests/ti-evms/ti-lm5155evm-fly-roundtrip.test.tsx +110 -1
- package/tests/ti-evms/ti-lmg342x-bb-evm-roundtrip.test.tsx +412 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// lib/get-board-using-template.ts
|
|
2
|
-
import { mmStr as
|
|
3
|
-
import { su as
|
|
2
|
+
import { mmStr as mmStr8 } from "@tscircuit/mm";
|
|
3
|
+
import { su as su13 } from "@tscircuit/soup-util";
|
|
4
4
|
|
|
5
5
|
// lib/generate-footprint-tsx/convert-copper-text.ts
|
|
6
6
|
import { su } from "@tscircuit/soup-util";
|
|
@@ -579,11 +579,31 @@ var convertSmtPads = (circuitJson) => {
|
|
|
579
579
|
return elementStrings;
|
|
580
580
|
};
|
|
581
581
|
|
|
582
|
+
// lib/generate-footprint-tsx/convert-vias.ts
|
|
583
|
+
import { mmStr as mmStr7 } from "@tscircuit/mm";
|
|
584
|
+
import { su as su12 } from "@tscircuit/soup-util";
|
|
585
|
+
var convertVias = (circuitJson) => {
|
|
586
|
+
return su12(circuitJson).pcb_via.list().map((via) => {
|
|
587
|
+
const fromLayer = via.from_layer ?? via.layers[0];
|
|
588
|
+
const toLayer = via.to_layer ?? via.layers[via.layers.length - 1];
|
|
589
|
+
if (!fromLayer || !toLayer) {
|
|
590
|
+
throw new Error(`Via ${via.pcb_via_id} has no copper layer endpoints`);
|
|
591
|
+
}
|
|
592
|
+
const legacyTented = "is_tented" in via && typeof via.is_tented === "boolean" ? via.is_tented : void 0;
|
|
593
|
+
const tentedOnTop = via.tented_on_top ?? legacyTented;
|
|
594
|
+
const tentedOnBottom = via.tented_on_bottom ?? legacyTented;
|
|
595
|
+
const tented = tentedOnTop && tentedOnBottom ? "both_sides" : tentedOnTop ? "top_tented" : tentedOnBottom ? "bottom_tented" : "exposed";
|
|
596
|
+
const tentedAttr = tentedOnTop !== void 0 || tentedOnBottom !== void 0 ? ` tented="${tented}"` : "";
|
|
597
|
+
return `<via pcbX="${mmStr7(via.x)}" pcbY="${mmStr7(via.y)}" holeDiameter="${mmStr7(via.hole_diameter)}" outerDiameter="${mmStr7(via.outer_diameter)}" fromLayer="${fromLayer}" toLayer="${toLayer}"${tentedAttr} />`;
|
|
598
|
+
});
|
|
599
|
+
};
|
|
600
|
+
|
|
582
601
|
// lib/generate-footprint-tsx.tsx
|
|
583
602
|
var generateFootprintTsx = (circuitJson) => {
|
|
584
603
|
const converters = [
|
|
585
604
|
convertHoles,
|
|
586
605
|
convertPlatedHoles,
|
|
606
|
+
convertVias,
|
|
587
607
|
convertSmtPads,
|
|
588
608
|
convertSilkscreen,
|
|
589
609
|
convertFabrication,
|
|
@@ -610,21 +630,21 @@ var getBoardUsingTemplate = ({
|
|
|
610
630
|
circuitJson,
|
|
611
631
|
componentName
|
|
612
632
|
}) => {
|
|
613
|
-
const pcbBoard =
|
|
633
|
+
const pcbBoard = su13(circuitJson).pcb_board.list()[0];
|
|
614
634
|
const boardProps = [];
|
|
615
635
|
if (pcbBoard) {
|
|
616
636
|
if (pcbBoard.width !== void 0) {
|
|
617
|
-
boardProps.push(`width="${
|
|
637
|
+
boardProps.push(`width="${mmStr8(pcbBoard.width)}"`);
|
|
618
638
|
}
|
|
619
639
|
if (pcbBoard.height !== void 0) {
|
|
620
|
-
boardProps.push(`height="${
|
|
640
|
+
boardProps.push(`height="${mmStr8(pcbBoard.height)}"`);
|
|
621
641
|
}
|
|
622
642
|
if (pcbBoard.outline && pcbBoard.outline.length > 0) {
|
|
623
643
|
const points = pcbBoard.outline.map((p) => `{ x: ${p.x}, y: ${p.y} }`).join(", ");
|
|
624
644
|
boardProps.push(`outline={[${points}]}`);
|
|
625
645
|
}
|
|
626
646
|
if (pcbBoard.thickness !== void 0) {
|
|
627
|
-
boardProps.push(`thickness="${
|
|
647
|
+
boardProps.push(`thickness="${mmStr8(pcbBoard.thickness)}"`);
|
|
628
648
|
}
|
|
629
649
|
if (pcbBoard.num_layers !== void 0) {
|
|
630
650
|
boardProps.push(`layers={${pcbBoard.num_layers}}`);
|
|
@@ -638,9 +658,10 @@ var getBoardUsingTemplate = ({
|
|
|
638
658
|
if (pcbBoard.silkscreen_color !== void 0) {
|
|
639
659
|
boardProps.push(`silkscreenColor="${pcbBoard.silkscreen_color}"`);
|
|
640
660
|
}
|
|
641
|
-
|
|
661
|
+
const resolvedAnchorPosition = pcbBoard.anchor_position ?? pcbBoard.center;
|
|
662
|
+
if (pcbBoard.anchor_position !== void 0 || resolvedAnchorPosition.x !== 0 || resolvedAnchorPosition.y !== 0) {
|
|
642
663
|
boardProps.push(
|
|
643
|
-
`boardAnchorPosition={{ x: ${
|
|
664
|
+
`boardAnchorPosition={{ x: ${resolvedAnchorPosition.x}, y: ${resolvedAnchorPosition.y} }}`
|
|
644
665
|
);
|
|
645
666
|
}
|
|
646
667
|
if (pcbBoard.anchor_alignment !== void 0) {
|
|
@@ -661,18 +682,18 @@ ${componentName ? `export default ${componentName}` : ""}
|
|
|
661
682
|
};
|
|
662
683
|
|
|
663
684
|
// lib/generate-symbol-tsx.ts
|
|
664
|
-
import { su as
|
|
685
|
+
import { su as su14 } from "@tscircuit/soup-util";
|
|
665
686
|
var escapeJsxText2 = (text) => text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
666
687
|
var generateSymbolTsx = (circuitJson) => {
|
|
667
|
-
const schematicArcs =
|
|
668
|
-
const schematicLines =
|
|
669
|
-
const schematicPaths =
|
|
670
|
-
const schematicTexts =
|
|
671
|
-
const schematicCircles =
|
|
672
|
-
const schematicBoxes =
|
|
673
|
-
const schematicRects =
|
|
674
|
-
const schematicTables =
|
|
675
|
-
const schematicTableCells =
|
|
688
|
+
const schematicArcs = su14(circuitJson).schematic_arc.list();
|
|
689
|
+
const schematicLines = su14(circuitJson).schematic_line.list();
|
|
690
|
+
const schematicPaths = su14(circuitJson).schematic_path.list();
|
|
691
|
+
const schematicTexts = su14(circuitJson).schematic_text.list();
|
|
692
|
+
const schematicCircles = su14(circuitJson).schematic_circle.list();
|
|
693
|
+
const schematicBoxes = su14(circuitJson).schematic_box.list();
|
|
694
|
+
const schematicRects = su14(circuitJson).schematic_rect.list();
|
|
695
|
+
const schematicTables = su14(circuitJson).schematic_table.list();
|
|
696
|
+
const schematicTableCells = su14(circuitJson).schematic_table_cell.list();
|
|
676
697
|
const elementStrings = [];
|
|
677
698
|
for (const arc of schematicArcs) {
|
|
678
699
|
const center = arc.center ?? { x: 0, y: 0 };
|
package/dist/cli/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
convertCircuitJsonToTscircuit
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-NOSIW5HF.js";
|
|
5
5
|
|
|
6
6
|
// cli/main.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -16,7 +16,7 @@ var package_default = {
|
|
|
16
16
|
"./cli": "./dist/cli/main.js"
|
|
17
17
|
},
|
|
18
18
|
type: "module",
|
|
19
|
-
version: "0.0.
|
|
19
|
+
version: "0.0.54",
|
|
20
20
|
repository: {
|
|
21
21
|
type: "git",
|
|
22
22
|
url: "https://github.com/tscircuit/circuit-json-to-tscircuit.git"
|
package/dist/lib/index.js
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { mmStr } from "@tscircuit/mm"
|
|
2
|
+
import { su } from "@tscircuit/soup-util"
|
|
3
|
+
import type { FootprintElementConverter } from "./converter-types"
|
|
4
|
+
|
|
5
|
+
export const convertVias: FootprintElementConverter = (circuitJson) => {
|
|
6
|
+
return su(circuitJson)
|
|
7
|
+
.pcb_via.list()
|
|
8
|
+
.map((via) => {
|
|
9
|
+
const fromLayer = via.from_layer ?? via.layers[0]
|
|
10
|
+
const toLayer = via.to_layer ?? via.layers[via.layers.length - 1]
|
|
11
|
+
if (!fromLayer || !toLayer) {
|
|
12
|
+
throw new Error(`Via ${via.pcb_via_id} has no copper layer endpoints`)
|
|
13
|
+
}
|
|
14
|
+
const legacyTented =
|
|
15
|
+
"is_tented" in via && typeof via.is_tented === "boolean"
|
|
16
|
+
? via.is_tented
|
|
17
|
+
: undefined
|
|
18
|
+
const tentedOnTop = via.tented_on_top ?? legacyTented
|
|
19
|
+
const tentedOnBottom = via.tented_on_bottom ?? legacyTented
|
|
20
|
+
const tented =
|
|
21
|
+
tentedOnTop && tentedOnBottom
|
|
22
|
+
? "both_sides"
|
|
23
|
+
: tentedOnTop
|
|
24
|
+
? "top_tented"
|
|
25
|
+
: tentedOnBottom
|
|
26
|
+
? "bottom_tented"
|
|
27
|
+
: "exposed"
|
|
28
|
+
const tentedAttr =
|
|
29
|
+
tentedOnTop !== undefined || tentedOnBottom !== undefined
|
|
30
|
+
? ` tented="${tented}"`
|
|
31
|
+
: ""
|
|
32
|
+
return `<via pcbX="${mmStr(via.x)}" pcbY="${mmStr(via.y)}" holeDiameter="${mmStr(via.hole_diameter)}" outerDiameter="${mmStr(via.outer_diameter)}" fromLayer="${fromLayer}" toLayer="${toLayer}"${tentedAttr} />`
|
|
33
|
+
})
|
|
34
|
+
}
|
|
@@ -11,6 +11,7 @@ import { convertPlatedHoles } from "./generate-footprint-tsx/convert-plated-hole
|
|
|
11
11
|
import { convertSilkscreen } from "./generate-footprint-tsx/convert-silkscreen"
|
|
12
12
|
import { convertSilkscreenText } from "./generate-footprint-tsx/convert-silkscreen-text"
|
|
13
13
|
import { convertSmtPads } from "./generate-footprint-tsx/convert-smt-pads"
|
|
14
|
+
import { convertVias } from "./generate-footprint-tsx/convert-vias"
|
|
14
15
|
|
|
15
16
|
export const generateFootprintTsx = (
|
|
16
17
|
circuitJson: AnyCircuitElement[],
|
|
@@ -18,6 +19,7 @@ export const generateFootprintTsx = (
|
|
|
18
19
|
const converters: FootprintElementConverter[] = [
|
|
19
20
|
convertHoles,
|
|
20
21
|
convertPlatedHoles,
|
|
22
|
+
convertVias,
|
|
21
23
|
convertSmtPads,
|
|
22
24
|
convertSilkscreen,
|
|
23
25
|
convertFabrication,
|
|
@@ -44,9 +44,14 @@ export const getBoardUsingTemplate = ({
|
|
|
44
44
|
if (pcbBoard.silkscreen_color !== undefined) {
|
|
45
45
|
boardProps.push(`silkscreenColor="${pcbBoard.silkscreen_color}"`)
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
const resolvedAnchorPosition = pcbBoard.anchor_position ?? pcbBoard.center
|
|
48
|
+
if (
|
|
49
|
+
pcbBoard.anchor_position !== undefined ||
|
|
50
|
+
resolvedAnchorPosition.x !== 0 ||
|
|
51
|
+
resolvedAnchorPosition.y !== 0
|
|
52
|
+
) {
|
|
48
53
|
boardProps.push(
|
|
49
|
-
`boardAnchorPosition={{ x: ${
|
|
54
|
+
`boardAnchorPosition={{ x: ${resolvedAnchorPosition.x}, y: ${resolvedAnchorPosition.y} }}`,
|
|
50
55
|
)
|
|
51
56
|
}
|
|
52
57
|
if (pcbBoard.anchor_alignment !== undefined) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type { CircuitJson, PcbBoard } from "circuit-json"
|
|
3
|
+
import { convertCircuitJsonToTscircuit } from "lib"
|
|
4
|
+
import { runTscircuitCode } from "tscircuit"
|
|
5
|
+
|
|
6
|
+
test("preserves offset board bounds without moving its footprint", async () => {
|
|
7
|
+
for (const center of [
|
|
8
|
+
{ x: 60, y: 85 },
|
|
9
|
+
{ x: -60, y: -85 },
|
|
10
|
+
]) {
|
|
11
|
+
const board: PcbBoard = {
|
|
12
|
+
type: "pcb_board",
|
|
13
|
+
pcb_board_id: "board",
|
|
14
|
+
center,
|
|
15
|
+
width: 20,
|
|
16
|
+
height: 10,
|
|
17
|
+
num_layers: 2,
|
|
18
|
+
thickness: 1.6,
|
|
19
|
+
material: "fr4",
|
|
20
|
+
outline: [
|
|
21
|
+
{ x: center.x - 10, y: center.y - 5 },
|
|
22
|
+
{ x: center.x + 10, y: center.y - 5 },
|
|
23
|
+
{ x: center.x + 10, y: center.y + 5 },
|
|
24
|
+
{ x: center.x - 10, y: center.y + 5 },
|
|
25
|
+
],
|
|
26
|
+
}
|
|
27
|
+
const circuitJson: CircuitJson = [
|
|
28
|
+
board,
|
|
29
|
+
{
|
|
30
|
+
type: "pcb_hole",
|
|
31
|
+
pcb_hole_id: "hole",
|
|
32
|
+
x: center.x + 2,
|
|
33
|
+
y: center.y - 1,
|
|
34
|
+
hole_shape: "circle",
|
|
35
|
+
hole_diameter: 1,
|
|
36
|
+
},
|
|
37
|
+
]
|
|
38
|
+
const rendered = (await runTscircuitCode(
|
|
39
|
+
convertCircuitJsonToTscircuit(circuitJson, {
|
|
40
|
+
componentName: "OffsetBoard",
|
|
41
|
+
}),
|
|
42
|
+
)) as CircuitJson
|
|
43
|
+
expect(
|
|
44
|
+
rendered.find((element) => element.type === "pcb_board"),
|
|
45
|
+
).toMatchObject({
|
|
46
|
+
center,
|
|
47
|
+
width: board.width,
|
|
48
|
+
height: board.height,
|
|
49
|
+
outline: board.outline,
|
|
50
|
+
})
|
|
51
|
+
expect(
|
|
52
|
+
rendered.find((element) => element.type === "pcb_hole"),
|
|
53
|
+
).toMatchObject({ x: center.x + 2, y: center.y - 1 })
|
|
54
|
+
}
|
|
55
|
+
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { expect } from "bun:test"
|
|
1
2
|
import { readFile } from "node:fs/promises"
|
|
2
3
|
import { join } from "node:path"
|
|
3
4
|
import { gunzipSync } from "node:zlib"
|
|
@@ -36,6 +37,25 @@ export async function createTiEvmRoundtrip({
|
|
|
36
37
|
generatedTscircuit,
|
|
37
38
|
)) as CircuitJson
|
|
38
39
|
|
|
40
|
+
const sourceBoard = sourceCircuitJson.find(
|
|
41
|
+
(element) => element.type === "pcb_board",
|
|
42
|
+
)
|
|
43
|
+
const renderedBoard = renderedCircuitJson.find(
|
|
44
|
+
(element) => element.type === "pcb_board",
|
|
45
|
+
)
|
|
46
|
+
if (!sourceBoard || !renderedBoard) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`${fixtureName} must contain source and rendered PCB boards`,
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
expect(renderedBoard).toMatchObject({ center: sourceBoard.center })
|
|
52
|
+
|
|
53
|
+
expect(
|
|
54
|
+
renderedCircuitJson.filter((element) => element.type === "pcb_via"),
|
|
55
|
+
).toHaveLength(
|
|
56
|
+
sourceCircuitJson.filter((element) => element.type === "pcb_via").length,
|
|
57
|
+
)
|
|
58
|
+
|
|
39
59
|
const sourcePcbSvg = convertCircuitJsonToPcbSvg(sourceCircuitJson, {
|
|
40
60
|
matchBoardAspectRatio: true,
|
|
41
61
|
})
|
|
@@ -76,7 +96,7 @@ function createComparisonSvg({
|
|
|
76
96
|
}): string {
|
|
77
97
|
return stackSvgsHorizontally([sourceSvg, renderedSvg], {
|
|
78
98
|
gap: 24,
|
|
79
|
-
normalizeSize:
|
|
99
|
+
normalizeSize: kind !== "PCB",
|
|
80
100
|
targetSize: 800,
|
|
81
101
|
rootAttributes: {
|
|
82
102
|
"aria-label": `${fixtureName} ${kind}: source Circuit JSON on left, generated tscircuit render on right`,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type { CircuitJson, PcbVia } from "circuit-json"
|
|
3
|
+
import { convertCircuitJsonToTscircuit } from "lib"
|
|
4
|
+
import { runTscircuitCode } from "tscircuit"
|
|
5
|
+
import { convertVias } from "../lib/generate-footprint-tsx/convert-vias"
|
|
6
|
+
|
|
7
|
+
test("preserves via geometry, copper layers and tenting", async () => {
|
|
8
|
+
const vias: (PcbVia & { is_tented?: boolean })[] = [
|
|
9
|
+
{
|
|
10
|
+
type: "pcb_via",
|
|
11
|
+
pcb_via_id: "through",
|
|
12
|
+
x: -2,
|
|
13
|
+
y: 3,
|
|
14
|
+
hole_diameter: 0.3,
|
|
15
|
+
outer_diameter: 0.6,
|
|
16
|
+
layers: ["top", "inner1", "inner2", "bottom"],
|
|
17
|
+
is_tented: true,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type: "pcb_via",
|
|
21
|
+
pcb_via_id: "blind",
|
|
22
|
+
x: 4,
|
|
23
|
+
y: -1,
|
|
24
|
+
hole_diameter: 0.2,
|
|
25
|
+
outer_diameter: 0.5,
|
|
26
|
+
layers: ["top", "inner1"],
|
|
27
|
+
tented_on_top: true,
|
|
28
|
+
tented_on_bottom: false,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: "pcb_via",
|
|
32
|
+
pcb_via_id: "buried",
|
|
33
|
+
x: 1,
|
|
34
|
+
y: 2,
|
|
35
|
+
hole_diameter: 0.25,
|
|
36
|
+
outer_diameter: 0.55,
|
|
37
|
+
layers: ["inner1", "inner2"],
|
|
38
|
+
is_tented: false,
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
const circuitJson: CircuitJson = [
|
|
42
|
+
{
|
|
43
|
+
type: "pcb_board",
|
|
44
|
+
pcb_board_id: "board",
|
|
45
|
+
center: { x: 0, y: 0 },
|
|
46
|
+
width: 20,
|
|
47
|
+
height: 20,
|
|
48
|
+
num_layers: 4,
|
|
49
|
+
thickness: 1.6,
|
|
50
|
+
material: "fr4",
|
|
51
|
+
},
|
|
52
|
+
...vias,
|
|
53
|
+
]
|
|
54
|
+
const generatedTscircuit = convertCircuitJsonToTscircuit(circuitJson, {
|
|
55
|
+
componentName: "ViaBoard",
|
|
56
|
+
})
|
|
57
|
+
const renderedCircuitJson = (await runTscircuitCode(
|
|
58
|
+
generatedTscircuit,
|
|
59
|
+
)) as CircuitJson
|
|
60
|
+
const renderedVias = renderedCircuitJson.filter(
|
|
61
|
+
(element) => element.type === "pcb_via",
|
|
62
|
+
)
|
|
63
|
+
expect(renderedVias).toHaveLength(vias.length)
|
|
64
|
+
for (const [index, via] of vias.entries()) {
|
|
65
|
+
expect(renderedVias[index]).toMatchObject({
|
|
66
|
+
x: via.x,
|
|
67
|
+
y: via.y,
|
|
68
|
+
hole_diameter: via.hole_diameter,
|
|
69
|
+
outer_diameter: via.outer_diameter,
|
|
70
|
+
layers: via.layers,
|
|
71
|
+
tented_on_top: via.tented_on_top ?? via.is_tented,
|
|
72
|
+
tented_on_bottom: via.tented_on_bottom ?? via.is_tented,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test("rejects vias without copper layer endpoints", () => {
|
|
78
|
+
expect(() =>
|
|
79
|
+
convertVias([
|
|
80
|
+
{
|
|
81
|
+
type: "pcb_via",
|
|
82
|
+
pcb_via_id: "missing_layers",
|
|
83
|
+
x: 0,
|
|
84
|
+
y: 0,
|
|
85
|
+
hole_diameter: 0.3,
|
|
86
|
+
outer_diameter: 0.6,
|
|
87
|
+
layers: [],
|
|
88
|
+
},
|
|
89
|
+
]),
|
|
90
|
+
).toThrow("Via missing_layers has no copper layer endpoints")
|
|
91
|
+
})
|