-#! /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
+# <something>.lht file(s).
+#
+# all dimensions are assumed to be in mm
-import math
-import sys
import hashlib
+def mils2mm(mils):
+ return 25.4 * (float(mils) / 1000)
+
class footprint(object):
- def __init__(self,
- output = sys.stdout):
- self.output = output
- 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
-
- def writeln(self, stuff):
- self.output.write(stuff + "\n")
+ def __init__(self):
+ 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):
- self.writeln("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):
- self.writeln("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):
- self.writeln("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):
- self.writeln(" ha:line.%u {" % id)
- self.writeln(" clearance = 0")
- self.writeln(" thickness = 0")
- self.writeln(" ha:attributes {")
- self.writeln(" subc-role = %s" % role)
- self.writeln(" }")
- self.writeln(" x1 = %u%s" % (x1, unit))
- self.writeln(" y1 = %u%s" % (y1, unit))
- self.writeln(" x2 = %u%s" % (x2, unit))
- self.writeln(" y2 = %u%s" % (y2, unit))
- self.writeln(" }")
- 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
- self.writeln(" ha:subc-aux {")
- self.writeln(" lid = 1")
- self.writeln(" ha:type {")
- self.writeln(" top = 1")
- self.writeln(" misc = 1")
- self.writeln(" virtual = 1")
- self.writeln(" }")
- self.writeln(" 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");
- self.writeln(" }")
- self.writeln(" }")
+ 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):
- self.writeln("li:pcb-rnd-subcircuit-v4 {")
- self.writeln(" ha:subc.0 {")
- self.writeln(" ha:attributes {")
- self.writeln(" description = %s" % self.description)
- self.writeln(" dist_license = %s" % self.dist_license)
- self.writeln(" use_license = %s" % self.use_license)
- self.writeln(" author = %s" % self.author)
- self.writeln(" }")
- self.writeln(" uid = %s" % self.create_uuid())
- self.writeln(" ha:data {")
- self.emit_padstack_prototypes()
- self.writeln(" li:objects {")
- self.emit_padstacks()
- self.writeln(" }")
- self.writeln(" li:layers {")
- self.emit_top_silk()
- self.emit_bbox()
- self.writeln(" }")
- self.writeln(" }")
- self.writeln(" }")
- self.writeln("}")
+ 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