Continuing @nall 's How to find reversed pin orientation in schematic vs PCB I have created a script to find swapped pads between PCB and schematic. Maybe it will be useful to someone.
In my case, it was after importing a project from Altium that was done by other people. So I don’t know if they messed it up in Altium or if was it a result of the importation.
The script uses netlists from the schematic:
and from the PCB:
It doesn’t use any external packages. It’s a vanilla file parsing. Well, because it seemed almost impossible with KiPython or KiUtils.
It analyzes nets of two-pin components and what other components are connected to those nets. So it relies on the reference designators being the same between PCB and Schematic (this can be checked first by opening PCB Update (F8) and checking for missing reference errors).
It then outputs which references are ok and which are swapped or for which only one pair of nets was found matching * - which is still likely to be a valid assessment, but to be sure it prints components on matching net for visual inspection.
Orientation of R38
ok
Orientation of R40
ok
Orientation of C66
swapped
Orientation of C67
swapped
Orientation of L12
ok*
Only single matching net pair: Net-(R40-Pad1) 327NETR40_1
['R40', 'C100', 'C98']
['R40', 'C100', 'C98']
...
It summarizes all footprints that need to be swapped. In my case the list was long:
Swapped refs:
['C66', 'C67', 'C128', 'C15', 'C14', 'C124', 'C125', 'L12', 'R41', 'R16', .... ]
In the end, as a bonus, it takes the list and swaps pads pin numbers in the Kicad PCB file. Since it operates directly on the board file, all my footprints are on the board without libraries. The other way could be to rotate symbols on the schematic.
Python3 script (no kicad libraries needed):
SCH_NETLIST = 'C:/path/MyBoard.net' #File / export / netlist
PCB_NETLIST = 'C:/path/MyBoard.d356'#File / Fabrication outputs / IPC D356 Netlist File
PCB_NEW = 'C:/path/MyBoard.kicad_pcb' #optional - I am modifying the file directly since my footprints are embedded in the pcbnew and not in the footprint library
def parse_sch_netlist():
nets = {}
net = {'refs': []} # Initialize net here
with open(SCH_NETLIST, 'r') as file:
for line in file:
line = line.strip()
# Start reading from this line onwards
if line.startswith('(net ('):
pattern = 'name "'
start_index = line.find(pattern) + len(pattern)
end_index = line.find('"', start_index)
netname = line[start_index:end_index]
net = {'refs': []} # clear currently processed net
nets[netname] = net
elif line.startswith('(node '):
pattern = '(ref "'
start_index = line.find(pattern) + len(pattern)
end_index = line.find('"', start_index)
ref = line[start_index:end_index]
pattern = '(pin "'
start_index = line.find(pattern) + len(pattern)
end_index = line.find('"', start_index)
pin = line[start_index:end_index]
net['refs'].append((ref, pin))
return nets
def parse_pcb_netlist():
nets = {}
with open(PCB_NETLIST, 'r') as file:
for line in file:
if len(line) > 20:
netname = line[0:20].strip()
ref = line[20:26].strip()
pin = line[27:33].strip()
if ref != 'VIA':
if netname in nets:
nets[netname]['refs'].append((ref, pin))
else:
nets[netname] = {'refs': [(ref, pin)]}
return nets
def transpose_nets_with_refs(netlist):
refs = {}
for net in netlist:
for refpin in netlist[net]['refs']:
ref = refpin[0]
pin = refpin[1]
if ref not in refs:
refs[ref] = [(pin, net)]
else:
refs[ref].append((pin, net))
# Sort nets according to pin for each referecne
for ref in refs:
refs[ref] = sorted(refs[ref], key=lambda x: x[0]) # Sort nets based on pin
# Remove pin names to alow for indexing by value
refs[ref] = [value[1] for value in refs[ref]]
return refs
def filter_two_net_elements(original_dict):
filtered_dict = {key: values for key, values in original_dict.items() if len(values) == 2}
return filtered_dict
def remove_pins_from_tuples(nets_refpins):
nets_refs = {}
for net in nets_refpins:
nets_refs[net] = [ref for ref, _ in nets_refpins[net]['refs']]
return nets_refs
nets_refpins_sch = parse_sch_netlist()
nets_refpins_pcb = parse_pcb_netlist()
#remove pins from tuples for easier net extraction later
nets_ref_sch = remove_pins_from_tuples(nets_refpins_sch)
nets_ref_pcb = remove_pins_from_tuples(nets_refpins_pcb)
ref_pinnets_sch = transpose_nets_with_refs(nets_refpins_sch)
ref_pinnets_pcb = transpose_nets_with_refs(nets_refpins_pcb)
#passives
ref2pin_nets_sch = filter_two_net_elements(ref_pinnets_sch)
ref2pin_nets_pcb = filter_two_net_elements(ref_pinnets_pcb)
# searcch for refs from pcb nets in sch nets
swapped_refs = []
for ref in ref2pin_nets_sch:
netpin1_sch = ref2pin_nets_sch[ref][0]
netpin2_sch = ref2pin_nets_sch[ref][1]
netpin1_pcb = ref2pin_nets_pcb[ref][0]
netpin2_pcb = ref2pin_nets_pcb[ref][1]
refs_net1_sch = nets_ref_sch[netpin1_sch]
refs_net2_sch = nets_ref_sch[netpin2_sch]
refs_net1_pcb = nets_ref_pcb[netpin1_pcb]
refs_net2_pcb = nets_ref_pcb[netpin2_pcb]
is_net1_equal = set(refs_net1_sch) == set(refs_net1_pcb)
is_net2_equal = set(refs_net2_sch) == set(refs_net2_pcb)
is_net1_swapped = set(refs_net1_sch) == set(refs_net2_pcb)
is_net2_swapped = set(refs_net2_sch) == set(refs_net1_pcb)
print("\nOrientation of " + ref)
if (is_net1_equal and is_net2_equal):
print('ok')
elif (is_net1_swapped and is_net2_swapped):
print('swapped')
swapped_refs.append(ref)
if (is_net1_equal and is_net2_equal):
print("error - both swapped and normal list net match")
elif(is_net1_equal or is_net2_equal):
print('ok*')
if(is_net1_equal):
print("Only single matching net pair: " + netpin1_sch + " " + netpin1_pcb)
print(sorted(refs_net1_sch))
print(sorted(refs_net1_pcb))
else:
print("Only single matching net pair: " + netpin2_sch + " " + netpin2_pcb)
print(sorted(refs_net2_sch))
print(sorted(refs_net2_pcb))
if(is_net1_swapped or is_net2_swapped):
print("error - pair of swapped list net matches and pair of normal list net matches")
elif(is_net1_swapped or is_net2_swapped):
print('swapped*')
swapped_refs.append(ref)
if is_net1_swapped:
print("Only single matching net pair: " + netpin1_sch + " " + netpin2_pcb)
print(sorted(refs_net1_sch))
print(sorted(refs_net2_pcb))
else:
print("Only single matching net pair: " + netpin2_sch + " " + netpin1_pcb)
print(sorted(refs_net2_sch))
print(sorted(refs_net1_pcb))
else:
print('undetermined') #nothing matches - make sure reference in schematic and pcb match
print(refs_net1_sch, refs_net2_sch, refs_net1_pcb, refs_net2_pcb)
print("* only one net list was matching")
print("Swapped refs:")
print(swapped_refs)
def swap_pads(lines, references_to_swap): #swap pad pins in the PCB file
PAD_1 = '(pad "1"'
PAD_2 = '(pad "2"'
new_lines = []
found_reference = False
for line in lines:
if any(f'(property "Reference" "{ref}"' in line for ref in references_to_swap):
found_reference = True
if found_reference:
if PAD_1 in line:
new_lines.append(line.replace(PAD_1, PAD_2))
elif PAD_2 in line:
new_lines.append(line.replace(PAD_2, PAD_1))
found_reference = False
else:
new_lines.append(line)
else:
new_lines.append(line)
return new_lines
if PCB_NEW is not None:
references_to_swap = swapped_refs
input_file_path = PCB_NEW
output_file_path = PCB_NEW + ".tmp"
with open(input_file_path, 'r') as pcbfile:
lines = pcbfile.readlines()
modified_lines = swap_pads(lines, references_to_swap)
input("Press Enter to save file")
with open(input_file_path, 'w') as pcbfile:
pcbfile.writelines(modified_lines)
print("Open PCB in kicad and save to fix kicad file format order")