X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=packages%2Ffplht.py;h=8d17148c548d9fb3aee32c4e5d4db28326b05da3;hb=d07e07f49396ba648f34d61dfb31c8cb595419a1;hp=e169792880c7d7a841b70cf1e23919415e232e49;hpb=c666c032c1ed88c2748ba5f56b13887e62fb2b36;p=hw%2Faltusmetrum diff --git a/packages/fplht.py b/packages/fplht.py index e169792..8d17148 100644 --- a/packages/fplht.py +++ b/packages/fplht.py @@ -1,111 +1,398 @@ -#! /usr/bin/python -# Copyright 2020 by Bdale Garbee . GPLv3 - +#! /usr/bin/python3 # Python library to assist in generating lihata format footprints for pcb-rnd +# Copyright 2020 by Bdale Garbee +# +# 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 # .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 " - 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