← Commit history
LICENSEadded+15
@@ -0,0 +1,15 @@+Adom-authored files on this page (symbol, footprint, 3D models built from the datasheet, GLB/LOD+derivatives, Fusion and Altium libraries, viewers, scripts in provenance/tools, README, JSON records+and rendered images) are licensed under the Creative Commons Attribution 4.0 International License+(CC BY 4.0): https://creativecommons.org/licenses/by/4.0/++Copyright 2026 Adom Inc.++Third-party material keeps its own terms and is not relicensed by this file:++- docs/cx3225ga08000d0ptvtt-datasheet.pdf and the page crops in docs/figures/ are Kyocera Corporation's+  catalog (CX3225GA, As of October 2025), reproduced for reference and citation. Source:+  https://ele.kyocera.com/assets/products/crystal-device/cx3225ga_e.pdf+- The KiCad schematic symbol is derived from KiCad's Device:Crystal symbol (kicad-symbols, CC-BY-SA 4.0+  with the KiCad library exception: https://gitlab.com/kicad/libraries/kicad-symbols/-/blob/master/LICENSE.md).+  The graphics and pin layout are KiCad's; Adom changed the name, properties and pin text format.
README.md+181−2
@@ -1,3 +1,182 @@-# CX3225GA08000D0PTVTT - 8 MHz Automotive Crystal, 3.2 x 2.5 mm+# CX3225GA08000D0PTVTT -Kyocera CX3225GA08000D0PTVTT: 8 MHz fundamental quartz crystal, 8 pF load, +-50 ppm, -40 to +150 C, AEC-Q200, 3.2 x 2.5 x 0.85 mm ceramic SMD with 2 pads. KiCad symbol and footprint from Kyocera's land pattern, datasheet-built 3D model, Fusion and Altium libraries.+**KYOCERA AVX** · 8 MHz quartz crystal for automotive use · 3.2 x 2.5 x 0.85 mm ceramic SMD, **2 pads**++CX3225GA08000D0PTVTT is an 8.000 MHz fundamental-mode quartz crystal from Kyocera's CX3225GA series: 8 pF load capacitance, +-50 ppm at 25 C, +-150 ppm over -40 to +150 C, 500 ohm maximum ESR, AEC-Q200 qualified, in a 3.2 x 2.5 mm ceramic package with two bottom electrodes (Kyocera CX3225GA catalog, As of October 2025, p.1).++::adom-verified{src="/adom/cx3225ga08000d0ptvtt/render/verification.json"}++## Two pads, not four++Most 3225 crystals (Kyocera CX3225SB, Epson TSX-3225, the KiCad `Crystal_SMD_3225-4Pin` footprint) have four corner pads, two of them ground. **CX3225GA is different: it has two large electrodes and no ground or case pad.** Kyocera's product page calls it a "2 Pads Crystal Unit" and the catalog's bottom view and land pattern show two electrodes.++Do not put it on a 4-pad 3225 footprint: on KiCad's 4-pin pattern each electrode covers one crystal pad and one GND pad, so the part would short both crystal terminals to ground. Use the 2-pad footprint on this page, which follows Kyocera's recommended land pattern.++## Key specifications++| Parameter | Value | Source |+|---|---|---|+| Nominal frequency | 8.000 MHz (code `08000`) | How to Order |+| Mode | Fundamental | Specifications |+| Load capacitance CL | 8.0 pF (code `D0`) | How to Order, Specifications |+| Frequency tolerance | +-50 ppm at 25 C +-3 C (code `P`) | How to Order, Specifications |+| Frequency vs temperature | +-150 ppm over -40 to +150 C, referred to 25 C (codes `T`, `V`) | How to Order, Specifications |+| ESR (motional series resistance) | 500 ohm max (8 <= F < 9.8 MHz) | Table 1 |+| Drive level | 10 uW typical, 200 uW max | Specifications |+| Shunt capacitance C0 | 3.0 pF max | Specifications |+| Operating and storage temperature | -40 to +150 C | Specifications |+| Qualification | AEC-Q200, PSL R4Y, MSL 1, RoHS compliant | Catalog header |+| Package | 3.2 +-0.1 x 2.5 +-0.1 x 0.85 +-0.1 mm, ceramic base with metal lid, 2 electrodes | Dimensions |+| Packaging | Tape and reel, 3000 pcs per reel | How to Order |+| Individual specification | `TT` (Kyocera's standard specification is `CC`; `TT` is an individual specification) | How to Order |++All values: Kyocera, Crystal Units Surface Mount Type CX3225GA, catalog page (As of October 2025), [PDF](docs/cx3225ga08000d0ptvtt-datasheet.pdf). The catalog also notes that frequencies from 12 MHz up are standard and asks buyers to contact Kyocera for 8 to 10 MHz; this 8 MHz part number is listed by Mouser and DigiKey.++**Design note.** With CL = 8 pF, the two load capacitors are about 2 x (8 pF minus the board's stray capacitance), typically 10 to 12 pF each for 2 to 3 pF of stray. At 500 ohm maximum ESR, check the oscillator's gain margin (negative resistance at least 3 to 5 times ESR, so about 1.5 to 2.5 kohm) against your MCU's crystal driver, and keep the drive level under 200 uW. These are general Pierce-oscillator rules, not Kyocera figures.++![Kyocera CX3225GA How to Order](docs/figures/cx3225ga08000d0ptvtt-ds-p1-ordering.png)++![Kyocera CX3225GA Specifications and ESR table](docs/figures/cx3225ga08000d0ptvtt-ds-p1-specifications.png)++## Pinout++| Pad | Name | Type | Function |+|---|---|---|---|+| 1 | 1 | passive | Crystal electrode: the one Kyocera draws with an R0.3 inner corner (left in the bottom view) |+| 2 | 2 | passive | Crystal electrode, the other side of the quartz blank |++There is no ground, case or no-connect pad. Kyocera numbers neither electrode and the crystal is not polarised: either pad may go to XIN or XOUT. Adom calls the electrode with the R0.3 corner pad 1 so the symbol, footprint and 3D model agree, but turning the part 180 degrees changes nothing electrically.++![Kyocera CX3225GA Dimensions and Recommended Land Pattern](docs/figures/cx3225ga08000d0ptvtt-ds-p1-dimensions-land-pattern.png)++## Ordering variants++Every CX3225GA part number uses the same package and land pattern, so the symbol, footprint and 3D model on this page fit any of them. Only the electrical rating changes. The part number is built as `CX3225GA` + frequency in kHz (5 digits) + load capacitance (2 characters) + tolerance + temperature range + temperature stability + individual specification:++| Field | This part | Other choices in the catalog |+|---|---|---|+| Frequency | `08000` = 8.000 MHz | 8,000 to 54,000 kHz |+| Load capacitance | `D0` = 8.0 pF | D to H with a decimal digit: 8.0 to 12.9 pF (for example `D1` = 8.1 pF) |+| Frequency tolerance | `P` = +-50 ppm | G +-15, H +-20, J +-25, K +-30, M +-40, P +-50 ppm |+| Operating temperature | `T` = -40 to +150 C | L -30 to 85, P -40 to 85, R -40 to 105, S -40 to 125, T -40 to 150 C |+| Temperature stability | `V` = +-150 ppm | Q +-30 to W +-200 ppm (the wider ranges allow only the looser grades) |+| Specification | `TT` (individual) | `CC` standard |++ESR depends on frequency (Table 1): 500 ohm for 8 to 9.8 MHz, 200 ohm for 9.8 to 14 MHz, 120 ohm for 14 to 16 MHz, 100 ohm for 16 to 54 MHz. This page's specifications, distributor numbers and virtual marking are for CX3225GA08000D0PTVTT only. Adom holds no reel of it.++## 3D models++::adom-models{src="/adom/cx3225ga08000d0ptvtt/render/models.json"}++| File | What it is |+|---|---|+| [cx3225ga08000d0ptvtt-etched.step](cx3225ga08000d0ptvtt-etched.step) | **The library model** for board CAD (KiCad, Fusion, Altium), with a virtual MPN mark in the `Adom.LaserEtch` group. The KiCad footprint binds to it. |+| [cx3225ga08000d0ptvtt.step](cx3225ga08000d0ptvtt.step) | The same package, unmarked. |+| [cx3225ga08000d0ptvtt-etched.glb](cx3225ga08000d0ptvtt-etched.glb) | The marked model as GLB, clean for boards and digital twins (no pad outlines, labels or silkscreen), with gold electrodes and ceramic materials. |+| [cx3225ga08000d0ptvtt-hero.glb](cx3225ga08000d0ptvtt-hero.glb) | The annotated page hero (adom-hero): the marked part plus teal dashed pad outlines, silkscreen and pad labels 1 and 2. Display only, never for a board. |+| [lod/](lod/manifest.json) | Board-use detail levels (below). |++Frame for every file: Z up, seated on z = 0, centred on the KiCad footprint origin, pad 1 electrode over footprint pad 1, binds with zero offset and rotation.++**Where the model comes from.** Kyocera publishes no STEP or EDA model for CX3225GA (product page and download page checked 2026-10-21). KiCad's only 3225 crystal model, `Crystal_SMD_3225-4Pin_3.2x2.5mm.step`, does not fit: it has four corner terminals and is 0.64 mm tall, against Kyocera's two electrodes and 0.85 +-0.1 mm. KiCad's package generator has no recipe for this package. So the model was **built by an AI (Claude) with CadQuery from Kyocera's drawing** ([provenance/tools/build_cx3225ga.py](provenance/tools/build_cx3225ga.py)), with colours taken from Kyocera's product photo (black lid, dark ceramic, gold castellations).++| Dimension (mm) | Kyocera | KiCad 3225-4Pin model | This model |+|---|---|---|---|+| Body length | 3.2 +-0.1 | 3.200 | 3.200 |+| Body width | 2.5 +-0.1 | 2.501 | 2.500 |+| Overall height | 0.85 +-0.1 | **0.640** (below the 0.75 minimum) | 0.850 |+| Base plus seal ring | 0.53 +-0.1 | not modelled | 0.530 |+| Terminals | 2 electrodes | **4 corner pads** | 2 electrodes |+| Electrode gap | 0.6 +-0.1 | n/a | 0.600 |+| Pad 1 electrode inner corner | R0.3 | n/a | 0.300 |+| Corner castellations | 4-R0.15 | n/a | 0.150 |+| End castellation width x height | 0.5 +-0.1 x 0.25 | n/a | 0.500 x 0.250 |+| Electrode height | not printed | n/a | 2.28 (measured) |+| Electrode outer edge from centre | not printed (arc) | n/a | 1.48 at the middle, 1.24 at the ends (measured) |+| Lid inset, castellation depth | not printed | n/a | 0.10, 0.10 (measured) |++The unprinted features were measured on a 600 dpi render of the catalog. That drawing is not drawn to one scale (3.2 mm is 157 px, but 2.5 mm is 129 to 145 px depending on the view), so each view is calibrated on its own printed body edges.++### Model proof: overlay on Kyocera's drawing++Red outline and blue fill: the model, projected at the drawing's scale. Black: Kyocera's top, front, end and bottom views. The bottom view shows only the electrodes, including the R0.3 corner that identifies pad 1.++![Model overlaid on Kyocera's CX3225GA drawing](docs/proof/cx3225ga08000d0ptvtt-overlay-kyocera-catalog.png)++### Top marking (virtual)++Kyocera's catalog does not document this part's top marking. The product photo shows a two-line code (`19707` / `K121F` on the sample pictured) whose meaning is not published. The models therefore carry a **virtual** identification mark, `CX3225GA` / `08000D0PTVTT`, on the lid, added by `adom-aiflow-etch` (single-stroke font, 0.24 mm text, legibility gate PASS). It is an identification aid in CAD, not a claim about what is printed on the real part.++![Etch check](docs/proof/cx3225ga08000d0ptvtt-etched.check.png)++## Detail levels for board and digital-twin viewers++::adom-component-lod{src="/adom/cx3225ga08000d0ptvtt/render/lod/manifest.json"}++| Level | File | Triangles | Draw calls | Size | Marking |+|---|---|---|---|---|---|+| high | [cx3225ga08000d0ptvtt-high.glb](lod/cx3225ga08000d0ptvtt-high.glb) | 1229 | 6 | 45 KiB | yes |+| medium | [cx3225ga08000d0ptvtt-medium.glb](lod/cx3225ga08000d0ptvtt-medium.glb) | 1229 | 6 | 34 KiB | yes |+| low | [cx3225ga08000d0ptvtt-low.glb](lod/cx3225ga08000d0ptvtt-low.glb) | 386 | 5 | 15 KiB | no |+| super-low | [cx3225ga08000d0ptvtt-super-low.glb](lod/cx3225ga08000d0ptvtt-super-low.glb) | 168 | 5 | 10 KiB | no |++All four levels share the library model's Z-up frame, units and seating, carry no footprint or silkscreen artwork, and keep both electrodes. High is the marked model batched per part (geometry unchanged); medium is the same, quantized; low is a simplified unmarked model; super-low comes from a simplified build without castellations. Status: candidate. The cached previews were inspected, but no streaming LOD viewer has qualified them yet.++## CAD files++| Tool | Files |+|---|---|+| KiCad | [cx3225ga08000d0ptvtt.kicad_sym](cx3225ga08000d0ptvtt.kicad_sym) (KiCad's Device:Crystal drawing, 2 pins), [cx3225ga08000d0ptvtt.kicad_mod](cx3225ga08000d0ptvtt.kicad_mod), [cx3225ga08000d0ptvtt-etched.step](cx3225ga08000d0ptvtt-etched.step) |+| Fusion / EAGLE | [cx3225ga08000d0ptvtt.lbr](cx3225ga08000d0ptvtt.lbr) (adom-lbr; symbol, package and pin-to-pad connects, with MPN, manufacturer, Mouser and DigiKey attributes) |+| Altium | [altium/CX3225GA08000D0PTVTT.SchLib](altium/CX3225GA08000D0PTVTT.SchLib) and [altium/CX3225GA08000D0PTVTT.PcbLib](altium/CX3225GA08000D0PTVTT.PcbLib) (adom-lbr; the PcbLib carries the 3D body), hub JSON [altium/CX3225GA08000D0PTVTT.adom-lbr.json](altium/CX3225GA08000D0PTVTT.adom-lbr.json) |+| Any | STEP and GLB above |++The symbol carries MPN, Manufacturer, Datasheet, Description, Mouser, DigiKey, LCSC (empty: not stocked) and Adom Wiki properties, so a KiCad BOM export has the ordering data. It keeps KiCad's standard two-pin crystal drawing because a crystal symbol has to read as a crystal.++**Footprint.** Two plain rectangular pads, 1.6 x 2.6 mm, 0.4 mm apart (centres at +-1.0 mm), exactly as Kyocera's recommended land pattern; courtyard 0.25 mm around the pads; silkscreen 0.15 mm clear of copper, with a bar on the pad 1 side. The pad-by-pad comparison is in [provenance/footprint.json](provenance/footprint.json).++## Sourcing++| Distributor | Part number | Stock (2026-10-21) | Link |+|---|---|---|---|+| Mouser | 581-CX3225GA0D0PTVTT | 5,985; 1 pc $0.93, 100 pcs $0.688, 3000 pcs $0.565; lead time 182 days | [Mouser](https://www.mouser.com/en/ProductDetail/KYOCERA-AVX/CX3225GA08000D0PTVTT?qs=QNEnbhJQKvZMiT3iOkPefA%3D%3D) |+| DigiKey | 478-CX3225GA08000D0PTVTTTR-ND | 0 (tape and reel) | [DigiKey](https://www.digikey.com/en/products/detail/kyocera-avx/CX3225GA08000D0PTVTT/22241851) |+| LCSC / JLCPCB | none | not listed (checked 2026-10-21) | |++Prices and stock are a dated snapshot from `adom-parts-search`; check the distributor before ordering.++## Manufacturer and resources++| Resource | Link |+|---|---|+| Product page | [CX3225GA, ele.kyocera.com](https://ele.kyocera.com/en/product/crystal-device/crystal_units/cx3225ga) |+| Catalog / datasheet | [cx3225ga_e.pdf](https://ele.kyocera.com/assets/products/crystal-device/cx3225ga_e.pdf) (As of October 2025, 1 page; [copy on this page](docs/cx3225ga08000d0ptvtt-datasheet.pdf)) |+| RoHS | [RoHS_CX3225GA.pdf](https://ele.kyocera.com/assets/products/crystal-device/environment/RoHS_CX3225GA.pdf) |+| REACH | [REACH_CX3225GA.pdf](https://ele.kyocera.com/assets/products/crystal-device/environment/REACH_CX3225GA.pdf) |+| Halogen-free | [Halogen-Free_CX3225GA.pdf](https://ele.kyocera.com/assets/products/crystal-device/environment/Halogen-Free_CX3225GA.pdf) |+| TSCA | [TSCA_CX3225GA.pdf](https://ele.kyocera.com/assets/products/crystal-device/environment/TSCA_CX3225GA.pdf) |+| All crystal downloads | [ele.kyocera.com support downloads](https://ele.kyocera.com/en/support/download/crystal-device/) |+| 3D / EDA model | none published by Kyocera (checked 2026-10-21) |++## Related++- Kyocera CX3225SB crystals (a different series: **4 pads**, 2 of them ground, -30 to +85 C): [CX3225SB12000H0FLJCC](https://wiki.adom.inc/adom/cx3225sb12000h0fljcc), [CX3225SB19200D0FPLCC](https://wiki.adom.inc/adom/cx3225sb19200d0fplcc), [CX3225SB25000D0FFFCC](https://wiki.adom.inc/adom/cx3225sb25000d0fffcc). These parts do not share a footprint with CX3225GA.++## Provenance++| Data | Source | Evidence |+|---|---|---|+| Specifications, ordering code | Kyocera CX3225GA catalog p.1, How to Order, Specifications, Table 1 | [figure](docs/figures/cx3225ga08000d0ptvtt-ds-p1-ordering.png), [figure](docs/figures/cx3225ga08000d0ptvtt-ds-p1-specifications.png) |+| Pinout (2 electrodes, pad 1 = R0.3 corner) | Catalog p.1, Dimensions (third-angle bottom view) | [figure](docs/figures/cx3225ga08000d0ptvtt-ds-p1-dimensions-land-pattern.png) |+| Footprint | Catalog p.1, Recommended Land Pattern | [provenance/footprint.json](provenance/footprint.json) |+| 3D model | Catalog p.1, Dimensions; Kyocera product photo for colours | [overlay](docs/proof/cx3225ga08000d0ptvtt-overlay-kyocera-catalog.png), [provenance/3d-model.json](provenance/3d-model.json) |+| Distributor numbers, price, stock | Mouser and DigiKey via adom-parts-search, 2026-10-21 | page.json `component.distributor_links` |+| Verification | Checks and file hashes | [verification.json](verification.json) |++- **Datasheet file:** [docs/cx3225ga08000d0ptvtt-datasheet.pdf](docs/cx3225ga08000d0ptvtt-datasheet.pdf), sha256 `6d81365428115f7f83ea500b2b37cee263daa69fdcc0844525e517ac7313ed46`, downloaded 2026-10-21 from ele.kyocera.com (PDF created 2025-09-26, "As of October 2025").+- **Tools:** CadQuery 2.8.0 (model), adom-aiflow-etch (virtual mark), step2glb 0.13.2 (GLB), glTF Transform 4.1.0 and meshoptimizer 0.22.0 (batching, LODs), component-hero hero_glb.py (hero), adom-footprint (footprint), adom-symbol (embedded viewers), adom-lbr (Fusion and Altium). Scripts are in [provenance/tools/](provenance/tools/).+- **Who:** built and checked by Claude (AI). No engineer has reviewed this page yet; the open items are listed in the verification log.++## License++Adom-authored files: [CC BY 4.0](LICENSE). The Kyocera catalog and its page crops remain Kyocera's. The symbol drawing comes from KiCad's Device:Crystal (CC-BY-SA 4.0 with the KiCad library exception).
docs/models/cx3225ga08000d0ptvtt-etched-glb-top.pngadded
⋯ 1 unchanged line ⋯
docs/models/cx3225ga08000d0ptvtt-etched-step-bottom.pngadded
⋯ 1 unchanged line ⋯
docs/models/cx3225ga08000d0ptvtt-etched-step-top.pngadded
⋯ 1 unchanged line ⋯
docs/models/cx3225ga08000d0ptvtt-glb-top.pngadded
⋯ 1 unchanged line ⋯
docs/models/cx3225ga08000d0ptvtt-hero-glb-top.pngadded
⋯ 1 unchanged line ⋯
docs/models/cx3225ga08000d0ptvtt-high-glb-top.pngadded
⋯ 1 unchanged line ⋯
docs/models/cx3225ga08000d0ptvtt-low-glb-top.pngadded
⋯ 1 unchanged line ⋯
docs/models/cx3225ga08000d0ptvtt-medium-glb-top.pngadded
⋯ 1 unchanged line ⋯
docs/models/cx3225ga08000d0ptvtt-step-bottom.pngadded
⋯ 1 unchanged line ⋯
docs/models/cx3225ga08000d0ptvtt-step-top.pngadded
⋯ 1 unchanged line ⋯
docs/models/cx3225ga08000d0ptvtt-super-low-glb-top.pngadded
⋯ 1 unchanged line ⋯
lod/cx3225ga08000d0ptvtt-high.glbadded
⋯ 1 unchanged line ⋯
lod/cx3225ga08000d0ptvtt-high.pngadded
⋯ 1 unchanged line ⋯
lod/cx3225ga08000d0ptvtt-low.glbadded
⋯ 1 unchanged line ⋯
lod/cx3225ga08000d0ptvtt-low.pngadded
⋯ 1 unchanged line ⋯
lod/cx3225ga08000d0ptvtt-medium.glbadded
⋯ 1 unchanged line ⋯
lod/cx3225ga08000d0ptvtt-medium.pngadded
⋯ 1 unchanged line ⋯
lod/cx3225ga08000d0ptvtt-super-low.glbadded
⋯ 1 unchanged line ⋯
lod/cx3225ga08000d0ptvtt-super-low.pngadded
⋯ 1 unchanged line ⋯
lod/manifest.jsonadded+163
@@ -0,0 +1,163 @@+{+ "schema": "adom/component-lod@1",+ "mpn": "CX3225GA08000D0PTVTT",+ "units": "metres (glTF)",+ "upAxis": "Z (Adom/KiCad frame; seated on z = 0, centred on the footprint origin)",+ "status": "candidate",+ "source": {+  "path": "cx3225ga08000d0ptvtt-etched.step",+  "sha256": "0aa4e2207762fd76f84df09c5ed2369a303a050593d20aab91ac4fea2704b0f5"+ },+ "generator": "provenance/tools/build_cx3225ga.py (CadQuery 2.8.0) -> adom-aiflow-etch -> step2glb convert --compress none -> glTF Transform 4.1.0 (dedup, join per named part, prune; weld + meshopt simplify on low and super-low; KHR_mesh_quantization on medium, low, super-low) via provenance/tools/lod.mjs -> provenance/tools/materials.py; previews by provenance/tools/render.py",+ "levels": [+  {+   "level": "high",+   "path": "lod/cx3225ga08000d0ptvtt-high.glb",+   "sha256": "a8893c37ea04e1a3c065bf5ec0f3baef6f7a1254ebf0b3ab3412c6dfd9a587cd",+   "sizeBytes": 46512,+   "decodedBufferBytes": 40806,+   "triangles": 1229,+   "primitives": 6,+   "boundsMm": [+    -1.6,+    -1.25,+    0.0,+    1.6,+    1.25,+    0.855+   ],+   "markingIncluded": true,+   "mark": {+    "mark_z_min_mm": 0.855,+    "mark_z_max_mm": 0.855,+    "chip_top_mm": 0.85,+    "lift_um": 5.0+   },+   "helperNodes": [],+   "extensions": [],+   "derivation": "etched STEP, uncompressed; primitives batched per part (lossless): ceramic base, seal ring, lid, both gold electrodes with their end castellations, and the virtual MPN mark",+   "review": "candidate: inspected as cached images (depth-buffered render); not qualified in a streaming LOD viewer",+   "preview": {+    "path": "lod/cx3225ga08000d0ptvtt-high.png",+    "sha256": "e50001546e4c4955c96fef0064c83b09a2ddf55322836ccd920552322f7f3461",+    "sizeBytes": 24105,+    "width": 400,+    "height": 400,+    "view": "iso (front-left, from above), same camera on every level",+    "sourceLevelSha256": "a8893c37ea04e1a3c065bf5ec0f3baef6f7a1254ebf0b3ab3412c6dfd9a587cd"+   }+  },+  {+   "level": "medium",+   "path": "lod/cx3225ga08000d0ptvtt-medium.glb",+   "sha256": "da7025446dd8a31327fd73672b38910e2cdba04ef3efd517e0c67c1ca7e6d4b9",+   "sizeBytes": 34488,+   "decodedBufferBytes": 23226,+   "triangles": 1229,+   "primitives": 6,+   "boundsMm": [+    -1.6,+    -1.25,+    0.0,+    1.6,+    1.25,+    0.855+   ],+   "markingIncluded": true,+   "mark": {+    "mark_z_min_mm": 0.855,+    "mark_z_max_mm": 0.855,+    "chip_top_mm": 0.85,+    "lift_um": 4.96+   },+   "helperNodes": [],+   "extensions": [+    "KHR_mesh_quantization"+   ],+   "derivation": "the high level, quantized (KHR_mesh_quantization); geometry otherwise unchanged, virtual mark kept",+   "review": "candidate: inspected as cached images (depth-buffered render); not qualified in a streaming LOD viewer",+   "preview": {+    "path": "lod/cx3225ga08000d0ptvtt-medium.png",+    "sha256": "2ef9c31d31e27e57648ebb2ed99d232ca81111f47212c6e1a613fdab06a8ba6e",+    "sizeBytes": 23084,+    "width": 400,+    "height": 400,+    "view": "iso (front-left, from above), same camera on every level",+    "sourceLevelSha256": "da7025446dd8a31327fd73672b38910e2cdba04ef3efd517e0c67c1ca7e6d4b9"+   }+  },+  {+   "level": "low",+   "path": "lod/cx3225ga08000d0ptvtt-low.glb",+   "sha256": "e71db4e8909b9c6aa942ceb4496099cb470289fd8d8817f9617d160f5342d35a",+   "sizeBytes": 15612,+   "decodedBufferBytes": 8556,+   "triangles": 386,+   "primitives": 5,+   "boundsMm": [+    -1.6,+    -1.25,+    0.0,+    1.6,+    1.25,+    0.85+   ],+   "markingIncluded": false,+   "mark": null,+   "helperNodes": [],+   "extensions": [+    "KHR_mesh_quantization"+   ],+   "derivation": "unmarked STEP, batched, welded and simplified (meshopt, ratio 0.5, error 0.2 % of the part), quantized; the virtual mark is omitted (unreadable at this size); both electrodes, castellations and the pad-1 R0.3 corner kept",+   "review": "candidate: inspected as cached images (depth-buffered render); not qualified in a streaming LOD viewer",+   "preview": {+    "path": "lod/cx3225ga08000d0ptvtt-low.png",+    "sha256": "28e1cc63e0ec319007b04dec5dd51d111ecf1c6fad434a6a1e71adb3c8221744",+    "sizeBytes": 10090,+    "width": 400,+    "height": 400,+    "view": "iso (front-left, from above), same camera on every level",+    "sourceLevelSha256": "e71db4e8909b9c6aa942ceb4496099cb470289fd8d8817f9617d160f5342d35a"+   }+  },+  {+   "level": "super-low",+   "path": "lod/cx3225ga08000d0ptvtt-super-low.glb",+   "sha256": "ea0326889bed080f3a98ac93f3c538c683cbab4f5f2ceb75342ba9d556ac8742",+   "sizeBytes": 9932,+   "decodedBufferBytes": 3984,+   "triangles": 168,+   "primitives": 5,+   "boundsMm": [+    -1.6,+    -1.25,+    -0.0,+    1.6,+    1.25,+    0.85+   ],+   "markingIncluded": false,+   "mark": null,+   "helperNodes": [],+   "extensions": [+    "KHR_mesh_quantization"+   ],+   "derivation": "the builder's --simple variant (no castellations, polygonal electrode outline, no R0.3 corner), simplified (ratio 0.5, error 0.4 %), quantized; both electrodes kept, marking omitted",+   "review": "candidate: inspected as cached images (depth-buffered render); not qualified in a streaming LOD viewer",+   "preview": {+    "path": "lod/cx3225ga08000d0ptvtt-super-low.png",+    "sha256": "2fed8bc2272f617972aa25ea0073ce4bea3d77d8e305d7815b23fc2d6f67e9ac",+    "sizeBytes": 8610,+    "width": 400,+    "height": 400,+    "view": "iso (front-left, from above), same camera on every level",+    "sourceLevelSha256": "ea0326889bed080f3a98ac93f3c538c683cbab4f5f2ceb75342ba9d556ac8742"+   }+  }+ ],+ "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 (cx3225ga08000d0ptvtt-hero.glb) and is never used on a board."+ ],+ "updated": "2026-10-21"+}
models.jsonadded+217
@@ -0,0 +1,217 @@+{+ "schema": "adom/models@1",+ "part": "CX3225GA08000D0PTVTT",+ "models": [+  {+   "path": "cx3225ga08000d0ptvtt-etched.glb",+   "format": "glb",+   "title": "CX3225GA08000D0PTVTT, virtual MPN mark (GLB, board use)",+   "status": "current",+   "slot": "model_3d_plain",+   "variant": "etched",+   "use": [+    "render"+   ],+   "unique": "Web and board-viewer copy of the marked STEP, clean (no pad outlines, labels or silkscreen), gold electrodes and ceramic materials",+   "marking": {+    "text": "CX3225GA / 08000D0PTVTT (virtual, Adom-added)",+    "surface": "top (lid), two lines along the 3.2 mm axis",+    "colour": "light grey on black lid"+   },+   "sha256": "a8893c37ea04e1a3c065bf5ec0f3baef6f7a1254ebf0b3ab3412c6dfd9a587cd",+   "git_blob": "04b094235d8c4a1a0625684acd12bd214456efd7",+   "bytes": 46512,+   "role": "derived",+   "colours": true,+   "derived_from": "cx3225ga08000d0ptvtt-etched.step",+   "source": "step2glb convert + glTF Transform 4.1 (batch per part) + provenance/tools/materials.py",+   "thumb": "docs/models/cx3225ga08000d0ptvtt-etched-glb-top.png"+  },+  {+   "path": "cx3225ga08000d0ptvtt-etched.step",+   "format": "step",+   "title": "CX3225GA08000D0PTVTT, virtual MPN mark (library model)",+   "status": "current",+   "slot": "model_3d_step",+   "variant": "etched",+   "use": [+    "board-cad"+   ],+   "unique": "The board-CAD model: 2-pad ceramic package built from Kyocera's drawing, with the virtual MPN in the Adom.LaserEtch group",+   "marking": {+    "text": "CX3225GA / 08000D0PTVTT (virtual, Adom-added)",+    "surface": "top (lid), two lines along the 3.2 mm axis",+    "colour": "light grey on black lid"+   },+   "sha256": "0aa4e2207762fd76f84df09c5ed2369a303a050593d20aab91ac4fea2704b0f5",+   "git_blob": "35503c2ee87ba8f4450fd2037f26b19fb8d83e9e",+   "bytes": 656119,+   "role": "datasheet-built",+   "colours": true,+   "derived_from": "cx3225ga08000d0ptvtt.step",+   "source": "Datasheet-built: provenance/tools/build_cx3225ga.py (CadQuery 2.8.0) from the Kyocera CX3225GA catalog drawing + adom-aiflow-etch",+   "thumb": "docs/models/cx3225ga08000d0ptvtt-etched-step-top.png",+   "thumb_bottom": "docs/models/cx3225ga08000d0ptvtt-etched-step-bottom.png"+  },+  {+   "path": "cx3225ga08000d0ptvtt-hero.glb",+   "format": "glb",+   "title": "CX3225GA08000D0PTVTT, annotated hero",+   "status": "current",+   "slot": "model_3d",+   "variant": "hero",+   "use": [+    "wiki-hero"+   ],+   "unique": "The marked part plus nonphysical reference art: teal dashed pad outlines, silkscreen, pad labels 1 and 2 (adom-hero); never for a board",+   "marking": {+    "text": "CX3225GA / 08000D0PTVTT (virtual, Adom-added)",+    "surface": "top (lid), two lines along the 3.2 mm axis",+    "colour": "light grey on black lid"+   },+   "sha256": "a4c097aecdba777eac663003068fb9c98b3869004a0201b63c119e59eff75ef3",+   "git_blob": "cf5ff9783761d39b5cd5ecd999bc51767a02f78e",+   "bytes": 60248,+   "role": "derived",+   "colours": true,+   "derived_from": "cx3225ga08000d0ptvtt-etched.glb",+   "source": "component-hero hero_glb.py",+   "thumb": "docs/models/cx3225ga08000d0ptvtt-hero-glb-top.png"+  },+  {+   "path": "cx3225ga08000d0ptvtt.glb",+   "format": "glb",+   "title": "CX3225GA08000D0PTVTT, unmarked (GLB)",+   "status": "current",+   "slot": null,+   "variant": "plain",+   "use": [+    "render"+   ],+   "unique": "Unmarked package as GLB",+   "marking": null,+   "sha256": "4410e12995e55bed00477c0f4ae4818b89437d507c5d5fcbb6586377db656c3c",+   "git_blob": "0be6262e2c650503a295f9dccff87a6a975e3e60",+   "bytes": 24428,+   "role": "derived",+   "colours": true,+   "derived_from": "cx3225ga08000d0ptvtt.step",+   "source": "step2glb convert + glTF Transform 4.1 + materials.py",+   "thumb": "docs/models/cx3225ga08000d0ptvtt-glb-top.png"+  },+  {+   "path": "cx3225ga08000d0ptvtt.step",+   "format": "step",+   "title": "CX3225GA08000D0PTVTT, unmarked",+   "status": "current",+   "slot": "model_3d_step_plain",+   "variant": "plain",+   "use": [+    "board-cad"+   ],+   "unique": "Same package without any top marking",+   "marking": null,+   "sha256": "b087c3960027505b285424149b9d178af47547fccbae53656f9fd73cb8e764e3",+   "git_blob": "e9f44bdafb40b94cec27d1a6b9f6b19be50679cf",+   "bytes": 226827,+   "role": "datasheet-built",+   "colours": true,+   "source": "Datasheet-built: provenance/tools/build_cx3225ga.py (CadQuery 2.8.0) from the Kyocera CX3225GA catalog drawing",+   "thumb": "docs/models/cx3225ga08000d0ptvtt-step-top.png",+   "thumb_bottom": "docs/models/cx3225ga08000d0ptvtt-step-bottom.png"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-high.glb",+   "format": "glb",+   "title": "Board LOD high",+   "status": "current",+   "slot": null,+   "variant": "lod",+   "use": [+    "render"+   ],+   "unique": "Board-use detail level high: no footprint art, virtual mark kept",+   "marking": {+    "text": "CX3225GA / 08000D0PTVTT (virtual, Adom-added)",+    "surface": "top (lid), two lines along the 3.2 mm axis",+    "colour": "light grey on black lid"+   },+   "sha256": "a8893c37ea04e1a3c065bf5ec0f3baef6f7a1254ebf0b3ab3412c6dfd9a587cd",+   "git_blob": "04b094235d8c4a1a0625684acd12bd214456efd7",+   "bytes": 46512,+   "role": "derived",+   "colours": true,+   "derived_from": "cx3225ga08000d0ptvtt-etched.glb",+   "source": "provenance/tools/lod.mjs",+   "thumb": "docs/models/cx3225ga08000d0ptvtt-high-glb-top.png"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-low.glb",+   "format": "glb",+   "title": "Board LOD low",+   "status": "current",+   "slot": null,+   "variant": "lod",+   "use": [+    "render"+   ],+   "unique": "Board-use detail level low: simplified, mark omitted (unreadable at this size)",+   "marking": null,+   "sha256": "e71db4e8909b9c6aa942ceb4496099cb470289fd8d8817f9617d160f5342d35a",+   "git_blob": "8993c155f0dc8edc63df2b53d86f38266d1daf94",+   "bytes": 15612,+   "role": "derived",+   "colours": true,+   "derived_from": "cx3225ga08000d0ptvtt.step",+   "source": "provenance/tools/lod.mjs",+   "thumb": "docs/models/cx3225ga08000d0ptvtt-low-glb-top.png"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-medium.glb",+   "format": "glb",+   "title": "Board LOD medium",+   "status": "current",+   "slot": null,+   "variant": "lod",+   "use": [+    "render"+   ],+   "unique": "Board-use detail level medium: no footprint art, virtual mark kept",+   "marking": {+    "text": "CX3225GA / 08000D0PTVTT (virtual, Adom-added)",+    "surface": "top (lid), two lines along the 3.2 mm axis",+    "colour": "light grey on black lid"+   },+   "sha256": "da7025446dd8a31327fd73672b38910e2cdba04ef3efd517e0c67c1ca7e6d4b9",+   "git_blob": "fc7d0df6d22a0267be6733ef2b9ff19b28a8c584",+   "bytes": 34488,+   "role": "derived",+   "colours": true,+   "derived_from": "cx3225ga08000d0ptvtt-etched.glb",+   "source": "provenance/tools/lod.mjs",+   "thumb": "docs/models/cx3225ga08000d0ptvtt-medium-glb-top.png"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-super-low.glb",+   "format": "glb",+   "title": "Board LOD super-low",+   "status": "current",+   "slot": null,+   "variant": "lod",+   "use": [+    "render"+   ],+   "unique": "Board-use detail level super-low: simplified, mark omitted (unreadable at this size)",+   "marking": null,+   "sha256": "ea0326889bed080f3a98ac93f3c538c683cbab4f5f2ceb75342ba9d556ac8742",+   "git_blob": "59d94f9edbd10c0421f59496312b4510219a3838",+   "bytes": 9932,+   "role": "derived",+   "colours": true,+   "derived_from": "cx3225ga08000d0ptvtt.step",+   "source": "provenance/tools/lod.mjs",+   "thumb": "docs/models/cx3225ga08000d0ptvtt-super-low-glb-top.png"+  }+ ],+ "updated": "2026-10-21"+}
provenance/3d-model.jsonadded+172
@@ -0,0 +1,172 @@+{+ "schema": "adom/3d-model-provenance@1",+ "mpn": "CX3225GA08000D0PTVTT",+ "date": "2026-10-21",+ "classification": "Datasheet-built: modelled by Claude (AI) with CadQuery from Kyocera's catalog drawing; not manufacturer CAD and not a manufacturer certification",+ "manufacturer_cad": {+  "status": "not available",+  "checked": "2026-10-21",+  "note": "Kyocera's CX3225GA product page (https://ele.kyocera.com/en/product/crystal-device/crystal_units/cx3225ga) and crystal download page list no STEP or EDA model for this series; the distributor (Mouser, DigiKey) product listings were not reachable for CAD from this environment."+ },+ "datasheet": {+  "doc": "Kyocera, Crystal Units Surface Mount Type CX3225GA, catalog page (As of October 2025)",+  "file": "docs/cx3225ga08000d0ptvtt-datasheet.pdf",+  "url": "https://ele.kyocera.com/assets/products/crystal-device/cx3225ga_e.pdf",+  "pages": 1,+  "sha256": "6d81365428115f7f83ea500b2b37cee263daa69fdcc0844525e517ac7313ed46",+  "pdf_dates": "created 2025-09-26, modified 2025-10-01; server Last-Modified 2026-10-08",+  "figures": [+   "docs/figures/cx3225ga08000d0ptvtt-ds-p1-dimensions-land-pattern.png",+   "docs/figures/cx3225ga08000d0ptvtt-ds-p1-ordering.png",+   "docs/figures/cx3225ga08000d0ptvtt-ds-p1-specifications.png",+   "docs/figures/cx3225ga08000d0ptvtt-ds-p1.png"+  ]+ },+ "kicad_library_model": {+  "file": "Crystal.3dshapes/Crystal_SMD_3225-4Pin_3.2x2.5mm.step",+  "source": "https://gitlab.com/kicad/libraries/kicad-packages3D/-/raw/master/Crystal.3dshapes/Crystal_SMD_3225-4Pin_3.2x2.5mm.step",+  "sha256": "d2a89df82ee5f0f56ef23ee59c8161b8d4e92af2b21959d72778316dde3f9f22",+  "bbox_mm": [+   3.2,+   2.501,+   0.64+  ],+  "terminals": 4,+  "verdict": "rejected: four corner terminals (CX3225GA has two full-height electrodes) and 0.64 mm overall height (Kyocera 0.85 +-0.1, so minimum 0.75). KiCad has no 2-pad 3225 crystal footprint or model; KiCad's package generator has no recipe for this package either, so the model was built from the drawing (wiki-component ladder step 3).",+  "kept_on_page": false+ },+ "model": {+  "file": "cx3225ga08000d0ptvtt.step",+  "sha256": "b087c3960027505b285424149b9d178af47547fccbae53656f9fd73cb8e764e3",+  "builder": "provenance/tools/build_cx3225ga.py (CadQuery 2.8.0, OpenCascade)",+  "frame": "Z up, millimetres in STEP (metres in GLB), seated on z = 0, centred on the KiCad footprint origin, pad 1 electrode at -X on footprint pad 1, binds with zero offset and rotation",+  "parts": [+   "base (dark ceramic, 0.02 to 0.48 mm)",+   "seal_ring (0.48 to 0.53 mm, inset 0.08)",+   "lid (black, 0.53 to 0.85 mm, inset 0.10, R0.2 corners)",+   "electrode_1 (gold, pad 1, R0.3 inner front corner)",+   "electrode_2 (gold, pad 2)"+  ],+  "colours": "from Kyocera's product photo (https://ele.kyocera.com/assets/products/crystal-device/cx3225ga.png): black top, dark purple-black ceramic, gold castellations",+  "dimensions_mm": [+   {+    "item": "body length",+    "datasheet": "3.2 +-0.1",+    "kicad_3225_4pin": "3.200",+    "model": "3.200"+   },+   {+    "item": "body width",+    "datasheet": "2.5 +-0.1",+    "kicad_3225_4pin": "2.501",+    "model": "2.500"+   },+   {+    "item": "overall height",+    "datasheet": "0.85 +-0.1",+    "kicad_3225_4pin": "0.640 (fails, below 0.75 min)",+    "model": "0.850"+   },+   {+    "item": "base + seal ring height",+    "datasheet": "0.53 +-0.1",+    "kicad_3225_4pin": "not modelled",+    "model": "0.530"+   },+   {+    "item": "electrode gap",+    "datasheet": "0.6 +-0.1",+    "kicad_3225_4pin": "n/a (4 corner pads)",+    "model": "0.600"+   },+   {+    "item": "pad-1 electrode inner corner radius",+    "datasheet": "R0.3",+    "kicad_3225_4pin": "n/a",+    "model": "0.300"+   },+   {+    "item": "corner castellation radius",+    "datasheet": "4-R0.15",+    "kicad_3225_4pin": "n/a",+    "model": "0.150"+   },+   {+    "item": "end castellation width",+    "datasheet": "0.5 +-0.1",+    "kicad_3225_4pin": "n/a",+    "model": "0.500"+   },+   {+    "item": "end castellation metallisation height",+    "datasheet": "0.25",+    "kicad_3225_4pin": "n/a",+    "model": "0.250"+   },+   {+    "item": "electrode height (y)",+    "datasheet": "not printed; measured 2.28",+    "kicad_3225_4pin": "n/a",+    "model": "2.280"+   },+   {+    "item": "electrode outer edge from centre at y = 0 / at the ends",+    "datasheet": "not printed; measured 1.48 / 1.24 (arc)",+    "kicad_3225_4pin": "n/a",+    "model": "1.480 / 1.240"+   },+   {+    "item": "lid inset",+    "datasheet": "not printed; measured 0.10",+    "kicad_3225_4pin": "n/a",+    "model": "0.100"+   },+   {+    "item": "end castellation depth",+    "datasheet": "not printed; measured 0.10",+    "kicad_3225_4pin": "n/a",+    "model": "0.100"+   },+   {+    "item": "electrode film thickness",+    "datasheet": "not printed",+    "kicad_3225_4pin": "n/a",+    "model": "0.020 (assumed)"+   }+  ],+  "measurement": "Undimensioned features measured off a 600 dpi render of the catalog page (pdftoppm). The drawing is not drawn to one scale: 3.2 mm = 157 px in the top/bottom views and 155.8 px in the front view, 2.5 mm = 141.8 px (top), 144.8 px (bottom) and 128.8 px (end view), 0.85 mm = 123 px (144.7 px/mm). Each feature is scaled on the printed body edge of its own view and axis.",+  "overlay": {+   "image": "docs/proof/cx3225ga08000d0ptvtt-overlay-kyocera-catalog.png",+   "tool": "provenance/tools/overlay_cx.py",+   "result": "model outline within line width of the drawing in top, front, end and bottom (electrode) views"+  },+  "pin1": "Kyocera numbers neither pad. Dimensions panel is third-angle (plan above the front view, bottom view below it), so the bottom view keeps X and flips Y. The electrode drawn with the R0.3 inner corner is at -X: Adom calls it pad 1. In the model it sits at -X with the R0.3 corner at the front (-Y, footprint +Y), on footprint pad 1 (x = -1.0 mm). The crystal is not polarised."+ },+ "marking": {+  "lines": [+   "CX3225GA",+   "08000D0PTVTT"+  ],+  "kind": "virtual identification mark, not the manufacturer's marking",+  "why": "Kyocera's catalog does not document this part's top marking; the product photo shows a two-line code (19707 / K121F on the sample) that is not the MPN and whose meaning is not published.",+  "tool": "adom-aiflow-etch (service-step2glb /laser-etch, single-stroke outline font)",+  "surface": "top of the lid, coplanar, 5 um lift, Adom.LaserEtch group",+  "gate": "PASS",+  "check_image": "docs/proof/cx3225ga08000d0ptvtt-etched.check.png",+  "plan": "docs/proof/cx3225ga08000d0ptvtt-etched.mark-plan.json"+ },+ "files": {+  "cx3225ga08000d0ptvtt.step": "b087c3960027505b285424149b9d178af47547fccbae53656f9fd73cb8e764e3",+  "cx3225ga08000d0ptvtt-etched.step": "0aa4e2207762fd76f84df09c5ed2369a303a050593d20aab91ac4fea2704b0f5",+  "cx3225ga08000d0ptvtt.glb": "4410e12995e55bed00477c0f4ae4818b89437d507c5d5fcbb6586377db656c3c",+  "cx3225ga08000d0ptvtt-etched.glb": "a8893c37ea04e1a3c065bf5ec0f3baef6f7a1254ebf0b3ab3412c6dfd9a587cd",+  "cx3225ga08000d0ptvtt-hero.glb": "a4c097aecdba777eac663003068fb9c98b3869004a0201b63c119e59eff75ef3"+ },+ "tools": {+  "cadquery": "2.8.0",+  "step2glb": "step2glb 0.13.2",+  "adom-aiflow-etch": "installed CLI",+  "gltf-transform": "4.1.0",+  "meshoptimizer": "0.22.0"+ }+}
provenance/files.jsonadded+349
@@ -0,0 +1,349 @@+{+ "schema": "adom/file-provenance@1",+ "page": "adom/cx3225ga08000d0ptvtt",+ "date": "2026-10-21",+ "files": [+  {+   "path": "LICENSE",+   "bytes": 1039,+   "sha256": "7d6e98d31450fa85604555f6352a0a9190c16f0b94146178c1cb54cbb862ff9b",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "README.md",+   "bytes": 15522,+   "sha256": "2a6c657e3fdcc6e51b5578d348a276c9721eeaf4699512ef236eb9045e942d78",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "altium/CX3225GA08000D0PTVTT.PcbLib",+   "bytes": 229376,+   "sha256": "99952aec47d06804c658e92a811dee640ef05a5a9b7c405807dcafeae1b3449d",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "altium/CX3225GA08000D0PTVTT.SchLib",+   "bytes": 28672,+   "sha256": "2af5cdfee353363ed7f0c852842df355bb41b3e3207599777178ee5b124fcd09",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "altium/CX3225GA08000D0PTVTT.adom-lbr.json",+   "bytes": 5798,+   "sha256": "f2651efb3e879c4ccd24da86764cacabd87fde1601ce2ebd25b113471db23e78",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "cx3225ga08000d0ptvtt-etched.glb",+   "bytes": 46512,+   "sha256": "a8893c37ea04e1a3c065bf5ec0f3baef6f7a1254ebf0b3ab3412c6dfd9a587cd",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "cx3225ga08000d0ptvtt-etched.step",+   "bytes": 656119,+   "sha256": "0aa4e2207762fd76f84df09c5ed2369a303a050593d20aab91ac4fea2704b0f5",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "cx3225ga08000d0ptvtt-hero.glb",+   "bytes": 60248,+   "sha256": "a4c097aecdba777eac663003068fb9c98b3869004a0201b63c119e59eff75ef3",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "cx3225ga08000d0ptvtt-hero.overlay.json",+   "bytes": 2106,+   "sha256": "1dcbbcd9349e9864349f76afefc0cb17d428ddea155b0f5aa22fb6e36d867eab",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "cx3225ga08000d0ptvtt.glb",+   "bytes": 24428,+   "sha256": "4410e12995e55bed00477c0f4ae4818b89437d507c5d5fcbb6586377db656c3c",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "cx3225ga08000d0ptvtt.kicad_mod",+   "bytes": 1565,+   "sha256": "63d13204bc5077360a5df69eab15ae3654d11cbff7dd5178ae56fdffb78bd1da",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "cx3225ga08000d0ptvtt.kicad_sym",+   "bytes": 3863,+   "sha256": "693344a7396e9be811c9a6a28d1f096b0538e2c31ee18e1695aab539b7b2f4bc",+   "origin": "Adom: KiCad Device:Crystal (kicad-symbols master, Device.kicad_symdir/Crystal.kicad_sym) renamed, properties added, pin text compacted"+  },+  {+   "path": "cx3225ga08000d0ptvtt.lbr",+   "bytes": 7792,+   "sha256": "056452b9370b397a36bd71176ce0e13fba6342cd9f749bad9c033210ddea91b4",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "cx3225ga08000d0ptvtt.step",+   "bytes": 226827,+   "sha256": "b087c3960027505b285424149b9d178af47547fccbae53656f9fd73cb8e764e3",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/cx3225ga08000d0ptvtt-datasheet.pdf",+   "bytes": 596614,+   "sha256": "6d81365428115f7f83ea500b2b37cee263daa69fdcc0844525e517ac7313ed46",+   "origin": "Kyocera Corporation, https://ele.kyocera.com/assets/products/crystal-device/cx3225ga_e.pdf, downloaded 2026-10-21, unmodified"+  },+  {+   "path": "docs/figures/cx3225ga08000d0ptvtt-ds-p1-dimensions-land-pattern.png",+   "bytes": 108139,+   "sha256": "5c8ac3bd8645e3ed5fc9eafec14b879dcd4be58631c0361010afa8fe68dc2824",+   "origin": "Kyocera catalog page crop (pdftoppm render), Kyocera copyright"+  },+  {+   "path": "docs/figures/cx3225ga08000d0ptvtt-ds-p1-ordering.png",+   "bytes": 352614,+   "sha256": "6ec6a13ba917d1d267d1791abcb18f9df79e976834f4365f470fed83459b4b9b",+   "origin": "Kyocera catalog page crop (pdftoppm render), Kyocera copyright"+  },+  {+   "path": "docs/figures/cx3225ga08000d0ptvtt-ds-p1-specifications.png",+   "bytes": 124008,+   "sha256": "2f23fce1c266537b18773f6aaec639314c8bf4bfb2be34db35f7a57a36b1b7c3",+   "origin": "Kyocera catalog page crop (pdftoppm render), Kyocera copyright"+  },+  {+   "path": "docs/figures/cx3225ga08000d0ptvtt-ds-p1.png",+   "bytes": 635059,+   "sha256": "d56fb882586a7bf5b9b354d5c8504849421f2ac3ed6fd6ef6e09ca2e188fdc0c",+   "origin": "Kyocera catalog page crop (pdftoppm render), Kyocera copyright"+  },+  {+   "path": "docs/hero.png",+   "bytes": 108413,+   "sha256": "eceaa32da496f6863d797cbd2ef9b15fb5422c9bb1cc9c6235ac15f68396f660",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/models/cx3225ga08000d0ptvtt-etched-glb-top.png",+   "bytes": 18473,+   "sha256": "d896f31bc7e4dcd824c5a35eb172267922ed07f88c0da2b55c524b7a9043b81f",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/models/cx3225ga08000d0ptvtt-etched-step-bottom.png",+   "bytes": 5168,+   "sha256": "1996321b98edbf4b40bcf0405f885ded392a6cdeab0cb69d4d439e7b0ff1f0d1",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/models/cx3225ga08000d0ptvtt-etched-step-top.png",+   "bytes": 18473,+   "sha256": "d896f31bc7e4dcd824c5a35eb172267922ed07f88c0da2b55c524b7a9043b81f",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/models/cx3225ga08000d0ptvtt-glb-top.png",+   "bytes": 8343,+   "sha256": "3753f54e0d7c0c5b5b26143c37e5015ea8e6f736246c23fef70a46cbbd1f43b3",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/models/cx3225ga08000d0ptvtt-hero-glb-top.png",+   "bytes": 23388,+   "sha256": "a4ea79f24e12becd86b4789c40ad5798f8e59efe688dd9c089b806215ec771f4",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/models/cx3225ga08000d0ptvtt-high-glb-top.png",+   "bytes": 24105,+   "sha256": "e50001546e4c4955c96fef0064c83b09a2ddf55322836ccd920552322f7f3461",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/models/cx3225ga08000d0ptvtt-low-glb-top.png",+   "bytes": 10090,+   "sha256": "28e1cc63e0ec319007b04dec5dd51d111ecf1c6fad434a6a1e71adb3c8221744",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/models/cx3225ga08000d0ptvtt-medium-glb-top.png",+   "bytes": 23084,+   "sha256": "2ef9c31d31e27e57648ebb2ed99d232ca81111f47212c6e1a613fdab06a8ba6e",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/models/cx3225ga08000d0ptvtt-step-bottom.png",+   "bytes": 5168,+   "sha256": "1996321b98edbf4b40bcf0405f885ded392a6cdeab0cb69d4d439e7b0ff1f0d1",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/models/cx3225ga08000d0ptvtt-step-top.png",+   "bytes": 8343,+   "sha256": "3753f54e0d7c0c5b5b26143c37e5015ea8e6f736246c23fef70a46cbbd1f43b3",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/models/cx3225ga08000d0ptvtt-super-low-glb-top.png",+   "bytes": 8610,+   "sha256": "2fed8bc2272f617972aa25ea0073ce4bea3d77d8e305d7815b23fc2d6f67e9ac",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/proof/cx3225ga08000d0ptvtt-etched.check.json",+   "bytes": 3030,+   "sha256": "bdc64613100406dd496eee4c27c62b89528e2bc3748a725654f64c47be5c3b76",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/proof/cx3225ga08000d0ptvtt-etched.check.png",+   "bytes": 12015,+   "sha256": "c0f94af02b42b8db217e818f627215c8e85f7492828f6c52fc14803a02c25952",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/proof/cx3225ga08000d0ptvtt-etched.mark-plan.json",+   "bytes": 3296,+   "sha256": "715a7fedcfc6b746e0ed2d19657763f81c6b93a167511b78b1eea0b5a4c8464f",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "docs/proof/cx3225ga08000d0ptvtt-overlay-kyocera-catalog.png",+   "bytes": 75447,+   "sha256": "3c9134682f80a33f82faf91c5ccb05dedcab79469d36cfbae7fe0f546d926a6c",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-high.glb",+   "bytes": 46512,+   "sha256": "a8893c37ea04e1a3c065bf5ec0f3baef6f7a1254ebf0b3ab3412c6dfd9a587cd",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-high.png",+   "bytes": 24105,+   "sha256": "e50001546e4c4955c96fef0064c83b09a2ddf55322836ccd920552322f7f3461",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-low.glb",+   "bytes": 15612,+   "sha256": "e71db4e8909b9c6aa942ceb4496099cb470289fd8d8817f9617d160f5342d35a",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-low.png",+   "bytes": 10090,+   "sha256": "28e1cc63e0ec319007b04dec5dd51d111ecf1c6fad434a6a1e71adb3c8221744",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-medium.glb",+   "bytes": 34488,+   "sha256": "da7025446dd8a31327fd73672b38910e2cdba04ef3efd517e0c67c1ca7e6d4b9",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-medium.png",+   "bytes": 23084,+   "sha256": "2ef9c31d31e27e57648ebb2ed99d232ca81111f47212c6e1a613fdab06a8ba6e",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-super-low.glb",+   "bytes": 9932,+   "sha256": "ea0326889bed080f3a98ac93f3c538c683cbab4f5f2ceb75342ba9d556ac8742",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "lod/cx3225ga08000d0ptvtt-super-low.png",+   "bytes": 8610,+   "sha256": "2fed8bc2272f617972aa25ea0073ce4bea3d77d8e305d7815b23fc2d6f67e9ac",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "lod/manifest.json",+   "bytes": 5773,+   "sha256": "d75d25f1636c930c9b83a4b1681b8db11608d6f614d9d4122a1baea38d62c775",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "models.json",+   "bytes": 7607,+   "sha256": "d7222b33566a3864ee93f0faa598610b3d6640470ec0f8ddbf75455a9d0c3db1",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "provenance/3d-model.json",+   "bytes": 7304,+   "sha256": "c65d072f3e186f892fab805b0aa97641b29aeb63c2d240119680e491465b7f50",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "provenance/footprint-create.json",+   "bytes": 573,+   "sha256": "016734346e902f62e607eefe267986c49e5ab90fd3d4bea1edd7f4b45f0a1300",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "provenance/footprint.json",+   "bytes": 2618,+   "sha256": "7ade5270a39ac816d27da86068051cf062fa77b66a91784709b496b052b09dcf",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "provenance/tools/build_cx3225ga.py",+   "bytes": 5647,+   "sha256": "c5bc70ec13bd5502713ba79c487a560821ccf8202974d31d3e223cda54e67ea7",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "provenance/tools/glbtool.py",+   "bytes": 6340,+   "sha256": "d8e96b7c3e9f578eeba82562cda5c736ba3f5909daecc89ed353eee5ef7cc928",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "provenance/tools/lod.mjs",+   "bytes": 1768,+   "sha256": "5b9117e9a9c25991c50534d9bfcb81325c25c7874d6ffd617c861e5259385b1b",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "provenance/tools/materials.py",+   "bytes": 2745,+   "sha256": "0d728a3616ee4e9fd4e47da09ddca897cb978b090704493affbb2597dce1f236",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "provenance/tools/overlay_cx.py",+   "bytes": 4313,+   "sha256": "f9852fcd955265ecb01b8782760a49db926cb31bed109ea84532dd3aa9da0137",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "provenance/tools/render.py",+   "bytes": 6751,+   "sha256": "59c3b5816431388872deaeede6d9811cda5c529721d12b3eba320ad0ac11d8b1",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "verification.json",+   "bytes": 6240,+   "sha256": "545c6efcafad9f3e0be2f3613442f3537c199211f64df1517a45a8b5641b3c14",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "viewers/footprint.html",+   "bytes": 21781,+   "sha256": "8a43be3591ab45eb5616456e7c40c89cd7c14de8c4a2d7fa4f9ef6eb81cf107f",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  },+  {+   "path": "viewers/symbol.html",+   "bytes": 126843,+   "sha256": "e30b8f0d7cca857372ce8c90f0b7719145869c38224278c78c227ae7de447df6",+   "origin": "Adom-authored (Claude, AI), see README Provenance"+  }+ ]+}
provenance/footprint-create.jsonadded+11
@@ -0,0 +1,11 @@+{+  "footprintName": "cx3225ga08000d0ptvtt",+  "description": "Kyocera CX3225GA crystal, 3.2 x 2.5 mm ceramic SMD, 2 pads; land pattern per Kyocera CX3225GA catalog (As of October 2025): two 1.6 x 2.6 mm pads, 0.4 mm gap",+  "tags": "crystal CX3225GA 3225 2-pad Kyocera",+  "pads": [+    {"number": "1", "x": -1.0, "y": 0, "dx": 1.6, "dy": 2.6, "shape": "rect"},+    {"number": "2", "x": 1.0, "y": 0, "dx": 1.6, "dy": 2.6, "shape": "rect"}+  ],+  "body": {"x1": -1.6, "y1": -1.25, "x2": 1.6, "y2": 1.25},+  "courtyard": {"x1": -2.05, "y1": -1.55, "x2": 2.05, "y2": 1.55}+}
provenance/footprint.jsonadded+54
@@ -0,0 +1,54 @@+{+ "schema": "adom/footprint-provenance@1",+ "mpn": "CX3225GA08000D0PTVTT",+ "date": "2026-10-21",+ "file": "cx3225ga08000d0ptvtt.kicad_mod",+ "sha256": "63d13204bc5077360a5df69eab15ae3654d11cbff7dd5178ae56fdffb78bd1da",+ "built_with": "adom-footprint create (provenance/footprint-create.json), then hand edits: pin-1 silk bracket at x = -2.06 (as KiCad's crystal footprints do), F.Fab outline with a pin-1 chamfer, ${REFERENCE} on F.Fab, value set to the MPN, model binding to the etched STEP",+ "source": {+  "doc": "Kyocera CX3225GA catalog (As of October 2025), Recommended Land Pattern",+  "file": "docs/cx3225ga08000d0ptvtt-datasheet.pdf",+  "figure": "docs/figures/cx3225ga08000d0ptvtt-ds-p1-dimensions-land-pattern.png"+ },+ "pad_comparison": [+  {+   "pad": "1",+   "kyocera": "1.6 x 2.6 mm rectangle, left",+   "footprint": "rect 1.6 x 2.6 at (-1.0, 0)",+   "match": true+  },+  {+   "pad": "2",+   "kyocera": "1.6 x 2.6 mm rectangle, right",+   "footprint": "rect 1.6 x 2.6 at (+1.0, 0)",+   "match": true+  },+  {+   "item": "gap between pads",+   "kyocera": "0.4 mm",+   "footprint": "0.4 mm (inner edges at +-0.2)",+   "match": true+  },+  {+   "item": "overall span",+   "kyocera": "1.6 + 0.4 + 1.6 = 3.6 mm",+   "footprint": "-1.8 to +1.8 mm",+   "match": true+  },+  {+   "item": "pad shape",+   "kyocera": "plain rectangles",+   "footprint": "rect (no corner rounding added, per the drawing)",+   "match": true+  }+ ],+ "package_vs_land": "Package 3.2 x 2.5 mm sits inside the 3.6 x 2.6 mm land; the electrodes (0.6 mm gap, outer arc to 1.48 mm) land fully on the pads; the dashed package outline in Kyocera's land drawing agrees.",+ "courtyard": "0.25 mm around the pads (+-2.05 x +-1.55 mm), IPC-7351 nominal",+ "silkscreen": "lines at y = +-1.51 mm (0.15 mm clear of the pads) and a vertical bar at x = -2.06 marking pad 1; nothing over copper",+ "kicad_library_comparison": {+  "footprint": "Crystal.pretty/Crystal_SMD_3225-4Pin_3.2x2.5mm.kicad_mod",+  "pads": "4 pads 1.4 x 1.2 mm at (+-1.1, +-0.85)",+  "verdict": "does not fit, and is unsafe for this part: CX3225GA has two large electrodes and no ground or case pads. On KiCad's 4-pin pattern the left electrode covers pads 1 and 4 and the right electrode covers pads 2 and 3, so a 4-pin crystal symbol (1 and 3 crystal, 2 and 4 GND) would short each crystal terminal to ground through the part. Kyocera's own 2-pad land pattern is used instead; KiCad has no 2-pad 3225 crystal footprint."+ },+ "pin1": "pad 1 = the electrode with the R0.3 inner corner in Kyocera's bottom view (Kyocera numbers neither pad; the crystal is not polarised)."+}
provenance/tools/__pycache__/glbtool.cpython-312.pycadded
⋯ 1 unchanged line ⋯
provenance/tools/build_cx3225ga.pyadded+113
@@ -0,0 +1,113 @@+"""Build the Kyocera CX3225GA (2-pad, 3.2 x 2.5 mm) 3D model from the Kyocera catalog drawing.++Source: Kyocera "Crystal Units Surface Mount Type CX3225GA" catalog page (As of October 2025),+https://ele.kyocera.com/assets/products/crystal-device/cx3225ga_e.pdf, Dimensions and Recommended+Land Pattern panels. Printed values are used as printed; undimensioned features (electrode outer arc,+electrode height, lid inset, castellation depth) were measured off the drawing at 600 dpi with the+scale calibrated per axis on the printed 3.2 and 2.5 body edges (see provenance/3d-model.json).++Frame: Adom/KiCad Z-up, millimetres, seated on z = 0, centred on the footprint origin. +X is the+footprint's pad 2 side; -Y is the front edge (footprint +Y, KiCad's Y points down). Pad 1 is the+electrode with the R0.3 inner corner (Kyocera's bottom view, third-angle projection).++usage: python build_cx3225ga.py out.step [--simple]+  --simple: no castellations, no electrode corner rounding (for the super-low LOD source)+"""+import math+import sys++import cadquery as cq++OUT = sys.argv[1]+SIMPLE = "--simple" in sys.argv++# printed (catalog)+L, W, H = 3.2, 2.5, 0.85          # body 3.2 +-0.1, 2.5 +-0.1, height 0.85 +-0.1+BASE_TOP = 0.53                    # 0.53 +-0.1: ceramic base plus seal ring, from the seating plane+GAP = 0.6                          # electrode gap 0.6 +-0.1+R_INNER = 0.3                      # R0.3 on pad 1's inner front corner+R_BODY = 0.15                      # 4-R0.15 corner castellations+NOTCH_W = 0.5                      # 0.5 +-0.1 end castellation width+CAST_H = 0.25                      # 0.25 end castellation metallisation height+# measured off the drawing (600 dpi, per-axis calibration)+EL_HALF_H = 1.14                   # electrode half height (y)+EL_OUT_MID = 1.48                  # electrode outer edge at y = 0+EL_OUT_END = 1.24                  # electrode outer edge at y = +-EL_HALF_H+NOTCH_D = 0.10                     # end castellation depth (top and bottom views: 15 px at 157 px/mm)+LID_INSET = 0.08                   # seal ring inset; the lid is 0.02 further in (0.10, front and end views)+SEAL_T = 0.05                      # seal ring under the lid+EL_T = 0.02                        # electrode film thickness (bottom), body seated on the films+LID_R = 0.2                        # lid corner radius++base_z0 = EL_T+base_t = BASE_TOP - SEAL_T - base_z0+lid_z0 = BASE_TOP+lid_t = H - BASE_TOP++# ceramic base+base = cq.Workplane("XY").box(L, W, base_t, centered=(True, True, False)).translate((0, 0, base_z0))+if not SIMPLE:+    for sx in (-1, 1):+        for sy in (-1, 1):+            cut = cq.Workplane("XY").circle(R_BODY).extrude(base_t).translate((sx * L / 2, sy * W / 2, base_z0))+            base = base.cut(cut)+        notch = cq.Workplane("XY").box(2 * NOTCH_D, NOTCH_W, base_t, centered=(True, True, False)).translate((sx * L / 2, 0, base_z0))+        base = base.cut(notch)++# seal ring and lid+seal = (cq.Workplane("XY").rect(L - 2 * LID_INSET, W - 2 * LID_INSET).extrude(SEAL_T)+        .edges("|Z").fillet(LID_R).translate((0, 0, BASE_TOP - SEAL_T)))+lid = (cq.Workplane("XY").rect(L - 2 * LID_INSET - 0.04, W - 2 * LID_INSET - 0.04).extrude(lid_t)+       .edges("|Z").fillet(LID_R).translate((0, 0, lid_z0)))+++def electrode(sign, rounded):+    """Bottom electrode: straight inner edge at |x| = GAP/2, circular outer arc through+    (EL_OUT_END, +-EL_HALF_H) and (EL_OUT_MID, 0)."""+    xi = GAP / 2+    s = EL_OUT_MID - EL_OUT_END+    R = (EL_HALF_H ** 2 + s ** 2) / (2 * s)+    cx = EL_OUT_MID - R+    if SIMPLE:+        pts = [(xi, -EL_HALF_H), (EL_OUT_END, -EL_HALF_H), (EL_OUT_MID, 0), (EL_OUT_END, EL_HALF_H), (xi, EL_HALF_H)]+        wp = cq.Workplane("XY").polyline(pts).close()+    else:+        wp = (cq.Workplane("XY").moveTo(xi, -EL_HALF_H).lineTo(EL_OUT_END, -EL_HALF_H)+              .threePointArc((EL_OUT_MID, 0), (EL_OUT_END, EL_HALF_H)).lineTo(xi, EL_HALF_H).close())+    e = wp.extrude(EL_T)+    if rounded and not SIMPLE:+        # R0.3 on the inner corner at the front edge (y = -EL_HALF_H in Z-up model coordinates)+        e = e.edges("|Z").edges(cq.selectors.NearestToPointSelector((xi, -EL_HALF_H, 0))).fillet(R_INNER)+    if sign < 0:+        e = e.mirror("YZ")+    return e+++pad1 = electrode(-1, rounded=True)+pad2 = electrode(+1, rounded=False)+if not SIMPLE:+    # end castellation metallisation, 0.25 high, joined to each electrode+    for sx, p in ((-1, "pad1"), (1, "pad2")):+        c = (cq.Workplane("XY").box(0.02, NOTCH_W, CAST_H, centered=(True, True, False))+             .translate((sx * (L / 2 - NOTCH_D - 0.01), 0, 0)))+        bridge = (cq.Workplane("XY").box(abs(L / 2 - NOTCH_D - EL_OUT_MID) + 0.04, NOTCH_W, EL_T, centered=(True, True, False))+                  .translate((sx * (L / 2 - NOTCH_D + EL_OUT_MID) / 2, 0, 0)))+        if p == "pad1":+            pad1 = pad1.union(c).union(bridge)+        else:+            pad2 = pad2.union(c).union(bridge)++CERAMIC = cq.Color(0.20, 0.17, 0.24)   # dark purple-black alumina (Kyocera product photo)+SEAL = cq.Color(0.55, 0.55, 0.55)+LID = cq.Color(0.07, 0.07, 0.07)       # black top (product photo)+GOLD = cq.Color(0.83, 0.69, 0.22)++asm = cq.Assembly(name="CX3225GA08000D0PTVTT")+asm.add(base, name="base", color=CERAMIC)+asm.add(seal, name="seal_ring", color=SEAL)+asm.add(lid, name="lid", color=LID)+asm.add(pad1, name="electrode_1", color=GOLD)+asm.add(pad2, name="electrode_2", color=GOLD)+asm.save(OUT, exportType="STEP")+bb = asm.toCompound().BoundingBox()+print(f"OK {OUT}: bbox x {bb.xmin:.3f}..{bb.xmax:.3f} y {bb.ymin:.3f}..{bb.ymax:.3f} z {bb.zmin:.3f}..{bb.zmax:.3f}")
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/lod.mjsadded+34
@@ -0,0 +1,34 @@+// LOD derivation for a chip GLB (glTF Transform 4.1 + meshoptimizer), per wiki-component-lod:+//   batch:    join primitives per material inside each named part (body, pins, mark, Adom.LaserEtch stay+//             separate nodes), dedup, prune; geometry unchanged (lossless batching)+//   simplify: weld then error-bounded meshopt simplification to a target ratio (lower levels only)+//   quantize: KHR_mesh_quantization+// usage: node lod.mjs in.glb out.glb [--simplify ratio:error] [--quantize]+import { NodeIO } from '@gltf-transform/core';+import { ALL_EXTENSIONS } from '@gltf-transform/extensions';+import { dedup, join, prune, weld, simplify, quantize } from '@gltf-transform/functions';+import { MeshoptSimplifier } from 'meshoptimizer';++const [, , src, out, ...opts] = process.argv;+const io = new NodeIO().registerExtensions(ALL_EXTENSIONS);+const doc = await io.read(src);+const before = count(doc);+const steps = [dedup(), join({ keepNamed: true }), prune()];+const si = opts.indexOf('--simplify');+if (si >= 0) {+  const [ratio, error] = opts[si + 1].split(':').map(Number);+  await MeshoptSimplifier.ready;+  steps.push(weld(), simplify({ simplifier: MeshoptSimplifier, ratio, error, lockBorder: false }), prune());+}+if (opts.includes('--quantize')) steps.push(quantize());+await doc.transform(...steps);+await io.write(out, doc);+console.log(JSON.stringify({ out, before, after: count(doc), nodes: doc.getRoot().listNodes().filter(n => n.getMesh()).map(n => n.getName()) }));++function count(d) {+  let prims = 0, tris = 0;+  for (const m of d.getRoot().listMeshes()) for (const p of m.listPrimitives()) {+    prims++; const i = p.getIndices(); tris += (i ? i.getCount() : p.getAttribute('POSITION').getCount()) / 3;+  }+  return { prims, tris };+}
provenance/tools/materials.pyadded+61
@@ -0,0 +1,61 @@+#!/usr/bin/env python3+"""Give the CX3225GA GLB physical materials, by the part each material is used on.+step2glb writes base colours only, so glTF's defaults (metallic 1, roughness 1) render every part as+rough metal. This sets, per node name from the STEP assembly (build_cx3225ga.py):+  base          -> "Adom.Body (ceramic)": metallic 0.0, roughness 0.7, colour kept+  seal_ring     -> "Adom.SealRing": metallic 1.0, roughness 0.45, colour kept+  lid           -> "Adom.Lid (black)": metallic 0.0, roughness 0.6, colour kept+  electrode_*   -> "Adom.Terminals (gold)": metallic 1.0, roughness 0.3, colour kept+  Adom.LaserEtch-> metallic 0.0, roughness 0.7, colour kept (the virtual MPN mark)+Geometry and node transforms are untouched.+usage: materials.py in.glb [out.glb]   (in place when out is omitted)"""+import sys+from pygltflib import GLTF2, Material, PbrMetallicRoughness++SPEC = {+    "base": ("Adom.Body (ceramic)", 0.0, 0.7),+    "seal_ring": ("Adom.SealRing", 1.0, 0.45),+    "lid": ("Adom.Lid (black)", 0.0, 0.6),+    "electrode": ("Adom.Terminals (gold)", 1.0, 0.3),+    "Adom.LaserEtch": (None, 0.0, 0.7),+}+++def main(src, out):+    g = GLTF2().load(src)+    done = {}+    for n in g.nodes:+        if n.mesh is None:+            continue+        key = next((k for k in SPEC if (n.name or "").startswith(k)), None)+        if key is None:+            continue+        name, metal, rough = SPEC[key]+        for p in g.meshes[n.mesh].primitives:+            if p.material is None:+                continue+            if (key, p.material) in done:+                p.material = done[(key, p.material)]+                continue+            old = g.materials[p.material]+            col = (old.pbrMetallicRoughness.baseColorFactor if old.pbrMetallicRoughness else None) or [0.5, 0.5, 0.5, 1]+            m = Material(name=name or old.name, pbrMetallicRoughness=PbrMetallicRoughness(+                baseColorFactor=col, metallicFactor=metal, roughnessFactor=rough), doubleSided=old.doubleSided,+                alphaMode=old.alphaMode)+            g.materials.append(m)+            done[(key, p.material)] = len(g.materials) - 1+            p.material = len(g.materials) - 1+    # drop materials no primitive uses any more+    used = sorted({p.material for m in g.meshes for p in m.primitives if p.material is not None})+    remap = {o: i for i, o in enumerate(used)}+    g.materials = [g.materials[o] for o in used]+    for m in g.meshes:+        for p in m.primitives:+            if p.material is not None:+                p.material = remap[p.material]+    g.save(out)+    print("OK", out, [m.name for m in g.materials])+++if __name__ == "__main__":+    main(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else sys.argv[1])
provenance/tools/overlay_cx.pyadded+90
@@ -0,0 +1,90 @@+#!/usr/bin/env python3+"""Overlay proof for the CX3225GA model: orthographic projections of the plain STEP blended over the+Kyocera catalog drawing (600 dpi render of cx3225ga_e.pdf). The drawing is not drawn to one scale, so+each view is calibrated per axis on its own printed body edges (3.2 mm and 2.5 mm, 0.85 mm height).+Views: top (whole model), front (X, Z), end (Y, Z), bottom (electrodes only; third-angle bottom view:+X kept, Y flipped, as Kyocera draws it).+usage: overlay_cx.py model.step datasheet-600dpi.png out.png"""+import sys+import numpy as np+import cadquery as cq+from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageChops++step, page_png, out = sys.argv[1:4]+asm = cq.importers.importStep(step)+solids = asm.solids().vals()+++def tess(sel):+    V, T = [], []+    for s in sel:+        v, t = s.tessellate(0.002, 0.1)+        o = len(V)+        V += [[p.x, p.y, p.z] for p in v]+        T += [[a + o, b + o, c + o] for a, b, c in t]+    return np.array(V), np.array(T)+++ALL = tess(solids)+# electrodes are the solids that touch z = 0+ELEC = tess([s for s in solids if s.BoundingBox().zmin < 1e-6 and s.BoundingBox().zmax < 0.3])++# (title, crop, mesh, axis_u, axis_v, sign_v, centre_px, scale_u, scale_v)+# centres and scales measured on the drawing (see provenance/3d-model.json "overlay")+VIEWS = [+    ("Top view (x 157, y 141.8 px/mm)", (700, 5170, 1360, 5640), ALL, 0, 1, 1, (1028.75, 5404.25), 157.0, 141.8),+    ("Front view (x 155.8, z 144.7 px/mm)", (700, 5700, 1360, 5930), ALL, 0, 2, 1, (1028.75, 5880.5), 155.8, 144.7),+    ("End view (y 128.8, z 144.7 px/mm)", (1680, 5700, 2090, 5930), ALL, 1, 2, 1, (1882.5, 5880.5), 128.8, 144.7),+    ("Bottom view, electrodes (x 157, y 144.8 px/mm)", (700, 6040, 1360, 6520), ELEC, 0, 1, -1, (1028.75, 6279.5), 157.0, 144.8),+]+page = Image.open(page_png).convert("L")+SS = 4+try:+    font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)+    small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 18)+except Exception:+    font = small = ImageFont.load_default()+panels = []+for title, (x0, y0, x1, y1), (V, T), au, av, sv, (cx, cy), su, svs in VIEWS:+    W, H = x1 - x0, y1 - y0+    px = (cx - x0 + V[:, au] * su) * SS+    # top view: model +y is up on paper; bottom view (third angle): model +y is down on paper+    py = (cy - y0 - sv * V[:, av] * svs) * SS+    m = Image.new("L", (W * SS, H * SS), 0)+    d = ImageDraw.Draw(m)+    for t in T:+        d.polygon([(px[i], py[i]) for i in t], fill=255)+    m = m.resize((W, H), Image.LANCZOS).point(lambda v: 255 if v > 127 else 0)+    edge = 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))+    o = Image.composite(Image.blend(base, ImageChops.multiply(base, tint), 0.55), base, m)+    o = Image.composite(Image.new("RGB", (W, H), (230, 30, 30)), o, edge)+    pan = Image.new("RGB", (W, H + 34), "white")+    pan.paste(o, (0, 34))+    ImageDraw.Draw(pan).text((6, 6), title, fill="black", font=font)+    panels.append(pan)+cap = ["CX3225GA08000D0PTVTT model (red outline, blue fill) over Kyocera's CX3225GA catalog drawing (As of October 2025), 600 dpi.",+       "The drawing is not drawn to one scale (3.2 mm = 157 px but 2.5 mm = 142 to 145 px, and 129 px in the end view), so each view is",+       "calibrated on its own printed body edges. Printed: 3.2 x 2.5 x 0.85, 0.53 base, 0.6 electrode gap, R0.3, 4-R0.15, 0.5 and 0.25 castellation.",+       "Measured off the drawing: electrode height 2.28, outer arc 1.24 to 1.48 from centre, lid inset 0.10, castellation depth 0.10."]+cols = 2+rows = [panels[i:i + cols] for i in range(0, len(panels), cols)]+Wt = max(sum(p.width for p in r) + 20 * (len(r) + 1) for r in rows)+Wt = max(Wt, 40 + max(int(small.getlength(c)) for c in cap))+Ht = sum(max(p.height for p in r) + 20 for r in rows) + 30 + 26 * len(cap)+sheet = Image.new("RGB", (Wt, Ht), "white")+y = 20+for r in rows:+    x = 20+    for p in r:+        sheet.paste(p, (x, y))+        x += p.width + 20+    y += max(p.height for p in r) + 20+dr = ImageDraw.Draw(sheet)+y += 10+for line in cap:+    dr.text((20, y), line, fill="black", font=small)+    y += 26+sheet.save(out)+print("wrote", out, sheet.size)
provenance/tools/render.pyadded+120
@@ -0,0 +1,120 @@+#!/usr/bin/env python3+"""Depth-buffered orthographic render of a chip GLB (or STEP), for LOD previews and close-ups.+Reads geometry with the page's glbtool loader (quantized accessors handled), shades with a key + fill+light, Lambert plus a Blinn specular that follows each material's metallic/roughness (so matte tin+reads as tin and epoxy as epoxy). Orthographic, 3x supersampled.+usage: render.py model.glb|model.step out.png [--view iso|top|front|side|corner] [--size 400]+                 [--box x0,x1,y0,y1,z0,z1 (mm: frame only this region)] [--bg 36,40,46] [--alpha]+views: top, bottom, iso = from front-left-up (pin-1 side); corner = low 3/4 view onto a lead corner;+front = along +Y; side = along -X."""+import sys, math, argparse+import numpy as np+from PIL import Image+sys.path.insert(0, sys.argv[0].rsplit("/", 1)[0])++def load(path):+    if path.lower().endswith((".step", ".stp")):+        import cadquery as cq+        s = cq.importers.importStep(path).val()+        v, t = s.tessellate(0.002, 0.1)+        P = np.array([[p.x, p.y, p.z] for p in v]) / 1000.0+        return [dict(path="step", P=P, I=np.array(t), col=[0.6, 0.6, 0.6, 1], metal=0.0, rough=0.6)]+    from pygltflib import GLTF2+    import importlib.util+    spec = importlib.util.spec_from_file_location("glbtool", ARGS.glbtool)+    gt = importlib.util.module_from_spec(spec); spec.loader.exec_module(gt)+    g, res = gt.tris(path)+    # attach metallic/roughness per primitive (glbtool keeps only the colour)+    i = 0+    for n, M, p in gt.walk(g):+        for pr in g.meshes[n.mesh].primitives:+            m = g.materials[pr.material] if pr.material is not None else None+            pbr = m.pbrMetallicRoughness if m else None+            res[i]["metal"] = (pbr.metallicFactor if pbr and pbr.metallicFactor is not None else 1.0)+            res[i]["rough"] = (pbr.roughnessFactor if pbr and pbr.roughnessFactor is not None else 1.0)+            i += 1+    return res++def view_matrix(name):+    def rz(a): a = math.radians(a); return np.array([[math.cos(a), -math.sin(a), 0], [math.sin(a), math.cos(a), 0], [0, 0, 1]])+    def rx(a): a = math.radians(a); return np.array([[1, 0, 0], [0, math.cos(a), -math.sin(a)], [0, math.sin(a), math.cos(a)]])+    if name == "top": return np.eye(3)+    if name == "bottom": return rx(180)            # from below, X still to the right+    if name == "front": return rx(-90)                 # look along +Y: X right, Z up+    if name == "side": return rx(-90) @ rz(-90)        # look along -X: Y right, Z up+    if name == "corner": return rx(-68) @ rz(-38)      # low 3/4 onto the -X/-Y lead corner+    return rx(-55) @ rz(-35)                           # iso++ap = argparse.ArgumentParser()+ap.add_argument("model"); ap.add_argument("out")+ap.add_argument("--view", default="iso"); ap.add_argument("--size", type=int, default=400)+ap.add_argument("--box"); ap.add_argument("--bg", default="36,40,46")+ap.add_argument("--turn", type=float, default=0.0, help="extra turn about Z before the view (deg)")+ap.add_argument("--alpha", action="store_true", help="transparent background (RGBA PNG)")+ap.add_argument("--glbtool", default="/tmp/claude-1001/-home-adom-project/43add640-14a6-413a-b15b-ffdadeb7cf16/scratchpad/stm32/provenance/tools/glbtool.py")+ARGS = ap.parse_args()+parts = load(ARGS.model)+R = view_matrix(ARGS.view)+if ARGS.turn:+    _a = math.radians(ARGS.turn)+    R = R @ np.array([[math.cos(_a), -math.sin(_a), 0], [math.sin(_a), math.cos(_a), 0], [0, 0, 1]])+allP = np.vstack([p["P"] for p in parts]) * 1000.0+if ARGS.box:+    b = [float(v) for v in ARGS.box.split(",")]+    corners = np.array([[x, y, z] for x in b[0:2] for y in b[2:4] for z in b[4:6]])+else:+    corners = allP+Q = corners @ R.T+lo, hi = Q.min(0), Q.max(0); ctr = (lo + hi) / 2; ext = max(hi[0] - lo[0], hi[1] - lo[1]) * 1.08+SS = 3; N = ARGS.size * SS; s = N / ext+img = np.zeros((N, N, 3)); zb = np.full((N, N), -1e9)+L1 = np.array([0.35, 0.45, 0.82]); L1 /= np.linalg.norm(L1)     # key, in view space+L2 = np.array([-0.6, -0.2, 0.5]); L2 /= np.linalg.norm(L2)      # fill+V = np.array([0, 0, 1.0])+for p in parts:+    X = (p["P"] * 1000.0) @ R.T+    sx = (X[:, 0] - ctr[0]) * s + N / 2; sy = N / 2 - (X[:, 1] - ctr[1]) * s; z = X[:, 2]+    col = np.array(p["col"][:3]); metal, rough = p.get("metal", 0.0), max(p.get("rough", 0.6), 0.08)+    shin = 2.0 / (rough ** 4) - 2.0+    for t in p["I"]:+        a, b2, c = t+        v0, v1, v2 = X[a], X[b2], X[c]+        nrm = np.cross(v1 - v0, v2 - v0); ln = np.linalg.norm(nrm)+        if ln == 0: continue+        nrm /= ln+        if nrm[2] < 0: nrm = -nrm+        diff = 0.18 + 0.62 * max(0, nrm @ L1) + 0.2 * max(0, nrm @ L2)+        H = (L1 + V); H /= np.linalg.norm(H)+        spec = max(0, nrm @ H) ** min(shin, 400) * (0.9 if metal > 0.5 else 0.25) * (1 - rough * 0.6)+        base = col * (0.55 + 0.45 * (1 - metal)) if metal > 0.5 else col+        shade = np.clip(base * diff + spec * (col if metal > 0.5 else np.ones(3)), 0, 1)+        shade = shade ** (1 / 2.2)+        xs = np.array([sx[a], sx[b2], sx[c]]); ys = np.array([sy[a], sy[b2], sy[c]]); zs = np.array([z[a], z[b2], z[c]])+        x0, x1 = int(max(0, np.floor(xs.min()))), int(min(N - 1, np.ceil(xs.max())))+        y0, y1 = int(max(0, np.floor(ys.min()))), int(min(N - 1, np.ceil(ys.max())))+        if x0 > x1 or y0 > y1: continue+        gx, gy = np.meshgrid(np.arange(x0, x1 + 1) + 0.5, np.arange(y0, y1 + 1) + 0.5)+        d = (ys[1] - ys[2]) * (xs[0] - xs[2]) + (xs[2] - xs[1]) * (ys[0] - ys[2])+        if abs(d) < 1e-12: continue+        w0 = ((ys[1] - ys[2]) * (gx - xs[2]) + (xs[2] - xs[1]) * (gy - ys[2])) / d+        w1 = ((ys[2] - ys[0]) * (gx - xs[2]) + (xs[0] - xs[2]) * (gy - ys[2])) / d+        w2 = 1 - w0 - w1+        m = (w0 >= -1e-6) & (w1 >= -1e-6) & (w2 >= -1e-6)+        if not m.any(): continue+        zz = w0 * zs[0] + w1 * zs[1] + w2 * zs[2]+        sub = zb[y0:y1 + 1, x0:x1 + 1]+        upd = m & (zz > sub)+        sub[upd] = zz[upd]+        img[y0:y1 + 1, x0:x1 + 1][upd] = shade+bg = np.array([int(v) for v in ARGS.bg.split(",")]) / 255.0+img[zb < -1e8] = bg+if ARGS.alpha:+    a = (zb > -1e8).astype(np.float64)[..., None]+    rgba = np.concatenate([img * a, a], 2)      # premultiplied so the LANCZOS edge stays clean+    out = np.asarray(Image.fromarray((rgba * 255).astype(np.uint8), "RGBA").resize((ARGS.size, ARGS.size), Image.LANCZOS)).astype(np.float64)+    al = out[..., 3:] / 255.0+    out[..., :3] = np.where(al > 0, out[..., :3] / np.maximum(al, 1e-6), 0)+    Image.fromarray(np.clip(out, 0, 255).astype(np.uint8), "RGBA").save(ARGS.out)+else:+    Image.fromarray((img * 255).astype(np.uint8)).resize((ARGS.size, ARGS.size), Image.LANCZOS).save(ARGS.out)+print("wrote", ARGS.out)
verification.jsonadded+230
@@ -0,0 +1,230 @@+{+ "schema": "adom/verification@1",+ "part": "CX3225GA08000D0PTVTT",+ "variants": [+  {+   "mpn": "CX3225GA08000D0PTVTT",+   "note": "8.000 MHz, 8 pF, +-50 ppm, -40 to +150 C, +-150 ppm, individual spec TT",+   "stocked": false+  }+ ],+ "entries": [+  {+   "date": "2026-10-21",+   "by": {+    "name": "Claude (AI)",+    "handle": "claude"+   },+   "status": "verified",+   "title": "Identity: MPN decoded against Kyocera's How to Order",+   "scope": [+    "identity"+   ],+   "variants": [+    "*"+   ],+   "checks": [+    {+     "text": "CX3225GA = series; 08000 = 8.000 MHz; D0 = 8.0 pF load; P = +-50 ppm; T = -40 to +150 C; V = +-150 ppm; TT = individual specification (standard is CC)",+     "result": "pass"+    },+    {+     "text": "Mouser 581-CX3225GA0D0PTVTT lists 8 MHz, 8 pF, +-50 ppm, 500 ohm, AEC-Q200 (adom-parts-search 2026-10-21)",+     "result": "pass"+    },+    {+     "text": "Kyocera's catalog says frequencies from 12 MHz up are supported and to contact Kyocera for 8 to 10 MHz; this 8 MHz MPN is orderable at Mouser and DigiKey",+     "result": "note"+    }+   ],+   "evidence": [+    {+     "label": "How to Order",+     "href": "docs/figures/cx3225ga08000d0ptvtt-ds-p1-ordering.png"+    },+    {+     "label": "Specifications",+     "href": "docs/figures/cx3225ga08000d0ptvtt-ds-p1-specifications.png"+    }+   ],+   "files": {+    "docs/cx3225ga08000d0ptvtt-datasheet.pdf": "6d81365428115f7f83ea500b2b37cee263daa69fdcc0844525e517ac7313ed46"+   }+  },+  {+   "date": "2026-10-21",+   "by": {+    "name": "Claude (AI)",+    "handle": "claude"+   },+   "status": "verified",+   "title": "Footprint matches Kyocera's recommended land pattern",+   "scope": [+    "footprint"+   ],+   "variants": [+    "*"+   ],+   "checks": [+    {+     "text": "Two 1.6 x 2.6 mm rectangular pads, 0.4 mm gap, centres at +-1.0 mm",+     "result": "pass"+    },+    {+     "text": "Courtyard 0.25 mm around the pads; silkscreen 0.15 mm clear of copper; pad-1 bar at x = -2.06",+     "result": "pass"+    },+    {+     "text": "CX3225GA is a 2-pad part; KiCad's Crystal_SMD_3225-4Pin footprint would short each crystal terminal to a GND pad and was not used",+     "result": "note"+    }+   ],+   "evidence": [+    {+     "label": "Land pattern",+     "href": "docs/figures/cx3225ga08000d0ptvtt-ds-p1-dimensions-land-pattern.png"+    },+    {+     "label": "Comparison",+     "href": "provenance/footprint.json"+    }+   ],+   "files": {+    "cx3225ga08000d0ptvtt.kicad_mod": "63d13204bc5077360a5df69eab15ae3654d11cbff7dd5178ae56fdffb78bd1da"+   }+  },+  {+   "date": "2026-10-21",+   "by": {+    "name": "Claude (AI)",+    "handle": "claude"+   },+   "status": "verified",+   "title": "3D model overlays Kyocera's drawing",+   "scope": [+    "3d"+   ],+   "variants": [+    "*"+   ],+   "checks": [+    {+     "text": "Top, front, end and bottom (electrode) views overlaid at the drawing's per-axis scale",+     "result": "pass"+    },+    {+     "text": "3.2 x 2.5 x 0.85 mm, base 0.53, electrode gap 0.6, R0.3, 4-R0.15, castellation 0.5 x 0.25 as printed",+     "result": "pass"+    },+    {+     "text": "Electrode outline, lid inset and castellation depth are measured off the drawing (not printed)",+     "result": "note"+    },+    {+     "text": "KiCad's Crystal_SMD_3225-4Pin model rejected: 4 terminals, 0.64 mm tall (datasheet 0.85 +-0.1)",+     "result": "note"+    }+   ],+   "evidence": [+    {+     "label": "Overlay",+     "href": "docs/proof/cx3225ga08000d0ptvtt-overlay-kyocera-catalog.png"+    },+    {+     "label": "Provenance",+     "href": "provenance/3d-model.json"+    }+   ],+   "files": {+    "cx3225ga08000d0ptvtt.step": "b087c3960027505b285424149b9d178af47547fccbae53656f9fd73cb8e764e3",+    "cx3225ga08000d0ptvtt-etched.step": "0aa4e2207762fd76f84df09c5ed2369a303a050593d20aab91ac4fea2704b0f5",+    "cx3225ga08000d0ptvtt-etched.glb": "a8893c37ea04e1a3c065bf5ec0f3baef6f7a1254ebf0b3ab3412c6dfd9a587cd"+   }+  },+  {+   "date": "2026-10-21",+   "by": {+    "name": "Claude (AI)",+    "handle": "claude"+   },+   "status": "verified",+   "title": "Pad 1 orientation chain",+   "scope": [+    "pin1",+    "footprint",+    "3d"+   ],+   "variants": [+    "*"+   ],+   "checks": [+    {+     "text": "Kyocera numbers neither electrode; Adom pad 1 = the electrode with the R0.3 inner corner (third-angle bottom view, left)",+     "result": "pass"+    },+    {+     "text": "Footprint pad 1 at x = -1.0 mm with the silk bar and F.Fab chamfer on that side",+     "result": "pass"+    },+    {+     "text": "3D: electrode_1 (R0.3 corner, front) at -X, over footprint pad 1; hero_glb alignment OK, offset 0.0 mm",+     "result": "pass"+    },+    {+     "text": "Symbol pin 1 maps to pad 1; the crystal is not polarised, so a 180 degree placement is electrically harmless",+     "result": "pass"+    }+   ],+   "evidence": [+    {+     "label": "Bottom view",+     "href": "docs/models/cx3225ga08000d0ptvtt-etched-step-bottom.png"+    },+    {+     "label": "Overlay",+     "href": "docs/proof/cx3225ga08000d0ptvtt-overlay-kyocera-catalog.png"+    }+   ],+   "files": {+    "cx3225ga08000d0ptvtt.kicad_mod": "63d13204bc5077360a5df69eab15ae3654d11cbff7dd5178ae56fdffb78bd1da",+    "cx3225ga08000d0ptvtt.kicad_sym": "693344a7396e9be811c9a6a28d1f096b0538e2c31ee18e1695aab539b7b2f4bc",+    "cx3225ga08000d0ptvtt-etched.step": "0aa4e2207762fd76f84df09c5ed2369a303a050593d20aab91ac4fea2704b0f5"+   }+  },+  {+   "date": "2026-10-21",+   "by": {+    "name": "Claude (AI)",+    "handle": "claude"+   },+   "status": "open",+   "title": "Open: no human review yet; virtual marking; LODs not qualified",+   "scope": [+    "review"+   ],+   "variants": [+    "*"+   ],+   "checks": [+    {+     "text": "Everything on this page was built and checked by an AI; no engineer has reviewed it",+     "result": "note"+    },+    {+     "text": "The top marking is a virtual MPN mark; Kyocera does not publish this part's real marking",+     "result": "note"+    },+    {+     "text": "LOD files are candidates; no streaming LOD viewer has qualified them",+     "result": "note"+    },+    {+     "text": "Not yet opened in native KiCad, Fusion or Altium",+     "result": "note"+    }+   ],+   "evidence": [],+   "files": {}+  }+ ]+}