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/check-lcd-pcb-fit.ts
// Checks supplied PCB body models under the repositioned display. Missing body
// models, screws, crimp wires and printing tolerances require physical fit checks.
import modeling from '@jscad/modeling'
import {executeJscadOperations} from 'jscad-planner'
import plan from '../lcd-model-plan.json'
const m=modeling as any
const c=await Bun.file('dist/index/circuit.json').json()
const names=new Map(c.filter((x:any)=>x.type==='source_component').map((x:any)=>[x.source_component_id,x.name]))
const pcbBodies=c.filter((x:any)=>x.type==='cad_component'&&x.pcb_component_id&&x.model_obj_url)
const lcd=plan.shapes.slice(0,49).map((p:any)=>m.transforms.translate([0,-7,.8],executeJscadOperations(m,p)))
const bounds=await Promise.all(pcbBodies.map(async (cad:any)=>{
const raw=await (async()=>{if(!cad.model_obj_url.startsWith('http'))return Bun.file(cad.model_obj_url).text();const r=await fetch(cad.model_obj_url);if(!r.ok)throw Error(`Model HTTP ${r.status}`);return r.text()})(),o=cad.model_origin_position??{x:0,y:0,z:0},a=cad.rotation.z*Math.PI/180,p=cad.position
const points=raw.split('\n').filter(l=>l.startsWith('v ')).map(l=>{
const v=l.trim().split(/\s+/).slice(1).map(Number),x=v[0]-o.x,y=v[1]-o.y
return [p.x+x*Math.cos(a)-y*Math.sin(a),p.y+x*Math.sin(a)+y*Math.cos(a),p.z+v[2]-o.z]
})
if(!points.length)throw Error('Empty OBJ '+names.get(cad.source_component_id))
const vertices=points
const polygons=raw.split('\n').filter(l=>l.startsWith('f ')).map(l=>m.geometries.poly3.create(l.trim().split(/\s+/).slice(1).map(s=>vertices[Number(s.split('/')[0])-1])))
return {geometry:m.geometries.geom3.create(polygons),name:names.get(cad.source_component_id),min:[0,1,2].map(i=>Math.min(...points.map(v=>v[i]))),max:[0,1,2].map(i=>Math.max(...points.map(v=>v[i])))}
}))
const collisions=[]
for(const b of bounds){
const box=m.primitives.cuboid({size:b.max.map((v,i)=>v-b.min[i]),center:b.max.map((v,i)=>(v+b.min[i])/2)})
for(let i=0;i<lcd.length;i++){
const lb=m.measurements.measureBoundingBox(lcd[i]);if([0,1,2].some(a=>b.min[a]>=lb[1][a]||b.max[a]<=lb[0][a]))continue
const volume=Math.abs(m.measurements.measureVolume(m.booleans.intersect(box,lcd[i])))
if(volume>1e-4){const actual=Math.abs(m.measurements.measureVolume(m.booleans.intersect(b.geometry,lcd[i])));if(actual>1e-4)collisions.push({component:b.name,lcdMesh:i,overlapMm3:actual})}
}
}
const report={status:collisions.length?'failed':'passed',method:'World-space PCB OBJ body envelopes, then exact mesh intersections versus 49 manufacturer LCD meshes',modeledBodies:bounds.length,bounds:bounds.map(({geometry,...b})=>b),collisions,limitation:'Unmodeled PCB parts, mounting hardware and cable bend radii need prototype fit verification.'}
await Bun.write('dist/enclosure/lcd-pcb-fit.json',JSON.stringify(report,null,2))
if(collisions.length)throw Error(JSON.stringify(collisions))
console.log(`Display clears all ${bounds.length} supplied PCB body models`)