Skip to content
This repository has been archived by the owner on Jan 2, 2025. It is now read-only.

Commit

Permalink
Fix Gerber Exporting, add tests for gerber export (#185)
Browse files Browse the repository at this point in the history
* minor improvements to header menu

* add test for exporting gerbers for keyboard and simple chip board

* deeper testing

* formatbot: Automatically format code

* disable color output in tests

---------

Co-authored-by: tscircuitbot <[email protected]>
  • Loading branch information
seveibar and tscircuitbot authored Sep 16, 2024
1 parent bd2f94a commit cff12dc
Show file tree
Hide file tree
Showing 14 changed files with 153 additions and 32 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ package-lock.json
**/package-lock.json

.yalc
yalc.lock
yalc.lock
.DS_Store
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[test]
preload = ["./cli/tests/fixtures/preload.ts"]
11 changes: 0 additions & 11 deletions cli/lib/cmd-fns/dev/fulfill-export-requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,6 @@ export const fulfillExportRequests = async (

if (export_request.export_parameters.should_export_gerber_zip) {
console.log(kleur.gray(`\n exporting gerbers...`))
if (typeof Bun !== "undefined") {
const err_str =
"Bun currently isn't capable of exporting due to an archiver bug, exports will not work."
console.log(kleur.red(err_str))
await dev_server_axios.post("/api/export_requests/update", {
export_request_id: export_request.export_request_id,
has_error: true,
error: err_str,
})
return
}
const zip_buffer = await exportGerbersToZipBuffer(
{
example_file_path: export_request.example_file_path!,
Expand Down
10 changes: 8 additions & 2 deletions cli/lib/cmd-fns/export-gerbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ import { exportGerbersToFile } from "cli/lib/export-fns/export-gerbers"
export const exportGerbersCmd = async (ctx: AppContext, args: any) => {
const params = z
.object({
file: z.string(),
file: z.string().optional(),
input: z.string().optional(),
export: z.string().optional(),
outputfile: z.string().optional().default("gerbers.zip"),
})
.refine((data) => data.file || data.input, {
message: "Either 'file' or 'input' must be provided",
})
.parse(args)

const inputFile = params.input || params.file

await exportGerbersToFile(
{
example_file_path: params.file,
example_file_path: inputFile!,
export_name: params.export,
output_zip_path: params.outputfile,
},
Expand Down
7 changes: 1 addition & 6 deletions cli/lib/export-fns/export-gerbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,9 @@ export const exportGerbersToFile = async (
}

console.log(kleur.gray("[zipping tmp dir]..."))
console.log(kleur.gray(" writing to " + params.output_zip_path))
const output = fs.createWriteStream(params.output_zip_path)

if (typeof Bun !== "undefined") {
throw new Error(
`Exporting gerbers doesn't currently work with Bun (bug w/ archiver module)`,
)
}

const archive = archiver("zip", {
zlib: { level: 9 },
})
Expand Down
6 changes: 5 additions & 1 deletion cli/lib/get-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,11 @@ export const getProgram = (ctx: AppContext) => {
exportCmd
.command("gerbers")
.description("Export Gerber files from an example file")
.requiredOption("--file <file>", "Input example files")
.option(
"--file <file>",
"Input example file (deprecated, use --input instead)",
)
.option("--input <input>", "Input example file")
.option(
"--export <export_name>",
"Name of export to soupify, if not specified, soupify the default/only export",
Expand Down
5 changes: 5 additions & 0 deletions cli/lib/util/create-context-and-run-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,12 @@ export const createContextAndRunProgram = async (process_args: any) => {
params: args,
}

if (args["color"] === false) {
kleur.enabled = false
}

delete args["cwd"]
delete args["color"]

const { _: positional, ...flagsAndParams } = args
const args_without_globals = positional.concat(dargs(flagsAndParams))
Expand Down
16 changes: 16 additions & 0 deletions cli/tests/export-gerber-keyboard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { test, expect, describe } from "bun:test"
import { $ } from "bun"
import { temporaryDirectory } from "tempy"
import { join } from "path"
import { existsSync } from "fs"

test("tsci export gerbers --input example-project/examples/macrokeypad.tsx", async () => {
const tempDir = temporaryDirectory()
const { stdout, stderr } =
await $`bun cli/cli.ts export gerbers --input example-project/examples/macrokeypad.tsx --outputfile ${join(tempDir, "gerbers.zip")} --no-color`

expect(stderr.toString()).toBe("")
expect(stdout.toString()).toContain("gerbers.zip")

expect(existsSync(join(tempDir, "gerbers.zip"))).toBe(true)
})
49 changes: 49 additions & 0 deletions cli/tests/export-gerber.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { test, expect, describe } from "bun:test"
import { $ } from "bun"
import { temporaryDirectory } from "tempy"
import { join } from "path"
import { existsSync, readdirSync, readFileSync } from "fs"
import extract from "extract-zip"
import pcbStackup from "pcb-stackup"

test("tsci export gerbers --input example-project/examples/basic-chip.tsx", async () => {
const tempDir = temporaryDirectory()
const gerberPath = join(tempDir, "gerbers.zip")
const { stdout, stderr } =
await $`bun cli/cli.ts export gerbers --input example-project/examples/basic-chip.tsx --outputfile ${gerberPath} --no-color`

expect(stderr.toString()).toBe("")
expect(stdout.toString()).toContain("gerbers.zip")

expect(existsSync(join(tempDir, "gerbers.zip"))).toBe(true)

await extract(gerberPath, { dir: join(tempDir, "gerbers") })

const files = readdirSync(join(tempDir, "gerbers"))
const expectedFiles = [
"F_Mask.gbr",
"F_SilkScreen.gbr",
"B_Cu.gbr",
"plated.drl",
"B_SilkScreen.gbr",
"F_Cu.gbr",
"B_Paste.gbr",
"F_Paste.gbr",
"B_Mask.gbr",
"Edge_Cuts.gbr",
]

expectedFiles.forEach((file) => {
expect(files).toContain(file)
})

const gerberOutputMap = Object.entries(
files.map((filename) => [
filename,
readFileSync(join(tempDir, "gerbers", filename)),
]),
)

// Unfortunately doesn't work in bun yet due to some bug in node:stream
// expect(gerberOutputMap).toMatchGerberSnapshot(import.meta.path)
})
54 changes: 54 additions & 0 deletions cli/tests/fixtures/preload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import "bun-match-svg"
import { expect } from "bun:test"
import pcbStackup, { type Stackup } from "pcb-stackup"
import { Readable } from "stream"

async function toMatchGerberSnapshot(
this: any,
gerberOutput: Record<string, string>,
testPathOriginal: string,
svgName?: string,
) {
// Create layers array from gerberOutput
const layers = Object.entries(gerberOutput).map(([filename, content]) => ({
filename,
gerber: Readable.from(content),
}))

try {
const stackup = await pcbStackup(layers)
const svgArray: string[] = []
const svgNames: string[] = []

for (const item of Object.keys(stackup!) as Array<keyof Stackup>) {
const layer = stackup[item] as { svg: string }
if (layer.svg) {
svgArray.push(layer.svg)
svgNames.push(`${svgName}-${item}`)
}
}
return expect(svgArray).toMatchMultipleSvgSnapshots(
testPathOriginal,
svgNames,
)
} catch (error) {
throw new Error(`Failed to generate PCB stackup: ${error}`)
}
}

expect.extend({
// biome-ignore lint/suspicious/noExplicitAny:
toMatchGerberSnapshot: toMatchGerberSnapshot as any,
})

declare module "bun:test" {
interface Matchers<T = unknown> {
/**
* This doesn't currently work in bun, some bug in node:stream
*/
toMatchGerberSnapshot(
testImportMetaPath: string,
svgName?: string,
): Promise<MatcherResult>
}
}
9 changes: 2 additions & 7 deletions example-project/examples/basic-chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ import manual_edits from "../src/manual-edits"

export const BasicChip = () => (
<board pcbX={0} pcbY={0} width="20mm" height="20mm">
<group
subcircuit
layout={layout()
.autoLayoutSchematic()
.manualPcbPlacement(manual_edits.pcb_placements)}
>
<group subcircuit>
<chip
name="U2"
schPortArrangement={{
Expand All @@ -24,7 +19,7 @@ export const BasicChip = () => (
"4": "D+",
}}
/>
<resistor name="R1" resistance="10kohm" footprint="0805" />
<resistor name="R1" pcbX={4} resistance="10kohm" footprint="0805" />
<trace from=".U2 > .1" to=".R1 > port.1" />
</group>
</board>
Expand Down
4 changes: 2 additions & 2 deletions frontend/views/HeaderMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export const HeaderMenu = () => {
Pick'n'Place CSV
</MenubarItem>
<MenubarItem onSelect={() => bomExportDialog.openDialog()}>
Bill of Materials
Bill of Materials CSV
</MenubarItem>
<MenubarItem
onSelect={() => {
Expand All @@ -154,7 +154,7 @@ export const HeaderMenu = () => {
Schematic (PDF)
</MenubarItem>
<MenubarItem onSelect={() => soupExportDialog.openDialog()}>
tscircuit Soup JSON
Circuit JSON
</MenubarItem>
</MenubarSubContent>
</MenubarSub>
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"description": "Command line tool for developing, publishing and installing tscircuit circuits",
"main": "./dist/cli.js",
"scripts": {
"start": "bun cli/cli.ts",
"cli": "bun cli/cli.ts",
"start": "bun run dev",
"dev": "bun run build:dev-server && concurrently 'bun run dev:frontend' 'bun run dev:test-project'",
"dev:fast": "concurrently 'bun run dev:frontend' 'bun run dev:test-project'",
"dev:frontend": "vite dev --config frontend/vite.config.ts",
Expand Down Expand Up @@ -69,6 +70,7 @@
"prompts": "^2.4.2",
"redaxios": "^0.5.1",
"semver": "^7.6.0",
"tempy": "^3.1.0",
"ts-morph": "^22.0.0",
"tsup": "^8.0.2",
"winterspec": "0.0.86",
Expand All @@ -81,7 +83,7 @@
"@tscircuit/soup-util": "*"
},
"devDependencies": {
"@biomejs/biome": "^1.9.0",
"@biomejs/biome": "^1.9.1",
"@headlessui/react": "^1.7.18",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-context-menu": "^2.1.5",
Expand Down Expand Up @@ -119,11 +121,14 @@
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"ava": "^6.1.1",
"bun-match-svg": "^0.0.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^1.0.0",
"concurrently": "^8.2.2",
"extract-zip": "^2.0.1",
"make-vfs": "^1.0.10",
"pcb-stackup": "^4.2.8",
"postcss": "^8.4.41",
"react": "^18.2.0",
"react-data-grid": "7.0.0-beta.37",
Expand Down

0 comments on commit cff12dc

Please sign in to comment.