Component Hero

Install?

The Adom standard (adom-hero 1.0) for a component page's annotated 3D hero, and a deterministic generator for it.

adom-wiki pkg install adom/component-hero

Latest: v0.1.2, published

Contents

README

markdown

Component hero standard (adom-hero 1.4)

Every component page on the wiki shows its part in 3D with the footprint drawn under it: the pads as teal dashed outlines, the silkscreen with pin 1, and each pad's signal name. This document defines that artwork exactly, so any person or AI builds the same thing, a linter can check it, and our factory can generate it for every page.

Status: 1.4, 2026-09-30 (see the change log at the end). Applied to 7 factory pages as a test (linked at the end). The generator is part of this page's skill (skills/component-hero/), so any AI that installs it can build a conforming hero.

SOT-23-6 hero: top and oblique

QFN-36 hero: exposed pad outlined, not labelled

Why a standard

In September 2026, 48 of the wiki's 15,979 component pages had this artwork. All 48 were built by hand by people's AIs following prose in the wiki-component skill. Three different styles exist, no generator was ever published, and it only happened when someone showed their AI an example. The factory's 15,580 pages had none.

The most common style, used on 45 pages by six authors, is the base for this standard. Call it style A. Its names and colours are kept exactly, so those 45 pages already conform on names and colours. What this version adds is precise geometry, pin-1 rules, a metadata block, a sidecar file and machine-checkable rules.

Two files: the hero and the board-use model

A component page ships two GLBs:

file purpose page.json key
<slug>-hero.glb presentation: chip + reference artwork component.parts.model_3d
the clean model (for factory pages <slug>-etched.glb) board use: downloads, EDA, assemblies component.parts.model_3d_plain

The wiki viewer shows model_3d by default. The artwork never goes into the board-use model: a part dropped onto a board must not bring outlines and labels with it. This follows the wiki-component-lod rule that every board-use GLB is free of footprint, silkscreen and label helpers.

The hero keeps the physical model untouched, including the virtual MPN marking (Adom.LaserEtch.MPN, made by service-occt). The artwork is only added.

Scene structure

scene
├── <physical model root>                            unchanged from the board-use GLB
└── Adom reference artwork (nonphysical)             node, no mesh
    ├── Footprint pads - teal dashed outlines - 50 percent opacity
    ├── Silkscreen reference lines - 30 percent opacity
    └── Signal names - 50 percent opacity
  • The three children are mesh nodes. Each mesh has one primitive, and that primitive's material carries the same name as the node.
  • A child may be absent when it would be empty (for example, no labels on a ball-grid part). Its absence is recorded in the metadata.
  • The artwork root sits at the scene root with an identity transform.

Coordinates

  • Axes: glTF x = KiCad x, glTF y = −KiCad y, Z up, metres.
  • Origin: the KiCad footprint origin, which is where KiCad places the part's 3D model.
  • Plane: all artwork is flat at z = −0.02 mm, just below the seating plane, so it never fights the underside of the chip for depth.

Materials

All three materials are:

  • unlit (KHR_materials_unlit, and the extension is listed in extensionsUsed);
  • alphaMode: BLEND;
  • metallicFactor 0, roughnessFactor 1;
  • doubleSided: true for the pad outlines and silkscreen, and doubleSided: false for the signal names, which carry two one-sided copies instead (see Layer 3).
layer material name (exact) baseColorFactor (RGBA)
Pad outlines Footprint pads - teal dashed outlines - 50 percent opacity [0, 0.902, 0.863, 0.5] (teal #00E6DC)
Silkscreen Silkscreen reference lines - 30 percent opacity [0.92, 0.96, 1.0, 0.3]
Signal names Signal names - 50 percent opacity [0.92, 0.96, 1.0, 0.5]

"70% transparent silkscreen" means alpha 0.3. The outline colour is teal, not blue: every hero built so far uses this teal, and the wiki-component skill specifies it.

Layer 1: pad outlines

  • What is drawn: the copper perimeter of every pad on F.Cu or *.Cu. Solder-mask and paste openings are not drawn. Non-plated holes are skipped, and drills are not drawn.
  • Shapes:
    • rect and trapezoid are drawn as rectangles.
    • roundrect uses corner radius = roundrect_rratio × min(w, h).
    • circle and oval are drawn as circles and stadiums.
    • custom is drawn from its first gr_poly primitive, or from its anchor rectangle when it has none.
    • Pad rotation is applied, including its sign.
  • Dashes: the outline is dashed as a closed path, and a dash may turn a corner.
    • Let span be the largest dimension of the pad field, in mm, and at least 1.
    • dash = clamp(0.025 × span, 0.12, 0.45) mm.
    • gap = 0.6 × dash.
    • stroke = clamp(0.006 × span, 0.03, 0.09) mm.
    • Per-pad cap, so small pads still read as their shape: dash ≤ max(perimeter / 6, 0.05), and stroke ≤ max(perimeter / 25, 0.02).
  • Geometry: each dash is one continuous ribbon of the stroke width, centred on the path, with mitred joins at every bend (mitre limit 4×). There are no gaps at bends and no overlapping triangles: overlaps double the alpha and show as bright seams. Curves (rounded corners, circles, ovals, silkscreen arcs) are sampled so that no straight piece is longer than 0.005 mm, so they stay smooth when zoomed in. Silkscreen strokes use the same ribbons. Meshes are indexed, so shared vertices are stored once.
  • Worked example: a SOT-23-6 gives 0.12 mm dashes with a 0.03 mm stroke; an LQFP-48 gives about 0.23 mm dashes with a 0.054 mm stroke. On 0.3 mm BGA balls the per-pad cap keeps them as dashed circles.
  • Why this weight: subtle, but visible at the wiki viewer's default zoom. The hand-built heroes' 0.015 mm stroke vanished there, so the 30% silkscreen was taken for the footprint; 1.1's 0.05 mm read as heavy.

Layer 2: silkscreen and pin 1

  • Footprint silkscreen: every F.SilkS fp_line, fp_arc (both the three-point and the angle form), fp_circle, fp_rect and fp_poly, at the footprint's own stroke width.
    • Solid-filled shapes and polygons are filled. A polygon with no fill setting counts as filled, because KiCad's pin-1 triangles are written that way.
    • Silkscreen text (REF**, the value) is not drawn.
  • Pin 1:
    • Footprint mark first. If the footprint's silkscreen already marks pin 1, that mark is the pin-1 indicator and nothing is added. A filled silkscreen shape counts as the mark when its centre is within 2.5 mm of pad 1 and no other pad is closer.
    • Otherwise, an added dot. A filled dot goes on this layer:
      • on parts with 3 or more signal pads, at pad 1 (or A1);
      • on a 2-pad part, only when a pin is named K, KA, CATHODE or +. The dot goes at that pin. Unpolarised 2-pad parts get no dot, because they have no orientation.
    • Dot size and position: radius = clamp(0.22 × the smaller pad dimension, 0.1, 0.35) mm. It is placed 0.2 mm plus its radius beyond pad 1's outer end, along pad 1's row (its column for side rows). On a ball grid it goes on the diagonal, away from the grid's centre.
    • Record: the metadata says which source marked pin 1, so an added dot is never mistaken for manufacturer evidence.

Layer 3: signal names

  • Text:
    • comes from the page's pinout (component.pins, pad number → name), exactly as the datasheet spells it;
    • active-low ~{X} is drawn as X with an overbar;
    • when a pin has no real name (~ or empty), the pad number is shown, so every label stays unique. Never a repeated "Terminal".
  • Font: DejaVu Sans Mono, filled glyphs with holes kept. Islands inside holes, such as the dot in the zero, stay filled. The glyphs come pre-triangulated at font size 1 in hero_glyphs.json (ASCII plus common symbols such as ±, µ, Ω, °, ×), so no font library is needed and every implementation draws identical text. A character not in the table is drawn as ?.
  • Readable from both sides: the mesh has two primitives with the same single-sided material. The first faces up (counter-clockwise seen from above). The second is a copy mirrored across each label's own horizontal centre line, facing down, so it reads correctly from below, when the model is tipped over toward the viewer. Each copy is visible only from its own side.
  • Which pads: each pad number is labelled once. There are no labels on:
    • exposed or thermal pads. A pad counts as thermal when it is named EP, PAD, TAB or 0, or when it is more than 4× the median pad area and sits inside the pad field on a part with 5 or more pads;
    • ball grids, meaning 16 or more pads with at least 4 distinct rows and 4 distinct columns, filling more than 60% of the grid. Labels there would sit under the body, and the metadata records why they were omitted.
  • Placement: outside the pad, on the side of the pad field it belongs to.
    • Left and right rows are horizontal, and the text runs away from the part.
    • Top and bottom rows are rotated 90° and read upward.
    • A 2-pad part is labelled left and right.
  • Size: label height = clamp(0.5 × pitch along its row, 0.12, 0.6) mm. On 2-pad parts it is clamp(0.7 × the pad's shorter side, 0.12, 0.6) mm. The gap from the pad is max(0.25 × height, 0.1) mm.
  • Collisions: a label that would overlap a filled silkscreen shape (the footprint's pin-1 triangle, the added dot) steps outward past it.

Metadata: asset.extras.adomHero

The hero's glTF asset.extras carries one object. Here is the real block from adom/opa2325idgkt:

{
  "spec": "adom-hero 1.4",
  "mpn": "OPA2325IDGKT",
  "kind": "component + footprint reference art",
  "board_use": false,
  "plane_z_mm": -0.02, "units": "metre", "upAxis": "Z",
  "layers": {
    "pad_outlines": { "material": "Footprint pads - teal dashed outlines - 50 percent opacity",
                      "rgba": [0, 0.902, 0.863, 0.5], "pads": 8,
                      "span_mm": 4.05, "dash_mm": 0.12, "gap_mm": 0.072, "pad_stroke_mm": 0.0300 },
    "silkscreen":   { "material": "Silkscreen reference lines - 30 percent opacity",
                      "rgba": [0.92, 0.96, 1.0, 0.3], "footprint_items": 3,
                      "pin1": { "source": "footprint silkscreen", "pad": "1" } },
    "signal_names": { "material": "Signal names - 50 percent opacity",
                      "rgba": [0.92, 0.96, 1.0, 0.5], "font": "DejaVu Sans Mono",
                      "labels": 8, "fallback_to_pad_number": 0 }
  },
  "align": { "ok": true, "centre_offset_mm": 0.0, "tolerance_mm": 0.324, "orientation_flipped": false,
             "contacts_bbox_mm": [-1.55, 1.55, -0.86, 0.86], "pads_bbox_mm": [-2.025, 2.025, -0.925, 0.925],
             "seating_z_mm": 0.0 },
  "sources": { "glb": "opa2325idgkt-etched.glb", "glb_sha256": "4b9b…e7bf",
               "footprint": "opa2325idgkt.kicad_mod", "footprint_sha256": "dca7…75a8",
               "pins": "page.json component.pins" },
  "generator": "factory/hero_glb.py"
}
  • silkscreen.pin1 is one of three things:
    • {source: "footprint silkscreen", pad};
    • {source: "added dot (AI)", pad, center_mm, r_mm};
    • null, when the part has no orientation.
  • signal_names.omitted appears when labels were skipped, with the reason.

Sidecar: <slug>-hero.overlay.json

Next to the hero, the page ships a sidecar with:

  • the same object as the metadata;
  • every pad: number, shape, centre, size and rotation, all in footprint millimetres;
  • every label: pad, text, anchor, alignment, orientation and height.

A reviewer or a tool can check the artwork without opening the GLB.

Alignment guard

The artwork is drawn from the footprint and the chip comes from its 3D model. If the model is offset or turned relative to the footprint, the artwork would look authoritative and be wrong. So before a hero is published, the chip's lowest geometry (its contacts) is compared with the pad field. The result is recorded in align, and a hero is published only when ok is true. It fails when:

  • The centres are too far apart. The limit is max(0.25 mm, 8% of the larger pad-field dimension).
  • The orientation is swapped. This means one box is more than 1.25× longer in x than in y while the other is more than 1.25× longer in y than in x, which is what a model turned 90° looks like.

Width alone is not used: a leadless body (QFN, WSON) rests its whole underside on the seating plane.

Checks for a linter

A component page conforms when all of these hold. Each can be checked from the GLB's JSON chunk alone, without downloading the buffers.

  1. component.parts.model_3d names a .glb whose asset.extras.adomHero.spec starts with adom-hero 1..
  2. adomHero.board_use is false.
  3. component.parts.model_3d_plain names a different GLB, and that GLB contains no node named Adom reference artwork (nonphysical) and no material with any of the three layer names.
  4. The hero has exactly one node named Adom reference artwork (nonphysical) at the scene root, and its children are drawn only from the three layer names.
  5. The node Footprint pads - teal dashed outlines - 50 percent opacity exists.
  6. Each layer's material has its exact name and baseColorFactor, with alphaMode: BLEND and KHR_materials_unlit; doubleSided is true for pad outlines and silkscreen and false for signal names, and the signal-names mesh has two primitives (up-facing and down-facing).
  7. adomHero.layers.pad_outlines.pads equals the number of copper pads in the page's .kicad_mod.
  8. adomHero.align.ok is true.
  9. A part with 3 or more signal pads has a non-null silkscreen.pin1.
  10. signal_names is present unless signal_names.omitted gives a reason.

Checks 1 to 6 are presence and format. Checks 7 to 10 compare against the page's own files.

Building one

The generator in this page's skill (skills/component-hero/scripts/hero_glb.py, also the factory's) implements everything above deterministically, with no model calls:

python3 hero_glb.py <board-use.glb> <footprint.kicad_mod> <page.json> <slug>-hero.glb [--png preview.png]

It needs hero_glyphs.json beside it. It prints one line: pads, silkscreen items, pin-1 source, labels and the alignment verdict. It also writes the sidecar next to the hero. --png writes a top and oblique preview for review. A hero is typically 50-300 KB and takes a few seconds to build.

To publish, add <slug>-hero.glb and <slug>-hero.overlay.json to the page, set model_3d and model_3d_plain as described above, bump the version, publish, and push page.json last.

Differences from the existing hand-built heroes

  • Kept: style A's layer and material names, colours and alphas, the separate hero file, the -0.02 mm plane and the unlit materials.
  • Now exact: dash, gap and stroke sizes, label height and placement, the pin-1 rules, and the exposed-pad and ball-grid rules.
  • Changed: the pad outlines are about 2× the hand-built heroes' stroke, so the footprint is visible at the viewer's default zoom, and each dash is one smooth continuous stroke.
  • Added:
    • the pin-1 dot on the silkscreen layer. John asked for it, and the one earlier attempt was a 3D block stuck on the chip;
    • the adomHero metadata and the sidecar;
    • the alignment guard;
    • label collision avoidance.
  • Not adopted:
    • the Adom.PadOutlines / Adom.PadLabels / Adom.Silkscreen naming on the three VL53 pages;
    • board-context .insertion.glb composites. Those are useful, but a different asset.
  • Existing style A pages: they already match on names and colours. Adding adomHero would make them pass checks 1-2 and 7-10.

Examples

Built with this standard (2026-09-30):

Hand-built style A, for comparison:

Open items

  • Ball grids have no labels. A later version could label the outer ring outside the body, or offer labels as a viewer toggle.
  • Through-hole drills are not drawn. Only the copper ring is.
  • Custom pads use their first primitive only. Complex custom pads, such as some connector shields, may need the union of all primitives.
  • Hero freshness. When a page's footprint or pinout changes, its hero must be rebuilt. The sidecar's source hashes let a lint rule detect a stale hero.

Change log

  • 1.4 (2026-09-30): signal names readable from both sides: two single-sided copies, the underside one mirrored top-to-bottom. Colby, viewing from below, saw "WODE" and "ΛIN". Text now comes from a pre-triangulated glyph table, so the generator no longer needs a font library.
  • 1.3 (2026-09-30): each dash is one continuous mitred ribbon, curves sampled to 0.005 mm, meshes indexed. Ray, zoomed in: "why is it not a continuous shape? and smooth?" The dashes were separate quads per segment, with wedge gaps and bright overlaps at rounded corners, and corners had 7 points per 90°.
  • 1.2 (2026-09-30): outline weight between 1.0 and 1.1 (stroke 0.03-0.09 mm, dash 0.12-0.45 mm). Colby liked the subtle look; 1.0 vanished at default zoom. Ray chose the middle.
  • 1.1 (2026-09-30): pad outlines bolder: stroke 0.015 to 0.05-0.15 mm, dash 0.07 to 0.18-0.6 mm. Colby asked whether the gray lines were the footprint: at 1.0 sizes the teal outlines vanished at the viewer's default zoom and only the silkscreen showed.
  • 1.0 (2026-09-30): first version, unified from the 45 hand-built style A heroes.