app
AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
← Commit history
Rebuild every native solder-mask mesh on its correct PCB side; cover split front/back exports
2 files changed
+44−11
tools/board-glb-copper.py+28−11@@ -698,17 +698,34 @@ def copper(args): elif len(bpbr.get('baseColorFactor', [1, 1, 1, 1])) > 3 and bpbr['baseColorFactor'][3] < 1: mask_mat_d['alphaMode'] = 'BLEND' g['materials'].append(mask_mat_d); mask_mat = len(g['materials']) - 1- prims = []- for s, z, up in (('F', top, True), ('B', bot, False)):- pos, nrm, idx = triangulate(L['mask'][s], fr, z, up, M)- if idx:- pr = add_mesh_data(g, bin_, pos, nrm, idx); pr['material'] = mask_mat; prims.append(pr)- if mi is not None:- g['meshes'][mi]['primitives'] = prims- g['meshes'][mi].setdefault('extras', {}).update({'adomBoardLayer': 'soldermask', 'adomGenerated': 'outline minus F.Mask/B.Mask apertures'})- else:- g['meshes'].append({'name': f'{L["stem"]}_soldermask', 'primitives': prims, 'extras': {'adomBoardLayer': 'soldermask', 'adomGenerated': 'outline minus F.Mask/B.Mask apertures'}})- add_node_like(g, fr.pcb_node, len(g['meshes']) - 1, 'adom-aiflow soldermask')+ # KiCad may export front/back masks as separate meshes. Rebuild every one,+ # in its own local frame, and keep each mesh on its original board side.+ targets = layers.get('soldermask', []) or [(None, None)]+ mid = (pcb_top + pcb_bot) / 2+ for mask_mi, mask_ni in targets:+ if mask_mi is None:+ face_specs = [('F', top, True), ('B', bot, False)]+ mask_M = np.eye(4)+ else:+ zhi = fr.z_in_pcb(mask_mi, mask_ni, 'max')+ zlo = fr.z_in_pcb(mask_mi, mask_ni, 'min')+ mask_M = fr.pcb_to_node(mask_ni)+ face_specs = []+ if zhi > mid: face_specs.append(('F', zhi, True))+ if zlo < mid: face_specs.append(('B', zlo, False))+ prims = []+ for side, z, up in face_specs:+ pos, nrm, idx = triangulate(L['mask'][side], fr, z, up, mask_M)+ if idx:+ pr = add_mesh_data(g, bin_, pos, nrm, idx)+ pr['material'] = mask_mat+ prims.append(pr)+ if mask_mi is not None:+ g['meshes'][mask_mi]['primitives'] = prims+ g['meshes'][mask_mi].setdefault('extras', {}).update({'adomBoardLayer': 'soldermask', 'adomGenerated': 'outline minus F.Mask/B.Mask apertures'})+ else:+ g['meshes'].append({'name': f'{L["stem"]}_soldermask', 'primitives': prims, 'extras': {'adomBoardLayer': 'soldermask', 'adomGenerated': 'outline minus F.Mask/B.Mask apertures'}})+ add_node_like(g, fr.pcb_node, len(g['meshes']) - 1, 'adom-aiflow soldermask') # 2b. silkscreen is clipped at every mask opening, as the fab clips it (KiCad's STEP export keeps the whole ink, so a # pad under a text renders with ink on bare copper: a board no fab makes). The STEP's silk is compressed and cannot be
tools/test-board-glb-copper.py+16@@ -66,6 +66,22 @@ with tempfile.TemporaryDirectory() as d: ('test_pad', pads, 1), ('test_soldermask', quad(100, 100, 120, 110, 1.56) + quad(100, 100, 120, 110, -0.05), 2)], raw) + # Native KiCad exports can split the mask into one top and one bottom mesh.+ split_raw = d / 'split-mask.glb'+ glb_with([('test_PCB', quad(100, 100, 120, 110, 0) + quad(100, 100, 120, 110, 1.51), 0),+ ('test_pad', pads, 1),+ ('test_soldermask', quad(100, 100, 120, 110, 1.56), 2),+ ('test_soldermask', quad(100, 100, 120, 110, -0.05), 2)], split_raw)+ split_fixed = d / 'split-mask-fixed.glb'+ code, out = run('copper', '--glb', split_raw, '--board', board, '--out', split_fixed)+ assert code == 0, out+ sg, sb = m.read_glb(split_fixed)+ sl = m.board_meshes(sg, 'test'); sf = m.Frame(sg, sl, m.board_layers(board)['edge'])+ masks = sl['soldermask']+ assert len(masks) == 2+ assert m.coverage(sg, sb, sf, *masks[0], False).is_empty+ assert m.coverage(sg, sb, sf, *masks[1], True).is_empty+ # 1. the raw export fails: grey pads with default metal/roughness, a blanket mask over every pad code, out = run('check', '--glb', raw, '--board', board) assert code == 1, out