AnasSarkiz/usb-c-pd-brushed-motor
USB-C PD-powered motor controller with protected input, selectable 5/9/12 V buck supply, MCU qualification, PWM H-bridge drive, overvoltage sensing, regenerative energy-dump resistors, and safety interlocks.
- Version
- 0.0.1-a48-cloud-pipeline9-unrouted-c41d79a-305b1e7a
- License
- unset
- Stars
- 0
scripts/ground_copper_geometry.py
"""Physical copper island graph. Net names select copper, never join islands.
Units are millimetres. Cross-layer connections require an actual plated barrel.
Unsupported/invalid geometry fails closed; no snapping or gap-closing tolerance.
"""
import math
from shapely.geometry import Point, LineString, Polygon, box
from shapely.affinity import rotate, translate
from shapely.ops import unary_union
RESOLUTION = 128
def checked_polygon(vertices):
coordinates = [(point['x'], point['y']) for point in vertices]
if not all(math.isfinite(n) for pair in coordinates for n in pair):
raise ValueError('Nonfinite copper polygon')
polygon = Polygon(coordinates)
if not polygon.is_valid or polygon.area <= 0:
raise ValueError('Invalid copper polygon')
return polygon
def pad_geometry(element, drill=False):
shape = element.get('hole_shape') if element['type'] == 'pcb_hole' else element['shape']
if shape == 'polygon':
if drill:
raise ValueError('Polygon drill requires explicit geometry support')
if element['type'] == 'pcb_smtpad':
return checked_polygon(element['points'])
geometry = checked_polygon(element['pad_outline'])
elif shape == 'circle':
diameter = element['hole_diameter'] if drill else element.get('outer_diameter', element.get('radius', 0) * 2)
if not math.isfinite(diameter) or diameter <= 0:
raise ValueError('Invalid circle diameter')
geometry = Point(0, 0).buffer(diameter / 2, quad_segs=RESOLUTION)
elif shape in ('rect', 'pill', 'rotated_pill'):
width = element['hole_width'] if drill else element.get('outer_width', element.get('width'))
height = element['hole_height'] if drill else element.get('outer_height', element.get('height'))
if not all(math.isfinite(n) and n > 0 for n in (width, height)):
raise ValueError('Invalid pad dimensions')
if 'pill' in shape:
radius = min(width, height) / 2
if width == height:
geometry = Point(0, 0).buffer(radius, quad_segs=RESOLUTION)
else:
endpoints = ((-width / 2 + radius, 0), (width / 2 - radius, 0)) if width > height else ((0, -height / 2 + radius), (0, height / 2 - radius))
geometry = LineString(endpoints).buffer(radius, quad_segs=RESOLUTION)
else:
geometry = box(-width / 2, -height / 2, width / 2, height / 2)
else:
raise ValueError(f'Unsupported pad geometry {shape}')
return translate(rotate(geometry, element.get('ccw_rotation', 0), origin=(0, 0)), element['x'], element['y'])
def pour_geometry(element):
if element['shape'] != 'brep':
raise ValueError('Unsupported copper pour shape')
brep = element['brep_shape']
outer = checked_polygon(brep['outer_ring']['vertices'])
holes = [checked_polygon(ring['vertices']) for ring in brep['inner_rings']]
return outer.difference(unary_union(holes))
def ground_islands(copper_by_layer, barrels):
islands = []
for layer, copper in copper_by_layer.items():
merged = unary_union(copper)
if not merged.is_valid:
raise ValueError('Invalid combined copper')
polygons = [merged] if merged.geom_type == 'Polygon' else list(merged.geoms)
for polygon in polygons:
if not polygon.is_empty:
if polygon.geom_type != 'Polygon':
raise ValueError('Unexpected non-area copper')
islands.append((layer, polygon))
parent = list(range(len(islands)))
for barrel in barrels:
touching = [i for i, (layer, polygon) in enumerate(islands)
if layer in barrel['layers'] and polygon.intersection(barrel['geometry']).area > 0]
roots = []
for index in touching:
while parent[index] != index:
index = parent[index]
roots.append(index)
if roots:
for root in roots[1:]:
parent[root] = roots[0]
roots = []
for index in range(len(islands)):
while parent[index] != index:
index = parent[index]
roots.append(index)
return islands, roots
def pad_roots(pad, graph):
islands, roots = graph
return {roots[i] for i, (layer, polygon) in enumerate(islands)
if layer in pad['layers'] and polygon.intersection(pad['geometry']).area > 0}