← Commit history

Board GLB with the Gerber artwork (mask, copper, ENIG, silkscreen) baked into its top and bottom faces and real material finishes (the STEP export left every material fully metallic)

Colby Knox ·f6f12e04fa ·4d ago ·parent 76d23e0
4 files changed +74
molecule/bake_board_glb.pyadded+74
@@ -0,0 +1,74 @@+#!/usr/bin/env python3+"""Bake the board's Gerber artwork and real finishes into the Altium board GLB, so any glTF viewer (the wiki's+3D viewer) shows what the renders show.++    bake_board_glb.py <board.glb> <top.png> <bottom.png> <out.glb>++- Materials: the STEP export leaves every material at glTF's default metallicFactor 1.0, so plastic, FR4 and+  silkscreen render as polished metal. Metal only for gold, tin and nickel tones (the render's rule), else+  metallic 0.05; roughness 0.35 for metal, 0.55 otherwise.+- Faces: top.png / bottom.png (textures.py: mask, copper, ENIG openings, silkscreen, holes from the Gerbers and+  drill file) as two alpha-masked quads, 12 um above the board's top face (z 0) and below its bottom face+  (z -0.4115 mm), over the 28 x 20 mm outline. bottom.png is already mirrored for viewing from below.+"""+import struct, sys+import numpy as np+from pygltflib import GLTF2, Image, Texture, Sampler, Material, PbrMetallicRoughness, TextureInfo, Mesh, Primitive, \+    Attributes, Node, Accessor, BufferView++src, top_png, bot_png, dst = sys.argv[1:5]+W, H, ZT, ZB, EPS = 0.028, 0.020, 0.0, -0.00041148, 0.000012+g = GLTF2().load(src)+blob = bytearray(g.binary_blob())++# materials: real finishes instead of all-metal+for m in g.materials:+    r, gg, b = m.pbrMetallicRoughness.baseColorFactor[:3]+    metal = (r > 0.55 and gg > 0.35 and b < 0.3) or (r > 0.5 and abs(r - gg) < 0.06 and abs(gg - b) < 0.08 and r < 0.8)+    m.pbrMetallicRoughness.metallicFactor = 0.85 if metal else 0.05+    m.pbrMetallicRoughness.roughnessFactor = 0.35 if metal else 0.55+++def add(data, target=None):+    while len(blob) % 4:+        blob.append(0)+    off = len(blob); blob.extend(data)+    g.bufferViews.append(BufferView(buffer=0, byteOffset=off, byteLength=len(data), target=target))+    return len(g.bufferViews) - 1+++def quad(z, normal_z, flip_u, img_bytes, name):+    # corners (x, y): u runs along x (reversed on the bottom, which the texture already mirrors), glTF v = 0 at the image top+    xy = [(0, 0), (W, 0), (W, H), (0, H)]+    pos = np.array([[x, y, z] for x, y in xy], np.float32)+    nrm = np.array([[0, 0, normal_z]] * 4, np.float32)+    uv = np.array([[(1 - x / W) if flip_u else x / W, 1 - y / H] for x, y in xy], np.float32)+    idx = np.array([0, 1, 2, 0, 2, 3] if normal_z > 0 else [0, 2, 1, 0, 3, 2], np.uint16)+    acc = []+    for arr, typ, ct, tgt in ((pos, "VEC3", 5126, 34962), (nrm, "VEC3", 5126, 34962), (uv, "VEC2", 5126, 34962), (idx, "SCALAR", 5123, 34963)):+        bv = add(arr.tobytes(), tgt)+        a = Accessor(bufferView=bv, componentType=ct, count=len(arr), type=typ)+        if arr is pos:+            a.min, a.max = pos.min(0).tolist(), pos.max(0).tolist()+        g.accessors.append(a); acc.append(len(g.accessors) - 1)+    g.images.append(Image(bufferView=add(img_bytes), mimeType="image/png", name=name))+    g.samplers = g.samplers or []+    if not g.samplers:+        g.samplers.append(Sampler(magFilter=9729, minFilter=9987, wrapS=33071, wrapT=33071))+    g.textures.append(Texture(source=len(g.images) - 1, sampler=0))+    g.materials.append(Material(name=name, alphaMode="MASK", alphaCutoff=0.5, doubleSided=False,+                                pbrMetallicRoughness=PbrMetallicRoughness(baseColorTexture=TextureInfo(index=len(g.textures) - 1),+                                                                          metallicFactor=0.0, roughnessFactor=0.32)))+    g.meshes.append(Mesh(name=name, primitives=[Primitive(attributes=Attributes(POSITION=acc[0], NORMAL=acc[1], TEXCOORD_0=acc[2]),+                                                          indices=acc[3], material=len(g.materials) - 1)]))+    g.nodes.append(Node(name=name, mesh=len(g.meshes) - 1))+    g.nodes[0].children.append(len(g.nodes) - 1)+++g.images = g.images or []; g.textures = g.textures or []+quad(ZT + EPS, 1, False, open(top_png, "rb").read(), "Board top (mask, copper, silkscreen from the Gerbers)")+quad(ZB - EPS, -1, True, open(bot_png, "rb").read(), "Board bottom (mask, copper, silkscreen from the Gerbers)")+g.buffers[0].byteLength = len(blob)+g.set_binary_blob(bytes(blob))+g.save_binary(dst)+print(dst, len(blob), "bytes buffer;", len(g.materials), "materials,", len(g.meshes), "meshes")
molecule/board-bottom-art.pngadded
⋯ 1 unchanged line ⋯
molecule/board-top-art.pngadded
⋯ 1 unchanged line ⋯
molecule/buck-12v5v-altium.glb
⋯ 1 unchanged line ⋯