Devesh36/pocket-audio-player
A portable, rechargeable microSD audio player built around an RP2040, SPI flash, USB‑C charging/power management, LCD/button controls, PCM5102A I²S DAC, and TPA6132A headphone amplifier in a 3D‑printed enclosure.
- Version
- 1.0.12
- License
- unset
- Stars
- 0
scripts/export-enclosure.tsx
import { renderToJscadPlan } from "jscad-fiber/headless"
import { executeJscadOperations, resolveReferencePlanes } from "jscad-planner"
import modeling from "@jscad/modeling"
import { CaseBase, CaseLid, ButtonPlunger, RingButton, caseDimensions } from "../enclosure"
const m = modeling as any
const parts = [
["case-base", <CaseBase />], ["case-lid", <CaseLid />],
["button-plunger", <ButtonPlunger />], ["ring-button", <RingButton />],
] as const
const measurements: any[] = []
for (const [name, jsx] of parts) {
const { geometry } = resolveReferencePlanes(renderToJscadPlan(jsx))
const solid = executeJscadOperations(m, geometry)
const bounds = m.measurements.measureBoundingBox(solid)
const volume = m.measurements.measureVolume(solid)
if (!(volume > 0)) throw new Error(`${name} has no printable volume`)
// Normalize each print to Z=0. Lid/plunger are inverted for their flat face.
let print = name === "case-base" ? solid : m.transforms.rotateX(Math.PI, solid)
const pb = m.measurements.measureBoundingBox(print)
print = m.transforms.translate([0, 0, -pb[0][2]], print)
const polygons = m.geometries.geom3.toPolygons(m.modifiers.generalize({snap:false}, print))
// CSG polygons can have T junctions. Preserve every boundary vertex and use
// a face-centre fan, so adjacent STL faces share identical subdivided edges.
const weld=(p:number[])=>p.map(n=>Math.round(n*1e5)/1e5)
const allVertices=[...new Map<string,number[]>(polygons.flatMap((p:any)=>p.vertices.map((v:number[])=>{const w=weld(v);return [w.join(","),w] as [string,number[]]}))).values()]
const lines = [`solid ${name}`]
for (const polygon of polygons) {
const original=polygon.vertices.map(weld), boundary:number[][]=[]
for(let i=0;i<original.length;i++){
const a=original[i],b=original[(i+1)%original.length],d=b.map((n:number,j:number)=>n-a[j]),length2=d.reduce((q:number,n:number)=>q+n*n,0)
if(length2<1e-12)continue
const onEdge=allVertices.map(v=>({v,t:v.reduce((q,n,j)=>q+(n-a[j])*d[j],0)/length2})).filter(({v,t})=>t>=-1e-8&&t<1-1e-8&&Math.hypot(...v.map((n,j)=>n-a[j]-t*d[j]))<0.00003).sort((a,b)=>a.t-b.t)
boundary.push(...onEdge.map(p=>p.v))
}
const centre=weld([0,1,2].map(j=>boundary.reduce((q,v)=>q+v[j],0)/boundary.length))
for(let i=0;i<boundary.length;i++){
const a=centre,b=boundary[i],c=boundary[(i+1)%boundary.length]
const u=b.map((n:number,j:number)=>n-a[j]),w=c.map((n:number,j:number)=>n-a[j])
const n=[u[1]*w[2]-u[2]*w[1],u[2]*w[0]-u[0]*w[2],u[0]*w[1]-u[1]*w[0]],norm=Math.hypot(...n)
if(norm<1e-10)continue
lines.push(`facet normal ${n.map(k=>k/norm).join(" ")}`,"outer loop",... [a,b,c].map(p=>`vertex ${p.join(" ")}`),"endloop","endfacet")
}
}
lines.push(`endsolid ${name}`)
await Bun.write(`dist/enclosure/${name}.stl`, lines.join("\n"))
await Bun.write(`dist/enclosure/${name}.jscad.json`, JSON.stringify(geometry,null,2))
measurements.push({name, bounds, volumeMm3:volume, printBounds:m.measurements.measureBoundingBox(print), triangles:lines.filter(l=>l.startsWith("facet ")).length})
}
await Bun.write("dist/enclosure/dimensions.json", JSON.stringify({caseDimensions,parts:measurements},null,2))
console.log(JSON.stringify(measurements,null,2))