main
John Lauer Hero billboard re-rendered from the new model; symbol chip outline redrawn from the new model's etched mark (KiCad symb… 56f2c72 15d ago
#!/usr/bin/env python3
"""Named chip outline drawn from the etched model's own mark edges, in adom-aiflow-etch's standard pose.
`adom-aiflow-etch outline` today only composes (service body outline + its single-stroke font on the
band); it no longer draws the etched STEP's own mark. This does that part itself, reusing the tool's
pose, iso projection and calibration:
  1. pose the plain and the etched STEP exactly as the tool does (plan stand-up + outline quarter turns);
  2. the service draws the plain body outline; calibrate() fits its scale/offset to the projected vertices;
  3. the mark = the etched STEP's edges that the plain STEP does not have (CadQuery, top contour only),
     projected with the same iso() and calibration, appended as <g id="adom-mark-name">;
  4. the tool's own gate (mark_check.check_outline) is run on the result.
usage: mark_art.py plain.step etched.step plan.json out.svg"""
import sys, os, json, html, tempfile
sys.path.insert(0, "/home/adom/project/adom_modules/adom/adom-aiflow/tools/laser-etch")
import numpy as np
import cadquery as cq
import mark_outline as MO, mark_svc as SVC, mark_check as MC, mark_geom as MG

plain, etched, plan_path, out = sys.argv[1:5]
plan = json.load(open(plan_path))
to_e, q_o, e_to_o = MO.frames(plan)
body = MO.posed(plain, plan, [("z", q_o)])
for nm in [n for _, n in body.products() if n in MG.MARK_NAMES]:
    body.remove_product(nm)
svg = SVC.outline(body.text, job=f"outline-{plan['mpn']}-body")["outlines"]["dark"]
verts = MO.vertices(body)
c1 = MO.calibrate(MC._polylines(svg), verts)
e = MO.posed(etched, plan, [("z", q_o)])

def edges(text):
    f = tempfile.NamedTemporaryFile(suffix=".step", delete=False); f.write(text.encode()); f.close()
    shp = cq.importers.importStep(f.name).val(); os.unlink(f.name)
    res = []
    for ed in shp.Edges():
        n = max(2, int(ed.Length() / 0.02) + 1)
        res.append(np.array([[p.x, p.y, p.z] for p in ed.positions(np.linspace(0, 1, n).tolist())]))
    return res

pe, ee = edges(body.text), edges(e.text)
pm = np.array([p[len(p) // 2] for p in pe] + [p[0] for p in pe])
mark = []
for p in ee:
    mid = p[len(p) // 2]
    if np.min(np.linalg.norm(pm - mid, axis=1)) > 1e-3:
        mark.append(p)
ztop = max(p[:, 2].max() for p in mark)
mark = [p for p in mark if p[:, 2].min() > ztop - 1e-3]         # top contour only (no side walls, no floor)
attrs = ' fill="none" stroke="#e6edf3" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="round"'
def m(v):
    q = MG.iso(v); return (c1["o"][0] + c1["s"] * q[0], c1["o"][1] + c1["s"] * q[1])
g = "".join('<polyline points="' + " ".join("%.2f,%.2f" % m(v) for v in p) + '"' + attrs + "/>" for p in mark)
L = plan["text"]["lines"]
open(out, "w").write(svg.replace("</svg>", '<g id="adom-mark-name" data-source="etched-step-mark-edges" data-lines="'
                                 + html.escape("|".join(L), quote=True) + '">' + g + "</g></svg>"))
a = MG.analyse(e)
rep = MC.check_outline(out, plan, a["centre"] if a else e_to_o([0, 0, 0]), SVC.step_meta(body.text, "outline-check-meta"),
                       a["bbox"] if a else None, verts=verts)
print(json.dumps(dict(out=out, markEdges=len(mark), calib=c1, gate=rep), default=str))