"""Check registered sense copper against native physical connectivity.""" import pcbnew as pcb def check_measurement_branches(board, branches): if not branches: return {'branches': [], 'errors': []} board.BuildConnectivity() connection = board.GetConnectivity() tracks = {t.m_Uuid.AsString(): t for t in board.GetTracks()} pads = {(f.GetReference(), p.GetNumber()): p for f in board.GetFootprints() for p in f.Pads()} results, errors = [], [] for branch in branches: name = branch['connection'] ids = set(branch['track_uuids']) source_key, target_key = tuple(branch['from']), tuple(branch['to']) source, target = pads.get(source_key), pads.get(target_key) issues = [] if source is None or target is None or not ids <= tracks.keys(): errors.append({'kind': 'kelvin_missing_items', 'connection': name}) continue if source.GetNetname() != target.GetNetname() or source.GetNetname() != branch['net']: issues.append('net_changed') if source_key[1] not in ('9', '10') or not target_key[0].startswith('Rs'): issues.append('unexpected_measurement_endpoints') seen = set() pending = {t.m_Uuid.AsString() for t in connection.GetConnectedTracks(source)} & ids while pending: uid = pending.pop() if uid in seen: continue seen.add(uid) pending.update({t.m_Uuid.AsString() for t in connection.GetConnectedTracks(tracks[uid])} & ids - seen) if seen != ids: issues.append('disconnected_registered_segments') touched = set() external = set() for uid in ids: track = tracks[uid] if isinstance(track, pcb.PCB_VIA) or track.GetLayer() != pcb.F_Cu: issues.append('measurement_changed_layer') if track.GetWidth() != pcb.FromMM(branch['width_mm']) or track.GetNetname() != branch['net']: issues.append('measurement_geometry_or_net_changed') touched.update((p.GetParentFootprint().GetReference(), p.GetNumber()) for p in connection.GetConnectedPads(track)) for other in connection.GetConnectedTracks(track): other_id = other.m_Uuid.AsString() if other_id in ids: continue # Future load copper may join at the shunt land, never on the sense run. shared = [q for q in (track.GetStart(), track.GetEnd()) if q == other.GetStart() or q == other.GetEnd()] if not shared or not all(target.HitTest(q) for q in shared): external.add(other_id) if touched != {source_key, target_key}: issues.append('unexpected_or_missing_pad_contact') if external: issues.append('branch_join_outside_shunt_land') length = sum(pcb.ToMM(tracks[uid].GetLength()) for uid in ids) if abs(length - branch['length_mm']) > .001: issues.append('measurement_length_changed_requires_review') result = {'connection': name, 'from': source_key, 'to': target_key, 'physically_touched_pads': sorted(touched), 'length_mm': round(length, 5), 'external_track_joins': sorted(external), 'passed': not issues} results.append(result) if issues: errors.append({'kind': 'kelvin_branch', 'connection': name, 'issues': sorted(set(issues))}) return {'branches': results, 'errors': errors, 'limits': 'Zones and final shunt load-current entry geometry require review after power copper is added'}