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/copper_clearance_geometry.py

"""Physical wire copper in board-world mm; no electrical or geometric snapping.

Linear taper polygons follow the installed native renderer's boundary. Other
interpolation modes require independent support rather than a guessed width.
"""
import math
from shapely.geometry import LineString, Polygon
from shapely.affinity import rotate, translate
from ground_copper_geometry import RESOLUTION


def wire_copper_segments(trace):
    route = trace['route']
    mode = trace.get('route_thickness_mode', 'constant')
    if mode not in ('constant', 'interpolated'):
        raise ValueError('Unsupported trace thickness mode')
    if any(point['route_type'] not in ('wire', 'via') for point in route):
        raise ValueError('Unsupported route primitive')
    segments = []
    for first, second in zip(route, route[1:]):
        if not all(math.isfinite(point[axis]) for point in (first, second) for axis in ('x', 'y')):
            raise ValueError('Nonfinite copper coordinates')
        if (first['x'], first['y']) == (second['x'], second['y']):
            continue
        if first['route_type'] == second['route_type'] == 'via':
            raise ValueError('Wire is missing between displaced vias')
        layer = first['layer'] if first['route_type'] == 'wire' else first['to_layer']
        next_layer = second['layer'] if second['route_type'] == 'wire' else second['from_layer']
        if layer != next_layer:
            raise ValueError('Wire changes layer without a barrel')
        start_width_mm = first.get('start_width', first['width']) if first['route_type'] == 'wire' else second.get('start_width', second['width'])
        interpolation = first.get('width_interpolation_mode') if first['route_type'] == 'wire' else None
        if first['route_type'] == 'wire' and mode == 'interpolated' and interpolation is None:
            interpolation = 'linear'
        if interpolation not in (None, 'linear'):
            raise ValueError('Unsupported copper taper')
        if interpolation and second['route_type'] == 'via' and 'end_width' not in first:
            raise ValueError('Taper ending at barrel lacks its end width')
        end_width_mm = first.get('end_width', second.get('width')) if interpolation else start_width_mm
        if not all(isinstance(width, (int, float)) and math.isfinite(width) and width > 0 for width in (start_width_mm, end_width_mm)):
            raise ValueError('Invalid copper width')
        coordinates = [(point['x'], point['y']) for point in (first, second)]
        if interpolation:
            length_mm = math.dist(*coordinates)
            polygon = Polygon([(0, start_width_mm / 2), (length_mm, end_width_mm / 2), (length_mm, -end_width_mm / 2), (0, -start_width_mm / 2)])
            ccw_rotation_degrees = math.degrees(math.atan2(second['y'] - first['y'], second['x'] - first['x']))
            geometry = translate(rotate(polygon, ccw_rotation_degrees, origin=(0, 0)), first['x'], first['y'])
        else:
            geometry = LineString(coordinates).buffer(start_width_mm / 2, quad_segs=RESOLUTION)
        segments.append({'layer': layer, 'geometry': geometry, 'minimumWidthMm': min(start_width_mm, end_width_mm)})
    return segments