Commit 38f36ae4 authored by John Beard's avatar John Beard Committed by jean-pierre charras
Browse files

Add John Beard's patch: new and useful footprint wizards (SIP/SIP, SOIC/SSOP/TSSOP/MSOP and BGA).

Add Python utility tools to make wizard scripts more easy to write.
parent 61fe97e6
Loading
Loading
Loading
Loading
+134 −0
Original line number Original line Diff line number Diff line
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#

import pcbnew

class FootprintWizardDrawingAids:
    """
    Collection of handy functions to simplify drawing shapes from within
    footprint wizards

    A "drawing context" is provided which can be used to set and retain
    settings such as line width and layer
    """
    def __init__(self, module):
        self.module = module
        #drawing context defaults
        self.dc = {
            'layer': pcbnew.SILKSCREEN_N_FRONT,
            'width': pcbnew.FromMM(0.2)
        }

    def SetWidth(self, width):
        self.dc['width'] = width

    def SetLayer(self, layer):
        self.dc['layer'] = layer

    def Line(self, x1, y1, x2, y2):

        outline = pcbnew.EDGE_MODULE(self.module)
        outline.SetWidth(self.dc['width'])
        outline.SetLayer(self.dc['layer'])
        outline.SetShape(pcbnew.S_SEGMENT)
        start = pcbnew.wxPoint(x1, y1)
        end = pcbnew.wxPoint(x2, y2)
        outline.SetStartEnd(start, end)
        self.module.Add(outline)

    # extends from (x1,y1) right
    def HLine(self, x, y, l):
        """
        Draw a horizontal line from (x,y), rightwards
        """
        self.Line(x, y, x + l, y)

    def VLine(self, x, y, l):
        """
        Draw a vertical line from (x1,y1), downwards
        """
        self.Line(x, y, x, y + l)

    def Polyline(self, pts):

        if len(pts) < 2:
            return

        for i in range(0, len(pts) - 1):
            self.Line(pts[i][0], pts[i][1], pts[i+1][0], pts[i+1][1])

    def Reference(self, x, y, size):
        """
        Draw the module's reference as the given point.

        The actual setting of the reference is not done in this drawing
        aid - that is up to the wizard
        """

        text_size = pcbnew.wxSize(size, size)

        self.module.Reference().SetPos0(pcbnew.wxPoint(x, y))
        self.module.Reference().SetTextPosition(self.module.Reference().GetPos0())
        self.module.Reference().SetSize(text_size)

    def Value(self, x, y, size):
        """
        As for references, draw the module's value
        """
        text_size = pcbnew.wxSize(size, size)

        self.module.Value().SetPos0(pcbnew.wxPoint(x, y))
        self.module.Value().SetTextPosition(self.module.Value().GetPos0())
        self.module.Value().SetSize(text_size)

    def Box(self, x, y, w, h):
        """
        Draw a rectangular box, centred at (x,y), with given width and
        height
        """
        self.VLine(x - w/2, y - h/2, h) # left
        self.VLine(x + w/2, y - h/2, h) # right
        self.HLine(x - w/2, y + h/2, w) # bottom
        self.HLine(x - w/2, y - h/2, w) # top

    def NotchedBox(self, x, y, w, h, notchW, notchH):
        """
        Draw a box with a notch in the top edge
        """
        #limit to half the overall width
        notchW = min(x + w/2, notchW)

        # draw notch
        self.Polyline([ #three sides of box
                        (x - w/2, y - h/2),
                        (x - w/2, y + h/2),
                        (x + w/2, y + h/2),
                        (x + w/2, y - h/2),
                        #the notch
                        (notchW/2, y - h/2),
                        (notchW/2, y - h/2 + notchH),
                        (-notchW/2, y - h/2 + notchH),
                        (-notchW/2, y - h/2),
                        (x - w/2, y - h/2)
                    ])

    def BoxWithDiagonalAtCorner(self, x, y, w, h, diagSetback):

        self.Box(x, y, w, h)

        #diagonal corner
        self.Line(x - w/2 + diagSetback, x - h/2, x - w/2,
                x - h/2 + diagSetback)
+250 −0
Original line number Original line Diff line number Diff line
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#

import pcbnew
import FootprintWizardDrawingAids

class FootprintWizardParameterManager:
    """
    Functions for helpfully managing parameters to a KiCAD Footprint
    Wizard.

    Abstracts away from whatever structure is used by pcbnew's footprint
    wizard class
    """

    def __init__(self):
        self.parameters = {}
        self.GenerateParameterList()

    def GenerateParameterList(self):
        """
        Construct parameters here, or leave out to have no parameters
        """
        pass

    def CheckParameters(self):
        """
        Implement this to make checks on parameter values, filling
        parameter_errors (or using the checker routines)

        Subclasses can implment their own and override the parent
        defaults and add new ones
        """
        pass

    uMM = 1
    uMils = 2
    uNatural = 3
    uBool = 4

    def AddParam(self, section, param, unit, default, hint = ''):
        """
        Add a parameter with some properties.

        TODO: Hints are not supported, as there is as yet nowhere to
        put them in the KiCAD interface
        """

        val = None
        if unit == self.uMM:
            val = pcbnew.FromMM(default)
        elif unit == self.uMils:
            val = pcbnew.FromMils(default)
        elif unit == self.uNatural:
            val = default
        elif unit == self.uBool:
            val = "True" if default else "False" #ugly stringing
        else:
            print "Warning: Unknown unit type: %s" % unit
            return

        if unit in [self.uNatural, self.uBool]:
            param = "*%s" % param #star prefix for natural

        if section not in self.parameters:
            self.parameters[section] = {}

        self.parameters[section][param] = val

    def _PrintParameterTable(self):
        """
        Pretty-print the parameters we have
        """
        for name, section in self.parameters.iteritems():
            print "  %s:" % name

            for key, value in section.iteritems():
                unit = ""
                if (type(value) is int or type(value) is float) and not "*" in key:
                    unit = "mm"

                if "*" in key:
                    key = key[1:]
                else:
                    value = pcbnew.ToMM(value)

                print "    %s: %s%s" % (key, value, unit)

    def _ParametersHaveErrors(self):
        """
        Return true if we discovered errors suring parameter processing
        """

        for name, section in self.parameter_errors.iteritems():
            for k, v in section.iteritems():
                if v:
                    return True

        return False

    def _PrintParameterErrors(self):
        """
        Pretty-print parameters with errors
        """

        for name, section in self.parameter_errors.iteritems():
            printed_section = False

            for key, value in section.iteritems():
                if value:
                    if not printed_section:
                        print "  %s:" % name

                    print "       %s: %s (have %s)" % (key, value,
                                        self.parameters[name][key])

    def ProcessParameters(self):
        """
        Make sure the parameters we have meet whatever expectations the
        footprint wizard has of them
        """

        self.ClearErrors()
        self.CheckParameters();

        if self._ParametersHaveErrors():
            print "Cannot build footprint: Parameters have errors:"
            self._PrintParameterErrors()
            return False

        print "Building new %s footprint with the following parameters:" % self.name

        self._PrintParameterTable()
        return True

    #################################################################
    # PARAMETER CHECKERS
    #################################################################

    def CheckParamPositiveInt(self, section, param, min_value = 1,
                                max_value = None, is_multiple_of = 1):
        """
        Make sure a parameter can be made into an int, and enforce
        limits if required
        """

        try:
            self.parameters[section][param] = int(self.parameters[section][param])
        except ValueError:
            self.parameter_errors[section][param] = "Must be a valid integer"
            return

        if min_value is not None and (self.parameters[section][param] < min_value):
            self.parameter_errors[section][param] = "Must be greater than or equal to %d" % (min_value)
            return

        if max_value is not None and (self.parameters[section][param] > min_value):
            self.parameter_errors[section][param] = "Must be less than or equal to %d" % (max_value)
            return

        if is_multiple_of > 1 and (self.parameters[section][param] % is_multiple_of) > 0:
            self.parameter_errors[section][param] = "Must be a multiple of %d" % is_multiple_of
            return

        return

    def CheckParamBool(self, section, param):
        """
        Make sure a parameter looks like a boolean, convert to native
        boolean type if so
        """
        if str(self.parameters[section][param]).lower() in ["true", "t", "y", "yes", "on", "1", "1.0"]:
            self.parameters[section][param] = True;
            return
        elif str(self.parameters[section][param]).lower() in ["false", "f", "n", "no", "off", "0", "0.0"]:
            self.parameters[section][param] = False;
            return

        self.parameter_errors[section][param] = "Must be boolean (true/false)"
        return


class HelpfulFootprintWizardPlugin(pcbnew.FootprintWizardPlugin,
                                    FootprintWizardParameterManager):
    """
    A class to simplify many aspects of footprint creation, leaving only
    the foot-print specific routines to the wizards themselves

    Generally, you need to implement:
        GetReference()
        GetValue()
        GenerateParameterList()
        CheckParameters()
        BuildThisFootprint()
        GetName()
        GetDescription()
    """
    def __init__(self):
        pcbnew.FootprintWizardPlugin.__init__(self)
        FootprintWizardParameterManager.__init__(self)

        self.name = self.GetName()
        self.decription = self.GetDescription()
        self.image = self.GetImage()

    def GetReference(self):
        raise NotImplementedError

    def GetValuePrefix(self):
        return "U" # footprints needing wizards of often ICs

    def GetImage(self):
        return ""

    def BuildThisFootprint(self):
        raise NotImplementedError

    def BuildFootprint(self):
        """
        Actually make the footprint. We defer all but the setup to
        the implmenting class
        """

        if not self.ProcessParameters():
            return

        self.module = pcbnew.MODULE(None) # create a new module

        self.draw = FootprintWizardDrawingAids.FootprintWizardDrawingAids(self.module)

        self.module.SetReference(self.GetReference())
        self.module.SetValue("%s**" % self.GetValuePrefix())

        fpid = pcbnew.FPID(self.module.GetReference())   #the name in library
        self.module.SetFPID( fpid )

        self.BuildThisFootprint() # implementer's build function
+148 −0
Original line number Original line Diff line number Diff line

import pcbnew

class PadMaker:
    """
    Useful construction functions for common types of pads
    """

    def __init__(self, module):
        self.module = module

    def THPad(self, w, l, drill, shape = pcbnew.PAD_OVAL):
        pad = pcbnew.D_PAD(self.module)

        pad.SetSize(pcbnew.wxSize(l, w))

        pad.SetShape(shape)

        pad.SetAttribute(pcbnew.PAD_STANDARD)
        pad.SetLayerMask(pcbnew.PAD_STANDARD_DEFAULT_LAYERS)
        pad.SetDrillSize(pcbnew.wxSize(drill, drill))

        return pad

    def SMDPad(self, w, l, shape = pcbnew.PAD_RECT):
        pad = pcbnew.D_PAD(self.module)
        pad.SetSize(pcbnew.wxSize(l, w))

        pad.SetShape(shape)

        pad.SetAttribute(pcbnew.PAD_SMD)
        pad.SetLayerMask(pcbnew.PAD_SMD_DEFAULT_LAYERS)

        return pad

    def SMTRoundPad(self, size):
        pad = self.SMDPad(size, size, shape = pcbnew.PAD_CIRCLE)
        return pad

class PadArray:

    def __init__(self):
        self.firstPad = 1;

    def SetFirstPadInArray(self, fpNum):
        self.firstPad = fpNum

    # HACK! pad should one day have its own clone method
    def ClonePad(self):

        pad = pcbnew.D_PAD(self.pad.GetParent())

        pad.SetSize(self.pad.GetSize())
        pad.SetShape(self.pad.GetShape())
        pad.SetAttribute(self.pad.GetAttribute())
        pad.SetLayerMask(self.pad.GetLayerMask())
        pad.SetDrillSize(self.pad.GetDrillSize())

        return pad

    def AddPad(self, pad):
        self.pad.GetParent().Add(pad)

class PadGridArray(PadArray):

    def __init__(self, pad, nx, ny, px, py, pin1Pos):
        # this pad is more of a "context", we will use it as a source of
        # pad data, but not actually add it
        self.pad = pad
        self.nx = int(nx)
        self.ny = int(ny)
        self.px = px
        self.py = py
        self.pin1Pos = pin1Pos

    # handy utility function 1 - A, 2 - B, 26 - AA, etc
    # aIndex = 0 for 0 - A
    def AlphaNameFromNumber(self, n, aIndex = 1):

        div, mod = divmod(n - aIndex, 26)
        alpha = chr(65 + mod)

        if div > 0:
            return self.AlphaNameFromNumber(div) + alpha;

        return alpha;

    # right to left, top to bottom
    def NamingFunction(self, x, y):
        return self.firstPad + (self.nx * y + x)

    #relocate the pad and add it as many times as we need
    def AddPadsToModule(self):

        for x in range(0, self.nx):
            for y in range(self.ny):
                posX = self.pin1Pos.x + (self.px * x)
                posY = self.pin1Pos.y + (self.py * y)

                pos = pcbnew.wxPoint(posX, posY)

                # THIS DOESN'T WORK yet!
                #pad = self.pad.Clone()
                pad = self.ClonePad()

                pad.SetPos0(pos)
                pad.SetPosition(pos)

                pad.SetPadName(str(self.NamingFunction(x,y)))

                self.AddPad(pad)

class PadLineArray(PadGridArray):

    def __init__(self, pad, n, pitch, isVertical, pin1Pos):

        if isVertical:
            PadGridArray.__init__(self, pad, 1, n, 0, pitch, pin1Pos)
        else:
            PadGridArray.__init__(self, pad, n, 1, pitch, 0, pin1Pos)

class RectPadArray(PadArray):

    def __init__(self, nx, ny, pitch, xpitch, ypitch, pin1Pos):

        #left row
        pin1Pos = pcbnew.wxPoint(-h_pitch / 2, -row_len / 2)
        array = PadLineArray(h_pad, pads_per_row, pad_pitch, True, pin1Pos)
        array.SetFirstPadInArray(1)
        array.AddPadsToModule()

        #bottom row
        pin1Pos = pcbnew.wxPoint(-row_len / 2, v_pitch / 2)
        array = PA.PadLineArray(v_pad, pads_per_row, pad_pitch, False, pin1Pos)
        array.SetFirstPadInArray(pads_per_row + 1)
        array.AddPadsToModule()

        #right row
        pin1Pos = pcbnew.wxPoint(h_pitch / 2, row_len / 2)
        array = PadLineArray(h_pad, pads_per_row, -pad_pitch, True, pin1Pos)
        array.SetFirstPadInArray(2*pads_per_row + 1)
        array.AddPadsToModule()

        #top row
        pin1Pos = pcbnew.wxPoint(row_len / 2, -v_pitch / 2)
        array = PadLineArray(v_pad, pads_per_row, -pad_pitch, False, pin1Pos)
        array.SetFirstPadInArray(3*pads_per_row + 1)
        array.AddPadsToModule()
+98 −0
Original line number Original line Diff line number Diff line
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#

from __future__ import division
import pcbnew

import HelpfulFootprintWizardPlugin as HFPW
import PadArray as PA


class BGAPadGridArray(PA.PadGridArray):

    def NamingFunction(self, x, y):
        return "%s%d" % (self.AlphaNameFromNumber(y + 1), x + 1)


class BGAWizard(HFPW.HelpfulFootprintWizardPlugin):

    def GetName(self):
        return "BGA"

    def GetDescription(self):
        return "Ball Grid Array Footprint Wizard"

    def GenerateParameterList(self):

        self.AddParam("Pads", "pad pitch", self.uMM, 1)
        self.AddParam("Pads", "pad size", self.uMM, 0.5)
        self.AddParam("Pads", "row count", self.uNatural, 5)
        self.AddParam("Pads", "column count", self.uNatural, 5)
        self.AddParam("Pads", "outline x margin", self.uMM, 1)
        self.AddParam("Pads", "outline y margin", self.uMM, 1)

    def CheckParameters(self):

        self.CheckParamPositiveInt("Pads", "*row count")
        self.CheckParamPositiveInt("Pads", "*column count")


    def GetReference(self):

        pins = self.parameters["Pads"]["*row count"] * self.parameters["Pads"]["*column count"]

        return "BGA %d" % pins


    def GetValuePrefix(self):
        return "U"


    def BuildThisFootprint(self):

        pads = self.parameters["Pads"]

        rows = pads["*row count"]
        cols = pads["*column count"]
        pad_size = pads["pad size"]

        pad_size = pcbnew.wxSize(pad_size, pad_size)

        pad_pitch = pads["pad pitch"]

        # add in the pads
        pad = PA.PadMaker(self.module).SMTRoundPad(pads["pad size"])

        pin1Pos = pcbnew.wxPoint(-((rows - 1) * pad_pitch) / 2,
                                -((cols - 1) * pad_pitch) / 2)

        array = BGAPadGridArray(pad, rows, cols, pad_pitch, pad_pitch, pin1Pos)
        array.AddPadsToModule()

        #box
        ssX = -pin1Pos.x + pads["outline x margin"]
        ssY = -pin1Pos.y + pads["outline y margin"]

        self.draw.BoxWithDiagonalAtCorner(0, 0, ssX*2, ssY*2, pads["outline x margin"])

        #reference and value
        textSize = pcbnew.FromMM(0.8)

        self.draw.Value(0, - ssY - textSize, textSize)
        self.draw.Reference(0, ssY + textSize, textSize)


BGAWizard().register()
+80 −226

File changed.

Preview size limit exceeded, changes collapsed.

Loading