Last active
December 28, 2015 18:17
-
-
Save alexisvl/dc0bf5f5c018d43fa736 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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