master
John Lauer Datasheet-true STEP height (0.55 mm), side MPN mark re-applied; GLBs, hero rebuilt without Z stretch 2e9a25d 20d ago
#!/usr/bin/env python3
"""Pick the face region a SIDE laser-etch mark must stay inside (Z-up STEP, already turned so the chosen side
faces +Z). Candidates: planar faces lying flat at the model's top Z (the turned side's outermost plane). Of the
candidates with at least a quarter of the largest candidate's area, take the one furthest along --up (the
part's original +Z expressed in this frame): the moulded body/lens band above a PCB substrate or the leads,
never the seam between them. Prints JSON: region bbox, z, face colour (linear RGB) and the candidate list.
Usage: side_region.py in.step --up ux,uy,uz"""
import sys, json, argparse
from OCP.STEPCAFControl import STEPCAFControl_Reader
from OCP.TDocStd import TDocStd_Document
from OCP.TCollection import TCollection_ExtendedString
from OCP.XCAFDoc import XCAFDoc_DocumentTool, XCAFDoc_ColorType
from OCP.TDF import TDF_ChildIterator, TDF_Label
from OCP.TopLoc import TopLoc_Location
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_FACE
from OCP.Bnd import Bnd_Box
from OCP.BRepBndLib import BRepBndLib
from OCP.GProp import GProp_GProps
from OCP.BRepGProp import BRepGProp
from OCP.Quantity import Quantity_Color
ap = argparse.ArgumentParser(); ap.add_argument('src'); ap.add_argument('--up', required=True); ap.add_argument('--pick', default='high', choices=['high', 'low']); a = ap.parse_args()
up = [float(v) for v in a.up.split(',')]
doc = TDocStd_Document(TCollection_ExtendedString("d"))
r = STEPCAFControl_Reader(); r.SetColorMode(True); r.SetNameMode(True); r.ReadFile(a.src); r.Transfer(doc)
st = XCAFDoc_DocumentTool.ShapeTool_s(doc.Main()); ct = XCAFDoc_DocumentTool.ColorTool_s(doc.Main())
faces = []
def colour(lab, f):
    c = Quantity_Color()
    for t in (XCAFDoc_ColorType.XCAFDoc_ColorSurf, XCAFDoc_ColorType.XCAFDoc_ColorGen):
        if ct.GetColor(f, t, c): return [c.Red(), c.Green(), c.Blue()]
    for t in (XCAFDoc_ColorType.XCAFDoc_ColorSurf, XCAFDoc_ColorType.XCAFDoc_ColorGen):
        if ct.GetColor_s(lab, t, c): return [c.Red(), c.Green(), c.Blue()]
    return None
def walk(l, loc):
    if st.IsReference_s(l):
        ref = TDF_Label(); st.GetReferredShape_s(l, ref); return walk(ref, loc.Multiplied(st.GetLocation_s(l)))
    if st.IsAssembly_s(l):
        it = TDF_ChildIterator(l, False)
        while it.More(): c = it.Value(); it.Next(); walk(c, loc)
        return
    ex = TopExp_Explorer(st.GetShape_s(l), TopAbs_FACE)
    while ex.More():
        f = ex.Current(); ex.Next(); fw = f.Moved(loc)
        b = Bnd_Box(); BRepBndLib.Add_s(fw, b); mn, mx = b.CornerMin(), b.CornerMax()
        g = GProp_GProps(); BRepGProp.SurfaceProperties_s(fw, g)
        faces.append(dict(bbox=[mn.X(), mn.Y(), mn.Z(), mx.X(), mx.Y(), mx.Z()], area=g.Mass(), rgb=colour(l, f)))
it = TDF_ChildIterator(st.Label(), False)
while it.More():
    l = it.Value(); it.Next()
    if st.IsFree_s(l) and st.IsTopLevel(l): walk(l, TopLoc_Location())
top = max(f['bbox'][5] for f in faces)
cand = [f for f in faces if f['bbox'][5] - f['bbox'][2] < 2e-3 and top - f['bbox'][5] < 2e-3 and f['area'] > 1e-6]
big = max(f['area'] for f in cand)
cand = [f for f in cand if f['area'] >= 0.25 * big]
cen = lambda f: [(f['bbox'][0] + f['bbox'][3]) / 2, (f['bbox'][1] + f['bbox'][4]) / 2, (f['bbox'][2] + f['bbox'][5]) / 2]
pick = (max if a.pick == 'high' else min)(cand, key=lambda f: sum(c * u for c, u in zip(cen(f), up)))   # --pick low: the body band under a window that runs the full length (WS2812B-2020)
print(json.dumps(dict(region=[pick['bbox'][0], pick['bbox'][1], pick['bbox'][3], pick['bbox'][4]], z=top, area=pick['area'], rgb=pick['rgb'],
                      candidates=[dict(region=[round(f['bbox'][0], 4), round(f['bbox'][1], 4), round(f['bbox'][3], 4), round(f['bbox'][4], 4)], area=round(f['area'], 4)) for f in cand])))