shibosoftwaredev/f1c100s-linux-nema17-stepper-controller

A four-layer NEMA 17 motor-controller PCB combining an Allwinner F1C100S Linux computer, USB‑PD/buck power system, W5500 Ethernet, STM32 real-time motion control, DRV8825 stepper driver, protection/temperature/current telemetry, USB interfaces, and local LED/buzzer diagnostics.

Version
0.4.56
License
unset
Stars
0

scripts/import-whole-board.py

"""Convert routed physical copper into complete source-associated paths.
Connectivity failures stop the import; the candidate still requires DRC.
"""
from route_geometry import *
from collections import defaultdict
from shapely.ops import unary_union
from shapely.strtree import STRtree
import heapq, sys, os, hashlib

# Use electrical identities, not render-order IDs: deleting an unrelated trace
# must not turn an Ethernet signal into an outer-layer-only power trunk.
preferred_widths={'USB_VBUS_A':.35,'USB_VBUS_B':.35,'BUCK_IN_CAP':.5,'BUCK_SW':.3,
    'VM_INPUT':.5,'VM_FUSED':.5,'VM_TVS_GND':.8,'VM_BULK_GND':.8,
    'DRV_VMA':.3,'DRV_VMB':.3,'VBUS_SOURCE_1':.6,'VBUS_SOURCE_2':.6,
    'VBUS_DRAIN_5':.6,'VBUS_DRAIN_6':.6,'VBUS_DRAIN_7':.6,'BUCK_INPUT_BYPASS':.5}
outer_source_names={'USB_VBUS_A','USB_VBUS_B','BUCK_SW','VM_INPUT','VM_FUSED','BUCK_INPUT_BYPASS'}

def contact_width(bridge,existing_copper):
    # Describe existing pad/annulus metal at a contact without manufacturing
    # extra copper outside that union or inventing a 0.12 mm neck inside it.
    return next((w for w in [.8,.6,.5,.45,.35,.3,.25,.2,.15,.12]
                 if existing_copper.buffer(2e-4).covers(bridge.buffer(w/2))),.12)

circuit = load_circuit()
root = connectivity(circuit)
mapping = json.loads(Path("routing/net-map.json").read_text())
net_for_root = mapping["roots"]
ports = {e["pcb_port_id"]: e for e in circuit if e["type"] == "pcb_port"}
for e in circuit:
    if e["type"] in ["pcb_smtpad", "pcb_plated_hole"] and e.get("pcb_port_id") in ports:
        ports[e["pcb_port_id"]]["layers"] = e.get("layers", [e.get("layer")])
by_source = {e["source_port_id"]: e for e in ports.values()}
source_traces = [e for e in circuit if e["type"] == "source_trace"]
tree = parse_sexpr(Path(sys.argv[1] if len(sys.argv)>1 else "routing/board.ses").read_text())
routes = one(tree, "routes")
res = one(routes, "resolution")
scale = {"um": .001, "mm": 1, "mil": .0254}[res[1]] / float(res[2])
assert scale <= .0001+1e-12, "Routing session needs at least 0.1 micrometre coordinate precision"
extra_routes=[] if os.environ.get('IGNORE_PROTECTED') else json.loads(Path("routing/protected-routes.json").read_text())
if os.environ.get("INCLUDE_CLOSURE"):
    extra_routes+=json.loads(Path("routing/closure-patches.json").read_text())
# SES writes integer coordinates at its declared resolution. Restore exact
# pad/protected-escape coordinates only within that format's rounding error
# (at most 0.071 micrometres here), avoiding false graph gaps and duplicate
# drills after the round trip. This is not an electrical clearance tolerance.
snap_radius=math.sqrt(2)*scale/2+1e-8
anchors=defaultdict(list)
def snap_xy(x,y,register=False):
    cell=(math.floor(x/scale),math.floor(y/scale))
    nearby=[p for dx in [-1,0,1] for dy in [-1,0,1]
            for p in anchors[(cell[0]+dx,cell[1]+dy)]
            if math.dist((x,y),p)<=snap_radius]
    if nearby:
        return min(nearby,key=lambda p:math.dist((x,y),p))
    if register:
        anchors[cell].append((x,y))
    return x,y
for p in ports.values():
    snap_xy(p["x"],p["y"],True)
for t in extra_routes:
    for p in t["route"]:
        snap_xy(p["x"],p["y"],True)
for t in extra_routes:
    for p in t["route"]:
        p["x"],p["y"]=snap_xy(p["x"],p["y"])
wires, vias = defaultdict(list), defaultdict(list)
for n in entries(one(routes, "network_out"), "net"):
    net = n[1]
    for w in entries(n, "wire"):
        p = one(w, "path")
        nums = [float(x)*scale for x in p[3:]]
        pts = [snap_xy(x,y) for x,y in zip(nums[::2], nums[1::2])]
        for a,b in zip(pts, pts[1:]):
            if math.dist(a,b)>1e-7:
                wires[net].append((LineString([a,b]), p[1], float(p[2])*scale))
    for v in entries(n, "via"):
        vias[net].append(snap_xy(float(v[2])*scale, float(v[3])*scale))
if Path("routing/native-features.json").exists():
    features=json.loads(Path("routing/native-features.json").read_text())
    for v in features["thermal_vias"]+features.get("auxiliary_vias",[]):
        vias[v["net"]].append((v["x"],v["y"]))
for trace in extra_routes:
    net = trace["connection_name"]
    for p in trace["route"]:
        if p["route_type"] == "via":
            vias[net].append((p["x"], p["y"]))
    for a,b in zip(trace["route"], trace["route"][1:]):
        if a["route_type"] == b["route_type"] == "wire" and a["layer"] == b["layer"]:
            wires[net].append((LineString([(a["x"],a["y"]),(b["x"],b["y"])]), a["layer"], max(a["width"],b["width"])))
for net in vias:
    vias[net]=list(dict.fromkeys((round(x,6),round(y,6)) for x,y in vias[net]))
for e in circuit:
    if e["type"]=="pcb_trace" and e.get("pcb_component_id"):
        p=ports.get(e["route"][0].get("start_pcb_port_id"))
        if not p:
            continue
        net=net_for_root[root(p["source_port_id"])]
        for a,b in zip(e["route"],e["route"][1:]):
            if a["route_type"]==b["route_type"]=="wire" and a["layer"]==b["layer"]:
                wires[net].append((LineString([(a["x"],a["y"]),(b["x"],b["y"])]),a["layer"],a["width"]))

pads = defaultdict(list)
for e in circuit:
    if e["type"] not in ["pcb_smtpad", "pcb_plated_hole"] or e.get("pcb_port_id") not in ports:
        continue
    net = net_for_root[root(ports[e["pcb_port_id"]]["source_port_id"])]
    pads[net].append((e, geometry(e, "outer_" if e["type"]=="pcb_plated_hole" else "")))

traces_by_net = defaultdict(list)
for t in source_traces:
    ids = t.get("connected_source_port_ids", []) + t.get("connected_source_net_ids", [])
    if ids and root(ids[0]) in net_for_root:
        traces_by_net[net_for_root[root(ids[0])]].append(t)

output, failed, stats, remaining_links = [], [], [], []
for net, traces in traces_by_net.items():
    graph = defaultdict(dict)
    special = defaultdict(set)
    pad_contacts = defaultdict(set)
    def node(x,y,l):
        return (round(x,6), round(y,6), l)
    def add(a,b,width,kind="wire",extra=None):
        if a==b:
            return
        cost = math.dist(a[:2],b[:2]) if kind=="wire" else 1.6 if kind=="via" else .001
        value = (cost,width,kind,extra)
        if b not in graph[a] or width>graph[a][b][1]:
            graph[a][b]=value
            graph[b][a]=value
    for x,y in vias[net]:
        ns = [node(x,y,l) for l in LAYERS]
        for a,b in zip(ns,ns[1:]):
            add(a,b,0,"via")
        for n in ns:
            special[n[2]].add(n)
    for e,g in pads[net]:
        p=ports[e["pcb_port_id"]]
        for layer in e.get("layers",[e.get("layer")]):
            pc=node(p["x"],p["y"],layer)
            for via_node in special[layer]:
                center=Point(via_node[:2])
                if g.distance(center)<=.225+1e-6:
                    bridge=LineString([pc[:2],via_node[:2]])
                    if g.buffer(1e-4).union(center.buffer(.2251)).covers(bridge):
                        add(pc,via_node,contact_width(bridge,g.union(center.buffer(.2251))))
                        pad_contacts[pc].add(via_node)
        if e["type"]=="pcb_plated_hole":
            ns=[node(p["x"],p["y"],l) for l in e["layers"]]
            for a,b in zip(ns,ns[1:]):
                add(a,b,.12,"pad",e["pcb_plated_hole_id"])
            for n in ns:
                pad_contacts[n].update(ns)
            for n in ns:
                special[n[2]].add(n)
    for layer in LAYERS:
        segments=[(g,w) for g,l,w in wires[net] if l==layer]
        if not segments:
            continue
        geos=[g for g,w in segments]
        stree=STRtree(geos)
        union=unary_union(geos)
        lines=[union] if union.geom_type=="LineString" else list(union.geoms)
        layerpads=[(e,g) for e,g in pads[net] if layer in e.get("layers",[e.get("layer")])]
        bridges=[]
        for line in lines:
            coords=list(line.coords)
            for a,b in zip(coords,coords[1:]):
                seg=LineString([a,b])
                midpoint=seg.interpolate(.5,normalized=True)
                widths=[segments[int(i)][1] for i in stree.query(seg.buffer(1e-5))
                        if geos[int(i)].distance(midpoint)<1e-5]
                width=max(widths or [.12])
                cuts=[(0,node(*a,layer)),(seg.length,node(*b,layer))]
                for n in special[layer]:
                    q=Point(n[:2])
                    if seg.distance(q)<1e-5:
                        cuts.append((seg.project(q),n))
                for e,g in layerpads:
                    if seg.distance(g)>width/2+1e-5:
                        continue
                    p=ports[e["pcb_port_id"]]
                    center=Point(p["x"],p["y"])
                    distance=seg.project(center)
                    projection=seg.interpolate(distance)
                    pc=node(p["x"],p["y"],layer)
                    pn=node(projection.x,projection.y,layer)
                    bridge=LineString([pc[:2],pn[:2]])
                    if not g.buffer(2e-4).union(seg.buffer(width/2+2e-4)).covers(bridge):
                        continue
                    cuts.append((distance,pn))
                    bridges.append((pc,pn,contact_width(bridge,g.union(seg.buffer(width/2)))))
                cuts.sort(key=lambda p:p[0])
                for (_,aa),(_,bb) in zip(cuts,cuts[1:]):
                    add(aa,bb,width)
        for a,b,w in bridges:
            add(a,b,w)
            pad_contacts[a].add(b)

    minimum_width=0
    def shortest(starts,targets,outer_only=False):
        queue=[(0,start) for start in starts]
        heapq.heapify(queue)
        cost={start:0 for start in starts}
        previous={}
        while queue:
            distance,a=heapq.heappop(queue)
            if distance>cost[a]+1e-9:
                continue
            if a in targets:
                path=[a]
                while path[-1] in previous:
                    path.append(previous[path[-1]])
                return list(reversed(path))
            for b,(length,w,kind,extra) in graph[a].items():
                if outer_only and kind=="wire" and a[2] not in ["top","bottom"]:
                    continue
                new=distance+length
                if kind=='wire' and w+1e-8<minimum_width:
                    new+=length*1_000_000
                if new<cost.get(b,float("inf")):
                    cost[b]=new
                    previous[b]=a
                    heapq.heappush(queue,(new,b))
        return None
    def portnode(p):
        return node(p["x"],p["y"],p["layers"][0])
    def emit_physical_path(path,source):
        chunks=[];chunk=[path[0]]
        for a,b in zip(path,path[1:]):
            if graph[a][b][2]=='pad':
                if len(chunk)>1:chunks.append(chunk)
                chunk=[b]
            else:chunk.append(b)
        if len(chunk)>1:chunks.append(chunk)
        for segment in chunks:
            result=[]
            for a,b in zip(segment,segment[1:]):
                _,w,kind,extra=graph[a][b]
                if kind=='wire':
                    result.extend({'route_type':'wire','x':p[0],'y':p[1],'layer':p[2],'width':w} for p in [a,b])
                elif kind=='via':
                    if result and result[-1].get('route_type')=='via' and result[-1]['x']==a[0] and result[-1]['y']==a[1]:
                        result[-1]['to_layer']=b[2]
                    else:result.append({'route_type':'via','x':a[0],'y':a[1],'from_layer':a[2],'to_layer':b[2],
                                        'via_diameter':.45,'via_hole_diameter':.3})
                else:raise AssertionError('Unsplit connector barrel')
            first,last=segment[0],segment[-1]
            first_port=next((p for p in netports if node(p['x'],p['y'],first[2])==first and first[2] in p['layers']),None)
            last_port=next((p for p in netports if node(p['x'],p['y'],last[2])==last and last[2] in p['layers']),None)
            assert first_port and last_port,('unanchored physical ground network',first,last)
            fw=next((p['width'] for p in result if p['route_type']=='wire'),.12)
            lw=next((p['width'] for p in reversed(result) if p['route_type']=='wire'),fw)
            result.insert(0,{'route_type':'wire','x':first[0],'y':first[1],'layer':first[2],'width':fw,
                             'start_pcb_port_id':first_port['pcb_port_id']})
            result.append({'route_type':'wire','x':last[0],'y':last[1],'layer':last[2],'width':lw,
                           'end_pcb_port_id':last_port['pcb_port_id']})
            output.append({'type':'pcb_trace','pcb_trace_id':f'whole_board_{len(output)}',
                'source_trace_id':source['source_trace_id'],'subcircuit_id':source['subcircuit_id'],
                'subcircuit_connectivity_map_key':source.get('subcircuit_connectivity_map_key'),'route':result})
    netports=[p for p in ports.values() if root(p["source_port_id"]) in net_for_root
              and net_for_root[root(p["source_port_id"])]==net]
    # Export real disconnected copper islands for short, targeted finishing
    # routes. No connectivity record or unrouted error is removed here.
    port_nodes={node(p["x"],p["y"],l) for p in netports for l in p["layers"]}
    unseen=set(graph)|port_nodes
    islands=[]
    while unseen:
        todo=[next(iter(unseen))]
        island=set()
        while todo:
            a=todo.pop()
            if a in island:
                continue
            island.add(a)
            todo.extend(b for b in graph[a] if b not in island)
        unseen.difference_update(island)
        if island&port_nodes:
            islands.append(island)
    if len(islands)>1:
        islands.sort(key=lambda nodes:len(nodes&port_nodes),reverse=True)
        connected=islands.pop(0)
        while islands:
            options=[]
            for i,island in enumerate(islands):
                for layer in LAYERS:
                    candidates=sorted(n for n in connected if n[2]==layer)
                    if not candidates:
                        continue
                    tree=STRtree([Point(n[:2]) for n in candidates])
                    for a in sorted(island):
                        b=candidates[int(tree.nearest(Point(a[:2])))]
                        options.append((math.dist(a[:2],b[:2])+(0 if a[2]==b[2] else 2),i,a,b))
            _,i,a,b=min(options)
            port=next((p for p in netports if node(p["x"],p["y"],a[2])==a),None)
            constraints=[t.get("min_trace_thickness",.12) for t in traces
                         if port and port["source_port_id"] in t.get("connected_source_port_ids",[])]
            remaining_links.append({"net":net,"a":a,"b":b,
                                    "width":max(constraints or [.12]),
                                    "port":port and port["pcb_port_id"],
                                    "a_vias":[[x,y,"inner1"] for x,y in sorted(vias[net],key=lambda q:math.dist(q,a[:2]))
                                              if node(x,y,"inner1") in islands[i]][:100],
                                    "b_vias":[[x,y,"inner1"] for x,y in sorted(vias[net],key=lambda q:math.dist(q,b[:2]))
                                              if node(x,y,"inner1") in connected][:100]})
            connected=connected|islands.pop(i)
    for t in traces:
        minimum_width=preferred_widths.get(t.get('name'),t.get('min_trace_thickness',0))
        ends=[by_source[i] for i in t.get("connected_source_port_ids",[]) if i in by_source]
        if not ends:
            continue
        start=ends[0]
        targets=ends[1:]
        if not targets:
            targets=[p for p in netports if p["pcb_port_id"]!=start["pcb_port_id"]]
            if not targets:
                continue
            targetsets=[{node(p["x"],p["y"],l) for p in targets for l in p["layers"]}]
        else:
            targetsets=[{node(p["x"],p["y"],l) for l in p["layers"]} for p in targets]
        covered = {portnode(start)}
        anchors = {portnode(start)}
        for targetset in targetsets:
            # Build one physical tree rather than duplicate the root-to-leaf
            # trunk for every terminal of a multi-port source net. Keep every
            # declared terminal explicitly port-anchored, including when a
            # previous wire merely grazed its pad rather than ending there.
            if anchors&targetset:
                continue
            path=shortest(anchors,targetset,t.get('name') in outer_source_names)
            if path:
                planar=sum(graph[a][b][0] for a,b in zip(path,path[1:]) if graph[a][b][2]=='wire')
                transitions={(a[0],a[1]) for a,b in zip(path,path[1:]) if graph[a][b][2]=='via'}
                if planar+1.6*len(transitions)>t.get('max_length',float('inf')):
                    preferred_width=minimum_width;minimum_width=0
                    path=shortest(anchors,targetset,t.get('name') in outer_source_names|{'BUCK_IN_CAP'})
                    minimum_width=preferred_width
            if not path:
                failed.append({"source_trace_id":t["source_trace_id"],"name":t.get("name"),
                               "net":net,"start":portnode(start),"targets":list(targetset),
                               "degree":len(graph[portnode(start)])})
                continue
            covered.update(path)
            anchors.add(path[-1])
            # A plated barrel is existing connector copper, not another via.
            # End one port-anchored wire at it and start another on its other
            # layer. This preserves the real connection and avoids unsupported
            # through_pad rendering or manufacturing a second drill there.
            chunks, chunk = [], [path[0]]
            for a,b in zip(path,path[1:]):
                if graph[a][b][2] == "pad":
                    if len(chunk)>1:
                        chunks.append(chunk)
                    chunk=[b]
                else:
                    chunk.append(b)
            if len(chunk)>1:
                chunks.append(chunk)
            for path in chunks:
              result=[]
              for a,b in zip(path,path[1:]):
                _,w,kind,extra=graph[a][b]
                if kind=="wire":
                    for p in [a,b]:
                        result.append({"route_type":"wire","x":p[0],"y":p[1],"layer":p[2],"width":w})
                elif kind=="via":
                    if result and result[-1].get("route_type")=="via" and result[-1]["x"]==a[0] and result[-1]["y"]==a[1]:
                        result[-1]["to_layer"]=b[2]
                    else:
                        result.append({"route_type":"via","x":a[0],"y":a[1],
                                       "from_layer":a[2],"to_layer":b[2],"via_diameter":.45,"via_hole_diameter":.3})
                else:
                    raise AssertionError("Unsplit connector barrel")
              first,last=path[0],path[-1]
              first_width=next((p["width"] for p in result if p["route_type"]=="wire"),.12)
              last_width=next((p["width"] for p in reversed(result) if p["route_type"]=="wire"),first_width)
              first_port=next((p for p in netports if node(p["x"],p["y"],first[2])==first and first[2] in p["layers"]),None)
              end=next((p for p in netports if node(p["x"],p["y"],last[2])==last and last[2] in p["layers"]),None)
              assert first_port and end, (t["name"],"unanchored physical route")
              result.insert(0,{"route_type":"wire","x":first[0],"y":first[1],"layer":first[2],
                             "width":first_width,"start_pcb_port_id":first_port["pcb_port_id"]})
              result.append({"route_type":"wire","x":last[0],"y":last[1],"layer":last[2],"width":last_width,
                           "end_pcb_port_id":end["pcb_port_id"]})
              output.append({"type":"pcb_trace","pcb_trace_id":f"whole_board_{len(output)}",
                           "source_trace_id":t["source_trace_id"],"subcircuit_id":t["subcircuit_id"],
                           "subcircuit_connectivity_map_key":t.get("subcircuit_connectivity_map_key"),"route":result})
    # A virtual net endpoint can be satisfied by a nearby pin. Those
    # local shortest paths alone do NOT retain the bridges between ground
    # clusters. Export a real terminal-spanning tree as well, using only the
    # measured physical graph. Disconnected islands remain reported above.
    network_sources=[t for t in traces if t.get('connected_source_net_ids')
                     and t.get('max_length',float('inf'))>=1000 and t.get('max_via_count') is None]
    network_source=max(network_sources,key=lambda t:len(t.get('connected_source_port_ids',[])),default=None)
    if network_source:
        minimum_width=0
        anchors=set()
        for p in netports:
            targets={node(p['x'],p['y'],layer) for layer in p['layers']}
            if anchors and not anchors&targets:
                path=shortest(anchors,targets)
                if path and len(path)>1:emit_physical_path(path,network_source)
            anchors.add(portnode(p))
    stats.append({"net":net,"wire_segments":len(wires[net]),"vias":len(vias[net]),"graph_nodes":len(graph)})

# Preserve the deliberately authored supply and return trunks, even where a
# shorter, narrow branch of the same net also connects the declared terminals.
# These are real checked copper, not substitute connectivity records.
load_names={'USB_VBUS_A','USB_VBUS_B','BUCK_IN_CAP','BUCK_SW','VM_INPUT','VM_FUSED',
    'VM_PROTECTED','VM_TVS_GND','VM_BULK_GND','DRV_VMA','DRV_VMB'}
load_ids={t['source_trace_id'] for t in source_traces if t.get('name') in load_names}
assert len(load_ids)==len(load_names),'all required power-source identities must exist'
load_routes={}
for trace in extra_routes:
    if trace.get('source_trace_id') in load_ids:
        key=(trace['source_trace_id'],tuple((p['route_type'],p['x'],p['y'],p.get('layer'),p.get('from_layer'),p.get('to_layer')) for p in trace['route']))
        if key in load_routes:
            for a,b in zip(load_routes[key]['route'],trace['route']):
                if a['route_type']=='wire':a['width']=max(a['width'],b['width'])
        else:load_routes[key]={**trace,'route':[dict(p) for p in trace['route']]}
for trace in load_routes.values():
        source=next(t for t in source_traces if t['source_trace_id']==trace['source_trace_id'])
        assert source.get('max_length') is None
        output.append({**trace,'subcircuit_id':source['subcircuit_id'],
                       'subcircuit_connectivity_map_key':source.get('subcircuit_connectivity_map_key')})
# An explicitly paired source can retain its checked authored route instead
# of taking an incidental narrow shortcut through the same electrical net.
# Virtual-net spanning trees above are deliberately not replaced here.
paired={}
for trace in extra_routes:
    source=next((t for t in source_traces if t['source_trace_id']==trace.get('source_trace_id')),None)
    if not source or source.get('connected_source_net_ids') or len(source.get('connected_source_port_ids',[]))!=2:continue
    terminals=[by_source.get(id) for id in source['connected_source_port_ids']]
    if not all(terminals):continue
    wire_points=[p for p in trace['route'] if p['route_type']=='wire']
    if len(wire_points)<2:continue
    first,last=wire_points[0],wire_points[-1]
    match=lambda p,q:math.hypot(p['x']-q['x'],p['y']-q['y'])<1e-5 and p['layer'] in q['layers']
    if match(first,terminals[1]) and match(last,terminals[0]):terminals.reverse()
    if not (match(first,terminals[0]) and match(last,terminals[1])):continue
    candidate={**trace,'route':[dict(p) for p in trace['route']],
               'subcircuit_id':source['subcircuit_id'],
               'subcircuit_connectivity_map_key':source.get('subcircuit_connectivity_map_key')}
    next(p for p in candidate['route'] if p['route_type']=='wire')['start_pcb_port_id']=terminals[0]['pcb_port_id']
    next(p for p in reversed(candidate['route']) if p['route_type']=='wire')['end_pcb_port_id']=terminals[1]['pcb_port_id']
    score=min(p['width'] for p in wire_points)
    if score>=paired.get(source['source_trace_id'],(-1,None))[0]:paired[source['source_trace_id']]=(score,candidate)
output=[t for t in output if t['source_trace_id'] not in paired]+[t for score,t in paired.values()]
# The two authored zero-via clock trees use crystal-centred branches. Emitting
# the generic consecutive port pairs would duplicate the capacitor branch and
# incorrectly add its length twice. Emit the actual connected tree once, with
# exact terminal IDs, rather than relaxing either clock's 10 mm limit.
clock_path=Path('routing/review-clock.json')
if clock_path.exists() and not os.environ.get('AUDIT_ONLY'):
    clock_trees=json.loads(clock_path.read_text())
    for sid in ['source_trace_18','source_trace_19']:
        source=next(s for s in source_traces if s['source_trace_id']==sid)
        assert source['max_length']==10 and source['max_via_count']==0
        authored=[];covered=set();length=0
        for t in [t for t in clock_trees if t['source_trace_id']==sid]:
            route=[dict(p) for p in t['route']]
            assert all(p['route_type']=='wire' and p['layer']=='top' for p in route)
            ends=[]
            for p in [route[0],route[-1]]:
                peers=[by_source[id] for id in source['connected_source_port_ids']
                       if math.hypot(by_source[id]['x']-p['x'],by_source[id]['y']-p['y'])<1e-6]
                assert len(peers)==1
                ends.append(peers[0]);covered.add(peers[0]['source_port_id'])
            route[0]['start_pcb_port_id']=ends[0]['pcb_port_id']
            route[-1]['end_pcb_port_id']=ends[1]['pcb_port_id']
            length+=sum(math.dist((a['x'],a['y']),(b['x'],b['y'])) for a,b in zip(route,route[1:]))
            authored.append({**t,'route':route,'subcircuit_id':source['subcircuit_id'],
                'subcircuit_connectivity_map_key':source.get('subcircuit_connectivity_map_key')})
        assert covered==set(source['connected_source_port_ids']) and length<=10
        output=[t for t in output if t['source_trace_id']!=sid]+authored
preserved_path=Path('routing/review-preserved-pairs.json')
if preserved_path.exists() and not os.environ.get('AUDIT_ONLY'):
    preserved=json.loads(preserved_path.read_text())
    for sid in sorted({t['source_trace_id'] for t in preserved}):
        source=next(s for s in source_traces if s['source_trace_id']==sid)
        assert not source.get('connected_source_net_ids') and len(source['connected_source_port_ids'])==2
        paths=[t for t in preserved if t['source_trace_id']==sid]
        for t in paths:
            first,last=t['route'][0],t['route'][-1]
            terminals=[by_source[id] for id in source['connected_source_port_ids']]
            assert all(any(math.hypot(p['x']-q['x'],p['y']-q['y'])<1e-6 for p in terminals) for q in [first,last]), 'Preserved route terminals moved'
        output=[t for t in output if t['source_trace_id']!=sid]+[{**t,'subcircuit_id':source['subcircuit_id'],'subcircuit_connectivity_map_key':source.get('subcircuit_connectivity_map_key')} for t in paths]
constant_width=[]
for trace in output:
    run=[];width=None
    for p in trace['route']:
        if p['route_type']=='wire' and width is not None and abs(p['width']-width)>1e-8:
            # Core coalesces coincident route points. Keep a width step as
            # two physically joined traces so that coalescing cannot widen
            # the preceding fine-pitch escape along its entire segment.
            assert run and math.hypot(run[-1]['x']-p['x'],run[-1]['y']-p['y'])<1e-5
            constant_width.append({**trace,'route':run})
            run=[];width=None
        run.append(p)
        if p['route_type']=='wire':width=p['width']
    if run:constant_width.append({**trace,'route':run})
output=[{**t,'pcb_trace_id':f'whole_board_{i}'} for i,t in enumerate(constant_width)]
output_dir=Path(os.environ.get('IMPORT_OUTPUT_DIR','routing'));output_dir.mkdir(parents=True,exist_ok=True)
(output_dir/"import-report.json").write_text(json.dumps({"paths":len(output),"failures":failed,
    "circuit_sha256":hashlib.sha256(Path(os.environ.get('CIRCUIT_INPUT','routing/placement.json')).read_bytes()).hexdigest(),
    "session_sha256":hashlib.sha256(Path(sys.argv[1] if len(sys.argv)>1 else 'routing/board.ses').read_bytes()).hexdigest(),
    "audit_only":bool(os.environ.get('AUDIT_ONLY')),"ignored_protected":bool(os.environ.get('IGNORE_PROTECTED')),
    "physical_island_links":len(remaining_links),"stats":stats},indent=2))
(output_dir/"remaining-links.json").write_text(json.dumps(remaining_links,indent=2))
(output_dir/"physical-copper.json").write_text(json.dumps({
    "wires":[{"net":n,"layer":l,"width":w,"points":list(g.coords)} for n,items in wires.items() for g,l,w in items],
    "vias":[{"net":n,"x":x,"y":y} for n,items in vias.items() for x,y in items],
},indent=2))
if not os.environ.get('AUDIT_ONLY') and not failed and not remaining_links:
    (output_dir/"complete-routes.json").write_text(json.dumps(output,indent=2))
print(json.dumps({"paths":len(output),"failures":len(failed)}))
for failure in failed[:15]:
    print(json.dumps({**failure, "targets": failure["targets"][:3]}))
if failed or remaining_links:
    sys.exit(1)