Open bug report

footprint/symbol generation 502s on EAGLE .brd input (blocks all Fusion-sourced molecules)

adonis · 1mo ago

Summary

step2glb convert --board <file>.brd reliably fails footprint + symbol generation with HTTP Error 502: Bad Gateway when the board is an EAGLE/Fusion .brd. Anchoring, gold-pin upgrade, meter normalization and Draco optimization all succeed in the same job — only the footprint/symbol stage 502s.

Because footprint_applied: true / footprint_pins: 4 is a hard publish gate in molecule-publish, this blocks publishing ANY Fusion-sourced molecule: the GLB is perfectly anchored but has no wire attach points.

Reproducer

step2glb convert nametag-blank-large-led.step \
  --board nametag-blank-large-led.brd \
  -o nametag-blank-large-led.glb

Output (identical across 3 consecutive runs):

OK: molecule GLB written (128 KB, 33 meshes, 63 nodes)
  molecule_anchored: true
  molecule_up_axis: "z"
  fl_marker: "PIN_LRG:MP1"
  pin_size: "large"
  pin_size_source: "markers"
  gold_upgraded: 9
WARN: footprint gen failed: HTTP Error 502: Bad Gateway

Job IDs (all status: complete, error: null, footprint_applied: null): 563332801b42997f, bba1c3e060f2a8cd, 22ede60248fe706e

stage_ms shows footprint_join: 0 — the stage never ran.

Both services are HEALTHY — this is not an outage

service-step2glb  HTTP 200  v1.2.1 (8ad7d0d4)  jobs_failed: 0  workers idle
service-kicad     HTTP 200  v1.1.0             kicad_version: 10.0.2

/health on service-step2glb reports kicad_base: https://kicad-rk5ue5pcfemi.adom.cloud, so footprint generation is an upstream call into service-kicad. That service answers 200 on /health, so the 502 is specific to the footprint-generation call, not the host.

Suspected cause

service-kicad is a KiCad service and its native board input is .kicad_pcb. We are handing it an EAGLE .brd. This lines up with a known seam already documented in molecule-publish § Stage 3.5: "KiCad sources render today; EAGLE/Fusion (.brd/.sch) pages report 'nothing rendered' and skip gracefully." Footprint gen appears to hit the same gap, but fails hard with a 502 instead of degrading gracefully.

Asks

  1. Support EAGLE .brd in footprint/symbol generation (KiCad 10 can import EAGLE boards, so an import-then-generate path on service-kicad looks viable).
  2. Failing that, surface a real error instead of a 502 — e.g. footprint gen: EAGLE .brd not supported, convert to .kicad_pcb first. The bare "502 Bad Gateway" reads as a transient outage and cost real debugging time before kicad_base in /health pointed at the actual upstream.

Environment

  • step2glb CLI 0.12.0 (binary reports 1.2.1), Linux container
  • Board: 160 x 48 mm, 1.6 mm, 2-layer, 21 elements, exported from Fusion 360 Electronics via fusion_export_eagle_source (fusion-bridge v1.9.237)
  • Reported by adonis

8 Replies

Ray · 1mo ago

Dug into this properly. The short version is that both asks are already resolved, and the reason you are still blocked is a different thing entirely: the upstream service is down, and has been since at least Aug 28.

The diagnosis in the report is wrong on two counts

Worth correcting because it is what cost you the afternoon, and it would cost the next person the same.

1. The upstream is not service-kicad. Footprint and symbol generation calls service-molecule-metadata, not service-kicad. From api/job_queue.py:

MOLECULE_METADATA_BASE = os.environ.get(
    "MOLECULE_METADATA_API", "https://molecule-metadata-89jotvl9tqs0.adom.cloud")
...
req = urllib.request.Request(f"{MOLECULE_METADATA_BASE}/api/{route}", ...)

kicad_base in /health is a real dependency of the service, just not this one. Reading it and concluding service-kicad was the culprit was a completely reasonable inference from what /health shows, which is why the error message has been fixed (below).

2. It is not EAGLE-specific. .brd is a first-class route, not an unhandled case:

route = {".kicad_pcb": "convert-pcb", ".brd": "convert-brd"}.get(ext)

And upstream, brd-importer is a shipped package sitting beside kicad-importer, with its own golden fixture. I confirmed a .kicad_pcb fails identically right now:

demo.brd       -> upstream service-molecule-metadata is not answering (HTTP 502)
demo.kicad_pcb -> upstream service-molecule-metadata is not answering (HTTP 502)

So ask 1, EAGLE .brd support, needs no work. It already exists.

The actual cause: the upstream is down

https://molecule-metadata-89jotvl9tqs0.adom.cloud returns 502 on every path, including /health. Still 502 as of 2026-08-31 21:57 UTC.

The 502 comes from Cloudflare, not from the app (server: cloudflare, 16-byte plain-text body), which means the edge cannot reach the origin at all. That is a stopped container or a dead process, not a bug.

The code is fine. I cloned adom-inc/molecule-metadata-importer, installed, built, ran it, and posted its own golden fixture:

POST /api/convert-pcb  BMA400.kicad_pcb  ->  200, 9838 bytes
{board, symbol, meta}    machinePins: 4    contacts: 7    symbol pins: 11

That is exactly the payload _apply_footprint_subprocess consumes. The moment someone starts that service, footprint and symbol generation works again and this issue closes itself.

I cannot restart it: the container is not on my ACL (containers get 89jotvl9tqs0 returns CONTAINER_NOT_FOUND). Whoever owns it needs to bring it back. If it is easier to move it, standing the importer up elsewhere and repointing MOLECULE_METADATA_API is a one-variable change on service-step2glb.

Ask 2 is shipped and live

The bare HTTP Error 502: Bad Gateway is gone. The warning now names the upstream, the route and the board, and separates "that host is down" from "your board is wrong", because those need opposite responses:

footprint gen failed: upstream service-molecule-metadata is not answering (HTTP 502) at https://molecule-metadata-89jotvl9tqs0.adom.cloud/api/convert-brd. This is an upstream outage, not a problem with nametag-blank-large-led.brd, and every board format is affected equally; footprint and symbol generation stay unavailable until it returns.

URLError and the generic case now carry the URL too. Live in service-step2glb 1.3.0 (1fe2212a).

Also live in 1.3.0, since it is the same deploy

/name-emboss no longer strips XCAF colours (#7). It read with STEPControl_Reader and wrote with STEPControl_Writer, the plain geometry classes, which carry no colour document. Verified against the live endpoint:

POST /name-emboss  ->  colors_in: 30, colors_out: 290

It was 0 out. The response now also reports colors_in / colors_out and warns when everything is lost, so it cannot fail silently again.

Leaving this open

Both asks on this issue are done, but you are still blocked, and will be until that container comes back. It seemed wrong to close an issue whose reporter still cannot publish a molecule.

Ray · 1mo ago

Closing: the block was the dead upstream, and it is back.

As of 2026-09-11 23:30 UTC, https://molecule-metadata-89jotvl9tqs0.adom.cloud answers from the app again. POST /api/convert-pcb with a real molecule board (74LVC1G86 SE-7) returns 200 with {board, symbol, meta}: 4 machinePins, 5 contacts. POST /api/convert-brd is served by the same app (a bogus file gets its own "Invalid BRD file format", not an edge 502), which is the route a .brd takes; I have no EAGLE .brd on this machine to run your exact reproducer, so if step2glb convert --board <file>.brd still warns on footprint generation, reopen with the job id and the warning text, which since 1.2.1 names the upstream and the route.

Nothing changed on the .brd support itself; as the earlier reply showed, .brd has been a first-class route all along. Current CLI is step2glb 0.13.0 (adom/adom-step2glb 1.3.0).

Colby Knox · 1mo ago

Reopening per the closing note: .brd footprint generation still fails, but with a new error, and it is not specific to this board.

Reproducer (2026-09-17, step2glb 0.11.2 CLI):

step2glb convert nametag-blank-large-led.step --board nametag-blank-large-led.brd --pin large
  molecule_anchored: true, fl_marker: "PIN_LRG:MP1", pin_size: large (markers)
WARN: footprint gen failed: service-molecule-metadata returned HTTP 500 for convert-brd on board.brd
  {"error":"Error processing file: No machine pins found; cannot establish origin."}

Job 1393f5ae8ecbac4e.

Control: a board that already shipped as a molecule fails the same way. Posting adom/bq25792-charger's own bq25792-charger.brd (14 elements MP1-MP14, packages PIN_MED / PIN_MED_CONTACT_WITH3D, library Adom_Library_v6) straight to the upstream:

curl -F [email protected] https://molecule-metadata-89jotvl9tqs0.adom.cloud/api/convert-brd
HTTP 500 {"error":"Error processing file: No machine pins found; cannot establish origin."}

What I ruled out on the nametag board: renaming the library from Adom Library OLD Don't Use to Adom_Library_v6, and relabelling the PIN_LRG elements as PIN_MED. Both still return the same 500. So the brd importer's machine-pin detection looks broken for every EAGLE/Fusion board, not a naming quirk of one file.

Blocks: adonis/nametag-blank-large-led becoming a wireable molecule (no _footprint.json / _symbol.json).

Ray · 1mo ago

Reproduced, and it isn't in step2glb or service-occt — the 500 comes from the brd importer's classifier upstream. I used your control board, bq25792-charger.brd, against /api/convert-brd directly, changing one thing at a time:

change result
as-is (PIN_MED, drill 1.2; contacts 0.84) 500, "No machine pins found"
package PIN_MED → PIN_MED_STANDARD, drill untouched 200, 4 machine pins, 0 contacts
+ contact drill 0.84 → 0.78 200, 4 pins, 9 contacts

Two causes, both in packages/contracts/src/classification.ts of molecule-metadata-importer:

  1. refinePinType (line ~158) returns null unless the package name contains short/sht/standard/std. PIN_MED and PIN_LRG from Adom_Library_v6 carry neither, so every pin is dropped after its drill already matched. That's why renaming the library didn't help: the name check wants a length word, not a library.
  2. Contacts match only 0.78 or 0.81 mm (DRILL_TOLERANCE 0.001). The library's contacts drill 0.84, so all nine are dropped even when the pins pass. Large sizes have no legacy values at all (the TODO on LEGACY_DRILL_BUCKETS), which is the nametag PIN_LRG case on top.

Production does include the June change that accepts legacy 1.2 mm pins, so this isn't a stale deploy — the same board fails on current main.

The fix belongs in molecule-metadata-importer: make the length keyword optional when the drill alone is unambiguous, and add 0.84 as a known contact size (and the large legacy sizes once known). Happy to send a PR if you'd rather. step2glb's WARN already names the upstream, route and error, so nothing changes on this side.

Colby Knox · 1mo ago

That lines up with what I saw, and it explains why renaming the library changed nothing. Yes please, send the PR to molecule-metadata-importer.

Legacy sizes from the two boards in hand, for the drill buckets:

package drill pad source
PIN_LRG 3.45 mm 5.2 mm adonis/nametag-blank-large-led, library "Adom Library OLD Don't Use"
PIN_MED 1.2 mm 1.6 mm same board, and adom/bq25792-charger (Adom_Library_v6)
PIN_MED_CONTACT_WITH3D 0.84 mm 1.3 mm adom/bq25792-charger

The large pin's 3.45 / 5.2 pair matches the current MachinePinLargeShort and MachinePinLargeStandard footprints exactly, so 3.45 is unambiguous for large. Medium is 1.2 on both the short and standard pages. So the drill alone does resolve the size on every one of these; only the short-versus-standard length stays unknown, and that is a length, not a size.

For what it is worth, the nametag board's STEP does name the length (MachinePin_Large_Short_Gold, MachinePin_Medium_Short_Gold), but the .brd does not carry it anywhere, so a .brd-only import cannot recover it. Defaulting to standard and saying so in the response seems better than dropping the pin.

Leaving this open since adonis/nametag-blank-large-led still has no _footprint.json / _symbol.json. Ping me when the importer ships and I will re-run the conversion and publish the molecule metadata.

Ray · 1mo ago

PR is up: https://github.com/adom-inc/molecule-metadata-importer/pull/6 — yours to review and merge, I have not touched main.

Your three sizes are in, and the two boards both parse now:

board before after
bq25792-charger.brd 0 of 13 kept, 500 4 medium pins + 9 medium contacts = 13 of 13
nametag-blank-large-led.brd 0 of 6 kept, 500 4 large + 2 medium pins = 6 of 6

One correction on the count: that board has 13 MP elements, not 14 — the numbering skips MP10. So 4 + 9 is all of them rather than one short, and nobody needs to go hunting for a missing pin.

Defaulting to standard and saying so is what it does: a hint with no length keyword now gives standard with lengthAssumed: true, and the flag rides through to board.machinePins[].lengthAssumed. All six nametag pins and all four bq pins come back flagged, which is correct — a .brd cannot carry the length. The field is optional so existing payloads still validate; contract version 2.1.0 → 2.2.0 for the addition.

One thing I changed from the obvious fix, and I want your eye on it. Taking 0.84 as a contact on the drill alone gives 49 contacts instead of 9 on your control board. It is PIN_MED_CONTACT_WITH3D's size, but it is also an ordinary through-hole size, and on that board it is used by six unrelated packages — XT60PW-M_20.8A_NEST, USBC, RASM7XX_DC_BARREL_JACK, 3X1_JUMP, 2X1_JUMP, 3X1_COMPONENT — for 40 pads against your 9 real ones. Every jumper header and barrel jack would have become a molecule contact: a wrong answer that looks like a right one, which is worse than the 500 it replaces, and the same misclassification the strict tolerance exists to prevent.

So 0.84 counts as a contact only when the package name says contact. 0.78, 0.81 and 2.62 are molecule-specific and still need no corroboration. If you would rather gate it some other way — an explicit package allowlist, or the 1.3 pad as the second signal — say so and I will rework it; the drill alone was the one option I was not willing to ship.

On the large sizes: 3.45 / 5.2 matching MachinePinLargeShort and MachinePinLargeStandard exactly means legacy large is the nominal value, so it needed no new entry — that answers the open TODO in the legacy block rather than adding to it. The pad diameters from your table are recorded next to the drills, since they are what make each drill unambiguous.

The repo had no tests, so this adds the first ones (node:test via tsx, wired into npm test): one per change plus guards that the tolerance and the unambiguous sizes do not loosen. The over-capture guard is load-bearing — I removed the corroboration on a throwaway copy and that test fails while the other eight pass. Build, check and test are green across all four workspaces.

Leaving this issue open as you asked, until adonis/nametag-blank-large-led has its _footprint.json and _symbol.json. Ping me once the importer ships and I will re-run the conversion end to end through the STEP engine if that helps.

Colby Knox · 1mo ago

One more data point for the large-pin drill buckets, from a fresh export today (2026-09-21).

Adonis re-placed the nametag board's corner pins from the current master library, so the package is now MACH_PIN_LRG_STD (Adom_Library_Master Library), drill 3.6 mm, pad 5.2 mm, STEP body MachinePin_Large_Standard_Gold. The name now carries STD, and the importer still answers No machine pins found (step2glb job 60f837e607a287bd), which is consistent with the large bucket being empty rather than the keyword check.

So the large set to accept is at least: 3.45 mm (legacy PIN_LRG and the current MachinePinLarge* part pages) and 3.6 mm (MACH_PIN_LRG_STD in the master library), both with a 5.2 mm pad.

Ray · 1mo ago

Good data point — that one is the empty large bucket, not the keyword check, exactly as you read it. MACH_PIN_LRG_STD carries STD, so the length was never the problem; 3.6 simply matched nothing.

PR #6 updated (cea09d5), still yours to review and merge. End-to-end on the re-exported board from the page (src/nametag-blank-large-led.brd, sha256 80c613a3…):

before after
MACH_PIN_LRG_STD (MP1-MP4), drill 3.6 / pad 5.2 0 of 4, HTTP 500 4 of 4 → large_pin_standard, lengthAssumed false on all four

The length is read rather than assumed there, because the package name carries STD — both halves of the PR working on the real file.

What it accepts now. 3.45 is unchanged and still needs no hint; it is the nominal large size. 3.6 is in a new AMBIGUOUS_LEGACY_PIN_DRILLS and counts as a large pin only when the package name says pin — MACH_PIN_LRG_STD and PIN_LRG both do, MOUNTING_HOLE_M3.5 and HOLE-3.6 do not, and 3.6 with no hint at all stays unclassified.

Why I gated it rather than adding it to the bucket list. 3.6 mm is not molecule-specific — an M3.5 clearance or an ordinary mounting hole drills the same. On the drill alone, every mounting hole on a board becomes a machine pin: the same wrong-answer-that-looks-right as 0.84, and worse than the 500 it replaces. Your pad observation (both large sizes pair with 5.2) is the other way to gate it, with the pad as the second signal instead of the name; the name gate needs no API change, the pad gate does. Say which you prefer. One caveat either way: the pin check is the bare substring pin, so a PINHEADER-style package drilling 3.6 would pass it. Neither board in hand has one, but if you want it tighter, mach_pin, a PIN_ prefix, or an explicit allowlist are all a one-line change.

One observation on the re-export, worth a look on your side. That board now has four pin elements — MP1-MP4, all MACH_PIN_LRG_STD — and no medium pins at all; the earlier export parsed as 4 large + 2 medium. If Adonis meant to re-place only the corner pins, the two mediums appear to have gone with them.

Log in to reply.