commit Brian's BOM generation scripts so they don't get lost
This commit is contained in:
parent
15f0147ece
commit
4c063574ae
7
TODO.txt
7
TODO.txt
|
@ -19,6 +19,11 @@ Common
|
||||||
* Push file open semantics down to one of the base frame classes ( likely candidate is
|
* Push file open semantics down to one of the base frame classes ( likely candidate is
|
||||||
WinEDA_BasicFrame ) so that file open behavior is consistent across all applications.
|
WinEDA_BasicFrame ) so that file open behavior is consistent across all applications.
|
||||||
|
|
||||||
|
* Look over Brian's python BOM generation scripts, which are now in
|
||||||
|
scripts/python/ky and sort them out, and get something into the installation as well.
|
||||||
|
Code came from Brian in this posting's attachment, which is ky2.zip:
|
||||||
|
https://lists.launchpad.net/kicad-developers/msg06763.html
|
||||||
|
but is now in scripts/python/ky temporarily.
|
||||||
|
|
||||||
|
|
||||||
CvPCB
|
CvPCB
|
||||||
|
@ -48,6 +53,4 @@ E6) Start initial work for changing component library file format to use Dick's
|
||||||
PCBNew
|
PCBNew
|
||||||
------
|
------
|
||||||
|
|
||||||
Dick:
|
|
||||||
|
|
||||||
* Use BOARD_ITEM::MenuIcon() in the onrightclick.cpp
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
#
|
||||||
|
# Example python script to generate a BOM from a KiCad generic netlist
|
||||||
|
#
|
||||||
|
# Example: Tab delimited list (The same as std output) Ungrouped
|
||||||
|
#
|
||||||
|
|
||||||
|
# Import the KiCad python helper module and the csv formatter
|
||||||
|
import ky
|
||||||
|
import csv
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Generate an instance of a generic netlist, and load the netlist tree from
|
||||||
|
# the command line option. If the file doesn't exist, execution will stop
|
||||||
|
net = ky.netlist(sys.argv[1])
|
||||||
|
|
||||||
|
# Open a file to write to, if the file cannot be opened output to stdout
|
||||||
|
# instead
|
||||||
|
try:
|
||||||
|
f = open(sys.argv[2], 'w')
|
||||||
|
except IOError:
|
||||||
|
print >> sys.stderr, __file__, ":", e
|
||||||
|
f = stdout
|
||||||
|
|
||||||
|
# Create a new csv writer object to use as the output formatter, although we
|
||||||
|
# are created a tab delimited list instead!
|
||||||
|
out = csv.writer(f, delimiter='\t', quoting=csv.QUOTE_NONE)
|
||||||
|
|
||||||
|
# Output a field delimited header line
|
||||||
|
out.writerow(['Source:', net.getSource()])
|
||||||
|
out.writerow(['Date:', net.getDate()])
|
||||||
|
out.writerow(['Tool:', net.getTool()])
|
||||||
|
out.writerow(['Component Count:', len(net.components)])
|
||||||
|
out.writerow(['Ref', 'Value', 'Part', 'Documentation', 'Description', 'Vendor'])
|
||||||
|
|
||||||
|
# Output all of the component information
|
||||||
|
for c in net.components:
|
||||||
|
out.writerow([c.getRef(), c.getValue(), c.getLib() + "/" + c.getPart(),
|
||||||
|
c.getDatasheet(), c.getDescription(), c.getField("Vendor")])
|
|
@ -0,0 +1,38 @@
|
||||||
|
#
|
||||||
|
# Example python script to generate a BOM from a KiCad generic netlist
|
||||||
|
#
|
||||||
|
# Example: Ungrouped (One component per row) CSV output
|
||||||
|
#
|
||||||
|
|
||||||
|
# Import the KiCad python helper module
|
||||||
|
import ky
|
||||||
|
import csv
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Generate an instance of a generic netlist, and load the netlist tree from
|
||||||
|
# the command line option. If the file doesn't exist, execution will stop
|
||||||
|
net = ky.netlist(sys.argv[1])
|
||||||
|
|
||||||
|
# Open a file to write to, if the file cannot be opened output to stdout
|
||||||
|
# instead
|
||||||
|
try:
|
||||||
|
f = open(sys.argv[2], 'w')
|
||||||
|
except IOError:
|
||||||
|
print >> sys.stderr, __file__, ":", e
|
||||||
|
f = stdout
|
||||||
|
|
||||||
|
# Create a new csv writer object to use as the output formatter
|
||||||
|
out = csv.writer(f, delimiter=',', quotechar="\"", quoting=csv.QUOTE_ALL)
|
||||||
|
|
||||||
|
# Output a field delimited header line
|
||||||
|
out.writerow(['Source:', net.getSource()])
|
||||||
|
out.writerow(['Date:', net.getDate()])
|
||||||
|
out.writerow(['Tool:', net.getTool()])
|
||||||
|
out.writerow(['Component Count:', len(net.components)])
|
||||||
|
out.writerow(['Ref', 'Value', 'Footprint', 'Datasheet', 'Manufacturer', 'Vendor'])
|
||||||
|
|
||||||
|
# Output all of the component information (One component per row)
|
||||||
|
for c in net.components:
|
||||||
|
out.writerow([c.getRef(), c.getValue(), c.getFootprint(), c.getDatasheet(),
|
||||||
|
c.getField("Manufacturer"), c.getField("Vendor")])
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
#
|
||||||
|
# Example python script to generate a BOM from a KiCad generic netlist
|
||||||
|
#
|
||||||
|
# Example: Sorted and Grouped CSV BOM
|
||||||
|
#
|
||||||
|
|
||||||
|
# Import the KiCad python helper module and the csv formatter
|
||||||
|
import ky
|
||||||
|
import csv
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Generate an instance of a generic netlist, and load the netlist tree from
|
||||||
|
# the command line option. If the file doesn't exist, execution will stop
|
||||||
|
net = ky.netlist(sys.argv[1])
|
||||||
|
|
||||||
|
# Open a file to write to, if the file cannot be opened output to stdout
|
||||||
|
# instead
|
||||||
|
try:
|
||||||
|
f = open(sys.argv[2], 'w')
|
||||||
|
except IOError:
|
||||||
|
print >> sys.stderr, __file__, ":", e
|
||||||
|
f = stdout
|
||||||
|
|
||||||
|
# Create a new csv writer object to use as the output formatter
|
||||||
|
out = csv.writer(f, delimiter=',', quotechar='\"', quoting=csv.QUOTE_ALL)
|
||||||
|
|
||||||
|
# Output a set of rows for a header providing general information
|
||||||
|
out.writerow(['Source:', net.getSource()])
|
||||||
|
out.writerow(['Date:', net.getDate()])
|
||||||
|
out.writerow(['Tool:', net.getTool()])
|
||||||
|
out.writerow(['Component Count:', len(net.components)])
|
||||||
|
out.writerow(['Ref', 'Qnty', 'Value', 'Part', 'Datasheet', 'Description', 'Vendor'])
|
||||||
|
|
||||||
|
# Get all of the components in groups of matching parts + values (see ky.py)
|
||||||
|
grouped = net.groupComponents()
|
||||||
|
|
||||||
|
# Output all of the component information
|
||||||
|
for group in grouped:
|
||||||
|
refs = ""
|
||||||
|
|
||||||
|
# Add the reference of every component in the group and keep a reference
|
||||||
|
# to the component so that the other data can be filled in once per group
|
||||||
|
for component in group:
|
||||||
|
refs += component.getRef() + ", "
|
||||||
|
c = component
|
||||||
|
|
||||||
|
# Fill in the component groups common data
|
||||||
|
out.writerow([refs, len(group), c.getValue(), c.getLib() + "/" + c.getPart(), c.getDatasheet(),
|
||||||
|
c.getDescription(), c.getField("Vendor")])
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
#
|
||||||
|
# Example python script to generate a BOM from a KiCad generic netlist
|
||||||
|
#
|
||||||
|
# Example: Sorted and Grouped HTML BOM
|
||||||
|
#
|
||||||
|
|
||||||
|
# Import the KiCad python helper module and the csv formatter
|
||||||
|
import ky
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Start with a basic html template
|
||||||
|
html = """
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1><!--SOURCE--></h1>
|
||||||
|
<p><!--DATE--></p>
|
||||||
|
<p><!--TOOL--></p>
|
||||||
|
<p><!--COMPCOUNT--></p>
|
||||||
|
<table>
|
||||||
|
<!--TABLEROW-->
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Generate an instance of a generic netlist, and load the netlist tree from
|
||||||
|
# the command line option. If the file doesn't exist, execution will stop
|
||||||
|
net = ky.netlist(sys.argv[1])
|
||||||
|
|
||||||
|
# Open a file to write to, if the file cannot be opened output to stdout
|
||||||
|
# instead
|
||||||
|
try:
|
||||||
|
f = open(sys.argv[2], 'w')
|
||||||
|
except IOError:
|
||||||
|
print >> sys.stderr, __file__, ":", e
|
||||||
|
f = stdout
|
||||||
|
|
||||||
|
# Output a set of rows for a header providing general information
|
||||||
|
html = html.replace('<!--SOURCE-->', net.getSource())
|
||||||
|
html = html.replace('<!--DATE-->', net.getDate())
|
||||||
|
html = html.replace('<!--TOOL-->', net.getTool())
|
||||||
|
html = html.replace('<!--COMPCOUNT-->', "<b>Component Count:</b>" + \
|
||||||
|
str(len(net.components)))
|
||||||
|
|
||||||
|
row = "<tr><th style='width:640px'>Ref</th>" + "<th>Qnty</th>"
|
||||||
|
row += "<th>Value</th>" + "<th>Part</th>" + "<th>Datasheet</th>"
|
||||||
|
row += "<th>Description</th>" + "<th>Vendor</th></tr>"
|
||||||
|
|
||||||
|
html = html.replace('<!--TABLEROW-->', row + "<!--TABLEROW-->")
|
||||||
|
|
||||||
|
# Get all of the components in groups of matching parts + values (see ky.py)
|
||||||
|
grouped = net.groupComponents()
|
||||||
|
|
||||||
|
# Output all of the component information
|
||||||
|
for group in grouped:
|
||||||
|
refs = ""
|
||||||
|
|
||||||
|
# Add the reference of every component in the group and keep a reference
|
||||||
|
# to the component so that the other data can be filled in once per group
|
||||||
|
for component in group:
|
||||||
|
refs += component.getRef() + ", "
|
||||||
|
c = component
|
||||||
|
|
||||||
|
row = "<tr><td>" + refs +"</td><td>" + str(len(group))
|
||||||
|
row += "</td><td>" + c.getValue() + "</td><td>" + c.getLib() + "/"
|
||||||
|
row += c.getPart() + "</td><td>" + c.getDatasheet() + "</td><td>"
|
||||||
|
row += c.getDescription() + "</td><td>" + c.getField("Vendor")
|
||||||
|
row += "</td></tr>"
|
||||||
|
|
||||||
|
html = html.replace('<!--TABLEROW-->', row + "<!--TABLEROW-->")
|
||||||
|
|
||||||
|
# Print the formatted html to the file
|
||||||
|
print >> f, html
|
|
@ -0,0 +1,113 @@
|
||||||
|
#
|
||||||
|
# Example python script to generate a BOM from a KiCad generic netlist
|
||||||
|
#
|
||||||
|
# Example: Sorted and Grouped HTML BOM with more advanced grouping
|
||||||
|
#
|
||||||
|
|
||||||
|
# Import the KiCad python helper module and the csv formatter
|
||||||
|
import ky
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Start with a basic html template
|
||||||
|
html = """
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<title>KiCad BOM Example 5</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1><!--SOURCE--></h1>
|
||||||
|
<p><!--DATE--></p>
|
||||||
|
<p><!--TOOL--></p>
|
||||||
|
<p><!--COMPCOUNT--></p>
|
||||||
|
<table>
|
||||||
|
<!--TABLEROW-->
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def myEqu(self, other):
|
||||||
|
"""myEqu is a more advanced equivalence function for components which is
|
||||||
|
used by component grouping. Normal operation is to group components based
|
||||||
|
on their Value, Library source, and Library part.
|
||||||
|
|
||||||
|
In this example of a more advanced equivalency operator we also compare the
|
||||||
|
custom fields Voltage, Tolerance and Manufacturer as well as the assigned
|
||||||
|
footprint. If these fields are not used in some parts they will simply be
|
||||||
|
ignored (they will match as both will be empty strings).
|
||||||
|
|
||||||
|
"""
|
||||||
|
result = True
|
||||||
|
if self.getValue() != other.getValue():
|
||||||
|
result = False
|
||||||
|
elif self.getLib() != other.getLib():
|
||||||
|
result = False
|
||||||
|
elif self.getPart() != other.getPart():
|
||||||
|
result = False
|
||||||
|
elif self.getFootprint() != other.getFootprint():
|
||||||
|
result = False
|
||||||
|
elif self.getField("Tolerance") != other.getField("Tolerance"):
|
||||||
|
result = False
|
||||||
|
elif self.getField("Manufacturer") != other.getField("Manufacturer"):
|
||||||
|
result = False
|
||||||
|
elif self.getField("Voltage") != other.getField("Voltage"):
|
||||||
|
result = False
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Override the component equivalence operator - it is important to do this
|
||||||
|
# before loading the netlist, otherwise all components will have the original
|
||||||
|
# equivalency operator.
|
||||||
|
ky.component.__equ__ = myEqu
|
||||||
|
|
||||||
|
# Generate an instance of a generic netlist, and load the netlist tree from
|
||||||
|
# video.tmp. If the file doesn't exist, execution will stop
|
||||||
|
net = ky.netlist(sys.argv[1])
|
||||||
|
|
||||||
|
# Open a file to write too, if the file cannot be opened output to stdout
|
||||||
|
# instead
|
||||||
|
try:
|
||||||
|
f = open(sys.argv[2], 'w')
|
||||||
|
except IOError:
|
||||||
|
print >> sys.stderr, __file__, ":", e
|
||||||
|
f = stdout
|
||||||
|
|
||||||
|
# Output a set of rows for a header providing general information
|
||||||
|
html = html.replace('<!--SOURCE-->', net.getSource())
|
||||||
|
html = html.replace('<!--DATE-->', net.getDate())
|
||||||
|
html = html.replace('<!--TOOL-->', net.getTool())
|
||||||
|
html = html.replace('<!--COMPCOUNT-->', "<b>Component Count:</b>" + \
|
||||||
|
str(len(net.components)))
|
||||||
|
|
||||||
|
row = "<tr><th style='width:640px'>Ref</th>" + "<th>Qnty</th>"
|
||||||
|
row += "<th>Value</th>" + "<th>Part</th>" + "<th>Datasheet</th>"
|
||||||
|
row += "<th>Description</th>" + "<th>Vendor</th></tr>"
|
||||||
|
|
||||||
|
html = html.replace('<!--TABLEROW-->', row + "<!--TABLEROW-->")
|
||||||
|
|
||||||
|
# Get all of the components in groups of matching parts + values (see ky.py)
|
||||||
|
grouped = net.groupComponents()
|
||||||
|
|
||||||
|
# Output all of the component information
|
||||||
|
for group in grouped:
|
||||||
|
refs = ""
|
||||||
|
|
||||||
|
# Add the reference of every component in the group and keep a reference
|
||||||
|
# to the component so that the other data can be filled in once per group
|
||||||
|
for component in group:
|
||||||
|
refs += component.getRef() + ", "
|
||||||
|
c = component
|
||||||
|
|
||||||
|
row = "<tr><td>" + refs +"</td><td>" + str(len(group))
|
||||||
|
row += "</td><td>" + c.getValue() + "</td><td>" + c.getLib() + "/"
|
||||||
|
row += c.getPart() + "</td><td>" + c.getDatasheet() + "</td><td>"
|
||||||
|
row += c.getDescription() + "</td><td>" + c.getField("Vendor")
|
||||||
|
row += "</td></tr>"
|
||||||
|
|
||||||
|
html = html.replace('<!--TABLEROW-->', row + "<!--TABLEROW-->")
|
||||||
|
|
||||||
|
# Print the formatted html to output file
|
||||||
|
print >> f, html
|
|
@ -0,0 +1,450 @@
|
||||||
|
#
|
||||||
|
# KiCad python module for interpreting generic netlists which can be used
|
||||||
|
# to generate Bills of materials, etc.
|
||||||
|
#
|
||||||
|
# No string formatting is used on purpose as the only string formatting that
|
||||||
|
# is current compatible with python 2.4+ to 3.0+ is the '%' method, and that
|
||||||
|
# is due to be deprecated in 3.0+ soon
|
||||||
|
#
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import xml.sax as sax
|
||||||
|
|
||||||
|
|
||||||
|
class component():
|
||||||
|
"""Class for a set of component information"""
|
||||||
|
def __init__(self, element):
|
||||||
|
self.element = element
|
||||||
|
self.libpart = None
|
||||||
|
|
||||||
|
# Set to true when this component is included in a component group
|
||||||
|
self.grouped = False
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""Equlivalency operator, remember this can be easily overloaded"""
|
||||||
|
result = False
|
||||||
|
if self.getValue() == other.getValue():
|
||||||
|
if self.getLib() == other.getLib():
|
||||||
|
if self.getPart() == other.getPart():
|
||||||
|
result = True
|
||||||
|
return result
|
||||||
|
|
||||||
|
def setPart(self, part):
|
||||||
|
self.libpart = part
|
||||||
|
|
||||||
|
def setValue(self, value):
|
||||||
|
"""Set the value of this component"""
|
||||||
|
v = self.element.getChild("value")
|
||||||
|
if v:
|
||||||
|
v.setChars(value)
|
||||||
|
|
||||||
|
def getValue(self):
|
||||||
|
return self.element.get("value")
|
||||||
|
|
||||||
|
def getRef(self):
|
||||||
|
return self.element.get("comp", "ref")
|
||||||
|
|
||||||
|
def getFootprint(self):
|
||||||
|
return self.element.get("footprint")
|
||||||
|
|
||||||
|
def getDatasheet(self):
|
||||||
|
return self.element.get("datasheet")
|
||||||
|
|
||||||
|
def getLib(self):
|
||||||
|
return self.element.get("libsource", "lib")
|
||||||
|
|
||||||
|
def getPart(self):
|
||||||
|
return self.element.get("libsource", "part")
|
||||||
|
|
||||||
|
def getTimestamp(self):
|
||||||
|
return self.element.get("tstamp")
|
||||||
|
|
||||||
|
def getDescription(self):
|
||||||
|
# When attempting to access the part, we must take care in case the part
|
||||||
|
# cannot be found in the netlist
|
||||||
|
try:
|
||||||
|
d = self.libpart.getDescription()
|
||||||
|
except AttributeError:
|
||||||
|
d = ""
|
||||||
|
return d
|
||||||
|
|
||||||
|
def getDatasheet(self):
|
||||||
|
# When attempting to access the part, we must take care in case the part
|
||||||
|
# cannot be found in the netlist
|
||||||
|
try:
|
||||||
|
d = self.libpart.getDatasheet()
|
||||||
|
except AttributeError:
|
||||||
|
d = ""
|
||||||
|
return d
|
||||||
|
|
||||||
|
def getField(self, name):
|
||||||
|
"""Return the value of a field named name. The component is first
|
||||||
|
checked for the field, and then the components library part is checked
|
||||||
|
for the field. If the field doesn't exist in either, an empty string is
|
||||||
|
returned
|
||||||
|
|
||||||
|
Keywords:
|
||||||
|
name -- The name of the field to return the value for
|
||||||
|
|
||||||
|
"""
|
||||||
|
field = self.element.get("field", "name", name)
|
||||||
|
if field == "":
|
||||||
|
try:
|
||||||
|
field = self.libpart.getField(name)
|
||||||
|
except AttributeError:
|
||||||
|
field = ""
|
||||||
|
return field
|
||||||
|
|
||||||
|
|
||||||
|
class netlistElement():
|
||||||
|
"""Generic netlist element. All elements for a netlist tree which can be
|
||||||
|
used to easily generate various output formats by propogating format
|
||||||
|
requests to all children
|
||||||
|
"""
|
||||||
|
def __init__(self, name, parent=None):
|
||||||
|
self.name = name
|
||||||
|
self.attributes = {}
|
||||||
|
self.parent = parent
|
||||||
|
self.chars = ""
|
||||||
|
self.children = []
|
||||||
|
self.indent = ""
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""String representation of this netlist element
|
||||||
|
|
||||||
|
"""
|
||||||
|
return (self.name + "[" + self.chars + "]" + " attr:" +
|
||||||
|
str(len(self.attributes[a])))
|
||||||
|
|
||||||
|
def formatXML(self, amChild=False):
|
||||||
|
"""Return this element formatted as XML
|
||||||
|
|
||||||
|
Keywords:
|
||||||
|
amChild -- If set to True, the start of document is not returned
|
||||||
|
|
||||||
|
"""
|
||||||
|
s = ""
|
||||||
|
|
||||||
|
if not amChild:
|
||||||
|
s = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||||
|
|
||||||
|
s += self.indent + "<" + self.name
|
||||||
|
for a in self.attributes:
|
||||||
|
s += " " + a + "=\"" + self.attributes[a] + "\""
|
||||||
|
|
||||||
|
if (len(self.chars) == 0) and (len(self.children) == 0):
|
||||||
|
s += "/>"
|
||||||
|
else:
|
||||||
|
s += ">" + self.chars
|
||||||
|
|
||||||
|
for c in self.children:
|
||||||
|
c.indent += self.indent + " "
|
||||||
|
s += "\n"
|
||||||
|
s += c.formatXML(True)
|
||||||
|
|
||||||
|
if (len(self.children) > 0):
|
||||||
|
s += "\n" + self.indent
|
||||||
|
|
||||||
|
if (len(self.children) > 0) or (len(self.chars) > 0):
|
||||||
|
s += "</" + self.name + ">"
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
def formatHTML(self, amChild=False):
|
||||||
|
"""Return this element formatted as HTML
|
||||||
|
|
||||||
|
Keywords:
|
||||||
|
amChild -- If set to True, the start of document is not returned
|
||||||
|
|
||||||
|
"""
|
||||||
|
s = ""
|
||||||
|
|
||||||
|
if not amChild:
|
||||||
|
s = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<title></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
"""
|
||||||
|
|
||||||
|
s += "<tr><td><b>" + self.name + "</b><br>" + self.chars + "</td><td><ul>"
|
||||||
|
for a in self.attributes:
|
||||||
|
s += "<li>" + a + " = " + self.attributes[a] + "</li>"
|
||||||
|
|
||||||
|
s += "</ul></td></tr>\n"
|
||||||
|
|
||||||
|
for c in self.children:
|
||||||
|
s += c.formatHTML(True)
|
||||||
|
|
||||||
|
if not amChild:
|
||||||
|
s += """</table>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
def addAttribute(self, attr, value):
|
||||||
|
"""Add an attribute to this element"""
|
||||||
|
self.attributes[attr] = value
|
||||||
|
|
||||||
|
def setChars(self, chars):
|
||||||
|
"""Set the characters for this element"""
|
||||||
|
self.chars = chars
|
||||||
|
|
||||||
|
def addChars(self, chars):
|
||||||
|
"""Add characters (textual value) to this element"""
|
||||||
|
self.chars += chars
|
||||||
|
|
||||||
|
def addChild(self, child):
|
||||||
|
"""Add a child element to this element"""
|
||||||
|
self.children.append(child)
|
||||||
|
return self.children[len(self.children) - 1]
|
||||||
|
|
||||||
|
def getParent(self):
|
||||||
|
"""Get the parent of this element (Could be None)"""
|
||||||
|
return self.parent
|
||||||
|
|
||||||
|
def setAttribute(self, attr, value):
|
||||||
|
"""Set an attributes value - in fact does the same thing as add
|
||||||
|
attribute
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.attributes[attr] = value
|
||||||
|
|
||||||
|
def getChild(self, name):
|
||||||
|
"""Returns a child element of name
|
||||||
|
|
||||||
|
Keywords:
|
||||||
|
name -- The name of the child element to return"""
|
||||||
|
for child in self.children:
|
||||||
|
if child.name == name:
|
||||||
|
return child
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get(self, element, attribute="", attrmatch=""):
|
||||||
|
"""Return the data for either an attribute, or else an element"""
|
||||||
|
if (self.name == element):
|
||||||
|
if attribute != "":
|
||||||
|
if attrmatch != "":
|
||||||
|
if self.attributes[attribute] == attrmatch:
|
||||||
|
return self.chars
|
||||||
|
else:
|
||||||
|
return self.attributes[attribute]
|
||||||
|
else:
|
||||||
|
return self.chars
|
||||||
|
|
||||||
|
for child in self.children:
|
||||||
|
if child.get(element, attribute, attrmatch) != "":
|
||||||
|
return child.get(element, attribute, attrmatch)
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
class netlist():
|
||||||
|
""" Kicad generic netlist class. Generally loaded from a kicad generic
|
||||||
|
netlist file. Includes several helper functions to ease BOM creating
|
||||||
|
scripts
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, fname=""):
|
||||||
|
"""Initialiser for the genericNetlist class
|
||||||
|
|
||||||
|
Keywords:
|
||||||
|
fname -- The name of the generic netlist file to open (Optional)
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.design = None
|
||||||
|
self.components = []
|
||||||
|
self.libparts = []
|
||||||
|
self.libraries = []
|
||||||
|
self.nets = []
|
||||||
|
|
||||||
|
# The entire tree is loaded into self.tree
|
||||||
|
self.tree = []
|
||||||
|
|
||||||
|
self._curr_element = None
|
||||||
|
|
||||||
|
if fname != "":
|
||||||
|
self.load(fname)
|
||||||
|
|
||||||
|
def addChars(self, content):
|
||||||
|
"""Add characters to the current element"""
|
||||||
|
self._curr_element.addChars(content)
|
||||||
|
|
||||||
|
def addElement(self, name):
|
||||||
|
"""Add a new kicad generic element to the list"""
|
||||||
|
if self._curr_element == None:
|
||||||
|
self.tree = netlistElement(name)
|
||||||
|
self._curr_element = self.tree
|
||||||
|
else:
|
||||||
|
self._curr_element = self._curr_element.addChild(
|
||||||
|
netlistElement(name, self._curr_element))
|
||||||
|
|
||||||
|
# If this element is a component, add it to the components list
|
||||||
|
if self._curr_element.name == "comp":
|
||||||
|
self.components.append(component(self._curr_element))
|
||||||
|
|
||||||
|
# Assign the design element
|
||||||
|
if self._curr_element.name == "design":
|
||||||
|
self.design = self._curr_element
|
||||||
|
|
||||||
|
# If this element is a library part, add it to the parts list
|
||||||
|
if self._curr_element.name == "libpart":
|
||||||
|
self.libparts.append(part(self._curr_element))
|
||||||
|
|
||||||
|
# If this element is a net, add it to the nets list
|
||||||
|
if self._curr_element.name = "net"
|
||||||
|
self.nets.append(self._curr_element)
|
||||||
|
|
||||||
|
# If this element is a library, add it to the libraries list
|
||||||
|
if self._curr_element.name = "library"
|
||||||
|
self.libraries.append(self._curr_element)
|
||||||
|
|
||||||
|
return self._curr_element
|
||||||
|
|
||||||
|
def endDocument(self):
|
||||||
|
"""Called when the netlist document has been fully parsed"""
|
||||||
|
# When the document is complete, the library parts must be linked to
|
||||||
|
# the components as they are seperate in the tree so as not to
|
||||||
|
# duplicate library part information for every component
|
||||||
|
for c in self.components:
|
||||||
|
for p in self.libparts:
|
||||||
|
if p.getPart() == c.getPart() and p.getLib() == c.getLib():
|
||||||
|
c.setPart(p)
|
||||||
|
|
||||||
|
def endElement(self):
|
||||||
|
"""End the current element and switch to its parent"""
|
||||||
|
self._curr_element = self._curr_element.getParent()
|
||||||
|
|
||||||
|
def getDate(self):
|
||||||
|
"""Return the date + time string generated by the tree creation tool"""
|
||||||
|
return self.design.get("date")
|
||||||
|
|
||||||
|
def getSource(self):
|
||||||
|
"""Return the source string for the design"""
|
||||||
|
return self.design.get("source")
|
||||||
|
|
||||||
|
def getTool(self):
|
||||||
|
"""Return the tool string which was used to create the netlist tree"""
|
||||||
|
return self.design.get("tool")
|
||||||
|
|
||||||
|
def groupComponents(self):
|
||||||
|
"""Return a list of component lists. Components are grouped together
|
||||||
|
when the value, library and part identifiers match
|
||||||
|
|
||||||
|
"""
|
||||||
|
groups = []
|
||||||
|
|
||||||
|
# Make sure to start off will all components ungrouped to begin with
|
||||||
|
for c in self.components:
|
||||||
|
c.grouped = False
|
||||||
|
|
||||||
|
# Group components based on the value, library and part identifiers
|
||||||
|
for c in self.components:
|
||||||
|
if c.grouped == False:
|
||||||
|
c.grouped = True
|
||||||
|
newgroup = []
|
||||||
|
newgroup.append(c)
|
||||||
|
|
||||||
|
# Check every other ungrouped component against this component
|
||||||
|
# and add to the group as necessary
|
||||||
|
for ci in self.components:
|
||||||
|
if ci.grouped == False and ci == c:
|
||||||
|
newgroup.append(ci)
|
||||||
|
ci.grouped = True
|
||||||
|
|
||||||
|
# Add the new component group to the groups list
|
||||||
|
groups.append(newgroup)
|
||||||
|
|
||||||
|
# Each group is a list of components, we need to sort each list first
|
||||||
|
# to get them in order as this makes for easier to read BOM's
|
||||||
|
for g in groups:
|
||||||
|
g = sorted(g, key=lambda g: g.getRef())
|
||||||
|
|
||||||
|
# Finally, sort the groups to order the references alphabetically
|
||||||
|
groups = sorted(groups, key=lambda group: group[0].getRef())
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def formatXML(self):
|
||||||
|
"""Return the whole netlist formatted in XML"""
|
||||||
|
return self.tree.formatXML()
|
||||||
|
|
||||||
|
def formatHTML(self):
|
||||||
|
"""Return the whole netlist formatted in HTML"""
|
||||||
|
return self.tree.formatHTML()
|
||||||
|
|
||||||
|
def load(self, fname):
|
||||||
|
"""Load a kicad generic netlist
|
||||||
|
|
||||||
|
Keywords:
|
||||||
|
fname -- The name of the generic netlist file to open
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._reader = sax.make_parser()
|
||||||
|
self._reader.setContentHandler(_gNetReader(self))
|
||||||
|
self._reader.parse(fname)
|
||||||
|
except IOError as e:
|
||||||
|
print >> sys.stderr, __file__, ":", e
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
|
class part():
|
||||||
|
"""Class for a library part"""
|
||||||
|
def __init__(self, part):
|
||||||
|
# The part is a reference to a libpart generic netlist element
|
||||||
|
self.element = part
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# simply print the generic netlist element associated with this part
|
||||||
|
return str(self.element)
|
||||||
|
|
||||||
|
def getDatasheet(self):
|
||||||
|
return self.element.get("docs")
|
||||||
|
|
||||||
|
def getLib(self):
|
||||||
|
return self.element.get("libpart", "lib")
|
||||||
|
|
||||||
|
def getPart(self):
|
||||||
|
return self.element.get("libpart", "part")
|
||||||
|
|
||||||
|
def getDescription(self):
|
||||||
|
return self.element.get("description")
|
||||||
|
|
||||||
|
def getField(self, name):
|
||||||
|
return self.element.get("field", "name", name)
|
||||||
|
|
||||||
|
|
||||||
|
class _gNetReader(sax.handler.ContentHandler):
|
||||||
|
"""SAX kicad generic netlist content handler - passes most of the work back
|
||||||
|
to the gNetlist class which builds a complete tree in RAM for the design
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, aParent):
|
||||||
|
self.parent = aParent
|
||||||
|
|
||||||
|
def startElement(self, name, attrs):
|
||||||
|
"""Start of a new XML element event"""
|
||||||
|
element = self.parent.addElement(name)
|
||||||
|
|
||||||
|
for name in attrs.getNames():
|
||||||
|
element.addAttribute(name, attrs.getValue(name))
|
||||||
|
|
||||||
|
def endElement(self, name):
|
||||||
|
self.parent.endElement()
|
||||||
|
|
||||||
|
def characters(self, content):
|
||||||
|
# Ignore erroneous white space - ignoreableWhitespace does not get rid
|
||||||
|
# of the need for this!
|
||||||
|
if not content.isspace():
|
||||||
|
self.parent.addChars(content)
|
||||||
|
|
||||||
|
def endDocument(self):
|
||||||
|
"""End of the XML document event"""
|
||||||
|
self.parent.endDocument()
|
|
@ -0,0 +1,23 @@
|
||||||
|
#
|
||||||
|
# Example python script to generate an equivalent XML document from XML input
|
||||||
|
#
|
||||||
|
# Example: Round robin, XML to XML conversion
|
||||||
|
#
|
||||||
|
|
||||||
|
# Import the KiCad python helper module and the csv formatter
|
||||||
|
import ky
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Generate an instance of a generic netlist, and load the netlist tree from
|
||||||
|
# the command line option. If the file doesn't exist, execution will stop
|
||||||
|
net = ky.netlist(sys.argv[1])
|
||||||
|
|
||||||
|
# Open a file to write to, if the file cannot be opened output to stdout
|
||||||
|
# instead
|
||||||
|
try:
|
||||||
|
f = open(sys.argv[2], 'w')
|
||||||
|
except IOError:
|
||||||
|
print >> sys.stderr, __file__, ":", e
|
||||||
|
f = stdout
|
||||||
|
|
||||||
|
print >> f, net.formatXML()
|
|
@ -0,0 +1,71 @@
|
||||||
|
#
|
||||||
|
# Example python script to generate an equivalent XML document from XML input
|
||||||
|
#
|
||||||
|
# Example: Round value robin, XML to XML conversion with partial value monging
|
||||||
|
#
|
||||||
|
|
||||||
|
# Import the KiCad python helper module and the csv formatter
|
||||||
|
import ky
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def checkvalue(self):
|
||||||
|
"""Check values, and replace with preferred/consistent values"""
|
||||||
|
ref = self.getRef()
|
||||||
|
r = ref.split("R")
|
||||||
|
c = ref.split("C")
|
||||||
|
v = self.getValue()
|
||||||
|
|
||||||
|
# Common to all values - convert decimation if necessary
|
||||||
|
dec = v.split(",")
|
||||||
|
if (len(dec) == 2):
|
||||||
|
newval = dec[0] + "." + dec[1]
|
||||||
|
self.setValue(newval)
|
||||||
|
v = self.getValue()
|
||||||
|
|
||||||
|
if len(r) == 2 and r[1].isdigit():
|
||||||
|
# This is a resistor - make values consistent
|
||||||
|
# If the value is a pure value, add R to the end of the value
|
||||||
|
if v.isdigit():
|
||||||
|
i = int(v)
|
||||||
|
if (i > 1000000):
|
||||||
|
i = i / 100000
|
||||||
|
v = str(i) + "M"
|
||||||
|
if (i > 1000):
|
||||||
|
i = i / 1000
|
||||||
|
v = str(i) + "K"
|
||||||
|
else:
|
||||||
|
v = str(i) + "R"
|
||||||
|
|
||||||
|
self.setValue(v)
|
||||||
|
else:
|
||||||
|
# Get the multiplier character
|
||||||
|
multiplier = v[len(v) - 1]
|
||||||
|
v = v.strip(multiplier)
|
||||||
|
v = v.split(".")
|
||||||
|
if (len(v) == 2):
|
||||||
|
newval = v[0] + multiplier + v[1]
|
||||||
|
self.setValue(newval)
|
||||||
|
v = self.getValue()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Give components a new method for checking the values (this could easily be a
|
||||||
|
# Company Part Number generator method instead)
|
||||||
|
ky.component.checkvalue = checkvalue
|
||||||
|
|
||||||
|
# Generate an instance of a generic netlist, and load the netlist tree from
|
||||||
|
# the command line option. If the file doesn't exist, execution will stop
|
||||||
|
net = ky.netlist(sys.argv[1])
|
||||||
|
|
||||||
|
# Open a file to write to, if the file cannot be opened output to stdout
|
||||||
|
# instead
|
||||||
|
try:
|
||||||
|
f = open(sys.argv[2], 'w')
|
||||||
|
except IOError:
|
||||||
|
print >> sys.stderr, __file__, ":", e
|
||||||
|
f = stdout
|
||||||
|
|
||||||
|
for c in net.components:
|
||||||
|
c.checkvalue()
|
||||||
|
|
||||||
|
print >> f, net.formatXML()
|
Loading…
Reference in New Issue