← Commit history

Rebuild every native solder-mask mesh on its correct PCB side; cover split front/back exports

John Lauer ·94be63b726 · ·parent 4b9096e
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