#!/usr/bin/env python3 """v6 T3a2 - patch Drogheda-sym:ZED-F9P for the REAL LGA-102 land pattern. Cross-check performed 2026-08-24 against the EasyEDA/LCSC part C5120257 generated by easyeda2kicad (Drogheda-v6.kicad_sym + Drogheda-v6.pretty): * pads 1-54 map EXACTLY to the u-blox datasheet pin assignment - every function matched (GND set, RF_IN 2, ANT_DETECT 4, ANT_OFF 5, ANT_SHORT_N 6, VCC_RF 7, GEOFENCE_STAT 19, RTK_STAT 20, RXD2 26, TXD2 27, VCC 33/34, V_BCKP 36, V_USB 38, USB_DM/DP 39/40, TXD 42, RXD 43, SDA 44, SCL 45, TX_READY 46, D_SEL 47, RESET_N 49, SAFEBOOT_N 50, EXTINT 51, TIMEPULSE 53, and the identical 21 Reserved pads). * pads 55-102 are 48 ADDITIONAL GND pads - the ground/thermal array under the module. So the datasheet transcription is independently confirmed, and the only thing missing from our symbol is the ground array. This patch adds pads 55-102 as GND, stacked on the pin-1 coordinate so a single GND label connects them all, and points Footprint at the real land pattern. Usage: python3 2026-08-24_v6_T3a2_f9p_ground_array.py [--apply] """ import os, sys, shutil, re KI = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '03-Architecture-Hardware', 'kicad') LIB = os.path.join(KI, 'Drogheda-sym.kicad_sym') APPLY = '--apply' in sys.argv FP = 'Drogheda-v6:LCC-LGA-102_L22.0-W17.0_FM660' src = open(LIB, encoding='utf-8').read() i = src.find(' (symbol "ZED-F9P"\n') if i < 0: sys.exit('ZED-F9P symbol not found') j = src.find(' (symbol "LDO_SOT23-5"\n') blk = src[i:j] if '(number "102"' in blk: sys.exit('REFUSING: ground array already present') # pin 1 (GND) coordinate, to stack the array on m = re.search(r'\(pin power_in line\s*\n\s*\(at ([-\d.]+) ([-\d.]+) (\d+)\)(?:(?!\(pin ).)*?\(number "1"', blk, re.S) if not m: sys.exit('could not locate pin 1') x, y, rot = m.group(1), m.group(2), m.group(3) print('stacking pads 55-102 on pin 1 at (%s, %s) rot %s' % (x, y, rot)) extra = '' for n in range(55, 103): extra += (' (pin passive line\n (at %s %s %s)\n (length 5.08)\n' ' (name "GND"\n (effects (font (size 1.27 1.27)))\n )\n' ' (number "%d"\n (effects (font (size 1.27 1.27)))\n )\n )\n' ) % (x, y, rot, n) k = blk.rindex(' )\n )\n') # close of the _1_1 sub-symbol newblk = blk[:k] + extra + blk[k:] newblk = newblk.replace('(property "Footprint" "Drogheda:u-blox_ZED-F9P_LGA54"', '(property "Footprint" "%s"' % FP, 1) newblk = newblk.replace('LCSC C5120257.', 'LCSC C5120257. Land pattern = LGA-102: pads 1-54 per the datasheet, pads 55-102 are an ' 'additional GND array under the module (stacked on the pin-1 coordinate in this symbol).', 1) new = src[:i] + newblk + src[j:] nums = re.findall(r'\(number "(\d+)"', newblk) print('pin numbers now: %d, unique %d, missing %s' % (len(nums), len(set(nums)), sorted(set(range(1,103)) - set(int(n) for n in nums)) or 'none')) print('footprint property ->', FP) def bal(t): d=0; instr=False; esc=False for ch in t: if esc: esc=False; continue if ch=='\\': esc=True; continue if ch=='"': instr = not instr; continue if instr: continue if ch=='(': d+=1 elif ch==')': d-=1 return d print('paren balance:', bal(new)) if not APPLY: print('\nDRY RUN - nothing written. Re-run with --apply.'); sys.exit(0) if bal(new) != 0: sys.exit('REFUSING: paren imbalance') shutil.copy2(LIB, LIB + '.T3a2bak') open(LIB, 'w', encoding='utf-8').write(new) print('\nAPPLIED. Backup: Drogheda-sym.kicad_sym.T3a2bak')