circuit-json-to-tscircuit 0.0.52 → 0.0.54

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.
@@ -1,6 +1,6 @@
1
1
  // lib/get-board-using-template.ts
2
- import { mmStr as mmStr7 } from "@tscircuit/mm";
3
- import { su as su12 } from "@tscircuit/soup-util";
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 = su12(circuitJson).pcb_board.list()[0];
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="${mmStr7(pcbBoard.width)}"`);
637
+ boardProps.push(`width="${mmStr8(pcbBoard.width)}"`);
618
638
  }
619
639
  if (pcbBoard.height !== void 0) {
620
- boardProps.push(`height="${mmStr7(pcbBoard.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="${mmStr7(pcbBoard.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}}`);
@@ -661,18 +681,18 @@ ${componentName ? `export default ${componentName}` : ""}
661
681
  };
662
682
 
663
683
  // lib/generate-symbol-tsx.ts
664
- import { su as su13 } from "@tscircuit/soup-util";
684
+ import { su as su14 } from "@tscircuit/soup-util";
665
685
  var escapeJsxText2 = (text) => text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
666
686
  var generateSymbolTsx = (circuitJson) => {
667
- const schematicArcs = su13(circuitJson).schematic_arc.list();
668
- const schematicLines = su13(circuitJson).schematic_line.list();
669
- const schematicPaths = su13(circuitJson).schematic_path.list();
670
- const schematicTexts = su13(circuitJson).schematic_text.list();
671
- const schematicCircles = su13(circuitJson).schematic_circle.list();
672
- const schematicBoxes = su13(circuitJson).schematic_box.list();
673
- const schematicRects = su13(circuitJson).schematic_rect.list();
674
- const schematicTables = su13(circuitJson).schematic_table.list();
675
- const schematicTableCells = su13(circuitJson).schematic_table_cell.list();
687
+ const schematicArcs = su14(circuitJson).schematic_arc.list();
688
+ const schematicLines = su14(circuitJson).schematic_line.list();
689
+ const schematicPaths = su14(circuitJson).schematic_path.list();
690
+ const schematicTexts = su14(circuitJson).schematic_text.list();
691
+ const schematicCircles = su14(circuitJson).schematic_circle.list();
692
+ const schematicBoxes = su14(circuitJson).schematic_box.list();
693
+ const schematicRects = su14(circuitJson).schematic_rect.list();
694
+ const schematicTables = su14(circuitJson).schematic_table.list();
695
+ const schematicTableCells = su14(circuitJson).schematic_table_cell.list();
676
696
  const elementStrings = [];
677
697
  for (const arc of schematicArcs) {
678
698
  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-YC65LHVL.js";
4
+ } from "../chunk-OFR6QZ6I.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.51",
19
+ version: "0.0.53",
20
20
  repository: {
21
21
  type: "git",
22
22
  url: "https://github.com/tscircuit/circuit-json-to-tscircuit.git"
@@ -37,14 +37,15 @@ var package_default = {
37
37
  "@types/react": "18",
38
38
  "@types/react-dom": "18",
39
39
  "bun-match-svg": "^0.0.15",
40
- "circuit-json": "^0.0.433",
40
+ "circuit-json": "^0.0.507",
41
41
  "circuit-to-svg": "^0.0.353",
42
42
  commander: "^13.1.0",
43
43
  prettier: "^3.9.5",
44
44
  shiki: "^4.3.1",
45
45
  "stack-svgs": "^0.0.1",
46
- tscircuit: "^0.0.1873",
47
- tsup: "^8.3.5"
46
+ tscircuit: "^0.0.2696",
47
+ tsup: "^8.3.5",
48
+ zod: "^3.25.76"
48
49
  },
49
50
  peerDependencies: {
50
51
  typescript: "^5.0.0"
package/dist/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  convertCircuitJsonToTscircuit
3
- } from "../chunk-YC65LHVL.js";
3
+ } from "../chunk-OFR6QZ6I.js";
4
4
  export {
5
5
  convertCircuitJsonToTscircuit
6
6
  };
@@ -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,
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "./cli": "./dist/cli/main.js"
7
7
  },
8
8
  "type": "module",
9
- "version": "0.0.52",
9
+ "version": "0.0.54",
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "https://github.com/tscircuit/circuit-json-to-tscircuit.git"
@@ -27,14 +27,15 @@
27
27
  "@types/react": "18",
28
28
  "@types/react-dom": "18",
29
29
  "bun-match-svg": "^0.0.15",
30
- "circuit-json": "^0.0.433",
30
+ "circuit-json": "^0.0.507",
31
31
  "circuit-to-svg": "^0.0.353",
32
32
  "commander": "^13.1.0",
33
33
  "prettier": "^3.9.5",
34
34
  "shiki": "^4.3.1",
35
35
  "stack-svgs": "^0.0.1",
36
- "tscircuit": "^0.0.1873",
37
- "tsup": "^8.3.5"
36
+ "tscircuit": "^0.0.2696",
37
+ "tsup": "^8.3.5",
38
+ "zod": "^3.25.76"
38
39
  },
39
40
  "peerDependencies": {
40
41
  "typescript": "^5.0.0"
@@ -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,12 @@ export async function createTiEvmRoundtrip({
36
37
  generatedTscircuit,
37
38
  )) as CircuitJson
38
39
 
40
+ expect(
41
+ renderedCircuitJson.filter((element) => element.type === "pcb_via"),
42
+ ).toHaveLength(
43
+ sourceCircuitJson.filter((element) => element.type === "pcb_via").length,
44
+ )
45
+
39
46
  const sourcePcbSvg = convertCircuitJsonToPcbSvg(sourceCircuitJson, {
40
47
  matchBoardAspectRatio: true,
41
48
  })
@@ -0,0 +1,47 @@
1
+ import { expect, test } from "bun:test"
2
+ import { convertCircuitJsonToTscircuit } from "lib"
3
+ import { runTscircuitCode } from "tscircuit"
4
+
5
+ test("through-hole ports use only layers present in the converted board", async () => {
6
+ for (const num_layers of [2, 4, 6, 10]) {
7
+ const layers = [
8
+ "top",
9
+ ...Array.from({ length: num_layers - 2 }, (_, i) => `inner${i + 1}`),
10
+ "bottom",
11
+ ]
12
+ const circuitJson = [
13
+ {
14
+ type: "pcb_board",
15
+ pcb_board_id: "board",
16
+ center: { x: 0, y: 0 },
17
+ width: 10,
18
+ height: 10,
19
+ num_layers,
20
+ thickness: 1.6,
21
+ material: "fr4",
22
+ },
23
+ {
24
+ type: "pcb_plated_hole",
25
+ pcb_plated_hole_id: "hole",
26
+ pcb_component_id: "component",
27
+ shape: "circle",
28
+ x: 0,
29
+ y: 0,
30
+ outer_diameter: 2,
31
+ hole_diameter: 1,
32
+ layers,
33
+ port_hints: ["1"],
34
+ },
35
+ ] as any
36
+ const tsx = convertCircuitJsonToTscircuit(circuitJson, {
37
+ componentName: "LayerBoard",
38
+ })
39
+ const rendered = await runTscircuitCode(tsx)
40
+ const ports = rendered.filter((el) => el.type === "pcb_port")
41
+ expect(ports).toHaveLength(1)
42
+ expect(ports[0].layers.map(String).sort()).toEqual([...layers].sort())
43
+ expect(rendered.filter((el) => el.type === "pcb_board")[0].num_layers).toBe(
44
+ num_layers,
45
+ )
46
+ }
47
+ }, 15000)
@@ -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
+ })