AnasSarkiz/ble-pedometer
This code defines and configures various electronic hardware components such as surface-mount resistors, crystals, diodes, transistors, connectors, and switches with their physical footprints, schematic symbols, and 3D CAD models for PCB assembly.
- Version
- 1.0.7
- License
- unset
- Stars
- 0
lib/get-silkscreen-text-issues.ts
import type { AnyCircuitElement, PcbSilkscreenText } from "circuit-json"
// Core emits null ownership for board-level legends, which are not footprints.
type BoardSilkscreenText = Omit<PcbSilkscreenText, "pcb_component_id"> & { pcb_component_id?: string | null }
interface Bounds { minX: number; maxX: number; minY: number; maxY: number }
function centeredBounds(box: { x: number; y: number; width: number; height: number }): Bounds {
return { minX: box.x - box.width / 2, maxX: box.x + box.width / 2, minY: box.y - box.height / 2, maxY: box.y + box.height / 2 }
}
function pointBounds(points: { x: number; y: number }[]): Bounds {
if (!points.length) throw new Error("Cannot check empty geometry")
return { minX: Math.min(...points.map(p => p.x)), maxX: Math.max(...points.map(p => p.x)), minY: Math.min(...points.map(p => p.y)), maxY: Math.max(...points.map(p => p.y)) }
}
function overlaps(a: Bounds, b: Bounds) {
const clearanceMm = 0.15
return a.minX < b.maxX + clearanceMm && a.maxX > b.minX - clearanceMm && a.minY < b.maxY + clearanceMm && a.maxY > b.minY - clearanceMm
}
/** Conservative text envelopes, not a substitute for the assembler's CAM check. */
export function getSilkscreenTextIssues(circuitJson: (AnyCircuitElement | BoardSilkscreenText)[]) {
const obstacles: Bounds[] = []
for (const element of circuitJson) {
if (element.type === "pcb_courtyard_outline" && element.layer === "top") obstacles.push(pointBounds(element.outline))
if (element.type === "pcb_smtpad" && element.layer === "top") {
if (element.shape === "polygon") obstacles.push(pointBounds(element.points))
else if (element.shape === "circle") obstacles.push(centeredBounds({ ...element, width: element.radius * 2, height: element.radius * 2 }))
else if (element.shape === "rotated_rect") {
// A circumscribed square is conservative for every pad rotation.
const diameter = Math.hypot(element.width, element.height)
obstacles.push(centeredBounds({ ...element, width: diameter, height: diameter }))
} else if (element.shape === "rect") obstacles.push(centeredBounds(element))
else throw new Error(`Unsupported pad shape: ${element.shape}`)
}
if (element.type === "pcb_via") obstacles.push(centeredBounds({ ...element, width: element.outer_diameter, height: element.outer_diameter }))
if (element.type === "pcb_hole") {
if (element.hole_shape !== "circle") throw new Error("Unsupported hole shape")
obstacles.push(centeredBounds({ ...element, width: element.hole_diameter, height: element.hole_diameter }))
}
if (element.type === "pcb_plated_hole") {
const diameter = "outer_diameter" in element ? element.outer_diameter : "outer_width" in element ? Math.hypot(element.outer_width, element.outer_height) : undefined
if (diameter === undefined) throw new Error("Unsupported plated-hole shape")
obstacles.push(centeredBounds({ ...element, width: diameter, height: diameter }))
}
}
const issues: string[] = []
const labels: { text: string; bounds: Bounds }[] = []
for (const element of circuitJson) {
if (element.type !== "pcb_silkscreen_text" || element.layer !== "top") continue
if (element.ccw_rotation !== 0 || element.anchor_alignment !== "center") {
issues.push(`Unsupported label orientation/alignment: ${element.text}`)
continue
}
// Deliberately larger than the narrow tscircuit2024 glyphs; includes ink height.
const lines = element.text.split("\n")
const bounds = centeredBounds({ ...element.anchor_position, width: Math.max(...lines.map(line => line.length)) * element.font_size * 0.7, height: lines.length * element.font_size })
if (bounds.minX < -29.8 || bounds.maxX > 29.8 || bounds.minY < -18.8 || bounds.maxY > 18.8) issues.push(`Label outside board margin: ${element.text}`)
if (obstacles.some(obstacle => overlaps(bounds, obstacle))) issues.push(`Label overlaps pad, drill or courtyard: ${element.text}`)
for (const other of labels) if (overlaps(bounds, other.bounds)) issues.push(`Label overlap: ${element.text} / ${other.text}`)
labels.push({ text: element.text, bounds })
}
return issues
}