def writeBoundingBox(fid, OriginX, OriginY, MaxXExtent, MaxYExtent): x = util.in2gerb(OriginX) y = util.in2gerb(OriginY) X = util.in2gerb(MaxXExtent) Y = util.in2gerb(MaxYExtent) makestroke.drawPolyline(fid, [(x, y), (X, y), (X, Y), (x, Y), (x, y)], 0, 0)
def writeBoundingBox(fid, OriginX, OriginY, MaxXExtent, MaxYExtent): x = util.in2gerb(OriginX) y = util.in2gerb(OriginY) X = util.in2gerb(MaxXExtent) Y = util.in2gerb(MaxYExtent) makestroke.drawPolyline(fid, [(x,y), (X,y), (X,Y), (x,Y), (x,y)], 0, 0)
def writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent, xspacing, yspacing): # For each job, write out 4 score lines, above, to the right, below, and # to the left. After we collect all potential scoring lines, we worry # about merging, etc. dx = xspacing / 2.0 dy = yspacing / 2.0 extents = (OriginX, OriginY, MaxXExtent, MaxYExtent) Lines = [] for layout in Place.jobs: x = layout.x - dx y = layout.y - dy X = layout.x + layout.width_in() + dx Y = layout.y + layout.height_in() + dy # Just so we don't get 3.75000000004 and 3.75000000009, we round to # 2.5 limits. x, y, X, Y = [round(val, 5) for val in [x, y, X, Y]] addHorizontalLine(Lines, OriginX, MaxXExtent, Y, extents) # above job addVerticalLine(Lines, X, OriginY, MaxYExtent, extents) # to the right of job addHorizontalLine(Lines, OriginX, MaxXExtent, y, extents) # below job addVerticalLine(Lines, x, OriginY, MaxYExtent, extents) # to the left of job # Combine disparate lines into single lines Lines = mergeLines(Lines) # Write 'em out for line in Lines: makestroke.drawPolyline(fid, [(util.in2gerb(line[0]), util.in2gerb(line[1])), (util.in2gerb(line[2]), util.in2gerb(line[3]))], 0, 0)
def writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent, xspacing, yspacing): # For each job, write out 4 score lines, above, to the right, below, and # to the left. After we collect all potential scoring lines, we worry # about merging, etc. dx = xspacing / 2.0 dy = yspacing / 2.0 extents = (OriginX, OriginY, MaxXExtent, MaxYExtent) Lines = [] for layout in Place.jobs: x = layout.x - dx y = layout.y - dy X = layout.x + layout.width_in() + dx Y = layout.y + layout.height_in() + dy # Just so we don't get 3.75000000004 and 3.75000000009, we round to # 2.5 limits. x, y, X, Y = [round(val, 5) for val in [x, y, X, Y]] addHorizontalLine(Lines, OriginX, MaxXExtent, Y, extents) # above job addVerticalLine(Lines, X, OriginY, MaxYExtent, extents) # to the right of job addHorizontalLine(Lines, OriginX, MaxXExtent, y, extents) # below job addVerticalLine(Lines, x, OriginY, MaxYExtent, extents) # to the left of job # Combine disparate lines into single lines Lines = mergeLines(Lines) # Write 'em out for line in Lines: makestroke.drawPolyline( fid, [(util.in2gerb(line[0]), util.in2gerb(line[1])), (util.in2gerb(line[2]), util.in2gerb(line[3]))], 0, 0)
def writeDimensionArrow(fid, OriginX, OriginY, MaxXExtent, MaxYExtent): x = util.in2gerb(OriginX) y = util.in2gerb(OriginY) X = util.in2gerb(MaxXExtent) Y = util.in2gerb(MaxYExtent) # This constant is how far away from the board the centerline of the dimension # arrows should be, in inches. dimspace = 0.2 # Convert it to Gerber (0.00001" or 2.5) units dimspace = util.in2gerb(dimspace) # Draw an arrow above the board, on the left side and right side makestroke.drawDimensionArrow(fid, x, Y + dimspace, makestroke.FACING_LEFT) makestroke.drawDimensionArrow(fid, X, Y + dimspace, makestroke.FACING_RIGHT) # Draw arrows to the right of the board, at top and bottom makestroke.drawDimensionArrow(fid, X + dimspace, Y, makestroke.FACING_UP) makestroke.drawDimensionArrow(fid, X + dimspace, y, makestroke.FACING_DOWN) # Now draw the text. First, horizontal text above the board. s = "{:.3f}\"".format(MaxXExtent - OriginX) ll, ur = makestroke.boundingBox(s, 0, 0) s_width = ur[0] - ll[0] # Width in 2.5 units s_height = ur[1] - ll[1] # Height in 2.5 units # Compute the position in 2.5 units where we should draw this. It should be # centered horizontally and also vertically about the dimension arrow centerline. posX = x + (x + X) / 2 posX -= s_width / 2 posY = Y + dimspace - s_height / 2 makestroke.writeString(fid, s, posX, posY, 0) # Finally, draw the extending lines from the text to the arrows. posY = Y + dimspace posX1 = posX - util.in2gerb(0.1) # 1000 posX2 = posX + s_width + util.in2gerb(0.1) # 1000 makestroke.drawLine(fid, x, posY, posX1, posY) makestroke.drawLine(fid, posX2, posY, X, posY) # Now do the vertical text s = "{:.3f}\"".format(MaxYExtent - OriginY) ll, ur = makestroke.boundingBox(s, 0, 0) s_width = ur[0] - ll[0] s_height = ur[1] - ll[1] # As above, figure out where to draw this. Rotation will be -90 degrees # so new origin will be top-left of bounding box after rotation. posX = X + dimspace - s_height / 2 posY = y + (y + Y) / 2 posY += s_width / 2 makestroke.writeString(fid, s, posX, posY, -90) # Draw extending lines posX = X + dimspace posY1 = posY + util.in2gerb(0.1) # 1000 posY2 = posY - s_width - util.in2gerb(0.1) # 1000 makestroke.drawLine(fid, posX, Y, posX, posY1) makestroke.drawLine(fid, posX, posY2, posX, y)
def writeDimensionArrow(fid, OriginX, OriginY, MaxXExtent, MaxYExtent): x = util.in2gerb(OriginX) y = util.in2gerb(OriginY) X = util.in2gerb(MaxXExtent) Y = util.in2gerb(MaxYExtent) # This constant is how far away from the board the centerline of the dimension # arrows should be, in inches. dimspace = 0.2 # Convert it to Gerber (0.00001" or 2.5) units dimspace = util.in2gerb(dimspace) # Draw an arrow above the board, on the left side and right side makestroke.drawDimensionArrow(fid, x, Y + dimspace, makestroke.FacingLeft) makestroke.drawDimensionArrow(fid, X, Y + dimspace, makestroke.FacingRight) # Draw arrows to the right of the board, at top and bottom makestroke.drawDimensionArrow(fid, X + dimspace, Y, makestroke.FacingUp) makestroke.drawDimensionArrow(fid, X + dimspace, y, makestroke.FacingDown) # Now draw the text. First, horizontal text above the board. s = '%.3f"' % (MaxXExtent - OriginX) ll, ur = makestroke.boundingBox(s, 0, 0) s_width = ur[0] - ll[0] # Width in 2.5 units s_height = ur[1] - ll[1] # Height in 2.5 units # Compute the position in 2.5 units where we should draw this. It should be # centered horizontally and also vertically about the dimension arrow centerline. posX = x + (x + X) / 2 posX -= s_width / 2 posY = Y + dimspace - s_height / 2 makestroke.writeString(fid, s, posX, posY, 0) # Finally, draw the extending lines from the text to the arrows. posY = Y + dimspace posX1 = posX - util.in2gerb(0.1) # 1000 posX2 = posX + s_width + util.in2gerb(0.1) # 1000 makestroke.drawLine(fid, x, posY, posX1, posY) makestroke.drawLine(fid, posX2, posY, X, posY) # Now do the vertical text s = '%.3f"' % (MaxYExtent - OriginY) ll, ur = makestroke.boundingBox(s, 0, 0) s_width = ur[0] - ll[0] s_height = ur[1] - ll[1] # As above, figure out where to draw this. Rotation will be -90 degrees # so new origin will be top-left of bounding box after rotation. posX = X + dimspace - s_height / 2 posY = y + (y + Y) / 2 posY += s_width / 2 makestroke.writeString(fid, s, posX, posY, -90) # Draw extending lines posX = X + dimspace posY1 = posY + util.in2gerb(0.1) # 1000 posY2 = posY - s_width - util.in2gerb(0.1) # 1000 makestroke.drawLine(fid, posX, Y, posX, posY1) makestroke.drawLine(fid, posX, posY2, posX, y)
def writeDrillLegend(fid, Tools, OriginY, MaxXExtent): # This is the spacing from the right edge of the board to where the # drill legend is to be drawn, in inches. Remember we have to allow # for dimension arrows, too. dimspace = 0.5 # inches # This is the spacing from the drill hit glyph to the drill size # in inches. glyphspace = 0.1 # inches # Convert to Gerber 2.5 units dimspace = util.in2gerb(dimspace) glyphspace = util.in2gerb(glyphspace) # Construct a list of tuples (toolSize, toolNumber) where toolNumber # is the position of the tool in Tools and toolSize is in inches. L = [] toolNumber = -1 for tool in Tools: toolNumber += 1 L.append((config.GlobalToolMap[tool], toolNumber)) # Now sort the list from smallest to largest L.sort() # And reverse to go from largest to smallest, so we can write the legend # from the bottom up L.reverse() # For each tool, draw a drill hit marker then the size of the tool # in inches. posY = util.in2gerb(OriginY) posX = util.in2gerb(MaxXExtent) + dimspace maxX = 0 for size, toolNum in L: # Determine string to write and midpoint of string s = '%.3f"' % size ll, ur = makestroke.boundingBox( s, posX + glyphspace, posY) # Returns lower-left point, upper-right point midpoint = (ur[1] + ll[1]) / 2 # Keep track of maximum extent of legend maxX = max(maxX, ur[0]) makestroke.drawDrillHit(fid, posX, midpoint, toolNum) makestroke.writeString(fid, s, posX + glyphspace, posY, 0) posY += int(round((ur[1] - ll[1]) * 1.5)) # Return value is lower-left of user text area, without any padding. return maxX, util.in2gerb(OriginY)
def writeOutline(fid, OriginX, OriginY, MaxXExtent, MaxYExtent): # Write width-1 aperture to file AP = aptable.Aperture(aptable.Circle, 'D10', 0.001) AP.writeDef(fid) # Choose drawing aperture D10 fid.write('D10*\n') # Draw the rectangle fid.write('X%07dY%07dD02*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(MaxYExtent))) # Top-left fid.write('X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(MaxYExtent))) # Top-right fid.write('X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(OriginY))) # Bottom-right fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left
def writeDrillLegend(fid, Tools, OriginY, MaxXExtent): # This is the spacing from the right edge of the board to where the # drill legend is to be drawn, in inches. Remember we have to allow # for dimension arrows, too. dimspace = 0.5 # inches # This is the spacing from the drill hit glyph to the drill size # in inches. glyphspace = 0.1 # inches # Convert to Gerber 2.5 units dimspace = util.in2gerb(dimspace) glyphspace = util.in2gerb(glyphspace) # Construct a list of tuples (toolSize, toolNumber) where toolNumber # is the position of the tool in Tools and toolSize is in inches. L = [] toolNumber = -1 for tool in Tools: toolNumber += 1 L.append((config.GlobalToolMap[tool], toolNumber)) # Now sort the list from smallest to largest L.sort() # And reverse to go from largest to smallest, so we can write the legend # from the bottom up L.reverse() # For each tool, draw a drill hit marker then the size of the tool # in inches. posY = util.in2gerb(OriginY) posX = util.in2gerb(MaxXExtent) + dimspace maxX = 0 for size, toolNum in L: # Determine string to write and midpoint of string s = "{:.3f}\"".format(size) ll, ur = makestroke.boundingBox(s, posX + glyphspace, posY) # Returns lower-left point, upper-right point midpoint = (ur[1] + ll[1]) / 2 # Keep track of maximum extent of legend maxX = max(maxX, ur[0]) makestroke.drawDrillHit(fid, posX, midpoint, toolNum) makestroke.writeString(fid, s, posX + glyphspace, posY, 0) posY += int(round((ur[1] - ll[1]) * 1.5)) # Return value is lower-left of user text area, without any padding. return maxX, util.in2gerb(OriginY)
def writeUserText(fid, X, Y): fname = config.Config['fabricationdrawingtext'] if not fname: return try: tfile = open(fname, 'rt') except Exception as detail: raise RuntimeError("Could not open fabrication drawing text file '{:s}':\n {:s}".format(fname, detail)) lines = tfile.readlines() tfile.close() lines.reverse() # We're going to print from bottom up # Offset X position to give some clearance from drill legend X += util.in2gerb(0.2) # 2000 for line in lines: # Get rid of CR line = line.replace('\x0D', '') # Strip off trailing whitespace line = line.rstrip() # Blank lines still need height, so must have at least one character if not line: line = ' ' ll, ur = makestroke.boundingBox(line, X, Y) makestroke.writeString(fid, line, X, Y, 0) Y += int(round((ur[1] - ll[1]) * 1.5))
def writeUserText(fid, X, Y): fname = config.Config['fabricationdrawingtext'] if not fname: return try: tfile = open(fname, 'rt') except Exception as detail: raise RuntimeError( "Could not open fabrication drawing text file '{:s}':\n {:s}". format(fname, detail)) lines = tfile.readlines() tfile.close() lines.reverse() # We're going to print from bottom up # Offset X position to give some clearance from drill legend X += util.in2gerb(0.2) # 2000 for line in lines: # Get rid of CR line = line.replace('\x0D', '') # Strip off trailing whitespace line = line.rstrip() # Blank lines still need height, so must have at least one character if not line: line = ' ' ll, ur = makestroke.boundingBox(line, X, Y) makestroke.writeString(fid, line, X, Y, 0) Y += int(round((ur[1] - ll[1]) * 1.5))
def rectangleAsRect(self, X, Y): """Return a 4-tuple (minx,miny,maxx,maxy) describing the area covered by this Rectangle aperture when flashed at center co-ordinates (X,Y)""" dx = util.in2gerb(self.dimx) dy = util.in2gerb(self.dimy) if dx & 1: # Odd-sized: X extents are (dx+1)/2 on the left and (dx-1)/2 on the right xm = (dx+1)/2 xp = xm-1 else: # Even-sized: X extents are X-dx/2 and X+dx/2 xm = xp = dx/2 if dy & 1: # Odd-sized: Y extents are (dy+1)/2 below and (dy-1)/2 above ym = (dy+1)/2 yp = ym-1 else: # Even-sized: Y extents are Y-dy/2 and Y+dy/2 ym = yp = dy/2 return (X-xm, Y-ym, X+xp, Y+yp)
def rectangleAsRect(self, X, Y): """Return a 4-tuple (minx,miny,maxx,maxy) describing the area covered by this Rectangle aperture when flashed at center co-ordinates (X,Y)""" dx = util.in2gerb(self.dimx) dy = util.in2gerb(self.dimy) if dx & 1: # Odd-sized: X extents are (dx+1)/2 on the left and (dx-1)/2 on the right xm = (dx + 1) / 2 xp = xm - 1 else: # Even-sized: X extents are X-dx/2 and X+dx/2 xm = xp = dx / 2 if dy & 1: # Odd-sized: Y extents are (dy+1)/2 below and (dy-1)/2 above ym = (dy + 1) / 2 yp = ym - 1 else: # Even-sized: Y extents are Y-dy/2 and Y+dy/2 ym = yp = dy / 2 return (X - xm, Y - ym, X + xp, Y + yp)
def writeFiducials(fid, drawcode, OriginX, OriginY, MaxXExtent, MaxYExtent): """Place fiducials at arbitrary points. The FiducialPoints list in the config specifies sets of X,Y co-ordinates. Positive values of X/Y represent offsets from the lower left of the panel. Negative values of X/Y represent offsets from the top right. So: FiducialPoints = 0.125,0.125,-0.125,-0.125 means to put a fiducial 0.125,0.125 from the lower left and 0.125,0.125 from the top right""" fid.write("{:s}*\n".format(drawcode)) # Choose drawing aperture fList = config.Config['fiducialpoints'].split(',') for i in range(0, len(fList), 2): x, y = float(fList[i]), float(fList[i + 1]) if x >= 0: x += OriginX else: x = MaxXExtent + x if y >= 0: y += OriginX else: y = MaxYExtent + y fid.write("X{:07d}Y{:07d}D03*\n".format(util.in2gerb(x), util.in2gerb(y)))
def writeFiducials(fid, drawcode, OriginX, OriginY, MaxXExtent, MaxYExtent): """Place fiducials at arbitrary points. The FiducialPoints list in the config specifies sets of X,Y co-ordinates. Positive values of X/Y represent offsets from the lower left of the panel. Negative values of X/Y represent offsets from the top right. So: FiducialPoints = 0.125,0.125,-0.125,-0.125 means to put a fiducial 0.125,0.125 from the lower left and 0.125,0.125 from the top right""" fid.write('%s*\n' % drawcode) # Choose drawing aperture fList = config.Config['fiducialpoints'].split(',') for i in range(0, len(fList), 2): x,y = float(fList[i]), float(fList[i+1]) if x>=0: x += OriginX else: x = MaxXExtent + x if y>=0: y += OriginX else: y = MaxYExtent + y fid.write('X%07dY%07dD03*\n' % (util.in2gerb(x), util.in2gerb(y)))
def writeOutline(fid, OriginX, OriginY, MaxXExtent, MaxYExtent): # Write width-1 aperture to file AP = aptable.Aperture(aptable.Circle, 'D10', 0.001) AP.writeDef(fid) # Choose drawing aperture D10 fid.write('D10*\n') # Draw the rectangle fid.write('X%07dY%07dD02*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(MaxYExtent))) # Top-left fid.write( 'X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(MaxYExtent))) # Top-right fid.write( 'X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(OriginY))) # Bottom-right fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left
def writeOutline(fid, OriginX, OriginY, MaxXExtent, MaxYExtent): # Write width-1 aperture to file AP = aptable.Aperture(aptable.Circle, 'D10', 0.001) AP.writeDef(fid) # Choose drawing aperture D10 writeCurrentAperture(fid, 10) # Draw the rectangle starting at the bottom left and going clockwise. fid.write("X{:07d}Y{:07d}D02*\n".format(util.in2gerb(OriginX), util.in2gerb(OriginY))) fid.write("Y{:07d}D01*\n".format(util.in2gerb(MaxYExtent))) fid.write("X{:07d}D01*\n".format(util.in2gerb(MaxXExtent))) fid.write("Y{:07d}D01*\n".format(util.in2gerb(OriginY))) fid.write("X{:07d}D01*\n".format(util.in2gerb(OriginX)))
def writeUserText(fid, X, Y): fname = config.Config['fabricationdrawingtext'] if not fname: return try: tfile = file(fname, 'rt') except Exception, detail: raise RuntimeError, "Could not open fabrication drawing text file '%s':\n %s" % ( fname, str(detail)) lines = tfile.readlines() tfile.close() lines.reverse() # We're going to print from bottom up # Offset X position to give some clearance from drill legend X += util.in2gerb(0.2) # 2000 for line in lines: # Get rid of CR line = string.replace(line, '\x0D', '') # Chop off \n #if line[-1] in string.whitespace: # line = line[:-1] # Strip off trailing whitespace line = string.rstrip(line) # Blank lines still need height, so must have at least one character if not line: line = ' '
def writeCropMarks(fid, drawing_code, OriginX, OriginY, MaxXExtent, MaxYExtent): """Add corner crop marks on the given layer""" # Draw 125mil lines at each corner, with line edge right up against # panel border. This means the center of the line is D/2 offset # from the panel border, where D is the drawing line diameter. fid.write("{:s}*\n".format(drawing_code)) # Choose drawing aperture offset = config.GAT[drawing_code].dimx / 2.0 # Lower-left x = OriginX + offset y = OriginY + offset fid.write("X{:07d}Y{:07d}D02*\n".format(util.in2gerb(x + 0.125), util.in2gerb(y + 0.000))) fid.write("X{:07d}D01*\n".format(util.in2gerb(x + 0.000))) fid.write("Y{:07d}D01*\n".format(util.in2gerb(y + 0.125))) # Lower-right x = MaxXExtent - offset y = OriginY + offset fid.write("X{:07d}Y{:07d}D02*\n".format(util.in2gerb(x + 0.000), util.in2gerb(y + 0.125))) fid.write("Y{:07d}D01*\n".format(util.in2gerb(y + 0.000))) fid.write("X{:07d}D01*\n".format(util.in2gerb(x - 0.125))) # Upper-right x = MaxXExtent - offset y = MaxYExtent - offset fid.write("X{:07d}Y{:07d}D02*\n".format(util.in2gerb(x - 0.125), util.in2gerb(y + 0.000))) fid.write("X{:07d}D01*\n".format(util.in2gerb(x + 0.000))) fid.write("Y{:07d}D01*\n".format(util.in2gerb(y - 0.125))) # Upper-left x = OriginX + offset y = MaxYExtent - offset fid.write("X{:07d}Y{:07d}D02*\n".format(util.in2gerb(x + 0.000), util.in2gerb(y - 0.125))) fid.write("Y{:07d}D01*\n".format(util.in2gerb(y + 0.000))) fid.write("X{:07d}D01*\n".format(util.in2gerb(x + 0.125)))
def merge(opts, args, gui=None): writeGerberHeader = writeGerberHeader22degrees global GUI GUI = gui skipDisclaimer = 0 for opt, arg in opts: if opt in ('--octagons', ): if arg == 'rotate': writeGerberHeader = writeGerberHeader0degrees elif arg == 'normal': writeGerberHeader = writeGerberHeader22degrees else: raise RuntimeError, 'Unknown octagon format' elif opt in ('--random-search', ): config.AutoSearchType = RANDOM_SEARCH elif opt in ('--full-search', ): config.AutoSearchType = EXHAUSTIVE_SEARCH elif opt in ('--rs-fsjobs', ): config.RandomSearchExhaustiveJobs = int(arg) elif opt in ('--search-timeout', ): config.SearchTimeout = int(arg) elif opt in ('--place-file', ): config.AutoSearchType = FROM_FILE config.PlacementFile = arg elif opt in ('--no-trim-gerber', ): config.TrimGerber = 0 elif opt in ('--no-trim-excellon', ): config.TrimExcellon = 0 elif opt in ('-s', '--skipdisclaimer'): skipDisclaimer = 1 else: raise RuntimeError, "Unknown option: %s" % opt if len(args) > 2 or len(args) < 1: raise RuntimeError, 'Invalid number of arguments' if (skipDisclaimer == 0): disclaimer() # Load up the Jobs global dictionary, also filling out GAT, the # global aperture table and GAMT, the global aperture macro table. updateGUI("Reading job files...") config.parseConfigFile(args[0]) # Display job properties for job in config.Jobs.values(): print 'Job %s:' % job.name, if job.Repeat > 1: print '(%d instances)' % job.Repeat else: print print ' Extents: (%d,%d)-(%d,%d)' % (job.minx, job.miny, job.maxx, job.maxy) # add metric support (1/1000 mm vs. 1/100,000 inch) if config.Config['measurementunits'] == 'inch': print ' Size: %f" x %f"' % (job.width_in(), job.height_in()) else: print ' Size: %5.3fmm x %5.3fmm' % (job.width_in(), job.height_in()) print # Trim drill locations and flash data to board extents if config.TrimExcellon: updateGUI("Trimming Excellon data...") print 'Trimming Excellon data to board outlines ...' for job in config.Jobs.values(): job.trimExcellon() if config.TrimGerber: updateGUI("Trimming Gerber data...") print 'Trimming Gerber data to board outlines ...' for job in config.Jobs.values(): job.trimGerber() # We start origin at (0.1", 0.1") just so we don't get numbers close to 0 # which could trip up Excellon leading-0 elimination. # I don't want to change the origin. If this a code bug, then it should be fixed (SDD) OriginX = OriginY = 0 #0.1 # Read the layout file and construct the nested list of jobs. If there # is no layout file, do auto-layout. updateGUI("Performing layout...") print 'Performing layout ...' if len(args) > 1: Layout = parselayout.parseLayoutFile(args[1]) # Do the layout, updating offsets for each component job. X = OriginX + config.Config['leftmargin'] Y = OriginY + config.Config['bottommargin'] for row in Layout: row.setPosition(X, Y) Y += row.height_in() + config.Config['yspacing'] # Construct a canonical placement from the layout Place = placement.Placement() Place.addFromLayout(Layout) del Layout elif config.AutoSearchType == FROM_FILE: Place = placement.Placement() Place.addFromFile(config.PlacementFile, config.Jobs) else: # Do an automatic layout based on our tiling algorithm. tile = tile_jobs(config.Jobs.values()) Place = placement.Placement() Place.addFromTiling(tile, OriginX + config.Config['leftmargin'], OriginY + config.Config['bottommargin']) (MaxXExtent, MaxYExtent) = Place.extents() MaxXExtent += config.Config['rightmargin'] MaxYExtent += config.Config['topmargin'] # Start printing out the Gerbers. In preparation for drawing cut marks # and crop marks, make sure we have an aperture to draw with. Use a 10mil line. # If we're doing a fabrication drawing, we'll need a 1mil line. OutputFiles = [] try: fullname = config.MergeOutputFiles['placement'] except KeyError: fullname = 'merged.placement.txt' Place.write(fullname) OutputFiles.append(fullname) # For cut lines AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['cutlinewidth']) drawing_code_cut = aptable.findInApertureTable(AP) if drawing_code_cut is None: drawing_code_cut = aptable.addToApertureTable(AP) # For score lines AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['scoringlinewidth']) drawing_code_score = aptable.findInApertureTable(AP) if drawing_code_score is None: drawing_code_score = aptable.addToApertureTable(AP) # For crop marks AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['cropmarkwidth']) drawing_code_crop = aptable.findInApertureTable(AP) if drawing_code_crop is None: drawing_code_crop = aptable.addToApertureTable(AP) # For fiducials drawing_code_fiducial_copper = drawing_code_fiducial_soldermask = None if config.Config['fiducialpoints']: AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['fiducialcopperdiameter']) drawing_code_fiducial_copper = aptable.findInApertureTable(AP) if drawing_code_fiducial_copper is None: drawing_code_fiducial_copper = aptable.addToApertureTable(AP) AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['fiducialmaskdiameter']) drawing_code_fiducial_soldermask = aptable.findInApertureTable(AP) if drawing_code_fiducial_soldermask is None: drawing_code_fiducial_soldermask = aptable.addToApertureTable(AP) # For fabrication drawing. AP = aptable.Aperture(aptable.Circle, 'D??', 0.001) drawing_code1 = aptable.findInApertureTable(AP) if drawing_code1 is None: drawing_code1 = aptable.addToApertureTable(AP) updateGUI("Writing merged files...") print 'Writing merged output files ...' for layername in config.LayerList.keys(): lname = layername if lname[0] == '*': lname = lname[1:] try: fullname = config.MergeOutputFiles[layername] except KeyError: fullname = 'merged.%s.ger' % lname OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeGerberHeader(fid) # Determine which apertures and macros are truly needed apUsedDict = {} apmUsedDict = {} for job in Place.jobs: apd, apmd = job.aperturesAndMacros(layername) apUsedDict.update(apd) apmUsedDict.update(apmd) # Increase aperature sizes to match minimum feature dimension if config.MinimumFeatureDimension.has_key(layername): print ' Thickening', lname, 'feature dimensions ...' # Fix each aperture used in this layer for ap in apUsedDict.keys(): new = config.GAT[ap].getAdjusted( config.MinimumFeatureDimension[layername]) if not new: ## current aperture size met minimum requirement continue else: ## new aperture was created new_code = aptable.findOrAddAperture( new ) ## get name of existing aperture or create new one if needed del apUsedDict[ ap] ## the old aperture is no longer used in this layer apUsedDict[ new_code] = None ## the new aperture will be used in this layer # Replace all references to the old aperture with the new one for joblayout in Place.jobs: job = joblayout.job ##access job inside job layout temp = [] if job.hasLayer(layername): for x in job.commands[layername]: if x == ap: temp.append( new_code ) ## replace old aperture with new one else: temp.append(x) ## keep old command job.commands[layername] = temp if config.Config['cutlinelayers'] and ( layername in config.Config['cutlinelayers']): apUsedDict[drawing_code_cut] = None if config.Config['scoringlinelayers'] and ( layername in config.Config['scoringlinelayers']): apUsedDict[drawing_code_score] = None if config.Config['cropmarklayers'] and ( layername in config.Config['cropmarklayers']): apUsedDict[drawing_code_crop] = None if config.Config['fiducialpoints']: if ((layername == '*toplayer') or (layername == '*bottomlayer')): apUsedDict[drawing_code_fiducial_copper] = None elif ((layername == '*topsoldermask') or (layername == '*bottomsoldermask')): apUsedDict[drawing_code_fiducial_soldermask] = None # Write only necessary macro and aperture definitions to Gerber file writeApertureMacros(fid, apmUsedDict) writeApertures(fid, apUsedDict) #for row in Layout: # row.writeGerber(fid, layername) # # Do cut lines # if config.Config['cutlinelayers'] and (layername in config.Config['cutlinelayers']): # fid.write('%s*\n' % drawing_code_cut) # Choose drawing aperture # row.writeCutLines(fid, drawing_code_cut, OriginX, OriginY, MaxXExtent, MaxYExtent) # Finally, write actual flash data for job in Place.jobs: updateGUI("Writing merged output files...") job.writeGerber(fid, layername) if config.Config['cutlinelayers'] and ( layername in config.Config['cutlinelayers']): fid.write('%s*\n' % drawing_code_cut) # Choose drawing aperture #print "writing drawcode_cut: %s" % drawing_code_cut job.writeCutLines(fid, drawing_code_cut, OriginX, OriginY, MaxXExtent, MaxYExtent) # Draw the scoring lines if config.Config['scoringlinelayers'] and layername in config.Config[ 'scoringlinelayers']: fid.write('%s*\n' % drawing_code_score) # Choose scoring aperture scoring.writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent) if config.Config['cropmarklayers']: if layername in config.Config['cropmarklayers']: writeCropMarks(fid, drawing_code_crop, OriginX, OriginY, MaxXExtent, MaxYExtent) if config.Config['fiducialpoints']: # If we are generating fiducials along the score lines, # and we have not yet done so, we need to generate those now, pass in None as the file # to prevent it actually writing out scoring lines and just generate the fiducials if config.Config['fiducialpoints'] == 'scoring': scoring.writeScoring(None, Place, OriginX, OriginY, MaxXExtent, MaxYExtent) if ((layername == '*toplayer') or (layername == '*bottomlayer')): writeFiducials(fid, drawing_code_fiducial_copper, OriginX, OriginY, MaxXExtent, MaxYExtent) elif ((layername == '*topsoldermask') or (layername == '*bottomsoldermask')): writeFiducials(fid, drawing_code_fiducial_soldermask, OriginX, OriginY, MaxXExtent, MaxYExtent) writeGerberFooter(fid) fid.close() # Write board outline layer if selected fullname = config.Config['outlinelayerfile'] if fullname and fullname.lower() != "none": OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeGerberHeader(fid) # Write width-1 aperture to file # add metric support if config.Config['measurementunits'] == 'inch': AP = aptable.Aperture(aptable.Circle, 'D10', 0.001) else: AP = aptable.Aperture(aptable.Circle, 'D10', 0.25) # we'll use 0.25 mm - same as Diptrace AP.writeDef(fid) # Choose drawing aperture D10 fid.write('D10*\n') # Draw the rectangle fid.write( 'X%07dY%07dD02*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left fid.write( 'X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(MaxYExtent))) # Top-left fid.write( 'X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(MaxYExtent))) # Top-right fid.write( 'X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(OriginY))) # Bottom-right fid.write( 'X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left writeGerberFooter(fid) fid.close() # Write scoring layer if selected fullname = config.Config['scoringfile'] if fullname and fullname.lower() != "none": OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeGerberHeader(fid) # Write width-1 aperture to file AP = aptable.Aperture(aptable.Circle, 'D10', 0.001) AP.writeDef(fid) # Choose drawing aperture D10 fid.write('D10*\n') # Draw the scoring lines scoring.writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent) writeGerberFooter(fid) fid.close() # Get a list of all tools used by merging keys from each job's dictionary # of tools. if 0: Tools = {} for job in config.Jobs.values(): for key in job.xcommands.keys(): Tools[key] = 1 Tools = Tools.keys() Tools.sort() else: toolNum = 0 # First construct global mapping of diameters to tool numbers for job in config.Jobs.values(): for tool, diam in job.xdiam.items(): if config.GlobalToolRMap.has_key(diam): continue toolNum += 1 config.GlobalToolRMap[diam] = "T%02d" % toolNum # Cluster similar tool sizes to reduce number of drills if config.Config['drillclustertolerance'] > 0: config.GlobalToolRMap = drillcluster.cluster( config.GlobalToolRMap, config.Config['drillclustertolerance']) drillcluster.remap(Place.jobs, config.GlobalToolRMap.items()) # Now construct mapping of tool numbers to diameters for diam, tool in config.GlobalToolRMap.items(): config.GlobalToolMap[tool] = diam # Tools is just a list of tool names Tools = config.GlobalToolMap.keys() Tools.sort() fullname = config.Config['fabricationdrawingfile'] if fullname and fullname.lower() != 'none': if len(Tools) > strokes.MaxNumDrillTools: raise RuntimeError, "Only %d different tool sizes supported for fabrication drawing." % strokes.MaxNumDrillTools OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeGerberHeader(fid) writeApertures(fid, {drawing_code1: None}) fid.write('%s*\n' % drawing_code1) # Choose drawing aperture fabdrawing.writeFabDrawing(fid, Place, Tools, OriginX, OriginY, MaxXExtent, MaxYExtent) writeGerberFooter(fid) fid.close() # Finally, print out the Excellon try: fullname = config.MergeOutputFiles['drills'] except KeyError: fullname = 'merged.drills.xln' OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeExcellonHeader(fid) # Ensure each one of our tools is represented in the tool list specified # by the user. for tool in Tools: try: size = config.GlobalToolMap[tool] except: raise RuntimeError, "INTERNAL ERROR: Tool code %s not found in global tool map" % tool writeExcellonTool(fid, tool, size) #for row in Layout: # row.writeExcellon(fid, size) for job in Place.jobs: job.writeExcellon(fid, size) writeExcellonFooter(fid) fid.close() updateGUI("Closing files...") # Compute stats jobarea = 0.0 #for row in Layout: # jobarea += row.jobarea() for job in Place.jobs: jobarea += job.jobarea() totalarea = ((MaxXExtent - OriginX) * (MaxYExtent - OriginY)) ToolStats = {} drillhits = 0 for tool in Tools: ToolStats[tool] = 0 #for row in Layout: # hits = row.drillhits(config.GlobalToolMap[tool]) # ToolStats[tool] += hits # drillhits += hits for job in Place.jobs: hits = job.drillhits(config.GlobalToolMap[tool]) ToolStats[tool] += hits drillhits += hits try: fullname = config.MergeOutputFiles['toollist'] except KeyError: fullname = 'merged.toollist.drl' OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') print '-' * 50 # add metric support (1/1000 mm vs. 1/100,000 inch) if config.Config['measurementunits'] == 'inch': print ' Job Size : %f" x %f"' % (MaxXExtent - OriginX, MaxYExtent - OriginY) print ' Job Area : %.2f sq. in.' % totalarea else: print ' Job Size : %.2fmm x %.2fmm' % (MaxXExtent - OriginX, MaxYExtent - OriginY) print ' Job Area : %.0f mm2' % totalarea print ' Area Usage : %.1f%%' % (jobarea / totalarea * 100) print ' Drill hits : %d' % drillhits if config.Config['measurementunits'] == 'inch': print 'Drill density : %.1f hits/sq.in.' % (drillhits / totalarea) else: print 'Drill density : %.2f hits/cm2' % (100 * drillhits / totalarea) print '\nTool List:' smallestDrill = 999.9 for tool in Tools: if ToolStats[tool]: if config.Config['measurementunits'] == 'inch': fid.write('%s %.4fin\n' % (tool, config.GlobalToolMap[tool])) print ' %s %.4f" %5d hits' % ( tool, config.GlobalToolMap[tool], ToolStats[tool]) else: fid.write('%s %.4fmm\n' % (tool, config.GlobalToolMap[tool])) print ' %s %.4fmm %5d hits' % ( tool, config.GlobalToolMap[tool], ToolStats[tool]) smallestDrill = min(smallestDrill, config.GlobalToolMap[tool]) fid.close() if config.Config['measurementunits'] == 'inch': print "Smallest Tool: %.4fin" % smallestDrill else: print "Smallest Tool: %.4fmm" % smallestDrill print print 'Output Files :' for f in OutputFiles: print ' ', f if (MaxXExtent - OriginX) > config.Config['panelwidth'] or ( MaxYExtent - OriginY) > config.Config['panelheight']: print '*' * 75 print '*' # add metric support (1/1000 mm vs. 1/100,000 inch) if config.Config['measurementunits'] == 'inch': print '* ERROR: Merged job exceeds panel dimensions of %.1f"x%.1f"' % ( config.Config['panelwidth'], config.Config['panelheight']) else: print '* ERROR: Merged job exceeds panel dimensions of %.1fmmx%.1fmm' % ( config.Config['panelwidth'], config.Config['panelheight']) print '*' print '*' * 75 sys.exit(1) # Done! return 0
def writeUserText(fid, X, Y): fname = config.Config['fabricationdrawingtext'] if not fname: return try: tfile = file(fname, 'rt') except Exception, detail: raise RuntimeError, "Could not open fabrication drawing text file '%s':\n %s" % (fname,str(detail)) lines = tfile.readlines() tfile.close() lines.reverse() # We're going to print from bottom up # Offset X position to give some clearance from drill legend X += util.in2gerb(0.2) # 2000 for line in lines: # Get rid of CR line = string.replace(line, '\x0D', '') # Chop off \n #if line[-1] in string.whitespace: # line = line[:-1] # Strip off trailing whitespace line = string.rstrip(line) # Blank lines still need height, so must have at least one character if not line: line = ' '
fname = config.Config['fabricationdrawingtext'] if not fname: return try: tfile = file(fname, 'rt') except Exception, detail: raise RuntimeError, "Could not open fabrication drawing text file '%s':\n %s" % (fname,str(detail)) lines = tfile.readlines() tfile.close() lines.reverse() # We're going to print from bottom up # Offset X position to give some clearance from drill legend # X += util.in2gerb(0.2*25.4) # 2000 # KHK orig. line if config.Config['measurementunits'] == 'inch': X += util.in2gerb(0.2) # 2000 else: X += util.in2gerb(10) # 2000 # additional text is placed right of drill legend for line in lines: # Get rid of CR line = string.replace(line, '\x0D', '') # Chop off \n #if line[-1] in string.whitespace: # line = line[:-1] # Strip off trailing whitespace line = string.rstrip(line) # Blank lines still need height, so must have at least one character
OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeGerberHeader(fid) # Write width-1 aperture to file AP = aptable.Aperture(aptable.Circle, 'D10', 0.001) AP.writeDef(fid) # Choose drawing aperture D10 fid.write('D10*\n') # Draw the rectangle fid.write( 'X%07dY%07dD02*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left fid.write( 'X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(MaxYExtent))) # Top-left fid.write( 'X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(MaxYExtent))) # Top-right fid.write( 'X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(OriginY))) # Bottom-right fid.write( 'X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left writeGerberFooter(fid) fid.close()
def writeCropMarks(fid, drawing_code, OriginX, OriginY, MaxXExtent, MaxYExtent): """Add corner crop marks on the given layer""" # Draw 125mil lines at each corner, with line edge right up against # panel border. This means the center of the line is D/2 offset # from the panel border, where D is the drawing line diameter. # use 3mm lines for metric fid.write('%s*\n' % drawing_code) # Choose drawing aperture offset = config.GAT[drawing_code].dimx/2.0 # should we be using 'cropmarkwidth' from config.py? if config.Config['measurementunits'] == 'inch': cropW = 0.125 #inch else: cropW = 3 #mm # Lower-left x = OriginX + offset y = OriginY + offset fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+cropW), util.in2gerb(y+0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+cropW))) # Lower-right x = MaxXExtent - offset y = OriginY + offset fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+cropW))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x-cropW), util.in2gerb(y+0.000))) # Upper-right x = MaxXExtent - offset y = MaxYExtent - offset fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x-cropW), util.in2gerb(y+0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y-cropW))) # Upper-left x = OriginX + offset y = MaxYExtent - offset fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x+0.000), util.in2gerb(y-cropW))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+0.000), util.in2gerb(y+0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x+cropW), util.in2gerb(y+0.000)))
def writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent): # For each job, write out 4 score lines, above, to the right, below, and # to the left. After we collect all potential scoring lines, we worry # about merging, etc. dx = config.Config['xspacing'] / 2.0 dy = config.Config['yspacing'] / 2.0 extents = (OriginX, OriginY, MaxXExtent, MaxYExtent) Lines = [] for layout in Place.jobs: x = layout.x - dx y = layout.y - dy X = layout.x + layout.width_in() + dx Y = layout.y + layout.height_in() + dy print "Crossing point xy: (" + str(x) + "," + str(y) + ")" print "Crossing point xY: (" + str(x) + "," + str(Y) + ")" print "Crossing point Xy: (" + str(X) + "," + str(y) + ")" print "Crossing point XY: (" + str(X) + "," + str(Y) + ")" print "---" #calculate mousebite location mousebiteX = layout.x + layout.width_in() / 2 mousebiteY = layout.y + layout.height_in() / 2 mousebiteLen = 0.080 # Just so we don't get 3.75000000004 and 3.75000000009, we round to # 2.5 limits. x, y, X, Y = [round(val, 5) for val in [x, y, X, Y]] if 1: # Scoring lines go all the way across the panel now addHorizontalLine(Lines, x, mousebiteX - mousebiteLen / 2, Y, extents) # above job addHorizontalLine(Lines, mousebiteX + mousebiteLen / 2, X, Y, extents) # above job addVerticalLine(Lines, X, y, mousebiteY - mousebiteLen / 2, extents) # to the right of job addVerticalLine(Lines, X, mousebiteY + mousebiteLen / 2, Y, extents) # to the right of job addHorizontalLine(Lines, x, mousebiteX - mousebiteLen / 2, y, extents) # below job addHorizontalLine(Lines, mousebiteX + mousebiteLen / 2, X, y, extents) # below job addVerticalLine(Lines, x, y, mousebiteY - mousebiteLen / 2, extents) # to the left of job addVerticalLine(Lines, X, mousebiteY + mousebiteLen / 2, Y, extents) # to the left of job else: addHorizontalLine(Lines, OriginX, MaxXExtent, Y, extents) # above job addVerticalLine(Lines, X, OriginY, MaxYExtent, extents) # to the right of job addHorizontalLine(Lines, OriginX, MaxXExtent, y, extents) # below job addVerticalLine(Lines, x, OriginY, MaxYExtent, extents) # to the left of job # Combine disparate lines into single lines Lines = mergeLines(Lines) #for line in Lines: # print [round(x,3) for x in line] # Write 'em out for line in Lines: makestroke.drawPolyline(fid, [(util.in2gerb(line[0]),util.in2gerb(line[1])), \ (util.in2gerb(line[2]),util.in2gerb(line[3]))], 0, 0)
if not fname: return try: tfile = file(fname, 'rt') except Exception, detail: raise RuntimeError, "Could not open fabrication drawing text file '%s':\n %s" % ( fname, str(detail)) lines = tfile.readlines() tfile.close() lines.reverse() # We're going to print from bottom up # Offset X position to give some clearance from drill legend # X += util.in2gerb(0.2*25.4) # 2000 # KHK orig. line if config.Config['measurementunits'] == 'inch': X += util.in2gerb(0.2) # 2000 else: X += util.in2gerb( 10) # 2000 # additional text is placed right of drill legend for line in lines: # Get rid of CR line = string.replace(line, '\x0D', '') # Chop off \n #if line[-1] in string.whitespace: # line = line[:-1] # Strip off trailing whitespace line = string.rstrip(line)
def writeCropMarks(fid, drawing_code, OriginX, OriginY, MaxXExtent, MaxYExtent): """Add corner crop marks on the given layer""" # Draw 125mil lines at each corner, with line edge right up against # panel border. This means the center of the line is D/2 offset # from the panel border, where D is the drawing line diameter. # use 3mm lines for metric fid.write('%s*\n' % drawing_code) # Choose drawing aperture offset = config.GAT[drawing_code].dimx / 2.0 # should we be using 'cropmarkwidth' from config.py? if config.Config['measurementunits'] == 'inch': cropW = 0.125 #inch else: cropW = 3 #mm # Lower-left x = OriginX + offset y = OriginY + offset fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x + cropW), util.in2gerb(y + 0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x + 0.000), util.in2gerb(y + 0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x + 0.000), util.in2gerb(y + cropW))) # Lower-right x = MaxXExtent - offset y = OriginY + offset fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x + 0.000), util.in2gerb(y + cropW))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x + 0.000), util.in2gerb(y + 0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x - cropW), util.in2gerb(y + 0.000))) # Upper-right x = MaxXExtent - offset y = MaxYExtent - offset fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x - cropW), util.in2gerb(y + 0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x + 0.000), util.in2gerb(y + 0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x + 0.000), util.in2gerb(y - cropW))) # Upper-left x = OriginX + offset y = MaxYExtent - offset fid.write('X%07dY%07dD02*\n' % (util.in2gerb(x + 0.000), util.in2gerb(y - cropW))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x + 0.000), util.in2gerb(y + 0.000))) fid.write('X%07dY%07dD01*\n' % (util.in2gerb(x + cropW), util.in2gerb(y + 0.000)))
fullname = config.Config['outlinelayerfile'] if fullname and fullname.lower() != "none": OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeGerberHeader(fid) # Write width-1 aperture to file AP = aptable.Aperture(aptable.Circle, 'D10', 0.001) AP.writeDef(fid) # Choose drawing aperture D10 fid.write('D10*\n') # Draw the rectangle fid.write('X%07dY%07dD02*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(MaxYExtent))) # Top-left fid.write('X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(MaxYExtent))) # Top-right fid.write('X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(OriginY))) # Bottom-right fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left writeGerberFooter(fid) fid.close() # Write scoring layer if selected fullname = config.Config['scoringfile'] if fullname and fullname.lower() != "none": OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeGerberHeader(fid)
def writeDimensionArrow(fid, OriginX, OriginY, MaxXExtent, MaxYExtent): x = util.in2gerb(OriginX) y = util.in2gerb(OriginY) X = util.in2gerb(MaxXExtent) Y = util.in2gerb(MaxYExtent) # This constant is how far away from the board the centerline of the dimension # arrows should be, in inches. # dimspace = 0.2 # KHK was the original line if config.Config['measurementunits'] == 'inch': dimspace = 0.2 # inches else: dimspace = 10 # mm - Distance of dimension line to board outline line # Convert it to Gerber (0.00001" or 2.5) units dimspace = util.in2gerb(dimspace) # Draw an arrow above the board, on the left side and right side makestroke.drawDimensionArrow(fid, x, Y+dimspace, makestroke.FacingLeft) makestroke.drawDimensionArrow(fid, X, Y+dimspace, makestroke.FacingRight) # Draw arrows to the right of the board, at top and bottom makestroke.drawDimensionArrow(fid, X+dimspace, Y, makestroke.FacingUp) makestroke.drawDimensionArrow(fid, X+dimspace, y, makestroke.FacingDown) # Now draw the text. First, horizontal text above the board. # s = '%.3f"' % (MaxXExtent - OriginX) # KHK orig line if config.Config['measurementunits'] == 'inch': s = '%.3f"' % (MaxXExtent - OriginX) # inche else: s = '%.3fmm' % (MaxXExtent - OriginX) # mm ll, ur = makestroke.boundingBox(s, 0, 0) s_width = ur[0]-ll[0] # Width in 2.5 units s_height = ur[1]-ll[1] # Height in 2.5 units # Compute the position in 2.5 units where we should draw this. It should be # centered horizontally and also vertically about the dimension arrow centerline. posX = x + (x+X)/2 posX -= s_width/2 posY = Y + dimspace - s_height/2 makestroke.writeString(fid, s, posX, posY, 0) # Finally, draw the extending lines from the text to the arrows. posY = Y + dimspace #posX1 = posX - util.in2gerb(0.1*24.5) # 1000 # KHK orig. line if config.Config['measurementunits'] == 'inch': posX1 = posX - util.in2gerb(0.1 ) # 1000 else: posX1 = posX - util.in2gerb(0.1*24.5) # 1000 # posX2 = posX + s_width + util.in2gerb(0.1*25.4) # 1000 # KHK orig. line if config.Config['measurementunits'] == 'inch': posX2 = posX + s_width + util.in2gerb(0.1) # 1000 else: posX2 = posX + s_width + util.in2gerb(0.1*25.4) # 1000 makestroke.drawLine(fid, x, posY, posX1, posY) makestroke.drawLine(fid, posX2, posY, X, posY) # Now do the vertical text # s = '%.3f"' % (MaxYExtent - OriginY) # KHK orig line if config.Config['measurementunits'] == 'inch': s = '%.3f"' % (MaxYExtent - OriginY) else: s = '%.3fmm' % (MaxYExtent - OriginY) ll, ur = makestroke.boundingBox(s, 0, 0) s_width = ur[0]-ll[0] s_height = ur[1]-ll[1] # As above, figure out where to draw this. Rotation will be -90 degrees # so new origin will be top-left of bounding box after rotation. posX = X + dimspace - s_height/2 posY = y + (y+Y)/2 posY += s_width/2 makestroke.writeString(fid, s, posX, posY, -90) # Draw extending lines posX = X + dimspace # posY1 = posY + util.in2gerb(0.1) # 1000 # KHK orig. line if config.Config['measurementunits'] == 'inch': posY1 = posY + util.in2gerb(0.1) # 1000 # KHK orig. line else: posY1 = posY + util.in2gerb(0.1*25.4) # 1000 # KHK orig. line # posY2 = posY - s_width - util.in2gerb(0.1) # 1000 # KHK orig. line if config.Config['measurementunits'] == 'inch': posY2 = posY - s_width - util.in2gerb(0.1) # 1000 # KHK orig. line else: posY2 = posY - s_width - util.in2gerb(0.1*25.4) # 1000 makestroke.drawLine(fid, posX, Y, posX, posY1) makestroke.drawLine(fid, posX, posY2, posX, y)
def merge(opts, args, gui = None): writeGerberHeader = writeGerberHeader22degrees global GUI GUI = gui skipDisclaimer = 0 for opt, arg in opts: if opt in ('--octagons',): if arg=='rotate': writeGerberHeader = writeGerberHeader0degrees elif arg=='normal': writeGerberHeader = writeGerberHeader22degrees else: raise RuntimeError, 'Unknown octagon format' elif opt in ('--random-search',): config.AutoSearchType = RANDOM_SEARCH elif opt in ('--full-search',): config.AutoSearchType = EXHAUSTIVE_SEARCH elif opt in ('--rs-fsjobs',): config.RandomSearchExhaustiveJobs = int(arg) elif opt in ('--search-timeout',): config.SearchTimeout = int(arg) elif opt in ('--place-file',): config.AutoSearchType = FROM_FILE config.PlacementFile = arg elif opt in ('--no-trim-gerber',): config.TrimGerber = 0 elif opt in ('--no-trim-excellon',): config.TrimExcellon = 0 elif opt in ('-s', '--skipdisclaimer'): skipDisclaimer = 1 else: raise RuntimeError, "Unknown option: %s" % opt if len(args) > 2 or len(args) < 1: raise RuntimeError, 'Invalid number of arguments' if (skipDisclaimer == 0): disclaimer() # Load up the Jobs global dictionary, also filling out GAT, the # global aperture table and GAMT, the global aperture macro table. updateGUI("Reading job files...") config.parseConfigFile(args[0]) # Force all X and Y coordinates positive by adding absolute value of minimum X and Y # KHK: name contains the names of the individual "jobs". A job represents one printed curcuit board. # KHK: Jobs is a dictionary, which is generated in config.py, the name of the individual pcb is used as key in the dictionary. # KHK: "jobs.Job is a specific instance xyz" - jobs.Job is a class with its onw methods, i.e. fixcoordinates() etc. - can be interpreted as a Gerber layer of a job. # KHK: name, job are arbitrary temporary variables used as index to store the key and value of the dictionary for name, job in config.Jobs.iteritems(): min_x, min_y = job.mincoordinates() shift_x = shift_y = 0 if min_x < 0: shift_x = abs(min_x) if min_y < 0: shift_y = abs(min_y) if (shift_x > 0) or (shift_y > 0): job.fixcoordinates( shift_x, shift_y ) # Display job properties for job in config.Jobs.values(): print 'Job %s:' % job.name, if job.Repeat > 1: print '(%d instances)' % job.Repeat else: print print ' Extents: (%d,%d)-(%d,%d)' % (job.minx,job.miny,job.maxx,job.maxy) # add metric support (1/1000 mm vs. 1/100,000 inch) if config.Config['measurementunits'] == 'inch': print ' Size: %f" x %f"' % (job.width_in(), job.height_in()) else: print ' Size: %5.3fmm x %5.3fmm' % (job.width_in(), job.height_in()) print # Trim drill locations and flash data to board extents if config.TrimExcellon: updateGUI("Trimming Excellon data...") print 'Trimming Excellon data to board outlines ...' for job in config.Jobs.values(): job.trimExcellon() if config.TrimGerber: updateGUI("Trimming Gerber data...") print 'Trimming Gerber data to board outlines ...' for job in config.Jobs.values(): job.trimGerber() # We start origin at (0.1", 0.1") just so we don't get numbers close to 0 # which could trip up Excellon leading-0 elimination. # I don't want to change the origin. If this a code bug, then it should be fixed (SDD) OriginX = OriginY = 0 #0.1 # Read the layout file and construct the nested list of jobs. If there # is no layout file, do auto-layout. updateGUI("Performing layout...") print 'Performing layout ...' if len(args) > 1: Layout = parselayout.parseLayoutFile(args[1]) # Do the layout, updating offsets for each component job. X = OriginX + config.Config['leftmargin'] Y = OriginY + config.Config['bottommargin'] for row in Layout: row.setPosition(X, Y) Y += row.height_in() + config.Config['yspacing'] # Construct a canonical placement from the layout Place = placement.Placement() Place.addFromLayout(Layout) del Layout elif config.AutoSearchType == FROM_FILE: Place = placement.Placement() Place.addFromFile(config.PlacementFile, config.Jobs) else: # Do an automatic layout based on our tiling algorithm. tile = tile_jobs(config.Jobs.values()) Place = placement.Placement() Place.addFromTiling(tile, OriginX + config.Config['leftmargin'], OriginY + config.Config['bottommargin']) (MaxXExtent,MaxYExtent) = Place.extents() MaxXExtent += config.Config['rightmargin'] MaxYExtent += config.Config['topmargin'] # Start printing out the Gerbers. In preparation for drawing cut marks # and crop marks, make sure we have an aperture to draw with. Use a 10mil line. # If we're doing a fabrication drawing, we'll need a 1mil line. OutputFiles = [] try: fullname = config.MergeOutputFiles['placement'] except KeyError: fullname = 'merged.placement.txt' Place.write(fullname) OutputFiles.append(fullname) # For cut lines AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['cutlinewidth']) drawing_code_cut = aptable.findInApertureTable(AP) if drawing_code_cut is None: drawing_code_cut = aptable.addToApertureTable(AP) # For crop marks AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['cropmarkwidth']) drawing_code_crop = aptable.findInApertureTable(AP) if drawing_code_crop is None: drawing_code_crop = aptable.addToApertureTable(AP) # For fiducials drawing_code_fiducial_copper = drawing_code_fiducial_soldermask = None if config.Config['fiducialpoints']: AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['fiducialcopperdiameter']) drawing_code_fiducial_copper = aptable.findInApertureTable(AP) if drawing_code_fiducial_copper is None: drawing_code_fiducial_copper = aptable.addToApertureTable(AP) AP = aptable.Aperture(aptable.Circle, 'D??', config.Config['fiducialmaskdiameter']) drawing_code_fiducial_soldermask = aptable.findInApertureTable(AP) if drawing_code_fiducial_soldermask is None: drawing_code_fiducial_soldermask = aptable.addToApertureTable(AP) # For fabrication drawing. AP = aptable.Aperture(aptable.Circle, 'D??', 0.001) drawing_code1 = aptable.findInApertureTable(AP) if drawing_code1 is None: drawing_code1 = aptable.addToApertureTable(AP) updateGUI("Writing merged files...") print 'Writing merged output files ...' for layername in config.LayerList.keys(): lname = layername if lname[0]=='*': lname = lname[1:] try: fullname = config.MergeOutputFiles[layername] except KeyError: fullname = 'merged.%s.ger' % lname OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeGerberHeader(fid) # Determine which apertures and macros are truly needed apUsedDict = {} apmUsedDict = {} for job in Place.jobs: apd, apmd = job.aperturesAndMacros(layername) apUsedDict.update(apd) apmUsedDict.update(apmd) # Increase aperature sizes to match minimum feature dimension if config.MinimumFeatureDimension.has_key(layername): print ' Thickening', lname, 'feature dimensions ...' # Fix each aperture used in this layer for ap in apUsedDict.keys(): new = config.GAT[ap].getAdjusted( config.MinimumFeatureDimension[layername] ) if not new: ## current aperture size met minimum requirement continue else: ## new aperture was created new_code = aptable.findOrAddAperture(new) ## get name of existing aperture or create new one if needed del apUsedDict[ap] ## the old aperture is no longer used in this layer apUsedDict[new_code] = None ## the new aperture will be used in this layer # Replace all references to the old aperture with the new one for joblayout in Place.jobs: job = joblayout.job ##access job inside job layout temp = [] if job.hasLayer(layername): for x in job.commands[layername]: if x == ap: temp.append(new_code) ## replace old aperture with new one else: temp.append(x) ## keep old command job.commands[layername] = temp if config.Config['cutlinelayers'] and (layername in config.Config['cutlinelayers']): apUsedDict[drawing_code_cut]=None if config.Config['cropmarklayers'] and (layername in config.Config['cropmarklayers']): apUsedDict[drawing_code_crop]=None if config.Config['fiducialpoints']: if ((layername=='*toplayer') or (layername=='*bottomlayer')): apUsedDict[drawing_code_fiducial_copper] = None elif ((layername=='*topsoldermask') or (layername=='*bottomsoldermask')): apUsedDict[drawing_code_fiducial_soldermask] = None # Write only necessary macro and aperture definitions to Gerber file writeApertureMacros(fid, apmUsedDict) writeApertures(fid, apUsedDict) #for row in Layout: # row.writeGerber(fid, layername) # # Do cut lines # if config.Config['cutlinelayers'] and (layername in config.Config['cutlinelayers']): # fid.write('%s*\n' % drawing_code_cut) # Choose drawing aperture # row.writeCutLines(fid, drawing_code_cut, OriginX, OriginY, MaxXExtent, MaxYExtent) # Finally, write actual flash data for job in Place.jobs: updateGUI("Writing merged output files...") job.writeGerber(fid, layername) if config.Config['cutlinelayers'] and (layername in config.Config['cutlinelayers']): fid.write('%s*\n' % drawing_code_cut) # Choose drawing aperture #print "writing drawcode_cut: %s" % drawing_code_cut job.writeCutLines(fid, drawing_code_cut, OriginX, OriginY, MaxXExtent, MaxYExtent) if config.Config['cropmarklayers']: if layername in config.Config['cropmarklayers']: writeCropMarks(fid, drawing_code_crop, OriginX, OriginY, MaxXExtent, MaxYExtent) if config.Config['fiducialpoints']: if ((layername=='*toplayer') or (layername=='*bottomlayer')): writeFiducials(fid, drawing_code_fiducial_copper, OriginX, OriginY, MaxXExtent, MaxYExtent) elif ((layername=='*topsoldermask') or (layername=='*bottomsoldermask')): writeFiducials(fid, drawing_code_fiducial_soldermask, OriginX, OriginY, MaxXExtent, MaxYExtent) # KHK Patch Begin: added for Seeed Studio to generate an outline for all panelized gerber files if config.Config['outlinelayers'] and (layername in config.Config['outlinelayers']): writeOutline(fid, OriginX, OriginY, MaxXExtent, MaxYExtent) # KHK Patch End: added for Seeed Studio to generate an outline for all panelized gerber files writeGerberFooter(fid) fid.close() # Write board outline layer if selected fullname = config.Config['outlinelayerfile'] if fullname and fullname.lower() != "none": OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeGerberHeader(fid) # Write width-1 aperture to file # add metric support if config.Config['measurementunits'] == 'inch': AP = aptable.Aperture(aptable.Circle, 'D10', 0.001) else: AP = aptable.Aperture(aptable.Circle, 'D10', 0.25) # we'll use 0.25 mm - same as Diptrace AP.writeDef(fid) # Choose drawing aperture D10 fid.write('D10*\n') # Draw the rectangle fid.write('X%07dY%07dD02*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(MaxYExtent))) # Top-left fid.write('X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(MaxYExtent))) # Top-right fid.write('X%07dY%07dD01*\n' % (util.in2gerb(MaxXExtent), util.in2gerb(OriginY))) # Bottom-right fid.write('X%07dY%07dD01*\n' % (util.in2gerb(OriginX), util.in2gerb(OriginY))) # Bottom-left writeGerberFooter(fid) fid.close() # Write scoring layer if selected fullname = config.Config['scoringfile'] if fullname and fullname.lower() != "none": OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeGerberHeader(fid) # Write width-1 aperture to file AP = aptable.Aperture(aptable.Circle, 'D10', 0.001) AP.writeDef(fid) # Choose drawing aperture D10 fid.write('D10*\n') # Draw the scoring lines scoring.writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent) writeGerberFooter(fid) fid.close() # Get a list of all tools used by merging keys from each job's dictionary # of tools. if 0: Tools = {} for job in config.Jobs.values(): for key in job.xcommands.keys(): Tools[key] = 1 Tools = Tools.keys() Tools.sort() else: toolNum = 0 # First construct global mapping of diameters to tool numbers for job in config.Jobs.values(): for tool,diam in job.xdiam.items(): if config.GlobalToolRMap.has_key(diam): continue toolNum += 1 config.GlobalToolRMap[diam] = "T%02d" % toolNum # Cluster similar tool sizes to reduce number of drills if config.Config['drillclustertolerance'] > 0: config.GlobalToolRMap = drillcluster.cluster( config.GlobalToolRMap, config.Config['drillclustertolerance'] ) drillcluster.remap( Place.jobs, config.GlobalToolRMap.items() ) # Now construct mapping of tool numbers to diameters for diam,tool in config.GlobalToolRMap.items(): config.GlobalToolMap[tool] = diam # Tools is just a list of tool names Tools = config.GlobalToolMap.keys() Tools.sort() fullname = config.Config['fabricationdrawingfile'] if fullname and fullname.lower() != 'none': if len(Tools) > strokes.MaxNumDrillTools: raise RuntimeError, "Only %d different tool sizes supported for fabrication drawing." % strokes.MaxNumDrillTools OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeGerberHeader(fid) writeApertures(fid, {drawing_code1: None}) fid.write('%s*\n' % drawing_code1) # Choose drawing aperture fabdrawing.writeFabDrawing(fid, Place, Tools, OriginX, OriginY, MaxXExtent, MaxYExtent) writeGerberFooter(fid) fid.close() # Finally, print out the Excellon try: fullname = config.MergeOutputFiles['drills'] except KeyError: fullname = 'merged.drills.xln' OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') writeExcellonHeader(fid) # Ensure each one of our tools is represented in the tool list specified # by the user. for tool in Tools: try: size = config.GlobalToolMap[tool] except: raise RuntimeError, "INTERNAL ERROR: Tool code %s not found in global tool map" % tool writeExcellonTool(fid, tool, size) #for row in Layout: # row.writeExcellon(fid, size) for job in Place.jobs: job.writeExcellon(fid, size) writeExcellonFooter(fid) fid.close() updateGUI("Closing files...") # Compute stats jobarea = 0.0 #for row in Layout: # jobarea += row.jobarea() for job in Place.jobs: jobarea += job.jobarea() totalarea = ((MaxXExtent-OriginX)*(MaxYExtent-OriginY)) ToolStats = {} drillhits = 0 for tool in Tools: ToolStats[tool]=0 #for row in Layout: # hits = row.drillhits(config.GlobalToolMap[tool]) # ToolStats[tool] += hits # drillhits += hits for job in Place.jobs: hits = job.drillhits(config.GlobalToolMap[tool]) ToolStats[tool] += hits drillhits += hits try: fullname = config.MergeOutputFiles['toollist'] except KeyError: fullname = 'merged.toollist.drl' OutputFiles.append(fullname) #print 'Writing %s ...' % fullname fid = file(fullname, 'wt') print '-'*50 # add metric support (1/1000 mm vs. 1/100,000 inch) if config.Config['measurementunits'] == 'inch': print ' Job Size : %f" x %f"' % (MaxXExtent-OriginX, MaxYExtent-OriginY) print ' Job Area : %.2f sq. in.' % totalarea else: print ' Job Size : %.2fmm x %.2fmm' % (MaxXExtent-OriginX, MaxYExtent-OriginY) print ' Job Area : %.0f mm2' % totalarea print ' Area Usage : %.1f%%' % (jobarea/totalarea*100) print ' Drill hits : %d' % drillhits if config.Config['measurementunits'] == 'inch': print 'Drill density : %.1f hits/sq.in.' % (drillhits/totalarea) else: print 'Drill density : %.2f hits/cm2' % (100*drillhits/totalarea) print '\nTool List:' smallestDrill = 999.9 for tool in Tools: if ToolStats[tool]: if config.Config['measurementunits'] == 'inch': fid.write('%s %.4fin\n' % (tool, config.GlobalToolMap[tool])) print ' %s %.4f" %5d hits' % (tool, config.GlobalToolMap[tool], ToolStats[tool]) else: fid.write('%s %.4fmm\n' % (tool, config.GlobalToolMap[tool])) print ' %s %.4fmm %5d hits' % (tool, config.GlobalToolMap[tool], ToolStats[tool]) smallestDrill = min(smallestDrill, config.GlobalToolMap[tool]) fid.close() if config.Config['measurementunits'] == 'inch': print "Smallest Tool: %.4fin" % smallestDrill else: print "Smallest Tool: %.4fmm" % smallestDrill print print 'Output Files :' for f in OutputFiles: print ' ', f if (MaxXExtent-OriginX)>config.Config['panelwidth'] or (MaxYExtent-OriginY)>config.Config['panelheight']: print '*'*75 print '*' # add metric support (1/1000 mm vs. 1/100,000 inch) if config.Config['measurementunits'] == 'inch': print '* ERROR: Merged job exceeds panel dimensions of %.1f"x%.1f"' % (config.Config['panelwidth'],config.Config['panelheight']) else: print '* ERROR: Merged job exceeds panel dimensions of %.1fmmx%.1fmm' % (config.Config['panelwidth'],config.Config['panelheight']) print '*' print '*'*75 sys.exit(1) # Done! return 0
def writeScoring(fid, Place, OriginX, OriginY, MaxXExtent, MaxYExtent): # For each job, write out 4 score lines, above, to the right, below, and # to the left. After we collect all potential scoring lines, we worry # about merging, etc. dx = config.Config['xspacing'] / 2.0 dy = config.Config['yspacing'] / 2.0 extents = (OriginX, OriginY, MaxXExtent, MaxYExtent) Lines = [] for layout in Place.jobs: x = layout.x - dx y = layout.y - dy X = layout.x + layout.width_in() + dx Y = layout.y + layout.height_in() + dy # Just so we don't get 3.75000000004 and 3.75000000009, we round to # 2.5 limits. x, y, X, Y = [round(val, 5) for val in [x, y, X, Y]] if config.Config['scoringstyle'] and config.Config[ 'scoringstyle'] == 'surround': # Scoring lines go all the way across the panel now addHorizontalLine(Lines, x, X, Y, extents) # above job addVerticalLine(Lines, X, y, Y, extents) # to the right of job addHorizontalLine(Lines, x, X, y, extents) # below job addVerticalLine(Lines, x, y, Y, extents) # to the left of job else: addHorizontalLine(Lines, OriginX, MaxXExtent, Y, extents) # above job addVerticalLine(Lines, X, OriginY, MaxYExtent, extents) # to the right of job addHorizontalLine(Lines, OriginX, MaxXExtent, y, extents) # below job addVerticalLine(Lines, x, OriginY, MaxYExtent, extents) # to the left of job # Combine disparate lines into single lines Lines = mergeLines(Lines) #for line in Lines: # print [round(x,3) for x in line] # Write 'em out if config.Config['fiducialpoints'] and config.Config[ 'fiducialpoints'] == 'scoring': # If fiducialpoints == 'scoring' rewrite it now to mark out the scoring lines newFiducuals = [] for line in Lines: # Fiducials at 10% in from each end of the line if isHorizontal(line): offset = (line[2] - line[0]) * 0.1 newFiducuals.append(line[0] + offset) newFiducuals.append(line[1]) newFiducuals.append(line[2] - offset) newFiducuals.append(line[3]) else: offset = (line[3] - line[1]) * 0.1 newFiducuals.append(line[0]) newFiducuals.append(line[1] + offset) newFiducuals.append(line[2]) newFiducuals.append(line[3] - offset) # Fiducials at the end of each line, unless it's a board edge # (in other words, line intersections) # I think this makes it a bit cluttered if 0: if not (line[0] == OriginX or line[1] == OriginY or line[0] == MaxXExtent or line[1] == MaxYExtent): newFiducuals.append(line[0]) newFiducuals.append(line[1]) if not (line[2] == OriginX or line[3] == OriginY or line[2] == MaxXExtent or line[3] == MaxYExtent): newFiducuals.append(line[2]) newFiducuals.append(line[3]) # Fiducials at 50% of each line if isHorizontal(line): offset = (line[2] - line[0]) * 0.5 newFiducuals.append(line[0] + offset) newFiducuals.append(line[1]) else: offset = (line[3] - line[1]) * 0.5 newFiducuals.append(line[0]) newFiducuals.append(line[1] + offset) config.Config['fiducialpoints'] = ','.join(map(str, newFiducuals)) if fid != None: for line in Lines: makestroke.drawPolyline(fid, [(util.in2gerb(line[0]),util.in2gerb(line[1])), \ (util.in2gerb(line[2]),util.in2gerb(line[3]))], 0, 0)