main
John Lauer Component record TPS16850VMAR: files batch 1 6db00ed 20h ago
#!/usr/bin/env python3
"""Depth-buffered orthographic render of an uncompressed chip GLB (Z up), for previews and checks.
usage: render_glb.py model.glb out.png [--view iso|top|bottom|front|side|corner] [--size 480] [--bg 36,40,46]
iso: from front-left-up (pin-1 side, -x/+y quadrant shown at the left); top: looking down -Z, +Y up;
bottom: looking up +Z (mirrored as seen from below); front: along +Y; side: along -X."""
import sys, math, argparse, json
import numpy as np
from PIL import Image
sys.path.insert(0, __file__.rsplit("/", 1)[0])
from pygltflib import GLTF2

ap = argparse.ArgumentParser(); ap.add_argument("glb"); ap.add_argument("out")
ap.add_argument("--view", default="iso"); ap.add_argument("--size", type=int, default=480)
ap.add_argument("--bg", default="36,40,46"); a = ap.parse_args()
CT = {5126: np.float32, 5125: np.uint32, 5123: np.uint16}
g = GLTF2().load(a.glb); blob = g.binary_blob()
def acc(i):
    x = g.accessors[i]; bv = g.bufferViews[x.bufferView]; n = {"SCALAR": 1, "VEC3": 3}[x.type]
    arr = np.frombuffer(blob, CT[x.componentType], x.count * n, (bv.byteOffset or 0) + (x.byteOffset or 0))
    return arr.reshape(-1, n) if n > 1 else arr
def mat(n):
    if n.matrix: return np.array(n.matrix).reshape(4, 4).T
    M = np.eye(4)
    if n.scale: M = M @ np.diag(list(n.scale) + [1])
    return M
tris = []
def walk(ni, M):
    n = g.nodes[ni]; M = M @ mat(n)
    if n.mesh is not None:
        for p in g.meshes[n.mesh].primitives:
            P = acc(p.attributes.POSITION).astype(float) * 1000; P = (np.c_[P, np.ones(len(P))] @ M.T)[:, :3]
            I = acc(p.indices).reshape(-1, 3)
            m = g.materials[p.material]; pbr = m.pbrMetallicRoughness
            tris.append((P[I], np.array(pbr.baseColorFactor[:3]), pbr.metallicFactor or 0.0, n.name))
    for c in n.children or []: walk(c, M)
for r in g.scenes[g.scene or 0].nodes: walk(r, np.eye(4))
def R(view):
    def rz(d): t = math.radians(d); return np.array([[math.cos(t), -math.sin(t), 0], [math.sin(t), math.cos(t), 0], [0, 0, 1]])
    def rx(d): t = math.radians(d); return np.array([[1, 0, 0], [0, math.cos(t), -math.sin(t)], [0, math.sin(t), math.cos(t)]])
    return {"top": np.eye(3), "bottom": rx(180), "front": rx(-90), "side": rx(-90) @ rz(90),
            "iso": rx(-55) @ rz(-35), "corner": rx(-70) @ rz(-30)}[view]
Rm = R(a.view)
allp = np.concatenate([t[0].reshape(-1, 3) for t in tris]) @ Rm.T
lo, hi = allp[:, :2].min(0), allp[:, :2].max(0); S = a.size * 3; k = 0.86 * S / max(hi - lo)
c0 = (lo + hi) / 2
img = np.zeros((S, S, 3)); img[:] = np.array([int(v) for v in a.bg.split(",")]) / 255; zb = np.full((S, S), -1e9)
L1 = np.array([-0.4, 0.5, 0.8]); L1 /= np.linalg.norm(L1); L2 = np.array([0.6, -0.3, 0.4]); L2 /= np.linalg.norm(L2)
for T, col, metal, name in tris:
    V = T @ Rm.T
    for tri in V:
        n = np.cross(tri[1] - tri[0], tri[2] - tri[0]); ln = np.linalg.norm(n)
        if ln == 0: continue
        n /= ln
        if n[2] < 0: n = -n
        sh = 0.35 + 0.55 * max(0, n @ L1) + 0.25 * max(0, n @ L2)
        c = np.clip(col ** (1 / 2.2) * sh + (0.15 * metal * max(0, n @ L1) ** 8), 0, 1)
        xy = (tri[:, :2] - c0) * k + S / 2; xy[:, 1] = S - xy[:, 1]
        x0, y0 = np.floor(xy.min(0)).astype(int); x1, y1 = np.ceil(xy.max(0)).astype(int)
        x0, y0 = max(x0, 0), max(y0, 0); x1, y1 = min(x1, S - 1), min(y1, S - 1)
        if x1 < x0 or y1 < y0: continue
        X, Y = np.meshgrid(np.arange(x0, x1 + 1) + 0.5, np.arange(y0, y1 + 1) + 0.5)
        (ax, ay), (bx, by), (cx, cy) = xy
        d = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy)
        if abs(d) < 1e-12: continue
        w1 = ((by - cy) * (X - cx) + (cx - bx) * (Y - cy)) / d; w2 = ((cy - ay) * (X - cx) + (ax - cx) * (Y - cy)) / d; w3 = 1 - w1 - w2
        inside = (w1 >= -1e-6) & (w2 >= -1e-6) & (w3 >= -1e-6)
        z = w1 * tri[0, 2] + w2 * tri[1, 2] + w3 * tri[2, 2] + (0.002 if name.startswith("Adom.LaserEtch") else 0)
        sub = zb[y0:y1 + 1, x0:x1 + 1]; m = inside & (z > sub)
        sub[m] = z[m]; img[y0:y1 + 1, x0:x1 + 1][m] = c
im = Image.fromarray((img * 255).astype(np.uint8)).resize((a.size, a.size), Image.LANCZOS)
im.save(a.out); print(a.out)