project
Adom Custom Electronic Load
Public Made by Adomby adom
Two-layer digitally controlled electronic-load prototype. Compact R11 is under active electrical, cooling and probe-access review; not a manufacturing release.
← Commit history
Record fine-grid current progress, operating heat cases and AIFlow 0.1.82 release
15 files changed
+1352−6
design/aiflow-0.1.82-release.json+10design/hotspot-v40-evidence.json+77design/hotspot-v42-evidence.json+159design/operating-heat-corners.json+49design/spec-operating-board-heat-attribution.json+432design/step12-hotspot-progress.pngdocs/completion-plan.md+10docs/operating-heat-corners.md+9docs/video-edit-plan.md+3−1tools/derive_operating_heat_corners.py+15tools/hotspot_via_candidate_v40.py+188tools/hotspot_via_candidate_v42.py+199tools/hotspot_via_candidate_v45.py+190tools/plot_hotspot_progress.py+3−3videos/inventory.json+8−2design/aiflow-0.1.82-release.jsonadded+10@@ -0,0 +1,10 @@+{+ "version": "0.1.82",+ "sourceCommit": "925c67272c80e9814d463f5d13eb14d5ed9ad3c2",+ "registryIntegrity": "sha256-36ce49503d1903416f3a0d3e35d6c917f9fb2b1bb97671dcccf62c98129c7e3c",+ "installedBinarySha256": "a0c5fa29793ab27de36ba879a7cd79595fd47a10a9f0a9b0dd97f6a293dbe221",+ "workspaceTestsPassed": 132,+ "preservedCommands": 57,+ "installedOnOtherUsersMachinesVerified": false,+ "sharedFieldsReplaced": false+}
design/hotspot-v40-evidence.jsonadded+77@@ -0,0 +1,77 @@+{+ "status": "isolated-not-landed",+ "candidate": "checkpoints/unclipped-hotspots-v1/candidate-v40.kicad_pcb",+ "boardSha256": "d4af834a5c76fdb31e2b01fd2bdaa8c5dfe52890a4531625f68a781b0b916f2a",+ "nativeDRC": {+ "errors": 0,+ "warnings": 0,+ "opens": 0,+ "sha256": "a5055c397414c4e8a8dba7cb404979a6ed438a243b4f1cc1a7992d96813bda57"+ },+ "solver": {+ "sha256": "202bb43774babdeb18f3e677090d559d01b9b62938bac8aa659326502f82f168",+ "sourceCommit": "e34bcddab94b3f14442bf511a8b6b35bcdb02237",+ "review": "https://wiki.adom.inc/adom/adom-fields/prs/9",+ "sharedRuntimeReplaced": false+ },+ "reports": [+ {+ "path": "checkpoints/unclipped-hotspots-v1/fields-v40-annular-0p1/fields.json",+ "sha256": "291b2c7693250dd03c7fef5a43ef67e61be9b2e5cda954fed3e186ea042adb1f",+ "inputIdentity": {+ "algorithm": "sha256",+ "board": "d4af834a5c76fdb31e2b01fd2bdaa8c5dfe52890a4531625f68a781b0b916f2a",+ "solverVersion": "0.6.2",+ "spec": "9050e3a86363f4958a3792dc41f3fb6a1ffbf09dd74681b343ec3048180c32d0"+ },+ "nets": [+ {+ "net": "LOAD_POS",+ "peakJ": 56.096134185791016,+ "peakAt": [+ 127.14999999999999,+ 84.75+ ],+ "peakLayer": "F.Cu",+ "residual": 9.59246368745904e-08+ },+ {+ "net": "SHUNT_P",+ "peakJ": 66.05319213867188,+ "peakAt": [+ 127.14999999999999,+ 83.25+ ],+ "peakLayer": "B.Cu",+ "residual": 9.407628619566938e-08+ },+ {+ "net": "GND",+ "peakJ": 58.3114013671875,+ "peakAt": [+ 139.65,+ 89.85000000000001+ ],+ "peakLayer": "F.Cu",+ "residual": 9.98356343597552e-08+ }+ ],+ "chips": [],+ "issues": [+ {+ "at": [+ 127.14999999999999,+ 83.25+ ],+ "fix": "Widen the neck or add a parallel path on the other layer with vias; the density map shows where the current squeezes.",+ "kind": "current-neck",+ "layer": "B.Cu",+ "net": "SHUNT_P",+ "reference": null,+ "severity": "medium",+ "what": "SHUNT_P: 66 A/mm\u00b2 at its worst point on B.Cu (3 A total; equivalent full-net width 1.30 mm at 35 \u00b5m; the threshold is 60 A/mm\u00b2)"+ }+ ]+ }+ ]+}
design/hotspot-v42-evidence.jsonadded+159@@ -0,0 +1,159 @@+{+ "status": "isolated-not-landed",+ "candidate": "checkpoints/unclipped-hotspots-v1/candidate-v42.kicad_pcb",+ "boardSha256": "36e33dd703eb232349fb8f7fb3cba15ac747f3acde4b3b388ec3e41166ad926f",+ "nativeDRC": {+ "errors": 0,+ "warnings": 0,+ "opens": 0,+ "sha256": "dc7e1c964c355131230e29e7de249458fda4dc2740ace0fce8bdf8f5ad33bdeb"+ },+ "solver": {+ "sha256": "202bb43774babdeb18f3e677090d559d01b9b62938bac8aa659326502f82f168",+ "sourceCommit": "e34bcddab94b3f14442bf511a8b6b35bcdb02237",+ "review": "https://wiki.adom.inc/adom/adom-fields/prs/9",+ "sharedRuntimeReplaced": false+ },+ "reports": [+ {+ "path": "checkpoints/unclipped-hotspots-v1/fields-v42-operating-heat/fields.json",+ "sha256": "4c6e912cae2d3150e9461506681fca80ef240f6030b8969a29833fbed9062566",+ "inputIdentity": {+ "algorithm": "sha256",+ "board": "36e33dd703eb232349fb8f7fb3cba15ac747f3acde4b3b388ec3e41166ad926f",+ "solverVersion": "0.6.2",+ "spec": "8d584b0ae2f5d08e6231cd5c20c90f496686c0ea19d9688942b581dcf95f429d"+ },+ "nets": [+ {+ "net": "DUT_POS",+ "peakJ": 48.27619934082031,+ "peakAt": [+ 107.1,+ 93.9+ ],+ "peakLayer": "F.Cu",+ "residual": 9.758311065623263e-08+ },+ {+ "net": "LOAD_POS",+ "peakJ": 42.60700988769531,+ "peakAt": [+ 126.5,+ 81.69999999999999+ ],+ "peakLayer": "B.Cu",+ "residual": 9.641151786896595e-08+ },+ {+ "net": "SHUNT_P",+ "peakJ": 61.06366729736328,+ "peakAt": [+ 126.5,+ 83.1+ ],+ "peakLayer": "B.Cu",+ "residual": 9.256156624373384e-08+ },+ {+ "net": "GND",+ "peakJ": 50.12680435180664,+ "peakAt": [+ 119.89999999999999,+ 100.89999999999999+ ],+ "peakLayer": "B.Cu",+ "residual": 9.979511995650379e-08+ },+ {+ "net": "DUT_FUSED",+ "peakJ": 45.19816589355469,+ "peakAt": [+ 109.1,+ 91.9+ ],+ "peakLayer": "F.Cu",+ "residual": 8.970915880576429e-08+ },+ {+ "net": "5V_AUX",+ "peakJ": 18.182907104492188,+ "peakAt": [+ 152.1,+ 90.1+ ],+ "peakLayer": "F.Cu",+ "residual": 7.935777150899023e-08+ }+ ],+ "chips": [+ {+ "airW": 0.009,+ "dielW": 0.022,+ "otherViaW": 0.0,+ "otherVias": 0,+ "planeW": 0.44,+ "platedPadW": 0.0,+ "platedPads": 0,+ "reference": "D1",+ "riseC": 40.1,+ "riseMovingC": 30.2,+ "tabNet": "LOAD_POS",+ "viaW": 0.204,+ "vias": 14,+ "viasWanted": 0,+ "watts": 0.6722222222222223,+ "x": 117.4,+ "y": 91.0+ },+ {+ "airW": 0.004,+ "dielW": 0.0,+ "otherViaW": 0.002,+ "otherVias": 1,+ "planeW": 0.022,+ "platedPadW": 0.0,+ "platedPads": 0,+ "reference": "RSH1",+ "riseC": 16.0,+ "riseMovingC": 8.3,+ "tabNet": "SHUNT_P",+ "viaW": 0.006,+ "vias": 3,+ "viasWanted": 0,+ "watts": 0.030025925925925928,+ "x": 136.625,+ "y": 85.7+ }+ ],+ "issues": [+ {+ "at": [+ 126.5,+ 83.1+ ],+ "fix": "Widen the neck or add a parallel path on the other layer with vias; the density map shows where the current squeezes.",+ "kind": "current-neck",+ "layer": "B.Cu",+ "net": "SHUNT_P",+ "reference": null,+ "severity": "medium",+ "what": "SHUNT_P: 61 A/mm\u00b2 at its worst point on B.Cu (3 A total; equivalent full-net width 1.40 mm at 35 \u00b5m; the threshold is 60 A/mm\u00b2)"+ },+ {+ "at": [+ 117.4,+ 91.0+ ],+ "fix": "Moving air brings it in; in still air it needs more copper under the tab, more vias, or a heatsink.",+ "kind": "tab-rise",+ "layer": "F.Cu",+ "net": "LOAD_POS",+ "reference": "D1",+ "severity": "medium",+ "what": "D1: +40 \u00b0C at its tab in still air (+30 \u00b0C in moving air) against a 35 \u00b0C budget"+ }+ ]+ }+ ]+}
design/operating-heat-corners.jsonadded+49@@ -0,0 +1,49 @@+{+ "status": "nominal-design-corners-not-qualified",+ "firmwareSha256": "85017ff0a3e37e7b5e41604989326fe623e7f73427ecd8e1a29c63a38d003638",+ "inputs": {+ "powerCommandW": 5,+ "powerGuardFactor": 1.1,+ "minVoltageV": 4.5,+ "maxVoltageV": 24,+ "currentCommandCapA": 3,+ "diodeForwardBoundV": 0.55,+ "shuntOhm": 0.02+ },+ "rows": [+ {+ "volts": 4.5,+ "commandCeilingA": 1.1111111111111112,+ "nominalMeasuredGuardA": 1.2222222222222223,+ "diodeBoundW": 0.6722222222222223,+ "shuntBoundW": 0.02987654320987655+ },+ {+ "volts": 5,+ "commandCeilingA": 1.0,+ "nominalMeasuredGuardA": 1.1,+ "diodeBoundW": 0.6050000000000001,+ "shuntBoundW": 0.024200000000000003+ },+ {+ "volts": 12,+ "commandCeilingA": 0.4166666666666667,+ "nominalMeasuredGuardA": 0.4583333333333333,+ "diodeBoundW": 0.2520833333333333,+ "shuntBoundW": 0.004201388888888888+ },+ {+ "volts": 24,+ "commandCeilingA": 0.20833333333333334,+ "nominalMeasuredGuardA": 0.22916666666666666,+ "diodeBoundW": 0.12604166666666666,+ "shuntBoundW": 0.001050347222222222+ }+ ],+ "limits": [+ "Measured guard does not prove true power without calibrated errors and verified shutdown latency.",+ "The 3 A conductor screen remains; these operating corners do not replace fault/transient analysis.",+ "Independent maximum Q1, diode and shunt heat sources overestimate simultaneous heat; do not describe them as one operating point.",+ "Diode forward-voltage bound inherited from the sourced B540C design; reverify temperature and current conditions before release."+ ]+}
design/spec-operating-board-heat-attribution.jsonadded+432@@ -0,0 +1,432 @@+{+ "board": "Adom electronic load R0, 64 x 72 mm, 2 layers; engineering prototype",+ "copperUm": 35,+ "clearance": 0.2,+ "edgeClearance": 0.2,+ "fixedRefs": [+ "MP1",+ "MP2",+ "MP3",+ "MP4",+ "J1",+ "J2",+ "J3",+ "J4",+ "J5",+ "J6",+ "J7"+ ],+ "wideNets": {+ "DUT_POS": [+ 2.0,+ 1.5,+ 1.2+ ],+ "DUT_FUSED": [+ 2.0,+ 1.5,+ 1.2+ ],+ "LOAD_POS": [+ 2.0,+ 1.5,+ 1.2+ ],+ "SHUNT_P": [+ 2.0,+ 1.5,+ 1.2+ ],+ "GND": [+ 2.0,+ 1.5,+ 1.2+ ]+ },+ "midNets": {+ "5V_AUX": [+ 0.5,+ 0.4+ ],+ "V_GATE": [+ 0.3,+ 0.25+ ]+ },+ "kelvin": {+ "U4.10": "RSH1.2",+ "U4.9": "RSH1.3"+ },+ "chains": [+ {+ "refs": [+ "R8",+ "R9",+ "R10"+ ],+ "direction": "down"+ },+ {+ "refs": [+ "R18",+ "R19"+ ],+ "direction": "down"+ },+ {+ "refs": [+ "R22",+ "R23"+ ],+ "direction": "down"+ },+ {+ "refs": [+ "R27",+ "R28"+ ],+ "direction": "down"+ },+ {+ "refs": [+ "R35",+ "R36",+ "R37"+ ],+ "direction": "down"+ }+ ],+ "loads": {+ "DUT_POS": {+ "amps": 3.000220385674931,+ "maxRiseC": 10,+ "terminals": [+ {+ "reference": "J1",+ "pad": "1",+ "amps": 3.000220385674931+ },+ {+ "reference": "F1",+ "pad": "1",+ "amps": -3+ },+ {+ "reference": "R24",+ "pad": "1",+ "amps": -0.00022038567493112948+ },+ {+ "reference": "TP1",+ "pad": "1",+ "amps": 0+ }+ ],+ "currentModel": "resistive-branches"+ },+ "DUT_FUSED": {+ "amps": 3,+ "maxRiseC": 10,+ "terminals": [+ {+ "reference": "F1",+ "pad": "2",+ "amps": 3+ },+ {+ "reference": "D1",+ "pad": "2",+ "amps": -3+ }+ ],+ "currentModel": "resistive-branches"+ },+ "LOAD_POS": {+ "amps": 3.0000289156626505,+ "maxRiseC": 10,+ "terminals": [+ {+ "reference": "D1",+ "pad": "1",+ "amps": 3.0000289156626505+ },+ {+ "reference": "Q1",+ "pad": "2",+ "amps": -3+ },+ {+ "reference": "U4",+ "pad": "8",+ "amps": -2.891566265060241e-05+ }+ ]+ },+ "SHUNT_P": {+ "amps": 3,+ "maxRiseC": 10,+ "terminals": [+ {+ "reference": "Q1",+ "pad": "3",+ "amps": 3+ },+ {+ "reference": "RSH1",+ "pad": "1",+ "amps": -3+ }+ ]+ },+ "GND": {+ "amps": 3,+ "maxRiseC": 10,+ "terminals": [+ {+ "reference": "RSH1",+ "pad": "4",+ "amps": 3+ },+ {+ "reference": "J2",+ "pad": "1",+ "amps": -3+ }+ ]+ },+ "5V_AUX": {+ "amps": 0.3,+ "maxRiseC": 10+ }+ },+ "hot": {+ "D1": {+ "watts": 0.6722222222222223,+ "tabNet": "LOAD_POS"+ },+ "RSH1": {+ "watts": 0.030025925925925928,+ "tabNet": "SHUNT_P"+ }+ },+ "hotMaxRiseC": 35,+ "pours": [+ {+ "net": "GND",+ "layers": [+ "B.Cu"+ ],+ "priority": 0,+ "connection": "solid",+ "name": "Power return and signal reference",+ "polygon": [+ [+ 98.3,+ 30.3+ ],+ [+ 161.7,+ 30.3+ ],+ [+ 161.7,+ 101.7+ ],+ [+ 98.3,+ 101.7+ ]+ ]+ },+ {+ "net": "GND",+ "layers": [+ "F.Cu"+ ],+ "priority": 0,+ "connection": "solid",+ "name": "Top return and shield",+ "polygon": [+ [+ 98.3,+ 30.3+ ],+ [+ 161.7,+ 30.3+ ],+ [+ 161.7,+ 101.7+ ],+ [+ 98.3,+ 101.7+ ]+ ]+ },+ {+ "name": "D1 heat spread front",+ "net": "LOAD_POS",+ "layers": [+ "F.Cu"+ ],+ "polygon": [+ [+ 106.5,+ 87.5+ ],+ [+ 114.5,+ 87.5+ ],+ [+ 114.5,+ 95+ ],+ [+ 106.5,+ 95+ ]+ ],+ "priority": 3,+ "connection": "solid",+ "clearance": 0.2,+ "minThickness": 0.25+ },+ {+ "name": "D1 heat spread back",+ "net": "LOAD_POS",+ "layers": [+ "B.Cu"+ ],+ "polygon": [+ [+ 106.5,+ 87.5+ ],+ [+ 114.5,+ 87.5+ ],+ [+ 114.5,+ 95+ ],+ [+ 106.5,+ 95+ ]+ ],+ "priority": 3,+ "connection": "solid",+ "clearance": 0.2,+ "minThickness": 0.25+ },+ {+ "name": "Shunt positive spread front",+ "net": "SHUNT_P",+ "layers": [+ "F.Cu"+ ],+ "polygon": [+ [+ 134.5,+ 84.7+ ],+ [+ 138,+ 84.7+ ],+ [+ 138,+ 88+ ],+ [+ 134.5,+ 88+ ]+ ],+ "priority": 3,+ "connection": "solid",+ "clearance": 0.2,+ "minThickness": 0.25+ },+ {+ "name": "Shunt positive spread back",+ "net": "SHUNT_P",+ "layers": [+ "B.Cu"+ ],+ "polygon": [+ [+ 134.5,+ 84.7+ ],+ [+ 138,+ 84.7+ ],+ [+ 138,+ 88+ ],+ [+ 134.5,+ 88+ ]+ ],+ "priority": 3,+ "connection": "solid",+ "clearance": 0.2,+ "minThickness": 0.25+ }+ ],+ "notPoured": [+ "BOOST_SW",+ "SERVO_FB",+ "SHUNT_P_SENSE",+ "SHUNT_N_SENSE",+ "I_SET",+ "DAC_OUT",+ "SET_DIV"+ ],+ "fab": {+ "rules": "fab",+ "target": "fab"+ },+ "sourcing": {+ "profile": "fab"+ },+ "molecule": {+ "pin": "medium",+ "gridMm": 2,+ "edgeMarginMm": 2,+ "cornerPinsNet": "GND"+ },+ "requireEvidence": [+ "calcs",+ "simulation"+ ],+ "qualificationNote": "Simultaneous 5 W/24 V/3 A limits; 3 A conductor, diode and shunt screens retained conservatively although initial DUT is 5 V/1 A. 25 W is a future option, not R0 rating. Actual cooling, USB and contact ratings remain unverified.",+ "planes": {+ "GND": "B.Cu"+ },+ "netClearance": {+ "USB_VBUS": 0.27+ },+ "stackupMm": [+ 1.53+ ],+ "dcMaxIterations": 60000,+ "diagnosticNote": "Nominal R0 measured-power guard corner at 4.5 V, D1 0.55 V at 25 C source bound, shunt +0.5% resistance tolerance. Q1 deliberately omitted for attribution. Original 3 A conductor/Joule screen retained conservatively. Not a full operating point or acceptance.",+ "branchAssumptions": {+ "DUT_POS": "24 V across a 100k/10k series divider, both -1 percent; full 3 A F1 path retained. ADC input leakage is not included.",+ "LOAD_POS": "INA226 VBUS nominal 830 kohm (typical, no max specified here) gives 28.916 uA at 24 V. Nominal-only boundary, not guaranteed maximum."+ },+ "branchCurrentModel": {+ "boardThicknessMm": 1.6,+ "barrelPlatingUm": 25,+ "maxBarrelDensityAperMm2": 60+ }+}
design/step12-hotspot-progress.png⋯ 1 unchanged line ⋯
docs/completion-plan.md+10@@ -1,3 +1,13 @@+## Latest continuation — v45 source-bend refinement++AIFlow 0.1.82 is source-pushed, registry-published and installed locally. It independently enforces each hot part’s heat, tab net, individual rise/theta budget and thermal convergence; 132 tests, all 57 commands and workflow regressions pass. Fields PR9 remains under owner review; shared Fields is unchanged.++V40’s 0.1 mm three-net solve improves ground to 58.31 and drain to 56.10 A/mm², both below the unchanged 60 screen. SHUNT_P remains 66.05 at the source-to-bus bend. V43/v44 were rejected by native spacing checks; v45 passes native DRC with zero errors, warnings and opens and is undergoing fine-grid analysis. No candidate is landed.++Thermal operating cases now explicitly derive from the existing 5 W / 4.5–24 V firmware envelope, retaining the separate 3 A conductor screen. Expanded diode copper and four additional thermal vias in v42 still predict about 40 C still-air rise, above 35 C. Package/heatsink transfer and physical fan performance remain unresolved. These are diagnostic studies, not accepted board evidence.++Dashboard updated with completed fine-grid results. 70 raw takes, 17 reviewed excerpts and 13 storyboard clips; the final narrated film is not yet assembled. Current/thermal/finish gates remain open.+ ## Latest continuation — v30 fine mesh running V27 failed 0.1 mm with GND 72.94, LOAD_POS 60.66 and SHUNT_P 70.56 A/mm2. V29 splits the return transfer and widens the two necks; v30 additionally fills the inside shunt bend. V30 passes native DRC 0/0/0 and the 0.2 mm three-net screen. Fresh v29/v30 fine solves are running. V30 plan is prepared but marked DO NOT EXECUTE; zone definitions and existing copper nets are unchanged. Installed AIFlow 0.1.81 correctly rejects the v27 fine result in an isolated CLI regression. The new current-map review clip brings raw takes to 70; storyboard now has 13 clips. Live board unchanged, current and thermal gates open.
docs/operating-heat-corners.mdadded+9@@ -0,0 +1,9 @@+# R0 operating heat corners++The existing firmware caps the current command at `min(3 A, 5 W / V)` and refuses measured input below 4.5 V. At the initial 5 V DUT, the command ceiling is therefore 1 A. The 3 A conductor screen is retained as a separate conservative current/fault screen; it is not a permitted continuous R0 operating point.++The existing measured-power guard trips above 110% of the 5 W command ceiling. At the 4.5 V floor this corresponds nominally to 1.222 A. Using the inherited 0.55 V diode-drop bound and 20 mΩ shunt gives 0.672 W in D1 and 0.0299 W in RSH1. These are nominal design calculations; measurement error, resistor tolerance, cold forward voltage and physical shutdown latency remain to be bounded. The [B540C manufacturer sheet](https://www.diodes.com/datasheet/download/B540C.pdf) specifies the 0.55 V maximum at 5 A and 25 C, not across every temperature.++The earlier 1.65 W diode / 0.18 W shunt test remains useful as an overload sensitivity case. It must not be described as the nominal continuous heat budget of this 5 W, 4.5–24 V firmware configuration. Conversely, reducing those heat inputs in an operating-case study does not make the 3 A conductor test pass or qualify the assembly. Keep the MOSFET case-to-sink model, board heat, real airflow and fault-transient energy checks distinct.++Reproduce with `python3 tools/derive_operating_heat_corners.py`; the report hashes the firmware policy and refuses if its envelope expressions changed. [Calculated cases](../design/operating-heat-corners.json).
docs/video-edit-plan.md+3−1@@ -4,7 +4,7 @@ Target: 4–5 minutes, narrated. This is a process film about an engineering pro ## Recorded material -The latest inventory contains 69 unique raw takes and 16 reviewed excerpts as of 10 October. Raw counts include failed/static takes; action cuts and speed variants are not additional recordings. See `videos/inventory.json` and [reviewed clips](build-clips.md).+The latest inventory contains 70 unique raw takes and 17 reviewed excerpts as of 10 October. Raw counts include failed/static takes; action cuts and speed variants are not additional recordings. See `videos/inventory.json` and [reviewed clips](build-clips.md). Use the real placement corrections, native board views, Mouser denial/native-browser comparison, simulated fan state transitions, ground creation and actual route commits. Exclude static setup, the dark fan preview and failed close-up. The Mouser material is a later walkthrough; do not imply uninterrupted original discovery. Fan RPM is simulated, with the existing 0.02× motion display label preserved. @@ -29,3 +29,5 @@ The original router debugging, regression-test work and direct AIFlow release we ## Current-screen discovery sequence The 10 October Fields clip is an original diagnostic walkthrough, but its 57.9 A/mm² display statistic was subsequently found to be percentile-clipped. If used, immediately explain the reporting correction and show the corrected findings: GND 76.36, LOAD_POS 68.60 and SHUNT_P 60.97 A/mm². Never edit the earlier image into evidence of a passing gate. The discovery and source correction themselves were not filmed; any later explanation must be labeled a review. Capture the actual accepted copper rework when it lands, then show the fresh same-board solve.++The later corrected-model review is now storyboard clip 13. It shows v27 fine-grid failures, not acceptance. Keep it short and connect it to the actual later copper correction; do not use the coarse-grid chart as a final pass. The final native landing, final checks and completed package shots remain to be recorded.
tools/derive_operating_heat_corners.pyadded+15@@ -0,0 +1,15 @@+"""Derive nominal R0 corners from the existing firmware envelope; no qualification."""+from pathlib import Path+import json,hashlib+root=Path(__file__).resolve().parents[1]+firmware=root/'firmware/load_core.c';code=firmware.read_text()+# Fail if the policy changes; this calculation must be reviewed with it.+for expression in ['s->volts<4.5f','s->volts>24','c->power_limit_w>5','c->power_limit_w*1.10f','fminf(3.0f,c->power_limit_w/s->volts)']:+ if expression not in code:raise ValueError('Firmware envelope changed: '+expression)+rows=[]+for v in [4.5,5,12,24]:+ command=min(3,5/v);guard=5.5/v+ rows.append(dict(volts=v,commandCeilingA=command,nominalMeasuredGuardA=guard,diodeBoundW=guard*.55,shuntBoundW=guard*guard*.02))+report=dict(status='nominal-design-corners-not-qualified',firmwareSha256=hashlib.sha256(firmware.read_bytes()).hexdigest(),inputs=dict(powerCommandW=5,powerGuardFactor=1.1,minVoltageV=4.5,maxVoltageV=24,currentCommandCapA=3,diodeForwardBoundV=.55,shuntOhm=.02),rows=rows,limits=['Measured guard does not prove true power without calibrated errors and verified shutdown latency.','The 3 A conductor screen remains; these operating corners do not replace fault/transient analysis.','Independent maximum Q1, diode and shunt heat sources overestimate simultaneous heat; do not describe them as one operating point.','Diode forward-voltage bound inherited from the sourced B540C design; reverify temperature and current conditions before release.'])+(root/'design/operating-heat-corners.json').write_text(json.dumps(report,indent=2)+'\n')+print(json.dumps(rows,indent=2))
tools/hotspot_via_candidate_v40.pyadded+188@@ -0,0 +1,188 @@+"""Run using native desktop KiCad Python; modifies only an isolated board copy."""+import pcbnew,sys,json+from pathlib import Path+source=Path(sys.argv[1]);out=Path(sys.argv[2]);board=pcbnew.LoadBoard(str(source));nets=board.GetNetsByName()+changes=[]+for net,x,y in [('GND',133.0,100.8),('GND',138.0,100.8),('GND',139.0,93.0),('GND',136.5,93.0),('GND',139.4,90.4),('GND',139.4,91.4),('GND',139.4,92.4)]:+ via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(x),pcbnew.FromMM(y)));via.SetWidth(pcbnew.FromMM(.8));via.SetDrill(pcbnew.FromMM(.4));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets[net]);board.Add(via);changes.append({'kind':'via','net':net,'at':[x,y],'sizeMm':.8,'drillMm':.4,'uuid':str(via.m_Uuid.AsString())})+for item in board.GetTracks():+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetWidth(pcbnew.FromMM(1.0));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','uuid':uid,'net':'LOAD_POS','sizeMm':1.0,'drillMm':.6})+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid in ['91eed079-74e4-4863-8dc1-f211a19b6ee0','ad353074-a567-44a9-a20a-744b8057c5a9']:+ board.Remove(item);changes.append({'kind':'remove-track','uuid':uid})+ if uid=='aa6c5964-74d4-410e-a69e-ffb2cd9c9999':+ item.SetWidth(pcbnew.FromMM(1.0));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','uuid':uid,'sizeMm':1.0,'drillMm':.6})+def track(net,layer,a,b,width):+ t=pcbnew.PCB_TRACK(board);t.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(a[0]),pcbnew.FromMM(a[1])));t.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(b[0]),pcbnew.FromMM(b[1])));t.SetWidth(pcbnew.FromMM(width));t.SetLayer(layer);t.SetNet(nets[net]);board.Add(t);changes.append({'kind':'track','net':net,'start':a,'end':b,'widthMm':width,'layer':board.GetLayerName(layer)})+for a,b in [((127.46,82),(127.46,83.3)),((127.46,83.3),(126.4,84.36)),((126.4,84.36),(126.4,86.5))]:track('LOAD_POS',pcbnew.F_Cu,a,b,1.5)+for a,b in [((139.4,90.4),(139.4,92)),((139.4,92),(138.4,93)),((138.4,93),(136.5,93))]:track('GND',pcbnew.B_Cu,a,b,1.5)+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='GND':+ p=item.GetPosition();xy=(round(pcbnew.ToMM(p.x),3),round(pcbnew.ToMM(p.y),3))+ if xy in [(139.4,86.8),(136.5,93.0),(139.4,90.4),(139.4,91.4),(139.4,92.4)]:item.SetWidth(pcbnew.FromMM(1));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','at':xy,'sizeMm':1,'drillMm':.6})+for fp in board.GetFootprints():+ delta={'Q2':-.3,'U3':.3,'R7':.1}.get(fp.GetReference())+ if delta:+ pos=fp.GetPosition();pos.x+=pcbnew.FromMM(delta);fp.SetPosition(pos);changes.append({'kind':'move-footprint','ref':fp.GetReference(),'dxMm':delta})+for item in list(board.GetTracks()):+ if item.GetNetname()=='SHUNT_P_SENSE' and isinstance(item,pcbnew.PCB_VIA):+ pos=item.GetPosition()+ if abs(pcbnew.ToMM(pos.x)-123.6)<.001 and abs(pcbnew.ToMM(pos.y)-85.4)<.001:+ pos.x=pcbnew.FromMM(123.3);item.SetPosition(pos);changes.append({'kind':'move-sense-via','from':[123.6,85.4],'to':[123.3,85.4]})+ if isinstance(item,pcbnew.PCB_VIA):continue+ if item.GetNetname()=='SHUNT_P_SENSE':+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ pos=getter()+ if abs(pcbnew.ToMM(pos.x)-123.6)<.001 and abs(pcbnew.ToMM(pos.y)-85.4)<.001:pos.x=pcbnew.FromMM(123.3);setter(pos)+ if item.GetNetname()=='SHUNT_N_SENSE' and item.GetLayer()==pcbnew.F_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ pos=getter();x,y=pcbnew.ToMM(pos.x),pcbnew.ToMM(pos.y)+ if 128.79<x<129.31 and 87.29<y<88.81:pos.x+=pcbnew.FromMM(.2);setter(pos)+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.4),pcbnew.FromMM(86.7)))+ if uid=='13d2f936-e4d3-40cb-9bf7-1b6b26ca4cf2':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(126.4),pcbnew.FromMM(86.7)));item.SetWidth(pcbnew.FromMM(1.5))+ if uid=='f120f8db-288f-4938-acbf-98ff5e4065df':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(133.5),pcbnew.FromMM(84.2)));item.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(84.2)));item.SetWidth(pcbnew.FromMM(1.9))+ if not isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='LOAD_POS' and item.GetLayer()==pcbnew.F_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter()+ if abs(pcbnew.ToMM(p.x)-126.4)<.001 and abs(pcbnew.ToMM(p.y)-86.5)<.001:p.y=pcbnew.FromMM(86.7);setter(p)+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA) or item.GetLayer()!=pcbnew.B_Cu or item.GetNetname()!='SHUNT_P_SENSE':continue+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(y-85.4)<.001 and (abs(x-123.3)<.001 or abs(x-132)<.001):+ p.y=pcbnew.FromMM(85.6)+ if abs(x-132)<.001:p.x=pcbnew.FromMM(132.2)+ setter(p)+# Ease the drain right-angle turn; relocate its adjacent setpoint via with endpoints.+track('LOAD_POS',pcbnew.B_Cu,(126.35,89.0),(125.65,89.7),1.5)+# Preserve explicit branch junctions instead of overlapped dangling endpoints.+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetWidth(pcbnew.FromMM(.8));item.SetDrill(pcbnew.FromMM(.4));item.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.05),pcbnew.FromMM(86.7)))+ if uid in ['b486fb59-b64e-41ee-a1a7-5a72fcf0a780','8e5d5e2b-78f3-4ed2-af96-409d2ea9d57b']:+ a=item.GetStart();b=item.GetEnd();start=(pcbnew.ToMM(a.x),pcbnew.ToMM(a.y));end=(pcbnew.ToMM(b.x),pcbnew.ToMM(b.y))+ mid=(126.35,89.0) if uid=='b486fb59-b64e-41ee-a1a7-5a72fcf0a780' else (125.65,89.7)+ board.Remove(item);track('LOAD_POS',pcbnew.B_Cu,start,mid,1.5);track('LOAD_POS',pcbnew.B_Cu,mid,end,1.5)+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(86.7)));via.SetWidth(pcbnew.FromMM(.8));via.SetDrill(pcbnew.FromMM(.4));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['LOAD_POS']);board.Add(via)+# Do not enlarge holes through narrow in-plane paths. Keep the three dedicated shunt thermal vias.+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid in ['34d3207a-b14a-40d8-be82-5b108c2b867b','9a71af6b-ea83-4f97-b7f6-3f69253c7314']:+ board.Remove(item);continue+ if isinstance(item,pcbnew.PCB_VIA):+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if uid=='aa6c5964-74d4-410e-a69e-ffb2cd9c9999' or (item.GetNetname()=='GND' and abs(x-139.4)<.001 and abs(y-86.8)<.001):+ item.SetWidth(pcbnew.FromMM(.8));item.SetDrill(pcbnew.FromMM(.4))+# Move the transfer hole off the center of the shunt bus; remove a nearly unused ground hole through the return neck.+for item in list(board.GetTracks()):+ if not isinstance(item,pcbnew.PCB_VIA):continue+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if item.GetNetname()=='GND' and abs(x-139.4)<.001 and abs(y-86.8)<.001:board.Remove(item)+ if str(item.m_Uuid.AsString())=='aa6c5964-74d4-410e-a69e-ffb2cd9c9999':item.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(133.7),pcbnew.FromMM(83.6)))+track('SHUNT_P',pcbnew.F_Cu,(133.7,83.6),(133.8,84.2),1.2)+# Preserve the full plated Q1 source connection; remove its redundant neck-drilling spur.+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid in ['cd48c727-15bf-4276-b4b8-ae4d936d829d','30d9e8bc-805a-4b1d-8a79-ad625ee59388']:+ board.Remove(item);continue+ if isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='GND':+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-139.4)<.001 and any(abs(y-t)<.001 for t in [90.4,91.4,92.4]):p.x=pcbnew.FromMM(139.8);item.SetPosition(p)+# Share vertical current with smaller holes and widen the plated-source exit.+for item in board.GetTracks():+ uid=str(item.m_Uuid.AsString())+ if uid in ['2f235494-7976-4048-be2d-b71507b2b45e','ec56cc46-09f5-4ff9-a1b6-61699db68686']:item.SetWidth(pcbnew.FromMM(1.75))+ if isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='GND':+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-139.8)<.001 and any(abs(y-t)<.001 for t in [90.4,91.4,92.4]):+ p.x=pcbnew.FromMM(139.4);item.SetPosition(p);item.SetWidth(pcbnew.FromMM(.6));item.SetDrill(pcbnew.FromMM(.3))+for item in board.GetTracks():+ if str(item.m_Uuid.AsString())=='f120f8db-288f-4938-acbf-98ff5e4065df':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(133.5),pcbnew.FromMM(84.25)));item.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(84.25)));item.SetWidth(pcbnew.FromMM(2.0))+# Fine-grid hotspot revision: spread the return transfer and soften the two power necks.+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA):+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if item.GetNetname()=='GND' and abs(x-139.4)<.001 and abs(y-90.4)<.001:+ p.x=pcbnew.FromMM(139.0);item.SetPosition(p)+ continue+ uid=str(item.m_Uuid.AsString())+ if uid in ['32cd5f76-8c9c-4680-a071-fe4f71064f23','cecc8caa-bb93-470d-a793-50ae2c93732f']:item.SetWidth(pcbnew.FromMM(1.6))+ if item.GetNetname()=='LOAD_POS' and item.GetLayer()==pcbnew.B_Cu:+ a=item.GetStart();b=item.GetEnd()+ if abs(pcbnew.ToMM(a.y)-89.7)<.001 and abs(pcbnew.ToMM(b.y)-89.7)<.001:item.SetWidth(pcbnew.FromMM(1.75))+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(139.8),pcbnew.FromMM(90.4)));via.SetWidth(pcbnew.FromMM(.6));via.SetDrill(pcbnew.FromMM(.3));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['GND']);board.Add(via)+track('GND',pcbnew.B_Cu,(139.0,90.4),(139.8,90.4),1.2)+# Keep clearance beside the setpoint via; widen only the diode-spread junction.+for item in list(board.GetTracks()):+ if isinstance(item,pcbnew.PCB_VIA) or item.GetNetname()!='LOAD_POS' or item.GetLayer()!=pcbnew.B_Cu:continue+ a=item.GetStart();b=item.GetEnd();ax,ay=pcbnew.ToMM(a.x),pcbnew.ToMM(a.y);bx,by=pcbnew.ToMM(b.x),pcbnew.ToMM(b.y)+ if abs(ay-89.7)<.001 and abs(by-89.7)<.001:+ item.SetWidth(pcbnew.FromMM(1.5))+ if min(ax,bx)<120 and max(ax,bx)>125:+ board.Remove(item);track('LOAD_POS',pcbnew.B_Cu,(119.8,89.7),(123.0,89.7),1.75);track('LOAD_POS',pcbnew.B_Cu,(123.0,89.7),(125.65,89.7),1.5)+# Fill the inside shunt-bus bend with a diagonal copper path.+track('SHUNT_P',pcbnew.B_Cu,(133.0,84.7),(134.7,85.5),1.2)+# Spread the plated-source exit without moving the gate or clamp circuit.+track('SHUNT_P',pcbnew.B_Cu,(125.1,81.8),(125.35,82.7),1.9)+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA):continue+ if item.GetNetname()=='LOAD_POS' and item.GetLayer()==pcbnew.B_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-126.35)<.001 and abs(y-89)<.001:p.y=pcbnew.FromMM(88.85);setter(p)+ if abs(x-125.65)<.001 and abs(y-89.7)<.001:p.x=pcbnew.FromMM(125.5);setter(p)+for x in [139.0,139.8]:+ via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(x),pcbnew.FromMM(89.9)));via.SetWidth(pcbnew.FromMM(.6));via.SetDrill(pcbnew.FromMM(.3));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['GND']);board.Add(via)+track('GND',pcbnew.B_Cu,(139,89.9),(139.8,89.9),.6)+track('GND',pcbnew.B_Cu,(139.4,89.9),(139.4,90.4),.6)+# Give the ground transfer more uninterrupted top copper; keep UART endpoints fixed.+for item in list(board.GetTracks()):+ if isinstance(item,pcbnew.PCB_VIA) or item.GetLayer()!=pcbnew.F_Cu:continue+ a=item.GetStart();b=item.GetEnd();ax,ay=pcbnew.ToMM(a.x),pcbnew.ToMM(a.y);bx,by=pcbnew.ToMM(b.x),pcbnew.ToMM(b.y)+ if item.GetNetname()=='UART_RX' and abs(ax-140.7)<.001 and abs(bx-140.7)<.001 and abs(ay-83.5)<.001 and abs(by-93.2)<.001:+ board.Remove(item)+ points=[(140.7,83.5),(140.7,88.0),(141.0,88.3),(141.0,91.8),(140.7,92.1),(140.7,93.2)]+ for p,q in zip(points,points[1:]):track('UART_RX',pcbnew.F_Cu,p,q,.25)+ if item.GetNetname()=='UART_TX' and abs(ax-137.9)<.001 and abs(bx-137.9)<.001 and abs(ay-83.7)<.001 and abs(by-98.1)<.001:+ board.Remove(item)+ points=[(137.9,83.7),(137.9,89.0),(137.4,89.5),(137.4,93.3),(137.9,93.8),(137.9,98.1)]+ for p,q in zip(points,points[1:]):track('UART_TX',pcbnew.F_Cu,p,q,.25)+# Move the setpoint layer transfer clear of a wider drain bend, preserving its net/endpoints.+for item in board.GetTracks():+ if item.GetNetname()=='I_SET':+ endpoints=[(item.GetPosition,item.SetPosition)] if isinstance(item,pcbnew.PCB_VIA) else [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]+ for getter,setter in endpoints:+ p=getter()+ if abs(pcbnew.ToMM(p.x)-125)<.001 and abs(pcbnew.ToMM(p.y)-88.4)<.001:p.y=pcbnew.FromMM(88);setter(p)+ if isinstance(item,pcbnew.PCB_VIA) or item.GetNetname()!='LOAD_POS' or item.GetLayer()!=pcbnew.B_Cu:continue+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-126.35)<.001 and abs(y-88.85)<.001:p.y=pcbnew.FromMM(89);setter(p)+ if abs(x-125.5)<.001 and abs(y-89.7)<.001:p.x=pcbnew.FromMM(125.65);setter(p)+ a=item.GetStart();b=item.GetEnd();ax,ay=pcbnew.ToMM(a.x),pcbnew.ToMM(a.y);bx,by=pcbnew.ToMM(b.x),pcbnew.ToMM(b.y)+ if abs(ay-89.7)<.001 and abs(by-89.7)<.001:item.SetWidth(pcbnew.FromMM(1.75))+ if abs(ax-bx)>.1 and abs(ay-by)>.1 and min(ax,bx)>125 and max(ax,bx)<127 and min(ay,by)>88.8 and max(ay,by)<89.8:item.SetWidth(pcbnew.FromMM(1.75))+# This local gate-drive segment carries the servo/gate current, not DUT current.+# Keep 0.5 mm copper and create clearance to the small setpoint via.+for item in board.GetTracks():+ if str(item.m_Uuid.AsString())=='a4e6790e-b900-47a1-bf1e-b5fb98adc8c7':item.SetWidth(pcbnew.FromMM(.5))+expected_nets={str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}+board.BuildConnectivity()+actual_nets={str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}+assert actual_nets==expected_nets, {'unexpectedNetChanges':[(k,v,actual_nets.get(k)) for k,v in expected_nets.items() if actual_nets.get(k)!=v]}+for zone in board.Zones():zone.SetNeedRefill(True)+pcbnew.ZONE_FILLER(board).Fill(board.Zones());assert {str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}==expected_nets, 'Zone fill changed copper nets'+pcbnew.SaveBoard(str(out),board)+readback=pcbnew.LoadBoard(str(out))+assert {str(item.m_Uuid.AsString()):item.GetNetname() for item in readback.GetTracks()}==expected_nets, 'Save/readback changed copper nets' +out.with_suffix('.changes.json').write_text(json.dumps({'source':str(source),'candidate':str(out),'changes':changes},indent=2));print(json.dumps({'saved':str(out),'changes':changes}))
tools/hotspot_via_candidate_v42.pyadded+199@@ -0,0 +1,199 @@+"""Run using native desktop KiCad Python; modifies only an isolated board copy."""+import pcbnew,sys,json+from pathlib import Path+source=Path(sys.argv[1]);out=Path(sys.argv[2]);board=pcbnew.LoadBoard(str(source));nets=board.GetNetsByName()+changes=[]+for net,x,y in [('GND',133.0,100.8),('GND',138.0,100.8),('GND',139.0,93.0),('GND',136.5,93.0),('GND',139.4,90.4),('GND',139.4,91.4),('GND',139.4,92.4)]:+ via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(x),pcbnew.FromMM(y)));via.SetWidth(pcbnew.FromMM(.8));via.SetDrill(pcbnew.FromMM(.4));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets[net]);board.Add(via);changes.append({'kind':'via','net':net,'at':[x,y],'sizeMm':.8,'drillMm':.4,'uuid':str(via.m_Uuid.AsString())})+for item in board.GetTracks():+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetWidth(pcbnew.FromMM(1.0));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','uuid':uid,'net':'LOAD_POS','sizeMm':1.0,'drillMm':.6})+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid in ['91eed079-74e4-4863-8dc1-f211a19b6ee0','ad353074-a567-44a9-a20a-744b8057c5a9']:+ board.Remove(item);changes.append({'kind':'remove-track','uuid':uid})+ if uid=='aa6c5964-74d4-410e-a69e-ffb2cd9c9999':+ item.SetWidth(pcbnew.FromMM(1.0));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','uuid':uid,'sizeMm':1.0,'drillMm':.6})+def track(net,layer,a,b,width):+ t=pcbnew.PCB_TRACK(board);t.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(a[0]),pcbnew.FromMM(a[1])));t.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(b[0]),pcbnew.FromMM(b[1])));t.SetWidth(pcbnew.FromMM(width));t.SetLayer(layer);t.SetNet(nets[net]);board.Add(t);changes.append({'kind':'track','net':net,'start':a,'end':b,'widthMm':width,'layer':board.GetLayerName(layer)})+for a,b in [((127.46,82),(127.46,83.3)),((127.46,83.3),(126.4,84.36)),((126.4,84.36),(126.4,86.5))]:track('LOAD_POS',pcbnew.F_Cu,a,b,1.5)+for a,b in [((139.4,90.4),(139.4,92)),((139.4,92),(138.4,93)),((138.4,93),(136.5,93))]:track('GND',pcbnew.B_Cu,a,b,1.5)+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='GND':+ p=item.GetPosition();xy=(round(pcbnew.ToMM(p.x),3),round(pcbnew.ToMM(p.y),3))+ if xy in [(139.4,86.8),(136.5,93.0),(139.4,90.4),(139.4,91.4),(139.4,92.4)]:item.SetWidth(pcbnew.FromMM(1));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','at':xy,'sizeMm':1,'drillMm':.6})+for fp in board.GetFootprints():+ delta={'Q2':-.3,'U3':.3,'R7':.1}.get(fp.GetReference())+ if delta:+ pos=fp.GetPosition();pos.x+=pcbnew.FromMM(delta);fp.SetPosition(pos);changes.append({'kind':'move-footprint','ref':fp.GetReference(),'dxMm':delta})+for item in list(board.GetTracks()):+ if item.GetNetname()=='SHUNT_P_SENSE' and isinstance(item,pcbnew.PCB_VIA):+ pos=item.GetPosition()+ if abs(pcbnew.ToMM(pos.x)-123.6)<.001 and abs(pcbnew.ToMM(pos.y)-85.4)<.001:+ pos.x=pcbnew.FromMM(123.3);item.SetPosition(pos);changes.append({'kind':'move-sense-via','from':[123.6,85.4],'to':[123.3,85.4]})+ if isinstance(item,pcbnew.PCB_VIA):continue+ if item.GetNetname()=='SHUNT_P_SENSE':+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ pos=getter()+ if abs(pcbnew.ToMM(pos.x)-123.6)<.001 and abs(pcbnew.ToMM(pos.y)-85.4)<.001:pos.x=pcbnew.FromMM(123.3);setter(pos)+ if item.GetNetname()=='SHUNT_N_SENSE' and item.GetLayer()==pcbnew.F_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ pos=getter();x,y=pcbnew.ToMM(pos.x),pcbnew.ToMM(pos.y)+ if 128.79<x<129.31 and 87.29<y<88.81:pos.x+=pcbnew.FromMM(.2);setter(pos)+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.4),pcbnew.FromMM(86.7)))+ if uid=='13d2f936-e4d3-40cb-9bf7-1b6b26ca4cf2':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(126.4),pcbnew.FromMM(86.7)));item.SetWidth(pcbnew.FromMM(1.5))+ if uid=='f120f8db-288f-4938-acbf-98ff5e4065df':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(133.5),pcbnew.FromMM(84.2)));item.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(84.2)));item.SetWidth(pcbnew.FromMM(1.9))+ if not isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='LOAD_POS' and item.GetLayer()==pcbnew.F_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter()+ if abs(pcbnew.ToMM(p.x)-126.4)<.001 and abs(pcbnew.ToMM(p.y)-86.5)<.001:p.y=pcbnew.FromMM(86.7);setter(p)+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA) or item.GetLayer()!=pcbnew.B_Cu or item.GetNetname()!='SHUNT_P_SENSE':continue+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(y-85.4)<.001 and (abs(x-123.3)<.001 or abs(x-132)<.001):+ p.y=pcbnew.FromMM(85.6)+ if abs(x-132)<.001:p.x=pcbnew.FromMM(132.2)+ setter(p)+# Ease the drain right-angle turn; relocate its adjacent setpoint via with endpoints.+track('LOAD_POS',pcbnew.B_Cu,(126.35,89.0),(125.65,89.7),1.5)+# Preserve explicit branch junctions instead of overlapped dangling endpoints.+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetWidth(pcbnew.FromMM(.8));item.SetDrill(pcbnew.FromMM(.4));item.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.05),pcbnew.FromMM(86.7)))+ if uid in ['b486fb59-b64e-41ee-a1a7-5a72fcf0a780','8e5d5e2b-78f3-4ed2-af96-409d2ea9d57b']:+ a=item.GetStart();b=item.GetEnd();start=(pcbnew.ToMM(a.x),pcbnew.ToMM(a.y));end=(pcbnew.ToMM(b.x),pcbnew.ToMM(b.y))+ mid=(126.35,89.0) if uid=='b486fb59-b64e-41ee-a1a7-5a72fcf0a780' else (125.65,89.7)+ board.Remove(item);track('LOAD_POS',pcbnew.B_Cu,start,mid,1.5);track('LOAD_POS',pcbnew.B_Cu,mid,end,1.5)+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(86.7)));via.SetWidth(pcbnew.FromMM(.8));via.SetDrill(pcbnew.FromMM(.4));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['LOAD_POS']);board.Add(via)+# Do not enlarge holes through narrow in-plane paths. Keep the three dedicated shunt thermal vias.+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid in ['34d3207a-b14a-40d8-be82-5b108c2b867b','9a71af6b-ea83-4f97-b7f6-3f69253c7314']:+ board.Remove(item);continue+ if isinstance(item,pcbnew.PCB_VIA):+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if uid=='aa6c5964-74d4-410e-a69e-ffb2cd9c9999' or (item.GetNetname()=='GND' and abs(x-139.4)<.001 and abs(y-86.8)<.001):+ item.SetWidth(pcbnew.FromMM(.8));item.SetDrill(pcbnew.FromMM(.4))+# Move the transfer hole off the center of the shunt bus; remove a nearly unused ground hole through the return neck.+for item in list(board.GetTracks()):+ if not isinstance(item,pcbnew.PCB_VIA):continue+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if item.GetNetname()=='GND' and abs(x-139.4)<.001 and abs(y-86.8)<.001:board.Remove(item)+ if str(item.m_Uuid.AsString())=='aa6c5964-74d4-410e-a69e-ffb2cd9c9999':item.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(133.7),pcbnew.FromMM(83.6)))+track('SHUNT_P',pcbnew.F_Cu,(133.7,83.6),(133.8,84.2),1.2)+# Preserve the full plated Q1 source connection; remove its redundant neck-drilling spur.+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid in ['cd48c727-15bf-4276-b4b8-ae4d936d829d','30d9e8bc-805a-4b1d-8a79-ad625ee59388']:+ board.Remove(item);continue+ if isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='GND':+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-139.4)<.001 and any(abs(y-t)<.001 for t in [90.4,91.4,92.4]):p.x=pcbnew.FromMM(139.8);item.SetPosition(p)+# Share vertical current with smaller holes and widen the plated-source exit.+for item in board.GetTracks():+ uid=str(item.m_Uuid.AsString())+ if uid in ['2f235494-7976-4048-be2d-b71507b2b45e','ec56cc46-09f5-4ff9-a1b6-61699db68686']:item.SetWidth(pcbnew.FromMM(1.75))+ if isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='GND':+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-139.8)<.001 and any(abs(y-t)<.001 for t in [90.4,91.4,92.4]):+ p.x=pcbnew.FromMM(139.4);item.SetPosition(p);item.SetWidth(pcbnew.FromMM(.6));item.SetDrill(pcbnew.FromMM(.3))+for item in board.GetTracks():+ if str(item.m_Uuid.AsString())=='f120f8db-288f-4938-acbf-98ff5e4065df':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(133.5),pcbnew.FromMM(84.25)));item.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(84.25)));item.SetWidth(pcbnew.FromMM(2.0))+# Fine-grid hotspot revision: spread the return transfer and soften the two power necks.+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA):+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if item.GetNetname()=='GND' and abs(x-139.4)<.001 and abs(y-90.4)<.001:+ p.x=pcbnew.FromMM(139.0);item.SetPosition(p)+ continue+ uid=str(item.m_Uuid.AsString())+ if uid in ['32cd5f76-8c9c-4680-a071-fe4f71064f23','cecc8caa-bb93-470d-a793-50ae2c93732f']:item.SetWidth(pcbnew.FromMM(1.6))+ if item.GetNetname()=='LOAD_POS' and item.GetLayer()==pcbnew.B_Cu:+ a=item.GetStart();b=item.GetEnd()+ if abs(pcbnew.ToMM(a.y)-89.7)<.001 and abs(pcbnew.ToMM(b.y)-89.7)<.001:item.SetWidth(pcbnew.FromMM(1.75))+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(139.8),pcbnew.FromMM(90.4)));via.SetWidth(pcbnew.FromMM(.6));via.SetDrill(pcbnew.FromMM(.3));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['GND']);board.Add(via)+track('GND',pcbnew.B_Cu,(139.0,90.4),(139.8,90.4),1.2)+# Keep clearance beside the setpoint via; widen only the diode-spread junction.+for item in list(board.GetTracks()):+ if isinstance(item,pcbnew.PCB_VIA) or item.GetNetname()!='LOAD_POS' or item.GetLayer()!=pcbnew.B_Cu:continue+ a=item.GetStart();b=item.GetEnd();ax,ay=pcbnew.ToMM(a.x),pcbnew.ToMM(a.y);bx,by=pcbnew.ToMM(b.x),pcbnew.ToMM(b.y)+ if abs(ay-89.7)<.001 and abs(by-89.7)<.001:+ item.SetWidth(pcbnew.FromMM(1.5))+ if min(ax,bx)<120 and max(ax,bx)>125:+ board.Remove(item);track('LOAD_POS',pcbnew.B_Cu,(119.8,89.7),(123.0,89.7),1.75);track('LOAD_POS',pcbnew.B_Cu,(123.0,89.7),(125.65,89.7),1.5)+# Fill the inside shunt-bus bend with a diagonal copper path.+track('SHUNT_P',pcbnew.B_Cu,(133.0,84.7),(134.7,85.5),1.2)+# Spread the plated-source exit without moving the gate or clamp circuit.+track('SHUNT_P',pcbnew.B_Cu,(125.1,81.8),(125.35,82.7),1.9)+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA):continue+ if item.GetNetname()=='LOAD_POS' and item.GetLayer()==pcbnew.B_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-126.35)<.001 and abs(y-89)<.001:p.y=pcbnew.FromMM(88.85);setter(p)+ if abs(x-125.65)<.001 and abs(y-89.7)<.001:p.x=pcbnew.FromMM(125.5);setter(p)+for x in [139.0,139.8]:+ via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(x),pcbnew.FromMM(89.9)));via.SetWidth(pcbnew.FromMM(.6));via.SetDrill(pcbnew.FromMM(.3));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['GND']);board.Add(via)+track('GND',pcbnew.B_Cu,(139,89.9),(139.8,89.9),.6)+track('GND',pcbnew.B_Cu,(139.4,89.9),(139.4,90.4),.6)+# Give the ground transfer more uninterrupted top copper; keep UART endpoints fixed.+for item in list(board.GetTracks()):+ if isinstance(item,pcbnew.PCB_VIA) or item.GetLayer()!=pcbnew.F_Cu:continue+ a=item.GetStart();b=item.GetEnd();ax,ay=pcbnew.ToMM(a.x),pcbnew.ToMM(a.y);bx,by=pcbnew.ToMM(b.x),pcbnew.ToMM(b.y)+ if item.GetNetname()=='UART_RX' and abs(ax-140.7)<.001 and abs(bx-140.7)<.001 and abs(ay-83.5)<.001 and abs(by-93.2)<.001:+ board.Remove(item)+ points=[(140.7,83.5),(140.7,88.0),(141.0,88.3),(141.0,91.8),(140.7,92.1),(140.7,93.2)]+ for p,q in zip(points,points[1:]):track('UART_RX',pcbnew.F_Cu,p,q,.25)+ if item.GetNetname()=='UART_TX' and abs(ax-137.9)<.001 and abs(bx-137.9)<.001 and abs(ay-83.7)<.001 and abs(by-98.1)<.001:+ board.Remove(item)+ points=[(137.9,83.7),(137.9,89.0),(137.4,89.5),(137.4,93.3),(137.9,93.8),(137.9,98.1)]+ for p,q in zip(points,points[1:]):track('UART_TX',pcbnew.F_Cu,p,q,.25)+# Move the setpoint layer transfer clear of a wider drain bend, preserving its net/endpoints.+for item in board.GetTracks():+ if item.GetNetname()=='I_SET':+ endpoints=[(item.GetPosition,item.SetPosition)] if isinstance(item,pcbnew.PCB_VIA) else [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]+ for getter,setter in endpoints:+ p=getter()+ if abs(pcbnew.ToMM(p.x)-125)<.001 and abs(pcbnew.ToMM(p.y)-88.4)<.001:p.y=pcbnew.FromMM(88);setter(p)+ if isinstance(item,pcbnew.PCB_VIA) or item.GetNetname()!='LOAD_POS' or item.GetLayer()!=pcbnew.B_Cu:continue+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-126.35)<.001 and abs(y-88.85)<.001:p.y=pcbnew.FromMM(89);setter(p)+ if abs(x-125.5)<.001 and abs(y-89.7)<.001:p.x=pcbnew.FromMM(125.65);setter(p)+ a=item.GetStart();b=item.GetEnd();ax,ay=pcbnew.ToMM(a.x),pcbnew.ToMM(a.y);bx,by=pcbnew.ToMM(b.x),pcbnew.ToMM(b.y)+ if abs(ay-89.7)<.001 and abs(by-89.7)<.001:item.SetWidth(pcbnew.FromMM(1.75))+ if abs(ax-bx)>.1 and abs(ay-by)>.1 and min(ax,bx)>125 and max(ax,bx)<127 and min(ay,by)>88.8 and max(ay,by)<89.8:item.SetWidth(pcbnew.FromMM(1.75))+# This local gate-drive segment carries the servo/gate current, not DUT current.+# Keep 0.5 mm copper and create clearance to the small setpoint via.+for item in board.GetTracks():+ if str(item.m_Uuid.AsString())=='a4e6790e-b900-47a1-bf1e-b5fb98adc8c7':item.SetWidth(pcbnew.FromMM(.5))+# Separate thermal-spread experiment, not a replacement of the accepted zone plan.+for z in list(board.Zones()):+ if not z.GetZoneName().startswith('D1 relocated heat spread'):continue+ layer=z.GetLayer();name=z.GetZoneName();board.Remove(z)+ z=pcbnew.ZONE(board);z.SetLayer(layer);z.SetNet(nets['LOAD_POS']);z.SetZoneName(name);z.SetAssignedPriority(3);z.SetLocalClearance(pcbnew.FromMM(.2));z.SetMinThickness(pcbnew.FromMM(.25));z.SetPadConnection(pcbnew.ZONE_CONNECTION_FULL);z.Outline().NewOutline()+ for x,y in [(114,82),(132,82),(132,100),(114,100)]:z.Outline().Append(pcbnew.FromMM(x),pcbnew.FromMM(y))+ board.Add(z)+# Shorter board-to-board heat paths beside the diode cathode, outside its solder pad.+for x in [115.6,119.2]:+ for y in ([90.2,90.9] if x==115.6 else [90.2,91.4]):+ via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(x),pcbnew.FromMM(y)));via.SetWidth(pcbnew.FromMM(.6));via.SetDrill(pcbnew.FromMM(.3));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['LOAD_POS']);board.Add(via)+expected_nets={str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}+board.BuildConnectivity()+actual_nets={str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}+assert actual_nets==expected_nets, {'unexpectedNetChanges':[(k,v,actual_nets.get(k)) for k,v in expected_nets.items() if actual_nets.get(k)!=v]}+for zone in board.Zones():zone.SetNeedRefill(True)+pcbnew.ZONE_FILLER(board).Fill(board.Zones());assert {str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}==expected_nets, 'Zone fill changed copper nets'+pcbnew.SaveBoard(str(out),board)+readback=pcbnew.LoadBoard(str(out))+assert {str(item.m_Uuid.AsString()):item.GetNetname() for item in readback.GetTracks()}==expected_nets, 'Save/readback changed copper nets' +out.with_suffix('.changes.json').write_text(json.dumps({'source':str(source),'candidate':str(out),'changes':changes},indent=2));print(json.dumps({'saved':str(out),'changes':changes}))
tools/hotspot_via_candidate_v45.pyadded+190@@ -0,0 +1,190 @@+"""Run using native desktop KiCad Python; modifies only an isolated board copy."""+import pcbnew,sys,json+from pathlib import Path+source=Path(sys.argv[1]);out=Path(sys.argv[2]);board=pcbnew.LoadBoard(str(source));nets=board.GetNetsByName()+changes=[]+for net,x,y in [('GND',133.0,100.8),('GND',138.0,100.8),('GND',139.0,93.0),('GND',136.5,93.0),('GND',139.4,90.4),('GND',139.4,91.4),('GND',139.4,92.4)]:+ via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(x),pcbnew.FromMM(y)));via.SetWidth(pcbnew.FromMM(.8));via.SetDrill(pcbnew.FromMM(.4));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets[net]);board.Add(via);changes.append({'kind':'via','net':net,'at':[x,y],'sizeMm':.8,'drillMm':.4,'uuid':str(via.m_Uuid.AsString())})+for item in board.GetTracks():+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetWidth(pcbnew.FromMM(1.0));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','uuid':uid,'net':'LOAD_POS','sizeMm':1.0,'drillMm':.6})+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid in ['91eed079-74e4-4863-8dc1-f211a19b6ee0','ad353074-a567-44a9-a20a-744b8057c5a9']:+ board.Remove(item);changes.append({'kind':'remove-track','uuid':uid})+ if uid=='aa6c5964-74d4-410e-a69e-ffb2cd9c9999':+ item.SetWidth(pcbnew.FromMM(1.0));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','uuid':uid,'sizeMm':1.0,'drillMm':.6})+def track(net,layer,a,b,width):+ t=pcbnew.PCB_TRACK(board);t.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(a[0]),pcbnew.FromMM(a[1])));t.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(b[0]),pcbnew.FromMM(b[1])));t.SetWidth(pcbnew.FromMM(width));t.SetLayer(layer);t.SetNet(nets[net]);board.Add(t);changes.append({'kind':'track','net':net,'start':a,'end':b,'widthMm':width,'layer':board.GetLayerName(layer)})+for a,b in [((127.46,82),(127.46,83.3)),((127.46,83.3),(126.4,84.36)),((126.4,84.36),(126.4,86.5))]:track('LOAD_POS',pcbnew.F_Cu,a,b,1.5)+for a,b in [((139.4,90.4),(139.4,92)),((139.4,92),(138.4,93)),((138.4,93),(136.5,93))]:track('GND',pcbnew.B_Cu,a,b,1.5)+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='GND':+ p=item.GetPosition();xy=(round(pcbnew.ToMM(p.x),3),round(pcbnew.ToMM(p.y),3))+ if xy in [(139.4,86.8),(136.5,93.0),(139.4,90.4),(139.4,91.4),(139.4,92.4)]:item.SetWidth(pcbnew.FromMM(1));item.SetDrill(pcbnew.FromMM(.6));changes.append({'kind':'resize-via','at':xy,'sizeMm':1,'drillMm':.6})+for fp in board.GetFootprints():+ delta={'Q2':-.3,'U3':.3,'R7':.1}.get(fp.GetReference())+ if delta:+ pos=fp.GetPosition();pos.x+=pcbnew.FromMM(delta);fp.SetPosition(pos);changes.append({'kind':'move-footprint','ref':fp.GetReference(),'dxMm':delta})+for item in list(board.GetTracks()):+ if item.GetNetname()=='SHUNT_P_SENSE' and isinstance(item,pcbnew.PCB_VIA):+ pos=item.GetPosition()+ if abs(pcbnew.ToMM(pos.x)-123.6)<.001 and abs(pcbnew.ToMM(pos.y)-85.4)<.001:+ pos.x=pcbnew.FromMM(123.3);item.SetPosition(pos);changes.append({'kind':'move-sense-via','from':[123.6,85.4],'to':[123.3,85.4]})+ if isinstance(item,pcbnew.PCB_VIA):continue+ if item.GetNetname()=='SHUNT_P_SENSE':+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ pos=getter()+ if abs(pcbnew.ToMM(pos.x)-123.6)<.001 and abs(pcbnew.ToMM(pos.y)-85.4)<.001:pos.x=pcbnew.FromMM(123.3);setter(pos)+ if item.GetNetname()=='SHUNT_N_SENSE' and item.GetLayer()==pcbnew.F_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ pos=getter();x,y=pcbnew.ToMM(pos.x),pcbnew.ToMM(pos.y)+ if 128.79<x<129.31 and 87.29<y<88.81:pos.x+=pcbnew.FromMM(.2);setter(pos)+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.4),pcbnew.FromMM(86.7)))+ if uid=='13d2f936-e4d3-40cb-9bf7-1b6b26ca4cf2':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(126.4),pcbnew.FromMM(86.7)));item.SetWidth(pcbnew.FromMM(1.5))+ if uid=='f120f8db-288f-4938-acbf-98ff5e4065df':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(133.5),pcbnew.FromMM(84.2)));item.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(84.2)));item.SetWidth(pcbnew.FromMM(1.9))+ if not isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='LOAD_POS' and item.GetLayer()==pcbnew.F_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter()+ if abs(pcbnew.ToMM(p.x)-126.4)<.001 and abs(pcbnew.ToMM(p.y)-86.5)<.001:p.y=pcbnew.FromMM(86.7);setter(p)+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA) or item.GetLayer()!=pcbnew.B_Cu or item.GetNetname()!='SHUNT_P_SENSE':continue+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(y-85.4)<.001 and (abs(x-123.3)<.001 or abs(x-132)<.001):+ p.y=pcbnew.FromMM(85.6)+ if abs(x-132)<.001:p.x=pcbnew.FromMM(132.2)+ setter(p)+# Ease the drain right-angle turn; relocate its adjacent setpoint via with endpoints.+track('LOAD_POS',pcbnew.B_Cu,(126.35,89.0),(125.65,89.7),1.5)+# Preserve explicit branch junctions instead of overlapped dangling endpoints.+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid=='32514162-b870-4c7a-95a2-b85b8db79257':+ item.SetWidth(pcbnew.FromMM(.8));item.SetDrill(pcbnew.FromMM(.4));item.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.05),pcbnew.FromMM(86.7)))+ if uid in ['b486fb59-b64e-41ee-a1a7-5a72fcf0a780','8e5d5e2b-78f3-4ed2-af96-409d2ea9d57b']:+ a=item.GetStart();b=item.GetEnd();start=(pcbnew.ToMM(a.x),pcbnew.ToMM(a.y));end=(pcbnew.ToMM(b.x),pcbnew.ToMM(b.y))+ mid=(126.35,89.0) if uid=='b486fb59-b64e-41ee-a1a7-5a72fcf0a780' else (125.65,89.7)+ board.Remove(item);track('LOAD_POS',pcbnew.B_Cu,start,mid,1.5);track('LOAD_POS',pcbnew.B_Cu,mid,end,1.5)+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(86.7)));via.SetWidth(pcbnew.FromMM(.8));via.SetDrill(pcbnew.FromMM(.4));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['LOAD_POS']);board.Add(via)+# Do not enlarge holes through narrow in-plane paths. Keep the three dedicated shunt thermal vias.+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid in ['34d3207a-b14a-40d8-be82-5b108c2b867b','9a71af6b-ea83-4f97-b7f6-3f69253c7314']:+ board.Remove(item);continue+ if isinstance(item,pcbnew.PCB_VIA):+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if uid=='aa6c5964-74d4-410e-a69e-ffb2cd9c9999' or (item.GetNetname()=='GND' and abs(x-139.4)<.001 and abs(y-86.8)<.001):+ item.SetWidth(pcbnew.FromMM(.8));item.SetDrill(pcbnew.FromMM(.4))+# Move the transfer hole off the center of the shunt bus; remove a nearly unused ground hole through the return neck.+for item in list(board.GetTracks()):+ if not isinstance(item,pcbnew.PCB_VIA):continue+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if item.GetNetname()=='GND' and abs(x-139.4)<.001 and abs(y-86.8)<.001:board.Remove(item)+ if str(item.m_Uuid.AsString())=='aa6c5964-74d4-410e-a69e-ffb2cd9c9999':item.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(133.7),pcbnew.FromMM(83.6)))+track('SHUNT_P',pcbnew.F_Cu,(133.7,83.6),(133.8,84.2),1.2)+# Preserve the full plated Q1 source connection; remove its redundant neck-drilling spur.+for item in list(board.GetTracks()):+ uid=str(item.m_Uuid.AsString())+ if uid in ['cd48c727-15bf-4276-b4b8-ae4d936d829d','30d9e8bc-805a-4b1d-8a79-ad625ee59388']:+ board.Remove(item);continue+ if isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='GND':+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-139.4)<.001 and any(abs(y-t)<.001 for t in [90.4,91.4,92.4]):p.x=pcbnew.FromMM(139.8);item.SetPosition(p)+# Share vertical current with smaller holes and widen the plated-source exit.+for item in board.GetTracks():+ uid=str(item.m_Uuid.AsString())+ if uid in ['2f235494-7976-4048-be2d-b71507b2b45e','ec56cc46-09f5-4ff9-a1b6-61699db68686']:item.SetWidth(pcbnew.FromMM(1.75))+ if isinstance(item,pcbnew.PCB_VIA) and item.GetNetname()=='GND':+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-139.8)<.001 and any(abs(y-t)<.001 for t in [90.4,91.4,92.4]):+ p.x=pcbnew.FromMM(139.4);item.SetPosition(p);item.SetWidth(pcbnew.FromMM(.6));item.SetDrill(pcbnew.FromMM(.3))+for item in board.GetTracks():+ if str(item.m_Uuid.AsString())=='f120f8db-288f-4938-acbf-98ff5e4065df':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(133.5),pcbnew.FromMM(84.25)));item.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(84.25)));item.SetWidth(pcbnew.FromMM(2.0))+# Fine-grid hotspot revision: spread the return transfer and soften the two power necks.+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA):+ p=item.GetPosition();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if item.GetNetname()=='GND' and abs(x-139.4)<.001 and abs(y-90.4)<.001:+ p.x=pcbnew.FromMM(139.0);item.SetPosition(p)+ continue+ uid=str(item.m_Uuid.AsString())+ if uid in ['32cd5f76-8c9c-4680-a071-fe4f71064f23','cecc8caa-bb93-470d-a793-50ae2c93732f']:item.SetWidth(pcbnew.FromMM(1.6))+ if item.GetNetname()=='LOAD_POS' and item.GetLayer()==pcbnew.B_Cu:+ a=item.GetStart();b=item.GetEnd()+ if abs(pcbnew.ToMM(a.y)-89.7)<.001 and abs(pcbnew.ToMM(b.y)-89.7)<.001:item.SetWidth(pcbnew.FromMM(1.75))+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(139.8),pcbnew.FromMM(90.4)));via.SetWidth(pcbnew.FromMM(.6));via.SetDrill(pcbnew.FromMM(.3));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['GND']);board.Add(via)+track('GND',pcbnew.B_Cu,(139.0,90.4),(139.8,90.4),1.2)+# Keep clearance beside the setpoint via; widen only the diode-spread junction.+for item in list(board.GetTracks()):+ if isinstance(item,pcbnew.PCB_VIA) or item.GetNetname()!='LOAD_POS' or item.GetLayer()!=pcbnew.B_Cu:continue+ a=item.GetStart();b=item.GetEnd();ax,ay=pcbnew.ToMM(a.x),pcbnew.ToMM(a.y);bx,by=pcbnew.ToMM(b.x),pcbnew.ToMM(b.y)+ if abs(ay-89.7)<.001 and abs(by-89.7)<.001:+ item.SetWidth(pcbnew.FromMM(1.5))+ if min(ax,bx)<120 and max(ax,bx)>125:+ board.Remove(item);track('LOAD_POS',pcbnew.B_Cu,(119.8,89.7),(123.0,89.7),1.75);track('LOAD_POS',pcbnew.B_Cu,(123.0,89.7),(125.65,89.7),1.5)+# Fill the inside shunt-bus bend with a diagonal copper path.+track('SHUNT_P',pcbnew.B_Cu,(133.0,84.7),(134.7,85.5),1.2)+# Spread the plated-source exit without moving the gate or clamp circuit.+track('SHUNT_P',pcbnew.B_Cu,(125.1,81.8),(125.35,82.7),1.9)+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA):continue+ if item.GetNetname()=='LOAD_POS' and item.GetLayer()==pcbnew.B_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-126.35)<.001 and abs(y-89)<.001:p.y=pcbnew.FromMM(88.85);setter(p)+ if abs(x-125.65)<.001 and abs(y-89.7)<.001:p.x=pcbnew.FromMM(125.5);setter(p)+for x in [139.0,139.8]:+ via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(x),pcbnew.FromMM(89.9)));via.SetWidth(pcbnew.FromMM(.6));via.SetDrill(pcbnew.FromMM(.3));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['GND']);board.Add(via)+track('GND',pcbnew.B_Cu,(139,89.9),(139.8,89.9),.6)+track('GND',pcbnew.B_Cu,(139.4,89.9),(139.4,90.4),.6)+# Give the ground transfer more uninterrupted top copper; keep UART endpoints fixed.+for item in list(board.GetTracks()):+ if isinstance(item,pcbnew.PCB_VIA) or item.GetLayer()!=pcbnew.F_Cu:continue+ a=item.GetStart();b=item.GetEnd();ax,ay=pcbnew.ToMM(a.x),pcbnew.ToMM(a.y);bx,by=pcbnew.ToMM(b.x),pcbnew.ToMM(b.y)+ if item.GetNetname()=='UART_RX' and abs(ax-140.7)<.001 and abs(bx-140.7)<.001 and abs(ay-83.5)<.001 and abs(by-93.2)<.001:+ board.Remove(item)+ points=[(140.7,83.5),(140.7,88.0),(141.0,88.3),(141.0,91.8),(140.7,92.1),(140.7,93.2)]+ for p,q in zip(points,points[1:]):track('UART_RX',pcbnew.F_Cu,p,q,.25)+ if item.GetNetname()=='UART_TX' and abs(ax-137.9)<.001 and abs(bx-137.9)<.001 and abs(ay-83.7)<.001 and abs(by-98.1)<.001:+ board.Remove(item)+ points=[(137.9,83.7),(137.9,89.0),(137.4,89.5),(137.4,93.3),(137.9,93.8),(137.9,98.1)]+ for p,q in zip(points,points[1:]):track('UART_TX',pcbnew.F_Cu,p,q,.25)+# Move the setpoint layer transfer clear of a wider drain bend, preserving its net/endpoints.+for item in board.GetTracks():+ if item.GetNetname()=='I_SET':+ endpoints=[(item.GetPosition,item.SetPosition)] if isinstance(item,pcbnew.PCB_VIA) else [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]+ for getter,setter in endpoints:+ p=getter()+ if abs(pcbnew.ToMM(p.x)-125)<.001 and abs(pcbnew.ToMM(p.y)-88.4)<.001:p.y=pcbnew.FromMM(88);setter(p)+ if isinstance(item,pcbnew.PCB_VIA) or item.GetNetname()!='LOAD_POS' or item.GetLayer()!=pcbnew.B_Cu:continue+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if abs(x-126.35)<.001 and abs(y-88.85)<.001:p.y=pcbnew.FromMM(89);setter(p)+ if abs(x-125.5)<.001 and abs(y-89.7)<.001:p.x=pcbnew.FromMM(125.65);setter(p)+ a=item.GetStart();b=item.GetEnd();ax,ay=pcbnew.ToMM(a.x),pcbnew.ToMM(a.y);bx,by=pcbnew.ToMM(b.x),pcbnew.ToMM(b.y)+ if abs(ay-89.7)<.001 and abs(by-89.7)<.001:item.SetWidth(pcbnew.FromMM(1.75))+ if abs(ax-bx)>.1 and abs(ay-by)>.1 and min(ax,bx)>125 and max(ax,bx)<127 and min(ay,by)>88.8 and max(ay,by)<89.8:item.SetWidth(pcbnew.FromMM(1.75))+# This local gate-drive segment carries the servo/gate current, not DUT current.+# Keep 0.5 mm copper and create clearance to the small setpoint via.+for item in board.GetTracks():+ if str(item.m_Uuid.AsString())=='a4e6790e-b900-47a1-bf1e-b5fb98adc8c7':item.SetWidth(pcbnew.FromMM(.5))+# Smooth the source-to-bus inner corner identified by the 0.1 mm solve.+track('SHUNT_P',pcbnew.B_Cu,(126.0,83.2),(127.4,83.95),1.1)+expected_nets={str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}+board.BuildConnectivity()+actual_nets={str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}+assert actual_nets==expected_nets, {'unexpectedNetChanges':[(k,v,actual_nets.get(k)) for k,v in expected_nets.items() if actual_nets.get(k)!=v]}+for zone in board.Zones():zone.SetNeedRefill(True)+pcbnew.ZONE_FILLER(board).Fill(board.Zones());assert {str(item.m_Uuid.AsString()):item.GetNetname() for item in board.GetTracks()}==expected_nets, 'Zone fill changed copper nets'+pcbnew.SaveBoard(str(out),board)+readback=pcbnew.LoadBoard(str(out))+assert {str(item.m_Uuid.AsString()):item.GetNetname() for item in readback.GetTracks()}==expected_nets, 'Save/readback changed copper nets' +out.with_suffix('.changes.json').write_text(json.dumps({'source':str(source),'candidate':str(out),'changes':changes},indent=2));print(json.dumps({'saved':str(out),'changes':changes}))
tools/plot_hotspot_progress.py+3−3@@ -5,11 +5,11 @@ import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt root=Path(__file__).resolve().parents[1]-base=json.loads((root/'design/fields-board19-annular/fields.json').read_text());new=json.loads((root/'checkpoints/unclipped-hotspots-v1/fields-v27-annular/fields.json').read_text())+base=json.loads((root/'checkpoints/unclipped-hotspots-v1/fields-v27-annular-0p1/fields.json').read_text());new=json.loads((root/'checkpoints/unclipped-hotspots-v1/fields-v40-annular-0p1/fields.json').read_text()) keys=['GND','LOAD_POS','SHUNT_P'];a={n['net']:n['peakJ'] for n in base['nets']};b={n['net']:n['peakJ'] for n in new['nets']} plt.rcParams.update({'font.size':15,'text.color':'#e6edf3','axes.labelcolor':'#e6edf3','xtick.color':'#c1ccd8','ytick.color':'#c1ccd8'}) fig,ax=plt.subplots(figsize=(16,9),facecolor='#101820');ax.set_facecolor('#101820')-x=list(range(3));ax.bar([i-.19 for i in x],[a[k] for k in keys],.36,color='#d58b50',label='Saved board 19');ax.bar([i+.19 for i in x],[b[k] for k in keys],.36,color='#56a6cf',label='Isolated candidate v27')+x=list(range(3));ax.bar([i-.19 for i in x],[a[k] for k in keys],.36,color='#d58b50',label='Candidate v27');ax.bar([i+.19 for i in x],[b[k] for k in keys],.36,color='#56a6cf',label='Candidate v40') for i,k in enumerate(keys): for off,value in [(-.19,a[k]),(.19,b[k])]:ax.text(i+off,value+1,f'{value:.1f}',ha='center',fontsize=18) ax.axhline(60,color='#fa7180',linewidth=2,linestyle='--',label='Unchanged screen: 60 A/mm²');ax.set_xticks(x,keys);ax.set_ylim(0,110);ax.set_ylabel('Unclipped modeled maximum (A/mm²)');ax.grid(axis='y',alpha=.15);ax.set_axisbelow(True)@@ -17,6 +17,6 @@ for spine in ax.spines.values():spine.set_color('#465360') legend=ax.legend(loc='upper right',frameon=False,fontsize=13) for t in legend.get_texts():t.set_color('#e6edf3') fig.suptitle('Current rework: improvement, not acceptance',fontsize=25,y=.94)-fig.text(.1,.065,'0.2 mm mesh · nominal 35 µm copper · 3 A main-path screen · coarse-mesh peaks below 60; finer mesh pending\nLive board unchanged. Finer mesh, full boundary conditions and thermal qualification remain open.',fontsize=13,color='#bec9d3')+fig.text(.1,.065,'0.1 mm mesh · nominal 35 µm copper · 3 A main-path screen · one remaining shunt-path peak exceeds 60\nLive board unchanged. Candidate v43 smooths the remaining source-to-shunt bend; acceptance stays open.',fontsize=13,color='#bec9d3') fig.subplots_adjust(top=.84,bottom=.19,left=.1,right=.95) fig.savefig(root/'design/step12-hotspot-progress.png',dpi=120,facecolor=fig.get_facecolor())
videos/inventory.json+8−2@@ -1,6 +1,6 @@ {- "rawTakeCount": 69,- "reviewedExcerptCount": 16,+ "rawTakeCount": 70,+ "reviewedExcerptCount": 17, "countingRule": "Unique raw window recording filenames in this project; excludes derivative action/speed exports and reference projects. Raw count includes rejected takes.", "takes": [ {@@ -416,6 +416,12 @@ "durationSeconds": 17.2, "topic": "Native baseline drain, shunt and ground copper selection before rework", "review": "Full decode and six-frame contact sheet reviewed; net highlights visibly change. Label as baseline inspection, not construction or a passing gate. Dense value labels limit readability; use only a short cropped explanatory excerpt."+ },+ {+ "file": "videos/raw/window-24324034-20261010-125642.mp4",+ "durationSeconds": 21.3,+ "topic": "Corrected drill-void/plated-wall fine-current review of unaccepted candidate v27",+ "review": "Full decode and contact sheet reviewed; 17-second labeled excerpt prepared; later review, not source correction or copper construction" } ] }