imrishabh18/rp2040-motor-controller
An RP2040-based NEMA17 stepper-motor controller with USB-C programming and PD motor-power negotiation, DRV8825 dual H-bridge drive, current/temperature telemetry, protected power filtering, status RGB LED, buzzer alarm, and optional magnetic encoder.
- Version
- 1.0.42
- License
- unset
- Stars
- 3
scripts/render-driver-thermal-vias.py
"""Render actual audited copper and drill geometry around the motor driver."""
import contextlib
import io
import json
import runpy
import sys
from pathlib import Path
from shapely.geometry import box
from shapely.ops import unary_union
sys.argv = ['scripts/audit-manufacturing.py', 'dist/index/circuit.json']
with contextlib.redirect_stdout(io.StringIO()):
data = runpy.run_path(sys.argv[0])
j = data['j']
ground = data['net'].root(next(e['source_net_id'] for e in j if e['type'] == 'source_net' and e['name'] == 'GND'))
crop = box(0, -17, 9, -6.5)
via_joins = {e['source_trace_id'] for e in j if e['type'] == 'source_trace' and e.get('display_name', '').startswith('DRV_IMPORTED_EP_')}
new_vias = [e for e in j if e['type'] == 'pcb_via' and e.get('source_trace_id') in via_joins]
def paths(geometry, fill):
pieces = list(geometry.geoms) if hasattr(geometry, 'geoms') else [geometry]
result = ''
for polygon in pieces:
if polygon.is_empty or polygon.geom_type != 'Polygon': continue
d = ''
for ring in [polygon.exterior, *polygon.interiors]:
points = list(ring.coords)
d += 'M' + ' L'.join(f'{x:.6f},{y:.6f}' for x, y in points) + ' Z'
result += f'<path d="{d}" fill="{fill}" fill-rule="evenodd"/>'
return result
panels = []
for layer in ('top', 'inner1', 'inner2', 'bottom'):
picture = ''
for is_ground, color in [(True, '#b63e37'), (False, '#6b7f91')]:
copper = unary_union([g for g, m in zip(data['shapes'], data['meta']) if m['layer'] == layer and (m['net'] == ground) == is_ground]).intersection(crop)
picture += paths(copper, color)
picture += '<rect x="2.75" y="-14.55" width="3.1" height="5.6" fill="none" stroke="#f6ca71" stroke-width="0.035" stroke-dasharray="0.15 0.10"/>'
for v in new_vias:
picture += f'<circle cx="{v["x"]}" cy="{v["y"]}" r="0.25" fill="none" stroke="#ffe79f" stroke-width="0.035"/>'
label = {'top':'TOP · exposed pad', 'inner1':'INNER 1 · local GND', 'inner2':'INNER 2 · local GND', 'bottom':'BOTTOM · GND pour'}[layer]
panel = f'<text x="20" y="34" fill="#f1f5f9" font-family="sans-serif" font-size="21">{label}</text><g transform="translate(20,-220) scale(48,-48)">{picture}</g>'
panels.append(panel)
svg = f'<svg xmlns="http://www.w3.org/2000/svg" width="472" height="635" viewBox="0 0 472 635"><rect width="472" height="635" fill="#121b25"/>{panel}</svg>'
Path(f'dist/thermal/driver-{layer}.svg').write_text(svg)
svg = '<svg xmlns="http://www.w3.org/2000/svg" width="1888" height="730" viewBox="0 0 1888 730"><rect width="1888" height="730" fill="#121b25"/>'
svg += '<text x="20" y="42" fill="#fff" font-family="sans-serif" font-size="27">DRV8825 · 12 imported thermal vias inside the exposed pad</text>'
for i, panel in enumerate(panels): svg += f'<g transform="translate({i*472},60)">{panel}</g>'
svg += '<text x="20" y="709" fill="#bac6d5" font-family="sans-serif" font-size="18">Red: GND copper · Gray: other nets · Gold rings: imported 0.30 mm plated drills / 0.50 mm pads · Solder mask hidden; bottom vias are tented</text></svg>'
Path('dist/thermal/driver-all-layers.svg').write_text(svg)
print('Wrote five thermal-via copper views from the audited circuit geometry')
