component
C1812C124J1GACTU - KEMET
Public Made by Adomby adom
MLCC 0.12 uF +/-5 %, 100 V, C0G/NP0, 1812 (4532 metric)
← Commit history
Component record for c1812c124j1gactu: files batch 2
37 files changed
+2619
docs/figures/kemet-ds-p4-electrical.pngdocs/figures/kemet-ds-p8-waterfall-1812.pngdocs/hero.pngdocs/models/step-plain.pngdocs/verify/c1812c124j1gactu-check.json+51docs/verify/c1812c124j1gactu-overlay.pngdocs/verify/hero-preview.pngland-pattern/README.md+41land-pattern/land-check.json+45land-pattern/land-pattern-icon.pngland-pattern/land-pattern.pnglod/high.glblod/high.pnglod/low.glblod/low.pnglod/manifest.json+178lod/medium.glblod/medium.pnglod/super-low.glblod/super-low.pngmodels.json+168package-reference.glbpackage.json+10provenance.json+460provenance/C1812C124J1GACTU.step.json+118provenance/tools/build.py+134provenance/tools/c1812c124j1gactu-overlay-spec.json+49provenance/tools/finish.py+49provenance/tools/landcheck.py+117provenance/tools/lod.py+199provenance/tools/ortho.py+148provenance/tools/overlay_chip.py+159provenance/tools/parts.py+53provenance/tools/push.sh+34provenance/tools/records.py+454provenance/tools/repose_step.py+117provenance/tools/verify_live.py+35docs/figures/kemet-ds-p4-electrical.pngadded⋯ 1 unchanged line ⋯
docs/figures/kemet-ds-p8-waterfall-1812.pngadded⋯ 1 unchanged line ⋯
docs/hero.pngadded⋯ 1 unchanged line ⋯
docs/models/step-plain.pngadded⋯ 1 unchanged line ⋯
docs/verify/c1812c124j1gactu-check.jsonadded+51@@ -0,0 +1,51 @@+{+ "step": "C1812C124J1GACTU.step",+ "bbox_mm": [+ -2.4,+ 2.4,+ -1.75,+ 1.75,+ 0.0,+ 1.55+ ],+ "checks": [+ {+ "dim": "L",+ "model_mm": 4.8,+ "datasheet_min": 4.2,+ "datasheet_max": 4.8,+ "pass": true+ },+ {+ "dim": "W",+ "model_mm": 3.5,+ "datasheet_min": 2.9000000000000004,+ "datasheet_max": 3.5,+ "pass": true+ },+ {+ "dim": "T/H",+ "model_mm": 1.55,+ "datasheet_min": 1.25,+ "datasheet_max": 1.5499999999999998,+ "pass": true+ },+ {+ "dim": "B (band width), top",+ "model_mm": 0.877,+ "datasheet_min": 0.25,+ "datasheet_max": 0.95,+ "pass": true+ },+ {+ "dim": "B (band width), bottom",+ "model_mm": 0.877,+ "datasheet_min": 0.25,+ "datasheet_max": 0.95,+ "pass": true+ }+ ],+ "z_up_seated_centred": true,+ "long_axis_x": true,+ "all_pass": true+}
docs/verify/c1812c124j1gactu-overlay.pngadded⋯ 1 unchanged line ⋯
docs/verify/hero-preview.pngadded⋯ 1 unchanged line ⋯
land-pattern/README.mdadded+41@@ -0,0 +1,41 @@+# Land pattern check: C1812C124J1GACTU++The footprint on this page is KiCad's standard library footprint `Capacitor_SMD:C_1812_4532Metric`, renamed to+`C_1812_4532Metric_C1812C124J1GACTU` so it can sit beside other parts in one library folder. The copper is unchanged; only the name and+the 3D model path (`${KIPRJMOD}/C1812C124J1GACTU.step`) differ from upstream. This page compares it with the land pattern KEMET+recommends for 1812 parts in datasheet C1003_C0G (2/20/2025), page 12, Table 3 (IPC-7351).++++## KEMET's three density levels for 1812 (mm)++| Level | C (centre to land centre) | Y (land length) | X (land width) | V1 x V2 (courtyard) |+|---|---|---|---|---|+| A, maximum protrusion | 2.15 | 1.60 | 3.60 | 6.90 x 4.60 |+| B, median (nominal) | 2.05 | 1.40 | 3.50 | 6.00 x 4.00 |+| C, minimum protrusion | 1.95 | 1.20 | 3.40 | 5.30 x 3.70 |++KEMET's drawing (p.12) gives C from the part centre to each land centre, so the gap between the lands is 2C - Y and the+overall length is 2C + Y. KEMET describes level B as "a robust solder attachment condition for reflow solder processes".++## KiCad pads against level B++| Dimension | KiCad C_1812_4532Metric (used) | KEMET density level B | Difference |+|---|---|---|---|+| gap between lands | 2.70 mm | 2.70 mm | +0.00 mm |+| land length | 1.40 mm | 1.40 mm | +0.00 mm |+| land width | 3.40 mm | 3.50 mm | -0.10 mm |+| overall length | 5.50 mm | 5.50 mm | +0.00 mm |++## Result++KiCad's footprint is IPC-7351 nominal, the same rule as KEMET's level B: land centres, land length, gap and overall length+are identical. The only difference is the land width: KiCad's pads are 3.40 mm wide, KEMET's level B lands+3.50 mm (0.10 mm narrower, 0.05 mm per side; level C uses 3.40 mm). The KiCad pads have rounded corners+(roundrect ratio 0.18); KEMET draws plain rectangles. KiCad's courtyard is 6.00 x 3.90 mm, KEMET's V1 x V2 6.00 x 4.00 mm.+Both cover the full printed body width (3.20 +/-0.30 mm). This page keeps the KiCad pads. A design that wants KEMET's+level B exactly should widen each pad to 3.50 mm.++KEMET lists the 1812 case as reflow only (datasheet p.3 and p.13).++The drawing and [land-check.json](land-check.json) come from `provenance/tools/landcheck.py`.
land-pattern/land-check.jsonadded+45@@ -0,0 +1,45 @@+{+ "footprint": "C_1812_4532Metric_C1812C124J1GACTU.kicad_mod",+ "kicad_library_footprint": "C_1812_4532Metric",+ "kicad": {+ "gap": 2.7,+ "len": 1.4,+ "wid": 3.4,+ "overall": 5.5+ },+ "maker_reference": {+ "gap": 2.7,+ "len": 1.4,+ "wid": 3.5,+ "overall": 5.5+ },+ "maker_source": "KEMET C1003_C0G 2/20/2025 p.12, Table 3, 1812, density level B (median, nominal): C 2.05 (centre to pad centre), Y 1.40, X 3.50, V1 6.00, V2 4.00 mm",+ "maker_letters": "gap = 2C - Y, land length = Y, land width = X, overall = 2C + Y (KEMET's letters C, Y, X)",+ "rows": [+ {+ "dim": "gap between lands",+ "kicad_mm": 2.7,+ "maker_nominal": 2.7,+ "difference_mm": 0.0+ },+ {+ "dim": "land length",+ "kicad_mm": 1.4,+ "maker_nominal": 1.4,+ "difference_mm": 0.0+ },+ {+ "dim": "land width",+ "kicad_mm": 3.4,+ "maker_nominal": 3.5,+ "difference_mm": -0.1+ },+ {+ "dim": "overall length",+ "kicad_mm": 5.5,+ "maker_nominal": 5.5,+ "difference_mm": 0.0+ }+ ],+ "all_within": null+}
land-pattern/land-pattern-icon.pngadded⋯ 1 unchanged line ⋯
land-pattern/land-pattern.pngadded⋯ 1 unchanged line ⋯
lod/high.glbadded⋯ 1 unchanged line ⋯
lod/high.pngadded⋯ 1 unchanged line ⋯
lod/low.glbadded⋯ 1 unchanged line ⋯
lod/low.pngadded⋯ 1 unchanged line ⋯
lod/manifest.jsonadded+178@@ -0,0 +1,178 @@+{+ "schema": "adom/component-lod@1",+ "mpn": "C1812C124J1GACTU",+ "upAxis": "Z",+ "units": "metres",+ "status": "candidate (AI-checked, not human-reviewed)",+ "purpose": "clean board-use component models: no footprint pads, outlines, labels or silkscreen; no marking (KEMET supplies C0G parts unmarked)",+ "source": {+ "path": "C1812C124J1GACTU.step",+ "sha256": "88e36c2337e5f1e514a7c5ad920e2abc2b0f399373a5eb1023a2c2af80c528ed"+ },+ "sourceToDisplay": "uniform scale 0.001 (mm to m); no rotation, no translation; origin = footprint origin, seat z = 0",+ "generator": "lod.py (OCCT via cadquery-ocp)",+ "removedHelpers": [],+ "levels": [+ {+ "level": "high",+ "path": "lod/high.glb",+ "sha256": "bef7ba8cde5577a9e4c18ae18557c1bfebfe35c9b3fbf5c380de2fc3504ccb20",+ "sizeBytes": 1497944,+ "decodedBufferBytes": 1496040,+ "triangles": 19676,+ "primitives": 2,+ "bounds": {+ "min": [+ -0.0024,+ -0.00175,+ 0.0+ ],+ "max": [+ 0.0024,+ 0.00175,+ 0.00155+ ]+ },+ "approximation": {+ "method": "OCCT BRepMesh_IncrementalMesh",+ "linearDeflectionMm": 0.0005,+ "angularDeflectionRad": 0.1+ },+ "detailVisibility": {+ "virtualMpn": false,+ "note": "no marking: the datasheet specifies none"+ },+ "review": "Candidate: rendered preview checked by AI (silhouette, terminals, seat z=0); not human-reviewed",+ "preview": {+ "path": "lod/high.png",+ "sha256": "6b6d873316fc536ba705988edb8cc15c0d875351d45fb158101c0e624de6cc20",+ "sizeBytes": 10872,+ "width": 400,+ "height": 300,+ "view": "iso",+ "sourceLevelSha256": "bef7ba8cde5577a9e4c18ae18557c1bfebfe35c9b3fbf5c380de2fc3504ccb20"+ }+ },+ {+ "level": "medium",+ "path": "lod/medium.glb",+ "sha256": "909900100576c0fb2203fd429a28f45080d64983bfdfbb5f1999927ad87428a0",+ "sizeBytes": 177448,+ "decodedBufferBytes": 175560,+ "triangles": 2412,+ "primitives": 2,+ "bounds": {+ "min": [+ -0.0024,+ -0.00175,+ 0.0+ ],+ "max": [+ 0.0024,+ 0.00175,+ 0.00155+ ]+ },+ "approximation": {+ "method": "OCCT BRepMesh_IncrementalMesh",+ "linearDeflectionMm": 0.004,+ "angularDeflectionRad": 0.35+ },+ "detailVisibility": {+ "virtualMpn": false,+ "note": "no marking: the datasheet specifies none"+ },+ "review": "Candidate: rendered preview checked by AI (silhouette, terminals, seat z=0); not human-reviewed",+ "preview": {+ "path": "lod/medium.png",+ "sha256": "2b833b2f1a2532901bf91e3c56027ce097bf29cf0199127f88c315ac05a60954",+ "sizeBytes": 10735,+ "width": 400,+ "height": 300,+ "view": "iso",+ "sourceLevelSha256": "909900100576c0fb2203fd429a28f45080d64983bfdfbb5f1999927ad87428a0"+ }+ },+ {+ "level": "low",+ "path": "lod/low.glb",+ "sha256": "8f1ad4771992ec4f9a9f75476d5e37ee50729a5b284172effcb6610a08462776",+ "sizeBytes": 47552,+ "decodedBufferBytes": 45672,+ "triangles": 668,+ "primitives": 2,+ "bounds": {+ "min": [+ -0.0024,+ -0.00175,+ 0.0+ ],+ "max": [+ 0.0024,+ 0.00175,+ 0.00155+ ]+ },+ "approximation": {+ "method": "OCCT BRepMesh_IncrementalMesh",+ "linearDeflectionMm": 0.02,+ "angularDeflectionRad": 0.9+ },+ "detailVisibility": {+ "virtualMpn": false,+ "note": "no marking: the datasheet specifies none"+ },+ "review": "Candidate: rendered preview checked by AI (silhouette, terminals, seat z=0); not human-reviewed",+ "preview": {+ "path": "lod/low.png",+ "sha256": "e9b5214c286b495842543087e64b79329e34eda4b1d5a8ea843f039252dd0784",+ "sizeBytes": 10883,+ "width": 400,+ "height": 300,+ "view": "iso",+ "sourceLevelSha256": "8f1ad4771992ec4f9a9f75476d5e37ee50729a5b284172effcb6610a08462776"+ }+ },+ {+ "level": "super-low",+ "path": "lod/super-low.glb",+ "sha256": "bce035912fe7694cf06a5e4d1947efe8cd8a4863a376e2df1070d2febda0ecb0",+ "sizeBytes": 3800,+ "decodedBufferBytes": 1944,+ "triangles": 36,+ "primitives": 2,+ "bounds": {+ "min": [+ -0.0024,+ -0.00175,+ 0.0+ ],+ "max": [+ 0.0024,+ 0.00175,+ 0.00155+ ]+ },+ "approximation": {+ "method": "per-material bounding boxes split at the part centre (terminals, body, overcoat)"+ },+ "detailVisibility": {+ "virtualMpn": false,+ "note": "no marking: the datasheet specifies none"+ },+ "review": "Candidate: rendered preview checked by AI (silhouette, terminals, seat z=0); not human-reviewed",+ "preview": {+ "path": "lod/super-low.png",+ "sha256": "175038a12e92cc626a29228a20d9821f6cdff3393608b7fd475f0172efacaa8c",+ "sizeBytes": 7960,+ "width": 400,+ "height": 300,+ "view": "iso",+ "sourceLevelSha256": "bce035912fe7694cf06a5e4d1947efe8cd8a4863a376e2df1070d2febda0ecb0"+ }+ }+ ],+ "hints": [+ "All four levels share pose and units; super-low is boxes per material group, chosen for board overviews."+ ]+}
lod/medium.glbadded⋯ 1 unchanged line ⋯
lod/medium.pngadded⋯ 1 unchanged line ⋯
lod/super-low.glbadded⋯ 1 unchanged line ⋯
lod/super-low.pngadded⋯ 1 unchanged line ⋯
models.jsonadded+168@@ -0,0 +1,168 @@+{+ "schema": "adom/models@1",+ "part": "C1812C124J1GACTU",+ "models": [+ {+ "path": "C1812C124J1GACTU.step",+ "format": "step",+ "title": "Package model (STEP, KEMET, Z-up)",+ "status": "current",+ "slot": "step",+ "variant": "plain",+ "role": "manufacturer",+ "use": [+ "board-cad"+ ],+ "marking": null,+ "derived_from": "vendor/C1812C124J1GACTU-kemet-original.step",+ "unique": "The board CAD model: KEMET's package solid re-posed to Z-up, seated on z = 0 and centred; KEMET's CAD logo glyphs dropped (the real part is unmarked)",+ "source": "KEMET 3D model (STEP) for C1812C124J1GACTU, https://search.kemet.com/component-documentation/download/step/C1812C124J1GACTU.step",+ "sha256": "88e36c2337e5f1e514a7c5ad920e2abc2b0f399373a5eb1023a2c2af80c528ed",+ "git_blob": "e4e7f1300c735efbd84ad2ae8bb9547feb87e368",+ "bytes": 376434,+ "thumb": "docs/models/step-plain.png"+ },+ {+ "path": "vendor/C1812C124J1GACTU-kemet-original.step",+ "format": "step",+ "title": "KEMET original download (STEP)",+ "status": "current",+ "variant": "original",+ "role": "manufacturer",+ "use": [+ "archive"+ ],+ "marking": {+ "text": "KEMET",+ "surface": "top of the ceramic",+ "colour": "SolidWorks default lavender (no colour set by KEMET)"+ },+ "unique": "The file exactly as downloaded: Y-up, corner at the origin, with KEMET's logo as three 5 um solids on top",+ "source": "KEMET 3D model (STEP) for C1812C124J1GACTU, https://search.kemet.com/component-documentation/download/step/C1812C124J1GACTU.step",+ "sha256": "b5d45febdee8defce1c3da229b540ed0a72760aeaa02501c728f7f8143eb6de7",+ "git_blob": "00a103d438e2cdf3caa51e8badb8cde33033cf51",+ "bytes": 361304+ },+ {+ "path": "package-reference.glb",+ "format": "glb",+ "title": "Package model (GLB)",+ "status": "current",+ "slot": "model_3d_plain",+ "variant": "plain",+ "role": "derived",+ "use": [+ "render",+ "board-insertion"+ ],+ "marking": null,+ "derived_from": "C1812C124J1GACTU.step",+ "unique": "Clean board-use model for viewers (step2glb service), no footprint artwork",+ "source": "KEMET 3D model (STEP) for C1812C124J1GACTU, https://search.kemet.com/component-documentation/download/step/C1812C124J1GACTU.step",+ "sha256": "53804863334397774437d2712467f7d99cf6a28381b8ca78c498bbbfedcf5aa2",+ "git_blob": "b8ef29926a372264231c35943dea1ffd0f0285ac",+ "bytes": 57820+ },+ {+ "path": "c1812c124j1gactu-hero.glb",+ "format": "glb",+ "title": "Hero model (GLB)",+ "status": "current",+ "slot": "model_3d",+ "variant": "hero",+ "role": "derived",+ "use": [+ "wiki-hero"+ ],+ "marking": null,+ "derived_from": "package-reference.glb",+ "unique": "The page's 3D view: the package plus teal dashed pad outlines, silkscreen and pad-number labels from the footprint (adom-hero standard)",+ "source": "adom/component-hero hero_glb.py",+ "sha256": "fd98debd3272da2303ea2af1a7af804b750ed9887356b33cdcff92680a7d21e2",+ "git_blob": "188e365829ba914437a8e46a8dd77a557ae4b6a1",+ "bytes": 93916,+ "thumb": "docs/verify/hero-preview.png"+ },+ {+ "path": "lod/high.glb",+ "format": "glb",+ "title": "Board LOD, high",+ "status": "current",+ "variant": "lod",+ "role": "derived",+ "use": [+ "board-insertion",+ "render"+ ],+ "marking": null,+ "derived_from": "C1812C124J1GACTU.step",+ "unique": "high detail for board and scaffold viewers: 19676 triangles, 2 primitives",+ "thumb": "lod/high.png",+ "source": "KEMET 3D model (STEP) for C1812C124J1GACTU, https://search.kemet.com/component-documentation/download/step/C1812C124J1GACTU.step",+ "sha256": "bef7ba8cde5577a9e4c18ae18557c1bfebfe35c9b3fbf5c380de2fc3504ccb20",+ "git_blob": "f358797688ddf3159914ca2297f8380c4f5266af",+ "bytes": 1497944+ },+ {+ "path": "lod/medium.glb",+ "format": "glb",+ "title": "Board LOD, medium",+ "status": "current",+ "variant": "lod",+ "role": "derived",+ "use": [+ "board-insertion",+ "render"+ ],+ "marking": null,+ "derived_from": "C1812C124J1GACTU.step",+ "unique": "medium detail for board and scaffold viewers: 2412 triangles, 2 primitives",+ "thumb": "lod/medium.png",+ "source": "KEMET 3D model (STEP) for C1812C124J1GACTU, https://search.kemet.com/component-documentation/download/step/C1812C124J1GACTU.step",+ "sha256": "909900100576c0fb2203fd429a28f45080d64983bfdfbb5f1999927ad87428a0",+ "git_blob": "2138efa5614c279fcd74d2f66e3147ec0da208fe",+ "bytes": 177448+ },+ {+ "path": "lod/low.glb",+ "format": "glb",+ "title": "Board LOD, low",+ "status": "current",+ "variant": "lod",+ "role": "derived",+ "use": [+ "board-insertion",+ "render"+ ],+ "marking": null,+ "derived_from": "C1812C124J1GACTU.step",+ "unique": "low detail for board and scaffold viewers: 668 triangles, 2 primitives",+ "thumb": "lod/low.png",+ "source": "KEMET 3D model (STEP) for C1812C124J1GACTU, https://search.kemet.com/component-documentation/download/step/C1812C124J1GACTU.step",+ "sha256": "8f1ad4771992ec4f9a9f75476d5e37ee50729a5b284172effcb6610a08462776",+ "git_blob": "8db1e6c840de09cfb4998b54acc9b87452bb2f0e",+ "bytes": 47552+ },+ {+ "path": "lod/super-low.glb",+ "format": "glb",+ "title": "Board LOD, super-low",+ "status": "current",+ "variant": "lod",+ "role": "derived",+ "use": [+ "board-insertion",+ "render"+ ],+ "marking": null,+ "derived_from": "C1812C124J1GACTU.step",+ "unique": "super-low detail for board and scaffold viewers: 36 triangles, 2 primitives",+ "thumb": "lod/super-low.png",+ "source": "KEMET 3D model (STEP) for C1812C124J1GACTU, https://search.kemet.com/component-documentation/download/step/C1812C124J1GACTU.step",+ "sha256": "bce035912fe7694cf06a5e4d1947efe8cd8a4863a376e2df1070d2febda0ecb0",+ "git_blob": "0f7896c1711e2f5c678e3d3065ce779c49c8c3b1",+ "bytes": 3800+ }+ ],+ "updated": "2026-10-21"+}
package-reference.glbadded⋯ 1 unchanged line ⋯
package.jsonadded+10@@ -0,0 +1,10 @@+{+ "name": "c1812c124j1gactu",+ "title": "C1812C124J1GACTU - KEMET",+ "version": "0.1.0",+ "description": "MLCC 0.12 uF +/-5 %, 100 V, C0G/NP0, 1812 (4532 metric)",+ "type": "hardware",+ "kind": "component",+ "license": "CC-BY-4.0",+ "files": []+}
provenance.jsonadded+460@@ -0,0 +1,460 @@+{+ "manufacturer_reference": "https://yageogroup.com/en/us/capacitors/ceramic/mlccs-smd/C1812C124J1GACTU.html",+ "datasheet": {+ "file": "docs/KEMET-C1003-C0G-2025-02-20.pdf",+ "sha256": "02d179914aeb9585eb2229ba8e18ef9d6b01c77c056de2af295d6950a2a5cc0d",+ "url": "https://content.kemet.com/datasheets/KEM_C1003_C0G_SMD.pdf",+ "same_bytes_at": "https://yageogroup.com/content/datasheet/asset/file/KEM_C1003_C0G_SMD",+ "title": "KEMET, Surface Mount Multilayer Ceramic Chip Capacitors (SMD MLCCs), C0G Dielectric, 10 to 250 VDC (Commercial Grade), C1003_C0G, 2/20/2025 (22 pages)",+ "revision": "C1003_C0G 2/20/2025",+ "checked": "2026-10-21"+ },+ "specs_read_from_datasheet": {+ "capacitance": "0.12 uF +/-5 % (124, J), p.1",+ "rated_voltage": "100 V (code 1), p.1",+ "dielectric": "C0G, p.1",+ "tcc": "+/-30 ppm/C, -55 to +125 C, p.1 and p.4",+ "voltage": "no capacitance change with applied rated DC voltage, p.2",+ "aging": "0 %, p.4",+ "df": "0.1 %, p.4",+ "ir": "1,000 MOhm uF or 100 GOhm, p.4",+ "dwv": "250 % rated, p.4",+ "dimensions_mm": {+ "L": "4.50 +/-0.30",+ "W": "3.20 +/-0.30",+ "T": "1.40 +/-0.15 (GH, p.8 and p.10)",+ "B": "0.60 +/-0.35",+ "S_min": "2.30"+ },+ "packaging": "TU: 7 inch reel, unmarked, 1,000 pcs plastic, 12 mm tape, 8 mm pitch (p.2, p.10, p.16)",+ "marking": "C0G supplied unmarked only, p.15",+ "soldering": "reflow only (1812), p.3 and p.13"+ },+ "symbol_source": "Original, written for this page (standard capacitor graphic, non-polar, pin ends on the 2.54 mm grid). CC-BY-4.0.",+ "footprint_source": "KiCad standard library Capacitor_SMD:C_1812_4532Metric (CC-BY-SA 4.0 with the KiCad library exception); renamed C_1812_4532Metric_C1812C124J1GACTU, model path ${KIPRJMOD}/C1812C124J1GACTU.step; copper unchanged",+ "footprint_upstream_url": "https://gitlab.com/kicad/libraries/kicad-footprints/-/raw/master/Capacitor_SMD.pretty/C_1812_4532Metric.kicad_mod",+ "footprint_upstream_sha256": "55049cf1f23070ef96b5a248163237009cf6f89b4690a42b422ce69a5832911d",+ "manufacturer_step": {+ "found": true,+ "url": "https://search.kemet.com/component-documentation/download/step/C1812C124J1GACTU.step",+ "also_served_at": "https://connect.kemet.com:7667/gateway/IntelliData-ComponentDocumentation/1.0/download/step/C1812C124J1GACTU (same bytes)",+ "method": "plain HTTP GET, no browser, no session",+ "retrieved": "2026-10-21",+ "file": "vendor/C1812C124J1GACTU-kemet-original.step",+ "sha256": "b5d45febdee8defce1c3da229b540ed0a72760aeaa02501c728f7f8143eb6de7",+ "header_name": "CCS_CCSTD_1812_L4.8W3.5T1.55B0.6.STEP",+ "authoring": "SolidWorks 2021, 2024-09-06",+ "units": "mm",+ "up_axis": "+Y",+ "repose": "provenance/C1812C124J1GACTU.step.json"+ },+ "board_step": {+ "file": "C1812C124J1GACTU.step",+ "sha256": "88e36c2337e5f1e514a7c5ad920e2abc2b0f399373a5eb1023a2c2af80c528ed",+ "transform": [+ [+ 1.0,+ 0.0,+ 0.0,+ -2.4+ ],+ [+ 0.0,+ 0.0,+ -1.0,+ -1.75+ ],+ [+ 0.0,+ 1.0,+ 0.0,+ 0.0+ ]+ ],+ "dropped": {+ "what": "KEMET logo glyph solids on the ceramic top (SolidWorks default lavender, no own colour); the real C0G part is unmarked (C1003_C0G p.15)",+ "solids": [+ {+ "faces": 25,+ "volume_mm3": 0.0019,+ "bbox_mm": [+ 2.0775,+ 1.4772,+ -2.1768,+ 2.592,+ 1.4822,+ -0.99+ ],+ "thickness_mm": 0.005+ },+ {+ "faces": 19,+ "volume_mm3": 0.00124,+ "bbox_mm": [+ 2.0775,+ 1.4772,+ -3.0648,+ 2.592,+ 1.4822,+ -2.0908+ ],+ "thickness_mm": 0.005+ },+ {+ "faces": 13,+ "volume_mm3": 0.00079,+ "bbox_mm": [+ 2.0775,+ 1.4772,+ -1.057,+ 2.592,+ 1.4822,+ -0.4375+ ],+ "thickness_mm": 0.005+ }+ ]+ },+ "kept": {+ "solid": "package (terminals and ceramic)",+ "faces": 72,+ "volume_mm3": 23.90241,+ "volume_after_mm3": 23.90241,+ "face_colours_srgb": [+ [+ 0.376,+ 0.231,+ 0.0+ ],+ [+ 0.552,+ 0.552,+ 0.552+ ]+ ]+ }+ },+ "geometry_status": "Manufacturer-issued CAD for this part number, every printed dimension checked for thickness code GH (docs/verify).",+ "marking": {+ "real_marking": null,+ "statement": "KEMET supplies C0G devices unmarked only (datasheet p.15), and the ordering code TU means 7 inch reel, unmarked (p.2)",+ "source": "KEMET C1003_C0G 2/20/2025 p.2 and p.15"+ },+ "frame": {+ "units": "mm (STEP), metres (GLB)",+ "up_axis": "Z",+ "seat_z": 0,+ "origin": "footprint origin"+ },+ "glb": {+ "tool": "step2glb convert (service)",+ "source": "C1812C124J1GACTU.step",+ "exit": 0+ },+ "hero_image": {+ "tool": "step2glb thumbnail --width 1600 --height 1000 (iso, 16:10)",+ "source": "C1812C124J1GACTU.step",+ "path": "docs/hero.png"+ },+ "hero_glb": {+ "file": "c1812c124j1gactu-hero.glb",+ "tool": "adom/component-hero skills/component-hero/scripts/hero_glb.py",+ "tool_sha256": "429e437a3f2e5f1dc3544fe65843ea4e019637f0a6163060cda2ae210f3af7bc",+ "inputs": [+ "package-reference.glb",+ "C_1812_4532Metric_C1812C124J1GACTU.kicad_mod",+ "page.json"+ ],+ "result": "OK c1812c124j1gactu-hero.glb: pads 2, silk items 2, pin1 none, labels 2; align OK (offset 0.0 mm)",+ "sha256": "fd98debd3272da2303ea2af1a7af804b750ed9887356b33cdcff92680a7d21e2"+ },+ "fusion_lbr": {+ "file": "C1812C124J1GACTU.lbr",+ "tool": "adom-lbr 2.47.7",+ "command": "adom-lbr generate --sym C1812C124J1GACTU.kicad_sym --fp C_1812_4532Metric_C1812C124J1GACTU.kicad_mod --part-metadata page.json -o C1812C124J1GACTU.lbr",+ "lint": {+ "passed": true,+ "errors": 0,+ "warnings": 0+ },+ "sha256": "8ab0ff6c67078aa179707b321443019cc088c6da010378e7dbff10433302ef1d"+ },+ "altium": {+ "files": [+ "altium/C1812C124J1GACTU.SchLib",+ "altium/C1812C124J1GACTU.PcbLib",+ "C1812C124J1GACTU.adom-lbr.json"+ ],+ "commands": [+ "adom-lbr import-kicad --sym C1812C124J1GACTU.kicad_sym --fp C_1812_4532Metric_C1812C124J1GACTU.kicad_mod --mpn C1812C124J1GACTU --out C1812C124J1GACTU.adom-lbr.json",+ "adom-lbr assemble-altium --no-dedupe --out altium/C1812C124J1GACTU C1812C124J1GACTU.adom-lbr.json"+ ],+ "report": {+ "_hint": "components are named by MPN; identical land patterns share one footprint. skipped[] is the complete drop list, never silent. lostFeatures[] names what the Altium lane could not carry (warn and ship); under --strict those parts move to refused[] and skipped[], the rest still assemble, status is partial and the exit code 2 while the files written stay valid.",+ "components": 1,+ "deduped": [],+ "footprints": 1,+ "lostFeatures": [],+ "models": {+ "embedded": 1,+ "missing": []+ },+ "pcblib": "altium/C1812C124J1GACTU.PcbLib",+ "refused": [],+ "renamed": [],+ "schlib": "altium/C1812C124J1GACTU.SchLib",+ "skipped": [],+ "status": "ok"+ },+ "hints": [+ "HINT: identity complete (MPN, Manufacturer, Datasheet, Description, Mouser, DigiKey, LCSC, Adom Wiki) - they become Altium parameters + Supplier links and EAGLE attributes."+ ],+ "sha256": {+ "altium/C1812C124J1GACTU.SchLib": "b84eb045fe23f2260ddda06777a2bd7a494691ab4be41a466bf73a9dc526b79f",+ "altium/C1812C124J1GACTU.PcbLib": "8fbecca79504564d582770b912d34fc9c62341bbd948d94df8d51f9174d0a356",+ "C1812C124J1GACTU.adom-lbr.json": "febc9b75092f87c62b3ac30d9e63593a14fecd36ffb4ecc7fd02b61e3575725d"+ }+ },+ "document_figures": [+ {+ "file": "docs/figures/kemet-ds-p1-overview-ordering.png",+ "document": "KEMET, Surface Mount Multilayer Ceramic Chip Capacitors (SMD MLCCs), C0G Dielectric, 10 to 250 VDC (Commercial Grade), C1003_C0G, 2/20/2025 (22 pages)",+ "page": 1,+ "crop_px_at_150dpi": [+ 60,+ 180,+ 1215,+ 1515+ ],+ "caption": "p.1 overview (no change in capacitance with time and voltage, +/-30 ppm/C) and ordering information (C 1812 C 124 J 1 G A C TU)",+ "sha256": "b24fcc4cfcf3ee41c7a2a3fcc681658b12e9bec39249a544b4d5e95967b935f0"+ },+ {+ "file": "docs/figures/kemet-ds-p2-benefits-packaging.png",+ "document": "KEMET, Surface Mount Multilayer Ceramic Chip Capacitors (SMD MLCCs), C0G Dielectric, 10 to 250 VDC (Commercial Grade), C1003_C0G, 2/20/2025 (22 pages)",+ "page": 2,+ "crop_px_at_150dpi": [+ 60,+ 150,+ 1215,+ 810+ ],+ "caption": "p.2 packaging C-spec (TU = 7 inch reel, unmarked) and benefits (no capacitance change with applied rated DC voltage, no decay with time)",+ "sha256": "66fc63e1866b22ed7f1d358d9a1c40f1bbc5d8ad848afd1050926927df1b4689"+ },+ {+ "file": "docs/figures/kemet-ds-p3-dimensions.png",+ "document": "KEMET, Surface Mount Multilayer Ceramic Chip Capacitors (SMD MLCCs), C0G Dielectric, 10 to 250 VDC (Commercial Grade), C1003_C0G, 2/20/2025 (22 pages)",+ "page": 3,+ "crop_px_at_150dpi": [+ 60,+ 150,+ 1215,+ 1185+ ],+ "caption": "p.3 dimensions: 1812 L 4.50 +/-0.30, W 3.20 +/-0.30, B 0.60 +/-0.35, S 2.30 min (mm)",+ "sha256": "4ca075141bbbf7977223b2d8b9aa1f377722b5d1fe7a1bd77ca96d311743b1d9"+ },+ {+ "file": "docs/figures/kemet-ds-p4-electrical.png",+ "document": "KEMET, Surface Mount Multilayer Ceramic Chip Capacitors (SMD MLCCs), C0G Dielectric, 10 to 250 VDC (Commercial Grade), C1003_C0G, 2/20/2025 (22 pages)",+ "page": 4,+ "crop_px_at_150dpi": [+ 60,+ 150,+ 1215,+ 1035+ ],+ "caption": "p.4 electrical parameters: -55 to +125 C, +/-30 ppm/C, 0 % aging, DWV 250 %, DF 0.1 %, IR",+ "sha256": "bf8972f6aa1ad36e02fd95f6c1e77fe98055ead639904713c4e03a46227c3f7b"+ },+ {+ "file": "docs/figures/kemet-ds-p8-waterfall-1812.png",+ "document": "KEMET, Surface Mount Multilayer Ceramic Chip Capacitors (SMD MLCCs), C0G Dielectric, 10 to 250 VDC (Commercial Grade), C1003_C0G, 2/20/2025 (22 pages)",+ "page": 8,+ "crop_px_at_150dpi": [+ 60,+ 150,+ 1215,+ 1080+ ],+ "caption": "p.8 Table 1B: 0.12 uF (124) in C1812C at 100 V (voltage code 1) has chip thickness code GH",+ "sha256": "b558affcc15fd4dc980a348155b4f2217d6dc4a26d8151121f1a4e0b156a9b39"+ },+ {+ "file": "docs/figures/kemet-ds-p10-thickness.png",+ "document": "KEMET, Surface Mount Multilayer Ceramic Chip Capacitors (SMD MLCCs), C0G Dielectric, 10 to 250 VDC (Commercial Grade), C1003_C0G, 2/20/2025 (22 pages)",+ "page": 10,+ "crop_px_at_150dpi": [+ 60,+ 150,+ 915,+ 1440+ ],+ "caption": "p.10 Table 2A: thickness code GH = 1.40 +/-0.15 mm; 1,000 pcs per 7 inch plastic reel",+ "sha256": "7211050566dcb2acafd592c9491b533f7c6c894bf6fbacecd2631484d3bd25e3"+ },+ {+ "file": "docs/figures/kemet-ds-p12-land-pattern.png",+ "document": "KEMET, Surface Mount Multilayer Ceramic Chip Capacitors (SMD MLCCs), C0G Dielectric, 10 to 250 VDC (Commercial Grade), C1003_C0G, 2/20/2025 (22 pages)",+ "page": 12,+ "crop_px_at_150dpi": [+ 60,+ 150,+ 1215,+ 1350+ ],+ "caption": "p.12 Table 3: IPC-7351 land pattern for 1812 at density levels A, B and C",+ "sha256": "c6245d9166539a8b215114c2364e169762830081943bd3258e8ee1974288b821"+ },+ {+ "file": "docs/figures/kemet-ds-p13-soldering.png",+ "document": "KEMET, Surface Mount Multilayer Ceramic Chip Capacitors (SMD MLCCs), C0G Dielectric, 10 to 250 VDC (Commercial Grade), C1003_C0G, 2/20/2025 (22 pages)",+ "page": 13,+ "crop_px_at_150dpi": [+ 60,+ 150,+ 1215,+ 1095+ ],+ "caption": "p.13 soldering: reflow only for 1812, profile for 100 % matte Sn (260 C peak)",+ "sha256": "3c337c99956fb0ba4e6ed1d7acaa5671618b2b59d1b793764926fc69f6442db7"+ },+ {+ "file": "docs/figures/kemet-ds-p15-marking.png",+ "document": "KEMET, Surface Mount Multilayer Ceramic Chip Capacitors (SMD MLCCs), C0G Dielectric, 10 to 250 VDC (Commercial Grade), C1003_C0G, 2/20/2025 (22 pages)",+ "page": 15,+ "crop_px_at_150dpi": [+ 60,+ 150,+ 1215,+ 1170+ ],+ "caption": "p.15 construction and marking: C0G devices are supplied unmarked only",+ "sha256": "0af48889b2a288a2fe612c5f01403017b43105619b7e2acc11406be113083466"+ }+ ],+ "datasheet_check": {+ "overlay": "docs/verify/c1812c124j1gactu-overlay.png",+ "numbers": "docs/verify/c1812c124j1gactu-check.json",+ "result": "PASS"+ },+ "land_pattern_check": {+ "readme": "land-pattern/README.md",+ "numbers": "land-pattern/land-check.json",+ "source": "KEMET C1003_C0G 2/20/2025 p.12, Table 3, 1812, density level B (median, nominal): C 2.05 (centre to pad centre), Y 1.40, X 3.50, V1 6.00, V2 4.00 mm",+ "levels": {+ "A": {+ "C": 2.15,+ "Y": 1.6,+ "X": 3.6,+ "V1": 6.9,+ "V2": 4.6+ },+ "B": {+ "C": 2.05,+ "Y": 1.4,+ "X": 3.5,+ "V1": 6.0,+ "V2": 4.0+ },+ "C": {+ "C": 1.95,+ "Y": 1.2,+ "X": 3.4,+ "V1": 5.3,+ "V2": 3.7+ }+ },+ "result": "KiCad matches KEMET level B except land width (3.40 vs 3.50 mm); KiCad kept"+ },+ "distributors": {+ "tool": "adom-parts-search search C1812C124J1GACTU",+ "date": "2026-10-21",+ "mouser": {+ "part": "80-C1812C124J1G",+ "url": "https://www.mouser.com/ProductDetail/KEMET/C1812C124J1GACTU?qs=Fn3VdHBzkcnCFZmhvW0Uag%3D%3D",+ "stock": 1701,+ "price": "$2.98 (1), $1.79 (100), lead time 266 days"+ },+ "digikey": {+ "part": "399-C1812C124J1GACTUTR-ND",+ "url": "https://www.digikey.com/en/products/detail/kemet/C1812C124J1GACTU/1874760",+ "stock": 1761,+ "price": "$3.53 (1)"+ },+ "lcsc": null,+ "lcsc_note": "JLCPCB/LCSC search for C1812C124J1GACTU returned no part on 2026-10-21"+ },+ "lod": {+ "manifest": "lod/manifest.json",+ "generator": "provenance/tools/lod.py",+ "source": "C1812C124J1GACTU.step",+ "source_sha256": "88e36c2337e5f1e514a7c5ad920e2abc2b0f399373a5eb1023a2c2af80c528ed"+ },+ "tools_note": "Scripts adapted from adom/grm188r72a103ka01d and adom/msash168sc7224ktca01; the hero generator is adom/component-hero hero_glb.py, unmodified.",+ "files": {+ "C1812C124J1GACTU.adom-lbr.json": "febc9b75092f87c62b3ac30d9e63593a14fecd36ffb4ecc7fd02b61e3575725d",+ "verification.json": "cd0cda2c7c786caa107147ba51c8de7946fd72dee65a1bd470000e113e013840",+ "C1812C124J1GACTU.kicad_sym": "83532d32c732fd73bc8966c290816fdb4d6c6a73870d65a187f82be83f90fe5c",+ "C1812C124J1GACTU.lbr": "8ab0ff6c67078aa179707b321443019cc088c6da010378e7dbff10433302ef1d",+ "package-reference.glb": "53804863334397774437d2712467f7d99cf6a28381b8ca78c498bbbfedcf5aa2",+ "c1812c124j1gactu-hero.overlay.json": "c2158a453c8646d4f82bc3e70447f284263c78413d9e48a86e9c0c47f7c731c7",+ "package.json": "a9bbcffea5ac512b12e930d6e183e3259bfbb125d5489916dee0d88a2e08a3f5",+ "c1812c124j1gactu-hero.glb": "fd98debd3272da2303ea2af1a7af804b750ed9887356b33cdcff92680a7d21e2",+ "C_1812_4532Metric_C1812C124J1GACTU.kicad_mod": "3a3d40605d16f4d89e01f0719afb667d0669a0b53ec5e4bad052f8eafab3a29d",+ "models.json": "f4133a569e0b1844aea3330a80acb6ae60518c1c9ebe50571ea7c8388cedc58d",+ "LICENSE": "f5f8a7678131996d5edd47f8fb283ecbd6e8850986dbbdabce1344315814e729",+ "page.json": "87c34a7227ebf54c859ae97f59de63cb09a2ae72b6a675c0a2b4313ce36597b7",+ "C1812C124J1GACTU.step": "88e36c2337e5f1e514a7c5ad920e2abc2b0f399373a5eb1023a2c2af80c528ed",+ "README.md": "6940a37cfdb964da30a0019ad1c8f68efba51718885c7b9f45be2c51665b4235",+ "provenance/C1812C124J1GACTU.step.json": "ee8035f1d805071413942a565258aaafeca4d924e378f50e9bb1ea1a5897bcd4",+ "provenance/tools/finish.py": "8aabfb27e56fd725b8c7e328b7314be41578cd39e0a30b76b596cdd259667402",+ "provenance/tools/parts.py": "857d73d0b15c362893e7907a4111954ce3b4be9d8cd63913da1e7063533b0e66",+ "provenance/tools/records.py": "3a83a0f00d402e8a1fb51179768c87449c0a1e0b35ba750d13e160a9595c836a",+ "provenance/tools/landcheck.py": "d3624535cee3fd54fb87602c767529c44f2d52b781c5acb73da682ce822a6bb7",+ "provenance/tools/overlay_chip.py": "52617a63c9504e020e574ec63f39f518734032fcda7b14ad158e3f8907b31723",+ "provenance/tools/ortho.py": "15f0e98e0a62384682a3129119c5d8e4a7a63e5cd10b356a10e12f4244020d29",+ "provenance/tools/lod.py": "98ac419e109fd403ef9522048530bc2f71147c196bab1dc9d5cda12fc2cd8b06",+ "provenance/tools/push.sh": "9730987a7d277b7e5386319767b97407da89ab20317beceb5a4f914160f82694",+ "provenance/tools/verify_live.py": "6b95fdc89dcc61cebd5295fc1c146060217084abc83312996158294843c71e03",+ "provenance/tools/repose_step.py": "13ba7954c9b42256840aba51529e1ae9548b0c056151249af4860645e0df9852",+ "provenance/tools/c1812c124j1gactu-overlay-spec.json": "fff878e0e736748817c6d4de2c94aa5161da4732e8245ecf5f2ccbd54ca377fe",+ "provenance/tools/build.py": "13f031b2e5480ee1ac759e3026de0ef584628cc7320914483c8e20ec4ce308e9",+ "lod/low.png": "e9b5214c286b495842543087e64b79329e34eda4b1d5a8ea843f039252dd0784",+ "lod/super-low.png": "175038a12e92cc626a29228a20d9821f6cdff3393608b7fd475f0172efacaa8c",+ "lod/low.glb": "8f1ad4771992ec4f9a9f75476d5e37ee50729a5b284172effcb6610a08462776",+ "lod/high.png": "6b6d873316fc536ba705988edb8cc15c0d875351d45fb158101c0e624de6cc20",+ "lod/manifest.json": "dd9359989e3c3775e0f2c266d92f9ec00abe4662315dd0ab420554c29af4be8d",+ "lod/super-low.glb": "bce035912fe7694cf06a5e4d1947efe8cd8a4863a376e2df1070d2febda0ecb0",+ "lod/medium.glb": "909900100576c0fb2203fd429a28f45080d64983bfdfbb5f1999927ad87428a0",+ "lod/high.glb": "bef7ba8cde5577a9e4c18ae18557c1bfebfe35c9b3fbf5c380de2fc3504ccb20",+ "lod/medium.png": "2b833b2f1a2532901bf91e3c56027ce097bf29cf0199127f88c315ac05a60954",+ "docs/KEMET-C1003-C0G-2025-02-20.pdf": "02d179914aeb9585eb2229ba8e18ef9d6b01c77c056de2af295d6950a2a5cc0d",+ "docs/hero.png": "a6ec189d403cd66fd838836392e2649745dd15daa5fa81d0399a54f504df1e45",+ "docs/models/step-plain.png": "c0ecdc20ca30682f90ffcf00ac366607240ad07db54bced23f404d34037d2950",+ "docs/verify/c1812c124j1gactu-check.json": "07f7bac8d13516dfcfe384cee27c8b70df2483a7aa669a1e0d840769daa47c8e",+ "docs/verify/hero-preview.png": "cad91eef07080746d55716f77cb7efdc8c176c504c5715c038635de9aed840dd",+ "docs/verify/c1812c124j1gactu-overlay.png": "30ae1c993d3d0dfbc34a91f4cbcc239c6a040d392beaa9f6066bd92e292d4ec2",+ "docs/figures/kemet-ds-p8-waterfall-1812.png": "b558affcc15fd4dc980a348155b4f2217d6dc4a26d8151121f1a4e0b156a9b39",+ "docs/figures/kemet-ds-p13-soldering.png": "3c337c99956fb0ba4e6ed1d7acaa5671618b2b59d1b793764926fc69f6442db7",+ "docs/figures/kemet-ds-p2-benefits-packaging.png": "66fc63e1866b22ed7f1d358d9a1c40f1bbc5d8ad848afd1050926927df1b4689",+ "docs/figures/kemet-ds-p12-land-pattern.png": "c6245d9166539a8b215114c2364e169762830081943bd3258e8ee1974288b821",+ "docs/figures/kemet-ds-p4-electrical.png": "bf8972f6aa1ad36e02fd95f6c1e77fe98055ead639904713c4e03a46227c3f7b",+ "docs/figures/figures.json": "b9d44071a97be2046a361d2602c948f7d21c22f777b71d4095368f6c2596ddbb",+ "docs/figures/kemet-ds-p15-marking.png": "0af48889b2a288a2fe612c5f01403017b43105619b7e2acc11406be113083466",+ "docs/figures/kemet-ds-p1-overview-ordering.png": "b24fcc4cfcf3ee41c7a2a3fcc681658b12e9bec39249a544b4d5e95967b935f0",+ "docs/figures/kemet-ds-p3-dimensions.png": "4ca075141bbbf7977223b2d8b9aa1f377722b5d1fe7a1bd77ca96d311743b1d9",+ "docs/figures/kemet-ds-p10-thickness.png": "7211050566dcb2acafd592c9491b533f7c6c894bf6fbacecd2631484d3bd25e3",+ "land-pattern/land-check.json": "807a04ca14e1aafb57f1b1b7c3903274c584b429309e2abd4615737a66c62cfc",+ "land-pattern/land-pattern-icon.png": "b7ea9d3ee69bf1624f511156c8a2318285537e0f7976d8c2421451c6ee6fb7da",+ "land-pattern/land-pattern.png": "b0d2226eb261a22ee847ef302abf24e3d304ca5f6a8a488e7503bcfbe9150ac0",+ "land-pattern/README.md": "c24e33ad743d4efc7836e5142afd68ca03d54221dff8a56c335b5d49370ff742",+ "vendor/C1812C124J1GACTU-kemet-original.step": "b5d45febdee8defce1c3da229b540ed0a72760aeaa02501c728f7f8143eb6de7",+ "altium/C1812C124J1GACTU.PcbLib": "8fbecca79504564d582770b912d34fc9c62341bbd948d94df8d51f9174d0a356",+ "altium/C1812C124J1GACTU.SchLib": "b84eb045fe23f2260ddda06777a2bd7a494691ab4be41a466bf73a9dc526b79f"+ },+ "files_note": "provenance.json itself is not listed.",+ "built_by": "Claude (AI agent) for John Lauer",+ "date": "2026-10-21"+}
provenance/C1812C124J1GACTU.step.jsonadded+118@@ -0,0 +1,118 @@+{+ "source": "C1812C124J1GACTU-kemet-original.step",+ "source_sha256": "b5d45febdee8defce1c3da229b540ed0a72760aeaa02501c728f7f8143eb6de7",+ "source_bbox_mm": [+ -0.0,+ -0.0,+ -3.5,+ 4.8,+ 1.55,+ 0.0+ ],+ "source_units": "mm",+ "source_up_axis": "+Y",+ "source_solids": 4,+ "kept": {+ "solid": "package (terminals and ceramic)",+ "faces": 72,+ "volume_mm3": 23.90241,+ "volume_after_mm3": 23.90241,+ "face_colours_srgb": [+ [+ 0.376,+ 0.231,+ 0.0+ ],+ [+ 0.552,+ 0.552,+ 0.552+ ]+ ]+ },+ "dropped": {+ "what": "KEMET logo glyph solids on the ceramic top (SolidWorks default lavender, no own colour); the real C0G part is unmarked (C1003_C0G p.15)",+ "solids": [+ {+ "faces": 25,+ "volume_mm3": 0.0019,+ "bbox_mm": [+ 2.0775,+ 1.4772,+ -2.1768,+ 2.592,+ 1.4822,+ -0.99+ ],+ "thickness_mm": 0.005+ },+ {+ "faces": 19,+ "volume_mm3": 0.00124,+ "bbox_mm": [+ 2.0775,+ 1.4772,+ -3.0648,+ 2.592,+ 1.4822,+ -2.0908+ ],+ "thickness_mm": 0.005+ },+ {+ "faces": 13,+ "volume_mm3": 0.00079,+ "bbox_mm": [+ 2.0775,+ 1.4772,+ -1.057,+ 2.592,+ 1.4822,+ -0.4375+ ],+ "thickness_mm": 0.005+ }+ ]+ },+ "rotation": "+90 deg about X (y -> z, z -> -y)",+ "translation_mm": [+ -2.4,+ -1.75,+ 0.0+ ],+ "matrix_3x4": [+ [+ 1.0,+ 0.0,+ 0.0,+ -2.4+ ],+ [+ 0.0,+ 0.0,+ -1.0,+ -1.75+ ],+ [+ 0.0,+ 1.0,+ 0.0,+ 0.0+ ]+ ],+ "output": "C1812C124J1GACTU.step",+ "output_sha256": "88e36c2337e5f1e514a7c5ad920e2abc2b0f399373a5eb1023a2c2af80c528ed",+ "output_bbox_mm": [+ -2.4,+ -1.75,+ -0.0,+ 2.4,+ 1.75,+ 1.55+ ],+ "LWT_mm": [+ 4.8,+ 3.5,+ 1.55+ ]+}
provenance/tools/build.pyadded+134@@ -0,0 +1,134 @@+"""Build the C1812C124J1GACTU page tree (KEMET STEP re-posed, KiCad footprint renamed, symbol, checks, GLB, LODs, figures).++usage: build.py (cadquery venv python, run with -I under tools/capped)+Writes /home/adom/project/uta-dynos/work/pages/c1812c124j1gactu/. Downloads are read from+/home/adom/project/uta-dynos/work/pages/_build/dl (never executed); scripts live in this folder.+"""+import sys, os, json, shutil, subprocess, hashlib, re+from PIL import Image+TOOLS = os.path.dirname(os.path.abspath(__file__))+sys.path.insert(0, TOOLS)+from parts import P, ROOT, FIGS, DOCS, TODAY, DS_URL+DL = os.path.join(ROOT, "dl"); WORK = os.path.join(ROOT, "work", "kemet"); os.makedirs(WORK, exist_ok=True)+PY = "/home/adom/.venvs/cq/bin/python"+slug, MPN = P["slug"], P["mpn"]+OUT = f"/home/adom/project/uta-dynos/work/pages/{slug}"+sha = lambda f: hashlib.sha256(open(f, "rb").read()).hexdigest()+def run(cmd, **kw):+ r = subprocess.run(cmd, capture_output=True, text=True, **kw)+ if r.returncode != 0:+ print("FAILED", cmd, r.stdout[-2000:], r.stderr[-2000:]); sys.exit(1)+ return r+for d in ("docs/verify", "docs/models", "docs/figures", "lod", "provenance/tools", "land-pattern", "altium", "vendor"):+ os.makedirs(os.path.join(OUT, d), exist_ok=True)++# ---------- STEP: KEMET's own model, original kept; board model re-posed to Z-up, logo glyphs dropped ----------+src_step = os.path.join(DL, "kemet", "step", f"{MPN}-kemet-original.step")+orig_name = f"vendor/{MPN}-kemet-original.step"+shutil.copyfile(src_step, os.path.join(OUT, orig_name))+step_name = f"{MPN}.step"; step_path = os.path.join(OUT, step_name)+r = run([PY, "-I", os.path.join(TOOLS, "repose_step.py"), src_step, step_path], cwd=TOOLS)+repose = json.loads(r.stdout.strip().splitlines()[-1]); repose["output"] = step_name+assert repose["source_sha256"] == sha(os.path.join(OUT, orig_name))+json.dump(repose, open(os.path.join(OUT, f"provenance/{step_name}.json"), "w"), indent=1)++# ---------- footprint: KiCad standard C_1812_4532Metric, renamed, model path set ----------+fp_name = f"{P['fp']}_{MPN}"+src_fp = open(os.path.join(DL, "kicad", P["fp"] + ".kicad_mod")).read()+fp = src_fp.replace(f'(footprint "{P["fp"]}"', f'(footprint "{fp_name}"', 1)+fp = re.sub(r'\(model "[^"]+"', f'(model "${{KIPRJMOD}}/{step_name}"', fp)+assert fp.count(fp_name) == 1 and "${KIPRJMOD}/" + step_name in fp+fp_file = f"{fp_name}.kicad_mod"+open(os.path.join(OUT, fp_file), "w").write(fp)++# ---------- symbol (standard capacitor graphic, pin ends on the 2.54 mm grid) ----------+d = P["dist"]+def prop(name, val, y, hide=True):+ h = " hide" if hide else ""+ return f' (property "{name}" "{val}" (at 0 {y} 0) (effects (font (size 1.27 1.27)){h}))\n'+props = (prop("Reference", "C", 0, False).replace("(at 0 0 0)", "(at 2.54 1.27 0)").replace("(font (size 1.27 1.27))", "(font (size 1.27 1.27)) (justify left)")+ + prop("Value", P["value_sym"], 0, False).replace("(at 0 0 0)", "(at 2.54 -1.27 0)").replace("(font (size 1.27 1.27))", "(font (size 1.27 1.27)) (justify left)")+ + prop("Footprint", fp_name, -7.62) + prop("Datasheet", DS_URL, -10.16) + prop("Description", P["brief"], -12.7)+ + prop("MPN", MPN, -15.24) + prop("Manufacturer", P["mfr"], -17.78)+ + prop("Mouser", d["mouser"][0], -20.32) + prop("DigiKey", d["digikey"][0], -22.86) + prop("LCSC", "", -25.4)+ + prop("Adom Wiki", f"https://wiki.adom.inc/adom/{slug}", -27.94))+sym = f'''(kicad_symbol_lib (version 20241209) (generator "adom_passive_symbol")+ (symbol "{MPN}" (pin_numbers (hide yes)) (pin_names (offset 0.254) (hide yes)) (exclude_from_sim no) (in_bom yes) (on_board yes)+{props} (property "ki_keywords" "capacitor mlcc 1812 4532 100V C0G NP0 120nF {MPN}" (at 0 0 0) (effects (font (size 1.27 1.27)) hide))+ (symbol "{MPN}_0_1"+ (polyline (pts (xy -2.032 -0.762) (xy 2.032 -0.762)) (stroke (width 0.508) (type default)) (fill (type none)))+ (polyline (pts (xy -2.032 0.762) (xy 2.032 0.762)) (stroke (width 0.508) (type default)) (fill (type none)))+ )+ (symbol "{MPN}_1_1"+ (pin passive line (at 0 2.54 270) (length 1.778) (name "~" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27)))))+ (pin passive line (at 0 -2.54 90) (length 1.778) (name "~" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27)))))+ )+ )+)+'''+sym_file = f"{MPN}.kicad_sym"+open(os.path.join(OUT, sym_file), "w").write(sym)++# ---------- documents + figure crops (150 dpi) ----------+pdfs = {}+for key, local, pub, title, url in DOCS:+ shutil.copyfile(os.path.join(DL, local), os.path.join(OUT, pub)); pdfs[key] = os.path.join(DL, local)+figs = []+for name, doc, page, box, caption in FIGS:+ png = os.path.join(WORK, f"fig150-{doc}-{page}.png")+ if not os.path.exists(png):+ run(["pdftoppm", "-r", "150", "-png", "-singlefile", "-f", str(page), "-l", str(page), pdfs[doc], png[:-4]])+ b = [int(round(v * 1.5)) for v in box]+ out = os.path.join(OUT, "docs/figures", name)+ Image.open(png).convert("RGB").crop(b).save(out, optimize=True)+ figs.append({"file": f"docs/figures/{name}", "document": next(t for k, _, _, t, _ in DOCS if k == doc), "page": page,+ "crop_px_at_150dpi": b, "caption": caption, "sha256": sha(out)})+json.dump(figs, open(os.path.join(OUT, "docs/figures/figures.json"), "w"), indent=1)++# ---------- overlay proof of the maker's STEP against the printed limits ----------+crops100 = []+for pg, box in ((3, [40, 330, 810, 770]), (10, [40, 100, 610, 960])):+ png = os.path.join(WORK, f"ds100-p{pg}.png")+ if not os.path.exists(png):+ run(["pdftoppm", "-r", "100", "-png", "-singlefile", "-f", str(pg), "-l", str(pg), pdfs["ds"], png[:-4]])+ crops100.append([png, box])+spec = {"label": MPN, "mpn": MPN, **P["overlay"], "crops": crops100, "marking": "none (C0G is supplied unmarked, p.15)"}+sp = os.path.join(WORK, "overlay-spec.json"); json.dump(spec, open(sp, "w"))+r = run([PY, "-I", os.path.join(TOOLS, "overlay_chip.py"), sp, step_path, os.path.join(OUT, f"docs/verify/{slug}-overlay.png")], cwd=TOOLS)+check = json.loads(r.stdout.strip().splitlines()[-1]); check["step"] = step_name+json.dump(check, open(os.path.join(OUT, f"docs/verify/{slug}-check.json"), "w"), indent=1)+pub = dict(spec); pub["crops"] = [["datasheet p.3 render at 100 dpi", crops100[0][1]], ["datasheet p.10 render at 100 dpi", crops100[1][1]]]+json.dump(pub, open(os.path.join(OUT, f"provenance/tools/{slug}-overlay-spec.json"), "w"), indent=1)+print("overlay all_pass:", check["all_pass"], [(c["dim"], c["model_mm"], c["pass"]) for c in check["checks"]])+if not check["all_pass"]:+ sys.exit(2)++# ---------- land: KiCad pads (used) vs KEMET Table 3 density level B ----------+r = run([PY, "-I", os.path.join(TOOLS, "landcheck.py"), slug, os.path.join(OUT, "land-pattern"), os.path.join(OUT, fp_file)], cwd=TOOLS)+land = json.loads(r.stdout.strip().splitlines()[-1])+json.dump(land, open(os.path.join(OUT, "land-pattern/land-check.json"), "w"), indent=1)++# ---------- GLB + thumbnails (step2glb service) ----------+glb_path = os.path.join(OUT, "package-reference.glb")+g = subprocess.run(["step2glb", "convert", step_path, "-o", glb_path], capture_output=True, text=True)+print("step2glb exit", g.returncode)+assert g.returncode == 0 and os.path.getsize(glb_path) > 1000+def thumb(src, out, wh=None):+ extra = ["--width", str(wh[0]), "--height", str(wh[1])] if wh else []+ return subprocess.run(["step2glb", "thumbnail", src, "-o", out] + extra, capture_output=True, text=True).returncode+th = [thumb(step_path, os.path.join(OUT, "docs/hero.png"), (1600, 1000)), thumb(step_path, os.path.join(OUT, "docs/models/step-plain.png"))]+print("thumbs", th)++# ---------- LODs ----------+run([PY, "-I", os.path.join(TOOLS, "lod.py"), step_path, MPN, OUT, step_name], cwd=TOOLS)+man = json.load(open(os.path.join(OUT, "lod/manifest.json")))+man["purpose"] = "clean board-use component models: no footprint pads, outlines, labels or silkscreen; no marking (KEMET supplies C0G parts unmarked)"+man["generator"] = "lod.py (OCCT via cadquery-ocp)"+json.dump(man, open(os.path.join(OUT, "lod/manifest.json"), "w"), indent=1)++for t in ("build.py", "parts.py", "repose_step.py", "ortho.py", "overlay_chip.py", "landcheck.py", "lod.py", "records.py", "finish.py", "push.sh", "verify_live.py"):+ if os.path.exists(os.path.join(TOOLS, t)):+ shutil.copyfile(os.path.join(TOOLS, t), os.path.join(OUT, "provenance/tools", t))+json.dump({"step": step_name, "orig": orig_name, "fp": fp_file, "sym": sym_file, "check": check, "land": land, "thumbs": th, "date": TODAY},+ open(os.path.join(ROOT, "work", f"{slug}-build.json"), "w"), indent=1)+print("built", OUT)
provenance/tools/c1812c124j1gactu-overlay-spec.jsonadded+49@@ -0,0 +1,49 @@+{+ "label": "C1812C124J1GACTU",+ "mpn": "C1812C124J1GACTU",+ "L": [+ 4.5,+ 0.3+ ],+ "W": [+ 3.2,+ 0.3+ ],+ "T": [+ 1.4,+ 0.15+ ],+ "band_top": [+ 0.25,+ 0.95+ ],+ "band_bot": [+ 0.25,+ 0.95+ ],+ "band_top_name": "B (band width), top",+ "band_bot_name": "B (band width), bottom",+ "ppmm": 100,+ "source": "KEMET C1003_C0G 2/20/2025 p.3 (1812) and p.10 (thickness code GH)",+ "crops": [+ [+ "datasheet p.3 render at 100 dpi",+ [+ 40,+ 330,+ 810,+ 770+ ]+ ],+ [+ "datasheet p.10 render at 100 dpi",+ [+ 40,+ 100,+ 610,+ 960+ ]+ ]+ ],+ "marking": "none (C0G is supplied unmarked, p.15)"+}
provenance/tools/finish.pyadded+49@@ -0,0 +1,49 @@+"""Fusion .lbr, Altium SchLib/PcbLib, hero GLB and the plain-STEP thumbnail for one page.++usage: finish.py <slug> (after build.py and a first records.py run, which writes page.json)+"""+import sys, os, json, subprocess, hashlib, shutil+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))+from parts import PARTS+ROOT = "/home/adom/project/uta-dynos/work/pages/_build"+HERO_PY = os.path.join(ROOT, "component-hero/skills/component-hero/scripts/hero_glb.py")+slug = sys.argv[1]+P = next(p for p in PARTS if p["slug"] == slug)+OUT = os.path.join(ROOT, "pages", slug); MPN = P["mpn"]+B = json.load(open(os.path.join(ROOT, "work", f"{slug}-build.json")))+sha = lambda f: hashlib.sha256(open(os.path.join(OUT, f), "rb").read()).hexdigest()+def run(cmd, cwd=OUT):+ r = subprocess.run(cmd, capture_output=True, text=True, cwd=cwd)+ if r.returncode != 0:+ print("FAILED", cmd, r.stdout[-1500:], r.stderr[-1500:]); sys.exit(1)+ return (r.stdout + r.stderr).strip()+rec = {}+# plain STEP thumbnail for models.json+run(["step2glb", "thumbnail", B["step"], "-o", "docs/models/step-plain.png"])+# Fusion / EAGLE+o = run(["adom-lbr", "generate", "--sym", B["sym"], "--fp", B["fp"], "--part-metadata", "page.json", "-o", f"{MPN}.lbr"])+_l = run(["adom-lbr", "lint", f"{MPN}.lbr"]); lint = json.loads(_l[_l.index("{"):_l.rindex("}") + 1])+assert lint["passed"], lint+rec["lbr"] = {"file": f"{MPN}.lbr", "tool": "adom-lbr " + run(["adom-lbr", "--version"]).split()[-1],+ "command": f"adom-lbr generate --sym {B['sym']} --fp {B['fp']} --part-metadata page.json -o {MPN}.lbr",+ "lint": {"passed": lint["passed"], "errors": len(lint.get("errors", [])), "warnings": len(lint.get("warnings", []))}, "sha256": sha(f"{MPN}.lbr")}+# Altium: hub JSON beside the STEP so the PcbLib embeds the body+os.makedirs(os.path.join(OUT, "altium"), exist_ok=True)+o1 = run(["adom-lbr", "import-kicad", "--sym", B["sym"], "--fp", B["fp"], "--mpn", MPN, "--out", f"{MPN}.adom-lbr.json"])+o2 = run(["adom-lbr", "assemble-altium", "--no-dedupe", "--out", f"altium/{MPN}", f"{MPN}.adom-lbr.json"])+rep = json.loads(o2[o2.index("{"):o2.rindex("}") + 1])+assert rep["status"] == "ok" and rep["models"]["embedded"] == 1, rep+rec["altium"] = {"files": [f"altium/{MPN}.SchLib", f"altium/{MPN}.PcbLib", f"{MPN}.adom-lbr.json"],+ "commands": [f"adom-lbr import-kicad --sym {B['sym']} --fp {B['fp']} --mpn {MPN} --out {MPN}.adom-lbr.json",+ f"adom-lbr assemble-altium --no-dedupe --out altium/{MPN} {MPN}.adom-lbr.json"],+ "report": rep, "hints": [l for l in o1.splitlines() if l.startswith("HINT")],+ "sha256": {f: sha(f) for f in (f"altium/{MPN}.SchLib", f"altium/{MPN}.PcbLib", f"{MPN}.adom-lbr.json")}}+# hero GLB (component-hero standard)+o = run(["/home/adom/.venvs/cq/bin/python", "-I", HERO_PY, "package-reference.glb", B["fp"], "page.json", f"{slug}-hero.glb", "--png", "docs/verify/hero-preview.png"])+print(o)+assert "align OK" in o, o+rec["hero"] = {"file": f"{slug}-hero.glb", "tool": "adom/component-hero skills/component-hero/scripts/hero_glb.py",+ "tool_sha256": hashlib.sha256(open(HERO_PY, "rb").read()).hexdigest(),+ "inputs": ["package-reference.glb", B["fp"], "page.json"], "result": o.splitlines()[-1], "sha256": sha(f"{slug}-hero.glb")}+json.dump(rec, open(os.path.join(ROOT, "work", f"{slug}-finish.json"), "w"), indent=1)+print("finished", slug)
provenance/tools/landcheck.pyadded+117@@ -0,0 +1,117 @@+"""Compare the KiCad footprint pads with the manufacturer's recommended land pattern.++usage: landcheck.py <slug> <outdir>+Reads parts.PARTS[slug]["land"] and the page's footprint (KiCad library geometry).+Writes <outdir>/land-pattern.png (true-scale drawing + table) and+<outdir>/land-pattern-icon.png (256 px square thumbnail), prints a JSON result.++Drawing: grey = part body at datasheet nominal, teal = KiCad pads (copper),+amber dashed = the maker's land at the middle of its printed range (or its+printed nominal), amber band = the range the maker allows for the inner and+outer pad edges where the maker prints a range.+"""+import sys, os, json, re, glob+from PIL import Image, ImageDraw, ImageFont+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))+from parts import PARTS++slug, outdir = sys.argv[1], sys.argv[2]+P = next(p for p in PARTS if p["slug"] == slug)+L = P["land"]+page = os.path.dirname(outdir.rstrip("/"))+fpf = sys.argv[3] if len(sys.argv) > 3 else glob.glob(os.path.join(page, "*.kicad_mod"))[0]+fp = open(fpf).read()+pads = [tuple(map(float, m)) for m in re.findall(r'\(pad "\d" smd (?:roundrect|rect)\s*\(at ([-\d.]+) ([-\d.]+)\)\s*\(size ([\d.]+) ([\d.]+)\)', fp)]+_m = re.search(r"roundrect_rratio ([\d.]+)", fp); RR = float(_m.group(1)) if _m else 0.0+x, _, w, h = pads[1]+k = {"gap": round(2 * x - w, 3), "len": round(w, 3), "wid": round(h, 3), "overall": round(2 * x + w, 3)}++def mid(v): return (v[0] + v[1]) / 2 if isinstance(v, list) else v+rows = []+if L["kind"] in ("range", "tol"):+ rng = {"gap": L["gap_a"], "len": L["len_b"], "wid": L["wid_c"]}+ rng["overall"] = L.get("overall") or [round(L["gap_a"][0] + 2 * L["len_b"][0], 3), round(L["gap_a"][1] + 2 * L["len_b"][1], 3)]+ derived_overall = not L.get("overall")+ for key, name in (("gap", "gap between lands"), ("len", "land length"), ("wid", "land width"),+ ("overall", "overall length (a + 2b, derived)" if derived_overall else "overall length")):+ lo, hi = rng[key]; v = k[key]+ ok = lo - 1e-6 <= v <= hi + 1e-6+ dev = 0 if ok else round(v - hi if v > hi else v - lo, 3)+ rows.append({"dim": name, "kicad_mm": v, "maker_min": lo, "maker_max": hi, "within": ok, "outside_by_mm": dev})+ ref = {kk: mid(rng[kk]) for kk in rng}+ if L["kind"] == "tol":+ ref = {"gap": L["nominal"]["gap_a"], "len": L["nominal"]["len_b"], "wid": L["nominal"]["wid_c"], "overall": L["nominal"]["overall"]}+else:+ ref = {"gap": L["gap_a"], "len": L["len_b"], "wid": L["wid_c"], "overall": L["overall"]}+ for key, name in (("gap", "gap between lands"), ("len", "land length"), ("wid", "land width"), ("overall", "overall length")):+ rows.append({"dim": name, "kicad_mm": k[key], "maker_nominal": ref[key], "difference_mm": round(k[key] - ref[key], 3)})++# ---------- drawing ----------+body = P["body_mm"]+span = max(k["overall"], ref["overall"] if isinstance(ref["overall"], float) else 0) + 0.4+PP = 560 / span+W_, H_ = 1000, int(max(k["wid"], ref["wid"], body["y_mm"]) * PP + 120)+img = Image.new("RGBA", (W_, H_), (255, 255, 255, 255))+d = ImageDraw.Draw(img, "RGBA")+cx, cy = W_ / 2, H_ / 2 + 10+def R(x0, y0, x1, y1): return [cx + x0 * PP, cy - y1 * PP, cx + x1 * PP, cy - y0 * PP]+# allowed edge bands (range kinds)+if L["kind"] in ("range", "tol"):+ g0, g1 = L["gap_a"]; b0, b1 = L["len_b"]; c1 = L["wid_c"][1]+ for s in (-1, 1):+ for a, b in ((g0 / 2, g1 / 2), (g0 / 2 + b0, g1 / 2 + b1)):+ xa, xb = sorted((s * a, s * b))+ d.rectangle(R(xa, -c1 / 2, xb, c1 / 2), fill=(255, 190, 70, 70))+# body+d.rectangle(R(-body["x_mm"] / 2, -body["y_mm"] / 2, body["x_mm"] / 2, body["y_mm"] / 2), outline=(110, 110, 110, 255), width=2)+# KiCad pads, rounded rect (the footprint's own roundrect_rratio)+for px, py, pw, ph in pads:+ rr = RR * min(pw, ph) * PP+ d.rounded_rectangle(R(px - pw / 2, py - ph / 2, px + pw / 2, py + ph / 2), radius=rr, fill=(0, 150, 150, 110), outline=(0, 120, 120, 255), width=2)+# maker land, dashed amber+def dashed(box, col, wdt=3):+ xa, ya, xb, yb = box+ for (p, q) in (((xa, ya), (xb, ya)), ((xb, ya), (xb, yb)), ((xb, yb), (xa, yb)), ((xa, yb), (xa, ya))):+ n = int(max(abs(q[0] - p[0]), abs(q[1] - p[1])) / 7) or 1+ for i in range(0, n, 2):+ t0, t1 = i / n, min(1, (i + 1) / n)+ d.line([(p[0] + (q[0] - p[0]) * t0, p[1] + (q[1] - p[1]) * t0), (p[0] + (q[0] - p[0]) * t1, p[1] + (q[1] - p[1]) * t1)], fill=col, width=wdt)+for s in (-1, 1):+ a, b = ref["gap"] / 2, ref["gap"] / 2 + ref["len"]+ xa, xb = sorted((s * a, s * b))+ dashed(R(xa, -ref["wid"] / 2, xb, ref["wid"] / 2), (200, 110, 0, 255))+icon_box = (int(cx - 0.5 * (k["overall"] + 0.25) * PP), int(cy - 0.5 * (k["overall"] + 0.25) * PP))+icon_side = int((k["overall"] + 0.25) * PP)+padded = Image.new("RGBA", (W_ + 2 * icon_side, H_ + 2 * icon_side), (255, 255, 255, 255)); padded.paste(img, (icon_side, icon_side))+icon = padded.crop((icon_box[0] + icon_side, icon_box[1] + icon_side, icon_box[0] + 2 * icon_side, icon_box[1] + 2 * icon_side)).convert("RGB").resize((256, 256), Image.LANCZOS)+mask = Image.new("L", (256, 256), 0); ImageDraw.Draw(mask).rounded_rectangle([0, 0, 255, 255], radius=28, fill=255)+ic = Image.new("RGBA", (256, 256), (0, 0, 0, 0)); ic.paste(icon, (0, 0), mask)+ic.save(os.path.join(outdir, "land-pattern-icon.png"))++f = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 15)+fb = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 17)+lines = []+for r in rows:+ if "within" in r:+ lines.append((f"{r['dim']}: KiCad {r['kicad_mm']:.2f} maker {r['maker_min']:.2f} to {r['maker_max']:.2f} "+ + ("inside" if r["within"] else f"outside by {r['outside_by_mm']:+.2f}"), (0, 120, 40) if r["within"] else (190, 90, 0)))+ else:+ lines.append((f"{r['dim']}: KiCad {r['kicad_mm']:.2f} maker {r['maker_nominal']:.2f} difference {r['difference_mm']:+.2f}", (40, 40, 40)))+sheet = Image.new("RGB", (W_, H_ + 100 + 24 * len(lines) + 70), (255, 255, 255))+sd = ImageDraw.Draw(sheet)+sd.text((14, 10), f"{P['mpn']}: KiCad {P['fp']} pads vs maker's land, true scale", fill=(0, 0, 0), font=fb)+sheet.paste(img.convert("RGB"), (0, 40))+y = H_ + 46+sd.text((14, y), "grey = body (nominal) teal = KiCad pads amber dashed = maker's land (middle of range or nominal)", fill=(60, 60, 60), font=f); y += 22+sd.text((14, y), "amber band = where the maker allows the inner and outer pad edges (range sheets only)", fill=(60, 60, 60), font=f); y += 26+for t, c in lines:+ sd.text((14, y), t, fill=c, font=f); y += 24+import textwrap+for ln in textwrap.wrap("Source: " + L["source"], 115):+ sd.text((14, y + 4), ln, fill=(90, 90, 90), font=f); y += 20+sheet.save(os.path.join(outdir, "land-pattern.png"))++res = {"footprint": os.path.basename(fpf), "kicad_library_footprint": P["fp"], "kicad": k, "maker_reference": ref,+ "maker_source": L["source"], "maker_letters": L["letters"], "rows": rows,+ "all_within": all(r.get("within", True) for r in rows) if L["kind"] != "nominal" else None}+print(json.dumps(res))
provenance/tools/lod.pyadded+199@@ -0,0 +1,199 @@+"""Board-use LOD GLBs (high, medium, low, super-low) for a small two-terminal chip.++Input: the page's board-use STEP (Z-up, mm, seated on z=0, centred).+Output (in <outdir>/lod/): high.glb medium.glb low.glb super-low.glb, a 400x300+PNG preview of each rendered from that GLB's own triangles, manifest.json+(schema adom/component-lod@1).++Every level: Z-up, metres, the same origin and pose as the STEP (no recentring,+no rotation), primitives batched by material (one per material), the STEP's own+colours. No footprint, pad outline, label or silkscreen helpers exist in the+source, so nothing is removed. high/medium/low are OCCT tessellations of the+exact B-rep at decreasing deflection; super-low replaces each material group by+its bounding box per side of the part (terminals, body, overcoat), which keeps+the silhouette, the terminal bands and the seating plane.++usage: lod.py part.step MPN outdir [source_label]+"""+import sys, os, json, struct, hashlib+import numpy as np+from PIL import Image, ImageDraw+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))+import ortho+from OCP.BRepMesh import BRepMesh_IncrementalMesh+from OCP.BRepTools import BRepTools++step, mpn, outdir = sys.argv[1], sys.argv[2], sys.argv[3]+src_label = sys.argv[4] if len(sys.argv) > 4 else os.path.basename(step)+# optional: the unmarked STEP; colours present only in the marked source are the marking,+# kept in high and medium, dropped in low and super-low (too small to read there)+plain_step = sys.argv[5] if len(sys.argv) > 5 else None+MARK_COLS = set()+lod_dir = os.path.join(outdir, "lod"); os.makedirs(lod_dir, exist_ok=True)++LEVELS = [("high", 0.0005, 0.1), ("medium", 0.004, 0.35), ("low", 0.02, 0.9)]++def tris_at(lin, ang):+ # ortho.load_tris meshes with fixed params; clean and remesh by patching BRepMesh call+ orig = ortho.BRepMesh_IncrementalMesh+ def mesh(shp, _l, rel, _a, par):+ BRepTools.Clean_s(shp)+ return orig(shp, lin, False, ang, True)+ ortho.BRepMesh_IncrementalMesh = mesh+ try:+ return ortho.load_tris(step)+ finally:+ ortho.BRepMesh_IncrementalMesh = orig++def boxes_from(tris):+ groups = {}+ for p, c in tris:+ groups.setdefault(c, []).append(p)+ allp = np.concatenate([p for p, _ in tris]); xmax = allp[:, 0].max()+ endc = {}+ for p, c in tris:+ if np.allclose(p[:, 0], xmax, atol=1e-4): endc[c] = endc.get(c, 0) + 1+ tcol = max(endc, key=endc.get)+ boxes = []+ for c, ps in groups.items():+ T = np.stack(ps)+ crosses = c != tcol and ((T[:, :, 0].min(1) < -1e-6) & (T[:, :, 0].max(1) > 1e-6)).any() # terminals: always two boxes+ P = T.reshape(-1, 3)+ parts = [P] if crosses else [P[P[:, 0] < 0], P[P[:, 0] > 0]]+ for Q in parts:+ if len(Q):+ boxes.append([Q.min(0), Q.max(0), c])+ tin = min(min(abs(lo[0]), abs(hi[0])) for lo, hi, c in boxes if c == tcol)+ # the body box reaches the same top as the overcoat box: coplanar tops z-fight, so stop the body at the overcoat's underside+ dark = min({c for _, _, c in boxes}, key=lambda c: sum(c))+ dk = [(lo, hi) for lo, hi, c in boxes if c == dark]+ if dark != tcol and dk:+ dlo = min(lo[2] for lo, hi in dk); dhi = max(hi[2] for lo, hi in dk)+ for b in boxes:+ if b[2] not in (dark, tcol) and b[1][2] >= dhi - 1e-6:+ b[1] = b[1].copy(); b[1][2] = dlo+ out = []+ for lo, hi, c in boxes:+ if c != tcol:+ lo = lo.copy(); hi = hi.copy(); lo[0] = max(lo[0], -tin); hi[0] = min(hi[0], tin)+ v = np.array([[x, y, z] for x in (lo[0], hi[0]) for y in (lo[1], hi[1]) for z in (lo[2], hi[2])])+ for a_, b_, cc, d in [(0, 1, 3, 2), (4, 6, 7, 5), (0, 4, 5, 1), (2, 3, 7, 6), (0, 2, 6, 4), (1, 5, 7, 3)]:+ out.append((v[[a_, b_, cc]], c)); out.append((v[[a_, cc, d]], c))+ return out++def write_glb(tris, path, name):+ mats = []; groups = {}+ for p, c in tris:+ if c not in groups:+ groups[c] = []; mats.append(c)+ groups[c].append(p)+ bin_ = b""; views = []; accs = []; prims = []+ def add_view(data, target):+ nonlocal bin_+ off = len(bin_); bin_ += data+ while len(bin_) % 4: bin_ += b"\0"+ views.append({"buffer": 0, "byteOffset": off, "byteLength": len(data), "target": target})+ return len(views) - 1+ decoded = 0; ntri = 0+ for mi, c in enumerate(mats):+ P = np.concatenate(groups[c]).reshape(-1, 3, 3)+ n = np.cross(P[:, 1] - P[:, 0], P[:, 2] - P[:, 0]); n /= np.linalg.norm(n, axis=1, keepdims=True) + 1e-30+ pos = (P.reshape(-1, 3) / 1000.0).astype(np.float32) # mm -> metres+ nor = np.repeat(n, 3, axis=0).astype(np.float32)+ # weld identical (position, normal) vertices+ key = np.concatenate([np.round(pos, 9), np.round(nor, 5)], 1)+ uniq, inv = np.unique(key, axis=0, return_inverse=True)+ upos = uniq[:, :3].astype(np.float32); unor = uniq[:, 3:].astype(np.float32)+ idx = inv.astype(np.uint16 if len(uniq) < 65535 else np.uint32).ravel()+ vp = add_view(upos.tobytes(), 34962); vn = add_view(unor.tobytes(), 34962); vi = add_view(idx.tobytes(), 34963)+ accs.append({"bufferView": vp, "componentType": 5126, "count": len(upos), "type": "VEC3",+ "min": upos.min(0).tolist(), "max": upos.max(0).tolist()})+ accs.append({"bufferView": vn, "componentType": 5126, "count": len(unor), "type": "VEC3"})+ accs.append({"bufferView": vi, "componentType": 5123 if idx.dtype == np.uint16 else 5125, "count": len(idx), "type": "SCALAR"})+ prims.append({"attributes": {"POSITION": len(accs) - 3, "NORMAL": len(accs) - 2}, "indices": len(accs) - 1, "material": mi})+ decoded += upos.nbytes + unor.nbytes + idx.nbytes; ntri += len(idx) // 3+ gl = {"asset": {"version": "2.0", "generator": "capencoder passives lod.py (OCCT tessellation)"},+ "scene": 0, "scenes": [{"nodes": [0]}],+ "nodes": [{"name": name, "mesh": 0, "extras": {"adomUpAxis": "Z", "units": "metres"}}],+ "meshes": [{"name": name, "primitives": prims}],+ "materials": [{"name": f"mat_{i}", "pbrMetallicRoughness": {"baseColorFactor": [*c, 1.0], "metallicFactor": 0.0, "roughnessFactor": 0.6}} for i, c in enumerate(mats)],+ "accessors": accs, "bufferViews": views, "buffers": [{"byteLength": len(bin_)}]}+ js = json.dumps(gl, separators=(",", ":")).encode()+ while len(js) % 4: js += b" "+ glb = struct.pack("<III", 0x46546C67, 2, 12 + 8 + len(js) + 8 + len(bin_)) + struct.pack("<II", len(js), 0x4E4F534A) + js + struct.pack("<II", len(bin_), 0x004E4942) + bin_+ open(path, "wb").write(glb)+ return {"decodedBufferBytes": decoded, "triangles": ntri, "primitives": len(prims)}++def read_glb_tris(path):+ b = open(path, "rb").read(); n = struct.unpack("<I", b[12:16])[0]+ j = json.loads(b[20:20 + n]); binc = b[20 + n + 8:]+ def acc(i, dt, w):+ a = j["accessors"][i]; v = j["bufferViews"][a["bufferView"]]+ return np.frombuffer(binc, dt, a["count"] * w, v["byteOffset"]).reshape(-1, w) if w > 1 else np.frombuffer(binc, dt, a["count"], v["byteOffset"])+ out = []+ for p in j["meshes"][0]["primitives"]:+ pos = acc(p["attributes"]["POSITION"], np.float32, 3) * 1000.0+ ia = j["accessors"][p["indices"]]+ idx = acc(p["indices"], np.uint16 if ia["componentType"] == 5123 else np.uint32, 1)+ col = tuple(j["materials"][p["material"]]["pbrMetallicRoughness"]["baseColorFactor"][:3])+ for t in idx.reshape(-1, 3):+ out.append((pos[t].astype(float), col))+ return out++ortho.VIEWS["iso"] = (tuple(np.array([-0.7071, 0.7071, 0])), tuple(np.array([-0.4082, -0.4082, 0.8165])), tuple(np.array([0.5774, 0.5774, 0.5774])))+def preview(path, png):+ tris = read_glb_tris(path)+ allp = np.concatenate([t[0] for t in tris]); ext = np.abs(allp).max()+ img = ortho.render(tris, "iso", 300 / (3.3 * ext), size=(400, 300))+ # painter's sorting by triangle centroid can bury faces in the top plane (and the marking 5 um above it);+ # faces in those planes cannot be hidden in a view from above, so draw them again, plane by plane, bottom first+ near = lambda c: any(max(abs(a - b) for a, b in zip(c, m)) < 2e-3 for m in MARK_COLS)+ zt = allp[:, 2].max()+ marked = [t for t in tris if MARK_COLS and near(t[1])]+ top = zt - 0.005 if marked else zt+ plane = [t for t in tris if not (MARK_COLS and near(t[1])) and np.abs(t[0][:, 2] - top).max() < 1e-4]+ for layer in (plane, [t for t in marked if t[0][:, 2].min() > top + 1e-4]):+ if layer:+ img.alpha_composite(ortho.render(layer, "iso", 300 / (3.3 * ext), size=(400, 300)))+ bg = Image.new("RGBA", img.size, (48, 57, 68, 255)); bg.alpha_composite(img); bg.convert("RGB").save(png)++sha = lambda f: hashlib.sha256(open(f, "rb").read()).hexdigest()+levels = []+src_tris = None+if plain_step:+ MARK_COLS = {c for _, c in ortho.load_tris(step)} - {c for _, c in ortho.load_tris(plain_step)}+ print("marking colours", MARK_COLS)+for name, lin, ang in LEVELS + [("super-low", None, None)]:+ if name == "super-low":+ tris = boxes_from(src_tris)+ else:+ tris = tris_at(lin, ang)+ if name == "high":+ src_tris = [t for t in tris if t[1] not in MARK_COLS]+ if name == "low" and MARK_COLS:+ tris = [t for t in tris if t[1] not in MARK_COLS]+ path = os.path.join(lod_dir, f"{name}.glb")+ st = write_glb(tris, path, mpn)+ back = read_glb_tris(path); P = np.concatenate([t[0] for t in back])+ png = os.path.join(lod_dir, f"{name}.png"); preview(path, png)+ rec = {"level": name, "path": f"lod/{name}.glb", "sha256": sha(path), "sizeBytes": os.path.getsize(path), **st,+ "bounds": {"min": (P.min(0) / 1000).round(7).tolist(), "max": (P.max(0) / 1000).round(7).tolist()},+ "approximation": ({"method": "OCCT BRepMesh_IncrementalMesh", "linearDeflectionMm": lin, "angularDeflectionRad": ang}+ if lin else {"method": "per-material bounding boxes split at the part centre (terminals, body, overcoat)"}),+ "detailVisibility": ({"virtualMpn": False, "manufacturerMarking": name in ("high", "medium"),+ "note": "datasheet marking code shown on the top coat" if name in ("high", "medium") else "datasheet marking omitted: unreadable at this level"}+ if MARK_COLS else {"virtualMpn": False, "note": "no marking: the datasheet specifies none"}),+ "review": "Candidate: rendered preview checked by AI (silhouette, terminals, seat z=0); not human-reviewed",+ "preview": {"path": f"lod/{name}.png", "sha256": sha(png), "sizeBytes": os.path.getsize(png), "width": 400, "height": 300,+ "view": "iso", "sourceLevelSha256": sha(path)}}+ levels.append(rec)+ print(name, st, rec["bounds"])+manifest = {"schema": "adom/component-lod@1", "mpn": mpn, "upAxis": "Z", "units": "metres",+ "status": "candidate (AI-checked, not human-reviewed)",+ "purpose": "clean board-use component models: no footprint pads, outlines, labels or silkscreen; no marking (none per datasheet)",+ "source": {"path": src_label, "sha256": sha(step)},+ "sourceToDisplay": "uniform scale 0.001 (mm to m); no rotation, no translation; origin = footprint origin, seat z = 0",+ "generator": "lod.py (capencoder passives, OCCT via cadquery-ocp)",+ "removedHelpers": [], "levels": levels,+ "hints": ["All four levels share pose and units; super-low is boxes per material group, chosen for board overviews."]}+json.dump(manifest, open(os.path.join(lod_dir, "manifest.json"), "w"), indent=1)
provenance/tools/ortho.pyadded+148@@ -0,0 +1,148 @@+"""Orthographic renderer for small component STEPs (face colours kept).++render(step, view, ppmm) -> PIL RGBA image, model origin at the image centre.+views: top (look down -Z, +X right, +Y up), bottom (look up +Z, +X right, +Y down,+i.e. the part flipped about X), front (look +Y, +X right, +Z up),+end (look -X, +Y right... from +X end: -Y right, +Z up).+"""+import sys+import numpy as np+from PIL import Image, ImageDraw+from OCP.STEPCAFControl import STEPCAFControl_Reader+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+from OCP.TopExp import TopExp_Explorer+from OCP.TopAbs import TopAbs_FACE, TopAbs_REVERSED, TopAbs_SOLID+from OCP.TopoDS import TopoDS+from OCP.Quantity import Quantity_Color+from OCP.BRepMesh import BRepMesh_IncrementalMesh+from OCP.BRep import BRep_Tool+from OCP.TopLoc import TopLoc_Location+++def load_tris(path):+ doc = TDocStd_Document(TCollection_ExtendedString("x"))+ r = STEPCAFControl_Reader(); r.SetColorMode(True); r.SetNameMode(True)+ r.ReadFile(path); r.Transfer(doc)+ st = XCAFDoc_DocumentTool.ShapeTool_s(doc.Main())+ ct = XCAFDoc_DocumentTool.ColorTool_s(doc.Main())+ labs = TDF_LabelSequence(); st.GetFreeShapes(labs)+ out = []+ leaves = []+ def walk(lab, loc, pcol):+ c = Quantity_Color(); col = pcol+ for t in (XCAFDoc_ColorType.XCAFDoc_ColorSurf, XCAFDoc_ColorType.XCAFDoc_ColorGen):+ if ct.GetColor_s(lab, t, c):+ col = (c.Red(), c.Green(), c.Blue()); break+ if st.IsReference_s(lab):+ ref = type(lab)(); st.GetReferredShape_s(lab, ref)+ walk(ref, loc.Multiplied(st.GetLocation_s(lab)), col); return+ kids = TDF_LabelSequence(); st.GetComponents_s(lab, kids)+ if kids.Length() > 0:+ for i in range(1, kids.Length() + 1): walk(kids.Value(i), loc, col)+ return+ # colours stored on sub-shape labels (e.g. one solid per glyph of a text compound)+ sub = TDF_LabelSequence(); st.GetSubShapes_s(lab, sub)+ if col is None and sub.Length() > 0:+ # a part whose colours sit on its solids and faces (the OCCT writer moves a part colour onto+ # each solid): draw every solid once in its solid colour; face colours still win below+ subs = [(st.GetShape_s(sub.Value(si)), sub.Value(si)) for si in range(1, sub.Length() + 1)]+ if any(s.ShapeType() == TopAbs_SOLID for s, _ in subs) and all(s.ShapeType() in (TopAbs_SOLID, TopAbs_FACE) for s, _ in subs):+ whole = st.GetShape_s(lab); ex = TopExp_Explorer(whole, TopAbs_SOLID)+ while ex.More():+ so = ex.Current(); scol = None+ for s, sl in subs:+ if s.ShapeType() == TopAbs_SOLID and s.IsSame(so):+ sc = Quantity_Color()+ for t in (XCAFDoc_ColorType.XCAFDoc_ColorSurf, XCAFDoc_ColorType.XCAFDoc_ColorGen):+ if ct.GetColor_s(sl, t, sc):+ scol = (sc.Red(), sc.Green(), sc.Blue()); break+ leaves.append((so.Moved(loc), scol)); ex.Next()+ return+ for si in range(1, sub.Length() + 1):+ sl = sub.Value(si); sc = Quantity_Color(); scol = None+ for t in (XCAFDoc_ColorType.XCAFDoc_ColorSurf, XCAFDoc_ColorType.XCAFDoc_ColorGen):+ if ct.GetColor_s(sl, t, sc):+ scol = (sc.Red(), sc.Green(), sc.Blue()); break+ leaves.append((st.GetShape_s(sl).Moved(loc), scol))+ return+ leaves.append((st.GetShape_s(lab).Moved(loc), col))+ for li in range(1, labs.Length() + 1):+ walk(labs.Value(li), TopLoc_Location(), None)+ for shp, pcol in leaves:+ BRepMesh_IncrementalMesh(shp, 0.002, False, 0.2, True)+ e = TopExp_Explorer(shp, TopAbs_FACE)+ while e.More():+ f = TopoDS.Face_s(e.Current())+ c = Quantity_Color(); col = None+ for t in (XCAFDoc_ColorType.XCAFDoc_ColorSurf, XCAFDoc_ColorType.XCAFDoc_ColorGen):+ if ct.GetColor(f, t, c):+ col = (c.Red(), c.Green(), c.Blue()); break+ if col is None:+ col = pcol or (0.6, 0.6, 0.6)+ loc = TopLoc_Location()+ tri = BRep_Tool.Triangulation_s(f, loc)+ if tri is not None:+ trsf = loc.Transformation()+ pts = np.array([(lambda p: (p.X(), p.Y(), p.Z()))(tri.Node(i).Transformed(trsf)) for i in range(1, tri.NbNodes() + 1)])+ rev = f.Orientation() == TopAbs_REVERSED+ for i in range(1, tri.NbTriangles() + 1):+ a, b, cc = tri.Triangle(i).Get()+ if rev:+ b, cc = cc, b+ out.append((pts[[a - 1, b - 1, cc - 1]], col))+ e.Next()+ return out+++VIEWS = {+ # (right axis vector, up axis vector, toward-viewer vector)+ "top": ((1, 0, 0), (0, 1, 0), (0, 0, 1)),+ "bottom": ((1, 0, 0), (0, -1, 0), (0, 0, -1)),+ "front": ((1, 0, 0), (0, 0, 1), (0, -1, 0)),+ "end": ((0, 1, 0), (0, 0, 1), (1, 0, 0)),+}+++def render(tris, view, ppmm, size=None, shade=True, ss=3):+ R, U, V = [np.array(v, float) for v in VIEWS[view]]+ allp = np.concatenate([t[0] for t in tris])+ ext = max(np.abs(allp @ R).max(), np.abs(allp @ U).max())+ if size is None:+ size = (int(2 * ext * ppmm + 40), int(2 * ext * ppmm + 40))+ W, H = size[0] * ss, size[1] * ss+ img = Image.new("RGBA", (W, H), (0, 0, 0, 0))+ d = ImageDraw.Draw(img)+ items = []+ light = np.array([0.3, 0.4, 0.85]); light /= np.linalg.norm(light)+ for p, col in tris:+ n = np.cross(p[1] - p[0], p[2] - p[0])+ nn = np.linalg.norm(n)+ if nn == 0:+ continue+ n /= nn+ if n @ V <= 1e-6:+ continue # back face+ depth = (p @ V).mean()+ lv = 0.55 + 0.45 * abs(n @ (R * light[0] + U * light[1] + V * light[2])) if shade else 1.0+ rgb = tuple(int(255 * min(1, c * lv)) for c in col)+ xy = [(W / 2 + (q @ R) * ppmm * ss, H / 2 - (q @ U) * ppmm * ss) for q in p]+ items.append((depth, xy, rgb))+ items.sort(key=lambda x: x[0])+ for _, xy, rgb in items:+ d.polygon(xy, fill=rgb + (255,), outline=rgb + (255,))+ return img.resize(size, Image.LANCZOS)+++if __name__ == "__main__":+ tris = load_tris(sys.argv[1])+ out = sys.argv[2]+ ppmm = float(sys.argv[3]) if len(sys.argv) > 3 else 300+ imgs = [render(tris, v, ppmm) for v in ("top", "bottom", "front", "end")]+ w = sum(i.width for i in imgs); h = max(i.height for i in imgs)+ sheet = Image.new("RGBA", (w, h), (255, 255, 255, 255)); x = 0+ for i in imgs:+ sheet.alpha_composite(i, (x, 0)); x += i.width+ sheet.save(out)
provenance/tools/overlay_chip.pyadded+159@@ -0,0 +1,159 @@+"""Prove a two-terminal chip model (MLCC / chip resistor) against its datasheet table.++The maker prints a dimension table with an unscaled sketch, so the overlay is made+against a true-scale drawing generated from the printed table (300 px/mm):+nominal outline solid black, tolerance limits dashed blue, the allowed range of+the terminal inner edge shaded amber. The model is rendered at the same scale+(50 % fill, red outline) on top. The numeric check measures the STEP itself.++usage: overlay_chip.py spec.json model.step out.png+spec: {"label","mpn","source","L":[nom,tol],"W":[nom,tol],"T":[nom,tol],+ (any of L/W/T may instead be {"nom":x or null,"min":a,"max":b} when the+ datasheet prints an asymmetric or max-only limit; a null nominal draws+ the printed maximum as the reference line),+ "band_top":[min,max],"band_bot":[min,max],"crops":[[png,[x0,y0,x1,y1]],...],+ "marking": "..."}+"""+import sys, json+import numpy as np+from PIL import Image, ImageDraw, ImageFont, ImageFilter+sys.path.insert(0, __file__.rsplit("/", 1)[0])+from ortho import load_tris, render+from OCP.STEPControl import STEPControl_Reader+from OCP.TopExp import TopExp_Explorer+from OCP.TopAbs import TopAbs_FACE+from OCP.TopoDS import TopoDS+from OCP.Bnd import Bnd_Box+from OCP.BRepBndLib import BRepBndLib+from OCP.BRepAdaptor import BRepAdaptor_Surface+from OCP.GeomAbs import GeomAbs_Plane++PP = 300.0+spec = json.load(open(sys.argv[1])); step = sys.argv[2]; out = sys.argv[3]+PP = float(spec.get("ppmm", PP)) # larger packages (0805) need a smaller scale to fit the 560 px panels+F = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"+font = ImageFont.truetype(F, 22); fsm = ImageFont.truetype(F, 18); fbig = ImageFont.truetype(F, 26)++# ---------- numeric measurement of the STEP ----------+tris = load_tris(step)+allp = np.concatenate([t[0] for t in tris])+x0, y0, z0 = allp.min(0); x1, y1, z1 = allp.max(0)+Lm, Wm, Tm = x1 - x0, y1 - y0, z1 - z0+# terminal colour = colour of triangles lying on the +X end face+endcols = {}+for p, c in tris:+ if np.allclose(p[:, 0], x1, atol=1e-4):+ endcols[c] = endcols.get(c, 0) + 1+tcol = max(endcols, key=endcols.get)+def band(zlev):+ xs = [p[:, 0] for p, c in tris if c == tcol and np.allclose(p[:, 2], zlev, atol=1e-4)]+ if not xs:+ return None+ xs = np.concatenate(xs)+ pos = xs[xs > 0]; neg = xs[xs < 0]+ return round(min(x1 - pos.min(), -x0 + neg.max() if len(neg) else 9), 3)+bt, bb = band(z1), band(z0)++def rng(v):+ if isinstance(v, dict):+ return (v["min"], v["max"])+ return (v[0] - v[1], v[0] + v[1]) if len(v) == 2 and v[1] < v[0] else tuple(v)+checks = []+def chk(name, val, lo, hi):+ ok = val is not None and lo - 1e-6 <= val <= hi + 1e-6+ checks.append((name, val, lo, hi, ok))+chk("L", round(Lm, 3), *rng(spec["L"]))+chk("W", round(Wm, 3), *rng(spec["W"]))+chk("T/H", round(Tm, 3), *rng(spec["T"]))+chk(spec.get("band_top_name", "terminal band, top"), bt, *spec["band_top"])+chk(spec.get("band_bot_name", "terminal band, bottom"), bb, *spec["band_bot"])+centred = bool(abs(x0 + x1) < 1e-3 and abs(y0 + y1) < 1e-3 and abs(z0) < 1e-3)+longx = bool(Lm > Wm)++# ---------- true-scale drawing + model per view ----------+def nlh(v):+ if isinstance(v, dict):+ lo, hi = v["min"], v["max"]+ return (v["nom"] if v.get("nom") is not None else hi), lo, hi+ return v[0], v[0] - v[1], v[0] + v[1]++def view_panel(view, A, B, bands, title):+ a_len, a_lo, a_hi = nlh(A); b_len, b_lo, b_hi = nlh(B)+ im = render(tris, view, PP, size=(560, 420))+ W_, H_ = im.size; cx = W_ / 2+ panel = Image.new("RGBA", (W_, H_), (255, 255, 255, 255))+ d = ImageDraw.Draw(panel)+ seat = view in ("front", "end")+ def rect(a, b, **kw):+ # a along horizontal, b along vertical; for side views the box sits on the seat line+ if seat:+ return [cx - a * PP / 2, H_ / 2 - b * PP, cx + a * PP / 2, H_ / 2]+ return [cx - a * PP / 2, H_ / 2 - b * PP / 2, cx + a * PP / 2, H_ / 2 + b * PP / 2]+ # allowed inner-edge range for the terminals (relative to nominal length)+ for (lo, hi, where) in bands:+ for s in (-1, 1):+ xa = cx + s * (a_len / 2 - hi) * PP; xb = cx + s * (a_len / 2 - lo) * PP+ if where == "full":+ ya, yb = rect(a_len, b_len)[1], rect(a_len, b_len)[3]+ elif where == "top":+ r_ = rect(a_len, b_len); ya, yb = r_[1] - 12, r_[1] + 12+ else:+ r_ = rect(a_len, b_len); ya, yb = r_[3] - 12, r_[3] + 12+ d.rectangle([min(xa, xb), ya, max(xa, xb), yb], fill=(255, 196, 80, 255))+ def dashed(box, col):+ xa, ya, xb, yb = box+ for (p, q) in (((xa, ya), (xb, ya)), ((xb, ya), (xb, yb)), ((xb, yb), (xa, yb)), ((xa, yb), (xa, ya))):+ n = int(max(abs(q[0] - p[0]), abs(q[1] - p[1])) / 8) or 1+ for i in range(0, n, 2):+ t0, t1 = i / n, min(1, (i + 1) / n)+ d.line([(p[0] + (q[0] - p[0]) * t0, p[1] + (q[1] - p[1]) * t0),+ (p[0] + (q[0] - p[0]) * t1, p[1] + (q[1] - p[1]) * t1)], fill=col, width=2)+ dashed(rect(a_lo, b_lo), (30, 90, 220))+ dashed(rect(a_hi, b_hi), (30, 90, 220))+ d.rectangle(rect(a_len, b_len), outline=(0, 0, 0), width=2)+ arr = np.asarray(im).copy(); alpha = arr[:, :, 3].copy(); arr[:, :, 3] = (alpha * 0.5).astype(np.uint8)+ panel.alpha_composite(Image.fromarray(arr))+ m = Image.fromarray(((alpha > 128) * 255).astype(np.uint8))+ edge = (np.asarray(m, int) - np.asarray(m.filter(ImageFilter.MinFilter(3)), int)) > 0+ red = np.zeros((H_, W_, 4), np.uint8); red[edge] = (220, 30, 30, 255)+ panel.alpha_composite(Image.fromarray(red))+ d = ImageDraw.Draw(panel); d.text((10, 8), title, fill=(0, 0, 0), font=fsm)+ return panel++L, W, T = spec["L"], spec["W"], spec["T"]+bt_r, bb_r = spec["band_top"], spec["band_bot"]+panels = [+ view_panel("top", L, W, [(bt_r[0], bt_r[1], "full")], "TOP (looking down, X right)"),+ view_panel("bottom", L, W, [(bb_r[0], bb_r[1], "full")], "BOTTOM (flipped about X)"),+ view_panel("front", L, T, [(bt_r[0], bt_r[1], "top"), (bb_r[0], bb_r[1], "bot")], "SIDE (looking +Y)"),+ view_panel("end", W, T, [], "END (looking -X)"),+]+pw, ph = panels[0].size+sheet_w = pw * 4 + 50+crops = [Image.open(p).convert("RGB").crop(tuple(b)) for p, b in spec["crops"]]+crop_h = 0; scaled = []+for c in crops:+ s = min(1.0, (sheet_w - 40) / c.width, 420 / c.height)+ c2 = c.resize((int(c.width * s), int(c.height * s)), Image.LANCZOS); scaled.append(c2); crop_h += c2.height + 20+text_h = 120 + 30 * len(checks) + 90+sheet = Image.new("RGB", (sheet_w, 80 + ph + text_h + crop_h), (255, 255, 255))+d = ImageDraw.Draw(sheet)+d.text((20, 15), f"{spec['label']}: model vs datasheet table, true scale {int(PP)} px/mm", fill=(0, 0, 0), font=fbig)+d.text((20, 50), f"Model: {step.rsplit('/', 1)[-1]} Datasheet: {spec['source']}", fill=(70, 70, 70), font=fsm)+for i, p in enumerate(panels):+ sheet.paste(p.convert("RGB"), (10 + i * (pw + 10), 80))+y = 90 + ph+d.text((20, y), "Legend: black = printed nominal, blue dashed = printed tolerance limits, amber = allowed position of the terminal inner edge, red outline + 50% fill = the 3D model.", fill=(0, 0, 0), font=fsm); y += 34+d.text((20, y), "Measured on the STEP value datasheet range result", fill=(0, 0, 0), font=font); y += 32+for name, val, lo, hi, ok in checks:+ d.text((20, y), f"{name:<34}{'-' if val is None else f'{val:.3f}':>8} {lo:.2f} to {hi:.2f} {'PASS' if ok else 'FAIL'}",+ fill=(0, 120, 40) if ok else (200, 0, 0), font=font); y += 30+d.text((20, y + 4), f"Frame: Z-up, seated z=0, centred: {'yes' if centred else 'NO'}; long axis on X: {'yes' if longx else 'NO'}. Marking: {spec['marking']}", fill=(0, 0, 0), font=fsm)+y += 50+for c in scaled:+ sheet.paste(c, (20, y)); d.rectangle([20, y, 20 + c.width, y + c.height], outline=(150, 150, 150)); y += c.height + 20+sheet.save(out)+res = {"step": step, "bbox_mm": [round(float(v), 4) for v in (x0, x1, y0, y1, z0, z1)],+ "checks": [{"dim": n, "model_mm": v, "datasheet_min": lo, "datasheet_max": hi, "pass": bool(ok)} for n, v, lo, hi, ok in checks],+ "z_up_seated_centred": bool(centred), "long_axis_x": bool(longx), "all_pass": bool(all(c[4] for c in checks) and centred and longx)}+print(json.dumps(res))
provenance/tools/parts.pyadded+53@@ -0,0 +1,53 @@+"""Catalog data for the KEMET C1812C124J1GACTU page (all values from the cited sources).++Sources: KEMET datasheet C1003_C0G (2/20/2025), the KEMET product page, KEMET's STEP for this part+number (plain HTTP GET, no browser), KiCad's Capacitor_SMD library. Distributor numbers and stock:+adom-parts-search, 2026-10-21.+"""+TODAY = "2026-10-21"+ROOT = "/home/adom/project/uta-dynos/work/pages/_build"+PRODUCT = "https://yageogroup.com/en/us/capacitors/ceramic/mlccs-smd/C1812C124J1GACTU.html" # kemet.com product URL redirects here+PRODUCT_KEMET = "https://www.kemet.com/en/us/capacitors/ceramic/mlccs-smd/C1812C124J1GACTU.html"+KSIM = "https://ksim3.kemet.com/capacitor-simulation?pn=C1812C124J1GACTU"+STEP_URL = "https://search.kemet.com/component-documentation/download/step/C1812C124J1GACTU.step"+DS_URL = "https://content.kemet.com/datasheets/KEM_C1003_C0G_SMD.pdf"+DS_URL_ALT = "https://yageogroup.com/content/datasheet/asset/file/KEM_C1003_C0G_SMD"+DS_PUB = "docs/KEMET-C1003-C0G-2025-02-20.pdf"+DS_TITLE = "KEMET, Surface Mount Multilayer Ceramic Chip Capacitors (SMD MLCCs), C0G Dielectric, 10 to 250 VDC (Commercial Grade), C1003_C0G, 2/20/2025 (22 pages)"+DOCS = [("ds", "kemet/KEMET-C1003-C0G-2025-02-20.pdf", DS_PUB, DS_TITLE, DS_URL)]+# (file, doc, page, crop box in 100 dpi pixels, caption); crops are cut from 150 dpi renders+FIGS = [+ ("kemet-ds-p1-overview-ordering.png", "ds", 1, [40, 120, 810, 1010], "p.1 overview (no change in capacitance with time and voltage, +/-30 ppm/C) and ordering information (C 1812 C 124 J 1 G A C TU)"),+ ("kemet-ds-p2-benefits-packaging.png", "ds", 2, [40, 100, 810, 540], "p.2 packaging C-spec (TU = 7 inch reel, unmarked) and benefits (no capacitance change with applied rated DC voltage, no decay with time)"),+ ("kemet-ds-p3-dimensions.png", "ds", 3, [40, 100, 810, 790], "p.3 dimensions: 1812 L 4.50 +/-0.30, W 3.20 +/-0.30, B 0.60 +/-0.35, S 2.30 min (mm)"),+ ("kemet-ds-p4-electrical.png", "ds", 4, [40, 100, 810, 690], "p.4 electrical parameters: -55 to +125 C, +/-30 ppm/C, 0 % aging, DWV 250 %, DF 0.1 %, IR"),+ ("kemet-ds-p8-waterfall-1812.png", "ds", 8, [40, 100, 810, 720], "p.8 Table 1B: 0.12 uF (124) in C1812C at 100 V (voltage code 1) has chip thickness code GH"),+ ("kemet-ds-p10-thickness.png", "ds", 10, [40, 100, 610, 960], "p.10 Table 2A: thickness code GH = 1.40 +/-0.15 mm; 1,000 pcs per 7 inch plastic reel"),+ ("kemet-ds-p12-land-pattern.png", "ds", 12, [40, 100, 810, 900], "p.12 Table 3: IPC-7351 land pattern for 1812 at density levels A, B and C"),+ ("kemet-ds-p13-soldering.png", "ds", 13, [40, 100, 810, 730], "p.13 soldering: reflow only for 1812, profile for 100 % matte Sn (260 C peak)"),+ ("kemet-ds-p15-marking.png", "ds", 15, [40, 100, 810, 780], "p.15 construction and marking: C0G devices are supplied unmarked only"),+]+P = dict(+ slug="c1812c124j1gactu", mpn="C1812C124J1GACTU", mfr="KEMET", mfr_full="KEMET Electronics Corporation (YAGEO Group)",+ source_slug="kemet", kind="C", category="Capacitor", subcategory="MLCC, Class 1 (C0G / NP0), commercial grade",+ value_sym="120n", value="0.12 uF (120 nF)", tol="+/-5 % (J)", package="1812 (4532 metric)",+ fp="C_1812_4532Metric", fp_lib="Capacitor_SMD", body_mm={"x_mm": 4.5, "y_mm": 3.2, "z_mm": 1.4},+ brief="MLCC 0.12 uF +/-5 %, 100 V, C0G/NP0, 1812 (4532 metric)",+ product_url=PRODUCT,+ dims_text="datasheet C1003_C0G p.3 (1812: L 4.50 +/-0.30, W 3.20 +/-0.30, B 0.60 +/-0.35, S 2.30 min) and p.8 + p.10 (0.12 uF, 100 V: thickness code GH, T 1.40 +/-0.15 mm)",+ overlay={"L": [4.5, 0.3], "W": [3.2, 0.3], "T": [1.4, 0.15], "band_top": [0.25, 0.95], "band_bot": [0.25, 0.95],+ "band_top_name": "B (band width), top", "band_bot_name": "B (band width), bottom", "ppmm": 100,+ "source": "KEMET C1003_C0G 2/20/2025 p.3 (1812) and p.10 (thickness code GH)"},+ land={"source": "KEMET C1003_C0G 2/20/2025 p.12, Table 3, 1812, density level B (median, nominal): C 2.05 (centre to pad centre), Y 1.40, X 3.50, V1 6.00, V2 4.00 mm",+ "kind": "nominal", "gap_a": 2.70, "len_b": 1.40, "wid_c": 3.50, "overall": 5.50,+ "letters": "gap = 2C - Y, land length = Y, land width = X, overall = 2C + Y (KEMET's letters C, Y, X)",+ "levels": {"A": {"C": 2.15, "Y": 1.60, "X": 3.60, "V1": 6.90, "V2": 4.60},+ "B": {"C": 2.05, "Y": 1.40, "X": 3.50, "V1": 6.00, "V2": 4.00},+ "C": {"C": 1.95, "Y": 1.20, "X": 3.40, "V1": 5.30, "V2": 3.70}}},+ marking="KEMET supplies C0G devices unmarked only (datasheet p.15), and the ordering code TU means 7 inch reel, unmarked (p.2)",+ marking_src="KEMET C1003_C0G 2/20/2025 p.2 and p.15",+ dist={"mouser": ("80-C1812C124J1G", "https://www.mouser.com/ProductDetail/KEMET/C1812C124J1GACTU?qs=Fn3VdHBzkcnCFZmhvW0Uag%3D%3D", 1701, "$2.98 (1), $1.79 (100), lead time 266 days"),+ "digikey": ("399-C1812C124J1GACTUTR-ND", "https://www.digikey.com/en/products/detail/kemet/C1812C124J1GACTU/1874760", 1761, "$3.53 (1)")},+ lcsc_note="JLCPCB/LCSC search for C1812C124J1GACTU returned no part on 2026-10-21",+)+PARTS = [P]
provenance/tools/push.shadded+34@@ -0,0 +1,34 @@+#!/usr/bin/env bash+# usage: push.sh <slug> (pushes /home/adom/project/uta-dynos/work/pages/<slug> to adom/<slug>)+set -u+slug="$1"; dir="/home/adom/project/uta-dynos/work/pages/$slug"+cd "$dir" || exit 1+mapfile -t files < <(find . -type f ! -name page.json -printf '%s %P\n' | sort -k2)+batch=(); size=0; n=0+push() {+ local tries=0+ while :; do+ out=$(adom-wiki repo push "adom/$slug" --files "$@" -m "$MSG" 2>&1); rc=$?+ if echo "$out" | grep -q -E '429|RATE_LIMITED'; then+ w=$(echo "$out" | grep -oE '"retry_after":[0-9]+' | grep -oE '[0-9]+' | head -1); w=${w:-20}+ echo "rate limited, waiting $w s"; sleep "$w"; tries=$((tries+1)); [ $tries -gt 5 ] && return 1; continue+ fi+ echo "$out" | tail -4; return $rc+ done+}+flush() {+ [ ${#batch[@]} -eq 0 ] && return 0+ n=$((n+1)); MSG="Component record for $slug: files batch $n"+ echo "== batch $n: ${#batch[@]} files, $size bytes"+ push "${batch[@]}" || { echo "PUSH FAILED"; exit 1; }+ batch=(); size=0; sleep 3+}+for line in "${files[@]}"; do+ s=${line%% *}; f=${line#* }+ if [ $((size + s)) -gt 3300000 ]; then flush; fi+ batch+=("$f"); size=$((size + s))+done+flush+MSG="Component record for $slug: page.json (last, keeps hero and title)"+echo "== page.json"+push page.json || { echo "PUSH FAILED"; exit 1; }
provenance/tools/records.pyadded+454@@ -0,0 +1,454 @@+"""Write README.md, land-pattern/README.md, page.json, package.json, models.json, provenance.json,+verification.json and LICENSE for the C1812C124J1GACTU page.++usage: records.py <slug> (run after build.py, again after finish.py)+"""+import sys, os, json, hashlib, re+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))+from parts import P, TODAY, ROOT, DOCS, PRODUCT, PRODUCT_KEMET, KSIM, STEP_URL, DS_URL, DS_URL_ALT++slug = sys.argv[1]; assert slug == P["slug"]+OUT = f"/home/adom/project/uta-dynos/work/pages/{slug}"+MPN = P["mpn"]; OWNER = "adom"+B = json.load(open(os.path.join(ROOT, "work", f"{slug}-build.json")))+STEP, ORIG, FP, SYM = B["step"], B["orig"], B["fp"], B["sym"]+check = B["check"]+land = json.load(open(os.path.join(OUT, "land-pattern/land-check.json")))+lod = json.load(open(os.path.join(OUT, "lod/manifest.json")))+figs = json.load(open(os.path.join(OUT, "docs/figures/figures.json")))+repose = json.load(open(os.path.join(OUT, f"provenance/{STEP}.json")))+p_ = lambda f: os.path.join(OUT, f)+ex = lambda f: os.path.exists(p_(f))+sha = lambda f: hashlib.sha256(open(p_(f), "rb").read()).hexdigest()+def blob(f):+ b = open(p_(f), "rb").read(); return hashlib.sha1(b"blob %d\0" % len(b) + b).hexdigest()+D = P["dist"]; L = P["land"]; LV = L["levels"]+ds = dict(zip(("key", "local", "file", "title", "url"), DOCS[0]))+by = {"name": "Claude (AI agent) for John Lauer", "handle": "john"}+HERO = f"{slug}-hero.glb"; HERO_SIDE = f"{slug}-hero.overlay.json"; HERO_PNG = "docs/verify/hero-preview.png"+LBR = f"{MPN}.lbr"; SCH = f"altium/{MPN}.SchLib"; PCB = f"altium/{MPN}.PcbLib"; HUB = f"{MPN}.adom-lbr.json"+REPOSE = f"provenance/{STEP}.json"+have_hero, have_alt, have_lbr = ex(HERO), ex(SCH) and ex(PCB), ex(LBR)+GL = "https://gitlab.com/kicad/libraries"+F = lambda n: f"docs/figures/{n}"+k = land["kicad"]+band = next(c for c in check["checks"] if c["dim"].startswith("B (band width), top"))["model_mm"]++# ---------------- land pattern write-up ----------------+t = "| Dimension | KiCad C_1812_4532Metric (used) | KEMET density level B | Difference |\n|---|---|---|---|\n"+for r in land["rows"]:+ t += f"| {r['dim']} | {r['kicad_mm']:.2f} mm | {r['maker_nominal']:.2f} mm | {r['difference_mm']:+.2f} mm |\n"+lv = "| Level | C (centre to land centre) | Y (land length) | X (land width) | V1 x V2 (courtyard) |\n|---|---|---|---|---|\n"+for n, name in (("A", "A, maximum protrusion"), ("B", "B, median (nominal)"), ("C", "C, minimum protrusion")):+ v = LV[n]; lv += f"| {name} | {v['C']:.2f} | {v['Y']:.2f} | {v['X']:.2f} | {v['V1']:.2f} x {v['V2']:.2f} |\n"+land_readme = f"""# Land pattern check: {MPN}++The footprint on this page is KiCad's standard library footprint `{P['fp_lib']}:{P['fp']}`, renamed to+`{FP[:-10]}` so it can sit beside other parts in one library folder. The copper is unchanged; only the name and+the 3D model path (`${{KIPRJMOD}}/{STEP}`) differ from upstream. This page compares it with the land pattern KEMET+recommends for 1812 parts in datasheet C1003_C0G (2/20/2025), page 12, Table 3 (IPC-7351).++++## KEMET's three density levels for 1812 (mm)++{lv}+KEMET's drawing (p.12) gives C from the part centre to each land centre, so the gap between the lands is 2C - Y and the+overall length is 2C + Y. KEMET describes level B as "a robust solder attachment condition for reflow solder processes".++## KiCad pads against level B++{t}+## Result++KiCad's footprint is IPC-7351 nominal, the same rule as KEMET's level B: land centres, land length, gap and overall length+are identical. The only difference is the land width: KiCad's pads are {k['wid']:.2f} mm wide, KEMET's level B lands+{LV['B']['X']:.2f} mm (0.10 mm narrower, 0.05 mm per side; level C uses 3.40 mm). The KiCad pads have rounded corners+(roundrect ratio 0.18); KEMET draws plain rectangles. KiCad's courtyard is 6.00 x 3.90 mm, KEMET's V1 x V2 6.00 x 4.00 mm.+Both cover the full printed body width (3.20 +/-0.30 mm). This page keeps the KiCad pads. A design that wants KEMET's+level B exactly should widen each pad to {LV['B']['X']:.2f} mm.++KEMET lists the 1812 case as reflow only (datasheet p.3 and p.13).++The drawing and [land-check.json](land-check.json) come from `provenance/tools/landcheck.py`.+"""+open(p_("land-pattern/README.md"), "w").write(land_readme)++# ---------------- verification.json ----------------+entries = []+ck = [{"text": f"{c['dim']}: model {c['model_mm']:.3f} mm, KEMET {c['datasheet_min']:.2f} to {c['datasheet_max']:.2f} mm", "result": "pass" if c["pass"] else "fail"} for c in check["checks"]]+ck += [{"text": "thickness code for this exact part: 0.12 uF (124) at 100 V (voltage code 1) in C1812C is GH (Table 1B, p.8), T = 1.40 +/-0.15 mm (Table 2A, p.10)", "result": "pass"},+ {"text": "KEMET's model is drawn at the top of every printed range: L 4.80 (max), W 3.50 (max), T 1.55 (GH max); its file name is CCS_CCSTD_1812_L4.8W3.5T1.55B0.6", "result": "note"},+ {"text": f"band measured on the top face: {band:.3f} mm from the end (KEMET's file name says B0.6); inside B 0.60 +/-0.35 mm", "result": "note"},+ {"text": "re-posed from Y-up to Z-up by one rigid rotation and translation; package solid volume unchanged (23.90241 mm3), 72 face colours copied", "result": "pass"},+ {"text": "three 5 um KEMET logo glyph solids dropped from the board model: KEMET supplies C0G parts unmarked only (p.15); the original download is kept unchanged under vendor/", "result": "pass"}]+entries.append({"date": TODAY, "by": by, "status": "verified", "title": "KEMET 3D model checked against the printed dimensions for thickness code GH",+ "scope": ["3d-model"], "variants": ["*"], "checks": ck,+ "evidence": [{"label": "overlay (top, bottom, side, end at true scale)", "href": f"docs/verify/{slug}-overlay.png"},+ {"label": "numbers", "href": f"docs/verify/{slug}-check.json"},+ {"label": "re-pose record", "href": REPOSE},+ {"label": "datasheet p.3 dimensions", "href": F("kemet-ds-p3-dimensions.png")},+ {"label": "datasheet p.8 thickness code", "href": F("kemet-ds-p8-waterfall-1812.png")},+ {"label": "datasheet p.10 thickness range", "href": F("kemet-ds-p10-thickness.png")}],+ "files": {STEP: sha(STEP), ORIG: sha(ORIG)}})+lck = [{"text": f"{r['dim']}: KiCad {r['kicad_mm']:.2f} mm, KEMET level B {r['maker_nominal']:.2f} mm ({r['difference_mm']:+.2f} mm)", "result": "note"} for r in land["rows"]]+lck.append({"text": "KiCad's IPC-7351 nominal footprint matches KEMET's level B except the land width (3.40 vs 3.50 mm); KiCad footprint kept (renamed, copper unchanged)", "result": "pass"})+lck.append({"text": "footprint names its 3D model: (model \"${KIPRJMOD}/" + STEP + "\")", "result": "pass"})+entries.append({"date": TODAY, "by": by, "status": "verified", "title": "Footprint compared with KEMET's recommended land pattern (Table 3, level B)",+ "scope": ["footprint"], "variants": ["*"], "checks": lck,+ "evidence": [{"label": "comparison drawing", "href": "land-pattern/land-pattern.png"}, {"label": "numbers", "href": "land-pattern/land-check.json"},+ {"label": "write-up", "href": "land-pattern/README.md"}, {"label": "datasheet p.12", "href": F("kemet-ds-p12-land-pattern.png")}],+ "files": {FP: sha(FP)}})+entries.append({"date": TODAY, "by": by, "status": "verified", "title": "Marking: the real part is unmarked",+ "scope": ["3d-model", "marking"], "variants": ["*"],+ "checks": [{"text": f"{P['marking']} ({P['marking_src']})", "result": "pass"},+ {"text": "no AI-added part-number etch on any model; KEMET's CAD logo removed from the board model, kept in the vendor original", "result": "pass"}],+ "evidence": [{"label": "datasheet p.15", "href": F("kemet-ds-p15-marking.png")}, {"label": "datasheet p.2", "href": F("kemet-ds-p2-benefits-packaging.png")}],+ "files": {STEP: sha(STEP)}})+entries.append({"date": TODAY, "by": by, "status": "verified", "title": "Board-use LOD GLBs are clean and share the STEP pose",+ "scope": ["lod"], "variants": ["*"],+ "checks": [{"text": "four levels written, read back, bounds equal the STEP (Z-up, metres, seat z = 0)", "result": "pass"},+ {"text": "no footprint, pad outline, label or silkscreen helpers; terminals present at every level", "result": "pass"},+ {"text": "previews looked at by the AI agent, not yet by a person", "result": "note"}],+ "evidence": [{"label": "manifest", "href": "lod/manifest.json"}], "files": {lv_["path"]: lv_["sha256"] for lv_ in lod["levels"]}})+entries.append({"date": TODAY, "by": by, "status": "verified", "title": "Pin 1, polarity and symbol",+ "scope": ["footprint", "3d-model", "symbol"], "variants": ["*"],+ "checks": [{"text": "non-polar two-terminal capacitor (datasheet p.2): pads 1 and 2 are interchangeable; symbol pins 1 and 2 map to pads 1 and 2", "result": "pass"},+ {"text": "symbol pin ends at (0, +2.54) and (0, -2.54) mm, on the 2.54 mm grid", "result": "pass"},+ {"text": "symbol fields MPN, Manufacturer, Mouser, DigiKey, Adom Wiki, Datasheet and Footprint filled; LCSC present but empty because JLCPCB/LCSC list no part (" + TODAY + ")", "result": "note"},+ {"text": "model terminals sit on the footprint pads (both centred on the origin, long axis on X)", "result": "pass"}],+ "evidence": [], "files": {FP: sha(FP), SYM: sha(SYM), STEP: sha(STEP)}})+if have_hero:+ entries.append({"date": TODAY, "by": by, "status": "verified", "title": "Hero GLB built to the adom-hero standard",+ "scope": ["3d-model", "hero"], "variants": ["*"],+ "checks": [{"text": "built with adom/component-hero hero_glb.py (unmodified) from package-reference.glb, the footprint and page.json; alignment check OK", "result": "pass"},+ {"text": "teal dashed pad outlines (alpha 0.5), silkscreen (alpha 0.3) and pad-number labels 1 and 2 (alpha 0.5); no pin-1 dot because the part is non-polar", "result": "pass"},+ {"text": "preview looked at by the AI agent; the live wiki viewer still needs a person's look", "result": "note"}],+ "evidence": [{"label": "preview", "href": HERO_PNG}, {"label": "overlay record", "href": HERO_SIDE}],+ "files": {HERO: sha(HERO), "package-reference.glb": sha("package-reference.glb")}})+if have_alt or have_lbr:+ entries.append({"date": TODAY, "by": by, "status": "verified", "title": "Fusion and Altium libraries generated from the KiCad symbol and footprint",+ "scope": ["symbol", "footprint"], "variants": ["*"],+ "checks": [{"text": "adom-lbr generate --part-metadata page.json: .lbr lint passed", "result": "pass"},+ {"text": "adom-lbr import-kicad then assemble-altium --no-dedupe: native .SchLib and .PcbLib written, STEP body embedded", "result": "pass"},+ {"text": "not yet opened in Fusion or Altium (no desktop app used for this page)", "result": "note"}],+ "evidence": [], "files": {f: sha(f) for f in (LBR, SCH, PCB, HUB) if ex(f)}})+json.dump({"schema": "adom/verification@1", "part": MPN, "variants": [], "entries": entries}, open(p_("verification.json"), "w"), indent=1)++# ---------------- models.json ----------------+SRC = f"KEMET 3D model (STEP) for {MPN}, {STEP_URL}"+models = [+ {"path": STEP, "format": "step", "title": "Package model (STEP, KEMET, Z-up)", "status": "current", "slot": "step", "variant": "plain", "role": "manufacturer",+ "use": ["board-cad"], "marking": None, "derived_from": ORIG,+ "unique": "The board CAD model: KEMET's package solid re-posed to Z-up, seated on z = 0 and centred; KEMET's CAD logo glyphs dropped (the real part is unmarked)",+ "source": SRC, "sha256": sha(STEP), "git_blob": blob(STEP), "bytes": os.path.getsize(p_(STEP)), "thumb": "docs/models/step-plain.png"},+ {"path": ORIG, "format": "step", "title": "KEMET original download (STEP)", "status": "current", "variant": "original", "role": "manufacturer",+ "use": ["archive"], "marking": {"text": "KEMET", "surface": "top of the ceramic", "colour": "SolidWorks default lavender (no colour set by KEMET)"},+ "unique": "The file exactly as downloaded: Y-up, corner at the origin, with KEMET's logo as three 5 um solids on top",+ "source": SRC, "sha256": sha(ORIG), "git_blob": blob(ORIG), "bytes": os.path.getsize(p_(ORIG))},+ {"path": "package-reference.glb", "format": "glb", "title": "Package model (GLB)", "status": "current", "slot": "model_3d_plain", "variant": "plain",+ "role": "derived", "use": ["render", "board-insertion"], "marking": None, "derived_from": STEP,+ "unique": "Clean board-use model for viewers (step2glb service), no footprint artwork", "source": SRC,+ "sha256": sha("package-reference.glb"), "git_blob": blob("package-reference.glb"), "bytes": os.path.getsize(p_("package-reference.glb"))},+]+if have_hero:+ models.append({"path": HERO, "format": "glb", "title": "Hero model (GLB)", "status": "current", "slot": "model_3d", "variant": "hero", "role": "derived",+ "use": ["wiki-hero"], "marking": None, "derived_from": "package-reference.glb",+ "unique": "The page's 3D view: the package plus teal dashed pad outlines, silkscreen and pad-number labels from the footprint (adom-hero standard)",+ "source": "adom/component-hero hero_glb.py", "sha256": sha(HERO), "git_blob": blob(HERO), "bytes": os.path.getsize(p_(HERO)), "thumb": HERO_PNG})+for lv_ in lod["levels"]:+ models.append({"path": lv_["path"], "format": "glb", "title": f"Board LOD, {lv_['level']}", "status": "current", "variant": "lod", "role": "derived",+ "use": ["board-insertion", "render"], "marking": None, "derived_from": STEP,+ "unique": f"{lv_['level']} detail for board and scaffold viewers: {lv_['triangles']} triangles, {lv_['primitives']} primitives",+ "thumb": lv_["preview"]["path"], "source": SRC, "sha256": lv_["sha256"], "git_blob": blob(lv_["path"]), "bytes": lv_["sizeBytes"]})+json.dump({"schema": "adom/models@1", "part": MPN, "models": models, "updated": TODAY}, open(p_("models.json"), "w"), indent=1)++# ---------------- LICENSE ----------------+open(p_("LICENSE"), "w").write(f"""{MPN} component page++Original Adom content on this page (README text, the KiCad symbol {SYM}, the verification+drawings, the JSON records and the scripts in provenance/tools/) is licensed under the Creative+Commons Attribution 4.0 International License (CC-BY-4.0): https://creativecommons.org/licenses/by/4.0/++Third-party files keep their own terms and are not relicensed by this page:+- {ORIG} is KEMET's 3D model for this part, downloaded from KEMET, under KEMET's terms.+ {STEP} and every model derived from it (package-reference.glb, {HERO}, lod/*.glb, the body+ inside altium/{MPN}.PcbLib) are re-posed derivatives of that file and keep KEMET's terms.+- {FP} is KiCad library content, CC-BY-SA 4.0 with the KiCad library exception+ ({GL}/kicad-footprints); the footprint inside {LBR} and altium/{MPN}.PcbLib keeps that license.+- {ds['file']} and the page crops in docs/figures/ are KEMET documents, copyright of KEMET+ Electronics Corporation (YAGEO Group), included unchanged (or cropped) for reference.+""")++# ---------------- README ----------------+spec_rows = [+ ("Capacitance", "0.12 uF (120 nF) +/-5 % (code 124, J)", "p.1 ordering information"),+ ("Rated voltage", "100 V DC (voltage code 1)", "p.1"),+ ("Dielectric", "C0G (NP0), EIA Class I, temperature compensating", "p.1"),+ ("Temperature coefficient", "0 +/-30 ppm/C from -55 to +125 C, referenced to +25 C and 0 V DC", "p.1, p.4"),+ ("Voltage coefficient", "no capacitance change with applied rated DC voltage", "p.1, p.2"),+ ("Aging", "0 % (maximum capacitance loss per decade hour); no capacitance decay with time", "p.2, p.4"),+ ("Dissipation factor", "0.1 % max at 25 C (1 kHz, 1.0 Vrms for parts above 1,000 pF)", "p.4"),+ ("Insulation resistance", "1,000 MOhm x uF or 100 GOhm, whichever is lower: about 8.3 GOhm min for 0.12 uF (100 V applied, 25 C)", "p.4"),+ ("Dielectric withstanding voltage", "250 % of rated voltage (250 V) for 5 +/-1 s, charge current up to 50 mA", "p.4"),+ ("After life, humidity and moisture tests", "capacitance shift 0.3 % or +/-0.25 pF max, DF 0.5 % max, IR 10 % of initial limit", "p.4"),+ ("Operating temperature", "-55 to +125 C", "p.4"),+ ("Size", "L 4.50 +/-0.30, W 3.20 +/-0.30, T 1.40 +/-0.15 (thickness code GH), B 0.60 +/-0.35, S 2.30 min (mm)", "p.3, p.8, p.10"),+ ("Termination", "100 % matte tin over nickel barrier over copper (termination code C)", "p.1, p.15"),+ ("Packaging (TU)", "7 inch reel, unmarked; embossed plastic tape 12 mm wide, 8 mm pitch; 1,000 pieces per reel", "p.2, p.10, p.16"),+ ("Soldering", "reflow only for 1812; matte Sn profile peaks at 260 C; up to three reflow passes", "p.3, p.13"),+ ("Compliance", "lead (Pb) free, RoHS and REACH compliant without exemptions", "p.1, p.2"),+ ("Marking", "none: C0G parts are supplied unmarked only", "p.2, p.15"),+]+spec_tbl = "| Parameter | Value | Datasheet page |\n|---|---|---|\n" + "".join(f"| {a} | {b} | {c} |\n" for a, b, c in spec_rows)+dist_tbl = ("| Distributor | Part number | Stock seen " + TODAY + " | Price seen |\n|---|---|---|---|\n"+ + "".join(f"| {n} | [{D[kk][0]}]({D[kk][1]}) | {D[kk][2]:,} | {D[kk][3]} |\n" for kk, n in (("mouser", "Mouser"), ("digikey", "DigiKey")))+ + "| LCSC / JLCPCB | not listed | - | - |\n")+fig_tbl = "| Figure | Source |\n|---|---|\n" + "".join(f"| [{f['caption']}]({f['file']}) | KEMET C1003_C0G, p.{f['page']} |\n" for f in figs)+cad_rows = [(f"`{SYM}`", "KiCad symbol (standard capacitor graphic, pin ends on the 2.54 mm grid) with MPN, Manufacturer, Mouser, DigiKey, LCSC (empty) and Adom Wiki fields"),+ (f"`{FP}`", f"KiCad footprint (KiCad library `{P['fp']}`, renamed; model `${{KIPRJMOD}}/{STEP}`)")]+if have_lbr: cad_rows.append((f"`{LBR}`", "Fusion / EAGLE library made with `adom-lbr generate`"))+if have_alt: cad_rows.append((f"`{SCH}`, `{PCB}`", f"Altium symbol and footprint libraries (native binary) made with `adom-lbr import-kicad` and `assemble-altium` (the PcbLib embeds the STEP body); `{HUB}` is the adom-lbr hub record"))+cad_rows += [(f"`{STEP}`", "3D model, STEP: KEMET's package solid, Z-up, seated and centred"), (f"`{ORIG}`", "KEMET's STEP exactly as downloaded"),+ ("`package-reference.glb`", "3D model, GLB (clean, for board viewers)")]+if have_hero: cad_rows.append((f"`{HERO}`", "the page's 3D view: package plus footprint pad outlines, silkscreen and pad labels"))+cad_rows += [("`lod/`", "board-use GLBs in four levels of detail, with previews and manifest")]+cad_tbl = "| File | What it is |\n|---|---|\n" + "".join(f"| {a} | {b} |\n" for a, b in cad_rows)+res_tbl = ("| Resource | Link |\n|---|---|\n"+ f"| Product page | {PRODUCT} (the KEMET address {PRODUCT_KEMET} redirects there) |\n"+ f"| Datasheet C1003_C0G, 2/20/2025 | [PDF on this page]({ds['file']}) ([KEMET]({DS_URL}), [YAGEO]({DS_URL_ALT})) |\n"+ f"| 3D model (STEP) | [{STEP_URL}]({STEP_URL}) (copy under `vendor/`) |\n"+ f"| KEMET K-SIM simulation (impedance, ESR, temperature rise) | {KSIM} |\n"+ "| Flexible termination version | KEMET product bulletin C1062_C0G_FT-CAP_SMD (datasheet p.1 note 1) |\n")+hero_para = (f"""+The page viewer shows `{HERO}`: the clean model plus nonphysical reference artwork drawn under it to the adom-hero+standard (adom/component-hero): the footprint's pads as teal dashed outlines at 50 % opacity, its silkscreen at 70 %+transparency and the pad numbers 1 and 2. The part is non-polar, so no pin-1 dot is added. The artwork never goes into+the STEP, `package-reference.glb` or the board LODs.+++""" if have_hero else "")+readme = f"""# {MPN}++KEMET {MPN}: {P['brief']}. A commercial grade, class I multilayer ceramic chip capacitor from KEMET's C0G series+(KEMET is part of the YAGEO Group). C0G holds its capacitance against DC voltage, temperature and time, which makes it the+choice for timing, tuning, filters, snubbers and other circuits where the value must not move.++::adom-verified{{src="/{OWNER}/{slug}/render/verification.json"}}++| Field | Value |+|---|---|+| Manufacturer | {P['mfr_full']} |+| Manufacturer part number | {MPN} |+| Type | {P['subcategory']} |+| Capacitance | {P['value']}, {P['tol']} |+| Rated voltage | 100 V DC |+| Dielectric | C0G (NP0): 0 +/-30 ppm/C, -55 to +125 C |+| Package | {P['package']}, 4.50 x 3.20 x 1.40 mm (thickness code GH) |+| Marking | none (C0G parts are supplied unmarked) |+| Datasheet | [{ds['title']}]({ds['file']}) |++## Why C0G: stable with voltage, temperature and time++The figures below are KEMET's, from the datasheet on this page:++- **Voltage.** "No capacitance change with respect to applied rated DC voltage" (p.2; the overview on p.1 says C0G+ "exhibits no change in capacitance with respect to time and voltage"). At 100 V this part is still 0.12 uF.+- **Temperature.** Capacitance change is limited to +/-30 ppm/C from -55 to +125 C, referenced to +25 C (p.1, p.4).+ Worked out from that limit: at most +/-0.3 % at +125 C and +/-0.24 % at -55 C.+- **Time.** Aging rate 0 % per decade hour (p.4), "no capacitance decay with time" (p.2).+- **No piezoelectric noise**, low ESR and ESL and high ripple current capability are listed as benefits (p.1).++By contrast, a class II X7R capacitor is allowed +/-15 % over -55 to +125 C (the EIA X7R definition), loses part of its+capacitance under DC bias, and ages over time; at a 100 V rating that bias loss can be large. That is the reason to+choose this C0G part over an X7R of the same value where the capacitance has to stay put: the cost is size, since 0.12 uF+at 100 V needs an 1812 case in C0G.++++++## Key specifications++{spec_tbl}+Page numbers refer to [KEMET C1003_C0G, 2/20/2025]({ds['file']}).++## Ordering++{dist_tbl}+Stock and prices are a snapshot from the Adom parts search on {TODAY}; check the distributor before ordering. DigiKey lists+the part as Active. {P['lcsc_note']}.++## Part number++C ceramic | 1812 case size | C standard series | 124 = 12 x 10^4 pF = 0.12 uF | J +/-5 % | 1 = 100 V | G = C0G |+A failure rate not applicable | C 100 % matte Sn termination | TU 7 inch reel, unmarked (datasheet p.1 and p.2). The+thickness is not in the part number: Table 1B (p.8) gives code GH for 0.12 uF at 100 V in 1812, and Table 2A (p.10)+gives GH = 1.40 +/-0.15 mm.++++## Manufacturer and resources++{res_tbl}+## Pins++| Pin | Function |+|---|---|+| 1 | terminal (non-polar) |+| 2 | terminal (non-polar) |++A ceramic capacitor has no polarity: either terminal may face either way (datasheet p.2, "non-polar device").++## Land pattern: KiCad footprint vs KEMET++<table>+<tr>+<td width="104"><a href="land-pattern/README.md"><img src="land-pattern/land-pattern-icon.png" width="112" alt="Land pattern"></a><br><sub> Land pattern </sub></td>+<td>The footprint is KiCad's standard `{P['fp']}`, and this guide lays its pads over KEMET's recommended IPC-7351 land (datasheet p.12, density level B) at true scale. Land centres, length and gap are identical; KiCad's pads are 0.10 mm narrower (3.40 vs 3.50 mm) and have rounded corners. Use it to decide whether to keep the KiCad pads or widen them to KEMET's level B before layout.<br><br><a href="land-pattern/README.md">The comparison, with KEMET's three density levels</a></td>+</tr>+</table>++## 3D model++The model is **KEMET's own STEP** for this part number, downloaded by plain HTTP from KEMET's component documentation+service. KEMET's file (CCS_CCSTD_1812_L4.8W3.5T1.55B0.6) is Y-up with a corner at the origin; `{STEP}` is the same+package solid moved by one rigid transform (rotate +90 degrees about X, then translate) so it is Z-up, seated on z = 0 and+centred on the footprint origin, with its face colours copied over and its volume unchanged. KEMET's file also carries a+"KEMET" logo as three 5 um thick solids on top of the ceramic, coloured only with SolidWorks' default lavender. The real+part has no marking (KEMET supplies C0G parts unmarked only, p.15), so the board model leaves the logo out. The download+itself is kept unchanged as `{ORIG}`, and every step is recorded in [`{REPOSE}`]({REPOSE}).++The model is drawn at the top of every printed range: 4.80 x 3.50 x 1.55 mm against L 4.50 +/-0.30, W 3.20 +/-0.30 and,+for this part's thickness code GH, T 1.40 +/-0.15 mm. Its terminal band measures {band:.2f} mm on the top face, inside+B 0.60 +/-0.35 mm. A real part will usually be smaller than the model in all three directions.+{hero_para}+::adom-models{{src="/{OWNER}/{slug}/render/models.json"}}++## Model checked against the printed dimensions++The model was rendered in orthographic top, bottom, side and end views at true scale and laid over a drawing made at the same+scale from the printed limits (nominal outline, tolerance limits, allowed terminal band). Every dimension is inside the printed+range: {", ".join(f"{c['dim']} {c['model_mm']:.3f} mm ({c['datasheet_min']:.2f} to {c['datasheet_max']:.2f})" for c in check['checks'])}.++++Source: {P['dims_text']}.++## Board levels of detail++Clean board-use GLBs (no footprint pads, outlines, labels or silkscreen) in four levels, all in the same Z-up, metre,+footprint-origin pose as the STEP. Previews are rendered from each GLB.++::adom-component-lod{{src="/{OWNER}/{slug}/render/lod/manifest.json"}}++## CAD files++{cad_tbl}+## Datasheet figures++The pages this record cites, cropped from the datasheet:++{fig_tbl}+## Sources and license++- KEMET datasheet C1003_C0G, 2/20/2025 ({DS_URL}), sha256 {sha(ds['file'])}+- KEMET 3D model: {STEP_URL}, sha256 {sha(ORIG)}, {TODAY}+- KiCad library footprint: [{P['fp_lib']}.pretty/{P['fp']}.kicad_mod]({GL}/kicad-footprints/-/blob/master/{P['fp_lib']}.pretty/{P['fp']}.kicad_mod)+- Distributor numbers, stock and prices: Mouser, DigiKey and JLCPCB/LCSC through the Adom parts search, {TODAY}++Original Adom content is CC-BY-4.0. KEMET's STEP, its derivatives and the datasheet keep KEMET's terms and the KiCad+footprint keeps its upstream license (CC-BY-SA 4.0 with the KiCad library exception); see [LICENSE](LICENSE).+[provenance.json](provenance.json) records every source and file hash; the scripts that built the checks are in+`provenance/tools/`.++This is a catalog record for the part, not a manufacturing approval: check KEMET's current specification for your design.+"""+readme = re.sub(r"\n{3,}", "\n\n", readme)+for bad in ("—", "–"):+ assert bad not in readme and bad not in land_readme, "dash in README"+open(p_("README.md"), "w").write(readme)++# ---------------- page.json / package.json ----------------+title = f"{MPN} - {P['mfr']}"+parts_ = {"symbol": SYM, "footprint": FP, "step": STEP, "model_3d": HERO if have_hero else "package-reference.glb",+ "model_3d_plain": "package-reference.glb", "datasheet": ds["file"]}+if have_lbr: parts_["lbr"] = LBR+if have_alt: parts_.update({"altium_schlib": SCH, "altium_pcblib": PCB})+component = {+ "mpn": MPN, "manufacturer": P["mfr"], "package": P["package"], "category": P["category"], "subcategory": P["subcategory"],+ "pin_count": 2, "body_size": P["body_mm"], "value": P["value"], "tolerance": P["tol"], "rating": "100 V DC",+ "dielectric": "C0G / NP0 (0 +/-30 ppm/C, -55 to +125 C; no change with DC voltage; 0 % aging)",+ "thickness_code": "GH (1.40 +/-0.15 mm)",+ "pins": [{"number": "1", "name": "~"}, {"number": "2", "name": "~"}],+ "parts": parts_,+ "manufacturer_links": {"product": PRODUCT, "datasheet_pdf": DS_URL, "step": STEP_URL, "simulation": KSIM},+ "distributor_links": {"mouser": {"part": D["mouser"][0], "url": D["mouser"][1]}, "digikey": {"part": D["digikey"][0], "url": D["digikey"][1]}, "lcsc": False},+ "lcsc_note": P["lcsc_note"],+ "stock": {"date": TODAY, "mouser": D["mouser"][2], "digikey": D["digikey"][2], "source": "adom-parts-search"},+ "marking": {"text": None, "statement": P["marking"], "source": P["marking_src"]},+ "provenance": {"model_3d": "KEMET STEP for this part number, re-posed to Z-up, CAD logo glyphs dropped, checked against the printed dimensions for thickness code GH",+ "footprint": "KiCad standard library C_1812_4532Metric, renamed", "land_pattern_check": "land-pattern/README.md (matches KEMET level B except land width 3.40 vs 3.50 mm)",+ "omitted": ["KEMET logo glyph solids from the board model (real part is unmarked; original kept in vendor/)"]},+}+eda = {"kicad": {"symbol": SYM, "footprint": FP, "model": STEP, "provenance": "generated"}}+if have_lbr: eda["fusion"] = {"lbr": LBR, "provenance": "generated"}+if have_alt: eda["altium"] = {"schlib": SCH, "pcblib": PCB, "provenance": "generated"}+page = {"slug": slug, "title": title, "eda_assets": eda, "type": "hardware", "kind": "component", "version": "0.1.0",+ "description": P["brief"], "brief": P["brief"],+ "tags": ["hardware", "component", "passive", "capacitor", "mlcc", "1812", "c0g", "np0", "100v", "kemet"],+ "discovery_triggers": [MPN, MPN.lower(), "80-C1812C124J1G", "0.12 uF 100 V 1812 C0G", "120nF 100V C0G 1812 capacitor", "KEMET C0G 1812"],+ "dependencies": {}, "license": "CC-BY-4.0", "hero": {"path": "docs/hero.png", "chrome": False},+ "component": component, "visibility": {"public": True}, "source": {"tool": "kemet", "url": PRODUCT}}+json.dump(page, open(p_("page.json"), "w"), indent=1)+json.dump({"name": slug, "title": title, "version": "0.1.0", "description": P["brief"], "type": "hardware", "kind": "component",+ "license": "CC-BY-4.0", "files": []}, open(p_("package.json"), "w"), indent=1)++# ---------------- provenance.json ----------------+files = {}+for dp, dn, fn in os.walk(OUT):+ for f in fn:+ rel = os.path.relpath(os.path.join(dp, f), OUT)+ if rel != "provenance.json":+ files[rel] = sha(rel)+finp = os.path.join(ROOT, "work", f"{slug}-finish.json")+fin = json.load(open(finp)) if os.path.exists(finp) else {}+prov = {+ "manufacturer_reference": PRODUCT,+ "datasheet": {"file": ds["file"], "sha256": sha(ds["file"]), "url": DS_URL, "same_bytes_at": DS_URL_ALT, "title": ds["title"], "revision": "C1003_C0G 2/20/2025", "checked": TODAY},+ "specs_read_from_datasheet": {"capacitance": "0.12 uF +/-5 % (124, J), p.1", "rated_voltage": "100 V (code 1), p.1", "dielectric": "C0G, p.1",+ "tcc": "+/-30 ppm/C, -55 to +125 C, p.1 and p.4", "voltage": "no capacitance change with applied rated DC voltage, p.2",+ "aging": "0 %, p.4", "df": "0.1 %, p.4", "ir": "1,000 MOhm uF or 100 GOhm, p.4", "dwv": "250 % rated, p.4",+ "dimensions_mm": {"L": "4.50 +/-0.30", "W": "3.20 +/-0.30", "T": "1.40 +/-0.15 (GH, p.8 and p.10)", "B": "0.60 +/-0.35", "S_min": "2.30"},+ "packaging": "TU: 7 inch reel, unmarked, 1,000 pcs plastic, 12 mm tape, 8 mm pitch (p.2, p.10, p.16)",+ "marking": "C0G supplied unmarked only, p.15", "soldering": "reflow only (1812), p.3 and p.13"},+ "symbol_source": "Original, written for this page (standard capacitor graphic, non-polar, pin ends on the 2.54 mm grid). CC-BY-4.0.",+ "footprint_source": f"KiCad standard library {P['fp_lib']}:{P['fp']} (CC-BY-SA 4.0 with the KiCad library exception); renamed {FP[:-10]}, model path ${{KIPRJMOD}}/{STEP}; copper unchanged",+ "footprint_upstream_url": f"{GL}/kicad-footprints/-/raw/master/{P['fp_lib']}.pretty/{P['fp']}.kicad_mod",+ "footprint_upstream_sha256": hashlib.sha256(open(os.path.join(ROOT, "dl/kicad", P["fp"] + ".kicad_mod"), "rb").read()).hexdigest(),+ "manufacturer_step": {"found": True, "url": STEP_URL,+ "also_served_at": "https://connect.kemet.com:7667/gateway/IntelliData-ComponentDocumentation/1.0/download/step/C1812C124J1GACTU (same bytes)",+ "method": "plain HTTP GET, no browser, no session", "retrieved": TODAY, "file": ORIG, "sha256": sha(ORIG),+ "header_name": "CCS_CCSTD_1812_L4.8W3.5T1.55B0.6.STEP", "authoring": "SolidWorks 2021, 2024-09-06",+ "units": "mm", "up_axis": "+Y", "repose": REPOSE},+ "board_step": {"file": STEP, "sha256": sha(STEP), "transform": repose["matrix_3x4"], "dropped": repose["dropped"], "kept": repose["kept"]},+ "geometry_status": "Manufacturer-issued CAD for this part number, every printed dimension checked for thickness code GH (docs/verify).",+ "marking": {"real_marking": None, "statement": P["marking"], "source": P["marking_src"]},+ "frame": {"units": "mm (STEP), metres (GLB)", "up_axis": "Z", "seat_z": 0, "origin": "footprint origin"},+ "glb": {"tool": "step2glb convert (service)", "source": STEP, "exit": 0},+ "hero_image": {"tool": "step2glb thumbnail --width 1600 --height 1000 (iso, 16:10)", "source": STEP, "path": "docs/hero.png"},+ "hero_glb": fin.get("hero"), "fusion_lbr": fin.get("lbr"), "altium": fin.get("altium"),+ "document_figures": figs,+ "datasheet_check": {"overlay": f"docs/verify/{slug}-overlay.png", "numbers": f"docs/verify/{slug}-check.json", "result": "PASS" if check["all_pass"] else "FAIL"},+ "land_pattern_check": {"readme": "land-pattern/README.md", "numbers": "land-pattern/land-check.json", "source": L["source"], "levels": LV,+ "result": "KiCad matches KEMET level B except land width (3.40 vs 3.50 mm); KiCad kept"},+ "distributors": {"tool": "adom-parts-search search " + MPN, "date": TODAY,+ "mouser": {"part": D["mouser"][0], "url": D["mouser"][1], "stock": D["mouser"][2], "price": D["mouser"][3]},+ "digikey": {"part": D["digikey"][0], "url": D["digikey"][1], "stock": D["digikey"][2], "price": D["digikey"][3]},+ "lcsc": None, "lcsc_note": P["lcsc_note"]},+ "lod": {"manifest": "lod/manifest.json", "generator": "provenance/tools/lod.py", "source": STEP, "source_sha256": sha(STEP)},+ "tools_note": "Scripts adapted from adom/grm188r72a103ka01d and adom/msash168sc7224ktca01; the hero generator is adom/component-hero hero_glb.py, unmodified.",+ "files": files, "files_note": "provenance.json itself is not listed.", "built_by": "Claude (AI agent) for John Lauer", "date": TODAY,+}+json.dump(prov, open(p_("provenance.json"), "w"), indent=1)+print("records written", slug, "hero" if have_hero else "-", "altium" if have_alt else "-", "lbr" if have_lbr else "-")
provenance/tools/repose_step.pyadded+117@@ -0,0 +1,117 @@+"""Re-pose KEMET's 1812 STEP into the Adom component frame; keep the package solid, drop the logo glyphs.++KEMET's file (CCS_CCSTD_1812_L4.8W3.5T1.55B0.6, SolidWorks 2021) is Y-up with one corner at the origin:+length on +X (0 to 4.8), thickness on +Y (0 to 1.55), width on -Z (-3.5 to 0). It holds four solids:+the package (one solid, 72 faces, every face coloured: terminals grey, ceramic brown) and three thin+solids (5 um thick) spelling "KEMET" on the top of the ceramic, which carry only SolidWorks' default+lavender. KEMET supplies C0G parts unmarked only (datasheet C1003_C0G p.15), so the logo is CAD+branding, not a marking on the real part: the board-use model keeps the package solid only. The+original download is kept unchanged beside it.++The Adom frame is Z-up, mm, seated on z = 0, centred on the footprint origin, long axis on X.+One rigid transform (rotate +90 deg about X, so y -> z and z -> -y, then translate) is applied with+BRepBuilderAPI_Transform and every face colour of the package is copied onto the moved face.+Nothing is scaled; the package solid's volume and faces are checked unchanged.++usage: repose_step.py in.step out.step (prints one JSON line)+"""+import sys, json, hashlib, math+from OCP.STEPCAFControl import STEPCAFControl_Reader, STEPCAFControl_Writer+from OCP.TDocStd import TDocStd_Document+from OCP.TCollection import TCollection_ExtendedString+from OCP.XCAFDoc import XCAFDoc_DocumentTool, XCAFDoc_ColorType+from OCP.Quantity import Quantity_Color+from OCP.TDF import TDF_LabelSequence+from OCP.IFSelect import IFSelect_RetDone+from OCP.STEPControl import STEPControl_AsIs+from OCP.gp import gp_Trsf, gp_Ax1, gp_Pnt, gp_Dir, gp_Vec+from OCP.Bnd import Bnd_Box+from OCP.BRepBndLib import BRepBndLib+from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform+from OCP.TDataStd import TDataStd_Name+from OCP.Interface import Interface_Static+from OCP.TopExp import TopExp_Explorer+from OCP.TopAbs import TopAbs_SOLID, TopAbs_FACE+from OCP.GProp import GProp_GProps+from OCP.BRepGProp import BRepGProp++src, dst = sys.argv[1], sys.argv[2]+SURF = XCAFDoc_ColorType.XCAFDoc_ColorSurf+doc = TDocStd_Document(TCollection_ExtendedString("XmlOcaf"))+r = STEPCAFControl_Reader(); r.SetColorMode(True); r.SetNameMode(True)+assert r.ReadFile(src) == IFSelect_RetDone+r.Transfer(doc)+st = XCAFDoc_DocumentTool.ShapeTool_s(doc.Main()); ct = XCAFDoc_DocumentTool.ColorTool_s(doc.Main())+free = TDF_LabelSequence(); st.GetFreeShapes(free)+assert free.Length() == 1, free.Length()+lab = free.Value(1)+assert not st.IsAssembly_s(lab)+shp = st.GetShape_s(lab)+bb = lambda s: (lambda b: (BRepBndLib.Add_s(s, b, False), b.Get())[1])(Bnd_Box())+x0, y0, z0, x1, y1, z1 = bb(shp)++def vol(s):+ g = GProp_GProps(); BRepGProp.VolumeProperties_s(s, g); return g.Mass()+solids = []+ex = TopExp_Explorer(shp, TopAbs_SOLID)+while ex.More():+ solids.append(ex.Current()); ex.Next()+solids.sort(key=vol, reverse=True)+body, glyphs = solids[0], solids[1:]+def nfaces(s):+ n = 0; e = TopExp_Explorer(s, TopAbs_FACE)+ while e.More(): n += 1; e.Next()+ return n+dropped = [{"faces": nfaces(g), "volume_mm3": round(vol(g), 5), "bbox_mm": [round(v, 4) for v in bb(g)],+ "thickness_mm": round(bb(g)[4] - bb(g)[1], 4)} for g in glyphs]+assert all(d["thickness_mm"] < 0.01 for d in dropped), dropped++# face colours of the package solid+subs = TDF_LabelSequence(); st.GetSubShapes_s(lab, subs)+face_cols = []+for i in range(1, subs.Length() + 1):+ sl = subs.Value(i); s = st.GetShape_s(sl)+ c = Quantity_Color()+ if s.ShapeType() == TopAbs_FACE and ct.GetColor_s(sl, SURF, c):+ face_cols.append((s, c))+body_faces = []+e = TopExp_Explorer(body, TopAbs_FACE)+while e.More(): body_faces.append(e.Current()); e.Next()+coloured = [(f, next((c for s, c in face_cols if s.IsSame(f)), None)) for f in body_faces]+assert all(c is not None for _, c in coloured), "package face without colour"++rot = gp_Trsf(); rot.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)), math.pi / 2)+tr = gp_Trsf(); tr.SetTranslation(gp_Vec(-(x0 + x1) / 2, (z0 + z1) / 2, -y0))+tot = tr.Multiplied(rot)+mv = BRepBuilderAPI_Transform(body, tot, True)+new = mv.Shape()++doc2 = TDocStd_Document(TCollection_ExtendedString("XmlOcaf"))+st2 = XCAFDoc_DocumentTool.ShapeTool_s(doc2.Main()); ct2 = XCAFDoc_DocumentTool.ColorTool_s(doc2.Main())+nl = st2.AddShape(new, False)+TDataStd_Name.Set_s(nl, TCollection_ExtendedString("C1812C124J1GACTU"))+for f, c in coloured:+ sl = st2.AddSubShape(nl, mv.ModifiedShape(f))+ assert not sl.IsNull(), "moved face not found"+ ct2.SetColor(sl, c, SURF)++Interface_Static.SetCVal_s("write.step.schema", "AP214IS")+Interface_Static.SetCVal_s("write.step.unit", "MM")+w = STEPCAFControl_Writer(); w.SetColorMode(True); w.SetNameMode(True)+w.Transfer(doc2, STEPControl_AsIs)+assert w.Write(dst) == IFSelect_RetDone++nb = bb(new)+sha = lambda f: hashlib.sha256(open(f, "rb").read()).hexdigest()+m = [[round(tot.Value(i, j), 6) for j in range(1, 5)] for i in range(1, 4)]+cols = sorted({(round(c.Red(), 3), round(c.Green(), 3), round(c.Blue(), 3)) for _, c in coloured})+print(json.dumps({"source": src.rsplit("/", 1)[-1], "source_sha256": sha(src), "source_bbox_mm": [round(v, 4) for v in (x0, y0, z0, x1, y1, z1)],+ "source_units": "mm", "source_up_axis": "+Y", "source_solids": len(solids),+ "kept": {"solid": "package (terminals and ceramic)", "faces": len(body_faces), "volume_mm3": round(vol(body), 5),+ "volume_after_mm3": round(vol(new), 5), "face_colours_srgb": cols},+ "dropped": {"what": "KEMET logo glyph solids on the ceramic top (SolidWorks default lavender, no own colour); the real C0G part is unmarked (C1003_C0G p.15)",+ "solids": dropped},+ "rotation": "+90 deg about X (y -> z, z -> -y)",+ "translation_mm": [round(-(x0 + x1) / 2, 4), round((z0 + z1) / 2, 4), round(-y0, 4)], "matrix_3x4": m,+ "output": dst.rsplit("/", 1)[-1], "output_sha256": sha(dst),+ "output_bbox_mm": [round(v, 4) for v in nb], "LWT_mm": [round(nb[3] - nb[0], 4), round(nb[4] - nb[1], 4), round(nb[5] - nb[2], 4)]}))
provenance/tools/verify_live.pyadded+35@@ -0,0 +1,35 @@+"""Check over HTTP that every file page.json declares (component.parts, eda_assets, hero) and every+file in the local page tree is served by the wiki, plus the viewer routes.+usage: verify_live.py <slug>"""+import sys, os, json, urllib.request, urllib.parse, hashlib+slug = sys.argv[1]; D = f"/home/adom/project/uta-dynos/work/pages/{slug}"+W = "https://wiki.adom.inc"+p = json.load(open(os.path.join(D, "page.json")))+declared = set(p["component"]["parts"].values())+for v in p.get("eda_assets", {}).values():+ declared |= {x for k, x in v.items() if k != "provenance"}+declared.add(p["hero"]["path"])+declared |= {"verification.json", "models.json", "lod/manifest.json", "README.md", "page.json"}+local = {os.path.relpath(os.path.join(a, f), D) for a, _, fs in os.walk(D) for f in fs}+def get(url):+ try:+ with urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "adom-verify"}), timeout=60) as r:+ return r.status, r.read()+ except urllib.error.HTTPError as e:+ return e.code, b""+ except Exception as e:+ return str(e), b""+bad = []+for f in sorted(declared | local):+ st, body = get(f"{W}/api/pages/adom/{slug}/files/" + urllib.parse.quote(f))+ same = os.path.exists(os.path.join(D, f)) and hashlib.sha256(body).hexdigest() == hashlib.sha256(open(os.path.join(D, f), "rb").read()).hexdigest()+ ok = st == 200 and len(body) > 0 and (same or f == "page.json")+ if not ok or f in declared:+ print(("OK " if ok else "BAD ") + f"{st} {len(body):>8} {'same' if same else 'DIFF'} {f}{' [declared]' if f in declared else ''}")+ if not ok: bad.append(f)+for r in ("viewer/3d", "viewer/symbol", "viewer/footprint"):+ st, body = get(f"{W}/{r}/adom/component/{slug}")+ print(f"route {r}: {st} {len(body)} bytes"); + if st != 200 or len(body) < 500: bad.append(r)+st, body = get(f"{W}/adom/{slug}"); print("page:", st, len(body))+print(json.dumps({"slug": slug, "checked": len(declared | local), "bad": bad}))