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
This commit is contained in:
unknown 2014-09-14 18:29:10 +02:00 committed by jean-pierre charras
parent 1edd8c8ac8
commit c1e0a8d067
9 changed files with 860 additions and 196 deletions

View File

@ -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)

View File

@ -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
# 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,13 +326,34 @@ 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
"""
if len(pts) < 2:
return
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])
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):
"""
@ -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,36 +385,115 @@ 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):
"""
Draw a box with a notch in the top edge
"""
#limit to half the overall width
# 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)
])
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):
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.Box(x, y, w, h)
self.TransformFlip(x, y, flip, push=True)
#diagonal corner
self.Line(x - w/2 + diagSetback, x - h/2, x - w/2,
x - h/2 + diagSetback)
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)
pts = [[0, 0],
[width / 2, width / 2],
[-width / 2, width / 2],
[0, 0]]
self.Polyline(pts)
self.PopTransform(2)

View File

@ -15,8 +15,10 @@
#
import pcbnew
import math
import FootprintWizardDrawingAids
class FootprintWizardParameterManager:
"""
Functions for helpfully managing parameters to a KiCAD Footprint
@ -50,8 +52,9 @@ class FootprintWizardParameterManager:
uMils = 2
uNatural = 3
uBool = 4
uString = 5
def AddParam(self, section, param, unit, default, hint = ''):
def AddParam(self, section, param, unit, default, hint=''):
"""
Add a parameter with some properties.
@ -66,14 +69,16 @@ 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
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 unit in [self.uNatural, self.uBool, self.uString]:
param = "*%s" % param # star prefix for natural
if section not in self.parameters:
self.parameters[section] = {}
@ -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,29 +157,37 @@ class FootprintWizardParameterManager:
# PARAMETER CHECKERS
#################################################################
def CheckParamPositiveInt(self, section, param, min_value = 1,
max_value = None, is_multiple_of = 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
limits if required
"""
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)"
@ -194,7 +211,7 @@ class FootprintWizardParameterManager:
class HelpfulFootprintWizardPlugin(pcbnew.FootprintWizardPlugin,
FootprintWizardParameterManager):
FootprintWizardParameterManager):
"""
A class to simplify many aspects of footprint creation, leaving only
the foot-print specific routines to the wizards themselves
@ -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):
return "U" # footprints needing wizards of often ICs
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.SetValue(self.GetValue())
self.module.SetReference("%s**" % self.GetReferencePrefix())
self.module.SetReference(self.GetReference())
self.module.SetValue("%s**" % self.GetValuePrefix())
fpid = pcbnew.FPID(self.module.GetValue()) # the name in library
self.module.SetFPID(fpid)
fpid = pcbnew.FPID(self.module.GetReference()) #the name in library
self.module.SetFPID( fpid )
self.BuildThisFootprint() # implementer's build function
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)

View File

@ -1,4 +1,27 @@
# PadArray.py
#
# Copyright 2014 john <john@johndev>
#
# 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 math
import pcbnew
class PadMaker:
@ -9,7 +32,7 @@ class PadMaker:
def __init__(self, module):
self.module = module
def THPad(self, w, l, drill, shape = pcbnew.PAD_OVAL):
def THPad(self, w, l, drill, shape=pcbnew.PAD_OVAL):
pad = pcbnew.D_PAD(self.module)
pad.SetSize(pcbnew.wxSize(l, w))
@ -22,7 +45,23 @@ class PadMaker:
return pad
def SMDPad(self, w, l, shape = pcbnew.PAD_RECT):
def THRoundPad(self, size, drill):
pad = self.THPad(size, size, drill, shape=pcbnew.PAD_CIRCLE)
return pad
def NPTHRoundPad(self, drill):
pad = pcbnew.D_PAD(self.module)
pad.SetSize(pcbnew.wxSize(drill, drill))
pad.SetShape(pcbnew.PAD_CIRCLE)
pad.SetAttribute(pcbnew.PAD_HOLE_NOT_PLATED)
pad.SetLayerSet(pad.UnplatedHoleMask())
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))
@ -34,23 +73,65 @@ class PadMaker:
return pad
def SMTRoundPad(self, size):
pad = self.SMDPad(size, size, shape = pcbnew.PAD_CIRCLE)
pad = self.SMDPad(size, size, shape=pcbnew.PAD_CIRCLE)
return pad
class PadArray:
def __init__(self):
self.firstPad = 1;
self.firstPadNum = 1
self.pinNames = None
self.firstPad = None
def SetPinNames(self, pinNames):
"""
Set a name for all the pins
"""
self.pinNames = pinNames
def SetFirstPadType(self, firstPad):
self.firstPad = firstPad
def SetFirstPadInArray(self, fpNum):
self.firstPad = fpNum
self.firstPadNum = fpNum
def AddPad(self, pad):
self.pad.GetParent().Add(pad)
def GetPad(self, is_first_pad, pos):
if (self.firstPad and is_first_pad):
pad = self.firstPad
else:
pad = self.pad
# create a new pad with same characteristics
pad = pad.Duplicate()
pad.SetPos0(pos)
pad.SetPosition(pos)
return pad
def GetName(self, *args, **kwargs):
if self.pinNames is None:
return self.NamingFunction(*args, **kwargs)
return self.pinNames
def NamingFunction(self, *args, **kwargs):
"""
Implement this as needed for each array type
"""
raise NotImplementedError;
class PadGridArray(PadArray):
def __init__(self, pad, nx, ny, px, py, pin1Pos):
def __init__(self, pad, nx, ny, px, py, centre=pcbnew.wxPoint(0, 0)):
PadArray.__init__(self)
# 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
@ -58,78 +139,121 @@ class PadGridArray(PadArray):
self.ny = int(ny)
self.px = px
self.py = py
self.pin1Pos = pin1Pos
self.centre = centre
# handy utility function 1 - A, 2 - B, 26 - AA, etc
# aIndex = 0 for 0 - A
# alphabet = set of allowable chars if not A-Z, eg ABCDEFGHJKLMNPRTUVWY for BGA
def AlphaNameFromNumber(self, n, aIndex = 1, alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
# alphabet = set of allowable chars if not A-Z,
# eg ABCDEFGHJKLMNPRTUVWY for BGA
def AlphaNameFromNumber(self, n, aIndex=1,
alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
div, mod = divmod(n - aIndex, len(alphabet))
alpha = alphabet[mod]
if div > 0:
return self.AlphaNameFromNumber(div, aIndex, alphabet) + alpha;
return self.AlphaNameFromNumber(div, aIndex, alphabet) + alpha
return alpha;
return alpha
# right to left, top to bottom
def NamingFunction(self, x, y):
return self.firstPad + (self.nx * y + x)
return self.firstPadNum + (self.nx * y + x)
#relocate the pad and add it as many times as we need
def AddPadsToModule(self):
def AddPadsToModule(self, dc):
pin1posX = self.centre.x - self.px * (self.nx - 1) / 2
pin1posY = self.centre.y - self.py * (self.ny - 1) / 2
for x in range(0, self.nx):
posX = pin1posX + (x * self.px)
for y in range(self.ny):
posX = self.pin1Pos.x + (self.px * x)
posY = self.pin1Pos.y + (self.py * y)
posY = pin1posY + (self.py * y)
pos = pcbnew.wxPoint(posX, posY)
pos = dc.TransformPoint(posX, posY)
# create a new pad with same characteristics
pad = self.pad.Duplicate()
pad = self.GetPad(x == 0 and y == 0, pos)
pad.SetPos0(pos)
pad.SetPosition(pos)
pad.SetPadName(str(self.NamingFunction(x,y)))
pad.SetPadName(self.GetName(x,y))
self.AddPad(pad)
class PadLineArray(PadGridArray):
def __init__(self, pad, n, pitch, isVertical, pin1Pos):
def __init__(self, pad, n, pitch, isVertical,
centre=pcbnew.wxPoint(0, 0)):
if isVertical:
PadGridArray.__init__(self, pad, 1, n, 0, pitch, pin1Pos)
PadGridArray.__init__(self, pad, 1, n, 0, pitch, centre)
else:
PadGridArray.__init__(self, pad, n, 1, pitch, 0, pin1Pos)
PadGridArray.__init__(self, pad, n, 1, pitch, 0, centre)
class RectPadArray(PadArray):
class PadCircleArray(PadArray):
def __init__(self, nx, ny, pitch, xpitch, ypitch, pin1Pos):
def __init__(self, pad, n, r, angle_offset=0, centre=pcbnew.wxPoint(0, 0),
clockwise=True):
PadArray.__init__(self)
# 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.n = int(n)
self.r = r
self.angle_offset = angle_offset
self.centre = centre
self.clockwise = clockwise
#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()
# around the circle, CW or CCW according to the flag
def NamingFunction(self, n):
return str(self.firstPadNum + n)
#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()
#relocate the pad and add it as many times as we need
def AddPadsToModule(self, dc):
#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()
for pin in range(0, self.n):
#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()
angle = self.angle_offset + (360 / self.n) * pin
if not self.clockwise:
angle = -angle
pos_x = math.sin(angle * math.pi / 180) * self.r
pos_y = -math.cos(angle * math.pi / 180) * self.r
pos = dc.TransformPoint(pos_x, pos_y)
pad = self.GetPad(pin == 0, pos)
pad.SetPadName(self.GetName(pin))
self.AddPad(pad)
class PadCustomArray(PadArray):
"""
Layout pads according to a custom array of [x,y] data
"""
def __init__(self, pad, array):
PadArray.__init__(self)
self.pad = pad
self.array = array
def NamingFunction(self, n):
return str(self.firstPadNum + n)
#relocate the pad and add it as many times as we need
def AddPadsToModule(self, dc):
for i in range(len(self.array)):
pos = dc.TransformPoint(self.array[i][0], self.array[i][1])
pad = self.GetPad(i == 0, pos)
pad.SetPadName(self.GetName(i))
self.AddPad(pad)

View File

@ -0,0 +1 @@

View File

@ -23,8 +23,10 @@ import PadArray as PA
class BGAPadGridArray(PA.PadGridArray):
def NamingFunction(self, x, y):
return "%s%d" % (self.AlphaNameFromNumber(y + 1, alphabet="ABCDEFGHJKLMNPRTUVWY"), x + 1)
def NamingFunction(self, n_x, n_y):
return "%s%d" % (
self.AlphaNameFromNumber(n_y + 1, alphabet="ABCDEFGHJKLMNPRTUVWY"),
n_x + 1)
class BGAWizard(HFPW.HelpfulFootprintWizardPlugin):
@ -46,21 +48,19 @@ class BGAWizard(HFPW.HelpfulFootprintWizardPlugin):
def CheckParameters(self):
self.CheckParamPositiveInt("Pads", "*row count")
self.CheckParamPositiveInt("Pads", "*column count")
self.CheckParamInt("Pads", "*row count")
self.CheckParamInt("Pads", "*column count")
def GetValue(self):
def GetReference(self):
pins = self.parameters["Pads"]["*row count"] * self.parameters["Pads"]["*column count"]
pins = (self.parameters["Pads"]["*row count"]
* self.parameters["Pads"]["*column count"])
return "BGA %d" % pins
def GetValuePrefix(self):
def GetReferencePrefix(self):
return "U"
def BuildThisFootprint(self):
pads = self.parameters["Pads"]
@ -76,23 +76,24 @@ class BGAWizard(HFPW.HelpfulFootprintWizardPlugin):
# add in the pads
pad = PA.PadMaker(self.module).SMTRoundPad(pads["pad size"])
pin1Pos = pcbnew.wxPoint(-((cols - 1) * pad_pitch) / 2,
-((rows - 1) * pad_pitch) / 2)
pin1_pos = pcbnew.wxPoint(-((cols - 1) * pad_pitch) / 2,
-((rows - 1) * pad_pitch) / 2)
array = BGAPadGridArray(pad, cols, rows, pad_pitch, pad_pitch, pin1Pos)
array.AddPadsToModule()
array = BGAPadGridArray(pad, cols, rows, pad_pitch, pad_pitch)
array.AddPadsToModule(self.draw)
#box
ssX = -pin1Pos.x + pads["outline x margin"]
ssY = -pin1Pos.y + pads["outline y margin"]
ssx = -pin1_pos.x + pads["outline x margin"]
ssy = -pin1_pos.y + pads["outline y margin"]
self.draw.BoxWithDiagonalAtCorner(0, 0, ssX*2, ssY*2, pads["outline x margin"])
self.draw.BoxWithDiagonalAtCorner(0, 0, ssx*2, ssy*2,
pads["outline x margin"])
#reference and value
textSize = pcbnew.FromMM(0.8)
text_size = pcbnew.FromMM(1.2) # IPC nominal
self.draw.Value(0, - ssY - textSize, textSize)
self.draw.Reference(0, ssY + textSize, textSize)
self.draw.Value(0, - ssy - text_size, text_size)
self.draw.Reference(0, ssy + text_size, text_size)
BGAWizard().register()

View File

@ -0,0 +1,77 @@
# 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 math
import pcbnew
import HelpfulFootprintWizardPlugin as HFPW
import PadArray as PA
class circular_array_wizard(HFPW.HelpfulFootprintWizardPlugin):
def GetName(self):
return "Circular Array"
def GetDescription(self):
return "Circular array of pads"
def GenerateParameterList(self):
self.AddParam("Pads", "n", self.uNatural, 6)
self.AddParam("Pads", "pad width", self.uMM, 1.5)
self.AddParam("Pads", "drill", self.uMM, 1)
self.AddParam("Pads", "circle diameter", self.uMM, 5)
self.AddParam("Pads", "first pad angle", self.uNatural, 0)
self.AddParam("Pads", "number clockwise", self.uBool, True)
self.AddParam("Pads", "first pad number", self.uNatural, 1)
def CheckParameters(self):
self.CheckParamInt("Pads", "*n")
self.CheckParamInt("Pads", "*first pad number")
self.CheckParamBool("Pads", "*number clockwise")
def GetValue(self):
return "A"
def GetReference(self):
return ""
def BuildThisFootprint(self):
prm = self.parameters['Pads']
pad_size = prm['pad width']
pad = PA.PadMaker(self.module).THPad(
prm['pad width'],
prm['pad width'],
prm['drill'])
array = PA.PadCircleArray(
pad, prm['*n'], prm['circle diameter'] / 2,
angle_offset=prm["*first pad angle"],
centre=pcbnew.wxPoint(0, 0),
clockwise=prm["*number clockwise"])
array.SetFirstPadInArray(prm["*first pad number"])
array.AddPadsToModule(self.draw)
circular_array_wizard().register()

View File

@ -1,9 +1,26 @@
# 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
import PadArray as PA
class QFPWizard(HelpfulFootprintWizardPlugin.HelpfulFootprintWizardPlugin):
def GetName(self):
@ -26,10 +43,10 @@ class QFPWizard(HelpfulFootprintWizardPlugin.HelpfulFootprintWizardPlugin):
def CheckParameters(self):
self.CheckParamPositiveInt("Pads", "*n", is_multiple_of = 4)
self.CheckParamInt("Pads", "*n", is_multiple_of=4)
self.CheckParamBool("Pads", "*oval")
def GetReference(self):
def GetValue(self):
return "QFP %d" % self.parameters["Pads"]["*n"]
def BuildThisFootprint(self):
@ -49,44 +66,58 @@ class QFPWizard(HelpfulFootprintWizardPlugin.HelpfulFootprintWizardPlugin):
pad_shape = pcbnew.PAD_OVAL if pads["*oval"] else pcbnew.PAD_RECT
h_pad = PA.PadMaker(self.module).SMDPad(pad_width, pad_length, shape = pad_shape)
v_pad = PA.PadMaker(self.module).SMDPad(pad_length, pad_width, shape = pad_shape)
h_pad = PA.PadMaker(self.module).SMDPad(
pad_width, pad_length, shape=pad_shape)
v_pad = PA.PadMaker(self.module).SMDPad(
pad_length, pad_width, shape=pad_shape)
#left row
pin1Pos = pcbnew.wxPoint(-h_pitch / 2, -row_len / 2)
array = PA.PadLineArray(h_pad, pads_per_row, pad_pitch, True, pin1Pos)
pin1Pos = pcbnew.wxPoint(-h_pitch / 2, 0)
array = PA.PadLineArray(h_pad, pads_per_row, pad_pitch, True,
pin1Pos)
array.SetFirstPadInArray(1)
array.AddPadsToModule()
array.AddPadsToModule(self.draw)
#bottom row
pin1Pos = pcbnew.wxPoint(-row_len / 2, v_pitch / 2)
array = PA.PadLineArray(v_pad, pads_per_row, pad_pitch, False, pin1Pos)
pin1Pos = pcbnew.wxPoint(0, v_pitch / 2)
array = PA.PadLineArray(v_pad, pads_per_row, pad_pitch, False,
pin1Pos)
array.SetFirstPadInArray(pads_per_row + 1)
array.AddPadsToModule()
array.AddPadsToModule(self.draw)
#right row
pin1Pos = pcbnew.wxPoint(h_pitch / 2, row_len / 2)
array = PA.PadLineArray(h_pad, pads_per_row, -pad_pitch, True, pin1Pos)
pin1Pos = pcbnew.wxPoint(h_pitch / 2, 0)
array = PA.PadLineArray(h_pad, pads_per_row, -pad_pitch, True,
pin1Pos)
array.SetFirstPadInArray(2*pads_per_row + 1)
array.AddPadsToModule()
array.AddPadsToModule(self.draw)
#top row
pin1Pos = pcbnew.wxPoint(row_len / 2, -v_pitch / 2)
array = PA.PadLineArray(v_pad, pads_per_row, -pad_pitch, False, pin1Pos)
pin1Pos = pcbnew.wxPoint(0, -v_pitch / 2)
array = PA.PadLineArray(v_pad, pads_per_row, -pad_pitch, False,
pin1Pos)
array.SetFirstPadInArray(3*pads_per_row + 1)
array.AddPadsToModule()
array.AddPadsToModule(self.draw)
limX = pads["package width"] / 2
limY = pads["package height"] / 2
lim_x = pads["package width"] / 2
lim_y = pads["package height"] / 2
inner = (row_len / 2) + pad_pitch
#top left - diagonal
self.draw.Line(-limX, -inner, -inner, -limY)
self.draw.Line(-lim_x, -inner, -inner, -lim_y)
# top right
self.draw.Polyline([(inner, -limY), (limX, -limY), (limX, -inner)])
self.draw.Polyline([(inner, -lim_y), (lim_x, -lim_y), (lim_x, -inner)])
# bottom left
self.draw.Polyline([(-inner, limY), (-limX, limY), (-limX, inner)])
self.draw.Polyline([(-inner, lim_y), (-lim_x, lim_y), (-lim_x, inner)])
# bottom right
self.draw.Polyline([(inner, limY), (limX, limY), (limX, inner)])
self.draw.Polyline([(inner, lim_y), (lim_x, lim_y), (lim_x, inner)])
#reference and value
text_size = pcbnew.FromMM(1.2) # IPC nominal
text_offset = v_pitch / 2 + text_size + pad_length / 2
self.draw.Value(0, -text_offset, text_size)
self.draw.Reference(0, text_offset, text_size)
QFPWizard().register()

View File

@ -24,10 +24,11 @@ import PadArray as PA
class RowedGridArray(PA.PadGridArray):
def NamingFunction(self, x, y):
if (x % 2) == 0: # even row, count up
return (x * self.ny) + y + 1;
else: # odd row, count down
return (self.ny * (x + 1)) - y;
if (x % 2) == 0: # even row, count up
return (x * self.ny) + y + 1
else: # odd row, count down
return (self.ny * (x + 1)) - y
class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin):
@ -39,9 +40,13 @@ class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin):
self.AddParam("Pads", "row count", self.uNatural, 2)
def CheckParameters(self):
self.CheckParamPositiveInt("Pads", "*row count")
self.CheckParamPositiveInt("Pads", "*n", is_multiple_of = self.parameters["Pads"]["*row count"])
self.CheckParamBool("Pads", "*silk screen inside") #can do this internally to parameter manager?
self.CheckParamInt("Pads", "*row count")
self.CheckParamInt(
"Pads", "*n",
is_multiple_of=self.parameters["Pads"]["*row count"])
# can do this internally to parameter manager?
self.CheckParamBool("Pads", "*silk screen inside")
def BuildThisFootprint(self):
@ -57,39 +62,40 @@ class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin):
pads_per_row = num_pads // num_rows
row_length = pad_pitch * (pads_per_row - 1) #fenceposts
row_length = pad_pitch * (pads_per_row - 1) # fenceposts
# add in the pads
pad = self.GetPad()
pin1Pos = pcbnew.wxPoint(-((num_rows - 1) * row_pitch) / 2, -row_length / 2)
pin1_pos = pcbnew.wxPoint(
-((num_rows - 1) * row_pitch) / 2,
-row_length / 2)
array = RowedGridArray(pad, num_rows, pads_per_row, row_pitch, pad_pitch, pin1Pos)
array.AddPadsToModule()
array = RowedGridArray(pad, num_rows, pads_per_row, row_pitch,
pad_pitch)
array.AddPadsToModule(self.draw)
# draw the Silk Screen
pad_length = pads["pad length"]
pad_width = pads["pad width"]
ssXOffset = -pad_length / 2 - pads["outline x margin"]
ssYOffset = -pad_width / 2 - pads["outline y margin"]
ssx_offset = -pad_length / 2 - pads["outline x margin"]
ssy_offset = -pad_width / 2 - pads["outline y margin"]
if pads["*silk screen inside"]:
ssXOffset *= -1
ssx_offset *= -1
ssX = -pin1Pos.x - ssXOffset
ssY = -pin1Pos.y - ssYOffset
ssx = -pin1_pos.x - ssx_offset
ssy = -pin1_pos.y - ssy_offset
self.DrawBox(ssX, ssY)
self.DrawBox(ssx, ssy)
#reference and value
textSize = pcbnew.FromMM(0.8)
text_size = pcbnew.FromMM(1.2) # IPC nominal
self.draw.Value(0, - ssY - textSize, textSize)
self.draw.Reference(0, ssY + textSize, textSize)
self.draw.Value(0, - ssy - text_size, text_size)
self.draw.Reference(0, ssy + text_size, text_size)
class SDIPWizard(RowedFootprint):
@ -111,15 +117,15 @@ class SDIPWizard(RowedFootprint):
self.AddParam("Pads", "outline x margin", self.uMM, 0.5)
self.AddParam("Pads", "outline y margin", self.uMM, 1)
def GetReference(self):
def GetValue(self):
rows = self.parameters["Pads"]["*row count"]
if rows == 1:
if rows == 1:
name = "SIP"
elif rows == 2:
name = "DIP"
else: # triple and up aren't really a thing, but call it something!
else: # triple and up aren't really a thing, but call it something!
name = "xIP"
return "%s %d" % (name, self.parameters["Pads"]["*n"])
@ -128,9 +134,10 @@ class SDIPWizard(RowedFootprint):
pad_length = self.parameters["Pads"]["pad length"]
pad_width = self.parameters["Pads"]["pad width"]
drill = self.parameters["Pads"]["drill size"]
return PA.PadMaker(self.module).THPad(pad_width, pad_length, drill, shape = pcbnew.PAD_OVAL)
return PA.PadMaker(self.module).THPad(
pad_width, pad_length, drill, shape=pcbnew.PAD_OVAL)
def DrawBox(self, ssX, ssY):
def DrawBox(self, ssx, ssy):
if self.parameters["Pads"]["*row count"] == 2:
@ -144,18 +151,19 @@ class SDIPWizard(RowedFootprint):
notchWidth = pcbnew.FromMM(3)
notchHeight = pcbnew.FromMM(1)
self.draw.NotchedBox(0, 0, ssX*2, ssY*2, notchWidth, notchHeight)
self.draw.NotchedBox(0, 0, ssx*2, ssy*2, notchWidth, notchHeight)
else:
# -----------------
# |1|2 3 4 5 6 7 8|
# -----------------
self.draw.Box(ssX*2, ssY*2)
self.draw.Box(0, 0, ssx*2, ssy*2)
#line between pin1 and pin2
pad_pitch = self.parameters["Pads"]["pad pitch"];
self.draw.HLine(-ssX, pin1Pos.y + pad_pitch/2, ssX * 2)
pad_pitch = self.parameters["Pads"]["pad pitch"]
line_y = - (self.parameters["Pads"]["*n"] - 2) * pad_pitch / 2
self.draw.HLine(-ssx, line_y, ssx * 2)
return ssX, ssY
return ssx, ssy
SDIPWizard().register()
@ -168,7 +176,7 @@ class SOICWizard(RowedFootprint):
def GetDescription(self):
return "SOIC, MSOP, SSOP, TSSOP, etc, footprint wizard"
def GetReference(self):
def GetValue(self):
return "%s %d" % ("SOIC", self.parameters["Pads"]["*n"])
def GenerateParameterList(self):
@ -186,15 +194,16 @@ class SOICWizard(RowedFootprint):
def GetPad(self):
pad_length = self.parameters["Pads"]["pad length"]
pad_width = self.parameters["Pads"]["pad width"]
return PA.PadMaker(self.module).SMDPad(pad_width, pad_length, shape = pcbnew.PAD_RECT)
return PA.PadMaker(self.module).SMDPad(
pad_width, pad_length, shape=pcbnew.PAD_RECT)
def DrawBox(self, ssX, ssY):
def DrawBox(self, ssx, ssy):
# ----------
# |8 7 6 5 |
# |1 2 3 4 |
# \---------
self.draw.BoxWithDiagonalAtCorner(0, 0, ssX*2, ssY*2, pcbnew.FromMM(1))
self.draw.BoxWithDiagonalAtCorner(0, 0, ssx*2, ssy*2, pcbnew.FromMM(1))
SOICWizard().register()