Skip to content

Commit 71d0566

Browse files
authored
Render true 3D board image for "3D" PNG exports instead of a flat SVG approximation (#3730)
1 parent 1040709 commit 71d0566

3 files changed

Lines changed: 53 additions & 42 deletions

File tree

bun.lock

Lines changed: 15 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,14 @@
119119
"@vercel/analytics": "^1.4.1",
120120
"@vitejs/plugin-react": "^4.3.1",
121121
"autoprefixer": "^10.4.20",
122+
"circuit-json-to-3d-png": "^0.0.6",
122123
"circuit-json-to-bom-csv": "^0.0.9",
123124
"circuit-json-to-gerber": "^0.0.51",
124125
"circuit-json-to-gltf": "^0.0.105",
125126
"circuit-json-to-kicad": "^0.0.153",
126127
"circuit-json-to-lbrn": "^0.0.23",
127128
"circuit-json-to-pnp-csv": "^0.0.7",
128129
"circuit-json-to-readable-netlist": "^0.0.13",
129-
"circuit-json-to-simple-3d": "^0.0.7",
130130
"circuit-json-to-spice": "^0.0.6",
131131
"circuit-json-to-step": "^0.0.20",
132132
"circuit-json-to-tscircuit": "^0.0.35",

src/lib/download-fns/download-circuit-png.ts

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AnyCircuitElement } from "circuit-json"
2-
import { convertCircuitJsonToSimple3dSvg } from "circuit-json-to-simple-3d"
2+
import { renderCircuitJsonTo3dPng } from "circuit-json-to-3d-png"
33
import {
44
convertCircuitJsonToAssemblySvg,
55
convertCircuitJsonToPcbSvg,
@@ -16,6 +16,18 @@ interface DownloadCircuitPngOptions {
1616
height?: number
1717
}
1818

19+
// SVG-based formats are rendered to an SVG string and then rasterized to PNG.
20+
// Anything not listed here (i.e. "3d") is rendered directly to PNG bytes.
21+
const SVG_RENDERERS: Record<
22+
string,
23+
(circuitJson: AnyCircuitElement[], options: any) => string
24+
> = {
25+
schematic: convertCircuitJsonToSchematicSvg,
26+
pcb: convertCircuitJsonToPcbSvg,
27+
assembly: convertCircuitJsonToAssemblySvg,
28+
pinout: convertCircuitJsonToPinoutSvg,
29+
}
30+
1931
const convertSvgToPng = async (svgString: string): Promise<Blob> => {
2032
return new Promise((resolve, reject) => {
2133
const canvas = document.createElement("canvas")
@@ -54,45 +66,36 @@ const convertSvgToPng = async (svgString: string): Promise<Blob> => {
5466
})
5567
}
5668

69+
const renderCircuitToPng = async (
70+
circuitJson: AnyCircuitElement[],
71+
options: DownloadCircuitPngOptions,
72+
): Promise<Blob> => {
73+
const renderSvg = SVG_RENDERERS[options.format.toLowerCase()]
74+
if (renderSvg) {
75+
const svgOptions: { width?: number; height?: number } = {}
76+
if (options.width) svgOptions.width = options.width
77+
if (options.height) svgOptions.height = options.height
78+
return convertSvgToPng(renderSvg(circuitJson, svgOptions))
79+
}
80+
81+
const pngBytes = await renderCircuitJsonTo3dPng(circuitJson, {
82+
cameraPreset: "top-left-corner",
83+
boardTextureResolution: 2048,
84+
})
85+
// Copy into a fresh ArrayBuffer: a Uint8Array view isn't assignable to BlobPart.
86+
const pngBuffer = new ArrayBuffer(pngBytes.byteLength)
87+
new Uint8Array(pngBuffer).set(pngBytes)
88+
return new Blob([pngBuffer], { type: "image/png" })
89+
}
90+
5791
export const downloadCircuitPng = async (
5892
circuitJson: AnyCircuitElement[],
5993
fileName: string,
6094
options: DownloadCircuitPngOptions = { format: "pcb" },
6195
) => {
6296
try {
63-
let blob: Blob
64-
let svg: string
65-
66-
const svgOptions: any = {}
67-
if (options.width) svgOptions.width = options.width
68-
if (options.height) svgOptions.height = options.height
69-
70-
switch (options.format.toLowerCase()) {
71-
case "schematic":
72-
svg = convertCircuitJsonToSchematicSvg(circuitJson, svgOptions)
73-
break
74-
case "pcb":
75-
svg = convertCircuitJsonToPcbSvg(circuitJson, svgOptions)
76-
break
77-
case "assembly":
78-
svg = convertCircuitJsonToAssemblySvg(circuitJson, svgOptions)
79-
break
80-
case "pinout":
81-
svg = convertCircuitJsonToPinoutSvg(circuitJson, svgOptions)
82-
break
83-
default:
84-
svg = await convertCircuitJsonToSimple3dSvg(circuitJson, {
85-
background: {
86-
color: "#fff",
87-
opacity: 0.0,
88-
},
89-
defaultZoomMultiplier: 1.1,
90-
})
91-
}
92-
93-
blob = await convertSvgToPng(svg)
94-
const downloadFileName = `${fileName}_${options.format}.png`
95-
saveAs(blob, downloadFileName)
97+
const blob = await renderCircuitToPng(circuitJson, options)
98+
saveAs(blob, `${fileName}_${options.format}.png`)
9699
} catch (error) {
97100
console.error(error)
98101
throw new Error(`Failed to download ${options.format} PNG: ${error}`)

0 commit comments

Comments
 (0)