-#! /usr/bin/python
+#! /usr/bin/python3
#
# generate lihata footprint for 4ucon 11071 tabbed micro USB connector
# Copyright 2020 by Bdale Garbee <bdale@gag.com>
import math
from fplht import *
+# dimensions taken from 4ucon part drawing
+# note that we define Y origin to be the board edge, not center mass
+
+pad_width = 0.4
+pad_height = 1.35
+pad_spacing = 0.65
+pad_y = 1.45 + 3.35 - pad_height/2
+
+tab_width = 1.9
+tab_height = 1.9
+tab_x = 0.2 + tab_width / 2
+tab_y = 1.45
+
+slot_x = 6.40 / 2
+slot_y = 1.45
+slot_width = 0.45
+slot_height = 1.55
+slot_copper = (2.15 - 1.55) / 2
+
+wing_width = 1.6
+wing_height = 1.4
+wing_x = 3.2
+wing_y = 1.45 + 2.25
+
fp = footprint()
fp.name = "11071"
fp.description = "4ucon 11071 tabbed micro USB connector"
+fp.pad({'x' : pad_spacing* 2, 'y' : pad_y, 'width' : pad_width, 'height' : pad_height, 'name' : 'VBUS', 'number' : '1'})
+fp.pad({'x' : pad_spacing* 1, 'y' : pad_y, 'width' : pad_width, 'height' : pad_height, 'name' : 'D-', 'number' : '2'})
+fp.pad({'x' : pad_spacing* 0, 'y' : pad_y, 'width' : pad_width, 'height' : pad_height, 'name' : 'D+', 'number' : '3'})
+fp.pad({'x' : pad_spacing* -1, 'y' : pad_y, 'width' : pad_width, 'height' : pad_height, 'name' : 'HS', 'number' : '4'})
+fp.pad({'x' : pad_spacing* -2, 'y' : pad_y, 'width' : pad_width, 'height' : pad_height, 'name' : 'GND', 'number' : '5'})
+
+# mounting "wings", or tabs next to the signal pads
+
+fp.pad({'x' : -wing_x, 'y' : wing_y, 'width' : wing_width, 'height' : wing_height, 'spacing' : 0, 'name' : "", 'number' : "G"})
+fp.pad({'x' : wing_x, 'y' : wing_y, 'width' : wing_width, 'height' : wing_height, 'spacing' : 0, 'name' : "", 'number' : "G"})
+
+# mounting tabs under package
+
+fp.pad({'x' : -tab_x, 'y' : tab_y, 'width' : tab_width, 'height' : tab_height, 'spacing' : 0, 'name' : "", 'number' : "G"})
+fp.pad({'x' : tab_x, 'y' : tab_y, 'width' : tab_width, 'height' : tab_height, 'spacing' : 0, 'name' : "", 'number' : "G"})
+
+# slots for through-hole mounting tabs
+fp.slot({'x' : -slot_x, 'y' : slot_y, 'width' : slot_width, 'height' : slot_height, 'thickness' : slot_width + slot_copper * 2, 'name' : 'GND', 'number' : 5})
+fp.slot({'x' : slot_x, 'y' : slot_y, 'width' : slot_width, 'height' : slot_height, 'thickness' : slot_width + slot_copper * 2, 'name' : 'GND', 'number' : 5})
+
fp.emit()
-#! /usr/bin/python
+#! /usr/bin/python3
# Python library to assist in generating lihata format footprints for pcb-rnd
# Copyright 2020 by Bdale Garbee <bdale@gag.com>
#
# 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():
+ self.name = ""
+ self.description = ""
+ self.dist_license = "GPLv3"
+ self.use_license = "Unlimited"
+ self.author = "Bdale Garbee <bdale@gag.com>"
+ 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():
+ # 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():
- # x1, y1, x2, y2, thickness
- # def arc():
- # x, y, width, height, startangle, deltaangle, thickness
+ def line():
+ # 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():
+ # 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_pin_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(" }")
+
+ 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.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(" }")
+ 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(" 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("}")