don't default author and copyright assertion
[hw/altusmetrum] / packages / fplht.py
1 #! /usr/bin/python3
2 # Python library to assist in generating lihata format footprints for pcb-rnd
3 # Copyright 2020 by Bdale Garbee <bdale@gag.com>
4 #
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.
9 #
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.
14
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).
20 #
21 # all dimensions are assumed to be in mm
22
23 import math
24 import hashlib
25
26 def mils2mm(mils):
27     return 25.4 * (float(mils) / 1000)
28
29 class footprint(object):
30     def __init__(self):
31         self.name = ""
32         self.description = ""
33         self.copyright = ""
34         self.dist_license = "GPLv3"
35         self.use_license = "Unlimited"
36         self.cnt = 0
37         self.items = []
38         self.padstacks = dict()
39
40         # process defaults, based on greatest process minimum at our vendors
41
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
47
48         self.line_thickness = mils2mm(10);        # default silk line width
49
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
55
56     def pad(self,d):
57         # we expect these to be provided
58         #
59         # x                center of pad
60         # y
61         # width                size of pad
62         # height
63         # name
64         # number
65         #
66         # the following are normally computed / defaulted, but can be provided
67         #
68         # x1                start of pad "line"
69         # y1
70         # x2                end of pad "line"
71         # y2
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'
78
79         # print ("adding pad")
80         d['type'] = 'pad'
81
82         if 'x1' not in d:
83                 d['x1'] = d['x'] - max(0, (d['width'] - d['height']) /2)
84         if 'x2' not in d:
85                 d['x2'] = d['x'] + max(0, (d['width'] - d['height']) /2)
86         if 'y1' not in d:
87                 d['y1'] = d['y'] - max(0, (d['height'] - d['width']) /2)
88         if 'y2' not in d:
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
98         if 'mask' not in d:
99                 d['mask'] = d['thickness'] + d['soldermask'] * 2
100         if 'options' not in d:
101                 d['options'] = 'square'
102
103         self.items = self.items + [d]
104
105     def pin():
106         # we expect these to be provided
107         #
108         # x                center of pin
109         # y
110         # drill                diameter of drill hole
111         # name
112         # number
113         #
114         # the following are normally computed / defaulted, but can be provided
115         #
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
123
124         # print ("adding pin")
125         d['type'] = 'pin'
126
127         if 'spacing' not in d:
128                 d['spacing'] = self.process_space
129         if 'ring' not in d:
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
135         if 'mask' not in d:
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'
140             else:
141                 d['options'] = ''
142         if 'thickness' not in d:
143                 d['thickness'] = d['drill'] + d['ring'] * 2
144         if 'mask' not in d:
145                 d['mask'] = d['thickness'] + d['soldermask'] * 2
146
147         self.items = self.items + [d]
148     
149     def slot(self,d):
150         # we expect these to be provided
151         #
152         # x                center of slot
153         # y
154         # width                size of slot
155         # height
156         # name
157         # number
158         #
159         # the following are normally computed / defaulted, but can be provided
160         #
161         # x1                start of slot "line"
162         # y1
163         # x2                end of slot "line"
164         # y2
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
170
171         # print ("adding slot")
172         d['type'] = 'slot'
173
174         if 'x1' not in d:
175                 d['x1'] = d['x'] - max(0, (d['width'] - d['height']) /2)
176         if 'x2' not in d:
177                 d['x2'] = d['x'] + max(0, (d['width'] - d['height']) /2)
178         if 'y1' not in d:
179                 d['y1'] = d['y'] - max(0, (d['height'] - d['width']) /2)
180         if 'y2' not in d:
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
190         if 'mask' not in d:
191                 d['mask'] = d['thickness'] + d['soldermask'] * 2
192
193         self.items = self.items + [d]
194
195     ## for silk layers
196     def line():
197         # we expect these to be provided
198         #
199         # x1                start of silk line
200         # y1
201         # x2                end of silk line
202         # y2
203         #
204         # the following are normally defaulted, but can be provided
205         #
206         # thickness        thickness of silk line
207
208         print ("adding line")
209         d['type'] = 'line'
210
211         if 'thickness' not in d:
212                 d['thickness'] = self.line_thickness
213         
214         self.items = self.items + [d]
215
216     def arc():
217         # we expect these to be provided
218         #
219         # x                center of silk arc
220         # y
221         # width        
222         # height
223         # startangle
224         # deltaangle
225         #
226         # the following are normally defaulted, but can be provided
227         #
228         # thickness        thickness of silk line
229
230         print ("adding arc")
231         d['type'] = 'arc'
232
233         if 'thickness' not in d:
234                 d['thickness'] = self.line_thickness
235         
236         self.items = self.items + [d]
237
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)
248         print("       }")
249
250     def write_pin_circle(size):
251         print("       ha:ps_circ {")
252         print("        x = 0")
253         print("        y = 0")
254         print("        dia = $u mm" % size)
255         print("       }")
256
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']) 
262         else:
263             write_pin_circle(p['copper']) 
264         print("       ha:layer_mask {")
265         print("        copper = 1")
266         print("        $s = 1" % layer)
267         print("       }")
268         print("      }")
269
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
273         protocount = 0
274         for i in self.items:
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']
281
282             # print ("myhash is %s" % myhash)
283             if myhash in self.padstacks:
284                 p = self.padstacks[myhash]
285                 i['prototypenumber'] = p['prototypenumber']
286             else:
287                 # print ("new hash!")
288                 i['prototypenumber'] = protocount
289                 protocount += 1
290                 self.padstacks[myhash] = i
291
292         # print ("%u unique prototypes found" % protocount)
293
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'])
300                 print("     htop = 0")
301                 print("     hbottom = 0")
302                 print("     hplated = 1")
303                 print("     hdia = %u mm" % p['drill'])
304                 print("     li:shape {")
305                 write_stack_shape(p, 'top')
306                 write_stack_shape(p, 'bottom')
307                 print("     }")
308                 print("    }")
309
310             if p['type'] == 'pad':
311                 print("    ha:ps_proto_v4.%u {" % p['prototypenumber'])
312                 print("     htop = 0")
313                 print("     hbottom = 0")
314                 print("     hplated = 1")
315                 print("     hdia = %u mm" % 0)         # bogus to the max
316                 print("     li:shape {")
317                 print("     }")
318                 print("    }")
319
320         print ("   }")
321
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 -----")
327         print(self.items)
328         print("----- end of defined items -----")
329
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)
336         print("       }")
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))
341         print("      }")
342         self.cnt = self.cnt + 1
343
344     def emit_top_silk(self):
345         print("top_silk will go here")
346         # process line and arc items
347
348     def emit_bbox(self):
349         self.cnt = 0
350         print("    ha:subc-aux {")
351         print("     lid = 1")
352         print("     ha:type {")
353         print("      top = 1")
354         print("      misc = 1")
355         print("      virtual = 1")
356         print("     }")
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");
361         print("     }")
362         print("    }")
363
364     def create_uuid(self):
365         return hashlib.md5(self.name.encode('utf-8')).hexdigest()
366
367     def emit(self):
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)
375         print("  }")
376         print("  uid = %s" % self.create_uuid())
377         print("  ha:data {")
378         self.emit_padstack_prototypes()
379         print("   li:objects {")
380         self.emit_padstacks()
381         print("   }")
382         print("   li:layers {")
383         self.emit_top_silk()
384         self.emit_bbox()
385         print("   }")
386         print("  }")
387         print(" }")
388         print("}")