Closed bug report

Lasered part number lands on the +Z face for every up-axis, so down orientations render it mirrored

Ray · 1mo ago ·edited ·closed by Ray

With --name, the lasered part number is always placed on the +Z face, whatever --up-axis says. On any down orientation (zDown, yDown, xDown) the marking therefore sits on the face pointing away from the camera, and because the glyphs are projected to 2D and drawn over the PNG without a depth test, they come out mirror-reversed across the part.

Split out of #12 (thumbnail zDown returned the top view). #12 is fixed and deployed; this one is independent and was not introduced by that fix.

Repro

step2glb thumbnail BSC016N06NS-ai-nominal.step --pose iso --up-axis zDown \
  --name BSC016N06NS --width 640 --height 480 -o iso-zdown-name.png

The part number reads backwards, lying across the exposed drain pad on the underside — a face that carries no marking on the real part.

Same on the top pose now that --pose top --up-axis zDown correctly shows the underside:

step2glb thumbnail BSC016N06NS-ai-nominal.step --pose top --up-axis zDown \
  --name BSC016N06NS --width 640 --height 480 -o top-zdown-name.png

Cause

_up_for_axis() in api/step_to_thumbnail.py maps the up-axis to a bare axis letter:

def _up_for_axis(up_axis):
    a = (up_axis or "").lower()
    if a in ("y", "yup", "ydown", "-y"):
        return "y"
    return "z"

zDown and asIs both return "z", yUp and yDown both return "y". The sign is dropped. _top_slice_inplane_bbox() and _name_world_polylines() then both take the max end of that axis as the top face, so a part authored upside-down gets its part number on its underside.

up_axis documents which source axis is the chip's up, so the chip's marked face is the one at the up end of it. For zDown that is zmin, not zmax.

xUp and xDown are worse than mirrored: neither is listed, so they fall through to "z" and the marking goes on a face 90 degrees away from the one they name.

Not a one-line change

Threading the sign through means _top_slice_inplane_bbox() has to slice from the other end, and _name_world_polylines() has to flip the in-plane basis to stay right-handed. It already carries a hand-correction for the y-up case, so getting the other five wrong would render the glyphs backwards on the correct face instead of forwards on the wrong one. The rule is that the basis (e_u, e_v, n) must be right-handed with n the outward normal of the marked face, which is a different flip for each of the six.

Correction to an earlier version of this issue: I first wrote that this sits in the laser-etch fitting code reworked in #13/#14/#15. It does not, and the separation is worth stating because it makes this safer to change than it first looked. /laser-etch goes through laser_etch.py to laser_fit.py, which takes no up-axis at all and finds the marked face geometrically via largest_planar_normal(). The thumbnail --name overlay is the older, independent path in step_to_thumbnail.py, last touched before that work landed. _up_for_axis() has exactly one caller. So the fit report is not at risk here; the verification this needs is a rendered check of all six orientations, not a re-run of the #13/#14/#15 fitting evidence.

Verified pre-existing: an iso/zDown render with --name is byte-identical before and after the #12 fix.

Ray is taking this one — it is next in the STEP-engine queue behind #17.

repro-iso-zDown-name-mirrored

repro-top-zDown-name-mirrored

1 Reply

Ray · 1mo ago

Fixed and deployed. Service 1.3.4 (dcde0b9a), live now.

_up_for_axis() returned a bare axis letter and dropped the sign, so the marking went onto +Z whatever --up-axis said. On a down orientation that is the face pointing away from the camera, and since the glyphs are projected to pixels with no depth test, they were drawn straight through the part, reversed.

up_axis names the axis the chip was authored up along, so the marked face is the one at the signed end of it: a zDown part's top face is at zmin. _up_for_axis now returns (axis, sign), the top-slice is taken from that end, the glyphs are lifted outward along the face normal rather than always +up, and the in-plane basis is flipped per face so that (e_u, e_v, outward normal) stays right-handed. That last part is the one that matters: it is what makes the text read forwards to a camera facing the face instead of mirrored, and it is a different flip for each of the six.

xUp and xDown were worse than mirrored. Neither was listed in _up_for_axis at all, so both fell through to "z" and put the part number on a face ninety degrees away from the one they name. They now land on the ±X face.

Verified on production, BSC016N06NS, iso pose, all six orientations, each byte-identical to the local render:

orientation vs before
asIs, yUp unchanged
zDown, yDown, xUp, xDown changed

asIs and yUp staying byte-identical is deliberate — they are the two the old code rendered correctly, including the y-up hand-correction added by hand in aded0e4, and the new per-face rule reproduces both rather than replacing them. The 30-cell pose × orientation matrix without --name is 30/30 unchanged, so nothing outside this path moved.

Handedness was proved rather than eyeballed. Rotating the STEP 180° about X turns the marked face into an ordinary +Z top, so rendering that plainly is an independent route to the same picture. Comparing glyph masks against the zDown render of the original gives IoU 0.66 with the in-plane rotation matched and 0.20 without — so the two routes differ by an in-plane rotation, not a mirror. That residual 180° is convention and --name-rot selects it; this keeps the convention the existing tuned renders use.

One note for anyone reproducing this: the --name overlay is reachable through the HTTP API (POST /thumbnail?name=…), not through step2glb thumbnail, which does not expose a --name flag.

PR: https://github.com/adom-inc/service-occt/pull/9 — api/test_name_face.py pins the six signed axes, the right-handedness of all six bases, and that the glyphs land on the marked face, outside it, and inside the footprint.

Log in to reply.