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/build.tsx

import { mkdir, writeFile, copyFile } from "node:fs/promises"
import { Circuit } from "@tscircuit/core"
import { convertCircuitJsonToPcbSvg, convertCircuitJsonToSchematicSvg } from "circuit-to-svg"
import { Resvg } from "@resvg/resvg-js"
import MuseVoiceRemote from "../MuseVoiceRemote.circuit"
import { parts, reviewBlockers } from "../design/parts"
import { assembly } from "../design/assembly"
import { createReviewHtml, createAssemblySvg } from "./review"
import { addVerifiedCopper } from "./routed-overlay"
import { catalogFor } from "../design/catalog"

await mkdir("dist", { recursive: true })
const circuit = new Circuit()
// Placement previews need PCB/source checks, not repeated schematic label packing.
// Normal builds and all published artifacts still render the full schematic.
circuit.schematicDisabled = process.env.MUSE_ROUTE === "preview"
circuit.add(<MuseVoiceRemote />)
await circuit.renderUntilSettled()
const json = circuit.getCircuitJson()
if (!process.env.MUSE_ROUTE) addVerifiedCopper(json)
const diagnostics = json.filter(e => /error|warning/.test(e.type))
const pcb = convertCircuitJsonToPcbSvg(json, { width: 1600, height: 1150, layer: "top",
  showPcbNotes: true, showFabricationNotes: false, showSolderMask: true, showPinNumbers: true, drawPaddingOutsideBoard: true,
  includeVersion: false })
const schematic = convertCircuitJsonToSchematicSvg(json, { width: 2200, height: 1800 })
const topAssembly = createAssemblySvg()
const artifacts: Record<string, string> = {
  "circuit.json": JSON.stringify(json, null, 2),
  "diagnostics.json": JSON.stringify(diagnostics, null, 2),
  "pcb-top.svg": pcb, "pcb.svg": pcb, "schematic.svg": schematic, "assembly-top.svg": topAssembly,
  "review.html": createReviewHtml(pcb, schematic, topAssembly),
  "bom.csv": "Reference,Value,MPN,Imported_Library,Layer,PCB_X_mm,PCB_Y_mm,Rotation_deg,Qualification\n" + parts.filter(p=>catalogFor(p)).map(p =>
    [p.name,p.value,catalogFor(p)!.mpn,catalogFor(p)!.library,"top",p.x,p.y,p.rotation ?? 0,p.status ?? "Stocked catalog part; electrical and enclosure qualification pending"].map(v => '"'+String(v).replaceAll('"','""')+'"').join(",")).join("\n") + "\n",
  "placement.json": JSON.stringify({ board: assembly, parts, reviewBlockers }, null, 2),
}
await Promise.all(Object.entries(artifacts).map(([name, content]) => writeFile(`dist/${name}`, content)))
// Registry previews must use the final verified copper, rather than rerender
// the source component without the native routing overlay.
await mkdir("dist/index", { recursive: true })
await Promise.all([
  writeFile("circuit.json", artifacts["circuit.json"]),
  writeFile("dist/index/circuit.json", artifacts["circuit.json"]),
])
if (!process.env.MUSE_ROUTE) await Promise.all([
  copyFile("hardware/MuseVoiceRemote.kicad_pcb","dist/MuseVoiceRemote.kicad_pcb"),
  copyFile("hardware/MuseVoiceRemote.kicad_pro","dist/MuseVoiceRemote.kicad_pro"),
  copyFile("hardware/drc-report.txt","dist/native-drc.txt"),
])
await writeFile("dist/assembly-top.png", new Resvg(topAssembly).render().asPng())
await writeFile("dist/pcb-top.png", new Resvg(pcb).render().asPng())
const fatal = diagnostics.filter(e => e.type.endsWith("_error"))
console.log(JSON.stringify({ components: json.filter(e=>e.type==="source_component").length,
  sourceTraces: json.filter(e=>e.type==="source_trace").length,
  pcbComponents: json.filter(e=>e.type==="pcb_component").length,
  copperRouting: process.env.MUSE_ROUTE === "1" ? "tscircuit attempt" : process.env.MUSE_ROUTE ? "unrouted placement preview" : "verified native FreeRouting copper overlay; native CAD is authoritative",
  copperSegments:json.filter(e=>e.type==="pcb_trace").length, diagnostics: diagnostics.length,
  artifacts: Object.keys(artifacts), fatalConnectivityErrors: fatal.length }, null, 2))
if (fatal.length) process.exitCode = 1