I recently realized that if you have a symbol or footprint library in KiCad that is below the project’s folder, the path gets stored as for instance ${KIPRJMOD}/subfolder/library.lib, but if it’s above the project’s folder it gets stored as an absolute path like /home/rsholmes/Documents/folder/library.lib. That’s rather annoying. For instance I often have a folder for a module containing two or more KiCad projects such as the PCB and the front panel, and I want them both to use the same library rather than two copies of (supposedly) the same library. But then if I move that folder, or if I put it up on GitHub, then that absolute path becomes garbage.
So I threw together the following Python script. You can run it from a KiCad project directory and give it fp-lib-table and sym-lib-table as arguments, and it’ll find any absolute paths in those files and convert them to paths relative to ${KIPRJMOD}, which it takes to be the current directory. (Or you can put ‘-p path’ in the command line and it’ll use the specified path instead of the current directory.) The original files are written to backups.
Then if you run KiCad it’ll look on those relative paths for the libraries instead of absolute paths.
If you specify -d num then it’ll only convert paths that are within that depth above the project directory. So you can convert any paths that are within the overall module’s folder structure while keeping any that point outside that structure as absolutes.
Yeah, error handling is a joke, but it should err on the side of harmlessness.
#!/usr/bin/python3
# Replace absolute paths in input KiCad files with paths relative to
# PWD or to specified (with -p option) path, by replacing path with
# ${KIPRJMOD}, its parent with ${KIPRJMOD}/.., etc.
# If -d num is specified, depth will be limited: Only paths that
# diverge num levels or fewer above PWD will be converted. (0 means
# only convert PWD, 1 means convert PWD or the directory above, etc.)
# E.g., for fp-lib-table in /home/rsholmes/Documents/DC_Mixer/PCB/ao_dc_mixer:
#
# (fp_lib_table
# (lib (name ao_tht)(type KiCad)(uri ${KIPRJMOD}/ao_tht.pretty)(options "")(descr ""))
# (lib (name ao_dc_mixer)(type KiCad)(uri "/home/rsholmes/Documents/DC_Mixer/Panel/ao_dc_mixer-panel/ao_dc_mixer.pretty")(options "")(descr ""))
#)
#
# becomes
#
# (fp_lib_table
# (lib (name ao_tht)(type KiCad)(uri ${KIPRJMOD}/ao_tht.pretty)(options "")(descr ""))
# (lib (name ao_dc_mixer)(type KiCad)(uri "${KIPRJMOD}/../../Panel/ao_dc_mixer-panel/ao_dc_mixer.pretty")(options "")(descr ""))
#)
from sys import argv
from os.path import dirname
from os import getcwd
def main():
pwd = getcwd()
depth = 9999
findingpwd = False
findingdepth = False
for argi in argv[1:]:
if findingpwd:
findingpwd = False
pwd = argi
elif findingdepth:
findingdepth = False
depth = int (argi)
elif argi == "-p":
findingpwd = True
elif argi == "-d":
findingdepth = True
else:
try:
print (depth)
with open(argi, 'r') as ifile:
ifc = ifile.read()
ifcc = str(ifc)
tarp = pwd
rep = '${KIPRJMOD}'
d = 0
while tarp != '/' and d <= depth:
ifc = ifc.replace(tarp, rep)
tarp = dirname(tarp)
rep = rep + "/.."
d += 1
if ifcc != ifc:
with open (argi+".bak", 'w') as ofile:
ofile.write(ifcc)
with open (argi, 'w') as ofile:
ofile.write(ifc)
except:
print ("Error with file", argi)
if __name__ == '__main__':
main()