component
STM32G431CBT6 - Arm Cortex-M4 Microcontroller
Public Made by Adomby adom
STMicroelectronics Microcontrollers (MCU/MPU/SOC), LQFP-48(7x7). Pinout checked against ST's open pin data, KiCad symbol/footprint/3D.
← Commit history
STM32G431CBT6: datasheet-built LQFP48 model (DS12589 Rev 2 Table 94) with ST's real top marking STM32G431 / CBT6 / Y WW / R (p.177), overlay proof, etch gate PASS, clean board LODs, adom-hero 1.4 hero, chip-outline symbol, provenance (4/5)
31 files changed
+2374−34
README.md+75−34artwork/STM32G431CBT6-3d-outline-dark-named.320.pngdocs/figures/stm32g431cbt6-ds-p174.pngdocs/models/stm32g431cbt6-etched-glb-top.pngdocs/models/stm32g431cbt6-etched-step-bottom.pngdocs/models/stm32g431cbt6-etched-step-top.pngdocs/models/stm32g431cbt6-glb-top.pngdocs/models/stm32g431cbt6-high-glb-top.pngdocs/models/stm32g431cbt6-kicad-generic-step-bottom.pngdocs/models/stm32g431cbt6-kicad-generic-step-top.pngdocs/models/stm32g431cbt6-low-glb-top.pngdocs/models/stm32g431cbt6-step-bottom.pngdocs/models/stm32g431cbt6-step-top.pngdocs/models/stm32g431cbt6-super-low-glb-top.pngdocs/proof/stm32g431cbt6-etched.check.pngdocs/proof/stm32g431cbt6-etched.mark-plan.json+129lod/manifest.json+162lod/stm32g431cbt6-low.pnglod/stm32g431cbt6-super-low.pngmodels.json+255provenance/3d-model.json+205provenance/tools/build_gullwing.py+61provenance/tools/extra_marks.py+127provenance/tools/gaugeL.py+11provenance/tools/glbtool.py+108provenance/tools/outline_from_etched.py+11provenance/tools/overlay.json+7provenance/tools/overlay.py+77provenance/tools/params.json+3provenance/tools/spec.json+3stm32g431cbt6-hero.overlay.json+1140README.md+75−34@@ -2,7 +2,7 @@ **STMicroelectronics** · Microcontrollers (MCU/MPU/SOC) · LQFP-48(7x7) -STM32G431CBT6 from STMicroelectronics, in a LQFP-48(7x7). Pinout and pin descriptions extracted from the manufacturer datasheet by ds2sf; symbol, footprint, and 3D from KiCad-official CAD.+STM32G431CBT6 from STMicroelectronics, in a LQFP-48(7x7). Pinout and pin descriptions extracted from the manufacturer datasheet by ds2sf; symbol and footprint from KiCad-official CAD; the 3D model is built from the ST datasheet drawing and carries ST's real top marking. ## Pinout @@ -91,59 +91,100 @@ Same part, different packaging or reel option. - `STM32G431CBT6V8T6` - `STM32G431CBT6VBT6` -## CAD (KiCad official)+## CAD -- Symbol: `STM32G431CBT6`-- Footprint: `Package_QFP:LQFP-48_7x7mm_P0.5mm`-- 3D model: LQFP-48(7x7) (KiCad packages3D, STEP)-- 3D model files: `stm32g431cbt6-etched.step` / `stm32g431cbt6-etched.glb` carry the MPN laser-etched on the die in a toggleable `Adom.LaserEtch` group (Fusion-ready); `stm32g431cbt6.step` / `stm32g431cbt6.glb` are the bare package body.+- Symbol: `STM32G431CBT6` (KiCad, adom-symbol 1.2.6, intelligent pin groups, chip outline baked in the body)+- Footprint: `Package_QFP:LQFP-48_7x7mm_P0.5mm` (KiCad official)+- Fusion / EAGLE: [stm32g431cbt6.lbr](stm32g431cbt6.lbr) -## 3D model downloads+## Top marking (the real one, from the datasheet) -Two STEP files. Same geometry, different body top:+Every 3D file on this page that carries a marking carries **the manufacturer's own top marking**, not an invented label: -| File | Size | Use it when |-|---|---|---|-| [`stm32g431cbt6.step`](stm32g431cbt6.step) | 617 KB | You want the bare part, or you mark your own boards. |-| [`stm32g431cbt6-etched.step`](stm32g431cbt6-etched.step) | 4.8 MB | You want the part with `STM32G431CBT6` already on it. |+| | |+|---|---|+| Marking | `STM32G431 / CBT6 / Y WW / R` |+| Source | ST DS12589 Rev 2 (October 2019), STM32G431x6/x8/xB datasheet, p.177 (6.4 LQFP48 device marking, Figure 51 LQFP48 top view example) |+| How it is drawn | Line 1 STM32G431 and line 2 CBT6 (product identification), date code Y WW (Y = year, WW = work week, shown as the placeholders ST prints), revision code R (placeholder, one letter), pin-1 dimple at the pin-1 corner. Not rendered: the ST logo (a trademark, not text) and the unlabelled circle ST draws beside it. |+| Placement | top face, flat light mark 5 um above the face (coplanar), in the Adom.LaserEtch group; along the long axis, upright in the standard pose (pin 1 lower left seen from the top) |+| Font | service-step2glb /laser-etch single-stroke outline font (not the manufacturer's font) |+| Gate | `adom-aiflow-etch check`: **PASS** (axis PASS, upright PASS, size PASS, band PASS, flat PASS, group PASS, colour PASS, widget PASS) |+| Layout note | ST's figure puts R in the bottom row beside the logo; the gate keeps all text clear of the pin-1 dimple band, so R sits one row higher at the right, under the date code. |++ -The marking in the etched file is real recessed geometry cut into the solid, not a-texture or a decal: 1196 faces against 747 in the bare model. It-survives export, section views, and rendering in any CAD tool, and it drops straight-into KiCad or Fusion with no extra setup.+The datasheet's marking figure (p.177): -## Provenance+ -Data extracted from the manufacturer datasheet, not a distributor listing:+## 3D models++::adom-models{src="/adom/stm32g431cbt6/render/models.json"}++| File | What it is |+|---|---|+| [stm32g431cbt6-etched.step](stm32g431cbt6-etched.step) | **The library model** for board CAD (KiCad, Fusion, Altium): the package with the real marking in an `Adom.LaserEtch` group. |+| [stm32g431cbt6.step](stm32g431cbt6.step) | The same package, unmarked. |+| [stm32g431cbt6-etched.glb](stm32g431cbt6-etched.glb) | The marked model as GLB, clean for boards and digital twins (no pad outlines, labels or silkscreen). |+| [stm32g431cbt6-hero.glb](stm32g431cbt6-hero.glb) | The annotated page hero (adom-hero 1.4): the marked chip plus teal dashed pad outlines, silkscreen with pin 1 and pad signal names. Display only, never for a board. |+| [lod/](lod/manifest.json) | Board-use detail levels (below). |+| [provenance/previous/](provenance/previous/stm32g431cbt6-kicad-generic.step) | The page's previous plain model, kept for reference. | -- **Datasheet:** [STMicroelectronics datasheet](https://www.st.com/resource/en/datasheet/stm32g431cbt6.pdf) (200 pp, sha256 `cabefb21b47dc4c1`, retrieved 2026-08-17, tier: manufacturer)-- **Extractor:** `ds2sf` (Claude). Every pin and dimension is cited to a datasheet page; it refuses to fabricate.-- **CAD:** KiCad official libraries (CC-BY-SA 4.0).+Frame for every file: Z up, seated on z = 0, centred on the KiCad footprint origin, pin 1 on footprint pad 1, binds with zero offset and rotation. The model is **AI-generated from the datasheet** (AI-derived), not manufacturer CAD.++## Model proof: overlay on the datasheet drawing++The plain model was rendered orthographically at the drawing's own scale and blended over the drawing views (p.174 (Figure 49 LQFP48 outline), p.175 (Table 94 LQFP48 mechanical data)). Red outline and blue fill: the model. Black lines: the datasheet.++++| Dimension (mm) | Datasheet min / typ / max | Model |+|---|---|---|+| D, E lead tip to tip | 8.80 / 9.00 / 9.20 | 9.000 |+| D1, E1 body | 6.80 / 7.00 / 7.20 | 7.000 |+| A overall height | max 1.60 | 1.500 |+| A1 standoff | 0.05 / - / 0.15 | 0.100 |+| A2 body thickness | 1.35 / 1.40 / 1.45 | 1.400 |+| b lead width | 0.17 / 0.22 / 0.27 | 0.220 |+| c lead thickness | 0.09 / - / 0.20 | 0.145 |+| e pitch | 0.50 | 0.500 |+| L foot (to the 0.25 mm gauge plane) | 0.45 / 0.60 / 0.75 | 0.600 |+| L1 lead length | 1.00 | 1.000 | -[Datasheet PDF](docs/stm32g431cbt6-datasheet.pdf)+Measured off the drawing where the datasheet prints no typical value: lead centre height 0.83 mm, body draft inset 0.13 mm per face (top view double outline), pin-1 dimple 0.75 mm diameter centred 1.00 mm and 1.14 mm from the pin-1 edges (Figure 49 at 56.57 px/mm). +Why the model was rebuilt: the previous model, KiCad packages3D LQFP-48_7x7mm_P0.5mm.step (CC-BY-SA 4.0), the page's previous plain model, within tolerance on every printed size except L: the foot measured 0.444 mm to the 0.25 mm gauge plane, under ST's 0.45 mm minimum. Rebuilt from Table 94 typical values instead. -<!-- astra-asset-provenance:start -->-## CAD provenance and independent review+Cited datasheet pages: -**Audit by Codex Astra (AI).** [Per-file provenance, sources and checks](https://wiki.adom.inc/ray/stm32g431cbt6/files/PROVENANCE.md) documents the current file hashes, source or generation inputs, modifications, verification limits and unknown origins. AI-created models are identified explicitly; a wiki upload is not manufacturer verification.+++ -**Unresolved original provenance:** symbol, footprint, step, glb. See the record before treating these as source-verified assets.+## Detail levels for board and digital-twin viewers -[Report a component problem](https://wiki.adom.inc/ray/stm32g431cbt6/issues) with the filename/hash and evidence.-<!-- astra-asset-provenance:end -->+::adom-component-lod{src="/adom/stm32g431cbt6/render/lod/manifest.json"} +| Level | File | Triangles | Size | Marking |+|---|---|---|---|---|+| high | [stm32g431cbt6-high.glb](lod/stm32g431cbt6-high.glb) | 2235 | 299 KiB | yes |+| medium | [stm32g431cbt6-medium.glb](lod/stm32g431cbt6-medium.glb) | 2235 | 91 KiB | yes |+| low | [stm32g431cbt6-low.glb](lod/stm32g431cbt6-low.glb) | 1568 | 70 KiB | no |+| super-low | [stm32g431cbt6-super-low.glb](lod/stm32g431cbt6-super-low.glb) | 1560 | 68 KiB | no | -[Optional MPN-marked model, preview and provenance](MPN-MARKING.md)+All four levels share the Z-up frame, units and seating of the library model, carry no footprint or silkscreen artwork, and keep the real terminals and the pin-1 cue. Low and super-low omit the marking (unreadable at those sizes). Status: candidate; the cached previews were inspected, a streaming LOD viewer has not qualified them. +## Symbol chip outline -## Displayed model and review status+The symbol body carries the chip outline drawn from the marked model, so the schematic shows the same marking as the 3D view: [artwork/STM32G431CBT6-3d-outline-dark-named.svg](artwork/STM32G431CBT6-3d-outline-dark-named.svg) (made with `adom-aiflow-etch outline`, gate PASS). -The main 3D viewer uses [stm32g431cbt6-hero-mpn.glb](stm32g431cbt6-hero-mpn.glb), derived from the reviewed [stm32g431cbt6-mpn.glb](stm32g431cbt6-mpn.glb). The exact MPN is AI-added identification artwork, not a factory marking. [Marking inputs, fit and licensing](MPN-MARKING.md) and [source provenance](PROVENANCE.md) remain applicable.+ -The hero applies only a 180-degree Z display rotation; no geometry is scaled. Use the original-frame marked STEP/GLB downloads for board assemblies. The rendered package and readable mark were reviewed; that does not establish manufacturer-exact dimensions, polarity, or native-tool qualification. [Binding and file hashes](hero-binding.json).+## 3D model provenance +[provenance/3d-model.json](provenance/3d-model.json) records the datasheet (document, revision, pages, SHA-256), every model dimension against the datasheet, what was measured off the drawing, the builder and its parameters ([provenance/tools/](provenance/tools/build_gullwing.py)), the etch gate result, file hashes, tool versions and the AI-derived classification. [PROVENANCE.md](PROVENANCE.md) is the readable summary. -## Footprint-aware inspection hero+## Datasheet provenance -The current hero adds separate nonphysical reference layers: teal dashed copper boundaries at 50% opacity, silkscreen at 30% opacity and actual symbol pin names (unique pad numbers when unnamed) at 50% opacity. Orbit underneath to compare physical terminals, copper and pin1 marks. Copper outlines are not soldermask/paste. Existing MPN, materials, animations and lights are preserved. These helpers are excluded from board-use STEP/GLB files. [Geometry, mapping and source hashes](stm32g431cbt6-hero-footprint.overlay.json); [pin-map input](hero-pin-map.json). Physical body can occlude seating-plane artwork; inspect the underside or isolate it. Generated display-font contours are not native fab artwork.+- **Datasheet:** ST DS12589 Rev 2 (October 2019), STM32G431x6/x8/xB datasheet ([PDF](docs/stm32g431cbt6-datasheet.pdf), sha256 `cabefb21b47dc4c1`). The PDF's metadata title names STM32G474; the document itself is DS12589, the STM32G431 datasheet.+- **Pins:** extracted by `ds2sf` (Claude); every pin cites a datasheet page.
artwork/STM32G431CBT6-3d-outline-dark-named.320.pngadded⋯ 1 unchanged line ⋯
docs/figures/stm32g431cbt6-ds-p174.pngadded⋯ 1 unchanged line ⋯
docs/models/stm32g431cbt6-etched-glb-top.pngadded⋯ 1 unchanged line ⋯
docs/models/stm32g431cbt6-etched-step-bottom.pngadded⋯ 1 unchanged line ⋯
docs/models/stm32g431cbt6-etched-step-top.pngadded⋯ 1 unchanged line ⋯
docs/models/stm32g431cbt6-glb-top.pngadded⋯ 1 unchanged line ⋯
docs/models/stm32g431cbt6-high-glb-top.pngadded⋯ 1 unchanged line ⋯
docs/models/stm32g431cbt6-kicad-generic-step-bottom.pngadded⋯ 1 unchanged line ⋯
docs/models/stm32g431cbt6-kicad-generic-step-top.pngadded⋯ 1 unchanged line ⋯
docs/models/stm32g431cbt6-low-glb-top.pngadded⋯ 1 unchanged line ⋯
docs/models/stm32g431cbt6-step-bottom.pngadded⋯ 1 unchanged line ⋯
docs/models/stm32g431cbt6-step-top.pngadded⋯ 1 unchanged line ⋯
docs/models/stm32g431cbt6-super-low-glb-top.pngadded⋯ 1 unchanged line ⋯
docs/proof/stm32g431cbt6-etched.check.pngadded⋯ 1 unchanged line ⋯
docs/proof/stm32g431cbt6-etched.mark-plan.jsonadded+129@@ -0,0 +1,129 @@+{+ "schema": "adom/mark-plan@1",+ "mpn": "STM32G431CBT6",+ "step": "/tmp/claude-1001/-home-adom-project/43add640-14a6-413a-b15b-ffdadeb7cf16/scratchpad/ics_work/stm/stm32g431cbt6.step",+ "footprint": "/tmp/claude-1001/-home-adom-project/43add640-14a6-413a-b15b-ffdadeb7cf16/scratchpad/ics_work/stm/stm32g431cbt6.kicad_mod",+ "standUp": [],+ "standUpSource": "as-is (Z up)",+ "inputs": {+ "up": "auto",+ "face": "auto",+ "along": null,+ "upSupplied": false,+ "faceSupplied": false,+ "faceSource": "inferred: top face"+ },+ "face": "top",+ "pin1": {+ "source": "footprint pad 1",+ "xyMm": [+ -4.1625,+ 2.75+ ],+ "corner": "left/lower"+ },+ "existingMarks": [],+ "vendorMarking": {+ "present": false,+ "glyphFaces": 4+ },+ "faceNormal": [+ 0,+ 0,+ 1+ ],+ "longAxis": {+ "axis": "y",+ "vector": [+ 0.0,+ 1.0,+ 0.0+ ],+ "why": "longest usable top dimension (6.74 mm vs 5.33 mm)",+ "source": "longest",+ "override": null,+ "pinRows": null+ },+ "readingDirection": {+ "vector": [+ -0.0,+ -1.0,+ 0.0+ ],+ "angleDeg": 270,+ "textUp": [+ 1.0,+ -0.0,+ 0.0+ ],+ "rule": "pin 1 / pad 1 at the start (left) of the text, and below it when it sits on a side"+ },+ "band": {+ "regionMm": [+ -1.965,+ -3.37,+ 3.37,+ 3.37+ ],+ "zMm": 1.5,+ "lengthMm": 6.74,+ "heightMm": 5.335,+ "why": "highest flat face over the body centre",+ "terminationsExcludedBy": null,+ "trimmedEnds": [],+ "keepouts": [+ [+ -2.735,+ 2.125,+ -1.985,+ 2.875+ ]+ ],+ "sideTurn": null,+ "margins": {+ "lengthEachSide": 0.12,+ "heightEachSide": 0.15+ }+ },+ "text": {+ "lines": [+ "STM32G",+ "431CBT6"+ ],+ "chars": 13,+ "capHeightMm": 0.9213,+ "smallestLineMm": 0.9213,+ "heightPctOfBand": 39.7,+ "why": "two lines: the smaller line (0.921 mm) beats one line (0.489 mm) by 1.88x",+ "alternative": {+ "lines": [+ "STM32G431CBT6"+ ],+ "capHeightMm": 0.4888,+ "smallestLineMm": 0.4888+ },+ "font": "service-step2glb /laser-etch (single-stroke outline font)"+ },+ "etch": {+ "poseQuarterTurnsZ": 1,+ "service": "/laser-etch?mode=flat",+ "note": "turn so the reading direction is +X, etch, fit the mark into the band, turn back"+ },+ "outline": {+ "upAxis": "z",+ "poseRotZDeg": 180,+ "rot": 90,+ "recipe": "adom-aiflow-etch outline <part.step> --footprint <part.kicad_mod> --mpn STM32G431CBT6 (stands the STEP up (as-is (Z up)), turns it 180 deg about Z and draws STM32G / 431CBT6 on the planned band, rot 90)",+ "why": "iso camera looks from +X+Y+Z: text along +Y (rot 90) or -X (rot 180) reads left to right with pin 1 toward the viewer"+ },+ "widget": {+ "px": 320,+ "pxPerMm": 27.67,+ "capHeightPx": 20.8+ },+ "hints": [+ "the top face is stored with a reversed normal (0,0,-1), as in KiCad's stock SOIC / TSSOP models; it is the top face by position (top of the part's bbox)"+ ],+ "warnings": [],+ "summary": "Mark along the 6.74 mm axis (-Y), 0.92 mm text (2 lines), upright with pin 1 left lower, outline rot 90 after a 180 deg pose turn; ~21 px caps at 320 px"+}
lod/manifest.jsonadded+162@@ -0,0 +1,162 @@+{+ "schema": "adom/component-lod@1",+ "mpn": "STM32G431CBT6",+ "units": "metres (glTF)",+ "upAxis": "Z (Adom/KiCad frame; seated on z = 0, centred on the footprint origin)",+ "status": "candidate",+ "source": {+ "path": "stm32g431cbt6-etched.step",+ "sha256": "73cf3b11aa4684b96ba7cd3de788811acb317a67b44d660349cf24d51fa63147"+ },+ "generator": "step2glb 0.13.2 (service-step2glb) convert --compress none|quantize; previews by provenance/tools/glbtool.py",+ "levels": [+ {+ "level": "high",+ "path": "lod/stm32g431cbt6-high.glb",+ "sha256": "15882e753c30077b50c64ba76dc9638709f072ba47f0ef4ce94385cfb8f2cca9",+ "sizeBytes": 306656,+ "decodedBufferBytes": 91338,+ "triangles": 2235,+ "primitives": 512,+ "boundsMm": [+ -4.5,+ -4.5,+ 0.0,+ 4.5,+ 4.5,+ 1.505+ ],+ "markingIncluded": true,+ "mark": {+ "mark_z_min_mm": 1.505,+ "mark_z_max_mm": 1.505,+ "chip_top_mm": 1.5,+ "lift_um": 5.0+ },+ "helperNodes": [],+ "extensions": [],+ "derivation": "etched STEP, uncompressed: chip, terminals, pin-1 dimple and the full real marking",+ "review": "candidate: inspected as a cached image; not qualified in a streaming LOD viewer",+ "preview": {+ "path": "lod/stm32g431cbt6-high.png",+ "sha256": "6304edd1b7e432f1f9b93fc6fddfb9d8435b6bd60c101220b281c735720a669d",+ "sizeBytes": 47671,+ "width": 400,+ "height": 400,+ "view": "iso (glbtool.py render)",+ "sourceLevelSha256": "15882e753c30077b50c64ba76dc9638709f072ba47f0ef4ce94385cfb8f2cca9"+ }+ },+ {+ "level": "medium",+ "path": "lod/stm32g431cbt6-medium.glb",+ "sha256": "4296399f12062309c7655d91af3f900cf0487d4e27af4adc9c0c943f82fae1ce",+ "sizeBytes": 93832,+ "decodedBufferBytes": 16158,+ "triangles": 2235,+ "primitives": 512,+ "boundsMm": [+ -4.5,+ -4.5,+ -0.0,+ 4.5,+ 4.5,+ 1.505+ ],+ "markingIncluded": true,+ "mark": {+ "mark_z_min_mm": 1.505,+ "mark_z_max_mm": 1.505,+ "chip_top_mm": 1.4999,+ "lift_um": 5.15+ },+ "helperNodes": [],+ "extensions": [+ "KHR_mesh_quantization"+ ],+ "derivation": "etched STEP, quantized (KHR_mesh_quantization); marking kept, its 5 um lift checked after quantization",+ "review": "candidate: inspected as a cached image; not qualified in a streaming LOD viewer",+ "preview": {+ "path": "lod/stm32g431cbt6-medium.png",+ "sha256": "29f0d12e4c5bc2663b9b57479944e8ded0611fcdaa17b2cebd6c8082b7dcbfd8",+ "sizeBytes": 47652,+ "width": 400,+ "height": 400,+ "view": "iso (glbtool.py render)",+ "sourceLevelSha256": "4296399f12062309c7655d91af3f900cf0487d4e27af4adc9c0c943f82fae1ce"+ }+ },+ {+ "level": "low",+ "path": "lod/stm32g431cbt6-low.glb",+ "sha256": "ff5fbe5cef3ce5392abfc820d0ea379077a975b44628f5f7afcbfee54ad6cbe2",+ "sizeBytes": 72672,+ "decodedBufferBytes": 5544,+ "triangles": 1568,+ "primitives": 495,+ "boundsMm": [+ -4.5,+ -4.5,+ -0.0,+ 4.5,+ 4.5,+ 1.5+ ],+ "markingIncluded": false,+ "mark": null,+ "helperNodes": [],+ "extensions": [+ "KHR_mesh_quantization"+ ],+ "derivation": "plain STEP, quantized; the marking is omitted (unreadable at this size), pin-1 dimple kept",+ "review": "candidate: inspected as a cached image; not qualified in a streaming LOD viewer",+ "preview": {+ "path": "lod/stm32g431cbt6-low.png",+ "sha256": "575d489c2d88090ad59dad2f23f21fc7bc98ce9fe21d16b3dae406ebfa9e8ff1",+ "sizeBytes": 37395,+ "width": 400,+ "height": 400,+ "view": "iso (glbtool.py render)",+ "sourceLevelSha256": "ff5fbe5cef3ce5392abfc820d0ea379077a975b44628f5f7afcbfee54ad6cbe2"+ }+ },+ {+ "level": "super-low",+ "path": "lod/stm32g431cbt6-super-low.glb",+ "sha256": "c92f7510a739df16c45bf50d60d09a32585276d451dd02bf5ece7be8b683d546",+ "sizeBytes": 70324,+ "decodedBufferBytes": 5256,+ "triangles": 1560,+ "primitives": 491,+ "boundsMm": [+ -4.5,+ -4.5,+ -0.0,+ 4.5,+ 4.5,+ 1.5+ ],+ "markingIncluded": false,+ "mark": null,+ "helperNodes": [],+ "extensions": [+ "KHR_mesh_quantization"+ ],+ "derivation": "same package with undrafted body faces, quantized; terminals and pin-1 cue kept, marking omitted",+ "review": "candidate: inspected as a cached image; not qualified in a streaming LOD viewer",+ "preview": {+ "path": "lod/stm32g431cbt6-super-low.png",+ "sha256": "928c6fcc110e2c639608701a9494d5c5a0c9f292454b1eece687cff14536ed44",+ "sizeBytes": 35502,+ "width": 400,+ "height": 400,+ "view": "iso (glbtool.py render)",+ "sourceLevelSha256": "c92f7510a739df16c45bf50d60d09a32585276d451dd02bf5ece7be8b683d546"+ }+ }+ ],+ "hints": [+ "Board-use files: no footprint pad outlines, pad labels, silkscreen or board artwork (helperNodes is empty on every level).",+ "The annotated hero is a separate file (stm32g431cbt6-hero.glb) and is never used on a board."+ ]+}
lod/stm32g431cbt6-low.pngadded⋯ 1 unchanged line ⋯
lod/stm32g431cbt6-super-low.pngadded⋯ 1 unchanged line ⋯
models.jsonadded+255@@ -0,0 +1,255 @@+{+ "schema": "adom/models@1",+ "part": "STM32G431CBT6",+ "models": [+ {+ "path": "lod/stm32g431cbt6-high.glb",+ "format": "glb",+ "title": "STM32G431CBT6 LOD high",+ "status": "current",+ "slot": null,+ "variant": "lod",+ "use": [+ "render",+ "board-insertion"+ ],+ "unique": "LOD high: identical bytes to the marked GLB; see lod/manifest.json",+ "marking": {+ "text": "STM32G431 / CBT6 / Y WW / R",+ "surface": "top, along the long axis, upright with pin 1 lower left",+ "colour": "light grey on black epoxy",+ "source": "ST DS12589 Rev 2 p.177"+ },+ "sha256": "15882e753c30077b50c64ba76dc9638709f072ba47f0ef4ce94385cfb8f2cca9",+ "git_blob": "475ebb0ea2141129df91c0816c671513c13c20a0",+ "bytes": 306656,+ "role": "derived",+ "colours": true,+ "derived_from": "stm32g431cbt6-etched.step",+ "source": "step2glb 0.13.2",+ "thumb": "docs/models/stm32g431cbt6-high-glb-top.png"+ },+ {+ "path": "lod/stm32g431cbt6-low.glb",+ "format": "glb",+ "title": "STM32G431CBT6 LOD low",+ "status": "current",+ "slot": null,+ "variant": "lod",+ "use": [+ "render",+ "board-insertion"+ ],+ "unique": "LOD low: unmarked, quantized; see lod/manifest.json",+ "marking": null,+ "sha256": "ff5fbe5cef3ce5392abfc820d0ea379077a975b44628f5f7afcbfee54ad6cbe2",+ "git_blob": "ebc9a56fa3f089c01ccda1a16f8dfd53fd3aa0f3",+ "bytes": 72672,+ "role": "derived",+ "colours": true,+ "derived_from": "stm32g431cbt6.step",+ "source": "step2glb 0.13.2",+ "thumb": "docs/models/stm32g431cbt6-low-glb-top.png"+ },+ {+ "path": "lod/stm32g431cbt6-medium.glb",+ "format": "glb",+ "title": "STM32G431CBT6 LOD medium",+ "status": "current",+ "slot": null,+ "variant": "lod",+ "use": [+ "render",+ "board-insertion"+ ],+ "unique": "LOD medium: marked, quantized; see lod/manifest.json",+ "marking": {+ "text": "STM32G431 / CBT6 / Y WW / R",+ "surface": "top, along the long axis, upright with pin 1 lower left",+ "colour": "light grey on black epoxy",+ "source": "ST DS12589 Rev 2 p.177"+ },+ "sha256": "4296399f12062309c7655d91af3f900cf0487d4e27af4adc9c0c943f82fae1ce",+ "git_blob": "939cfc0686b3a8d4d8a5406a3950d94646c15346",+ "bytes": 93832,+ "role": "derived",+ "colours": true,+ "derived_from": "stm32g431cbt6-etched.step",+ "source": "step2glb 0.13.2",+ "thumb": "docs/models/stm32g431cbt6-medium-glb-top.png"+ },+ {+ "path": "lod/stm32g431cbt6-super-low.glb",+ "format": "glb",+ "title": "STM32G431CBT6 LOD super-low",+ "status": "current",+ "slot": null,+ "variant": "lod",+ "use": [+ "render",+ "board-insertion"+ ],+ "unique": "LOD super-low: undrafted body, unmarked, quantized; see lod/manifest.json",+ "marking": null,+ "sha256": "c92f7510a739df16c45bf50d60d09a32585276d451dd02bf5ece7be8b683d546",+ "git_blob": "626509cd8392ec2eb4e14d9460be08d8f71102df",+ "bytes": 70324,+ "role": "derived",+ "colours": true,+ "derived_from": "stm32g431cbt6.step",+ "source": "step2glb 0.13.2",+ "thumb": "docs/models/stm32g431cbt6-super-low-glb-top.png"+ },+ {+ "path": "provenance/previous/stm32g431cbt6-kicad-generic.step",+ "format": "step",+ "title": "Previous plain model: KiCad packages3D generic (CC-BY-SA 4.0)",+ "status": "previous",+ "slot": null,+ "variant": "original",+ "use": [+ "archive"+ ],+ "unique": "The generic KiCad model this page carried before; replaced because it misses the datasheet (see provenance/3d-model.json)",+ "marking": null,+ "sha256": "9dea352d93177c2904afd7d7b68ea8855ce1ab691f69f8b3e13655600514b6bf",+ "git_blob": "7dc7c197ed4a79a170f7a98838280341b3bb00fc",+ "bytes": 631616,+ "role": "derived",+ "colours": true,+ "derived_from": null,+ "source": "KiCad packages3D",+ "thumb_bottom": "docs/models/stm32g431cbt6-kicad-generic-step-bottom.png",+ "thumb": "docs/models/stm32g431cbt6-kicad-generic-step-top.png"+ },+ {+ "path": "stm32g431cbt6-etched.glb",+ "format": "glb",+ "title": "STM32G431CBT6, real top marking (GLB, board use)",+ "status": "current",+ "slot": "model_3d_plain",+ "variant": "etched",+ "use": [+ "render",+ "board-insertion"+ ],+ "unique": "Web/board-viewer copy of the marked STEP, clean (no pad outlines, labels or silkscreen)",+ "marking": {+ "text": "STM32G431 / CBT6 / Y WW / R",+ "surface": "top, along the long axis, upright with pin 1 lower left",+ "colour": "light grey on black epoxy",+ "source": "ST DS12589 Rev 2 p.177"+ },+ "sha256": "15882e753c30077b50c64ba76dc9638709f072ba47f0ef4ce94385cfb8f2cca9",+ "git_blob": "475ebb0ea2141129df91c0816c671513c13c20a0",+ "bytes": 306656,+ "role": "derived",+ "colours": true,+ "derived_from": "stm32g431cbt6-etched.step",+ "source": "step2glb 0.13.2 --compress none",+ "thumb": "docs/models/stm32g431cbt6-etched-glb-top.png"+ },+ {+ "path": "stm32g431cbt6-etched.step",+ "format": "step",+ "title": "STM32G431CBT6, real top marking (library model)",+ "status": "current",+ "slot": "model_3d_step",+ "variant": "etched",+ "use": [+ "board-cad"+ ],+ "unique": "The board-CAD model: datasheet-built package with the manufacturer's real top marking from the datasheet, in the Adom.LaserEtch group",+ "marking": {+ "text": "STM32G431 / CBT6 / Y WW / R",+ "surface": "top, along the long axis, upright with pin 1 lower left",+ "colour": "light grey on black epoxy",+ "source": "ST DS12589 Rev 2 p.177"+ },+ "sha256": "73cf3b11aa4684b96ba7cd3de788811acb317a67b44d660349cf24d51fa63147",+ "git_blob": "bcb4118a4430262ad43b98af5b1c8d3345339cff",+ "bytes": 1954016,+ "role": "datasheet-built",+ "colours": true,+ "derived_from": "stm32g431cbt6.step",+ "source": "provenance/3d-model.json",+ "thumb_bottom": "docs/models/stm32g431cbt6-etched-step-bottom.png",+ "thumb": "docs/models/stm32g431cbt6-etched-step-top.png"+ },+ {+ "path": "stm32g431cbt6-hero.glb",+ "format": "glb",+ "title": "STM32G431CBT6, annotated hero",+ "status": "current",+ "slot": "model_3d",+ "variant": "hero",+ "use": [+ "wiki-hero"+ ],+ "unique": "The marked chip plus nonphysical reference art: teal dashed pad outlines, silkscreen with pin 1, pad signal names (adom-hero 1.4); never for a board",+ "marking": {+ "text": "STM32G431 / CBT6 / Y WW / R",+ "surface": "top, along the long axis, upright with pin 1 lower left",+ "colour": "light grey on black epoxy",+ "source": "ST DS12589 Rev 2 p.177"+ },+ "sha256": "5f0660fda7177d4f6503a3bef6aebd768581923efd216709138305ffb4c8ff2f",+ "git_blob": "dba12d0c1f6ad78e76f1081463c52f0491daad06",+ "bytes": 789484,+ "role": "derived",+ "colours": true,+ "derived_from": "stm32g431cbt6-etched.glb",+ "source": "adom/component-hero hero_glb.py",+ "thumb": "docs/models/stm32g431cbt6-hero-glb-top.png"+ },+ {+ "path": "stm32g431cbt6.glb",+ "format": "glb",+ "title": "STM32G431CBT6, unmarked (GLB)",+ "status": "current",+ "slot": null,+ "variant": "plain",+ "use": [+ "render"+ ],+ "unique": "Web copy of the unmarked STEP",+ "marking": null,+ "sha256": "00fead282c7c0123c29420ec47d4da489c0e67a6735f2da6b2d45129b577df3b",+ "git_blob": "44bf7af67873ce3f38b02116093c06ca891ef46f",+ "bytes": 277588,+ "role": "derived",+ "colours": true,+ "derived_from": "stm32g431cbt6.step",+ "source": "step2glb 0.13.2 --compress none",+ "thumb": "docs/models/stm32g431cbt6-glb-top.png"+ },+ {+ "path": "stm32g431cbt6.step",+ "format": "step",+ "title": "STM32G431CBT6, unmarked",+ "status": "current",+ "slot": "model_3d_step_plain",+ "variant": "plain",+ "use": [+ "board-cad"+ ],+ "unique": "Same package with no marking, for anyone who marks their own boards",+ "marking": null,+ "sha256": "e1a453b4c6cc2df7b817392b9dbbf45b615e68c09836b819a30b35c169fa124c",+ "git_blob": "d98bb22502fc898a9b5306517106f4efb30e0525",+ "bytes": 1623223,+ "role": "datasheet-built",+ "colours": true,+ "derived_from": null,+ "source": "provenance/3d-model.json",+ "thumb_bottom": "docs/models/stm32g431cbt6-step-bottom.png",+ "thumb": "docs/models/stm32g431cbt6-step-top.png"+ }+ ],+ "updated": "2026-10-19",+ "front": [+ 0,+ -1,+ 0+ ]+}
provenance/3d-model.jsonadded+205@@ -0,0 +1,205 @@+{+ "schema": "adom/3d-model-provenance@1",+ "mpn": "STM32G431CBT6",+ "date": "2026-10-19",+ "classification": "AI-derived: datasheet-built 3D model and datasheet marking, produced by Claude (AI) for John Lauer; not manufacturer CAD and not a manufacturer certification",+ "datasheet": {+ "doc": "ST DS12589 Rev 2 (October 2019), STM32G431x6/x8/xB datasheet",+ "file": "docs/stm32g431cbt6-datasheet.pdf",+ "url": "https://www.st.com/resource/en/datasheet/stm32g431cb.pdf",+ "marking_pages": "p.177 (6.4 LQFP48 device marking, Figure 51 LQFP48 top view example)",+ "drawing_pages": "p.174 (Figure 49 LQFP48 outline), p.175 (Table 94 LQFP48 mechanical data)",+ "figures": [+ "docs/figures/stm32g431cbt6-ds-p174.png",+ "docs/figures/stm32g431cbt6-ds-p175.png",+ "docs/figures/stm32g431cbt6-ds-p177.png"+ ],+ "sha256": "cabefb21b47dc4c12494b3d9283e3ed1b83925036a65194580b8ebe68adeb71e"+ },+ "marking": {+ "lines": [+ "STM32G431",+ "CBT6",+ "Y WW",+ "R"+ ],+ "description": "Line 1 STM32G431 and line 2 CBT6 (product identification), date code Y WW (Y = year, WW = work week, shown as the placeholders ST prints), revision code R (placeholder, one letter), pin-1 dimple at the pin-1 corner. Not rendered: the ST logo (a trademark, not text) and the unlabelled circle ST draws beside it.",+ "layout_note": "ST's figure puts R in the bottom row beside the logo; the gate keeps all text clear of the pin-1 dimple band, so R sits one row higher at the right, under the date code.",+ "placeholders": "variable fields are drawn with the placeholder letters the datasheet itself prints",+ "surface": "top face, flat light mark 5 um above the face (coplanar), in the Adom.LaserEtch group",+ "font": "service-step2glb /laser-etch single-stroke outline font (not the manufacturer's font)",+ "etch": "STM32G431|CBT6 (planner etch) + Y WW + R (same service glyphs, same group)",+ "gate": {+ "tool": "adom-aiflow-etch check (adom/adom-aiflow 0.2.5)",+ "status": "PASS",+ "checks": {+ "axis": "PASS",+ "upright": "PASS",+ "size": "PASS",+ "band": "PASS",+ "flat": "PASS",+ "group": "PASS",+ "colour": "PASS",+ "widget": "PASS"+ }+ }+ },+ "model": {+ "file": "stm32g431cbt6.step",+ "sha256": "e1a453b4c6cc2df7b817392b9dbbf45b615e68c09836b819a30b35c169fa124c",+ "builder": "provenance/tools/build_gullwing.py",+ "params": "provenance/tools/params.json",+ "cad_kernel": "CadQuery 2.8.0 (OpenCascade)",+ "frame": "Z up, seated on z = 0, centred on the KiCad footprint origin, pin 1 on footprint pad 1, binds with zero offset and rotation",+ "dimensions_mm": [+ {+ "item": "D, E lead tip to tip",+ "datasheet_min_typ_max": "8.80 / 9.00 / 9.20",+ "model": "9.000"+ },+ {+ "item": "D1, E1 body",+ "datasheet_min_typ_max": "6.80 / 7.00 / 7.20",+ "model": "7.000"+ },+ {+ "item": "A overall height",+ "datasheet_min_typ_max": "max 1.60",+ "model": "1.500"+ },+ {+ "item": "A1 standoff",+ "datasheet_min_typ_max": "0.05 / - / 0.15",+ "model": "0.100"+ },+ {+ "item": "A2 body thickness",+ "datasheet_min_typ_max": "1.35 / 1.40 / 1.45",+ "model": "1.400"+ },+ {+ "item": "b lead width",+ "datasheet_min_typ_max": "0.17 / 0.22 / 0.27",+ "model": "0.220"+ },+ {+ "item": "c lead thickness",+ "datasheet_min_typ_max": "0.09 / - / 0.20",+ "model": "0.145"+ },+ {+ "item": "e pitch",+ "datasheet_min_typ_max": "0.50",+ "model": "0.500"+ },+ {+ "item": "L foot (to the 0.25 mm gauge plane)",+ "datasheet_min_typ_max": "0.45 / 0.60 / 0.75",+ "model": "0.600"+ },+ {+ "item": "L1 lead length",+ "datasheet_min_typ_max": "1.00",+ "model": "1.000"+ }+ ],+ "measured_off_drawing": "lead centre height 0.83 mm, body draft inset 0.13 mm per face (top view double outline), pin-1 dimple 0.75 mm diameter centred 1.00 mm and 1.14 mm from the pin-1 edges (Figure 49 at 56.57 px/mm)",+ "overlay_proof": "docs/proof/stm32g431cbt6-overlay-ds12589-p174.png",+ "previous": {+ "file": "provenance/previous/stm32g431cbt6-kicad-generic.step",+ "what": "KiCad packages3D LQFP-48_7x7mm_P0.5mm.step (CC-BY-SA 4.0), the page's previous plain model",+ "why": "within tolerance on every printed size except L: the foot measured 0.444 mm to the 0.25 mm gauge plane, under ST's 0.45 mm minimum. Rebuilt from Table 94 typical values instead.",+ "sha256": "9dea352d93177c2904afd7d7b68ea8855ce1ab691f69f8b3e13655600514b6bf"+ }+ },+ "etched": {+ "file": "stm32g431cbt6-etched.step",+ "sha256": "73cf3b11aa4684b96ba7cd3de788811acb317a67b44d660349cf24d51fa63147",+ "check_image": "docs/proof/stm32g431cbt6-etched.check.png",+ "plan": "docs/proof/stm32g431cbt6-etched.mark-plan.json"+ },+ "glb": {+ "stm32g431cbt6.glb": "00fead282c7c0123c29420ec47d4da489c0e67a6735f2da6b2d45129b577df3b",+ "stm32g431cbt6-etched.glb": "15882e753c30077b50c64ba76dc9638709f072ba47f0ef4ce94385cfb8f2cca9",+ "stm32g431cbt6-hero.glb": "5f0660fda7177d4f6503a3bef6aebd768581923efd216709138305ffb4c8ff2f"+ },+ "hero": {+ "file": "stm32g431cbt6-hero.glb",+ "generator": "adom/component-hero hero_glb.py (adom-hero 1.4)",+ "board_use": false,+ "preview": "docs/proof/stm32g431cbt6-hero-preview.png"+ },+ "outline": {+ "file": "artwork/STM32G431CBT6-3d-outline-dark-named.svg",+ "sha256": "1d63e0b37912ee9f8453cd9f439b93e634802c298c85b569188c129bd35de2fa",+ "tool": "adom-aiflow-etch outline (etched-STEP mode: the mark's own edges, so all four marking lines show)",+ "gate": "PASS"+ },+ "page_hero_image": "docs/hero.png: the previous billboard with its chip render replaced by a render of stm32g431cbt6-etched.glb",+ "removed_superseded": [+ {+ "path": "stm32g431cbt6-mpn.step",+ "sha256": "8250f63808691f8cc8878494a1d296a890a8a4429eee743b08815ee22a9acc65"+ },+ {+ "path": "stm32g431cbt6-mpn-overlay.step",+ "sha256": "72e6354474c848ed704c42931fe71e60ea35331f5958c8ce3dd5f680088abc48"+ },+ {+ "path": "stm32g431cbt6-mpn.glb",+ "sha256": "d3c14f24b39ffaf89def6e8405e045ff13792a6eacca31f748b9cd67b8c1a6a3"+ },+ {+ "path": "stm32g431cbt6-mpn.png",+ "sha256": "634dc1d21c79556c21b48fa1b4249ad921245b4d7d20b617e7f659e765010ef8"+ },+ {+ "path": "stm32g431cbt6-hero-mpn.glb",+ "sha256": "d4594009e3c830dcd2de4ca87bc6f0ba690a2479516a36f2bd5509585344ceb2"+ },+ {+ "path": "stm32g431cbt6-hero-footprint.glb",+ "sha256": "b519c4f9d0abfbf83a974ba240e51312e8db8190e10d554c38ee1e99b2f7bc3e"+ },+ {+ "path": "stm32g431cbt6-hero-footprint.overlay.json",+ "sha256": "7cf083537f370c72c9a28f479d07a453692cab6845c9e062b2aaad6897ed5aec"+ },+ {+ "path": "stm32g431cbt6-etched.fit.json",+ "sha256": "1db908d3146b1bdd1a9a2f75790e6526c64e6395e137127d48347c52ccc8595d"+ },+ {+ "path": "MPN-MARKING.md",+ "sha256": "d49795c9395b424742f231d3096809e1fd50fc078b1b14a8fbeefca9e4d3d100"+ },+ {+ "path": "mpn-marking.json",+ "sha256": "c226ee005e6e7af9e55903c9c3ef09d8323920aba8201fefb883ed3324ddfbff"+ },+ {+ "path": "fit-mpn-surface.py",+ "sha256": "791563f83b56cbc9bb2885ca5ad7134cb8b3ecf54c1724e8338df570d5e6492f"+ },+ {+ "path": "hero-binding.json",+ "sha256": "5c79cb5993a00ba5488f351f759a831db839dc2bca39549c58435ce8c856b75d"+ },+ {+ "path": "hero-pin-map.json",+ "sha256": "12c97148bf07bb6be87914bf2c4fc201971f40349cf1ffc478f1bc9fce72cfc6"+ },+ {+ "path": "MPN-SOURCE-LICENSE.md",+ "sha256": "285b027a721577d0bc0198f36e948c6c282ddaa4b58fef8ed826749576eb0aef"+ }+ ],+ "tools": {+ "adom-aiflow-etch": "adom/adom-aiflow 0.2.5",+ "step2glb": "0.13.2",+ "adom-symbol": "1.2.6",+ "adom-lbr": "2.47.5",+ "cadquery": "2.8.0"+ },+ "license": "Adom-authored model and artwork: CC-BY-4.0. The previous KiCad model kept in provenance/previous is CC-BY-SA 4.0 (KiCad packages3D)."+}
provenance/tools/build_gullwing.pyadded+61@@ -0,0 +1,61 @@+#!/usr/bin/env python3+"""Datasheet-built gull-wing package (SOIC / QFP) in the Adom/KiCad frame: Z up, seated on z=0, centred,+pin 1 where the KiCad footprint puts pad 1 (top left, x<0 y>0 in the Y-up model frame), pads numbered+counter-clockwise seen from the top.+usage: build_gullwing.py params.json out.step+params: name, kind ("dual"|"quad"), body_x, body_y (max body size at the parting line, x = across the+pin rows for dual), span_x, span_y (lead tip to tip; dual uses span_x), a1 (standoff), top (overall+height), zp (lead centre height), c (lead thickness), b (lead width), e (pitch), L (foot length tip to the+outer face of the vertical leg = gauge-plane length with square bends), n_side (pins per row/side),+inset_bot, inset_top (draft insets of the bottom/top faces), dot {x, y, d, depth} or null.+Square lead bends and plain drafted body faces; every number is cited in the provenance record."""+import sys, json, math, cadquery as cq+P = json.load(open(sys.argv[1])); out = sys.argv[2]+bx, by, a1, top, zp, c, b, e, L = (P[k] for k in ("body_x", "body_y", "a1", "top", "zp", "c", "b", "e", "L"))+ib, it = P["inset_bot"], P["inset_top"]+def rect(z, w, l):+ return cq.Wire.makePolygon([cq.Vector(-w/2, -l/2, z), cq.Vector(w/2, -l/2, z), cq.Vector(w/2, l/2, z), cq.Vector(-w/2, l/2, z)], close=True)+body = cq.Solid.makeLoft([rect(a1, bx - 2*ib, by - 2*ib), rect(zp, bx, by)]).fuse(+ cq.Solid.makeLoft([rect(zp, bx, by), rect(top, bx - 2*it, by - 2*it)])).clean()+dot = None+if P.get("dot"):+ d = P["dot"]+ cut = cq.Solid.makeCylinder(d["d"]/2, d["depth"] + 0.1, cq.Vector(d["x"], d["y"], top - d["depth"]))+ body = body.cut(cut).clean()+ dot = cq.Solid.makeCylinder(d["d"]/2, d["depth"] - 0.01, cq.Vector(d["x"], d["y"], top - d["depth"]))+def lead_x(half_body, tip, y):+ """a lead on the -x side running along -x, centred at y"""+ xt = tip; xv = tip - L # outer face of the vertical leg (positive distances)+ xin = half_body - 0.15+ lt, lb = zp + c/2, zp - c/2+ pts = [(xin, lb), (xin, lt), (xv, lt), (xv, c), (xt, c), (xt, 0), (xv - c, 0), (xv - c, lb)]+ # build on +x then mirror: profile in XZ+ prof = cq.Workplane("XZ").polyline([(-x, z) for x, z in pts]).close().extrude(b/2, both=True).val()+ return prof.translate(cq.Vector(0, y, 0))+def rotz(s, deg):+ return s.rotate(cq.Vector(0, 0, 0), cq.Vector(0, 0, 1), deg)+n = P["n_side"]; offs = [((n - 1)/2 - i) * e for i in range(n)] # +y first (pin 1 at the top of the left row)+leads = [] # (pin number, solid)+num = 1+if P["kind"] == "dual":+ for y in offs: # left row, top to bottom: 1..n+ leads.append((num, lead_x(bx/2, P["span_x"]/2, y))); num += 1+ for y in reversed(offs): # right row, bottom to top: n+1..2n+ leads.append((num, rotz(lead_x(bx/2, P["span_x"]/2, -y), 180))); num += 1+else:+ # left side (pins 1..n top to bottom), bottom side (left to right), right side (bottom to top), top side (right to left)+ for side, ang in enumerate((0, 90, 180, 270)):+ half = (bx if side % 2 == 0 else by)/2; tip = (P["span_x"] if side % 2 == 0 else P["span_y"])/2+ for y in offs:+ leads.append((num, rotz(lead_x(half, tip, y), ang))); num += 1+asm = cq.Assembly(name=P["name"])+asm.add(body, name="body", color=cq.Color(0.148, 0.145, 0.145))+if dot is not None:+ asm.add(dot, name="pin1_dimple", color=cq.Color(0.33, 0.32, 0.31))+for k, s in leads:+ asm.add(s, name=f"lead_{k}", color=cq.Color(0.824, 0.820, 0.781))+asm.save(out, "STEP")+allc = cq.Compound.makeCompound([body] + [s for _, s in leads]); bb = allc.BoundingBox()+p1 = leads[0][1].BoundingBox()+print(json.dumps(dict(bbox=[round(v, 3) for v in (bb.xmin, bb.xmax, bb.ymin, bb.ymax, bb.zmin, bb.zmax)],+ pin1_centre=[round((p1.xmin + p1.xmax)/2, 3), round((p1.ymin + p1.ymax)/2, 3)], leads=len(leads))))
provenance/tools/extra_marks.pyadded+127@@ -0,0 +1,127 @@+#!/usr/bin/env python3+"""Add extra marking pieces (date code, revision code) to an adom-aiflow-etch result, in the same+Adom.LaserEtch group, flat on the same plane, using the same /laser-etch service glyphs.+Layout is given in the mark's reading frame (r along the reading direction, up = text up), mm,+relative to the centre of the existing mark block.+usage: extra_marks.py etched.step out.step spec.json+spec: {"align_line2": "left"|"center", "shift_up": 0.0,+ "pieces": [{"text": "Y WW", "cap": 0.5, "anchor": "right"|"left"|"center", "r": 2.5, "up": -1.3}]}+ r/up = position of the piece's anchor edge (r) and its vertical centre (up), after shift_up."""+import sys, json, os, tempfile+sys.path.insert(0, "/home/adom/project/adom_modules/adom/adom-aiflow/tools/laser-etch")+import step_text as ST, mark_geom as MG, mark_plan as MP, mark_svc as SVC+from mark_etch import PROXY+src, out, spec = sys.argv[1], sys.argv[2], json.load(open(sys.argv[3]))+A = ST.Step(src); A.flatten()+m = MG.analyse(A, "Adom.LaserEtch")+u, v, z = m["u"], m["v"], m["z"]+def to_xy(r, up):+ return (r * u[0] + up * v[0], r * u[1] + up * v[1])+def rf(p): # board xy -> reading frame+ return (p[0] * u[0] + p[1] * u[1], p[0] * v[0] + p[1] * v[1])+bb = m["bbox"]+corners = [rf((bb[0], bb[1])), rf((bb[3], bb[4]))]+r0, r1 = sorted([corners[0][0], corners[1][0]]); u0, u1 = sorted([corners[0][1], corners[1][1]])+rc, uc = (r0 + r1) / 2, (u0 + u1) / 2+report = dict(u=u, v=v, z=z, block_r=[r0, r1], block_up=[u0, u1], lines=[])+def line_rf(l):+ b = l["box"]; a = rf((b[0], b[1])); c = rf((b[2], b[3]))+ return sorted([a[0], c[0]]), sorted([a[1], c[1]])+if spec.get("align_line2") == "left" and len(m["lines"]) == 2:+ l1r, _ = line_rf(m["lines"][0]); l2r, _ = line_rf(m["lines"][1])+ dr = l1r[0] - l2r[0]+ ents = A.faces_subset_entities("Adom.LaserEtch", m["lines"][1]["faces"])+ dx, dy = to_xy(dr, 0)+ A.transform_entities(ents, (0, 0, 0), 1.0, (dx, dy, 0))+su = spec.get("shift_up", 0.0)+if "block_up_centre" in spec:+ su = spec["block_up_centre"] - uc+if su:+ dx, dy = to_xy(0, su)+ A.transform_product("Adom.LaserEtch", (0, 0, z), 1.0, 0, (dx, dy, 0), set_z=z)+tmp = tempfile.mkdtemp()+A.write(os.path.join(tmp, "base.step"))+piece_files = []+last_q = 0+for i, p in enumerate(spec["pieces"]):+ proxy = ST.Step(PROXY).scale_axes(-5, -2, -0.05, 10, 4, 0.05)+ e = ST.Step(SVC.laser_etch(proxy.text, p["text"], None, job="extra-mark"))+ e.flatten()+ me = MG.analyse(e, "Adom.LaserEtch")+ if p.get("turn_like_previous") and i > 0:+ q = last_q+ else:+ q = MP.quarter_turns_to(me["u"], (u[0], u[1]))+ last_q = q+ q = (q + p.get("extra_q", 0)) % 4+ e.transform_product("Adom.LaserEtch", me["centre"], 1.0, q)+ me = MG.analyse(e, "Adom.LaserEtch")+ k = p["cap"] / me["capMm"]+ e.transform_product("Adom.LaserEtch", me["centre"], k, 0)+ me = MG.analyse(e, "Adom.LaserEtch")+ b = me["bbox"]; a = rf((b[0], b[1])); c = rf((b[3], b[4]))+ pr0, pr1 = sorted([a[0], c[0]]); pu0, pu1 = sorted([a[1], c[1]])+ anchor = p.get("anchor", "center")+ cur_r = {"left": pr0, "right": pr1, "center": (pr0 + pr1) / 2}[anchor]+ if spec.get("absolute"):+ tr, tu = p["r"] - cur_r, p["up"] - (pu0 + pu1) / 2+ else:+ tr, tu = rc + p["r"] - cur_r, uc + su + p["up"] - (pu0 + pu1) / 2+ dx, dy = to_xy(tr, tu)+ e.transform_product("Adom.LaserEtch", (0, 0, 0), 1.0, 0, (dx, dy, 0), set_z=z)+ f = os.path.join(tmp, "piece%d.step" % i); e.write(f); piece_files.append(f)+ report["lines"].append(dict(text=p["text"], cap=p["cap"], r=[pr0 + tr, pr1 + tr], up=[pu0 + tu, pu1 + tu]))+# merge with XCAF+from OCP.STEPCAFControl import STEPCAFControl_Reader, STEPCAFControl_Writer+from OCP.STEPControl import STEPControl_AsIs+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_LabelSequence, TDF_Label+from OCP.TDataStd import TDataStd_Name+from OCP.TopoDS import TopoDS_Compound+from OCP.BRep import BRep_Builder+from OCP.TopExp import TopExp_Explorer+from OCP.TopAbs import TopAbs_FACE+from OCP.Quantity import Quantity_Color+from OCP.Interface import Interface_Static+def read(f):+ d = TDocStd_Document(TCollection_ExtendedString("XmlOcaf"))+ r = STEPCAFControl_Reader(); r.SetColorMode(True); r.SetNameMode(True); r.ReadFile(f); r.Transfer(d)+ return d+def nm(l):+ n = TDataStd_Name(); return n.Get().ToExtString() if l.FindAttribute(TDataStd_Name.GetID_s(), n) else ""+def find(st, want):+ seq = TDF_LabelSequence(); st.GetShapes(seq)+ for i in range(1, seq.Length() + 1):+ if nm(seq.Value(i)) == want and not st.IsAssembly_s(seq.Value(i)):+ return seq.Value(i)+doc = read(os.path.join(tmp, "base.step"))+st = XCAFDoc_DocumentTool.ShapeTool_s(doc.Main()); ct = XCAFDoc_DocumentTool.ColorTool_s(doc.Main())+lab = find(st, "Adom.LaserEtch")+assert lab is not None, "no Adom.LaserEtch part"+col = Quantity_Color()+got = ct.GetColor_s(lab, XCAFDoc_ColorType.XCAFDoc_ColorSurf, col) or ct.GetColor_s(lab, XCAFDoc_ColorType.XCAFDoc_ColorGen, col) or ct.GetColor(st.GetShape_s(lab), XCAFDoc_ColorType.XCAFDoc_ColorSurf, col)+if not got:+ ex = TopExp_Explorer(st.GetShape_s(lab), TopAbs_FACE)+ while ex.More():+ if ct.GetColor(ex.Current(), XCAFDoc_ColorType.XCAFDoc_ColorSurf, col): got = True; break+ ex.Next()+comp = TopoDS_Compound(); bld = BRep_Builder(); bld.MakeCompound(comp)+bld.Add(comp, st.GetShape_s(lab))+for f in piece_files:+ d2 = read(f); st2 = XCAFDoc_DocumentTool.ShapeTool_s(d2.Main())+ l2 = find(st2, "Adom.LaserEtch")+ bld.Add(comp, st2.GetShape_s(l2))+st.SetShape(lab, comp)+st.UpdateAssemblies()+if got:+ ct.SetColor(lab, col, XCAFDoc_ColorType.XCAFDoc_ColorSurf)+ ex = TopExp_Explorer(comp, TopAbs_FACE)+ while ex.More():+ ct.SetColor(ex.Current(), col, XCAFDoc_ColorType.XCAFDoc_ColorSurf); ex.Next()+Interface_Static.SetCVal_s("write.step.schema", "AP214IS")+w = STEPCAFControl_Writer(); w.SetColorMode(True); w.SetNameMode(True)+w.Transfer(doc, STEPControl_AsIs); w.Write(out)+report["colour"] = [col.Red(), col.Green(), col.Blue()] if got else None+print(json.dumps(report, indent=1))
provenance/tools/gaugeL.pyadded+11@@ -0,0 +1,11 @@+import sys, cadquery as cq+from OCP.BRepAlgoAPI import BRepAlgoAPI_Section+from OCP.gp import gp_Pln, gp_Pnt, gp_Dir+s = cq.importers.importStep(sys.argv[1]).val(); y = float(sys.argv[2])+bb = s.BoundingBox()+# vertical section through one left lead centre (plane y=const), then horizontal line z=0.25 on it+sec = BRepAlgoAPI_Section(s.wrapped, gp_Pln(gp_Pnt(0, y, 0), gp_Dir(0, 1, 0))); sec.Build(); prof = cq.Shape.cast(sec.Shape())+sec2 = BRepAlgoAPI_Section(prof.wrapped, gp_Pln(gp_Pnt(0, 0, 0.25), gp_Dir(0, 0, 1))); sec2.Build()+pts = sorted(v.X for v in cq.Shape.cast(sec2.Shape()).Vertices())+left = [p for p in pts if p < 0]+print("tip", round(bb.xmin, 3), "gauge-plane crossings (left):", [round(p, 3) for p in left], "L =", round(left[0] - bb.xmin, 3) if left else None)
provenance/tools/glbtool.pyadded+108@@ -0,0 +1,108 @@+#!/usr/bin/env python3+"""GLB stats + preview render (numpy/PIL, flat shading), reading quantized accessors correctly.+usage: glbtool.py stats a.glb [...] -> JSON per file+ glbtool.py render a.glb out.png [view=iso|top] [size=400]"""+import sys, json, hashlib, math, struct+import numpy as np+from pygltflib import GLTF2+from PIL import Image, ImageDraw+CT = {5120: np.int8, 5121: np.uint8, 5122: np.int16, 5123: np.uint16, 5125: np.uint32, 5126: np.float32}+NC = {"SCALAR": 1, "VEC2": 2, "VEC3": 3, "VEC4": 4, "MAT4": 16}+def acc(g, blob, i):+ a = g.accessors[i]; bv = g.bufferViews[a.bufferView]; dt = np.dtype(CT[a.componentType]); n = NC[a.type]+ off = (bv.byteOffset or 0) + (a.byteOffset or 0); stride = bv.byteStride or dt.itemsize * n+ raw = np.frombuffer(blob, dtype=np.uint8, count=stride * (a.count - 1) + dt.itemsize * n, offset=off)+ arr = np.lib.stride_tricks.as_strided(raw, shape=(a.count, n * dt.itemsize), strides=(stride, 1)).copy().view(dt).reshape(a.count, n).astype(np.float64)+ if a.normalized:+ arr = {np.int8: lambda x: np.maximum(x / 127, -1), np.uint8: lambda x: x / 255, np.int16: lambda x: np.maximum(x / 32767, -1), np.uint16: lambda x: x / 65535}[dt.type](arr)+ return arr+def node_mat(n):+ if n.matrix: return np.array(n.matrix).reshape(4, 4).T+ M = np.eye(4)+ if n.scale: M = np.diag(list(n.scale) + [1]) @ M+ if n.rotation:+ x, y, z, w = n.rotation+ R = np.array([[1-2*(y*y+z*z), 2*(x*y-z*w), 2*(x*z+y*w)], [2*(x*y+z*w), 1-2*(x*x+z*z), 2*(y*z-x*w)], [2*(x*z-y*w), 2*(y*z+x*w), 1-2*(x*x+y*y)]])+ R4 = np.eye(4); R4[:3, :3] = R; M = R4 @ M+ if n.translation:+ T = np.eye(4); T[:3, 3] = n.translation; M = T @ M+ return M+def walk(g):+ out = []+ def rec(i, P, path):+ n = g.nodes[i]; M = P @ node_mat(n); p = path + [n.name or str(i)]+ if n.mesh is not None: out.append((n, M, p))+ for c in n.children or []: rec(c, M, p)+ for r in g.scenes[g.scene or 0].nodes: rec(r, np.eye(4), [])+ return out+def load(f):+ g = GLTF2().load(f); return g, g.binary_blob()+def tris(f):+ g, blob = load(f); res = []+ for n, M, path in walk(g):+ for pr in g.meshes[n.mesh].primitives:+ P = acc(g, blob, pr.attributes.POSITION); P = (M @ np.c_[P, np.ones(len(P))].T).T[:, :3]+ I = acc(g, blob, pr.indices).astype(int).reshape(-1, 3) if pr.indices is not None else np.arange(len(P)).reshape(-1, 3)+ mat = g.materials[pr.material] if pr.material is not None else None+ col = mat.pbrMetallicRoughness.baseColorFactor if mat and mat.pbrMetallicRoughness else [0.6, 0.6, 0.6, 1]+ res.append(dict(path="/".join(path), P=P, I=I, col=col))+ return g, res+def stats(f):+ g, res = tris(f); data = open(f, "rb").read()+ allP = np.vstack([r["P"] for r in res])+ dec = 0+ for a in g.accessors: dec += a.count * NC[a.type] * np.dtype(CT[a.componentType]).itemsize+ names = [n.name or "" for n in g.nodes]+ helpers = [n for n in names if any(k in n.lower() for k in ("pad outline", "silkscreen", "signal name", "reference artwork", "footprint pads", "courtyard"))]+ mark = [r for r in res if "LaserEtch" in r["path"]]+ chip = [r for r in res if "LaserEtch" not in r["path"]]+ mz = None+ if mark:+ mp = np.vstack([r["P"] for r in mark]); top = max(r["P"][:, 2].max() for r in chip)+ mz = dict(mark_z_min_mm=round(mp[:, 2].min() * 1000, 4), mark_z_max_mm=round(mp[:, 2].max() * 1000, 4), chip_top_mm=round(top * 1000, 4),+ lift_um=round((mp[:, 2].min() - top) * 1e6, 2))+ return dict(file=f.split("/")[-1], sha256=hashlib.sha256(data).hexdigest(), bytes=len(data), triangles=int(sum(len(r["I"]) for r in res)),+ primitives=len(res), decodedBytes=int(dec), boundsMm=[round(v * 1000, 3) for v in (*allP.min(0), *allP.max(0))],+ helperNodes=helpers, mark=mz, extensions=g.extensionsUsed or [])+def render(f, out, view="iso", size=400):+ g, res = tris(f)+ allP = np.vstack([r["P"] for r in res]); c = (allP.min(0) + allP.max(0)) / 2+ if view == "top":+ R = np.eye(3)+ elif view.startswith("hero"):+ # pose: reading direction (-Y in the board frame) turned to +X, then a 3/4 view from the front, above+ P90 = np.array([[0, -1, 0], [1, 0, 0], [0, 0, 1]])+ a = math.radians(28); b = math.radians(-48)+ Rz = np.array([[math.cos(a), -math.sin(a), 0], [math.sin(a), math.cos(a), 0], [0, 0, 1]])+ Rx = np.array([[1, 0, 0], [0, math.cos(b), -math.sin(b)], [0, math.sin(b), math.cos(b)]])+ R = Rx @ Rz @ P90+ else: # iso: rotate -45 about z then tilt 55 about x (camera from front-left-up, pin-1 side)+ a = math.radians(-35); b = math.radians(-55)+ Rz = np.array([[math.cos(a), -math.sin(a), 0], [math.sin(a), math.cos(a), 0], [0, 0, 1]])+ Rx = np.array([[1, 0, 0], [0, math.cos(b), -math.sin(b)], [0, math.sin(b), math.cos(b)]])+ R = Rx @ Rz+ light = np.array([0.3, -0.5, 0.8]); light /= np.linalg.norm(light)+ polys = []+ for r in res:+ Q = (r["P"] - c) @ R.T+ for t in r["I"]:+ v = Q[t]; nrm = np.cross(v[1] - v[0], v[2] - v[0]); ln = np.linalg.norm(nrm)+ if ln == 0: continue+ nrm /= ln+ if nrm[2] < 0: nrm = -nrm+ sh = 0.35 + 0.65 * max(0, float(nrm @ light))+ col = tuple(int(255 * min(1, (x ** (1 / 2.2)) * sh)) for x in r["col"][:3])+ polys.append(((1 if "LaserEtch" in r["path"] else 0), v[:, 2].mean(), v[:, :2], col))+ polys.sort(key=lambda p: (p[0], p[1]))+ polys = [(p[1], p[2], p[3]) for p in polys]+ Q = np.vstack([p[1] for p in polys]); ext = (Q.max(0) - Q.min(0)).max()+ s = size * 0.86 / ext; S = 3+ im = Image.new("RGBA", (size * S, size * S), (0, 0, 0, 0) if view.startswith("hero") else (36, 40, 46, 255)); d = ImageDraw.Draw(im)+ for _, v, col in polys:+ d.polygon([(size * S / 2 + x * s * S, size * S / 2 - y * s * S) for x, y in v], fill=col)+ im.resize((size, size), Image.LANCZOS).save(out)+if __name__ == "__main__":+ if sys.argv[1] == "stats":+ print(json.dumps([stats(f) for f in sys.argv[2:]], indent=1))+ else:+ render(sys.argv[2], sys.argv[3], *(sys.argv[4:5] or ["iso"]), *(map(int, sys.argv[5:6])))
provenance/tools/outline_from_etched.pyadded+11@@ -0,0 +1,11 @@+#!/usr/bin/env python3+"""Named outline drawn from the etched model's own mark edges (mark_outline's fallback mode), for a+multi-piece manufacturer marking the composed single-stroke name (max 2 lines) cannot express.+usage: outline_from_etched.py plain.step etched.step footprint.kicad_mod MPN out_dir [plan.json]"""+import sys, json+sys.path.insert(0, "/home/adom/project/adom_modules/adom/adom-aiflow/tools/laser-etch")+import mark_outline as MO, mark_check as MC, mark_plan as MP+step, etched, fp, mpn, out_dir = sys.argv[1:6]+plan = json.load(open(sys.argv[6])) if len(sys.argv) > 6 else MP.plan(step, fp, mpn, "auto", "auto", "high", MP.MARGIN_W, MP.MARGIN_H)+MC.outline_text = lambda *a, **k: (None, None) # force the etched-STEP outline mode+print(json.dumps(MO.outlines(step, plan, etched, out_dir), indent=1, default=str)[:3000])
provenance/tools/overlay.jsonadded+7@@ -0,0 +1,7 @@+{"step": "stm32g431cbt6.step", "out": "stm32g431cbt6-overlay-ds12589-p174.png", "body": {"x": 3.5, "y": 3.5, "zmin": 0.1},+ "views": [{"page": "../../ics_dl/st/hi-174.png", "crop": [960, 1330, 1580, 1950], "view": "top", "rot": 90, "centre": [1260, 1633.5], "scale": 56.57, "title": "Top view (Figure 49), 56.57 px/mm"},+ {"page": "../../ics_dl/st/hi-174.png", "crop": [960, 790, 1580, 950], "view": "front", "centre": [1260, 916], "scale": 56.57, "title": "Side view (Figure 49), same scale"}],+ "caption": ["STM32G431CBT6 LQFP48 model (red outline, blue fill; orange = body top) over ST DS12589 Rev 2 p.174 Figure 49, rendered at 300 dpi.",+ "Scale calibrated on the drawing itself: D1 = 7.00 mm drawn 396 px, D = 9.00 mm drawn 509 px (56.57 px/mm both).",+ "Model (Table 94 typ): D=E 9.00, D1=E1 7.00, A1 0.10, A2 1.40 (A 1.50), b 0.22, e 0.50, L 0.60, c 0.145. ST notes the drawing is not to scale;",+ "the side view is drawn at about A max (1.60), so the model top sits about 0.1 mm under the drawn top."]}
provenance/tools/overlay.pyadded+77@@ -0,0 +1,77 @@+#!/usr/bin/env python3+"""Overlay proof: an orthographic render of a STEP blended over a datasheet drawing view.+usage: overlay.py spec.json+spec: {"step": "...", "out": "...png", "body": {"x": half, "y": half, "zmin": z}, # body box for the inner outline+ "views": [{"page": "png", "crop": [x0,y0,x1,y1], "view": "top"|"front"|"side", "rot": deg (top only),+ "flip": false, "centre": [px,py] (page px of model origin: top=body centre, front/side=(centre, seating plane)),+ "scale": px_per_mm, "title": "..."}],+ "caption": ["line", ...]}+front = looking along +Y (shows X horizontally, Z up); side = looking along -X (shows Y, Z)."""+import sys, json, math+import numpy as np+import cadquery as cq+from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageChops+spec = json.load(open(sys.argv[1]))+shape = cq.importers.importStep(spec["step"]).val()+verts, tris = shape.tessellate(0.005, 0.2)+V = np.array([[v.x, v.y, v.z] for v in verts]); T = np.array(tris)+bx = spec.get("body")+def proj(view, rot=0):+ if view == "top":+ a = math.radians(rot); c, s = math.cos(a), math.sin(a)+ return np.stack([c * V[:, 0] - s * V[:, 1], s * V[:, 0] + c * V[:, 1]], 1)+ if view == "front":+ return np.stack([V[:, 0], V[:, 2]], 1)+ if view == "side":+ return np.stack([V[:, 1], V[:, 2]], 1)+def body_tris():+ if not bx: return np.zeros(len(T), bool)+ c = V[T].mean(1)+ return (np.abs(c[:, 0]) <= bx["x"] + 1e-3) & (np.abs(c[:, 1]) <= bx["y"] + 1e-3) & (c[:, 2] >= bx["zmin"] - 1e-3)+BT = body_tris()+SS = 4+panels = []+try:+ font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 22)+ small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 18)+except Exception:+ font = small = ImageFont.load_default()+for vw in spec["views"]:+ page = Image.open(vw["page"]).convert("L")+ x0, y0, x1, y1 = vw["crop"]+ W, H = x1 - x0, y1 - y0+ P = proj(vw["view"], vw.get("rot", 0))+ if vw.get("flip"): P[:, 0] = -P[:, 0]+ s = vw["scale"]; cx, cy = vw["centre"]+ px = (cx - x0 + P[:, 0] * s) * SS; py = (cy - y0 - P[:, 1] * s) * SS+ def mask(sel):+ m = Image.new("L", (W * SS, H * SS), 0); d = ImageDraw.Draw(m)+ for t in T[sel]:+ d.polygon([(px[i], py[i]) for i in t], fill=255)+ return m.resize((W, H), Image.LANCZOS).point(lambda v: 255 if v > 127 else 0)+ m_all = mask(np.ones(len(T), bool))+ m_body = mask(BT) if BT.any() and vw["view"] == "top" else None+ def edge(m):+ return ImageChops.subtract(m, m.filter(ImageFilter.MinFilter(3)))+ base = page.crop((x0, y0, x1, y1)).convert("RGB")+ tint = Image.new("RGB", (W, H), (0, 170, 220))+ out = Image.composite(Image.blend(base, ImageChops.multiply(base, tint), 0.55), base, m_all)+ red = Image.new("RGB", (W, H), (230, 30, 30))+ out = Image.composite(red, out, edge(m_all))+ if m_body is not None:+ out = Image.composite(Image.new("RGB", (W, H), (240, 120, 0)), out, edge(m_body))+ hdr = 34+ pan = Image.new("RGB", (W, H + hdr), "white"); pan.paste(out, (0, hdr))+ ImageDraw.Draw(pan).text((6, 6), vw.get("title", vw["view"]), fill="black", font=font)+ panels.append(pan)+cap = spec.get("caption", [])+Wt = max(sum(p.width for p in panels) + 20 * (len(panels) + 1), 40 + max([int(small.getlength(c)) for c in cap] + [0]))+Ht = max(p.height for p in panels) + 40 + 26 * len(cap)+sheet = Image.new("RGB", (Wt, Ht), "white"); x = 20+for p in panels:+ sheet.paste(p, (x, 20)); x += p.width + 20+d = ImageDraw.Draw(sheet); y = max(p.height for p in panels) + 30+for line in cap:+ d.text((20, y), line, fill="black", font=small); y += 26+sheet.save(spec["out"])+print("wrote", spec["out"], sheet.size)
provenance/tools/params.jsonadded+3@@ -0,0 +1,3 @@+{"name": "STM32G431CBT6_LQFP48", "kind": "quad", "body_x": 7.0, "body_y": 7.0, "span_x": 9.0, "span_y": 9.0,+ "a1": 0.10, "top": 1.50, "zp": 0.83, "c": 0.145, "b": 0.22, "e": 0.5, "L": 0.60, "n_side": 12,+ "inset_bot": 0.13, "inset_top": 0.13, "dot": {"x": -2.36, "y": 2.50, "d": 0.75, "depth": 0.05}}
provenance/tools/spec.jsonadded+3@@ -0,0 +1,3 @@+{"align_line2": "left", "absolute": true, "block_up_centre": 1.35,+ "pieces": [{"text": "Y WW", "cap": 0.75, "anchor": "right", "r": 2.75, "up": -0.15},+ {"text": "R", "extra_q": 3, "cap": 0.62, "anchor": "right", "r": 2.75, "up": -1.5}]}
stm32g431cbt6-hero.overlay.jsonadded+1140@@ -0,0 +1,1140 @@+{+ "spec": "adom-hero 1.4",+ "mpn": "STM32G431CBT6",+ "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,+ 0.902,+ 0.863,+ 0.5+ ],+ "pads": 48,+ "span_mm": 9.8,+ "dash_mm": 0.245,+ "gap_mm": 0.147,+ "pad_stroke_mm": 0.0588+ },+ "silkscreen": {+ "material": "Silkscreen reference lines - 30 percent opacity",+ "rgba": [+ 0.92,+ 0.96,+ 1.0,+ 0.3+ ],+ "footprint_items": 9,+ "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": 48,+ "sides": "top + mirrored underside (single-sided)",+ "omitted": null,+ "fallback_to_pad_number": 0+ }+ },+ "align": {+ "ok": true,+ "centre_offset_mm": 0.0,+ "tolerance_mm": 0.784,+ "orientation_flipped": false,+ "contacts_bbox_mm": [+ -4.5,+ 4.5,+ -4.5,+ 4.5+ ],+ "pads_bbox_mm": [+ -4.9,+ 4.9,+ -4.9,+ 4.9+ ],+ "seating_z_mm": 0.0+ },+ "sources": {+ "glb": "stm32g431cbt6-etched.glb",+ "glb_sha256": "15882e753c30077b50c64ba76dc9638709f072ba47f0ef4ce94385cfb8f2cca9",+ "footprint": "stm32g431cbt6.kicad_mod",+ "footprint_sha256": "1ccc5ad4d95398a4c6eb4b4cd1c429865368d06b8373613c6fcb332b53106ee0",+ "pins": "page.json component.pins"+ },+ "generator": "factory/hero_glb.py",+ "labels": [+ {+ "pad": "1",+ "text": "VBAT",+ "x": -5.0,+ "y": -2.75,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "2",+ "text": "PC13",+ "x": -5.0,+ "y": -2.25,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "3",+ "text": "PC14-OSC32_IN",+ "x": -5.0,+ "y": -1.75,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "4",+ "text": "PC15-OSC32_OUT",+ "x": -5.0,+ "y": -1.25,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "5",+ "text": "PF0-OSC_IN",+ "x": -5.0,+ "y": -0.75,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "6",+ "text": "PF1-OSC_OUT",+ "x": -5.0,+ "y": -0.25,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "7",+ "text": "PG10-NRST",+ "x": -5.0,+ "y": 0.25,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "8",+ "text": "PA0",+ "x": -5.0,+ "y": 0.75,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "9",+ "text": "PA1",+ "x": -5.0,+ "y": 1.25,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "10",+ "text": "PA2",+ "x": -5.0,+ "y": 1.75,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "11",+ "text": "PA3",+ "x": -5.0,+ "y": 2.25,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "12",+ "text": "PA4",+ "x": -5.0,+ "y": 2.75,+ "align": "right",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "13",+ "text": "PA5",+ "x": -2.75,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "14",+ "text": "PA6",+ "x": -2.25,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "15",+ "text": "PA7",+ "x": -1.75,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "16",+ "text": "PB0",+ "x": -1.25,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "17",+ "text": "PB1",+ "x": -0.75,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "18",+ "text": "PB2",+ "x": -0.25,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "19",+ "text": "VSSA",+ "x": 0.25,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "20",+ "text": "VREF+",+ "x": 0.75,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "21",+ "text": "VDDA",+ "x": 1.25,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "22",+ "text": "PB10",+ "x": 1.75,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "23",+ "text": "VSS",+ "x": 2.25,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "24",+ "text": "VDD",+ "x": 2.75,+ "y": 5.0,+ "align": "right",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "25",+ "text": "PB11",+ "x": 5.0,+ "y": 2.75,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "26",+ "text": "PB12",+ "x": 5.0,+ "y": 2.25,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "27",+ "text": "PB13",+ "x": 5.0,+ "y": 1.75,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "28",+ "text": "PB14",+ "x": 5.0,+ "y": 1.25,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "29",+ "text": "PB15",+ "x": 5.0,+ "y": 0.75,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "30",+ "text": "PA8",+ "x": 5.0,+ "y": 0.25,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "31",+ "text": "PA9",+ "x": 5.0,+ "y": -0.25,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "32",+ "text": "PA10",+ "x": 5.0,+ "y": -0.75,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "33",+ "text": "PA11",+ "x": 5.0,+ "y": -1.25,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "34",+ "text": "PA12",+ "x": 5.0,+ "y": -1.75,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "35",+ "text": "VSS",+ "x": 5.0,+ "y": -2.25,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "36",+ "text": "VDD",+ "x": 5.0,+ "y": -2.75,+ "align": "left",+ "vertical": false,+ "h": 0.25+ },+ {+ "pad": "37",+ "text": "PA13",+ "x": 2.75,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "38",+ "text": "PA14",+ "x": 2.25,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "39",+ "text": "PA15",+ "x": 1.75,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "40",+ "text": "PB3",+ "x": 1.25,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "41",+ "text": "PB4",+ "x": 0.75,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "42",+ "text": "PB5",+ "x": 0.25,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "43",+ "text": "PB6",+ "x": -0.25,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "44",+ "text": "PB7",+ "x": -0.75,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "45",+ "text": "PB8-BOOT0",+ "x": -1.25,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "46",+ "text": "PB9",+ "x": -1.75,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "47",+ "text": "VSS",+ "x": -2.25,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ },+ {+ "pad": "48",+ "text": "VDD",+ "x": -2.75,+ "y": -5.0,+ "align": "left",+ "vertical": true,+ "h": 0.25+ }+ ],+ "pads": [+ {+ "number": "1",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ -2.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "2",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ -2.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "3",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ -1.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "4",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ -1.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "5",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ -0.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "6",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ -0.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "7",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ 0.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "8",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ 0.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "9",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ 1.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "10",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ 1.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "11",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ 2.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "12",+ "shape": "roundrect",+ "center_mm": [+ -4.1625,+ 2.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "13",+ "shape": "roundrect",+ "center_mm": [+ -2.75,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "14",+ "shape": "roundrect",+ "center_mm": [+ -2.25,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "15",+ "shape": "roundrect",+ "center_mm": [+ -1.75,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "16",+ "shape": "roundrect",+ "center_mm": [+ -1.25,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "17",+ "shape": "roundrect",+ "center_mm": [+ -0.75,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "18",+ "shape": "roundrect",+ "center_mm": [+ -0.25,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "19",+ "shape": "roundrect",+ "center_mm": [+ 0.25,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "20",+ "shape": "roundrect",+ "center_mm": [+ 0.75,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "21",+ "shape": "roundrect",+ "center_mm": [+ 1.25,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "22",+ "shape": "roundrect",+ "center_mm": [+ 1.75,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "23",+ "shape": "roundrect",+ "center_mm": [+ 2.25,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "24",+ "shape": "roundrect",+ "center_mm": [+ 2.75,+ 4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "25",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ 2.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "26",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ 2.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "27",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ 1.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "28",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ 1.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "29",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ 0.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "30",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ 0.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "31",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ -0.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "32",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ -0.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "33",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ -1.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "34",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ -1.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "35",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ -2.25+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "36",+ "shape": "roundrect",+ "center_mm": [+ 4.1625,+ -2.75+ ],+ "size_mm": [+ 1.475,+ 0.3+ ],+ "rot": 0.0+ },+ {+ "number": "37",+ "shape": "roundrect",+ "center_mm": [+ 2.75,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "38",+ "shape": "roundrect",+ "center_mm": [+ 2.25,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "39",+ "shape": "roundrect",+ "center_mm": [+ 1.75,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "40",+ "shape": "roundrect",+ "center_mm": [+ 1.25,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "41",+ "shape": "roundrect",+ "center_mm": [+ 0.75,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "42",+ "shape": "roundrect",+ "center_mm": [+ 0.25,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "43",+ "shape": "roundrect",+ "center_mm": [+ -0.25,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "44",+ "shape": "roundrect",+ "center_mm": [+ -0.75,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "45",+ "shape": "roundrect",+ "center_mm": [+ -1.25,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "46",+ "shape": "roundrect",+ "center_mm": [+ -1.75,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "47",+ "shape": "roundrect",+ "center_mm": [+ -2.25,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ },+ {+ "number": "48",+ "shape": "roundrect",+ "center_mm": [+ -2.75,+ -4.1625+ ],+ "size_mm": [+ 0.3,+ 1.475+ ],+ "rot": 0.0+ }+ ]+}