main
John Lauer LODs, models.json, verification log, provenance and tools, README, license c543194 17h ago
#!/usr/bin/env python3
"""Overlay proof for the CX3225GA model: orthographic projections of the plain STEP blended over the
Kyocera catalog drawing (600 dpi render of cx3225ga_e.pdf). The drawing is not drawn to one scale, so
each view is calibrated per axis on its own printed body edges (3.2 mm and 2.5 mm, 0.85 mm height).
Views: top (whole model), front (X, Z), end (Y, Z), bottom (electrodes only; third-angle bottom view:
X kept, Y flipped, as Kyocera draws it).
usage: overlay_cx.py model.step datasheet-600dpi.png out.png"""
import sys
import numpy as np
import cadquery as cq
from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageChops

step, page_png, out = sys.argv[1:4]
asm = cq.importers.importStep(step)
solids = asm.solids().vals()


def tess(sel):
    V, T = [], []
    for s in sel:
        v, t = s.tessellate(0.002, 0.1)
        o = len(V)
        V += [[p.x, p.y, p.z] for p in v]
        T += [[a + o, b + o, c + o] for a, b, c in t]
    return np.array(V), np.array(T)


ALL = tess(solids)
# electrodes are the solids that touch z = 0
ELEC = tess([s for s in solids if s.BoundingBox().zmin < 1e-6 and s.BoundingBox().zmax < 0.3])

# (title, crop, mesh, axis_u, axis_v, sign_v, centre_px, scale_u, scale_v)
# centres and scales measured on the drawing (see provenance/3d-model.json "overlay")
VIEWS = [
    ("Top view (x 157, y 141.8 px/mm)", (700, 5170, 1360, 5640), ALL, 0, 1, 1, (1028.75, 5404.25), 157.0, 141.8),
    ("Front view (x 155.8, z 144.7 px/mm)", (700, 5700, 1360, 5930), ALL, 0, 2, 1, (1028.75, 5880.5), 155.8, 144.7),
    ("End view (y 128.8, z 144.7 px/mm)", (1680, 5700, 2090, 5930), ALL, 1, 2, 1, (1882.5, 5880.5), 128.8, 144.7),
    ("Bottom view, electrodes (x 157, y 144.8 px/mm)", (700, 6040, 1360, 6520), ELEC, 0, 1, -1, (1028.75, 6279.5), 157.0, 144.8),
]
page = Image.open(page_png).convert("L")
SS = 4
try:
    font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
    small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 18)
except Exception:
    font = small = ImageFont.load_default()
panels = []
for title, (x0, y0, x1, y1), (V, T), au, av, sv, (cx, cy), su, svs in VIEWS:
    W, H = x1 - x0, y1 - y0
    px = (cx - x0 + V[:, au] * su) * SS
    # top view: model +y is up on paper; bottom view (third angle): model +y is down on paper
    py = (cy - y0 - sv * V[:, av] * svs) * SS
    m = Image.new("L", (W * SS, H * SS), 0)
    d = ImageDraw.Draw(m)
    for t in T:
        d.polygon([(px[i], py[i]) for i in t], fill=255)
    m = m.resize((W, H), Image.LANCZOS).point(lambda v: 255 if v > 127 else 0)
    edge = ImageChops.subtract(m, m.filter(ImageFilter.MinFilter(3)))
    base = page.crop((x0, y0, x1, y1)).convert("RGB")
    tint = Image.new("RGB", (W, H), (0, 170, 220))
    o = Image.composite(Image.blend(base, ImageChops.multiply(base, tint), 0.55), base, m)
    o = Image.composite(Image.new("RGB", (W, H), (230, 30, 30)), o, edge)
    pan = Image.new("RGB", (W, H + 34), "white")
    pan.paste(o, (0, 34))
    ImageDraw.Draw(pan).text((6, 6), title, fill="black", font=font)
    panels.append(pan)
cap = ["CX3225GA08000D0PTVTT model (red outline, blue fill) over Kyocera's CX3225GA catalog drawing (As of October 2025), 600 dpi.",
       "The drawing is not drawn to one scale (3.2 mm = 157 px but 2.5 mm = 142 to 145 px, and 129 px in the end view), so each view is",
       "calibrated on its own printed body edges. Printed: 3.2 x 2.5 x 0.85, 0.53 base, 0.6 electrode gap, R0.3, 4-R0.15, 0.5 and 0.25 castellation.",
       "Measured off the drawing: electrode height 2.28, outer arc 1.24 to 1.48 from centre, lid inset 0.10, castellation depth 0.10."]
cols = 2
rows = [panels[i:i + cols] for i in range(0, len(panels), cols)]
Wt = max(sum(p.width for p in r) + 20 * (len(r) + 1) for r in rows)
Wt = max(Wt, 40 + max(int(small.getlength(c)) for c in cap))
Ht = sum(max(p.height for p in r) + 20 for r in rows) + 30 + 26 * len(cap)
sheet = Image.new("RGB", (Wt, Ht), "white")
y = 20
for r in rows:
    x = 20
    for p in r:
        sheet.paste(p, (x, y))
        x += p.width + 20
    y += max(p.height for p in r) + 20
dr = ImageDraw.Draw(sheet)
y += 10
for line in cap:
    dr.text((20, y), line, fill="black", font=small)
    y += 26
sheet.save(out)
print("wrote", out, sheet.size)