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 isolated source-current and cooling refinements with native net-intent checks
14 files changed
+2069−5
design/hotspot-v52-evidence.json+77design/hotspot-v62-evidence.json+159design/hotspot-v63-evidence.json+57design/hotspot-v64-ground-route.json+102design/hotspot-v65-evidence.json+159docs/completion-plan.md+5−5docs/current-cooling-refinement.md+10tools/hotspot_via_candidate_v62.py+202tools/hotspot_via_candidate_v63.py+221tools/hotspot_via_candidate_v64.py+203tools/hotspot_via_candidate_v65.py+216tools/hotspot_via_candidate_v66.py+221tools/hotspot_via_candidate_v67.py+221tools/hotspot_via_candidate_v68.py+216design/hotspot-v52-evidence.jsonadded+77@@ -0,0 +1,77 @@+{+ "status": "isolated-not-landed",+ "candidate": "checkpoints/unclipped-hotspots-v1/candidate-v52.kicad_pcb",+ "boardSha256": "7c0181927f447f3fa176310497fc8809d01c560495fe3c82fb94adf820764d66",+ "nativeDRC": {+ "errors": 0,+ "warnings": 0,+ "opens": 0,+ "sha256": "6448ec6dcf65b0bfe86577d7eb59c1d733346a55490af1648ef3cff580daa293"+ },+ "solver": {+ "sha256": "202bb43774babdeb18f3e677090d559d01b9b62938bac8aa659326502f82f168",+ "sourceCommit": "e34bcddab94b3f14442bf511a8b6b35bcdb02237",+ "review": "https://wiki.adom.inc/adom/adom-fields/prs/9",+ "sharedRuntimeReplaced": false+ },+ "reports": [+ {+ "path": "checkpoints/unclipped-hotspots-v1/fields-v52-annular-0p1/fields.json",+ "sha256": "bd7ce8a6469342ba70bdbf0e15146e4b4801bda415c9f964b2021a6e433788fd",+ "inputIdentity": {+ "algorithm": "sha256",+ "board": "7c0181927f447f3fa176310497fc8809d01c560495fe3c82fb94adf820764d66",+ "solverVersion": "0.6.2",+ "spec": "9050e3a86363f4958a3792dc41f3fb6a1ffbf09dd74681b343ec3048180c32d0"+ },+ "nets": [+ {+ "net": "LOAD_POS",+ "peakJ": 56.10619354248047,+ "peakAt": [+ 127.14999999999999,+ 84.75+ ],+ "peakLayer": "F.Cu",+ "residual": 9.863910531633251e-08+ },+ {+ "net": "SHUNT_P",+ "peakJ": 64.24640655517578,+ "peakAt": [+ 126.25,+ 82.45+ ],+ "peakLayer": "B.Cu",+ "residual": 9.389174385796774e-08+ },+ {+ "net": "GND",+ "peakJ": 58.31138229370117,+ "peakAt": [+ 139.65,+ 89.85000000000001+ ],+ "peakLayer": "F.Cu",+ "residual": 9.944699011132516e-08+ }+ ],+ "chips": [],+ "issues": [+ {+ "at": [+ 126.25,+ 82.45+ ],+ "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: 64 A/mm\u00b2 at its worst point on B.Cu (3 A total; equivalent full-net width 1.33 mm at 35 \u00b5m; the threshold is 60 A/mm\u00b2)"+ }+ ]+ }+ ]+}
design/hotspot-v62-evidence.jsonadded+159@@ -0,0 +1,159 @@+{+ "status": "isolated-not-landed",+ "candidate": "checkpoints/unclipped-hotspots-v1/candidate-v62.kicad_pcb",+ "boardSha256": "e183ce2d0e9008ae846b8e399903476b90186004a3e454ed05963b1a5c0454fe",+ "nativeDRC": {+ "errors": 0,+ "warnings": 0,+ "opens": 0,+ "sha256": "4bca551e0a162e1f1550440f9704148d6487a46508d5e4bd5e76978ee1261e4b"+ },+ "solver": {+ "sha256": "202bb43774babdeb18f3e677090d559d01b9b62938bac8aa659326502f82f168",+ "sourceCommit": "e34bcddab94b3f14442bf511a8b6b35bcdb02237",+ "review": "https://wiki.adom.inc/adom/adom-fields/prs/9",+ "sharedRuntimeReplaced": false+ },+ "reports": [+ {+ "path": "checkpoints/unclipped-hotspots-v1/fields-v62-operating-heat/fields.json",+ "sha256": "7bdac55ea4a1b95b71b2800820545c3a5fa9daf3523785e72111578547ea35ac",+ "inputIdentity": {+ "algorithm": "sha256",+ "board": "e183ce2d0e9008ae846b8e399903476b90186004a3e454ed05963b1a5c0454fe",+ "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.621585845947266,+ "peakAt": [+ 126.5,+ 81.69999999999999+ ],+ "peakLayer": "B.Cu",+ "residual": 9.629448401300361e-08+ },+ {+ "net": "SHUNT_P",+ "peakJ": 61.06366729736328,+ "peakAt": [+ 126.5,+ 83.1+ ],+ "peakLayer": "B.Cu",+ "residual": 9.256156624373384e-08+ },+ {+ "net": "GND",+ "peakJ": 46.69049835205078,+ "peakAt": [+ 119.89999999999999,+ 100.89999999999999+ ],+ "peakLayer": "B.Cu",+ "residual": 9.884057980409636e-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.442,+ "platedPadW": 0.0,+ "platedPads": 0,+ "reference": "D1",+ "riseC": 39.3,+ "riseMovingC": 29.4,+ "tabNet": "LOAD_POS",+ "viaW": 0.202,+ "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": 15.8,+ "riseMovingC": 8.1,+ "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: +39 \u00b0C at its tab in still air (+29 \u00b0C in moving air) against a 35 \u00b0C budget"+ }+ ]+ }+ ]+}
design/hotspot-v63-evidence.jsonadded+57@@ -0,0 +1,57 @@+{+ "status": "isolated-not-landed",+ "candidate": "checkpoints/unclipped-hotspots-v1/candidate-v63.kicad_pcb",+ "boardSha256": "b2e94c44ca22928b05584370269fa2589dfc5a6a8e8a006d90f7956f83ce3fbe",+ "nativeDRC": {+ "errors": 0,+ "warnings": 0,+ "opens": 0,+ "sha256": "b24c738a7be3a04a5b4c5ea9983727fcb6e3a0e869c209ab5cb0f4eeda0b2ea9"+ },+ "solver": {+ "sha256": "202bb43774babdeb18f3e677090d559d01b9b62938bac8aa659326502f82f168",+ "sourceCommit": "e34bcddab94b3f14442bf511a8b6b35bcdb02237",+ "review": "https://wiki.adom.inc/adom/adom-fields/prs/9",+ "sharedRuntimeReplaced": false+ },+ "reports": [+ {+ "path": "checkpoints/unclipped-hotspots-v1/fields-v63-shunt-0p1/fields.json",+ "sha256": "cb26e6c8bc862691b27b53bcc7103cc51739f0e87df8d7fd48d849d753a4d58e",+ "inputIdentity": {+ "algorithm": "sha256",+ "board": "b2e94c44ca22928b05584370269fa2589dfc5a6a8e8a006d90f7956f83ce3fbe",+ "solverVersion": "0.6.2",+ "spec": "3bfd3ab688ed72678e6e61402fb1e6840a34e33904de74723e7759d1d79c8b4c"+ },+ "nets": [+ {+ "net": "SHUNT_P",+ "peakJ": 64.80438232421875,+ "peakAt": [+ 125.55,+ 83.05+ ],+ "peakLayer": "B.Cu",+ "residual": 9.618914437419029e-08+ }+ ],+ "chips": [],+ "issues": [+ {+ "at": [+ 125.55,+ 83.05+ ],+ "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: 65 A/mm\u00b2 at its worst point on B.Cu (3 A total; equivalent full-net width 1.32 mm at 35 \u00b5m; the threshold is 60 A/mm\u00b2)"+ }+ ]+ }+ ]+}
design/hotspot-v64-ground-route.jsonadded+102@@ -0,0 +1,102 @@+{+ "candidate": "v64",+ "status": "proposal-native-validation-required",+ "widthMm": 0.25,+ "viaSizeMm": 0.6,+ "viaDrillMm": 0.3,+ "path": [+ {+ "layer": "B.Cu",+ "at": [+ 117.1,+ 77.2+ ]+ },+ {+ "layer": "B.Cu",+ "at": [+ 116.0,+ 77.2+ ]+ },+ {+ "layer": "B.Cu",+ "at": [+ 115.4,+ 77.8+ ]+ },+ {+ "layer": "B.Cu",+ "at": [+ 114.9,+ 77.8+ ]+ },+ {+ "layer": "B.Cu",+ "at": [+ 113.8,+ 78.9+ ]+ },+ {+ "layer": "B.Cu",+ "at": [+ 106.4,+ 78.9+ ]+ },+ {+ "layer": "F.Cu",+ "at": [+ 106.4,+ 78.9+ ]+ },+ {+ "layer": "F.Cu",+ "at": [+ 105.9,+ 79.4+ ]+ },+ {+ "layer": "F.Cu",+ "at": [+ 105.9,+ 79.5+ ]+ },+ {+ "layer": "F.Cu",+ "at": [+ 105.8,+ 79.6+ ]+ },+ {+ "layer": "F.Cu",+ "at": [+ 105.8,+ 80.1+ ]+ },+ {+ "layer": "F.Cu",+ "at": [+ 105.3,+ 80.6+ ]+ },+ {+ "layer": "B.Cu",+ "at": [+ 105.3,+ 80.6+ ]+ }+ ],+ "searchedCells": 18036,+ "note": "Conservative pad boxes, track/via obstacles; existing pours may retreat. Native refill, net intent and DRC remain mandatory."+}
design/hotspot-v65-evidence.jsonadded+159@@ -0,0 +1,159 @@+{+ "status": "isolated-not-landed",+ "candidate": "checkpoints/unclipped-hotspots-v1/candidate-v65.kicad_pcb",+ "boardSha256": "8c99375ef85a3b534246c8ed9af3ce39caa63f7f2a548027edd137e26069f5c4",+ "nativeDRC": {+ "errors": 0,+ "warnings": 0,+ "opens": 0,+ "sha256": "9b449aec0cde5872aa89202635419e3e3c79ee9f80f55df033df9ca0849a5ee0"+ },+ "solver": {+ "sha256": "202bb43774babdeb18f3e677090d559d01b9b62938bac8aa659326502f82f168",+ "sourceCommit": "e34bcddab94b3f14442bf511a8b6b35bcdb02237",+ "review": "https://wiki.adom.inc/adom/adom-fields/prs/9",+ "sharedRuntimeReplaced": false+ },+ "reports": [+ {+ "path": "checkpoints/unclipped-hotspots-v1/fields-v65-operating-heat/fields.json",+ "sha256": "9ff16c26e65ae6ba30d6e976931883dbb6d6f732a9beca71d8b6643a1c0f66ee",+ "inputIdentity": {+ "algorithm": "sha256",+ "board": "8c99375ef85a3b534246c8ed9af3ce39caa63f7f2a548027edd137e26069f5c4",+ "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": 39.16014862060547,+ "peakAt": [+ 120.5,+ 84.69999999999999+ ],+ "peakLayer": "B.Cu",+ "residual": 9.900742578073167e-08+ },+ {+ "net": "SHUNT_P",+ "peakJ": 61.06366729736328,+ "peakAt": [+ 126.5,+ 83.1+ ],+ "peakLayer": "B.Cu",+ "residual": 9.256156624373384e-08+ },+ {+ "net": "GND",+ "peakJ": 46.60985565185547,+ "peakAt": [+ 119.89999999999999,+ 100.89999999999999+ ],+ "peakLayer": "B.Cu",+ "residual": 9.767060176371889e-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.444,+ "platedPadW": 0.0,+ "platedPads": 0,+ "reference": "D1",+ "riseC": 38.5,+ "riseMovingC": 28.9,+ "tabNet": "LOAD_POS",+ "viaW": 0.199,+ "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": 15.4,+ "riseMovingC": 7.9,+ "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: +38 \u00b0C at its tab in still air (+29 \u00b0C in moving air) against a 35 \u00b0C budget"+ }+ ]+ }+ ]+}
docs/completion-plan.md+5−5@@ -1,12 +1,12 @@-## Latest continuation — v45 source-bend refinement+## Latest continuation — source current and diode cooling -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.+AIFlow 0.1.82 remains published and installed; Fields PR9 is still under owner review. The shared Fields executable has not been replaced. The live board revision was rechecked on 10 October and remains 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.+V52 remains the best completed fine-grid three-net result: GND 58.31, LOAD_POS 56.11, SHUNT_P 64.25 A/mm² against the unchanged 60 screen. V58 worsened the source exit to 80.11 and was rejected. V63 added a parallel top-layer source connection but moved the peak onto its via (64.80); v67 tests a smaller, offset via and has zero native DRC errors, warnings and opens. Its fine-grid solve is pending. -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.+V61 was rejected before save when native connectivity reassigned its conflicting LOAD_POS via to GND. Removing that via and preserving the ground repair produced DRC-clean v62. Its operating-heat diagnostic predicts D1 +39.3°C still air / +29.4°C moving air. Expanded bottom copper plus a calculated ground reconnection in DRC-clean v65 improves this to +38.5°C / +28.9°C, still above the 35°C budget. V68 explores more lateral spreading. These studies exclude Q1 heat and retain a conservative separate 3 A conductor screen; they do not establish full-board thermal acceptance. -Dashboard updated with completed fine-grid results. 71 raw takes, 18 reviewed excerpts and 14 storyboard clips; the final narrated film is not yet assembled. Current/thermal/finish gates remain open.+No candidate is landed. Keep the native UUID/net invariant, original fabrication rules, physical heatsink/airflow qualification and complete same-board validation. Current/thermal/finish gates remain open. 71 raw takes, 18 reviewed excerpts, 14 storyboard clips; the final narrated film is not assembled. ## Latest continuation — v30 fine mesh running
docs/current-cooling-refinement.mdadded+10@@ -0,0 +1,10 @@+# Source current and diode cooling refinement++AIFlow 0.1.82 remains published and installed; Fields PR9 is still under owner review. The shared Fields executable has not been replaced. The live board revision was rechecked on 10 October and remains unchanged.++V52 remains the best completed fine-grid three-net result: GND 58.31, LOAD_POS 56.11, SHUNT_P 64.25 A/mm² against the unchanged 60 screen. V58 worsened the source exit to 80.11 and was rejected. V63 added a parallel top-layer source connection but moved the peak onto its via (64.80); v67 tests a smaller, offset via and has zero native DRC errors, warnings and opens. Its fine-grid solve is pending.++V61 was rejected before save when native connectivity reassigned its conflicting LOAD_POS via to GND. Removing that via and preserving the ground repair produced DRC-clean v62. Its operating-heat diagnostic predicts D1 +39.3°C still air / +29.4°C moving air. Expanded bottom copper plus a calculated ground reconnection in DRC-clean v65 improves this to +38.5°C / +28.9°C, still above the 35°C budget. V68 explores more lateral spreading. These studies exclude Q1 heat and retain a conservative separate 3 A conductor screen; they do not establish full-board thermal acceptance.++No candidate is landed. Keep the native UUID/net invariant, original fabrication rules, physical heatsink/airflow qualification and complete same-board validation. Current/thermal/finish gates remain open. 71 raw takes, 18 reviewed excerpts, 14 storyboard clips; the final narrated film is not assembled.+
tools/hotspot_via_candidate_v62.pyadded+202@@ -0,0 +1,202 @@+"""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,99),(104,99),(104,89.4),(114,89.4)]: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)+# Clearance-aware ground connection to the existing lower return plane.+track('GND',pcbnew.F_Cu,(112.728,88.696),(112.7,88.8),.4)+track('GND',pcbnew.F_Cu,(112.7,88.8),(112.7,99.5),.4)+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_v63.pyadded+221@@ -0,0 +1,221 @@+"""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))+# Widen the source bus on its lower edge; retain the neighboring gate/drain clearances.+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA):continue+ if str(item.m_Uuid.AsString())=='f120f8db-288f-4938-acbf-98ff5e4065df':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(133.5),pcbnew.FromMM(84.4)));item.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(84.4)));item.SetWidth(pcbnew.FromMM(2.3))+ if item.GetNetname()=='SHUNT_P_SENSE' and item.GetLayer()==pcbnew.B_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter()+ if abs(pcbnew.ToMM(p.y)-85.6)<.001:p.y=pcbnew.FromMM(85.9);setter(p)+track('SHUNT_P_SENSE',pcbnew.B_Cu,(123.3,85.4),(123.3,85.9),.25)+# Move the drain layer transfer down to preserve space for the widened shunt bus and Kelvin trace.+for item in board.GetTracks():+ if item.GetNetname()!='LOAD_POS':continue+ 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();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if 126.0<x<126.9 and (abs(y-86.7)<.001 or abs(y-87.0)<.001):p.y=pcbnew.FromMM(87.2);setter(p)+# Keep the setpoint route below the moved power transfer, with its original op-amp pad fixed.+for item in list(board.GetTracks()):+ if item.GetNetname()!='I_SET':continue+ if not isinstance(item,pcbnew.PCB_VIA) and item.GetLayer()==pcbnew.F_Cu:+ a=item.GetStart();b=item.GetEnd()+ if min(pcbnew.ToMM(a.x),pcbnew.ToMM(b.x))>=125 and min(pcbnew.ToMM(a.y),pcbnew.ToMM(b.y))>=87.9:board.Remove(item);continue+ 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)<.001:p.y=pcbnew.FromMM(88.3);setter(p)+track('I_SET',pcbnew.F_Cu,(127.8625,87.95),(127.8625,88.3),.25)+track('I_SET',pcbnew.F_Cu,(127.8625,88.3),(125,88.3),.25)+# Share source exit current across both copper layers before the bottom bus turn.+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(125.3),pcbnew.FromMM(83.2)));via.SetWidth(pcbnew.FromMM(.8));via.SetDrill(pcbnew.FromMM(.4));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['SHUNT_P']);board.Add(via)+track('SHUNT_P',pcbnew.F_Cu,(124.92,82),(124.92,82.82),1.0)+track('SHUNT_P',pcbnew.F_Cu,(124.92,82.82),(125.3,83.2),1.0)+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_v64.pyadded+203@@ -0,0 +1,203 @@+"""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()+ points=[(114,82),(132,82),(132,99),(104,99),(104,89.4),(114,89.4)] if layer==pcbnew.F_Cu else [(118.8,70),(132,70),(132,99),(104,99),(104,89.4),(114,89.4),(114,84),(118.8,84)]+ for x,y in points: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)+# Clearance-aware ground connection to the existing lower return plane.+track('GND',pcbnew.F_Cu,(112.728,88.696),(112.7,88.8),.4)+track('GND',pcbnew.F_Cu,(112.7,88.8),(112.7,99.5),.4)+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_v65.pyadded+216@@ -0,0 +1,216 @@+"""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()+ points=[(114,82),(132,82),(132,99),(104,99),(104,89.4),(114,89.4)] if layer==pcbnew.F_Cu else [(118.8,70),(132,70),(132,99),(104,99),(104,89.4),(114,89.4),(114,84),(118.8,84)]+ for x,y in points: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)+# Clearance-aware ground connection to the existing lower return plane.+track('GND',pcbnew.F_Cu,(112.728,88.696),(112.7,88.8),.4)+track('GND',pcbnew.F_Cu,(112.7,88.8),(112.7,99.5),.4)+# Restore the displaced signal-ground region; native checks remain mandatory.+track('GND',pcbnew.B_Cu,(117.1, 77.2),(116.0, 77.2),.25)+track('GND',pcbnew.B_Cu,(116.0, 77.2),(115.4, 77.8),.25)+track('GND',pcbnew.B_Cu,(115.4, 77.8),(114.9, 77.8),.25)+track('GND',pcbnew.B_Cu,(114.9, 77.8),(113.8, 78.9),.25)+track('GND',pcbnew.B_Cu,(113.8, 78.9),(106.4, 78.9),.25)+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(106.4),pcbnew.FromMM(78.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.F_Cu,(106.4, 78.9),(105.9, 79.4),.25)+track('GND',pcbnew.F_Cu,(105.9, 79.4),(105.9, 79.5),.25)+track('GND',pcbnew.F_Cu,(105.9, 79.5),(105.8, 79.6),.25)+track('GND',pcbnew.F_Cu,(105.8, 79.6),(105.8, 80.1),.25)+track('GND',pcbnew.F_Cu,(105.8, 80.1),(105.3, 80.6),.25)+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(105.3),pcbnew.FromMM(80.6)));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)+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_v66.pyadded+221@@ -0,0 +1,221 @@+"""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))+# Widen the source bus on its lower edge; retain the neighboring gate/drain clearances.+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA):continue+ if str(item.m_Uuid.AsString())=='f120f8db-288f-4938-acbf-98ff5e4065df':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(133.5),pcbnew.FromMM(84.4)));item.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(84.4)));item.SetWidth(pcbnew.FromMM(2.3))+ if item.GetNetname()=='SHUNT_P_SENSE' and item.GetLayer()==pcbnew.B_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter()+ if abs(pcbnew.ToMM(p.y)-85.6)<.001:p.y=pcbnew.FromMM(85.9);setter(p)+track('SHUNT_P_SENSE',pcbnew.B_Cu,(123.3,85.4),(123.3,85.9),.25)+# Move the drain layer transfer down to preserve space for the widened shunt bus and Kelvin trace.+for item in board.GetTracks():+ if item.GetNetname()!='LOAD_POS':continue+ 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();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if 126.0<x<126.9 and (abs(y-86.7)<.001 or abs(y-87.0)<.001):p.y=pcbnew.FromMM(87.2);setter(p)+# Keep the setpoint route below the moved power transfer, with its original op-amp pad fixed.+for item in list(board.GetTracks()):+ if item.GetNetname()!='I_SET':continue+ if not isinstance(item,pcbnew.PCB_VIA) and item.GetLayer()==pcbnew.F_Cu:+ a=item.GetStart();b=item.GetEnd()+ if min(pcbnew.ToMM(a.x),pcbnew.ToMM(b.x))>=125 and min(pcbnew.ToMM(a.y),pcbnew.ToMM(b.y))>=87.9:board.Remove(item);continue+ 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)<.001:p.y=pcbnew.FromMM(88.3);setter(p)+track('I_SET',pcbnew.F_Cu,(127.8625,87.95),(127.8625,88.3),.25)+track('I_SET',pcbnew.F_Cu,(127.8625,88.3),(125,88.3),.25)+# Parallel source connection, smaller hole off the inner bus turn.+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(125.1),pcbnew.FromMM(83.45)));via.SetWidth(pcbnew.FromMM(.6));via.SetDrill(pcbnew.FromMM(.3));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['SHUNT_P']);board.Add(via)+track('SHUNT_P',pcbnew.F_Cu,(124.92,82),(124.92,83.27),1.0)+track('SHUNT_P',pcbnew.F_Cu,(124.92,83.27),(125.1,83.45),1.0)+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_v67.pyadded+221@@ -0,0 +1,221 @@+"""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))+# Widen the source bus on its lower edge; retain the neighboring gate/drain clearances.+for item in board.GetTracks():+ if isinstance(item,pcbnew.PCB_VIA):continue+ if str(item.m_Uuid.AsString())=='f120f8db-288f-4938-acbf-98ff5e4065df':+ item.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(133.5),pcbnew.FromMM(84.4)));item.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(126.8),pcbnew.FromMM(84.4)));item.SetWidth(pcbnew.FromMM(2.3))+ if item.GetNetname()=='SHUNT_P_SENSE' and item.GetLayer()==pcbnew.B_Cu:+ for getter,setter in [(item.GetStart,item.SetStart),(item.GetEnd,item.SetEnd)]:+ p=getter()+ if abs(pcbnew.ToMM(p.y)-85.6)<.001:p.y=pcbnew.FromMM(85.9);setter(p)+track('SHUNT_P_SENSE',pcbnew.B_Cu,(123.3,85.4),(123.3,85.9),.25)+# Move the drain layer transfer down to preserve space for the widened shunt bus and Kelvin trace.+for item in board.GetTracks():+ if item.GetNetname()!='LOAD_POS':continue+ 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();x,y=pcbnew.ToMM(p.x),pcbnew.ToMM(p.y)+ if 126.0<x<126.9 and (abs(y-86.7)<.001 or abs(y-87.0)<.001):p.y=pcbnew.FromMM(87.2);setter(p)+# Keep the setpoint route below the moved power transfer, with its original op-amp pad fixed.+for item in list(board.GetTracks()):+ if item.GetNetname()!='I_SET':continue+ if not isinstance(item,pcbnew.PCB_VIA) and item.GetLayer()==pcbnew.F_Cu:+ a=item.GetStart();b=item.GetEnd()+ if min(pcbnew.ToMM(a.x),pcbnew.ToMM(b.x))>=125 and min(pcbnew.ToMM(a.y),pcbnew.ToMM(b.y))>=87.9:board.Remove(item);continue+ 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)<.001:p.y=pcbnew.FromMM(88.3);setter(p)+track('I_SET',pcbnew.F_Cu,(127.8625,87.95),(127.8625,88.3),.25)+track('I_SET',pcbnew.F_Cu,(127.8625,88.3),(125,88.3),.25)+# Parallel source connection, smaller hole off the inner bus turn.+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(125.1),pcbnew.FromMM(83.45)));via.SetWidth(pcbnew.FromMM(.6));via.SetDrill(pcbnew.FromMM(.3));via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(nets['SHUNT_P']);board.Add(via)+track('SHUNT_P',pcbnew.F_Cu,(124.92,82),(124.92,83.27),.65)+track('SHUNT_P',pcbnew.F_Cu,(124.92,83.27),(125.1,83.45),.65)+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_v68.pyadded+216@@ -0,0 +1,216 @@+"""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()+ points=[(114,82),(138,82),(138,99),(100,99),(100,89.4),(114,89.4)] if layer==pcbnew.F_Cu else [(118.8,70),(132,70),(132,82),(138,82),(138,99),(100,99),(100,89.4),(114,89.4),(114,84),(118.8,84)]+ for x,y in points: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)+# Clearance-aware ground connection to the existing lower return plane.+track('GND',pcbnew.F_Cu,(112.728,88.696),(112.7,88.8),.4)+track('GND',pcbnew.F_Cu,(112.7,88.8),(112.7,99.5),.4)+# Restore the displaced signal-ground region; native checks remain mandatory.+track('GND',pcbnew.B_Cu,(117.1, 77.2),(116.0, 77.2),.25)+track('GND',pcbnew.B_Cu,(116.0, 77.2),(115.4, 77.8),.25)+track('GND',pcbnew.B_Cu,(115.4, 77.8),(114.9, 77.8),.25)+track('GND',pcbnew.B_Cu,(114.9, 77.8),(113.8, 78.9),.25)+track('GND',pcbnew.B_Cu,(113.8, 78.9),(106.4, 78.9),.25)+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(106.4),pcbnew.FromMM(78.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.F_Cu,(106.4, 78.9),(105.9, 79.4),.25)+track('GND',pcbnew.F_Cu,(105.9, 79.4),(105.9, 79.5),.25)+track('GND',pcbnew.F_Cu,(105.9, 79.5),(105.8, 79.6),.25)+track('GND',pcbnew.F_Cu,(105.8, 79.6),(105.8, 80.1),.25)+track('GND',pcbnew.F_Cu,(105.8, 80.1),(105.3, 80.6),.25)+via=pcbnew.PCB_VIA(board);via.SetPosition(pcbnew.VECTOR2I(pcbnew.FromMM(105.3),pcbnew.FromMM(80.6)));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)+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}))