Skip to content

Commit

Permalink
Merge pull request #16 from tscircuit/circuit-json-export
Browse files Browse the repository at this point in the history
circuit json export
  • Loading branch information
seveibar authored Jan 7, 2025
2 parents 9fc51f1 + d83188a commit db4381c
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 1 deletion.
Binary file modified bun.lockb
Binary file not shown.
90 changes: 90 additions & 0 deletions cli/export/register.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Command } from "commander"
import { createCircuitWebWorker } from "@tscircuit/eval-webworker"
import webWorkerBundleUrl from "@tscircuit/eval-webworker/blob-url"
import { getVirtualFileSystemFromDirPath } from "make-vfs"
import path from "node:path"
import fs from "node:fs"

const ALLOWED_FORMATS = [
"json",
"circuit-json",
"schematic-svg",
"pcb-svg",
"gerbers",
"readable-netlist",
"gltf",
"specctra-dsn",
] as const

type Format = (typeof ALLOWED_FORMATS)[number]

const OUTPUT_EXTENSIONS = {
json: ".circuit.json",
"circuit-json": ".circuit.json",
"schematic-svg": "-schematic.svg",
"pcb-svg": "-pcb.svg",
gerbers: "-gerbers.zip",
"readable-netlist": "-readable.netlist",
gltf: ".gltf",
"specctra-dsn": ".dsn",
}

export const registerExport = (program: Command) => {
program
.command("export")
.description("Export tscircuit code to various formats")
.argument("<file>", "Path to the snippet file")
.option("-f, --format <format>", "Output format")
.option("-o, --output <path>", "Output file path")
.action(async (file, options) => {
const { format = "circuit-json" } = options
let { output } = options
if (!ALLOWED_FORMATS.includes(format)) {
throw new Error(
`Invalid format: ${format}\nSupported formats: ${ALLOWED_FORMATS.join(",")}`,
)
}

if (!output) {
output = path.basename(file).replace(/\.[^.]+$/, "")
}

const worker = await createCircuitWebWorker({
webWorkerUrl: webWorkerBundleUrl,
})

const projectDir = path.dirname(file)

const relativeComponentPath = path.relative(projectDir, file)

await worker.executeWithFsMap({
entrypoint: "entrypoint.tsx",
fsMap: {
...((await getVirtualFileSystemFromDirPath({
dirPath: projectDir,
contentFormat: "string",
})) as Record<string, string>),
"entrypoint.tsx": `
import MyCircuit from "./${relativeComponentPath}"
circuit.add(<MyCircuit />)
`,
},
})

await worker.renderUntilSettled()

const circuitJson = await worker.getCircuitJson()

const outputPath = path.join(
projectDir,
`${output}${OUTPUT_EXTENSIONS[format as Format]}`,
)

fs.writeFileSync(outputPath, JSON.stringify(circuitJson))

console.log(`Exported to ${outputPath}`)

process.exit(0)
})
}
3 changes: 3 additions & 0 deletions cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { registerClone } from "./clone/register"
import { perfectCli } from "perfect-cli"
import pkg from "../package.json"
import semver from "semver"
import { registerExport } from "./export/register"

const program = new Command()

Expand All @@ -30,6 +31,8 @@ registerAuthLogout(program)
registerConfig(program)
registerConfigPrint(program)

registerExport(program)

if (process.argv.length === 2) {
perfectCli(program, process.argv)
} else {
Expand Down
File renamed without changes.
174 changes: 174 additions & 0 deletions example-dir/snippet2-large-led-matrix.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { useHS91L02W2C01 } from "@tsci/seveibar.HS91L02W2C01"
import manualEdits from "./manual-edits.json"
import { WS2812B_2020 as LedWithIc } from "@tsci/seveibar.WS2812B_2020"
import { usePICO_W } from "@tsci/seveibar.PICO_W"

type Point = { x: number; y: number }

type GridCellPositions = {
index: number
center: Point
topLeft: Point
bottomRight: Point
}

type GridOptions = {
rows: number
cols: number
xSpacing?: number
ySpacing?: number
width?: number
height?: number
offsetX?: number
offsetY?: number
yDirection?: "cartesian" | "up-is-negative"
}

// ToDO import from tscircuit utils in the future
function grid({
rows,
cols,
xSpacing,
ySpacing,
width,
height,
offsetX = 0,
offsetY = 0,
yDirection = "cartesian",
}: GridOptions): GridCellPositions[] {
// Calculate cell dimensions
const cellWidth = width ? width / cols : (xSpacing ?? 1)
const cellHeight = height ? height / rows : (ySpacing ?? 1)

const cells: GridCellPositions[] = []

for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const index = row * cols + col

// Calculate center position
const centerX = offsetX + col * cellWidth + cellWidth / 2
const rawCenterY = offsetY + row * cellHeight + cellHeight / 2

// Adjust Y coordinate based on yDirection
const centerY =
yDirection === "cartesian"
? offsetY + (rows - 1 - row) * cellHeight + cellHeight / 2
: rawCenterY

cells.push({
row,
col,
index,
center: { x: centerX, y: centerY },
topLeft: {
x: centerX - cellWidth / 2,
y: centerY + cellHeight / 2,
},
bottomRight: {
x: centerX + cellWidth / 2,
y: centerY - cellHeight / 2,
},
})
}
}

return cells
}

export default () => {
const U1 = usePICO_W("U1")
const U2 = useHS91L02W2C01("U2")
return (
<board
width="316mm"
height="52mm"
manualEdits={manualEdits}
routingDisabled
>
<U1 pcbRotation="90deg" pcbX={-122 - 15} pcbY={0} />
<U2
GND="net.GND"
VCC={"net.V5"}
SDA={U1.GP26_ADC0_I2C1SDA}
SCL={U1.GP27_ADC1_I2C1SCL}
schX={-7}
schY={0}
pcbX={-122 + 5}
pcbY={19}
/>
{grid({
cols: 53,
rows: 7,
xSpacing: 5,
ySpacing: 5,
offsetX: 3 - 122,
offsetY: -32 / 2 - 7.5,
}).map(({ center, index, row, col }) => {
const ledName = `LED${index + 1}`
const prevLedName = index > 0 ? `LED${index}` : null
const capName = `C_${ledName}`
const ledSchX = ((center.x / 2) * 8) / 5 + 2 + 101
const ledSchY = 5 + center.y / 1.5
return (
<>
<LedWithIc
schX={ledSchX}
schY={ledSchY}
name={ledName}
pcbX={center.x}
pcbY={center.y}
/>
<trace from={`.${ledName} .GND`} to="net.GND" />
<trace from={`.${ledName} .VDD`} to="net.V5" />
{prevLedName && (
<trace from={`.${prevLedName} .DO`} to={`.${ledName} .DI`} />
)}
<capacitor
name={capName}
footprint="0402"
capacitance="100nF"
pcbX={center.x}
pcbY={center.y - 2.2}
pcbRotation="180deg"
schX={ledSchX}
schY={ledSchY - 1.1}
schRotation="180deg"
/>
<trace from={`.${capName} .neg`} to="net.GND" />
<trace from={`.${capName} .pos`} to="net.V5" />
</>
)
})}

<capacitor
name="C1"
capacitance="100uF"
footprint="1206"
schX={5}
pcbX={-122}
pcbRotation="90deg"
schRotation="270deg"
/>
<trace from=".C1 .neg" to="net.GND" />
<trace from=".C1 .pos" to="net.V5" />

<trace from=".LED1 .DI" to={U1.GP11_SPI1TX_I2C1SCL} />
<trace from={U1.GND1} to="net.GND" />
<trace from={U1.GND2} to="net.GND" />
<trace from={U1.GND3} to="net.GND" />
<trace from={U1.GND4} to="net.GND" />
<trace from={U1.GND5} to="net.GND" />
<trace from={U1.GND6} to="net.GND" />
<trace from={U1.GND7} to="net.GND" />

<trace from={U1.VBUS} to="net.V5" />
<footprint>
<hole pcbX={-32 - 122} pcbY={20} diameter="4.8mm" />
<hole pcbX={-32 - 122} pcbY={-20} diameter="4.8mm" />
<hole pcbX={32 + 122} pcbY={20} diameter="4.8mm" />
<hole pcbX={32 + 122} pcbY={-20} diameter="4.8mm" />
</footprint>
</board>
)
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"cosmiconfig": "^9.0.0",
"delay": "^6.0.0",
"ky": "^1.7.4",
"make-vfs": "^1.0.15",
"perfect-cli": "^1.0.20",
"semver": "^7.6.3"
}
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
},
"exclude": ["example-dir"]
}

0 comments on commit db4381c

Please sign in to comment.