2 # Python library to assist in generating lihata format footprints for pcb-rnd
3 # Copyright 2020 by Bdale Garbee <bdale@gag.com>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # Each footprint should have a parent Python script that initializes this
16 # library and provides a high-level description of the primitives required.
17 # The footprint is delivered to stdout, so the encapsulating Makefile and/or
18 # iterating wrapper script is responsible for redirecting to the desired
19 # <something>.lht file(s).
21 # all dimensions are assumed to be in mm
27 return 25.4 * (float(mils) / 1000)
29 class footprint(object):
34 self.dist_license = "GPLv3"
35 self.use_license = "Unlimited"
38 self.padstacks = dict()
40 # process defaults, based on greatest process minimum at our vendors
42 self.process_soldermask = mils2mm(3); # space between pad and mask
43 self.process_trace = mils2mm(6); # trace width
44 self.process_space = mils2mm(6); # spacing
45 self.process_drill = mils2mm(13); # drill dize
46 self.process_ring = mils2mm(7); # annular ring
48 self.line_thickness = mils2mm(10); # default silk line width
50 # the basic plan is that each primitive function puts the object
51 # definition in the relevant dictionary .. when we go to emit the
52 # part, we'll do things like work out the minimum set of padstack
53 # prototypes by iterating over the dictionaries... then output the
54 # working set of required objects
57 # we expect these to be provided
66 # the following are normally computed / defaulted, but can be provided
68 # x1 start of pad "line"
70 # x2 end of pad "line"
72 # thickness thickness of pad "line"
73 # spacing space between pad and other traces
74 # soldermask space between pad and solder mask
75 # clearance twice the spacing between pad and other traces
76 # mask thickness of pad and solder mask
77 # options default to 'square'
79 # print ("adding pad")
83 d['x1'] = d['x'] - max(0, (d['width'] - d['height']) /2)
85 d['x2'] = d['x'] + max(0, (d['width'] - d['height']) /2)
87 d['y1'] = d['y'] - max(0, (d['height'] - d['width']) /2)
89 d['y2'] = d['y'] + max(0, (d['height'] - d['width']) /2)
90 if 'thickness' not in d:
91 d['thickness'] = min(d['width'], d['height'])
92 if 'spacing' not in d:
93 d['spacing'] = self.process_space
94 if 'soldermask' not in d:
95 d['soldermask'] = self.process_soldermask
96 if 'clearance' not in d:
97 d['clearance'] = d['spacing'] / 2
99 d['mask'] = d['thickness'] + d['soldermask'] * 2
100 if 'options' not in d:
101 d['options'] = 'square'
103 self.items = self.items + [d]
106 # we expect these to be provided
110 # drill diameter of drill hole
114 # the following are normally computed / defaulted, but can be provided
116 # ring width of annular ring around hole
117 # spacing space between pin and other traces
118 # soldermask space between pin and solder mask
119 # thickness thickness of pin "line"
120 # clearance twice the spacing between pad and other traces
121 # mask thickness of pad and solder mask
122 # options default to 'square' for pin number 1
124 # print ("adding pin")
127 if 'spacing' not in d:
128 d['spacing'] = self.process_space
130 d['ring'] = self.process_ring
131 if 'soldermask' not in d:
132 d['soldermask'] = self.process_soldermask
133 if 'clearance' not in d:
134 d['clearance'] = d['spacing'] * 2
136 d['mask'] = d['thickness'] + d['soldermask'] * 2
137 if 'options' not in d: # default pin 1 to square ring
138 if d['number'] == '1':
139 d['options'] = 'square'
142 if 'thickness' not in d:
143 d['thickness'] = d['drill'] + d['ring'] * 2
145 d['mask'] = d['thickness'] + d['soldermask'] * 2
147 self.items = self.items + [d]
150 # we expect these to be provided
159 # the following are normally computed / defaulted, but can be provided
161 # x1 start of slot "line"
163 # x2 end of slot "line"
165 # spacing space between slot copper and other traces
166 # thickness width of copper edge to edge across slot
167 # soldermask space between slot copper and solder mask
168 # clearance twice the spacing between slot copper and other traces
169 # mask thickness of slot copper and solder mask
171 # print ("adding slot")
175 d['x1'] = d['x'] - max(0, (d['width'] - d['height']) /2)
177 d['x2'] = d['x'] + max(0, (d['width'] - d['height']) /2)
179 d['y1'] = d['y'] - max(0, (d['height'] - d['width']) /2)
181 d['y2'] = d['y'] + max(0, (d['height'] - d['width']) /2)
182 if 'spacing' not in d:
183 d['spacing'] = self.process_space
184 if 'thickness' not in d:
185 d['thickness'] = d['width'] + self.process_ring * 2
186 if 'soldermask' not in d:
187 d['soldermask'] = self.process_soldermask
188 if 'clearance' not in d:
189 d['clearance'] = d['spacing'] / 2
191 d['mask'] = d['thickness'] + d['soldermask'] * 2
193 self.items = self.items + [d]
197 # we expect these to be provided
199 # x1 start of silk line
201 # x2 end of silk line
204 # the following are normally defaulted, but can be provided
206 # thickness thickness of silk line
208 print ("adding line")
211 if 'thickness' not in d:
212 d['thickness'] = self.line_thickness
214 self.items = self.items + [d]
217 # we expect these to be provided
219 # x center of silk arc
226 # the following are normally defaulted, but can be provided
228 # thickness thickness of silk line
233 if 'thickness' not in d:
234 d['thickness'] = self.line_thickness
236 self.items = self.items + [d]
238 def write_pin_square(size):
239 print(" li:ps_poly {")
240 print(" %u mm" % -size / 2)
241 print(" %u mm" % -size / 2)
242 print(" %u mm" % size / 2)
243 print(" %u mm" % -size / 2)
244 print(" %u mm" % size / 2)
245 print(" %u mm" % size / 2)
246 print(" %u mm" % -size / 2)
247 print(" %u mm" % size / 2)
250 def write_pin_circle(size):
251 print(" ha:ps_circ {")
254 print(" dia = $u mm" % size)
257 def write_pin_shape(p, layer):
258 print(" ha:ps_shape_v4 {")
259 print(" clearance = %u mm" % p['clearance'])
260 if p['options'] == 'square':
261 write_pin_square(p['copper'])
263 write_pin_circle(p['copper'])
264 print(" ha:layer_mask {")
266 print(" $s = 1" % layer)
270 def emit_padstack_prototypes(self):
271 # iterate over copper items creating min set of padstack prototypes,
272 # and annotating items with their prototype number as we go
275 if i['type'] == 'pad':
276 myhash = i['type']+str(i['width'])+str(i['height'])+str(i['thickness'])+str(i['spacing'])+str(i['soldermask'])+str(i['clearance'])+str(i['mask'])+i['options']
277 if i['type'] == 'slot':
278 myhash = i['type']+str(i['width'])+str(i['height'])+str(i['thickness'])+str(i['spacing'])+str(i['soldermask'])+str(i['clearance'])+str(i['mask'])
279 if i['type'] == 'pin':
280 myhash = i['type']+str(i['drill'])+str(i['ring'])+str(i['spacing'])+str(i['soldermask'])+str(i['clearance'])+str(i['mask'])+i['options']
282 # print ("myhash is %s" % myhash)
283 if myhash in self.padstacks:
284 p = self.padstacks[myhash]
285 i['prototypenumber'] = p['prototypenumber']
287 # print ("new hash!")
288 i['prototypenumber'] = protocount
290 self.padstacks[myhash] = i
292 # print ("%u unique prototypes found" % protocount)
294 # now that we have the set of prototypes, emit them
295 print (" li:padstack_prototypes {")
296 for thisp in self.padstacks:
297 p = self.padstacks[thisp]
298 if p['type'] == 'pin':
299 print(" ha:ps_proto_v4.%u {" % p['prototypenumber'])
301 print(" hbottom = 0")
302 print(" hplated = 1")
303 print(" hdia = %u mm" % p['drill'])
305 write_stack_shape(p, 'top')
306 write_stack_shape(p, 'bottom')
310 if p['type'] == 'pad':
311 print(" ha:ps_proto_v4.%u {" % p['prototypenumber'])
313 print(" hbottom = 0")
314 print(" hplated = 1")
315 print(" hdia = %u mm" % 0) # bogus to the max
322 def emit_padstacks(self):
323 print("padstacks will go here")
324 # process pad, pin, slot items referencing relevant prototypes
325 print("%u items defined" % len(self.items))
326 print("----- start of defined items -----")
328 print("----- end of defined items -----")
330 def emit_line(self, role, id, x1, y1, x2, y2, unit):
331 print(" ha:line.%u {" % id)
332 print(" clearance = 0")
333 print(" thickness = 0")
334 print(" ha:attributes {")
335 print(" subc-role = %s" % role)
337 print(" x1 = %u%s" % (x1, unit))
338 print(" y1 = %u%s" % (y1, unit))
339 print(" x2 = %u%s" % (x2, unit))
340 print(" y2 = %u%s" % (y2, unit))
342 self.cnt = self.cnt + 1
344 def emit_top_silk(self):
345 print("top_silk will go here")
346 # process line and arc items
350 print(" ha:subc-aux {")
355 print(" virtual = 1")
357 print(" li:objects {")
358 self.emit_line("origin", self.cnt, 0, 0, 0, 0, "mm");
359 self.emit_line("x", self.cnt, 0, 0, 1, 0, "mm");
360 self.emit_line("y", self.cnt, 0, 0, 0, 1, "mm");
364 def create_uuid(self):
365 return hashlib.md5(self.name.encode('utf-8')).hexdigest()
368 print("li:pcb-rnd-subcircuit-v4 {")
369 print(" ha:subc.0 {")
370 print(" ha:attributes {")
371 print(" description = %s" % self.description)
372 print(" copyright = %s" % self.copyright)
373 print(" dist_license = %s" % self.dist_license)
374 print(" use_license = %s" % self.use_license)
376 print(" uid = %s" % self.create_uuid())
378 self.emit_padstack_prototypes()
379 print(" li:objects {")
380 self.emit_padstacks()
382 print(" li:layers {")