0hmX/robocraze-ov7670-camera-module-clone
This code defines and generates a 4-layer PCB hardware layout featuring an OV7670 VGA camera sensor module with fixed component placement, power regulation using ME6211 voltage regulators, a 2x9 header interface, and associated signal routing and mechanical footprints, designed for manufacturing and assembly.
- Version
- 1.0.7
- License
- MIT
- Stars
- 0
scripts/render-bottom-placement.ts
export {}
type CircuitElement = {
type: string
source_component_id?: string
name?: string
center?: { x: number; y: number }
layer?: string
rotation?: number
do_not_place?: boolean
}
const [inputPath, outputPath, title = "Bottom placement"] = Bun.argv.slice(2)
if (!inputPath || !outputPath) {
throw new Error("usage: render-bottom-placement.ts <circuit.json> <output.svg> [title]")
}
const elements = (await Bun.file(inputPath).json()) as CircuitElement[]
const names = new Map(
elements
.filter((element) => element.type === "source_component")
.map((element) => [element.source_component_id, element.name]),
)
const components = elements.filter(
(element) => element.type === "pcb_component" && element.layer === "bottom",
)
const board = { width: 35.16, height: 34.29 }
const scale = 30
const margin = 76
const px = (x: number) => margin + (x + board.width / 2) * scale
const py = (y: number) => margin + (board.height / 2 - y) * scale
const esc = (value: string) =>
value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")
const sizeFor = (name: string) => {
if (name === "J1") return { width: 5.58, height: 23.39 }
if (name === "U1" || name === "U2") return { width: 4.22, height: 3.62 }
return { width: 2.96, height: 1.46 }
}
const colorFor = (name: string) => {
if (name === "J1") return "#20252b"
if (name === "U1" || name === "U2") return "#6d28d9"
if (name.startsWith("C")) return "#f8fafc"
if (name.startsWith("R")) return "#fde68a"
return "#f87171"
}
const drawings = components
.map((component) => {
const name = names.get(component.source_component_id) ?? "?"
const center = component.center!
const size = sizeFor(name)
const rotation = component.rotation ?? 0
const x = px(center.x)
const y = py(center.y)
const opacity = component.do_not_place ? 0.45 : 1
return `<g transform="translate(${x} ${y}) rotate(${-rotation})" opacity="${opacity}">
<rect x="${(-size.width * scale) / 2}" y="${(-size.height * scale) / 2}" width="${size.width * scale}" height="${size.height * scale}" rx="8" fill="${colorFor(name)}" stroke="#111827" stroke-width="3" ${component.do_not_place ? 'stroke-dasharray="8 6"' : ""}/>
<text x="0" y="5" text-anchor="middle" font-family="ui-monospace, SFMono-Regular, monospace" font-size="17" font-weight="700" fill="#111827" transform="rotate(${rotation})">${esc(name)}</text>
</g>`
})
.join("\n")
const holes = [
[-14.765, 14.235],
[14.765, 14.235],
[-14.765, -14.235],
[14.765, -14.235],
]
.map(
([x, y]) =>
`<circle cx="${px(x)}" cy="${py(y)}" r="${1.25 * scale}" fill="#0f172a" stroke="#e2e8f0" stroke-width="5"/>`,
)
.join("\n")
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="1208" height="1210" viewBox="0 0 1208 1210">
<rect width="1208" height="1210" fill="#0b1220"/>
<text x="604" y="38" text-anchor="middle" font-family="system-ui, sans-serif" font-size="27" font-weight="700" fill="#f8fafc">${esc(title)}</text>
<text x="604" y="64" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" fill="#94a3b8">Bottom component view · board coordinates are not mirrored</text>
<rect x="${margin}" y="${margin}" width="${board.width * scale}" height="${board.height * scale}" rx="14" fill="#1565a8" stroke="#e2e8f0" stroke-width="7"/>
<rect x="${px(3 - 9.5)}" y="${py(9.5)}" width="${19 * scale}" height="${19 * scale}" fill="#22d3ee" fill-opacity="0.08" stroke="#67e8f9" stroke-width="5" stroke-dasharray="16 10"/>
<text x="${px(3)}" y="${py(0)}" text-anchor="middle" font-family="system-ui, sans-serif" font-size="23" font-weight="700" fill="#cffafe">EMPTY LENS / SENSOR COURTYARD</text>
<circle cx="${px(3)}" cy="${py(7)}" r="${scale}" fill="#0f172a" stroke="#67e8f9" stroke-width="5"/>
<circle cx="${px(3)}" cy="${py(-7)}" r="${scale}" fill="#0f172a" stroke="#67e8f9" stroke-width="5"/>
${holes}
${drawings}
<g transform="translate(82 1162)">
<rect width="22" height="22" rx="4" fill="#6d28d9"/><text x="31" y="18" fill="#e2e8f0" font-family="system-ui" font-size="17">Regulators</text>
<rect x="170" width="22" height="22" rx="4" fill="#f8fafc"/><text x="201" y="18" fill="#e2e8f0" font-family="system-ui" font-size="17">Capacitors</text>
<rect x="345" width="22" height="22" rx="4" fill="#fde68a"/><text x="376" y="18" fill="#e2e8f0" font-family="system-ui" font-size="17">Resistors</text>
</g>
</svg>`
await Bun.write(outputPath, svg)
console.log(`Wrote ${outputPath}`)