AnasSarkiz/muse-voice-remote
Muse Voice Remote — bare ESP32-S3R8, 155 TOP JLCPCB parts with real supplier CAD, native 3D assembly and GLB exports. Revised 17 mm case allocation; 0.30 mm via drill /0.45 mm pad. Checked prototype PCB; mechanical, RF and MUSE qualification pending.
- Version
- 1.3.2
- License
- unset
- Stars
- 0
scripts/preserve-jlcpcb-vias.ts
// Thermal VIAs supplied inside an exposed pad are footprint geometry. The
// upstream JSX exporter emits standalone unconnected <via> components instead.
// Retain coordinates/drills/copper and bind the plated holes to the owning pin.
export function preserveThermalVias(tsx: string, circuit: Record<string, any>[]) {
const vias = circuit.filter(e=>e.type==="pcb_via")
const bindings = vias.map(via=>{
const radius = via.outer_diameter/2
const owners = circuit.filter(p=>p.type==="pcb_smtpad" && p.shape==="rect" &&
Math.abs(via.x-p.x)+radius<=p.width/2 && Math.abs(via.y-p.y)+radius<=p.height/2)
if (owners.length!==1) throw new Error("Cannot identify supplier thermal-via pad; do not drop or redraw it.")
const pin = owners[0].port_hints?.find((hint:string)=>/^pin\d+$/.test(hint))
if (!pin) throw new Error("Supplier thermal pad lacks a physical pin binding.")
return {via,pad:owners[0],pin}
})
const converted = tsx.replace(/<via\s+[^>]+\/>/g,tag=>{
const x=Number.parseFloat(tag.match(/pcbX="([^"]+)"/)?.[1]??"NaN")
const y=Number.parseFloat(tag.match(/pcbY="([^"]+)"/)?.[1]??"NaN")
const binding=bindings.find(b=>Math.hypot(b.via.x-x,b.via.y-y)<0.00001)
if (!binding) throw new Error("Generated via does not match retained supplier geometry.")
return tag.replace(/^<via/,`<platedhole portHints={["${binding.pin}"]} shape="circle"`)
.replace(/\s+layers=\{\[[^\]]+\]\}/,"")
})
const convertedCircuit=circuit.map(e=>{
const binding=bindings.find(b=>b.via===e)
if (!binding) return e
return {type:"pcb_plated_hole",pcb_plated_hole_id:`supplier_thermal_${e.pcb_via_id}`,
pcb_component_id:binding.pad.pcb_component_id,pcb_port_id:binding.pad.pcb_port_id,
shape:"circle",x:e.x,y:e.y,outer_diameter:e.outer_diameter,hole_diameter:e.hole_diameter,
port_hints:[binding.pin],layers:["top","bottom"]}
})
return {tsx:converted,circuit:convertedCircuit,bindings:bindings.map(b=>({
pin:b.pin,x:b.via.x,y:b.via.y,outerDiameter:b.via.outer_diameter,holeDiameter:b.via.hole_diameter}))}
}