Example #1
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)
Example #2
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)
Example #3
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)
Example #4
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)
Example #5
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)
Example #6
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.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)
Example #7
0
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)
Example #8
0
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
Example #9
0
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)
Example #10
0
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))
Example #11
0
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))
Example #12
0
  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)
Example #13
0
    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)
Example #14
0
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)))
Example #15
0
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)))
Example #16
0
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
Example #17
0
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)))
Example #18
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 = ' '
Example #19
0
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)))
Example #20
0
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
Example #21
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 = ' '
Example #22
0
  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
Example #23
0
        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()
Example #24
0
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)))
Example #25
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

        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)
Example #26
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)
Example #27
0
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)))
Example #28
0
  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)
Example #29
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          # 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)
Example #30
0
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
Example #31
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)