Closed general

validate passes a library with a dangling <connect> pad ref; generate has no pin-map input

John Lauer · 2mo ago ·closed by John Lauer

validate passes a library whose <connect> points at a pad that does not exist

adom-lbr 2.7.1 generated a .lbr with a dangling pad reference and validate called it complete.

Reproduction

adom/type-c-31-m-12 (a USB-C receptacle). Its KiCad sources use different identifiers for the shield:

symbol : (name "SHIELD") (number "S1")
footprint pads : A1 A4..A12 B1 B4..B12 SH        <- the shield pad is "SH"

Generate and validate:

adom-lbr generate --sym type-c-31-m-12.kicad_sym --fp type-c-31-m-12.kicad_mod -o out.lbr
adom-lbr validate out.lbr
  -> OK: out.lbr is a valid EAGLE .lbr with complete deviceset

But the generated connect is:

<connect gate="G$1" pin="SHIELD" pad="S1"/>     <- pad "S1" does not exist in the package

generate correctly used the symbol pin's NUMBER (S1) as the pad reference - that is the KiCad convention - but this part's footprint names that pad SH. The result is a library whose shield connects to nothing, and validate reports it as complete.

Why this is worth catching in validate

The failure is invisible everywhere a human would look. The symbol renders, the footprint renders, the 3D renders, a four-frame Fusion evidence set passes, and the shield is simply unconnected. On a USB-C receptacle that is an EMI and mechanical-retention defect that only appears in hardware.

It is also cheap to check - the package's pads and the symbol's pins are both in the same file:

for each <connect pin=P pad=D>:
    P must appear as a <pin name=...>  in the referenced gate's symbol
    D must appear as a <smd|pad name=...> in the referenced package

Suggested output rather than a bare failure, so the caller can act:

FAIL: 1 dangling connect
  pin "SHIELD" -> pad "S1"   (no such pad; unreferenced pads in package: SH)
  hint: symbol pin numbers and footprint pad names disagree for this part.
        Supply a pin map, or rename the pad in the footprint.

Naming the unreferenced pad is the useful half - SH is right there and obviously the intended target, so the fix suggests itself.

Worth knowing: the same part is broken in KiCad too

This is not only an EAGLE export problem. KiCad also binds symbol pin NUMBER to footprint pad NAME, so S1 finds no pad in KiCad either and the shield is unconnected in the KiCad netlist as well. So a validate that catches this would be catching a genuine source-data defect, not an artifact of conversion.

Also: generate has no pin-map input

The full interface is --sym --fp -o --name --symbol-art --symbol-art-scale. When the convention does not hold, there is currently no way to express the intended correspondence, so this class of part cannot be converted correctly at all. fusion-bridge's fusion_create_library accepts a pinMap and reports pinMapApplied, so a --pin-map flag (or a pinmap.json sidecar convention) would let that pass through to the converter that actually writes the connects.

I published exactly such a sidecar at adom/type-c-31-m-12 (pinmap.json = {"S1": "SH"}) while working around this.

Found while ralph-testing Adom Bridge's Fusion library creation across 13 Adom demo components; this was the only part of the 13 whose generated library had a dangling reference, and the other 12 came out clean and correct.

3 Replies

John Lauer · 2mo ago

Root cause is upstream of the .lbr: import-kicad DROPS the unmatched pin from the hub

Better evidence than my original report. The dangling .lbr connect is a symptom; the loss happens when building the canonical JSON.

adom-lbr import-kicad --sym type-c-31-m-12.kicad_sym \
                      --fp  type-c-31-m-12.kicad_mod \
                      --mpn type-c-31-m-12 --out type-c-31-m-12.adom-lbr.json

Result:

symbol.pins    : 17
footprint.pads : 18   ['', A1, A4..A12, B1, B4..B12, SH]
connect        : 16          <-- one short

pins present in connect[] : 16
PINS DROPPED FROM connect[]: ['S1']      <-- SHIELD, silently gone

No warning, no error, exit 0. S1 (name SHIELD) has no same-named pad, so it is omitted from connect[] rather than reported.

Why this is the more serious framing

The hub is the format everything else derives from - export-altium, export-kicad, export-intlib, and the EAGLE .lbr. A pin lost here is lost in every downstream format simultaneously, and each one looks internally consistent afterwards. The .lbr case I filed above is just where it happened to become visible (as pad="S1" referencing a pad that does not exist).

There is also an empty-string pad name in footprint.pads (''), which may be unrelated but looks like a parse artifact worth a glance.

The ask, revised

  1. Never drop a pin silently. If a symbol pin cannot be matched to a pad, that is a fact about the part and belongs in the output - either as a connect entry with a null/unresolved pad, or in an unmatched list, plus a warning on stderr and a non-zero exit under a --strict flag.
  2. Let the caller supply the correspondence. import-kicad has no pin-map input (--sym --fp --mpn --out), so a part like this cannot be converted correctly at all today. A --pin-map <json> taking {pinName_or_number: padName} would resolve it at the hub, where it then flows to every export for free.
  3. validate should catch what slipped through - both the dangling pad ref (original report) and, if you keep the hub in play, a connect[] shorter than symbol.pins.

The part is genuinely defective too, worth stating

This is not purely a converter bug. KiCad also binds symbol pin NUMBER to footprint pad NAME, so S1 finds no pad in KiCad either - the shield is unconnected in the KiCad netlist as well. The converter is faithfully reproducing a source inconsistency; the objection is that it does so silently, and that there is no way to correct it during conversion.

The right long-term fix for this specific part is at source (rename the footprint pad SH to S1, or the symbol pin to SH), but a converter that can express an explicit map is what makes the general case survivable - BGA and connector parts diverge like this routinely.

Ray · 2mo ago

Both halves are implemented and verified against adom/type-c-31-m-12 itself.

validate now fails the dangling connect, with the message shaped the way you asked:

FAIL: nomap.lbr has 1 error(s)
  [connect-pad-unresolved] pin "SHIELD" -> pad "S1" (no such pad in package
  'USB_C_Receptacle_USB2.0_16P'; unreferenced pads in package: SH). Symbol pin
  numbers and footprint pad names disagree for this part: supply a pin map
  (generate --pin-map) or rename the pad in the footprint.

Exit code 2, so a linter can just call it.

Why it could not catch this before, since the blind spot is wider than the one part: the check existed, but it was a warning (so the run still passed) and it read <smd> only, so every through-hole part passed blind too. It is now an error, counts <pad> as well, and resolves against the package the device actually names instead of globally. The unreferenced-pad list is deduplicated, because this part's shield is four tabs all named SH and "SH, SH, SH, SH" reads like four problems.

generate takes a pin map: --pin-map, and it picks up pinmap.json beside the symbol automatically, so the sidecar you published just works with no flag:

$ adom-lbr generate --sym type-c-31-m-12.kicad_sym --fp type-c-31-m-12.kicad_mod -o out.lbr
HINT: pin map applied (1 entries) from pinmap.json
HINT: Lint PASSED with 0 error(s), 0 warning(s)
$ grep SHIELD out.lbr
<connect gate="G$1" pin="SHIELD" pad="SH"/>

Two more rules from your Fusion testing, both now errors, since the linter will want all four from one call:

  • package3d-urn: a published .lbr carrying <package3d> or urn:adsk fails. It is a pointer into the minting account's cloud, not geometry, so it resolves to nothing for anyone else.
  • empty-packages: this was matched as the literal string <packages></packages>, so a section holding only whitespace slipped straight through. That is how a library shipped with zero packages while its .kicad_mod had two good SMD pads.

.step present is a page-level rule rather than anything readable from the .lbr, so that one belongs in the linter, not here.

5 regression tests, including the through-hole case and the whitespace-only packages case. Verified end to end on the real part: moving pinmap.json aside reproduces the dangling connect exactly, putting it back produces pad="SH" and validates clean.

Not released yet: this sits on a branch off 2.6.1 while main is 2.7.1, so it needs porting up and a version cut before the bridge picks it up. Flagging that specifically because the bridge now depends on adom-lbr.

John Lauer · 1mo ago

Closing: both halves of this issue are now released.

Ray's fix (validate + pin map) shipped in 2.8.0 and is live in 2.10.1. Verified against this exact part today: with pinmap.json in place, generate prints HINT: pin map applied (1 entries) from pinmap.json and emits <connect gate="G$1" pin="SHIELD" pad="SH SH@2 SH@3 SH@4"/> (the @-suffixes are new from #10, since the shield is a 4-tab same-net array). With the sidecar moved aside, validate fails with connect-pad-unresolved naming SHIELD, S1, and the unreferenced SH, exit 2.

The remaining half, the root cause from the addendum, is fixed in 2.10.1: import-kicad no longer silently drops an unmatched pin from connect[].

  • It now honors --pin-map and the pinmap.json sidecar beside the symbol, same convention as generate. With the sidecar, this part's hub JSON has connect: 17 including {"pin": "S1", "pad": "SH"} (was 16 with S1 silently gone).
  • Without a map, the drop is loud:
WARN: 1 symbol pin(s) match no footprint pad and were left out of connect[]: S1
      unreferenced pads in footprint: SH - if one of these is the intended target,
      supply --pin-map (or a pinmap.json sidecar)

Naming the unreferenced pad was your "useful half" suggestion from the original report, so it is in both messages.

Log in to reply.