ShiboSoftwareDev/nema8-20mm-dual-usbc-stepper-controller
A compact PCB component set comprising a pushbutton, logic buffer, Schottky diodes, USB connector, resettable fuse, microcontroller, voltage regulator, and polarized capacitors.
- Version
- 1.0.35
- License
- unset
- Stars
- 0
scripts/find-free-via-sites.mjs
import fs from "node:fs"
const option = (name, fallback) => Number(
process.argv.find((arg) => arg.startsWith(`--${name}=`))?.split("=")[1] ?? fallback,
)
const step = option("step", 0.2)
const excludeTraceFrom = option("exclude-traces-from", 122)
const ignoredSourceIndex = option("ignore-source", -1)
const circuit = JSON.parse(fs.readFileSync("dist/index/circuit.json", "utf8"))
.filter((element) => !String(element.type).endsWith("error"))
const sourceIndex = (element) => {
const ids = `${element.source_trace_id ?? ""} ${element.pcb_trace_id ?? ""}`
.match(/source_trace_(\d+)/g) ?? []
return ids.map((id) => Number(id.slice("source_trace_".length)))
}
const accessoryTraceIds = new Set(circuit
.filter((element) => element.type === "pcb_trace" &&
sourceIndex(element).some((index) => index >= excludeTraceFrom))
.map((element) => element.pcb_trace_id))
const ignoredTraceIds = new Set(circuit
.filter((element) => element.type === "pcb_trace" &&
sourceIndex(element).includes(ignoredSourceIndex))
.map((element) => element.pcb_trace_id))
const base = circuit.filter((element) =>
!accessoryTraceIds.has(element.pcb_trace_id) &&
!ignoredTraceIds.has(element.pcb_trace_id) &&
!(element.type === "pcb_via" && (accessoryTraceIds.has(element.pcb_trace_id) ||
ignoredTraceIds.has(element.pcb_trace_id))))
const pointSegmentDistance = (px, py, ax, ay, bx, by) => {
const dx = bx - ax
const dy = by - ay
const lengthSquared = dx * dx + dy * dy
const t = lengthSquared === 0 ? 0 : Math.max(0, Math.min(1,
((px - ax) * dx + (py - ay) * dy) / lengthSquared))
return Math.hypot(px - (ax + t * dx), py - (ay + t * dy))
}
const pointRectDistance = (x, y, element) => {
const angle = -(((element.ccw_rotation ?? element.rotation ?? 0) * Math.PI) / 180)
const dx = x - element.x
const dy = y - element.y
const localX = dx * Math.cos(angle) - dy * Math.sin(angle)
const localY = dx * Math.sin(angle) + dy * Math.cos(angle)
const outsideX = Math.max(Math.abs(localX) -
(element.width ?? element.outer_width ?? element.outer_diameter ?? 0) / 2, 0)
const outsideY = Math.max(Math.abs(localY) -
(element.height ?? element.outer_height ?? element.outer_diameter ?? 0) / 2, 0)
return Math.hypot(outsideX, outsideY)
}
const fixedVias = base.filter((element) => element.type === "pcb_via")
const pads = base.filter((element) => ["pcb_smtpad", "pcb_plated_hole"].includes(element.type))
const holes = base.filter((element) => element.type === "pcb_hole")
const traceSegments = base.filter((element) => element.type === "pcb_trace").flatMap((trace) => {
const segments = []
let previous
for (const point of trace.route ?? []) {
if (point.route_type === "via") {
previous = undefined
continue
}
if (point.route_type !== "wire") continue
if (previous?.layer === point.layer) segments.push({
ax: previous.x, ay: previous.y, bx: point.x, by: point.y,
width: Math.max(previous.width ?? 0.15, point.width ?? 0.15),
pcb_trace_id: trace.pcb_trace_id,
})
previous = point
}
return segments
})
const viaRadius = 0.225
const blockers = (x, y) => {
const found = []
if (Math.abs(x) > 9.575 || Math.abs(y) > 9.575) found.push("board-edge")
for (const via of fixedVias) if (Math.hypot(x - via.x, y - via.y) <
viaRadius + (via.outer_diameter ?? 0.45) / 2 + 0.1) found.push(`via:${via.pcb_via_id}`)
for (const pad of pads) if (pointRectDistance(x, y, pad) < viaRadius + 0.1)
found.push(`pad:${pad.pcb_smtpad_id ?? pad.pcb_plated_hole_id}`)
for (const hole of holes) if (Math.hypot(x - hole.x, y - hole.y) <
viaRadius + (hole.diameter ?? hole.hole_diameter ?? 0) / 2 + 0.2) found.push(`hole:${hole.pcb_hole_id}`)
for (const segment of traceSegments) if (pointSegmentDistance(x, y,
segment.ax, segment.ay, segment.bx, segment.by) < viaRadius + segment.width / 2 + 0.1)
found.push(`trace:${segment.pcb_trace_id ?? "unknown"}`)
return found
}
const legal = []
const scored = []
for (let y = option("ymin", -9); y <= option("ymax", 9) + 1e-9; y += step) {
for (let x = option("xmin", -9); x <= option("xmax", 9) + 1e-9; x += step) {
const px = Number(x.toFixed(3))
const py = Number(y.toFixed(3))
const pointBlockers = blockers(px, py)
if (pointBlockers.length === 0) legal.push([px, py])
scored.push({ x: px, y: py, blockers: pointBlockers })
}
}
const bestCount = option("best", 0)
console.log(JSON.stringify({ count: legal.length, legal,
best: bestCount > 0 ? scored.sort((a, b) => a.blockers.length - b.blockers.length).slice(0, bestCount) : undefined }))