Finding swapped pads between PCB and schematic

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")
2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.