Commit c1e0a8d0 authored by unknown's avatar unknown Committed by jean-pierre charras
Browse files

Patch for the Python footprint wizard helpers. This adds a few more drawing...

Patch for the Python footprint wizard helpers. This adds a few more drawing functions, such as for circles, and also uses a matrix-based transform stack with greatly simplifies constructing footprints consisting of regularly spaced elements (e.g. in lines, grids, circles, or some list of points that you specify).
This fixes bug #1366299
parent 1edd8c8a
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ class FPCFootprintWizard(FootprintWizardPlugin):
            pad.SetSize(size)
            pad.SetShape(PAD_RECT)
            pad.SetAttribute(PAD_SMD)
            pad.SetLayerSet( pad.StandardMask() )
            pad.SetLayerSet( pad.SMDMask() )
            pad.SetPos0(pos)
            pad.SetPosition(pos)
            pad.SetPadName(name)
+403 −38
Original line number Diff line number Diff line
@@ -14,7 +14,11 @@
#  MA 02110-1301, USA.
#

from __future__ import division

import pcbnew
import math


class FootprintWizardDrawingAids:
    """
@@ -24,31 +28,291 @@ class FootprintWizardDrawingAids:
    A "drawing context" is provided which can be used to set and retain
    settings such as line width and layer
    """

    # directions (in degrees, compass-like)
    dirN = 0
    dirNE = 45
    dirE = 90
    dirSE = 135
    dirS = 180
    dirSW = 225
    dirW = 270
    dirNW = 315

    # flip constants
    flipNone = 0
    flipX = 1  # flip X values, i.e. about Y
    flipY = 2  # flip Y valuersabout X
    flipBoth = 3

    xfrmIDENTITY = [1, 0, 0, 0, 1, 0]  # no transform

    def __init__(self, module):
        self.module = module
        # drawing context defaults
        self.dc = {
            'layer': pcbnew.SILKSCREEN_N_FRONT,
            'width': pcbnew.FromMM(0.2)
            'layer': pcbnew.F_SilkS,
            'width': pcbnew.FromMM(0.2),
            'transforms': [],
            'transform': self.xfrmIDENTITY
        }

    def PushTransform(self, mat):
        """
        Add a transform to the top of the stack and recompute the
        overall transform
        """
        self.dc['transforms'].append(mat)
        self.RecomputeTransforms()

    def PopTransform(self, num=1):
        """
        Remove a transform from the top of the stack and recompute the
        overall transform
        """

        for i in range(num):
            mat = self.dc['transforms'].pop()
        self.RecomputeTransforms()
        return mat

    def ResetTransform(self):
        """
        Reset the transform stack to the identity matrix
        """
        self.dc['transforms'] = []
        self.RecomputeTransforms()

    def _ComposeMatricesWithIdentity(self, mats):
        """
        Compose a sequence of matrices together by sequential
        pre-mutiplciation with the identity matrix
        """

        x = self.xfrmIDENTITY

        for mat in mats:
            #precompose with each transform in turn
            x = [
                x[0] * mat[0] + x[1] * mat[3],
                x[0] * mat[1] + x[1] * mat[4],
                x[0] * mat[2] + x[1] * mat[5] + x[2],
                x[3] * mat[0] + x[4] * mat[3],
                x[3] * mat[1] + x[4] * mat[4],
                x[3] * mat[2] + x[4] * mat[5] + x[5]]

        return x

    def RecomputeTransforms(self):
        """
        Re-compute the transform stack into a single transform and
        store in the DC
        """
        self.dc['transform'] = self._ComposeMatricesWithIdentity(
            self.dc['transforms'])

    def TransformTranslate(self, x, y, push=True):
        """
        Set up and return a transform matrix representing a translartion
        optionally pushing onto the stack

        (   1  0   x  )
        (   0  1   y  )
        """
        mat = [1, 0, x, 0, 1, y]

        if push:
            self.PushTransform(mat)
        return mat

    def TransformFlipOrigin(self, flip, push=True):
        """
        Set up and return a transform matrix representing a horizontal,
        vertical or both flip about the origin
        """
        mat = None
        if flip == self.flipX:
            mat = [-1, 0, 0, 0, 1, 0]
        elif flip == self.flipY:
            mat = [1, 0, 0, 0, -1, 0]
        elif flip == self.flipBoth:
            mat = [-1, 0, 0, 0, -1, 0]
        elif flip == self.flipNone:
            mat = self.xfrmIDENTITY
        else:
            raise ValueError

        if push:
            self.PushTransform(mat)
        return mat

    def TransformFlip(self, x, y, flip=flipNone, push=True):
        """
        Set up and return a transform matrix representing a horizontal,
        vertical or both flip about a point (x,y)

        This is performed by a translate-to-origin, flip, translate-
        back sequence
        """
        mats = [self.TransformTranslate(x, y, push=False),
                self.TransformFlipOrigin(flip, push=False),
                self.TransformTranslate(-x, -y, push=False)]

        #distill into a single matrix
        mat = self._ComposeMatricesWithIdentity(mats)

        if push:
            self.PushTransform(mat)
        return mat

    def TransformRotationOrigin(self, rot, push=True):
        """
        Set up and return a transform matrix representing a rotation
        about the origin, and optionally push onto the stack

        (   cos(t)  -sin(t)   0  )
        (   sin(t)   cos(t)   0  )
        """
        rads = rot * math.pi / 180
        mat = [math.cos(rads), -math.sin(rads), 0,
               math.sin(rads), math.cos(rads), 0]

        if push:
            self.PushTransform(mat)
        return mat

    def TransformRotation(self, x, y, rot, push=True):
        """
        Set up and return a transform matrix representing a rotation
        about the pooint (x,y), and optionally push onto the stack

        This is performed by a translate-to-origin, rotate, translate-
        back sequence
        """

        mats = [self.TransformTranslate(x, y, push=False),
                self.TransformRotationOrigin(rot, push=False),
                self.TransformTranslate(-x, -y, push=False)]

        #distill into a single matrix
        mat = self._ComposeMatricesWithIdentity(mats)

        if push:
            self.PushTransform(mat)
        return mat

    def TransformScaleOrigin(self, sx, sy=None, push=True):
        """
        Set up and return a transform matrix representing a scale about
        the origin, and optionally push onto the stack

        (   sx   0   0  )
        (    0  sy   0  )
        """

        if sy is None:
            sy = sx

        mat = [sx, 0, 0, 0, sy, 0]

        if push:
            self.PushTransform(mat)
        return mat

    def TransformPoint(self, x, y, mat=None):
        """
        Return a point (x, y) transformed by the given matrix, or if
        that is not given, the drawing context transform
        """

        if not mat:
            mat = self.dc['transform']

        return pcbnew.wxPoint(x * mat[0] + y * mat[1] + mat[2],
                              x * mat[3] + y * mat[4] + mat[5])

    def SetWidth(self, width):
        """
        Set the current pen width used for subsequent drawing
        operations
        """
        self.dc['width'] = width

    def GetWidth(self):
        """
        Get the current drawing context width
        """
        return self.dc['width']

    def SetLayer(self, layer):
        """
        Set the current drawing layer, used for subsequent drawing
        operations
        """
        self.dc['layer'] = layer

    def Line(self, x1, y1, x2, y2):
        """
        Draw a line from (x1, y1) to (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)
        start = self.TransformPoint(x1, y1)
        end = self.TransformPoint(x2, y2)
        outline.SetStartEnd(start, end)
        self.module.Add(outline)

    def Circle(self, x, y, r, filled=False):
        """
        Draw a circle at (x,y) of radius r

        If filled is true, the width and radius of the line will be set
        such that the circle appears filled
        """
        circle = pcbnew.EDGE_MODULE(self.module)
        start = self.TransformPoint(x, y)

        if filled:
            circle.SetWidth(r)
            end = self.TransformPoint(x, y + r/2)
        else:
            circle.SetWidth(self.dc['width'])
            end = self.TransformPoint(x, y + r)

        circle.SetLayer(self.dc['layer'])
        circle.SetShape(pcbnew.S_CIRCLE)
        circle.SetStartEnd(start, end)
        self.module.Add(circle)

    def Arc(self, cx, cy, sx, sy, a):
        """
        Draw an arc based on centre, start and angle

        The transform matrix is applied

        Note that this won't work properly if the result is not a
        circular arc (eg a horzontal scale)
        """
        circle = pcbnew.EDGE_MODULE(self.module)
        circle.SetWidth(self.dc['width'])

        center = self.TransformPoint(cx, cy)
        start = self.TransformPoint(sx, sy)

        circle.SetLayer(self.dc['layer'])
        circle.SetShape(pcbnew.S_ARC)

        # check if the angle needs to be reverse (a flip scaling)
        if cmp(self.dc['transform'][0], 0) != cmp(self.dc['transform'][4], 0):
            a = -a

        circle.SetAngle(a)
        circle.SetStartEnd(center, start)
        self.module.Add(circle)

    # extends from (x1,y1) right
    def HLine(self, x, y, l):
        """
@@ -62,14 +326,35 @@ class FootprintWizardDrawingAids:
        """
        self.Line(x, y, x, y + l)

    def Polyline(self, pts):
    def Polyline(self, pts, mirrorX=None, mirrorY=None):
        """
        Draw a polyline, optinally mirroring around the given points
        """

        def _PolyLineInternal(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])

        _PolyLineInternal(pts)  # original

        if mirrorX is not None:
            self.TransformFlip(mirrorX, 0, self.flipX)
            _PolyLineInternal(pts)
            self.PopTransform()

        if mirrorY is not None:
            self.TransformFlipOrigin(0, mirrorY, self.flipY)
            _PolyLineInternal(pts)
            self.PopTransform()

        if mirrorX is not None and mirrorY is not None:
            self.TransformFlip(mirrorX, mirrorY, self.flipBoth)  # both
            _PolyLineInternal(pts)
            self.PopTransform()

    def Reference(self, x, y, size):
        """
        Draw the module's reference as the given point.
@@ -80,8 +365,9 @@ class FootprintWizardDrawingAids:

        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().SetPos0(self.TransformPoint(x, y))
        self.module.Reference().SetTextPosition(
            self.module.Reference().GetPos0())
        self.module.Reference().SetSize(text_size)

    def Value(self, x, y, size):
@@ -90,7 +376,7 @@ class FootprintWizardDrawingAids:
        """
        text_size = pcbnew.wxSize(size, size)

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

@@ -99,10 +385,40 @@ class FootprintWizardDrawingAids:
        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

        pts = [[x - w/2, y - h/2],  # left
               [x + w/2, y - h/2],  # right
               [x + w/2, y + h/2],  # bottom
               [x - w/2, y + h/2],  # top
               [x - w/2, y - h/2]]  # close

        self.Polyline(pts)

    def NotchedCircle(self, x, y, r, notch_w, notch_h):
        """
        Circle radus r centred at (x, y) with a raised or depressed notch
        at the top

        Notch height is measured from the top of the circle radius
        """
        # find the angle where the notch vertical meets the circle
        angle_intercept = math.asin(notch_w/(2 * r))

        # and find the co-ords of this point
        sx = math.sin(angle_intercept) * r
        sy = -math.cos(angle_intercept) * r

        # NOTE: this may be out by a factor of ten one day
        arc_angle = (math.pi * 2 - angle_intercept * 2) * (1800/math.pi)

        self.Arc(x,y, sx, sy, arc_angle)

        pts = [[sx,  sy],
               [sx,  -r - notch_h],
               [-sx, -r - notch_h],
               [-sx, sy]]

        self.Polyline(pts)

    def NotchedBox(self, x, y, w, h, notchW, notchH):
        """
@@ -125,10 +441,59 @@ class FootprintWizardDrawingAids:
            (x - w/2, y - h/2)
        ])

    def BoxWithDiagonalAtCorner(self, x, y, w, h, diagSetback):
    def BoxWithDiagonalAtCorner(self, x, y, w, h,
                                setback=pcbnew.FromMM(1.27), flip=flipNone):
        """
        Draw a box with a diagonal at the top left corner
        """

        self.TransformFlip(x, y, flip, push=True)

        pts = [[x - w/2 + setback, y - h/2],
               [x - w/2,           y - h/2 + setback],
               [x - w/2,           y + h/2],
               [x + w/2,           y + h/2],
               [x + w/2,           y - h/2],
               [x - w/2 + setback, y - h/2]]

        self.Polyline(pts)

        self.PopTransform()

    def BoxWithOpenCorner(self, x, y, w, h,
                          setback=pcbnew.FromMM(1.27), flip=flipNone):
        """
        Draw a box with an opening at the top left corner
        """

        self.TransformTranslate(x, y)
        self.TransformFlipOrigin(flip)

        pts = [[- w/2,           - h/2 + setback],
               [- w/2,           + h/2],
               [+ w/2,           + h/2],
               [+ w/2,           - h/2],
               [- w/2 + setback, - h/2]]

        self.Polyline(pts)

        self.PopTransform(num=2)

    def MarkerArrow(self, x, y, direction=dirN, width=pcbnew.FromMM(1)):
        """
        Draw a marker arrow facing in the given direction, with the
        point at (x,y)

        Direction of 0 is north
        """

        self.TransformTranslate(x, y)
        self.TransformRotationOrigin(direction)

        self.Box(x, y, w, h)
        pts = [[0,          0],
               [width / 2,  width / 2],
               [-width / 2, width / 2],
               [0,          0]]

        #diagonal corner
        self.Line(x - w/2 + diagSetback, x - h/2, x - w/2,
                x - h/2 + diagSetback)
        self.Polyline(pts)
        self.PopTransform(2)
+91 −35
Original line number Diff line number Diff line
@@ -15,8 +15,10 @@
#

import pcbnew
import math
import FootprintWizardDrawingAids


class FootprintWizardParameterManager:
    """
    Functions for helpfully managing parameters to a KiCAD Footprint
@@ -50,6 +52,7 @@ class FootprintWizardParameterManager:
    uMils = 2
    uNatural = 3
    uBool = 4
    uString = 5

    def AddParam(self, section, param, unit, default, hint=''):
        """
@@ -66,13 +69,15 @@ class FootprintWizardParameterManager:
            val = pcbnew.FromMils(default)
        elif unit == self.uNatural:
            val = default
        elif unit == self.uString:
            val = str(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]:
        if unit in [self.uNatural, self.uBool, self.uString]:
            param = "*%s" % param  # star prefix for natural

        if section not in self.parameters:
@@ -89,7 +94,8 @@ class FootprintWizardParameterManager:

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

                if "*" in key:
@@ -101,7 +107,7 @@ class FootprintWizardParameterManager:

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

        for name, section in self.parameter_errors.iteritems():
@@ -124,8 +130,8 @@ class FootprintWizardParameterManager:
                    if not printed_section:
                        print "  %s:" % name

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

    def ProcessParameters(self):
        """
@@ -134,14 +140,15 @@ class FootprintWizardParameterManager:
        """

        self.ClearErrors()
        self.CheckParameters();
        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
        print ("Building new %s footprint with the following parameters:"
               % self.name)

        self._PrintParameterTable()
        return True
@@ -150,7 +157,7 @@ class FootprintWizardParameterManager:
    # PARAMETER CHECKERS
    #################################################################

    def CheckParamPositiveInt(self, section, param, min_value = 1,
    def CheckParamInt(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
@@ -158,21 +165,29 @@ class FootprintWizardParameterManager:
        """

        try:
            self.parameters[section][param] = int(self.parameters[section][param])
            self.parameters[section][param] = (
                int(self.parameters[section][param]))
        except ValueError:
            self.parameter_errors[section][param] = "Must be a valid integer"
            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)
        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)
        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
        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
@@ -182,11 +197,13 @@ class FootprintWizardParameterManager:
        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;
        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;
        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)"
@@ -216,16 +233,46 @@ class HelpfulFootprintWizardPlugin(pcbnew.FootprintWizardPlugin,
        self.decription = self.GetDescription()
        self.image = self.GetImage()

    def GetReference(self):
    def GetValue(self):
        raise NotImplementedError

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

    def GetImage(self):
        return ""

    def GetTextSize(self):
        """
        IPC nominal
        """
        return pcbnew.FromMM(1.2)

    def GetTextThickness(self):
        """
        Thicker than IPC guidelines (10% of text height = 0.12mm)
        as 5 wires/mm is a common silk screen limitation
        """
        return pcbnew.FromMM(0.2)

    def SetModule3DModel(self):
        """
        Set a 3D model for the module

        Default is to do nothing, you need to implement this if you have
        a model to set

        FIXME: This doesn't seem to be enabled yet?
        """
        pass

    def BuildThisFootprint(self):
        """
        Draw the footprint.

        This is specific to each footprint class, you need to implment
        this to draw what you want
        """
        raise NotImplementedError

    def BuildFootprint(self):
@@ -234,17 +281,26 @@ class HelpfulFootprintWizardPlugin(pcbnew.FootprintWizardPlugin,
        the implmenting class
        """

        self.module = pcbnew.MODULE(None)  # create a new module
        # do it first, so if we return early, we don't segfault KiCad

        if not self.ProcessParameters():
            return

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

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

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

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

        self.BuildThisFootprint()  # implementer's build function

        self.SetModule3DModel()  # add a 3d module if specified

        thick = self.GetTextThickness()

        self.module.Reference().SetThickness(thick)
        self.module.Value().SetThickness(thick)
Loading