gokul/usb3-sd-hub
Engineering draft: four-layer, computer-powered USB 3 hub with one USB-C host socket, two USB-C and two USB-A accessory sockets, and a full-size SD slot. Board and three schematic previews are available. Routing and production checks are incomplete; not fabrication ready.
- Version
- 0.1.5
- License
- MIT
- Stars
- 0
verification/check-partial-routes.ts
PCB
Schematic
import { createHash } from "node:crypto"
import { readFileSync, writeFileSync } from "node:fs"
import { any_circuit_element, pcb_trace, type AnyCircuitElement } from "circuit-json"
import { checkPadTraceClearance, checkViaPadClearance, checkViaTraceClearance, checkDifferentNetViaSpacing, checkEachPcbTraceNonOverlapping, checkTracesAreContiguous, checkHoleTraceClearance, checkPcbTracesOutOfBoard, checkPcbTraceSelfShorts } from "@tscircuit/checks"
import { inspectPairSeparation, type TraceForAudit } from "./pair-measurements"
type RoutedTrace = TraceForAudit & { pcb_trace_id: string; connection_name: string }
type RoutingPhase = { traces: RoutedTrace[] }
const routingEvent: { simpleRouteJson: RoutingPhase } = JSON.parse(readFileSync(new URL("./accepted-phase-routes.json", import.meta.url), "utf8"))
const routingPhase = routingEvent.simpleRouteJson
const nativeCircuitJson: AnyCircuitElement[] = JSON.parse(readFileSync(new URL("./partial-routing.circuit.json", import.meta.url), "utf8"))
const circuitJson = nativeCircuitJson.filter((record) => record.type !== "pcb_trace" && !(record.type === "pcb_via" && record.pcb_trace_id))
const board = circuitJson.find((record) => record.type === "pcb_board")
if (!board || board.type !== "pcb_board") throw new Error("Board missing")
for (const [traceIndex, trace] of routingPhase.traces.entries()) {
const sourceNet = circuitJson.find((record) => record.type === "source_net" && record.source_net_id === trace.connection_name)
const sourceTrace = circuitJson.find((record) => record.type === "source_trace" && record.connected_source_net_ids?.includes(trace.connection_name))
if (sourceNet?.type !== "source_net" || sourceTrace?.type !== "source_trace") throw new Error("Native route provenance missing")
const pcbTrace = pcb_trace.parse({ type: "pcb_trace", pcb_trace_id: `pcb_trace_review_${traceIndex}`, source_trace_id: sourceTrace.source_trace_id, subcircuit_id: board.subcircuit_id, route: trace.route })
circuitJson.push(pcbTrace)
for (const [pointIndex, point] of pcbTrace.route.entries()) {
if (point.route_type !== "via") continue
circuitJson.push({ type: "pcb_via", pcb_via_id: `pcb_via_review_${traceIndex}_${pointIndex}`, pcb_trace_id: pcbTrace.pcb_trace_id, x: point.x, y: point.y, hole_diameter: point.via_hole_diameter ?? 0.2, outer_diameter: point.via_diameter ?? 0.45, layers: ["top", "inner1", "inner2", "bottom"], from_layer: point.from_layer, to_layer: point.to_layer, source_net_id: sourceNet.source_net_id, subcircuit_id: sourceNet.subcircuit_id, subcircuit_connectivity_map_key: sourceNet.subcircuit_connectivity_map_key })
}
}
const schemaErrors = circuitJson.filter((record) => !any_circuit_element.safeParse(record).success)
const checks = {
pad_trace_clearance: checkPadTraceClearance(circuitJson, { minClearance: 0.13 }),
via_pad_clearance: checkViaPadClearance(circuitJson, { minClearance: 0.13 }),
via_trace_clearance: checkViaTraceClearance(circuitJson, { minClearance: 0.13 }),
via_via_clearance: checkDifferentNetViaSpacing(circuitJson, { minClearance: 0.13 }),
trace_overlap: checkEachPcbTraceNonOverlapping(circuitJson, { minClearance: 0.13 }),
continuity: checkTracesAreContiguous(circuitJson),
hole_trace_clearance: checkHoleTraceClearance(circuitJson),
board_bounds: checkPcbTracesOutOfBoard(circuitJson),
self_shorts: checkPcbTraceSelfShorts(circuitJson),
}
const pairs = [["source_net_7","source_net_8"],["source_net_9","source_net_10"],["source_net_11","source_net_12"],["source_net_13","source_net_14"],["source_net_15","source_net_16"],["source_net_17","source_net_18"],["source_net_19","source_net_20"],["source_net_21","source_net_22"],["source_net_23","source_net_24"],["source_net_25","source_net_26"],["source_net_27","source_net_28"]].map(([positiveName,negativeName]) => {
const positive = routingPhase.traces.find((trace) => trace.connection_name === positiveName)
const negative = routingPhase.traces.find((trace) => trace.connection_name === negativeName)
if(!positive || !negative) throw new Error(`Pair missing: ${positiveName}/${negativeName}`)
const measurements = [inspectPairSeparation({trace:positive,pairedTrace:negative}),inspectPairSeparation({trace:negative,pairedTrace:positive})]
return {measurements,length_mismatch_mm:Math.abs(measurements[0].copper_length_mm-measurements[1].copper_length_mm)}
})
const completedSectionsGeometryPassed = schemaErrors.length === 0 && Object.values(checks).every(errors => errors.length === 0) && pairs.every(pair => pair.length_mismatch_mm <= 0.127 && pair.measurements.every(measurement => measurement.longest_potential_uncoupled_length_mm <= 2))
const report = { source_snapshot_sha256: createHash("sha256").update(readFileSync(new URL("./partial-routing.circuit.json",import.meta.url))).digest("hex"), completed_sections_geometry_passed: completedSectionsGeometryPassed, native_error_counts: nativeCircuitJson.filter(row=>row.type.includes("error")).reduce<Record<string,number>>((counts,row)=>{counts[row.type]=(counts[row.type]??0)+1;return counts},{}), schema_error_count: schemaErrors.length, schema_error_types: schemaErrors.map((record) => record.type), checks, pairs, full_board_routing_complete: false, fabrication_ready: false }
writeFileSync(new URL("./pair-geometry-check.json", import.meta.url), JSON.stringify(report, null, 2))
console.log(JSON.stringify(report, null, 2))
if(!completedSectionsGeometryPassed)process.exitCode=1