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
26 return 25.4 * (float(mils) / 1000)
28 class footprint(object):
33 self.dist_license = "GPLv3"
34 self.use_license = "Unlimited"
37 self.padstacks = dict()
39 # process defaults, based on greatest process minimum at our vendors
41 self.process_soldermask = mils2mm(3) # space between pad and mask
42 self.process_trace = mils2mm(6) # trace width
43 self.process_space = mils2mm(6) # spacing
44 self.process_drill = mils2mm(13) # drill dize
45 self.process_ring = mils2mm(7) # annular ring
47 self.line_thickness = mils2mm(10) # default silk line width
49 # the basic plan is that each primitive function puts the object
50 # definition in the relevant dictionary .. when we go to emit the
51 # part, we'll do things like work out the minimum set of padstack
52 # prototypes by iterating over the dictionaries... then output the
53 # working set of required objects
56 # we expect these to be provided
65 # the following are normally computed / defaulted, but can be provided
67 # x1 start of pad "line"
69 # x2 end of pad "line"
71 # thickness thickness of pad "line"
72 # spacing space between pad and other traces
73 # soldermask space between pad and solder mask
74 # clearance twice the spacing between pad and other traces
75 # mask thickness of pad and solder mask
76 # options default to 'square'
78 # print ("adding pad")
82 d['x1'] = d['x'] - max(0, (d['width'] - d['height']) / 2)
84 d['x2'] = d['x'] + max(0, (d['width'] - d['height']) / 2)
86 d['y1'] = d['y'] - max(0, (d['height'] - d['width']) / 2)
88 d['y2'] = d['y'] + max(0, (d['height'] - d['width']) / 2)
89 if 'thickness' not in d:
90 d['thickness'] = min(d['width'], d['height'])
91 if 'spacing' not in d:
92 d['spacing'] = self.process_space
93 if 'soldermask' not in d:
94 d['soldermask'] = self.process_soldermask
95 if 'clearance' not in d:
96 d['clearance'] = d['spacing'] / 2
98 d['mask'] = d['thickness'] + d['soldermask'] * 2
99 if 'options' not in d:
100 d['options'] = 'square'
102 self.items = self.items + [d]
105 # we expect these to be provided
109 # drill diameter of drill hole
113 # the following are normally computed / defaulted, but can be provided
115 # ring width of annular ring around hole
116 # spacing space between pin and other traces
117 # soldermask space between pin and solder mask
118 # thickness thickness of pin "line"
119 # clearance twice the spacing between pad and other traces
120 # mask thickness of pad and solder mask
121 # options default to 'square' for pin number 1
123 # print ("adding pin")
126 if 'spacing' not in d:
127 d['spacing'] = self.process_space
129 d['ring'] = self.process_ring
130 if 'soldermask' not in d:
131 d['soldermask'] = self.process_soldermask
132 if 'clearance' not in d:
133 d['clearance'] = d['spacing'] * 2
135 d['mask'] = d['thickness'] + d['soldermask'] * 2
136 if 'options' not in d: # default pin 1 to square ring
137 if d['number'] == '1':
138 d['options'] = 'square'
141 if 'thickness' not in d:
142 d['thickness'] = d['drill'] + d['ring'] * 2
144 d['mask'] = d['thickness'] + d['soldermask'] * 2
146 self.items = self.items + [d]
149 # we expect these to be provided
158 # the following are normally computed / defaulted, but can be provided
160 # x1 start of slot "line"
162 # x2 end of slot "line"
164 # spacing space between slot copper and other traces
165 # thickness width of copper edge to edge across slot
166 # soldermask space between slot copper and solder mask
167 # clearance twice the spacing between slot copper and other traces
168 # mask thickness of slot copper and solder mask
170 # print ("adding slot")
174 d['x1'] = d['x'] - max(0, (d['width'] - d['height']) / 2)
176 d['x2'] = d['x'] + max(0, (d['width'] - d['height']) / 2)
178 d['y1'] = d['y'] - max(0, (d['height'] - d['width']) / 2)
180 d['y2'] = d['y'] + max(0, (d['height'] - d['width']) / 2)
181 if 'spacing' not in d:
182 d['spacing'] = self.process_space
183 if 'thickness' not in d:
184 d['thickness'] = d['width'] + self.process_ring * 2
185 if 'soldermask' not in d:
186 d['soldermask'] = self.process_soldermask
187 if 'clearance' not in d:
188 d['clearance'] = d['spacing'] / 2
190 d['mask'] = d['thickness'] + d['soldermask'] * 2
192 self.items = self.items + [d]
196 # we expect these to be provided
198 # x1 start of silk line
200 # x2 end of silk line
203 # the following are normally defaulted, but can be provided
205 # thickness thickness of silk line
210 if 'thickness' not in d:
211 d['thickness'] = self.line_thickness
213 self.items = self.items + [d]
216 # we expect these to be provided
218 # x center of silk arc
225 # the following are normally defaulted, but can be provided
227 # thickness thickness of silk line
232 if 'thickness' not in d:
233 d['thickness'] = self.line_thickness
235 self.items = self.items + [d]
237 def write_pin_square(size):
238 print(" li:ps_poly {")
239 print(" %u mm" % -size / 2)
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)
249 def write_pin_circle(size):
250 print(" ha:ps_circ {")
253 print(" dia = $u mm" % size)
256 def write_stack_shape(p, layer):
257 print(" ha:ps_shape_v4 {")
258 print(" clearance = %u mm" % p['clearance'])
259 if p['options'] == 'square':
260 write_pin_square(p['copper'])
262 write_pin_circle(p['copper'])
263 print(" ha:layer_mask {")
265 print(" $s = 1" % layer)
269 def emit_padstack_prototypes(self):
270 # iterate over copper items creating min set of padstack prototypes,
271 # and annotating items with their prototype number as we go
274 if i['type'] == 'pad':
275 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']
276 if i['type'] == 'slot':
277 myhash = i['type']+str(i['width'])+str(i['height'])+str(i['thickness'])+str(i['spacing'])+str(i['soldermask'])+str(i['clearance'])+str(i['mask'])
278 if i['type'] == 'pin':
279 myhash = i['type']+str(i['drill'])+str(i['ring'])+str(i['spacing'])+str(i['soldermask'])+str(i['clearance'])+str(i['mask'])+i['options']
281 # print ("myhash is %s" % myhash)
282 if myhash in self.padstacks:
283 p = self.padstacks[myhash]
284 i['prototypenumber'] = p['prototypenumber']
286 # print ("new hash!")
287 i['prototypenumber'] = protocount
289 self.padstacks[myhash] = i
291 # print ("%u unique prototypes found" % protocount)
293 # now that we have the set of prototypes, emit them
294 print(" li:padstack_prototypes {")
295 for thisp in self.padstacks:
296 p = self.padstacks[thisp]
297 if p['type'] == 'pin':
298 print(" ha:ps_proto_v4.%u {" % p['prototypenumber'])
300 print(" hbottom = 0")
301 print(" hplated = 1")
302 print(" hdia = %u mm" % p['drill'])
304 write_stack_shape(p, 'top')
305 write_stack_shape(p, 'bottom')
309 if p['type'] == 'pad':
310 print(" ha:ps_proto_v4.%u {" % p['prototypenumber'])
312 print(" hbottom = 0")
313 print(" hplated = 1")
314 print(" hdia = %u mm" % 0) # bogus to the max
319 if p['type'] == 'slot':
320 # this is just a copy of the 'pad' code for now
321 print(" ha:ps_proto_v4.%u {" % p['prototypenumber'])
323 print(" hbottom = 0")
324 print(" hplated = 1")
325 print(" hdia = %u mm" % 0) # bogus to the max
332 def emit_padstacks(self):
333 print("padstacks will go here")
334 # process pad, pin, slot items referencing relevant prototypes
335 print("%u items defined" % len(self.items))
336 print("----- start of defined items -----")
338 print("----- end of defined items -----")
340 def emit_line(self, role, id, x1, y1, x2, y2, unit):
341 print(" ha:line.%u {" % id)
342 print(" clearance = 0")
343 print(" thickness = 0")
344 print(" ha:attributes {")
345 print(" subc-role = %s" % role)
347 print(" x1 = %u%s" % (x1, unit))
348 print(" y1 = %u%s" % (y1, unit))
349 print(" x2 = %u%s" % (x2, unit))
350 print(" y2 = %u%s" % (y2, unit))
352 self.cnt = self.cnt + 1
354 def emit_top_silk(self):
355 print("top_silk will go here")
356 # process line and arc items
360 print(" ha:subc-aux {")
365 print(" virtual = 1")
367 print(" li:objects {")
368 self.emit_line("origin", self.cnt, 0, 0, 0, 0, "mm")
369 self.emit_line("x", self.cnt, 0, 0, 1, 0, "mm")
370 self.emit_line("y", self.cnt, 0, 0, 0, 1, "mm")
374 def create_uuid(self):
375 return hashlib.md5(self.name.encode('utf-8')).hexdigest()
378 print("li:pcb-rnd-subcircuit-v4 {")
379 print(" ha:subc.0 {")
380 print(" ha:attributes {")
381 print(" description = %s" % self.description)
382 print(" copyright = %s" % self.copyright)
383 print(" dist_license = %s" % self.dist_license)
384 print(" use_license = %s" % self.use_license)
386 print(" uid = %s" % self.create_uuid())
388 self.emit_padstack_prototypes()
389 print(" li:objects {")
390 self.emit_padstacks()
392 print(" li:layers {")