Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"workspaces": {
"": {
"name": "@tscircuit/runframe",
"dependencies": {
"circuit-json-to-kicad": "^0.0.3",
"kicadts": "^0.0.9",
},
Comment on lines +6 to +9
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears there's a discrepancy between the PR description and implementation. The PR description states these KiCad conversion packages should be moved to devDependencies, but they're being added to dependencies in this diff. These packages should be placed in the devDependencies section instead to align with the stated purpose of the PR.

Suggested change
"dependencies": {
"circuit-json-to-kicad": "^0.0.3",
"kicadts": "^0.0.9",
},
"dependencies": {
},
"devDependencies": {
"circuit-json-to-kicad": "^0.0.3",
"kicadts": "^0.0.9",
},

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

"devDependencies": {
"@babel/standalone": "^7.26.10",
"@biomejs/biome": "^1.9.4",
Expand Down Expand Up @@ -878,6 +882,8 @@

"circuit-json-to-gltf": ["[email protected]", "", { "peerDependencies": { "@resvg/resvg-js": "2", "@resvg/resvg-wasm": "2", "@tscircuit/circuit-json-util": "*", "circuit-json": "*", "circuit-to-svg": "*", "typescript": "^5" }, "optionalPeers": ["@resvg/resvg-wasm"] }, "sha512-MbTAxzqgcfKho0veYceEM2LamAmIqax1rG2cnR6MhnEowDLFPO8Sd2qoXStqDbWADXQpIglJ0NdICTu8igUvFg=="],

"circuit-json-to-kicad": ["[email protected]", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-WrX1rUaRGOdsitKBk/fU9wgwpzxv7iZpVf7ORTWMUkui1iZ3gbXUNGAgKgrH0390SWFm0zGEiHlkLEqxWgu7ww=="],

"circuit-json-to-pnp-csv": ["[email protected]", "", { "dependencies": { "papaparse": "^5.4.1" }, "peerDependencies": { "@tscircuit/soup-util": "*", "typescript": "^5.0.0" } }, "sha512-WAdNRHtaPhQM8X5NN/43WMBKDCEKQSLShg/mHIZxMUzJviymnfbq6rJj/2WvDqm/bogey34PyTEHwF3mC7zxlQ=="],

"circuit-json-to-readable-netlist": ["[email protected]", "", { "peerDependencies": { "@tscircuit/circuit-json-util": "*", "circuit-json": "*", "circuit-json-to-connectivity-map": "*", "typescript": "^5.0.0" } }, "sha512-Wwk/PdEuqKTQM8HRNVzohgcVpicSRkfcUW+eeycb4ZUaJNunGFEEpBkGHPguIcuG9RJBQrGp3XfBVoBUXeBmWQ=="],
Expand Down Expand Up @@ -1392,6 +1398,8 @@

"kicad-converter": ["[email protected]", "", { "dependencies": { "@tscircuit/soup-util": "^0.0.30", "circuit-json-to-connectivity-map": "^0.0.16" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-3beH+cL75SLhuvpeYp7inQtpWq9yZp85v2SuvgN2XhDD492nEc/N5Jf1z5eAgUuL/8VpjCj/zZtk7FpSCb3+Wg=="],

"kicadts": ["[email protected]", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-IfMcQxZ6+sTFLynI+5htPPZPeLCDHNMcMqkJFe9YQg+z2swattZK8KQAn4u0auhzNtwVqy+3MmzPV7MBstrrrQ=="],

"kind-of": ["[email protected]", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="],

"kleur": ["[email protected]", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
Expand Down
7 changes: 6 additions & 1 deletion lib/optional-features/exporting/export-and-download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import type { CircuitJson } from "circuit-json"
import { exportFabricationFiles } from "./formats/export-fabrication-files"
import { openForDownload } from "./open-for-download"
import { exportGlb } from "./formats/export-glb"
import { exportKicadProject } from "./formats/export-kicad-project"

export const availableExports = [
{ extension: "json", name: "Circuit JSON" },
{ extension: "zip", name: "Fabrication Files" },
{ extension: "zip", name: "KiCad Project" },
{ extension: "glb", name: "GLB (Binary GLTF)" },
// { extension: "svg", name: "SVG" },
// { extension: "dsn", name: "Specctra DSN" },
// { extension: "kicad_mod", name: "KiCad Module" },
// { extension: "kicad_project", name: "KiCad Project" },
// { extension: "gbr", name: "Gerbers" },
] as const satisfies Array<{ extension: string; name: string }>

Expand All @@ -29,6 +30,10 @@ export const exportAndDownload = async ({
exportFabricationFiles({ circuitJson, projectName })
return
}
if (exportName === "KiCad Project") {
await exportKicadProject({ circuitJson, projectName })
return
}
if (exportName === "Circuit JSON") {
openForDownload(JSON.stringify(circuitJson, null, 2), {
fileName: `${projectName}.circuit.json`,
Expand Down
45 changes: 45 additions & 0 deletions lib/optional-features/exporting/formats/export-kicad-project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { CircuitJson } from "circuit-json"
import JSZip from "jszip"
import {
CircuitJsonToKicadPcbConverter,
CircuitJsonToKicadSchConverter,
} from "circuit-json-to-kicad"
import { openForDownload } from "../open-for-download"

export const createKicadProjectZip = async ({
circuitJson,
projectName,
}: {
circuitJson: CircuitJson
projectName: string
}) => {
const schConverter = new CircuitJsonToKicadSchConverter(circuitJson as any)
schConverter.runUntilFinished()
const schContent = schConverter.getOutputString()

const pcbConverter = new CircuitJsonToKicadPcbConverter(circuitJson as any)
pcbConverter.runUntilFinished()
const pcbContent = pcbConverter.getOutputString()

const zip = new JSZip()

zip.file(`${projectName}.kicad_sch`, schContent)
zip.file(`${projectName}.kicad_pcb`, pcbContent)

return zip
}

export const exportKicadProject = async ({
circuitJson,
projectName,
}: {
circuitJson: CircuitJson
projectName: string
}) => {
const zip = await createKicadProjectZip({ circuitJson, projectName })
const zipBlob = await zip.generateAsync({ type: "blob" })

openForDownload(zipBlob, {
fileName: `${projectName}_kicad_project.zip`,
})
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"circuit-json-to-bom-csv": "^0.0.8",
"circuit-json-to-gerber": "^0.0.34",
"circuit-json-to-gltf": "^0.0.7",
"circuit-json-to-kicad": "^0.0.3",
"circuit-json-to-pnp-csv": "^0.0.7",
"circuit-to-svg": "^0.0.205",
"class-variance-authority": "^0.7.1",
Expand All @@ -81,6 +82,7 @@
"fuse.js": "^7.1.0",
"jszip": "^3.10.1",
"kicad-component-converter": "^0.1.21",
"kicadts": "^0.0.9",
"ky": "^1.7.5",
"lucide-react": "^0.488.0",
"marked": "^15.0.12",
Expand Down
52 changes: 52 additions & 0 deletions tests/export-kicad-project.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { expect, test } from "bun:test"
import { createCircuitWebWorker } from "@tscircuit/eval"
import evalWebWorkerBlobUrl from "@tscircuit/eval/blob-url"
import { createKicadProjectZip } from "lib/optional-features/exporting/formats/export-kicad-project"

const createSimpleCircuitJson = async () => {
const worker = await createCircuitWebWorker({
webWorkerUrl: evalWebWorkerBlobUrl,
verbose: true,
})

await worker.executeWithFsMap({
fsMap: {
"main.tsx": `
circuit.add(
<board width="10mm" height="10mm">
<resistor name="R1" resistance="1k" footprint="0402" />
<capacitor name="C1" capacitance="1uF" footprint="0603" />
<trace from=".R1 .pin1" to=".C1 .pin1" />
</board>
)
`,
},
entrypoint: "main.tsx",
})

await worker.renderUntilSettled()
const circuitJson = await worker.getCircuitJson()

return circuitJson
}

test("createKicadProjectZip generates KiCad schematic and PCB files", async () => {
const circuitJson = await createSimpleCircuitJson()
const projectName = "test-project"

const zip = await createKicadProjectZip({ circuitJson, projectName })

const schFile = zip.file(`${projectName}.kicad_sch`)
const pcbFile = zip.file(`${projectName}.kicad_pcb`)

expect(schFile).toBeDefined()
expect(pcbFile).toBeDefined()

const schContent = await schFile?.async("string")
const pcbContent = await pcbFile?.async("string")

expect(schContent).toBeTruthy()
expect(pcbContent).toBeTruthy()
expect(schContent).toContain("(kicad_sch")
expect(pcbContent).toContain("(kicad_pcb")
})
Loading