app
Adom Chip Thumbnailer
Public Made by Adomby adom
The chip-icon factory: from a STEP file it renders the complete family of chip icons, shaded 3D orientations, transparent icons, name-lasered variants, and 11 vector outline styles, in a live web app + CLI.
master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
#!/usr/bin/env python3
"""Read a flat laser mark's geometry from STEP text: glyph faces, reading direction, lines, cap height,
plane height and colour. Used by the etch (to fit the service's mark into the planned band) and by the
legibility gate. Works in any frame where the mark lies in a horizontal plane."""
import math, re, statistics
import step_text as ST
MARK_NAMES = ("Adom.LaserEtch", "laser_etch")
def mark_name(step):
names = [n for _, n in step.products()]
for n in MARK_NAMES:
if n in names:
return n
return None
def analyse(step, name=None):
name = name or mark_name(step)
if not name:
return None
ents = step.product_entities(name)
ents -= step._frame_ents(step.rep_frames()) if getattr(step, "_flat", False) else set()
faces = []
for f in step.faces_points(ents):
if len(f["points"]) < 2:
continue
b = ST.bbox(f["points"])
faces.append(dict(id=f["id"], bbox=b, c=[(b[0] + b[3]) / 2, (b[1] + b[4]) / 2, (b[2] + b[5]) / 2]))
if not faces:
return None
allp = [f["bbox"] for f in faces]
bb = [min(b[0] for b in allp), min(b[1] for b in allp), min(b[2] for b in allp), max(b[3] for b in allp), max(b[4] for b in allp), max(b[5] for b in allp)]
sx = statistics.pstdev([f["c"][0] for f in faces]) if len(faces) > 1 else bb[3] - bb[0]
sy = statistics.pstdev([f["c"][1] for f in faces]) if len(faces) > 1 else bb[4] - bb[1]
axis = "x" if sx >= sy else "y"
ai = 0 if axis == "x" else 1
mid = (bb[ai] + bb[ai + 3]) / 2
first = faces[0]["c"][ai]
sign = 1 if first <= mid else -1 # the service writes glyphs in reading order
u = [0.0, 0.0, 0.0]; u[ai] = float(sign)
v = [-u[1], u[0], 0.0]
vi = 1 - ai; vs = v[vi]
# lines: cluster glyph centres along the text-up direction
ext_v = [f["bbox"][vi + 3] - f["bbox"][vi] for f in faces]
hmax = max(ext_v)
order = sorted(faces, key=lambda f: f["c"][vi] * vs, reverse=True) # top line first
gaps = [(order[i]["c"][vi] * vs - order[i + 1]["c"][vi] * vs, i) for i in range(len(order) - 1)]
lines = [order]
if gaps:
g, i = max(gaps)
if g > 0.6 * hmax:
lines = [order[: i + 1], order[i + 1:]]
out_lines = []
for ln in lines:
e = [f["bbox"][vi + 3] - f["bbox"][vi] for f in ln]
big = [x for x in e if x >= 0.6 * max(e)]
lo = min(f["bbox"][ai] for f in ln); hi = max(f["bbox"][ai + 3] for f in ln)
vlo = min(f["bbox"][vi] for f in ln); vhi = max(f["bbox"][vi + 3] for f in ln)
out_lines.append(dict(faces=[f["id"] for f in ln], capMm=statistics.median(big), lengthMm=hi - lo,
box=[min(f["bbox"][0] for f in ln), min(f["bbox"][1] for f in ln), max(f["bbox"][3] for f in ln), max(f["bbox"][4] for f in ln)],
centre=[(min(f["bbox"][0] for f in ln) + max(f["bbox"][3] for f in ln)) / 2, (min(f["bbox"][1] for f in ln) + max(f["bbox"][4] for f in ln)) / 2]))
return dict(name=name, faces=faces, bbox=bb, axis=axis, u=u, v=v, lines=out_lines, z=bb[5], flatMm=bb[5] - bb[2],
capMm=min(l["capMm"] for l in out_lines), centre=[(bb[0] + bb[3]) / 2, (bb[1] + bb[4]) / 2, (bb[2] + bb[5]) / 2])
def colour(step, name):
"""sRGB of the mark (STEP COLOUR_RGB on styled items of its geometry), or None."""
ents = step.product_entities(name) or set()
for k in step.order:
if step.kind(k) == "STYLED_ITEM" and set(step.refs(k)) & ents:
for x in step.reach([k]):
if step.kind(x) == "COLOUR_RGB":
m = re.findall(r"[-+0-9.Ee]+", step.body[x].split(",", 1)[1])
return [float(t) for t in m[:3]]
return None
# ---- iso outline projection (service-step2glb /outline camera looks from +X+Y+Z; SVG y down) ----------
R2, R6 = math.sqrt(2), math.sqrt(6)
def iso(p):
x, y, z = p
return ((-x + y) / R2, (x + y - 2 * z) / R6)
def iso_inv(X, Y, z):
"""The point on the plane Z = z that projects to (X, Y)."""
a = R6 * Y + 2 * z; b = R2 * X
return [(a - b) / 2, (a + b) / 2, z]
def vertices(st):
out = []
for k in st.order:
if st.kind(k) == "VERTEX_POINT":
c = st.coords(st.refs(k)[0])
if c and len(c) == 3:
out.append(c)
return out
def calibrate(svg_polys, pts3):
"""Scale and offset of an outline drawing: its strokes' extents against the model's projected points
(orthographic, so one scale; `agree` is the X/Y scale mismatch, a self-check)."""
P = [p for t in svg_polys for p in t["pts"]]
V = [iso(v) for v in pts3]
wx = max(v[0] for v in V) - min(v[0] for v in V); wy = max(v[1] for v in V) - min(v[1] for v in V)
sx = (max(p[0] for p in P) - min(p[0] for p in P)) / wx; sy = (max(p[1] for p in P) - min(p[1] for p in P)) / wy
s = (sx + sy) / 2
o = (min(p[0] for p in P) - s * min(v[0] for v in V), min(p[1] for p in P) - s * min(v[1] for v in V))
return dict(s=s, o=o, agree=abs(sx - sy) / s)