seveibar/pd-dual-stepper-controller

Bottom-side PCB silkscreen artwork consisting of embedded mirrored TS Circuit SVG logos, with via-clearance-aware lower-board placement.

Version
0.1.0
License
unset
Stars
0

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')