pull in a 40-pin DIP footprint from old pcb library
[hw/altusmetrum] / packages / fplht.py
index e169792880c7d7a841b70cf1e23919415e232e49..8d17148c548d9fb3aee32c4e5d4db28326b05da3 100644 (file)
-#! /usr/bin/python
-# Copyright 2020 by Bdale Garbee <bdale@gag.com>.  GPLv3
-
+#! /usr/bin/python3
 # Python library to assist in generating lihata format footprints for pcb-rnd
+# Copyright 2020 by Bdale Garbee <bdale@gag.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
 
-# Each footprint should have a parent Python script that initializes this 
-# library and provides a high-level description of the primitives required.  
-# The footprint is delivered to stdout, so the encapsulating Makefile and/or 
-# iterating wrapper script is responsible for redirecting to the desired 
+# Each footprint should have a parent Python script that initializes this
+# library and provides a high-level description of the primitives required.
+# The footprint is delivered to stdout, so the encapsulating Makefile and/or
+# iterating wrapper script is responsible for redirecting to the desired
 # <something>.lht file(s).
+#
+# all dimensions are assumed to be in mm
 
-import math
 import hashlib
 
+def mils2mm(mils):
+    return 25.4 * (float(mils) / 1000)
+
 class footprint(object):
     def __init__(self):
-       self.units = "mm"
-       self.name = ""
-       self.description = ""
-       self.dist_license = "GPLv3"
-       self.use_license = "Unlimited"
-       self.author = "Bdale Garbee <bdale@gag.com>"
-       self.cnt = 0
-
-    # primitive objects
-    # (proposed argument lists taken from pcb land_patterns doc)
-    # -- can probably collapse sflags+nflags->flags?
-
-       # the basic plan is that each primitive function puts the object
-       # definition in the relevant dictionary .. when we go to emit the
-       # part, we'll do things like work out the minimum set of padstack
-       # prototypes by iterating over the dictionaries... then output the
-       # working set of required objects
-
-    ## for copper layers
-    # def pad():
-    #  x1, y1, x2, y2, thickness, clearance, mask, name, number, sflags, nflags
-    # def pin():
-    #  x, y, thickness, clearance, mask, drill, name, number, sflags, nflags
-    # def slot():
-
-    ## for silk layers
-    # def line():
-    #  x1, y1, x2, y2, thickness
-    # def arc():
-    #  x, y, width, height, startangle, deltaangle, thickness
+        self.name = ""
+        self.description = ""
+        self.copyright = ""
+        self.dist_license = "GPLv3"
+        self.use_license = "Unlimited"
+        self.cnt = 0
+        self.items = []
+        self.padstacks = dict()
+
+        # process defaults, based on greatest process minimum at our vendors
+
+        self.process_soldermask = mils2mm(3)    # space between pad and mask
+        self.process_trace = mils2mm(6)        # trace width
+        self.process_space = mils2mm(6)        # spacing
+        self.process_drill = mils2mm(13)        # drill dize
+        self.process_ring = mils2mm(7)                # annular ring
+
+        self.line_thickness = mils2mm(10)        # default silk line width
+
+        # the basic plan is that each primitive function puts the object
+        # definition in the relevant dictionary .. when we go to emit the
+        # part, we'll do things like work out the minimum set of padstack
+        # prototypes by iterating over the dictionaries... then output the
+        # working set of required objects
+
+    def pad(self, d):
+        # we expect these to be provided
+        #
+        # x             center of pad
+        # y
+        # width         size of pad
+        # height
+        # name
+        # number
+        #
+        # the following are normally computed / defaulted, but can be provided
+        #
+        # x1             start of pad "line"
+        # y1
+        # x2             end of pad "line"
+        # y2
+        # thickness      thickness of pad "line"
+        # spacing        space between pad and other traces
+        # soldermask     space between pad and solder mask
+        # clearance      twice the spacing between pad and other traces
+        # mask           thickness of pad and solder mask
+        # options        default to 'square'
+
+        # print ("adding pad")
+        d['type'] = 'pad'
+
+        if 'x1' not in d:
+            d['x1'] = d['x'] - max(0, (d['width'] - d['height']) / 2)
+        if 'x2' not in d:
+            d['x2'] = d['x'] + max(0, (d['width'] - d['height']) / 2)
+        if 'y1' not in d:
+            d['y1'] = d['y'] - max(0, (d['height'] - d['width']) / 2)
+        if 'y2' not in d:
+            d['y2'] = d['y'] + max(0, (d['height'] - d['width']) / 2)
+        if 'thickness' not in d:
+            d['thickness'] = min(d['width'], d['height'])
+        if 'spacing' not in d:
+            d['spacing'] = self.process_space
+        if 'soldermask' not in d:
+            d['soldermask'] = self.process_soldermask
+        if 'clearance' not in d:
+            d['clearance'] = d['spacing'] / 2
+        if 'mask' not in d:
+            d['mask'] = d['thickness'] + d['soldermask'] * 2
+        if 'options' not in d:
+            d['options'] = 'square'
+
+        self.items = self.items + [d]
+
+    def pin(self, d):
+        # we expect these to be provided
+        #
+        # x             center of pin
+        # y
+        # drill         diameter of drill hole
+        # name
+        # number
+        #
+        # the following are normally computed / defaulted, but can be provided
+        #
+        # ring          width of annular ring around hole
+        # spacing       space between pin and other traces
+        # soldermask    space between pin and solder mask
+        # thickness     thickness of pin "line"
+        # clearance     twice the spacing between pad and other traces
+        # mask          thickness of pad and solder mask
+        # options       default to 'square' for pin number 1
+
+        # print ("adding pin")
+        d['type'] = 'pin'
+
+        if 'spacing' not in d:
+            d['spacing'] = self.process_space
+        if 'ring' not in d:
+            d['ring'] = self.process_ring
+        if 'soldermask' not in d:
+            d['soldermask'] = self.process_soldermask
+        if 'clearance' not in d:
+            d['clearance'] = d['spacing'] * 2
+        if 'mask' not in d:
+            d['mask'] = d['thickness'] + d['soldermask'] * 2
+        if 'options' not in d:                # default pin 1 to square ring
+            if d['number'] == '1':
+                d['options'] = 'square'
+            else:
+                d['options'] = ''
+        if 'thickness' not in d:
+            d['thickness'] = d['drill'] + d['ring'] * 2
+        if 'mask' not in d:
+            d['mask'] = d['thickness'] + d['soldermask'] * 2
+
+        self.items = self.items + [d]
+
+    def slot(self, d):
+        # we expect these to be provided
+        #
+        # x             center of slot
+        # y
+        # width         size of slot
+        # height
+        # name
+        # number
+        #
+        # the following are normally computed / defaulted, but can be provided
+        #
+        # x1            start of slot "line"
+        # y1
+        # x2            end of slot "line"
+        # y2
+        # spacing       space between slot copper and other traces
+        # thickness     width of copper edge to edge across slot
+        # soldermask    space between slot copper and solder mask
+        # clearance     twice the spacing between slot copper and other traces
+        # mask          thickness of slot copper and solder mask
+
+        # print ("adding slot")
+        d['type'] = 'slot'
+
+        if 'x1' not in d:
+            d['x1'] = d['x'] - max(0, (d['width'] - d['height']) / 2)
+        if 'x2' not in d:
+            d['x2'] = d['x'] + max(0, (d['width'] - d['height']) / 2)
+        if 'y1' not in d:
+            d['y1'] = d['y'] - max(0, (d['height'] - d['width']) / 2)
+        if 'y2' not in d:
+            d['y2'] = d['y'] + max(0, (d['height'] - d['width']) / 2)
+        if 'spacing' not in d:
+            d['spacing'] = self.process_space
+        if 'thickness' not in d:
+            d['thickness'] = d['width'] + self.process_ring * 2
+        if 'soldermask' not in d:
+            d['soldermask'] = self.process_soldermask
+        if 'clearance' not in d:
+            d['clearance'] = d['spacing'] / 2
+        if 'mask' not in d:
+            d['mask'] = d['thickness'] + d['soldermask'] * 2
+
+        self.items = self.items + [d]
+
+    # for silk layers
+    def line(self, d):
+        # we expect these to be provided
+        #
+        # x1            start of silk line
+        # y1
+        # x2            end of silk line
+        # y2
+        #
+        # the following are normally defaulted, but can be provided
+        #
+        # thickness     thickness of silk line
+
+        print("adding line")
+        d['type'] = 'line'
+
+        if 'thickness' not in d:
+            d['thickness'] = self.line_thickness
+
+        self.items = self.items + [d]
+
+    def arc(self, d):
+        # we expect these to be provided
+        #
+        # x             center of silk arc
+        # y
+        # width
+        # height
+        # startangle
+        # deltaangle
+        #
+        # the following are normally defaulted, but can be provided
+        #
+        # thickness     thickness of silk line
+
+        print("adding arc")
+        d['type'] = 'arc'
+
+        if 'thickness' not in d:
+            d['thickness'] = self.line_thickness
+
+        self.items = self.items + [d]
+
+    def write_pin_square(size):
+        print("       li:ps_poly {")
+        print("        %u mm" % -size / 2)
+        print("        %u mm" % -size / 2)
+        print("        %u mm" % size / 2)
+        print("        %u mm" % -size / 2)
+        print("        %u mm" % size / 2)
+        print("        %u mm" % size / 2)
+        print("        %u mm" % -size / 2)
+        print("        %u mm" % size / 2)
+        print("       }")
+
+    def write_pin_circle(size):
+        print("       ha:ps_circ {")
+        print("        x = 0")
+        print("        y = 0")
+        print("        dia = $u mm" % size)
+        print("       }")
+
+    def write_stack_shape(p, layer):
+        print("      ha:ps_shape_v4 {")
+        print("       clearance = %u mm" % p['clearance'])
+        if p['options'] == 'square':
+            write_pin_square(p['copper'])
+        else:
+            write_pin_circle(p['copper'])
+        print("       ha:layer_mask {")
+        print("        copper = 1")
+        print("        $s = 1" % layer)
+        print("       }")
+        print("      }")
 
     def emit_padstack_prototypes(self):
-       print("prototypes will go here")
+        # iterate over copper items creating min set of padstack prototypes,
+        # and annotating items with their prototype number as we go
+        protocount = 0
+        for i in self.items:
+            if i['type'] == 'pad':
+                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']
+            if i['type'] == 'slot':
+                myhash = i['type']+str(i['width'])+str(i['height'])+str(i['thickness'])+str(i['spacing'])+str(i['soldermask'])+str(i['clearance'])+str(i['mask'])
+            if i['type'] == 'pin':
+                myhash = i['type']+str(i['drill'])+str(i['ring'])+str(i['spacing'])+str(i['soldermask'])+str(i['clearance'])+str(i['mask'])+i['options']
 
-    def emit_padstacks(self):
-       print("padstacks will go here")
+            # print ("myhash is %s" % myhash)
+            if myhash in self.padstacks:
+                p = self.padstacks[myhash]
+                i['prototypenumber'] = p['prototypenumber']
+            else:
+                # print ("new hash!")
+                i['prototypenumber'] = protocount
+                protocount += 1
+                self.padstacks[myhash] = i
 
-    def emit_top_silk(self):
-       print("top_silk will go here")
+        # print ("%u unique prototypes found" % protocount)
+
+        # now that we have the set of prototypes, emit them
+        print("   li:padstack_prototypes {")
+        for thisp in self.padstacks:
+            p = self.padstacks[thisp]
+            if p['type'] == 'pin':
+                print("    ha:ps_proto_v4.%u {" % p['prototypenumber'])
+                print("     htop = 0")
+                print("     hbottom = 0")
+                print("     hplated = 1")
+                print("     hdia = %u mm" % p['drill'])
+                print("     li:shape {")
+                write_stack_shape(p, 'top')
+                write_stack_shape(p, 'bottom')
+                print("     }")
+                print("    }")
+
+            if p['type'] == 'pad':
+                print("    ha:ps_proto_v4.%u {" % p['prototypenumber'])
+                print("     htop = 0")
+                print("     hbottom = 0")
+                print("     hplated = 1")
+                print("     hdia = %u mm" % 0)         # bogus to the max
+                print("     li:shape {")
+                print("     }")
+                print("    }")
+
+            if p['type'] == 'slot':
+                # this is just a copy of the 'pad' code for now
+                print("    ha:ps_proto_v4.%u {" % p['prototypenumber'])
+                print("     htop = 0")
+                print("     hbottom = 0")
+                print("     hplated = 1")
+                print("     hdia = %u mm" % 0)         # bogus to the max
+                print("     li:shape {")
+                print("     }")
+                print("    }")
+
+        print("   }")
+
+    def emit_padstacks(self):
+        print("padstacks will go here")
+        # process pad, pin, slot items referencing relevant prototypes
+        print("%u items defined" % len(self.items))
+        print("----- start of defined items -----")
+        print(self.items)
+        print("----- end of defined items -----")
 
     def emit_line(self, role, id, x1, y1, x2, y2, unit):
-       print("      ha:line.%u {" % id)
-       print("       clearance = 0")
-       print("       thickness = 0")
-       print("       ha:attributes {")
-       print("        subc-role = %s" % role)
-       print("       }")
-       print("       x1 = %u%s" % (x1, unit))
-       print("       y1 = %u%s" % (y1, unit))
-       print("       x2 = %u%s" % (x2, unit))
-       print("       y2 = %u%s" % (y2, unit))
-       print("      }")
-       self.cnt = self.cnt + 1
+        print("      ha:line.%u {" % id)
+        print("       clearance = 0")
+        print("       thickness = 0")
+        print("       ha:attributes {")
+        print("        subc-role = %s" % role)
+        print("       }")
+        print("       x1 = %u%s" % (x1, unit))
+        print("       y1 = %u%s" % (y1, unit))
+        print("       x2 = %u%s" % (x2, unit))
+        print("       y2 = %u%s" % (y2, unit))
+        print("      }")
+        self.cnt = self.cnt + 1
+
+    def emit_top_silk(self):
+        print("top_silk will go here")
+        # process line and arc items
 
     def emit_bbox(self):
-       self.cnt = 0
-       print("    ha:subc-aux {")
-       print("     lid = 1")
-       print("     ha:type {")
-       print("      top = 1")
-       print("      misc = 1")
-       print("      virtual = 1")
-       print("     }")
-       print("     li:objects {")
-        self.emit_line("origin", self.cnt, 0, 0, 0, 0, "mm");
-        self.emit_line("x", self.cnt, 0, 0, 1, 0, "mm");
-        self.emit_line("y", self.cnt, 0, 0, 0, 1, "mm");
-       print("     }")
-       print("    }")
+        self.cnt = 0
+        print("    ha:subc-aux {")
+        print("     lid = 1")
+        print("     ha:type {")
+        print("      top = 1")
+        print("      misc = 1")
+        print("      virtual = 1")
+        print("     }")
+        print("     li:objects {")
+        self.emit_line("origin", self.cnt, 0, 0, 0, 0, "mm")
+        self.emit_line("x", self.cnt, 0, 0, 1, 0, "mm")
+        self.emit_line("y", self.cnt, 0, 0, 0, 1, "mm")
+        print("     }")
+        print("    }")
 
     def create_uuid(self):
-       return hashlib.md5(self.name).hexdigest()
+        return hashlib.md5(self.name.encode('utf-8')).hexdigest()
 
     def emit(self):
-       print("li:pcb-rnd-subcircuit-v4 {")
-       print(" ha:subc.0 {")
-       print("  ha:attributes {")
-       print("   description = %s" % self.description)
-       print("   dist_license = %s" % self.dist_license)
-       print("   use_license = %s" % self.use_license)
-       print("   author = %s" % self.author)
-       print("  }")
-       print("  uid = %s" % self.create_uuid())
-       print("  ha:data {")
-       self.emit_padstack_prototypes()
-       print("   li:objects {")
-       self.emit_padstacks()
-       print("   }")
-       print("   li:layers {")
-       self.emit_top_silk()
-       self.emit_bbox()
-       print("   }")
-       print("  }")
-       print(" }")
-       print("}")
+        print("li:pcb-rnd-subcircuit-v4 {")
+        print(" ha:subc.0 {")
+        print("  ha:attributes {")
+        print("   description = %s" % self.description)
+        print("   copyright = %s" % self.copyright)
+        print("   dist_license = %s" % self.dist_license)
+        print("   use_license = %s" % self.use_license)
+        print("  }")
+        print("  uid = %s" % self.create_uuid())
+        print("  ha:data {")
+        self.emit_padstack_prototypes()
+        print("   li:objects {")
+        self.emit_padstacks()
+        print("   }")
+        print("   li:layers {")
+        self.emit_top_silk()
+        self.emit_bbox()
+        print("   }")
+        print("  }")
+        print(" }")
+        print("}")
\ No newline at end of file