# -*- coding: UTF-8 -*-
"""
Module for importing KiCAD XML netlist. It also opens all .sch files
in the hierarchy including and under the sheet for which the XML file
was generated and collects verbatim text blocks.
The .sch files are assumed to be in the same folder as the intermediate XML
netlist.
"""
try:
from lxml import etree
# print("running with lxml.etree")
except ImportError:
try:
# Python 2.5
import xml.etree.cElementTree as etree
# print("running with cElementTree on Python 2.5+")
except ImportError:
try:
# Python 2.5
import xml.etree.ElementTree as etree
# print("running with ElementTree on Python 2.5+")
except ImportError:
try:
# normal cElementTree install
import cElementTree as etree
# print("running with cElementTree")
except ImportError:
try:
# normal ElementTree install
import elementtree.ElementTree as etree
# print("running with ElementTree")
except ImportError:
raise ImportError("Failed to import ElementTree from any known place")
import re, sys, os
from pyopus.netlister import PyNetlisterError
__all__ = [ "readKicadXML" ]
def argsort(seq):
return sorted(range(len(seq)), key=seq.__getitem__)
def readSheet(node):
sheet={}
sheet["attrib"]=node.attrib
tblock=node.find("title_block")
for cname in ["title", "company", "rev", "date", "source"]:
el=tblock.find(cname)
sheet[cname]=el.text
n=[]
t=[]
for c in tblock.findall("comment"):
a=c.attrib
n.append(a["number"])
t.append(a["value"])
ii=argsort(n)
sheet["comments"]=[t[ndx] for ndx in ii]
return sheet
def readDesign(node):
sheets={}
sheetOrder=[]
data={
"sheets": sheets,
"sheetOrder": sheetOrder,
}
for c in node:
if c.tag in set(["source", "date", "tool"]):
data[c.tag]=c.text
elif c.tag=="sheet":
sheet=readSheet(c)
name=sheet["attrib"]["name"]
sheets[name]=sheet
sheetOrder.append(name)
return data
def readComponents(node):
components={}
componentOrder=[]
for c in node:
ref=c.attrib["ref"]
desc={}
for ca in c:
if ca.tag=="fields":
fields={}
for f in ca:
name=f.attrib["name"]
fields[name]=f.text
desc["fields"]=fields
elif ca.tag=="libsource":
desc["libsource"]=(ca.attrib["lib"], ca.attrib["part"])
elif ca.tag=="sheetpath":
d={}
d.update(ca.attrib)
desc["sheetpath"]=d
else:
# Ordinary attribute
desc[ca.tag]=ca.text
components[ref]=desc
componentOrder.append(ref)
return components, componentOrder
def readLibparts(node):
libparts={}
for c in node:
tag=(c.attrib["lib"], c.attrib["part"])
df=c.find("description")
description=df.text if df is not None else None
fields={}
for field in c.find("fields"):
fields[field.attrib["name"]]=field.text
pins={}
pinOrder=[]
pn=c.find("pins")
if pn is not None:
for pin in c.find("pins"):
d={}
d.update(pin.attrib)
if "num" in d:
num=int(d["num"])
del d["num"]
pins[num]=d
pinOrder.append(num)
libparts[tag]={
"fields": fields,
"pins": pins,
"pinOrder": sorted(pinOrder)
}
# Get aliases, link alias entries to original
al=c.find("aliases")
if al is not None:
for alias in al:
tag1=(tag[0],alias.text)
libparts[tag1]=libparts[tag]
return libparts
def readLibraries(node):
libraries={}
for c in node:
name=c.attrib["logical"]
src=c.find("uri").text
libraries[name]=src
return libraries
def readNets(node):
nets={}
for c in node:
code=c.attrib["code"]
name=c.attrib["name"]
pinlist=[]
for n in c:
pinlist.append(
(n.attrib["ref"], int(n.attrib["pin"]))
)
nets[name]={
"code": code,
"pins": pinlist
}
return nets
# start of string, "Text notes ", 0 or more any character except newline, before end of line or end of string
textSchPat=re.compile("^Text Notes .*$", flags=re.MULTILINE)
# start of string, 0 or more whitespaces, "Text", integer, 1 or more space or tab, "position", 1 or more space or tab, "=", 1 or more space or tab
# "top" or "bottom", 0 or more whitespaces, 0 or more any character except newline, before end of line or end of string
verbatimTextPat=re.compile(r"^\s*Text([1-9][0-9]*)[ \t]+position[ \t]*=[ \t]*(top|bottom)\s*(.*)$", flags=re.MULTILINE)
textSchPatV6=re.compile("^\s*\(text\s+\"", flags=re.MULTILINE)
# start of string, 0 or more whitespaces, "Text", integer, 1 or more space or tab, "position", 1 or more space or tab, "=", 1 or more space or tab
# "top" or "bottom", 0 or more whitespaces, 0 or more any character except newline, before end of line or end of string
verbatimTextPatV6=re.compile(r"Text([1-9][0-9]*)[ \t]+position[ \t]*=[ \t]*(top|bottom)\s*(.*)$", flags=re.MULTILINE)
def collectTexts(xmlFile, data):
data["texts"]={}
# Look in the input XML file folder
head, tail = os.path.split(xmlFile)
# Go through sheets, open schematic files, and extract text blocks
for name, sheet in data["design"]["sheets"].items():
source=sheet["source"]
try:
with open(os.path.join(head, source), "r") as f:
txt=f.read(-1)
except IOError:
raise PyNetlisterError("Failed to read schematic file '"+source+"'.")
# Determine file format
if txt.startswith("EESchema Schematic File Version 4"):
isV6=False
textFindPattern=textSchPat
textPattern=verbatimTextPat
else:
isV6=True
textFindPattern=textSchPatV6
textPattern=verbatimTextPatV6
# Find text blocks
for match in textFindPattern.finditer(txt):
if match:
pos=match.end()
# print("1-")
# print(txt[pos:pos+100])
# print("--1")
# Parse text block
match=textPattern.search(txt, pos)
if match:
if isV6:
num=int(match.group(1))
position=match.group(2)
# Get rid of escape sequences
text=match.group(3).encode("utf-8").decode("unicode_escape").strip()
# Remove trailing quote
if text[-1]=="\"":
text=text[:-1]
text=text.strip()
else:
num=int(match.group(1))
position=match.group(2)
# Get rid of escape sequences
text=match.group(3).encode("utf-8").decode("unicode_escape").strip()
# print("Number: %d" % num)
# print("Position: "+position)
# print("Text: "+text)
# print("")
textDesc={
"position": position,
"text": text,
"sheet": name
}
data["texts"][num]=textDesc
[docs]def readKicadXML(filename):
"""
Reads a KiCAD XML netlist from file *filename* and returns the netlist
as a Python structure.
"""
data={}
try:
with open(filename, "r") as f:
tree=etree.parse(filename)
# <export>
root=tree.getroot()
data["version"]=root.attrib["version"]
# Children (design, components, libparts, libraries, nets)
for c in root:
if c.tag=="design":
data["design"]=readDesign(c)
elif c.tag=="components":
data["components"], data["componentOrder"] = readComponents(c)
elif c.tag=="libparts":
data["libparts"]=readLibparts(c)
elif c.tag=="libraries":
data["libraries"]=readLibraries(c)
elif c.tag=="nets":
data["nets"]=readNets(c)
collectTexts(filename, data)
except IOError:
raise PyNetlisterError("Failed to open input file '"+filename+"'.")
return data
if __name__ == "__main__":
from pprint import pprint
infile=sys.argv[1]
pprint(readKicadXML(infile))