Skip to content

Instantly share code, notes, and snippets.

@alexisvl
Last active December 28, 2015 18:17
Show Gist options
  • Save alexisvl/dc0bf5f5c018d43fa736 to your computer and use it in GitHub Desktop.
Save alexisvl/dc0bf5f5c018d43fa736 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# Public domain.
import argparse
import collections
import os
import sys
Library = collections.OrderedDict
def main(argv):
parser = argparse.ArgumentParser(description="Concatenate KiCad symbol libraries.")
parser.add_argument("-o", metavar="DEST", required=True,
dest="dest", help="name of destination library")
parser.add_argument("src", metavar="SRC", nargs="+",
help="names of source libraries")
parser.add_argument("--delete-sources", "-D", dest="delete",
action="store_const", const=True, default=False,
help="delete sources after concatenation")
parser.add_argument("-n", dest="dry",
action="store_const", const=True, default=False,
help="dry run - do nothing, but list contents of merged library")
parser.add_argument("--clobber", dest="clobber",
action="store_const", const=True, default=False,
help="clobber duplicate symbols, preferring those listed later")
parser.add_argument("--sort", dest="sort",
action="store_const", const=True, default=False,
help="sort the final library")
args = parser.parse_args(argv)
libraries = [load_lib(i) for i in args.src]
dest_lib = Library()
for i in libraries:
cat_lib(dest_lib, i, clobber=args.clobber)
if args.sort:
dest_lib = sorted_lib(dest_lib)
if args.dry:
for i in dest_lib:
print(i)
return
if args.delete:
for i in args.src:
unlink_if_exists(i + ".lib")
unlink_if_exists(i + ".dcm")
with open(args.dest + ".lib", "w") as f:
Part.writefile(f, dest_lib)
if True in [i.dcm is not None for i in dest_lib.values()]:
with open(args.dest + ".dcm", "w") as f:
DCM.writefile(f, dest_lib)
def unlink_if_exists(i):
try:
os.unlink(i)
except FileNotFoundError:
pass
def load_lib(name):
"""Load a KiCad symbol library as a dictionary of str->Part.
"""
parts = Library()
with open(name + ".lib") as f:
assert f.readline() == "EESchema-LIBRARY Version 2.3\n", "invalid lib file format in " + name
assert f.readline() == "#encoding utf-8\n", "invalid lib file format in " + name
while True:
part = Part.load(f)
if part is None:
break
parts[part.name] = part
if os.path.exists(name + ".dcm"):
with open(name + ".dcm") as f:
assert f.readline() == "EESchema-DOCLIB Version 2.0\n", "invalid DCM file format in " + name
while True:
dcm = DCM.load(f)
if dcm is None:
break
assert dcm.name in parts, ("DCM contains part %s not found in library %s" % (dcm.name, name))
parts[dcm.name].dcm = dcm
return parts
def cat_lib(dest, src, clobber):
"""Concatenate library 'src' into library 'dest'"""
for i in src:
assert i not in dest or clobber, ("Duplicate part %s found in multiple libraries. Use --clobber to overwrite." % i)
dest[i] = src[i]
def sorted_lib(lib):
"""Sort a library alphabetically, returning a new one."""
new_lib = Library()
keys = sorted(lib.keys())
for i in keys:
new_lib[i] = lib[i]
return new_lib
class Part(object):
"""Very simplistic representation of a KiCad part. Not actually parsed."""
def __init__(self, name, data):
self.name = name
self.data = data
self.dcm = None
def write(self, f):
f.write("#\n")
f.write("# %s\n" % self.name)
f.write("#\n")
f.writelines(self.data)
@classmethod
def load(cls, f):
name = None
data = []
for line in f:
if line.startswith("#"):
continue
if line.startswith("DEF "):
name = line.split()[1]
data.append(line)
if line == "ENDDEF\n":
break
if name is None:
return None
else:
return cls(name, data)
@classmethod
def writefile(cls, f, parts):
f.write("EESchema-LIBRARY Version 2.3\n")
f.write("#encoding utf-8\n")
for i in parts.values():
i.write(f)
f.write("#\n")
f.write("#End Library\n")
class DCM(object):
"""Very simplistic representation of a KiCad DCM file. Not actually parsed."""
def __init__(self, name, data):
self.name = name
self.data = data
def write(self, f):
f.write("#\n")
f.writelines(self.data)
@classmethod
def load(cls, f):
name = None
data = []
for line in f:
if line.startswith("#"):
continue
if line.startswith("$CMP "):
name = line.split()[1]
data.append(line)
if line == "$ENDCMP\n":
break
if name is None:
return None
else:
return cls(name, data)
@classmethod
def writefile(cls, f, parts):
f.write("EESchema-DOCLIB Version 2.0\n")
for i in parts.values():
if i.dcm is not None:
i.dcm.write(f)
f.write("#\n")
f.write("#End Doc Library")
if __name__ == "__main__":
main(sys.argv[1:])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment