├── README.md
├── .gitattributes
├── .gitignore
├── bom_single_line_r31.py
├── bom_grouped_v33.py
├── bom_single_line_r31_ky.py
└── bom_grouped_v33_ky.py
/README.md:
--------------------------------------------------------------------------------
1 | @package
2 | Generate a comma delimited list (csv file type).
3 |
4 | Components are sorted by ref and grouped by:
5 | Value Footprint Voltage Tolerance Temp Current
6 | Fields are (if exist)
7 | 'Ref', 'Qnty', 'Value', 'Footprint',
8 | 'Manf#', 'Voltage', 'Tolerance', 'Power', 'Temp', 'Current', 'Description'
9 | version 3.3 - 06.2016 [bom_grouped_v33_ky.py]
10 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | # =========================
21 | # Operating System Files
22 | # =========================
23 |
24 | # OSX
25 | # =========================
26 |
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear in the root of a volume
35 | .DocumentRevisions-V100
36 | .fseventsd
37 | .Spotlight-V100
38 | .TemporaryItems
39 | .Trashes
40 | .VolumeIcon.icns
41 |
42 | # Directories potentially created on remote AFP share
43 | .AppleDB
44 | .AppleDesktop
45 | Network Trash Folder
46 | Temporary Items
47 | .apdisk
48 |
--------------------------------------------------------------------------------
/bom_single_line_r31.py:
--------------------------------------------------------------------------------
1 | #
2 | # Example python script to generate a BOM from a KiCad generic netlist
3 | #
4 | # Example: Sorted and Grouped CSV BOM
5 | # grouped Part Value, Footprint, Voltage, Toll
6 | #
7 |
8 | # Import the KiCad python helper module and the csv formatter
9 |
10 |
11 | """
12 | @package
13 | Generate a comma delimited list (csv file type).
14 | Components are sorted by ref in signle line
15 | Fields are (if exist)
16 | 'Ref', 'Qnty', 'Value', 'Footprint',
17 | 'Description', 'Voltage', 'Tolerance', 'Power', 'Temp', 'Current'
18 | version 31 - 05.2015 [bom_single_line_r31_ky.py]
19 | """
20 |
21 | #maui
22 | import logging
23 | import textwrap
24 | #maui
25 | import bom_single_line_r31_ky
26 | import shutil
27 | import os
28 | import ntpath
29 |
30 | import csv
31 | import sys
32 |
33 | # logging.basicConfig(filename='example.log',level=logging.DEBUG)
34 |
35 | # Generate an instance of a generic netlist, and load the netlist tree from
36 | # the command line option. If the file doesn't exist, execution will stop
37 | #maui
38 | net = bom_single_line_r31_ky.netlist(sys.argv[1])
39 |
40 | # Open a file to write to, if the file cannot be opened output to stdout
41 | # instead
42 | try:
43 | f = open(sys.argv[2], 'wb')
44 | except IOError:
45 | print >> sys.stderr, __file__, ":", e
46 | f = stdout
47 |
48 | # Create a new csv writer object to use as the output formatter
49 | #out = csv.writer(f, delimiter=',', quotechar='\"', quoting=csv.QUOTE_ALL)
50 | #maui
51 | out = csv.writer(f, delimiter=',', quotechar='\"', escapechar='', quoting=csv.QUOTE_NONE)
52 |
53 | # Output a set of rows for a header providing general information
54 | #maui
55 | out.writerow(['In:', sys.argv[1]])
56 |
57 | out.writerow(['Source:', net.getSource()])
58 | out.writerow(['Date:', net.getDate()])
59 | out.writerow(['Tool:', net.getTool()])
60 | out.writerow(['Component Count:', len(net.components)])
61 | out.writerow(['Ref', 'Qnty', 'Value', 'Footprint', 'Description', 'Voltage', 'Tolerance', 'Power', 'Temp', 'Current'])
62 |
63 |
64 | #logging.debug('This message should go to the log file')
65 | #logging.info('So should this')
66 | #logging.warning('And this, too')
67 |
68 | # Get all of the components in groups of matching parts + values (see ky.py)
69 | grouped = net.groupComponents()
70 |
71 | # Output all of the component information
72 | for group in grouped:
73 | refs = ""
74 |
75 | # Add the reference of every component in the group and keep a reference
76 | # to the component so that the other data can be filled in once per group
77 | for component in group:
78 | #refs += component.getRef() + ", "
79 | refs += component.getRef()
80 | c = component
81 |
82 | #maui
83 | #logging.warning(c.getFootprint())
84 |
85 | # Fill in the component groups common data
86 | # out.writerow([refs, len(group), c.getValue(), c.getLib() + "/" + c.getPart(), c.getDatasheet(),
87 | # c.getDescription(), c.getField("Vendor")])
88 | #out.writerow([refs, len(group), c.getValue(), c.getLib() + "/" + c.getFootprint(),
89 | # out.writerow([refs, c.getValue(), c.getLib() + "/" + c.getFootprint(),
90 | # c.getDescription(), c.getField("Voltage"), c.getField("Toll")])
91 |
92 | # out.writerow([refs, c.getValue(), c.getFootprint(),
93 | # c.getDescription(), c.getField("Voltage"), c.getField("Tolerance"), c.getField("Temp")])
94 |
95 | out.writerow([refs, len(group), c.getValue(), c.getFootprint(),
96 | c.getDescription(), c.getField('Voltage'), c.getField('Tolerance'), c.getField('Power'), c.getField('Temp'), c.getField('Current')])
97 |
98 | #maui
99 | #os.path.splitext(sys.argv[2])[0]
100 | f.close()
101 | #csv_file=ntpath.basename(sys.argv[2]).split('.')[0]+'.csv'
102 | csv_file=os.path.splitext(sys.argv[2])[0]+'.csv'
103 |
104 | shutil.copy (sys.argv[2], csv_file)
105 | #print sys.argv[2], "=>", ntpath.basename(sys.argv[2]).split('.')[0]+".csv
106 |
107 | res = []
108 | max_len_element = [0,0,0,0,0,0,0,0,0,0,0]
109 |
110 | with open(csv_file) as csvfile:
111 | file1 = csv.reader(csvfile, delimiter=',')
112 | line=''
113 | p=0
114 | for row in file1:
115 | k=0
116 | for element in row:
117 | # print (len(element))
118 | len_element=len(element)
119 | k=k+1
120 | # print k
121 | if p>4:
122 | if len_element <50:
123 | if len_element > max_len_element[k]:
124 | max_len_element[k]=len_element
125 | else:
126 | max_len_element[k]=50
127 | p=p+1
128 | p=0
129 |
130 | with open(csv_file) as csvfile:
131 | file2 = csv.reader(csvfile, delimiter=',')
132 | line=''
133 | p=0
134 | for row in file2:
135 | k=0
136 | for element in row:
137 | # print (len(element))
138 | len_element=len(element)
139 | k=k+1
140 | # print k
141 | #dif = 30- len_element
142 | dif=0
143 | if p>4:
144 | if len_element >= 50:
145 | wrapped = textwrap.wrap(element, 50)
146 | # for index, item in enumerate(wrapped):
147 | # print index, item
148 | # print item[0]
149 | for line1 in wrapped:
150 | #print line1
151 | line +=line1+'\r\n'
152 | line=line.rstrip('\n');
153 | line=line.rstrip('\r');
154 | index = line.find(line1)
155 | line_space=''
156 | for index2 in range(k-1):
157 | line_space+=(max_len_element[k-1]+3)*' '
158 | line_space=line_space[:len(line_space)-3]
159 | line = line[:index] + line_space + line[index:]
160 | dif = 50-len(line1)+3
161 | line+=','.ljust(dif,' ')
162 | #dif = max_len_element[k]-50+3
163 | #line += element+ ','.ljust(dif,' ')+'\r\n'
164 | else:
165 | dif = max_len_element[k]-len_element+3
166 | line += element+ ','.ljust(dif,' ')
167 | else:
168 | line += element
169 | line += '\r\n'
170 | p=p+1
171 | res.append(line)
172 |
173 | txt_file=os.path.splitext(sys.argv[2])[0]+'.txt'
174 | # now, outside the loop, we can do this:
175 | with open(txt_file, 'wb') as f:
176 | f.writelines(res)
177 |
178 |
--------------------------------------------------------------------------------
/bom_grouped_v33.py:
--------------------------------------------------------------------------------
1 | #
2 | # Example python script to generate a BOM from a KiCad generic netlist
3 | #
4 | # Example: Sorted and Grouped CSV BOM
5 | # grouped Part Value, Footprint, Voltage, Toll
6 | #
7 |
8 | # Import the KiCad python helper module and the csv formatter
9 |
10 | """
11 | @package
12 | Generate a comma delimited list (csv file type).
13 | Components are sorted by ref and grouped by:
14 | Value Footprint Voltage Tolerance Temp Current
15 | Fields are (if exist)
16 | 'Ref', 'Qnty', 'Value', 'Footprint',
17 | 'Manf#', 'Voltage', 'Tolerance', 'Power', 'Temp', 'Current', 'Description'
18 | version 3.3 - 06.2016 [bom_grouped_v33_ky.py]
19 | """
20 |
21 | #maui
22 | import logging
23 | import textwrap
24 | #maui
25 | import bom_grouped_v33_ky
26 | import shutil
27 | import os
28 | import ntpath
29 |
30 | import csv
31 | import sys
32 |
33 |
34 | # logging.basicConfig(filename='example.log',level=logging.DEBUG)
35 |
36 | # Generate an instance of a generic netlist, and load the netlist tree from
37 | # the command line option. If the file doesn't exist, execution will stop
38 | #maui
39 | net = bom_grouped_v33_ky.netlist(sys.argv[1])
40 |
41 | # Open a file to write to, if the file cannot be opened output to stdout
42 | # instead
43 | try:
44 | f = open(sys.argv[2], 'wb')
45 | except IOError:
46 | print >> sys.stderr, __file__, ":", e
47 | f = stdout
48 |
49 | # Create a new csv writer object to use as the output formatter
50 | #out = csv.writer(f, delimiter=',', quotechar='\"', quoting=csv.QUOTE_ALL)
51 | #maui
52 | out = csv.writer(f, delimiter=',', quotechar='\"')
53 | #out = csv.writer(f, delimiter=',', quotechar='\"', escapechar='', quoting=csv.QUOTE_ALL)
54 | #out = csv.writer(f, delimiter='\t', quotechar='\"', escapechar='', quoting=csv.QUOTE_NONE)
55 |
56 | # Output a set of rows for a header providing general information
57 | #maui
58 | out.writerow(['In:', sys.argv[1]])
59 |
60 | out.writerow(['Source:', net.getSource()])
61 | out.writerow(['Date:', net.getDate()])
62 | out.writerow(['Tool:', net.getTool()])
63 | out.writerow(['Component Count:', len(net.components)])
64 | out.writerow(['Ref', 'Qnty', 'Value', 'Footprint', 'Manf#', 'Voltage', 'Tolerance', 'Power', 'Temp', 'Current', 'Description'])
65 |
66 | #logging.debug('This message should go to the log file')
67 | #logging.info('So should this')
68 | #logging.warning('And this, too')
69 |
70 | # Get all of the components in groups of matching parts + values (see ky.py)
71 | grouped = net.groupComponents()
72 |
73 | # Output all of the component information
74 | for group in grouped:
75 | refs = ""
76 |
77 | # Add the reference of every component in the group and keep a reference
78 | # to the component so that the other data can be filled in once per group
79 | for component in group:
80 | #refs += component.getRef() + ", "
81 | if len(group)==1:
82 | refs += component.getRef()
83 | else :
84 | refs += component.getRef()+ " "
85 | c = component
86 | #maui
87 | refs=refs.rstrip(' ');
88 | #logging.warning(c.getFootprint())
89 |
90 | # Fill in the component groups common data
91 | # out.writerow([refs, len(group), c.getValue(), c.getLib() + "/" + c.getPart(), c.getDatasheet(),
92 | # c.getDescription(), c.getField("Vendor")])
93 | #out.writerow([refs, len(group), c.getValue(), c.getLib() + "/" + c.getFootprint(),
94 | # c.getDescription(), c.getField("Voltage"), c.getField("Toll")])
95 |
96 | out.writerow([refs, len(group), c.getValue(), c.getFootprint(),
97 | c.getField('Manf#'), c.getField('Voltage'), c.getField('Tolerance'), c.getField('Power'), c.getField('Temp'), c.getField('Current'), c.getDescription()])
98 |
99 | # out.writerow([("{0:30s}".format(c.getField("Value")), "{0:30s}".format(c.getField("Footprint")) )] )
100 |
101 | #maui
102 | #os.path.splitext(sys.argv[2])[0]
103 | f.close()
104 | #csv_file=ntpath.basename(sys.argv[2]).split('.')[0]+'.csv'
105 | csv_file=os.path.splitext(sys.argv[2])[0]+'.csv'
106 |
107 | shutil.copy (sys.argv[2], csv_file)
108 | #print sys.argv[2], "=>", ntpath.basename(sys.argv[2]).split('.')[0]+".csv
109 |
110 | res = []
111 | max_len_element = [0,0,0,0,0,0,0,0,0,0,0,0]
112 |
113 | with open(csv_file) as csvfile:
114 | file1 = csv.reader(csvfile, delimiter=',')
115 | line=''
116 | p=0
117 | for row in file1:
118 | k=0
119 | for element in row:
120 | # print (len(element))
121 | len_element=len(element)
122 | k=k+1
123 | # print k
124 | if p>4:
125 | if len_element <50:
126 | if len_element > max_len_element[k]:
127 | max_len_element[k]=len_element
128 | else:
129 | max_len_element[k]=50
130 | p=p+1
131 | p=0
132 |
133 | with open(csv_file) as csvfile:
134 | file2 = csv.reader(csvfile, delimiter=',')
135 | line=''
136 | p=0
137 | for row in file2:
138 | k=0
139 | for element in row:
140 | # print (len(element))
141 | len_element=len(element)
142 | k=k+1
143 | # print k
144 | #dif = 30- len_element
145 | dif=0
146 | if p>4:
147 | if len_element >= 50:
148 | wrapped = textwrap.wrap(element, 50)
149 | # for index, item in enumerate(wrapped):
150 | # print index, item
151 | # print item[0]
152 | for line1 in wrapped:
153 | #print line1
154 | line +=line1+'\r\n'
155 | line=line.rstrip('\n');
156 | line=line.rstrip('\r');
157 | index = line.find(line1)
158 | line_space=''
159 | for index2 in range(k-1):
160 | line_space+=(max_len_element[k-1]+3)*' '
161 | line_space=line_space[:len(line_space)-3]
162 | line = line[:index] + line_space + line[index:]
163 | dif = 50-len(line1)+3
164 | line+=','.ljust(dif,' ')
165 | #dif = max_len_element[k]-50+3
166 | #line += element+ ','.ljust(dif,' ')+'\r\n'
167 | else:
168 | dif = max_len_element[k]-len_element+3
169 | line += element+ ','.ljust(dif,' ')
170 | else:
171 | line += element
172 | line += '\r\n'
173 | p=p+1
174 | res.append(line)
175 |
176 | txt_file=os.path.splitext(sys.argv[2])[0]+'.txt'
177 | # now, outside the loop, we can do this:
178 | with open(txt_file, 'wb') as f:
179 | f.writelines(res)
180 |
181 |
--------------------------------------------------------------------------------
/bom_single_line_r31_ky.py:
--------------------------------------------------------------------------------
1 | #
2 | # KiCad python module for interpreting generic netlists which can be used
3 | # to generate Bills of materials, etc.
4 | #
5 | # No string formatting is used on purpose as the only string formatting that
6 | # is current compatible with python 2.4+ to 3.0+ is the '%' method, and that
7 | # is due to be deprecated in 3.0+ soon
8 | #
9 |
10 | """
11 | @package
12 | children of 'bom_single_line_r31.py'
13 | version 31 - 05.2015 [bom_single_line_r31_ky.py]
14 | """
15 |
16 | import sys
17 | import xml.sax as sax
18 | #maui
19 | import logging
20 |
21 | class component():
22 | """Class for a set of component information"""
23 | def __init__(self, element):
24 | self.element = element
25 | self.libpart = None
26 |
27 | # Set to true when this component is included in a component group
28 | self.grouped = False
29 |
30 | def __eq__(self, other):
31 | """Equlivalency operator, remember this can be easily overloaded"""
32 | result = False
33 | #if self.getValue() == other.getValue():
34 | # if self.getLib() == other.getLib():
35 | # if self.getPart() == other.getPart():
36 | # if self.getFootprint() == other.getFootprint():
37 | # #logging.warning('%s 1', self.getFootprint());
38 | # #logging.warning('%s 2', other.getFootprint());
39 | # #logging.warning('%s 2', self.getField('Voltage'));
40 | # if self.getField('Voltage') == other.getField('Voltage'): #maui test
41 | # if self.getField('Toll') == other.getField('Toll'): #maui test
42 | # result = False
43 | return result
44 |
45 | def setPart(self, part):
46 | self.libpart = part
47 |
48 | def setValue(self, value):
49 | """Set the value of this component"""
50 | v = self.element.getChild("value")
51 | if v:
52 | v.setChars(value)
53 |
54 | def getValue(self):
55 | return self.element.get("value")
56 |
57 | def getRef(self):
58 | return self.element.get("comp", "ref")
59 |
60 | def getFootprint(self):
61 | return self.element.get("footprint")
62 |
63 | def getDatasheet(self):
64 | return self.element.get("datasheet")
65 |
66 | def getLib(self):
67 | return self.element.get("libsource", "lib")
68 |
69 | def getPart(self):
70 | return self.element.get("libsource", "part")
71 |
72 | def getTimestamp(self):
73 | return self.element.get("tstamp")
74 |
75 | def getDescription(self):
76 | # When attempting to access the part, we must take care in case the part
77 | # cannot be found in the netlist
78 | try:
79 | d = self.libpart.getDescription()
80 | except AttributeError:
81 | d = ""
82 | return d
83 |
84 | def getDatasheet(self):
85 | # When attempting to access the part, we must take care in case the part
86 | # cannot be found in the netlist
87 | try:
88 | d = self.libpart.getDatasheet()
89 | except AttributeError:
90 | d = ""
91 | return d
92 |
93 | def getField(self, name):
94 | """Return the value of a field named name. The component is first
95 | checked for the field, and then the components library part is checked
96 | for the field. If the field doesn't exist in either, an empty string is
97 | returned
98 |
99 | Keywords:
100 | name -- The name of the field to return the value for
101 |
102 | """
103 | field = self.element.get("field", "name", name).encode('utf-8')
104 | if field == "":
105 | try:
106 | field = self.libpart.getField(name)
107 | except AttributeError:
108 | field = ""
109 | return field
110 |
111 |
112 | class netlistElement():
113 | """Generic netlist element. All elements for a netlist tree which can be
114 | used to easily generate various output formats by propogating format
115 | requests to all children
116 | """
117 | def __init__(self, name, parent=None):
118 | self.name = name
119 | self.attributes = {}
120 | self.parent = parent
121 | self.chars = ""
122 | self.children = []
123 | self.indent = ""
124 |
125 | def __str__(self):
126 | """String representation of this netlist element
127 |
128 | """
129 | return (self.name + "[" + self.chars + "]" + " attr:" +
130 | str(len(self.attributes[a])))
131 |
132 | def formatXML(self, amChild=False):
133 | """Return this element formatted as XML
134 |
135 | Keywords:
136 | amChild -- If set to True, the start of document is not returned
137 |
138 | """
139 | s = ""
140 |
141 | if not amChild:
142 | s = "\n"
143 |
144 | s += self.indent + "<" + self.name
145 | for a in self.attributes:
146 | s += " " + a + "=\"" + self.attributes[a] + "\""
147 |
148 | if (len(self.chars) == 0) and (len(self.children) == 0):
149 | s += "/>"
150 | else:
151 | s += ">" + self.chars
152 |
153 | for c in self.children:
154 | c.indent += self.indent + " "
155 | s += "\n"
156 | s += c.formatXML(True)
157 |
158 | if (len(self.children) > 0):
159 | s += "\n" + self.indent
160 |
161 | if (len(self.children) > 0) or (len(self.chars) > 0):
162 | s += "" + self.name + ">"
163 |
164 | return s
165 |
166 | def formatHTML(self, amChild=False):
167 | """Return this element formatted as HTML
168 |
169 | Keywords:
170 | amChild -- If set to True, the start of document is not returned
171 |
172 | """
173 | s = ""
174 |
175 | if not amChild:
176 | s = """
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | """
186 |
187 | s += "" + self.name + " " + self.chars + " | "
188 | for a in self.attributes:
189 | s += "- " + a + " = " + self.attributes[a] + "
"
190 |
191 | s += " |
\n"
192 |
193 | for c in self.children:
194 | s += c.formatHTML(True)
195 |
196 | if not amChild:
197 | s += """
198 |
199 | """
200 |
201 | return s
202 |
203 | def addAttribute(self, attr, value):
204 | """Add an attribute to this element"""
205 | self.attributes[attr] = value
206 |
207 | def setChars(self, chars):
208 | """Set the characters for this element"""
209 | self.chars = chars
210 |
211 | def addChars(self, chars):
212 | """Add characters (textual value) to this element"""
213 | self.chars += chars
214 |
215 | def addChild(self, child):
216 | """Add a child element to this element"""
217 | self.children.append(child)
218 | return self.children[len(self.children) - 1]
219 |
220 | def getParent(self):
221 | """Get the parent of this element (Could be None)"""
222 | return self.parent
223 |
224 | def setAttribute(self, attr, value):
225 | """Set an attributes value - in fact does the same thing as add
226 | attribute
227 |
228 | """
229 | self.attributes[attr] = value
230 |
231 | def getChild(self, name):
232 | """Returns a child element of name
233 |
234 | Keywords:
235 | name -- The name of the child element to return"""
236 | for child in self.children:
237 | if child.name == name:
238 | return child
239 |
240 | return None
241 |
242 | def get(self, element, attribute="", attrmatch=""):
243 | """Return the data for either an attribute, or else an element"""
244 | if (self.name == element):
245 | if attribute != "":
246 | if attrmatch != "":
247 | if self.attributes[attribute] == attrmatch:
248 | return self.chars
249 | else:
250 | return self.attributes[attribute]
251 | else:
252 | return self.chars
253 |
254 | for child in self.children:
255 | if child.get(element, attribute, attrmatch) != "":
256 | return child.get(element, attribute, attrmatch)
257 |
258 | return ""
259 |
260 |
261 | class netlist():
262 | """ Kicad generic netlist class. Generally loaded from a kicad generic
263 | netlist file. Includes several helper functions to ease BOM creating
264 | scripts
265 |
266 | """
267 | def __init__(self, fname=""):
268 | """Initialiser for the genericNetlist class
269 |
270 | Keywords:
271 | fname -- The name of the generic netlist file to open (Optional)
272 |
273 | """
274 | self.design = None
275 | self.components = []
276 | self.libparts = []
277 | self.libraries = []
278 | self.nets = []
279 |
280 | # The entire tree is loaded into self.tree
281 | self.tree = []
282 |
283 | self._curr_element = None
284 |
285 | if fname != "":
286 | self.load(fname)
287 |
288 | def addChars(self, content):
289 | """Add characters to the current element"""
290 | self._curr_element.addChars(content)
291 |
292 | def addElement(self, name):
293 | """Add a new kicad generic element to the list"""
294 | if self._curr_element == None:
295 | self.tree = netlistElement(name)
296 | self._curr_element = self.tree
297 | else:
298 | self._curr_element = self._curr_element.addChild(
299 | netlistElement(name, self._curr_element))
300 |
301 | # If this element is a component, add it to the components list
302 | if self._curr_element.name == "comp":
303 | self.components.append(component(self._curr_element))
304 |
305 | # Assign the design element
306 | if self._curr_element.name == "design":
307 | self.design = self._curr_element
308 |
309 | # If this element is a library part, add it to the parts list
310 | if self._curr_element.name == "libpart":
311 | self.libparts.append(part(self._curr_element))
312 |
313 | # If this element is a net, add it to the nets list
314 | if self._curr_element.name == "net":
315 | self.nets.append(self._curr_element)
316 |
317 | # If this element is a library, add it to the libraries list
318 | if self._curr_element.name == "library":
319 | self.libraries.append(self._curr_element)
320 |
321 | return self._curr_element
322 |
323 | def endDocument(self):
324 | """Called when the netlist document has been fully parsed"""
325 | # When the document is complete, the library parts must be linked to
326 | # the components as they are seperate in the tree so as not to
327 | # duplicate library part information for every component
328 | for c in self.components:
329 | for p in self.libparts:
330 | if p.getPart() == c.getPart() and p.getLib() == c.getLib():
331 | c.setPart(p)
332 |
333 | def endElement(self):
334 | """End the current element and switch to its parent"""
335 | self._curr_element = self._curr_element.getParent()
336 |
337 | def getDate(self):
338 | """Return the date + time string generated by the tree creation tool"""
339 | return self.design.get("date")
340 |
341 | def getSource(self):
342 | """Return the source string for the design"""
343 | return self.design.get("source")
344 |
345 | def getTool(self):
346 | """Return the tool string which was used to create the netlist tree"""
347 | return self.design.get("tool")
348 |
349 | def groupComponents(self):
350 | """Return a list of component lists. Components are grouped together
351 | when the value, library and part identifiers match
352 |
353 | """
354 | groups = []
355 |
356 | # Make sure to start off will all components ungrouped to begin with
357 | for c in self.components:
358 | c.grouped = False
359 |
360 | # Group components based on the value, library and part identifiers
361 | for c in self.components:
362 | if c.grouped == False:
363 | c.grouped = True
364 | newgroup = []
365 | newgroup.append(c)
366 |
367 | # Check every other ungrouped component against this component
368 | # and add to the group as necessary
369 | for ci in self.components:
370 | if ci.grouped == False and ci == c:
371 | newgroup.append(ci)
372 | ci.grouped = True
373 |
374 | # Add the new component group to the groups list
375 | groups.append(newgroup)
376 |
377 | # Each group is a list of components, we need to sort each list first
378 | # to get them in order as this makes for easier to read BOM's
379 | for g in groups:
380 | g = sorted(g, key=lambda g: g.getRef())
381 |
382 | # Finally, sort the groups to order the references alphabetically
383 | groups = sorted(groups, key=lambda group: group[0].getRef())
384 |
385 | return groups
386 |
387 | def formatXML(self):
388 | """Return the whole netlist formatted in XML"""
389 | return self.tree.formatXML()
390 |
391 | def formatHTML(self):
392 | """Return the whole netlist formatted in HTML"""
393 | return self.tree.formatHTML()
394 |
395 | def load(self, fname):
396 | """Load a kicad generic netlist
397 |
398 | Keywords:
399 | fname -- The name of the generic netlist file to open
400 |
401 | """
402 | try:
403 | self._reader = sax.make_parser()
404 | self._reader.setContentHandler(_gNetReader(self))
405 | self._reader.parse(fname)
406 | except IOError as e:
407 | print >> sys.stderr, __file__, ":", e
408 | sys.exit(-1)
409 |
410 |
411 | class part():
412 | """Class for a library part"""
413 | def __init__(self, part):
414 | # The part is a reference to a libpart generic netlist element
415 | self.element = part
416 |
417 | def __str__(self):
418 | # simply print the generic netlist element associated with this part
419 | return str(self.element)
420 |
421 | def getDatasheet(self):
422 | return self.element.get("docs")
423 |
424 | def getLib(self):
425 | return self.element.get("libpart", "lib")
426 |
427 | def getPart(self):
428 | return self.element.get("libpart", "part")
429 |
430 | def getDescription(self):
431 | return self.element.get("description")
432 |
433 | def getField(self, name):
434 | return self.element.get("field", "name", name)
435 |
436 |
437 | class _gNetReader(sax.handler.ContentHandler):
438 | """SAX kicad generic netlist content handler - passes most of the work back
439 | to the gNetlist class which builds a complete tree in RAM for the design
440 |
441 | """
442 | def __init__(self, aParent):
443 | self.parent = aParent
444 |
445 | def startElement(self, name, attrs):
446 | """Start of a new XML element event"""
447 | element = self.parent.addElement(name)
448 |
449 | for name in attrs.getNames():
450 | element.addAttribute(name, attrs.getValue(name))
451 |
452 | def endElement(self, name):
453 | self.parent.endElement()
454 |
455 | def characters(self, content):
456 | # Ignore erroneous white space - ignoreableWhitespace does not get rid
457 | # of the need for this!
458 | if not content.isspace():
459 | self.parent.addChars(content)
460 |
461 | def endDocument(self):
462 | """End of the XML document event"""
463 | self.parent.endDocument()
464 |
--------------------------------------------------------------------------------
/bom_grouped_v33_ky.py:
--------------------------------------------------------------------------------
1 | #
2 | # KiCad python module for interpreting generic netlists which can be used
3 | # to generate Bills of materials, etc.
4 | #
5 | # No string formatting is used on purpose as the only string formatting that
6 | # is current compatible with python 2.4+ to 3.0+ is the '%' method, and that
7 | # is due to be deprecated in 3.0+ soon
8 | #
9 |
10 |
11 | """
12 | @package
13 | children of 'bom_grouped_v33.py'
14 | version r33 - 06.2016 [bom_grouped_v33_ky.py]
15 | """
16 |
17 | import sys
18 | import xml.sax as sax
19 | #maui
20 | import logging
21 |
22 | class component():
23 | """Class for a set of component information"""
24 | def __init__(self, element):
25 | self.element = element
26 | self.libpart = None
27 |
28 | # Set to true when this component is included in a component group
29 | self.grouped = False
30 |
31 | def __eq__(self, other):
32 | """Equlivalency operator, remember this can be easily overloaded"""
33 | result = False
34 | if self.getValue() == other.getValue():
35 | if self.getLib() == other.getLib():
36 | if self.getPart() == other.getPart():
37 | if self.getFootprint() == other.getFootprint():
38 | #logging.warning('%s 1', self.getFootprint());
39 | #logging.warning('%s 2', other.getFootprint());
40 | #logging.warning('%s 2', self.getField('Voltage'));
41 | if self.getField('Manf#') == other.getField('Manf#'): #maui test
42 | if self.getField('Voltage') == other.getField('Voltage'): #maui test
43 | if self.getField('Tolerance') == other.getField('Tolerance'): #maui test
44 | if self.getField('Power') == other.getField('Power'): #maui test
45 | if self.getField('Temp') == other.getField('Temp'): #maui test
46 | if self.getField('Current') == other.getField('Current'): #maui test
47 | result = True
48 | return result
49 |
50 | def setPart(self, part):
51 | self.libpart = part
52 |
53 | def setValue(self, value):
54 | """Set the value of this component"""
55 | v = self.element.getChild("value")
56 | if v:
57 | v.setChars(value)
58 |
59 | def getValue(self):
60 | return self.element.get("value")
61 |
62 | def getRef(self):
63 | return self.element.get("comp", "ref")
64 |
65 | def getFootprint(self):
66 | return self.element.get("footprint")
67 |
68 | def getDatasheet(self):
69 | return self.element.get("datasheet")
70 |
71 | def getLib(self):
72 | return self.element.get("libsource", "lib")
73 |
74 | def getPart(self):
75 | return self.element.get("libsource", "part")
76 |
77 | def getTimestamp(self):
78 | return self.element.get("tstamp")
79 |
80 | def getDescription(self):
81 | # When attempting to access the part, we must take care in case the part
82 | # cannot be found in the netlist
83 | try:
84 | d = self.libpart.getDescription()
85 | except AttributeError:
86 | d = ""
87 | return d
88 |
89 | def getDatasheet(self):
90 | # When attempting to access the part, we must take care in case the part
91 | # cannot be found in the netlist
92 | try:
93 | d = self.libpart.getDatasheet()
94 | except AttributeError:
95 | d = ""
96 | return d
97 |
98 | def getField(self, name):
99 | """Return the value of a field named name. The component is first
100 | checked for the field, and then the components library part is checked
101 | for the field. If the field doesn't exist in either, an empty string is
102 | returned
103 |
104 | Keywords:
105 | name -- The name of the field to return the value for
106 |
107 | """
108 | # field = self.element.get("field", "name", name).encode('ascii', 'ignore').decode('ascii')
109 | field = self.element.get("field", "name", name).encode('utf-8')
110 | if field == "":
111 | try:
112 | field = self.libpart.getField(name)
113 | except AttributeError:
114 | field = ""
115 | return field
116 |
117 |
118 | class netlistElement():
119 | """Generic netlist element. All elements for a netlist tree which can be
120 | used to easily generate various output formats by propogating format
121 | requests to all children
122 | """
123 | def __init__(self, name, parent=None):
124 | self.name = name
125 | self.attributes = {}
126 | self.parent = parent
127 | self.chars = ""
128 | self.children = []
129 | self.indent = ""
130 |
131 | def __str__(self):
132 | """String representation of this netlist element
133 |
134 | """
135 | return (self.name + "[" + self.chars + "]" + " attr:" +
136 | str(len(self.attributes[a])))
137 |
138 | def formatXML(self, amChild=False):
139 | """Return this element formatted as XML
140 |
141 | Keywords:
142 | amChild -- If set to True, the start of document is not returned
143 |
144 | """
145 | s = ""
146 |
147 | if not amChild:
148 | s = "\n"
149 |
150 | s += self.indent + "<" + self.name
151 | for a in self.attributes:
152 | s += " " + a + "=\"" + self.attributes[a] + "\""
153 |
154 | if (len(self.chars) == 0) and (len(self.children) == 0):
155 | s += "/>"
156 | else:
157 | s += ">" + self.chars
158 |
159 | for c in self.children:
160 | c.indent += self.indent + " "
161 | s += "\n"
162 | s += c.formatXML(True)
163 |
164 | if (len(self.children) > 0):
165 | s += "\n" + self.indent
166 |
167 | if (len(self.children) > 0) or (len(self.chars) > 0):
168 | s += "" + self.name + ">"
169 |
170 | return s
171 |
172 | def formatHTML(self, amChild=False):
173 | """Return this element formatted as HTML
174 |
175 | Keywords:
176 | amChild -- If set to True, the start of document is not returned
177 |
178 | """
179 | s = ""
180 |
181 | if not amChild:
182 | s = """
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 | """
192 |
193 | s += "" + self.name + " " + self.chars + " | "
194 | for a in self.attributes:
195 | s += "- " + a + " = " + self.attributes[a] + "
"
196 |
197 | s += " |
\n"
198 |
199 | for c in self.children:
200 | s += c.formatHTML(True)
201 |
202 | if not amChild:
203 | s += """
204 |
205 | """
206 |
207 | return s
208 |
209 | def addAttribute(self, attr, value):
210 | """Add an attribute to this element"""
211 | self.attributes[attr] = value
212 |
213 | def setChars(self, chars):
214 | """Set the characters for this element"""
215 | self.chars = chars
216 |
217 | def addChars(self, chars):
218 | """Add characters (textual value) to this element"""
219 | self.chars += chars
220 |
221 | def addChild(self, child):
222 | """Add a child element to this element"""
223 | self.children.append(child)
224 | return self.children[len(self.children) - 1]
225 |
226 | def getParent(self):
227 | """Get the parent of this element (Could be None)"""
228 | return self.parent
229 |
230 | def setAttribute(self, attr, value):
231 | """Set an attributes value - in fact does the same thing as add
232 | attribute
233 |
234 | """
235 | self.attributes[attr] = value
236 |
237 | def getChild(self, name):
238 | """Returns a child element of name
239 |
240 | Keywords:
241 | name -- The name of the child element to return"""
242 | for child in self.children:
243 | if child.name == name:
244 | return child
245 |
246 | return None
247 |
248 | def get(self, element, attribute="", attrmatch=""):
249 | """Return the data for either an attribute, or else an element"""
250 | if (self.name == element):
251 | if attribute != "":
252 | if attrmatch != "":
253 | if self.attributes[attribute] == attrmatch:
254 | return self.chars
255 | else:
256 | return self.attributes[attribute]
257 | else:
258 | return self.chars
259 |
260 | for child in self.children:
261 | if child.get(element, attribute, attrmatch) != "":
262 | return child.get(element, attribute, attrmatch)
263 |
264 | return ""
265 |
266 |
267 | class netlist():
268 | """ Kicad generic netlist class. Generally loaded from a kicad generic
269 | netlist file. Includes several helper functions to ease BOM creating
270 | scripts
271 |
272 | """
273 | def __init__(self, fname=""):
274 | """Initialiser for the genericNetlist class
275 |
276 | Keywords:
277 | fname -- The name of the generic netlist file to open (Optional)
278 |
279 | """
280 | self.design = None
281 | self.components = []
282 | self.libparts = []
283 | self.libraries = []
284 | self.nets = []
285 |
286 | # The entire tree is loaded into self.tree
287 | self.tree = []
288 |
289 | self._curr_element = None
290 |
291 | if fname != "":
292 | self.load(fname)
293 |
294 | def addChars(self, content):
295 | """Add characters to the current element"""
296 | self._curr_element.addChars(content)
297 |
298 | def addElement(self, name):
299 | """Add a new kicad generic element to the list"""
300 | if self._curr_element == None:
301 | self.tree = netlistElement(name)
302 | self._curr_element = self.tree
303 | else:
304 | self._curr_element = self._curr_element.addChild(
305 | netlistElement(name, self._curr_element))
306 |
307 | # If this element is a component, add it to the components list
308 | if self._curr_element.name == "comp":
309 | self.components.append(component(self._curr_element))
310 |
311 | # Assign the design element
312 | if self._curr_element.name == "design":
313 | self.design = self._curr_element
314 |
315 | # If this element is a library part, add it to the parts list
316 | if self._curr_element.name == "libpart":
317 | self.libparts.append(part(self._curr_element))
318 |
319 | # If this element is a net, add it to the nets list
320 | if self._curr_element.name == "net":
321 | self.nets.append(self._curr_element)
322 |
323 | # If this element is a library, add it to the libraries list
324 | if self._curr_element.name == "library":
325 | self.libraries.append(self._curr_element)
326 |
327 | return self._curr_element
328 |
329 | def endDocument(self):
330 | """Called when the netlist document has been fully parsed"""
331 | # When the document is complete, the library parts must be linked to
332 | # the components as they are seperate in the tree so as not to
333 | # duplicate library part information for every component
334 | for c in self.components:
335 | for p in self.libparts:
336 | if p.getPart() == c.getPart() and p.getLib() == c.getLib():
337 | c.setPart(p)
338 |
339 | def endElement(self):
340 | """End the current element and switch to its parent"""
341 | self._curr_element = self._curr_element.getParent()
342 |
343 | def getDate(self):
344 | """Return the date + time string generated by the tree creation tool"""
345 | return self.design.get("date")
346 |
347 | def getSource(self):
348 | """Return the source string for the design"""
349 | return self.design.get("source")
350 |
351 | def getTool(self):
352 | """Return the tool string which was used to create the netlist tree"""
353 | return self.design.get("tool")
354 |
355 | def groupComponents(self):
356 | """Return a list of component lists. Components are grouped together
357 | when the value, library and part identifiers match
358 |
359 | """
360 | groups = []
361 |
362 | # Make sure to start off will all components ungrouped to begin with
363 | for c in self.components:
364 | c.grouped = False
365 |
366 | # Group components based on the value, library and part identifiers
367 | for c in self.components:
368 | if c.grouped == False:
369 | c.grouped = True
370 | newgroup = []
371 | newgroup.append(c)
372 |
373 | # Check every other ungrouped component against this component
374 | # and add to the group as necessary
375 | for ci in self.components:
376 | if ci.grouped == False and ci == c:
377 | newgroup.append(ci)
378 | ci.grouped = True
379 |
380 | # Add the new component group to the groups list
381 | groups.append(newgroup)
382 |
383 | # Each group is a list of components, we need to sort each list first
384 | # to get them in order as this makes for easier to read BOM's
385 | for g in groups:
386 | g = sorted(g, key=lambda g: g.getRef())
387 |
388 | # Finally, sort the groups to order the references alphabetically
389 | groups = sorted(groups, key=lambda group: group[0].getRef()) #maui
390 | #groups = sorted(groups, key=lambda group: int(group[0].getRef()[1:])) #maui to complete
391 | return groups
392 |
393 | def formatXML(self):
394 | """Return the whole netlist formatted in XML"""
395 | return self.tree.formatXML()
396 |
397 | def formatHTML(self):
398 | """Return the whole netlist formatted in HTML"""
399 | return self.tree.formatHTML()
400 |
401 | def load(self, fname):
402 | """Load a kicad generic netlist
403 |
404 | Keywords:
405 | fname -- The name of the generic netlist file to open
406 |
407 | """
408 | try:
409 | self._reader = sax.make_parser()
410 | self._reader.setContentHandler(_gNetReader(self))
411 | self._reader.parse(fname)
412 | except IOError as e:
413 | print >> sys.stderr, __file__, ":", e
414 | sys.exit(-1)
415 |
416 |
417 | class part():
418 | """Class for a library part"""
419 | def __init__(self, part):
420 | # The part is a reference to a libpart generic netlist element
421 | self.element = part
422 |
423 | def __str__(self):
424 | # simply print the generic netlist element associated with this part
425 | return str(self.element)
426 |
427 | def getDatasheet(self):
428 | return self.element.get("docs")
429 |
430 | def getLib(self):
431 | return self.element.get("libpart", "lib")
432 |
433 | def getPart(self):
434 | return self.element.get("libpart", "part")
435 |
436 | def getDescription(self):
437 | return self.element.get("description")
438 |
439 | def getField(self, name):
440 | return self.element.get("field", "name", name)
441 |
442 |
443 | class _gNetReader(sax.handler.ContentHandler):
444 | """SAX kicad generic netlist content handler - passes most of the work back
445 | to the gNetlist class which builds a complete tree in RAM for the design
446 |
447 | """
448 | def __init__(self, aParent):
449 | self.parent = aParent
450 |
451 | def startElement(self, name, attrs):
452 | """Start of a new XML element event"""
453 | element = self.parent.addElement(name)
454 |
455 | for name in attrs.getNames():
456 | element.addAttribute(name, attrs.getValue(name))
457 |
458 | def endElement(self, name):
459 | self.parent.endElement()
460 |
461 | def characters(self, content):
462 | # Ignore erroneous white space - ignoreableWhitespace does not get rid
463 | # of the need for this!
464 | if not content.isspace():
465 | self.parent.addChars(content)
466 |
467 | def endDocument(self):
468 | """End of the XML document event"""
469 | self.parent.endDocument()
470 |
--------------------------------------------------------------------------------