#!/usr/bin/env python # This program is licensed under the GNU GPL. See gpl.txt for full license. # Copyright 2007 Cory Cross. All rights reserved. from string import atof def myatof(string_in): if(string_in==''): return 0.0 return atof(string_in) class FpGenerator: # save method: override this if you wanted to, for example, insert the footprint # into a database instead of into the filesystem def save( self ): f = open( self.SAVEDIR + self.filename, 'w' ) f.write(self.COPYRIGHT) f.write(self.LICENSE) f.write( self.element ) f.close() if __name__ == '__main__' or self.PrintSavedFiles: import sys sys.stderr.write('Saved to ' + self.SAVEDIR + self.filename + '\n' ) # getMilth method: converts a given unit system to PCB's 1/100th of a mil def getMilth( self, value ): if ( self.system == 'inch' ): return int(value * 100000) elif ( self.system == 'mm' ): return int(value * 3937.0079) elif ( self.system == 'mil' ): return int(value * 100) # smt_dip method: saves the footprint data in the attribute 'self.element'; # saves the requested filename in the attribute 'self.filename' # If you're looking to write your own generator method, I recommend starting # with this one as opposed to the smt_polarized_cap generator because I # learned a lot writing that one, making this one ten times better! Even # with error checking it ended up being around the same length. # This function is called once for each of the data lines. I previously had # this method called once per file, so you could add custom attributes that'd # be common across all the lines in the file, but I've decided that verbosity # is not a vice and you can write another script if you wish to automatically # copy a given dataset N times. I did! def smt_dip(self, line): ##Form is: ##Prefix`Description`number_of_pins`interior_outline?(blank means exterior outline)`pad_height`pad_width`inter_pad_spacing`cross_pad_spacing ll = line.split('`') try: (pad_height, pad_width, inter_pad_spacing, cross_pad_spacing) = [self.getMilth(atof(inval.strip())) for inval in ll[4:]] except ValueError, e: print "Wrong number of arguments, got:", ll, '\n', e return False if(pad_height >= inter_pad_spacing+1000): print "Warning: The pads will smash together when the pad height is >=~ the length between the pad centers!", ll[0] + ll[2] self.filename = ll[0] + ll[2] + '.fp' number_of_pins = int(atof(ll[2])) interior_silkscreen_p = ll[3] if( number_of_pins % 2 != 0 ): print "Hey! Only even number of pins, please. Aborting construction", ll[0] + ll[2] return False #Set up the beginning of the element string, with description # and mark point of (0,0) self.element = 'Element["" "' + ll[1] + '" "U0" "" 0 0 ' #Calculate where to place the text, append to element string, and add rest of header self.element += str( -cross_pad_spacing/2 ) + ' ' + str( int(-inter_pad_spacing*(number_of_pins/4.0)) - pad_height/2 - 2000 ) + ' 0 100 ""]\n(' ##Generate pads (square) ph = pad_header = '\n\tPad[' if( pad_height > pad_width ): print "Higher than wider pads not yet programmed!", ll[0] + ll[2] pad_gap = pad_width/2 - pad_height/2 def draw_pad( x_coord, y_coord, count ): self.element += ph + ' '.join([str(i) for i in (x_coord-pad_gap, y_coord, x_coord+pad_gap, y_coord, pad_height, 1200, pad_height+1200)]) + ' "" "'+str(count)+'" "square"]' x_initial_offset = cross_pad_spacing/2 if( (number_of_pins/2) % 2 == 0): y_initial_offset = int(inter_pad_spacing*(.5 + number_of_pins/4 - 1)) else: y_initial_offset = int(inter_pad_spacing*( (number_of_pins-2)/4)) for pin_number in range(0,number_of_pins/2): draw_pad( -x_initial_offset, -y_initial_offset + pin_number*inter_pad_spacing, pin_number+1 ) draw_pad( x_initial_offset, -y_initial_offset + pin_number*inter_pad_spacing, number_of_pins-pin_number ) ##Generate silkscreen outline sh = silk_header = '\n\tElementLine [' silk_line_space = 2000 if( interior_silkscreen_p ): x_silk = x_initial_offset - silk_line_space - pad_width/2 y_silk = y_initial_offset + pad_height/2 else: #Calculate exterior points... print "Need to Calculate exterior points..." return 0 pass #Go and draw it for xm1,ym1 in [[1,1],[-1,-1]]: for xm2,ym2 in [[-1,1],[1,-1]]: self.element += sh + ' '.join([str(int(i)) for i in (xm1*x_silk, ym1*y_silk, xm2*x_silk, ym2*y_silk, 1000)]) + ']' #Draw the pin1 notch for xm1 in [1,-1]: self.element += sh + ' '.join([str(int(i)) for i in (xm1*x_silk, -y_silk, 0, -y_silk+2000, 1000)]) + ']' #Ending self.element += '\n)\n' #Successful creation! return True def smt_polarized_cap(self, line): ll = line.split('`') (diameter, height, width, length, pin_to_pin_length, pad_width, pin_spacing_length) = [self.getMilth(atof(inval.strip())) for inval in ll[2:]] #For these descriptions, point of view is looking down at the component on # a board sitting on a flat table, positive end facing you #diameter=diameter of the can #height=the dimension you can't see from this POV; how high the part comes off the board #width=dimension of part left-to-right #length=dimsion of part up-to-down self.filename = ll[0] + '.fp' #Set up the beginning of the element string, with description # and mark point of (0,0) self.element = 'Element["" "' + ll[1] + '" "" "" 0 0 ' #Calculate where to place the text, append to element string, and add rest of header self.element += str( -10000 - pin_spacing_length ) + ' ' + str( 4000 + width/2 ) + ' 0 100 ""]\n(' #Generate pads (square) ph = pad_header = '\n\tPad[' extra_pad_length = 4000 pad_length = extra_pad_length + pin_to_pin_length/2 - pin_spacing_length/2 if(pad_width > pad_length): x_coord = pad_length/2 + pin_spacing_length/2 y_coord = pad_width/2 - pad_length/2 x2_coord = x_coord else: x_coord = pin_to_pin_length/2 + extra_pad_length - pad_width/2 x2_coord = pin_spacing_length/2 + pad_width/2 y_coord = 0 pad_trace_width = min(pad_length,pad_width) self.element += ph + ' '.join([str(i) for i in (-x_coord, y_coord, -x2_coord, -y_coord, pad_trace_width, 1200, pad_trace_width+1200)]) + ' "Positive" "1" "square"]' self.element += ph + ' '.join([str(i) for i in (x_coord, y_coord, x2_coord, -y_coord, pad_trace_width, 1200, pad_trace_width+1200)]) + ' "Negative" "2" "square"]' #Generate silkscreen outline sh = silk_header = '\n\tElementLine [' silk_line_space = 2000 #end lines (left & right) def draw_line( x1_coord, y1_coord, x2_coord, y2_coord, X_MIRROR=False, Y_MIRROR=False, WIDTH=1000 ): def do_line( xmul, ymul ): self.element += sh + ' '.join([str(int(i)) for i in (xmul*x1_coord,ymul*y1_coord, xmul*x2_coord,ymul*y2_coord, WIDTH)]) + ']' if( X_MIRROR and not Y_MIRROR ): for x in (1, -1): do_line(x, 1) elif( not X_MIRROR and Y_MIRROR ): for y in (1, -1): do_line(1, y) elif( X_MIRROR and Y_MIRROR ): for x in (1, -1): for y in (1, -1): do_line(x, y) else: do_line(1,1) def draw_centered_vert_line( x_coord, length ): draw_line( x_coord, length/2, x_coord, -length/2 ) def draw_centered_vert_line_with_middle_gap( x_coord, length, gap ): if( gap > length): (gap, length) = (length, gap) draw_line( x_coord, length/2, x_coord, gap/2, Y_MIRROR=True ) x_outside = length/2 + silk_line_space ##Left line, inside_mitered_corner_width = .9*width draw_centered_vert_line_with_middle_gap( -x_outside, inside_mitered_corner_width, pad_width ) ##Mitered corners mitered_length_inset = -.90*(length/2) draw_line( -x_outside,inside_mitered_corner_width/2, mitered_length_inset, width/2+silk_line_space, Y_MIRROR=True ) ##Sides draw_line( mitered_length_inset, width/2+silk_line_space, x_outside, width/2+silk_line_space, Y_MIRROR=True ) ##Negative End draw_centered_vert_line_with_middle_gap( x_outside, width+2*silk_line_space, pad_width) #The outside negative bar draw_line((pin_to_pin_length/2+extra_pad_length)+silk_line_space+1000, .5*(width/2+silk_line_space), (pin_to_pin_length/2+extra_pad_length)+silk_line_space+1000, -.5*(width/2+silk_line_space), WIDTH=2000) #Ending self.element += '\n)\n' #Successful creation! return True def through_hole_connector(self, line): ##Form is: ##Prefix`Description`number_of_pins`right_angle(V or RA)`rows(#)`numbering_scheme('d' for DIP, 'r' for Ribbon Cable, 'm' for Molex, or 'D' for each row to share a number)`reversed_numbering(blank is false)`hole_diameter`pad_diameter`inter_pad_spacing`cross_pad_spacing(if multiple row)`silk_x_dimension`silk_y_dimension`dimension_from_bottom_silk_edge_to_bottom_pincenter`peg_offset_x`peg_offset_y`peg_hole`where_pegs ##dimension_along_connector_path is the y direction ##where_pegs is a comma-deliminated list of compass directions i.e. NW,NE will place pegs in the upper-left and upper-right locations if(line[0] == '#'): return False ll = line.split('`') try: (hole_diameter, pad_diameter, inter_pad_spacing, cross_pad_spacing,silk_x_dimension,silk_y_dimension,dimension_from_bottom_silk_edge_to_bottom_pincenter,peg_offset_x,peg_offset_y,peg_hole) = [self.getMilth(myatof(inval.strip())) for inval in ll[7:17]] except ValueError, e: print "Wrong number of arguments, got:", ll, '\n', e return False prefix = ll[0] description = ll[1] number_of_pins = int(atof(ll[2])) right_angle_p = lambda : (ll[3]=='RA') rows = int(atof(ll[4])) numbering_scheme = ll[5] reversed_numbering = ll[6] where_pegs = ll[17] pegs_p = lambda : (not (where_pegs=='')) if(pad_diameter >= inter_pad_spacing+1000 or (pad_diameter >= cross_pad_spacing+1000 and rows>1) ): print "Warning: The pads will smash together when the pad diameter is larger than the pin spacing + a tolerance!", ll[0] + ll[2] if( numbering_scheme != 'd' and numbering_scheme != 'r' and numbering_scheme != 'm' and numbering_scheme != 'D' ): print 'Defaulting to Molex numbering scheme ', ll[0] + ll[2] numbering_scheme = 'm' self.filename = prefix + '-' + ll[3] + '-' + str(number_of_pins) + numbering_scheme + '.fp' if( number_of_pins % rows != 0 ): print "Hey! Must have a rectangular number of pins, please. Aborting construction", prefix + str(number_of_pins) return False self.element = '' #Set up the beginning of the element string, with description # and mark point of (0,0) elementheader = 'Element["" "' + description + '" "CONN" "" 0 0 ' ##Generate pads (square) ph = pin_header = '\n\tPin[' def draw_pin( x_coord, y_coord, number, hole=False ): if( reversed_numbering == '' and number.__class__ == int): number = number_of_pins - number + 1 self.element += ph + ' '.join([str(i) for i in (x_coord,y_coord, pad_diameter, 2400, pad_diameter+1000, hole_diameter)]) + ' "" "'+str(number)+'" ' a = [] if(number==1 or number=='1'): a.append('square') if(hole): a.append('hole') self.element += '"' + ','.join(a) + '"]' num_cols = number_of_pins/rows if(num_cols%2 == 0): first_x_coord = int(-(.5 + (num_cols/2-1))*inter_pad_spacing) else: #odd first_x_coord = -(num_cols-1)/2*inter_pad_spacing last_x_coord = -first_x_coord if(rows%2 == 0): first_y_coord = int(-(.5 + (rows/2-1))*cross_pad_spacing) else: #odd first_y_coord = -(rows-1)/2*cross_pad_spacing last_y_coord = -first_y_coord if( numbering_scheme == 'm' ): for row in range(rows): number_offset = num_cols*row y = first_y_coord + row*cross_pad_spacing for col in range(num_cols): x = first_x_coord + col*inter_pad_spacing draw_pin( x, y, number_offset+col+1 ) elif( numbering_scheme == 'r' ): for col in range(num_cols): number_offset = rows*(num_cols-1-col) x = first_x_coord + col*inter_pad_spacing for row in range(rows): y = first_y_coord + row*cross_pad_spacing draw_pin( x, y, number_offset+row+1 ) elif( numbering_scheme == 'd' ): for row in range(rows): number_offset = num_cols*row y = first_y_coord + row*cross_pad_spacing for col in range(num_cols): x = first_x_coord + col*inter_pad_spacing if( row % 2 == 0): draw_pin( x, y, number_offset+col+1 ) else: draw_pin( x, y, number_offset+(num_cols-col) ) elif( numbering_scheme == 'D' ): for row in range(rows): y = first_y_coord + row*cross_pad_spacing for col in range(num_cols): x = first_x_coord + col*inter_pad_spacing draw_pin( x, y, str(col+1) ) ##Generate Pegs for place in where_pegs.split(','): if( len(place) == 0 ): continue plen = len(place) > 1 if( place[0] == 'N' ): y = first_y_coord - peg_offset_y if(plen and place[1] == 'E'): x = last_x_coord + peg_offset_x elif(plen and place[1] == 'W'): x = first_x_coord - peg_offset_x else: x = 0 elif( place[0] == 'S' ): y = last_y_coord + peg_offset_y if(plen and place[1] == 'E'): x = last_x_coord + peg_offset_x elif(plen and place[1] == 'W'): x = first_x_coord - peg_offset_x else: x = 0 elif( place[0] == 'E' ): x = last_x_coord + peg_offset_x y = 0 elif( place[0] == 'W' ): x = first_x_coord - peg_offset_x y = 0 else: print "Invalid peg directions ", ll[0] + ll[2] return False draw_pin(x,y,'',hole=True) ##Generate silkscreen outline sh = silk_header = '\n\tElementLine [' silk_line_space = 2000 y_silk_bottom = last_y_coord + dimension_from_bottom_silk_edge_to_bottom_pincenter #Go and draw it for ya in (0,-silk_y_dimension): self.element += sh + ' '.join([str(int(i)) for i in (-silk_x_dimension/2, ya+y_silk_bottom, silk_x_dimension/2, ya+y_silk_bottom, 1000)]) + ']' for x_silk in (-silk_x_dimension/2,silk_x_dimension/2): self.element += sh + ' '.join([str(int(i)) for i in (x_silk, y_silk_bottom, x_silk, y_silk_bottom-silk_y_dimension, 1000)]) + ']' #Calculate where to place the text, append to element string, and add rest of header elementheader += str( -cross_pad_spacing/2 ) + ' ' + str(y_silk_bottom-silk_y_dimension-10000 ) + ' 0 100 ""]\n(' self.element = elementheader + self.element #Ending self.element += '\n)\n' #Successful creation! return True def through_hole_capacitor(self, line): ##Form is: ##square caps: Prefix`Description`polarized?('p' or 'n')`'square'(no quotes)`pin_diameter`pin_copper_diameter`pin_spacing`silk_outline_length`silk_outline_width ##round caps: Prefix`Description`polarized?('p' or 'n')`'round' (no quotes)`pin_diameter`pin_copper_diameter`pin_spacing`silk_outline_diameter if( line[0] == '#' ): return False ll = line.split('`') prefix = ll[0] description = ll[1] polarized = ll[2] shape = ll[3] self.filename = prefix + '.fp' if( polarized != 'p' ): polarized = 'n' (pin_diameter, pin_copper_diameter, pin_spacing)= [self.getMilth(myatof(inval.strip())) for inval in ll[4:7]] if( shape == 'square' ): (silk_outline_length, silk_outline_width) = [self.getMilth(myatof(inval.strip())) for inval in ll[7:9]] elif( shape == 'round' ): silk_outline_diameter = self.getMilth(myatof(ll[7].strip())) else: print "Capacitor must be square or round;", ll[0] + ll[2] return False self.element = 'Element["" "' + ll[1] + '" "" "" 0 0 ' self.element += '0' + ' ' + '0' + ' 0 100 ""]\n(' ph = pin_header = '\n\tPin[' def draw_pin( x_coord, y_coord, number, hole=False ): self.element += ph + ' '.join([str(i) for i in (x_coord,y_coord, pin_copper_diameter, 1200, pin_copper_diameter+1000, pin_diameter)]) + ' "" "'+str(number)+'" ' a = [] if(number==1 and polarized == 'p'): a.append('square') if(hole): a.append('hole') self.element += '"' + ','.join(a) + '"]' count = 0 for x in [-pin_spacing/2,pin_spacing/2]: count = count + 1 draw_pin(x,0,count) ## Silk screen outline and polarized sign... sh = silk_header = '\n\tElementLine [' silk_line_space = 2000 #end lines (left & right) def draw_line( x1_coord, y1_coord, x2_coord, y2_coord, X_MIRROR=False, Y_MIRROR=False, WIDTH=1000 ): def do_line( xmul, ymul ): self.element += sh + ' '.join([str(int(i)) for i in (xmul*x1_coord,ymul*y1_coord, xmul*x2_coord,ymul*y2_coord, WIDTH)]) + ']' if( X_MIRROR and not Y_MIRROR ): for x in (1, -1): do_line(x, 1) elif( not X_MIRROR and Y_MIRROR ): for y in (1, -1): do_line(1, y) elif( X_MIRROR and Y_MIRROR ): for x in (1, -1): for y in (1, -1): do_line(x, y) else: do_line(1,1) if( shape == 'square' ): draw_line(-silk_outline_length/2,silk_outline_width/2,silk_outline_length/2,silk_outline_width/2,Y_MIRROR=True) draw_line(silk_outline_length/2,-silk_outline_width/2,silk_outline_length/2,silk_outline_width/2,X_MIRROR=True) else: print "Circular outlines not programmed yet..." return False if( polarized == 'p' ): print "Polarization symbol not programmed yet..." return False #Ending self.element += '\n)\n' #Successful creation! return True def load( self, fileptr ): try: self.system = fileptr.readline().split('=')[1][:-1] func = getattr(self, fileptr.readline().split('=')[1][:-1]) except Exception,e: print "Missing either system= or function= at the beginning of the file" print e sys.exit(-1) for line in fileptr.readlines(): if(func(line)): self.save() #Need trailing / SAVEDIR="/home/cory/gaf/pcb-elements/" LICENSE=""" # This symbol is covered under the GNU GPL, with the following exception. # See the file gpl.txt for the full license. #As a special exception, if you create a design which uses this footprint, # and embed this footprint or unaltered portions of this footprint into the # design, this footprint does not by itself cause the resulting design to # be covered by the GNU General Public License. This exception does not # however invalidate any other reasons why the design itself might be # covered by the GNU General Public License. If you modify this # footprint, you may extend this exception to your version of the # footprint, but you are not obligated to do so. If you do not # wish to do so, delete this exception statement from your version. """ COPYRIGHT="#Copyright 2007 Cory Cross\n" PrintSavedFiles=False if __name__ == '__main__': import sys #Need trailing / FpGenerator.SAVEDIR="/tmp/" n = FpGenerator() n.load(sys.stdin)