diff options
Diffstat (limited to 'pcbnew/scripting/plugins/FootprintWizardDrawingAids.py')
-rw-r--r-- | pcbnew/scripting/plugins/FootprintWizardDrawingAids.py | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/pcbnew/scripting/plugins/FootprintWizardDrawingAids.py b/pcbnew/scripting/plugins/FootprintWizardDrawingAids.py new file mode 100644 index 0000000..058fe1e --- /dev/null +++ b/pcbnew/scripting/plugins/FootprintWizardDrawingAids.py @@ -0,0 +1,523 @@ +# 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 math + + +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 tickness 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 + + # these values come from our KiCad Library Convention 0.11 + defaultLineThickness = pcbnew.FromMM(0.15) + + def DefaultGraphicLayer(self): + return pcbnew.F_SilkS + + def DefaultTextValueLayer(self): + return pcbnew.F_Fab + + def __init__(self, module): + self.module = module + # drawing context defaults + self.dc = { + 'layer': self.DefaultGraphicLayer(), + 'lineThickness': self.defaultLineThickness, + '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 point (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 SetLineTickness(self, lineThickness): + """ + Set the current pen lineThickness used for subsequent drawing + operations + """ + self.dc['lineThickness'] = lineThickness + + def GetLineTickness(self): + """ + Get the current drawing context line tickness + """ + return self.dc['lineThickness'] + + def SetLayer(self, layer): + """ + Set the current drawing layer, used for subsequent drawing + operations + """ + self.dc['layer'] = layer + + def GetLayer(self): + """ + return the current drawing layer, used drawing operations + """ + return self.dc['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.GetLineTickness()) + outline.SetLayer(self.GetLayer()) + outline.SetShape(pcbnew.S_SEGMENT) + 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 thickness 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['lineThickness']) + 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['lineThickness']) + + 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): + """ + 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, 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, orientation_degree = 0): + """ + 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(self.TransformPoint(x, y)) + self.module.Reference().SetTextPosition( + self.module.Reference().GetPos0()) + self.module.Reference().SetSize(text_size) + self.module.Reference().SetOrientation(orientation_degree*10) # internal angles are in 0.1 deg + + def Value(self, x, y, size, orientation_degree = 0): + """ + As for references, draw the module's value + """ + text_size = pcbnew.wxSize(size, size) + + self.module.Value().SetPos0(self.TransformPoint(x, y)) + self.module.Value().SetTextPosition(self.module.Value().GetPos0()) + self.module.Value().SetSize(text_size) + self.module.Value().SetLayer(self.DefaultTextValueLayer()) + self.module.Value().SetOrientation(orientation_degree*10) # internal angles are in 0.1 deg + + def Box(self, x, y, w, h): + """ + Draw a rectangular box, centred at (x,y), with given width and + height + """ + + 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, rotate=0): + """ + 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 + """ + + self.TransformRotation(x, y, rotate) + + # 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) + self.PopTransform() + + def NotchedBox(self, x, y, w, h, notchW, notchH, rotate=0): + """ + Draw a box with a notch in the top edge + """ + + self.TransformRotation(x, y, rotate) + + # 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.PopTransform() + + 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) + + pts = [[0, 0], + [width / 2, width / 2], + [-width / 2, width / 2], + [0, 0]] + + self.Polyline(pts) + self.PopTransform(2) |