← Commit history
design/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"     }   ] }