894 lines
28 KiB
Python
894 lines
28 KiB
Python
#
|
|
# This program source code file is part of KiCad, a free EDA CAD application.
|
|
#
|
|
# Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
|
|
#
|
|
# 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 FootprintWizard(pcbnew.FootprintWizardPlugin):
|
|
"""!
|
|
A class to simplify many aspects of footprint creation, leaving only
|
|
the footprint specific routines to the wizards themselves.
|
|
|
|
Inherit this class to make a new wizard.
|
|
|
|
Provides simplified access to helpers like drawing functions, a transform
|
|
matrix stack and simple parameter checking.
|
|
|
|
Generally, you need to implement:
|
|
GetValue()
|
|
GenerateParameterList()
|
|
CheckParameters()
|
|
BuildThisFootprint()
|
|
GetName()
|
|
GetDescription()
|
|
"""
|
|
|
|
# Copy units from pcbnew
|
|
uMM = pcbnew.uMM
|
|
uMils = pcbnew.uMils
|
|
uFloat = pcbnew.uFloat
|
|
uInteger = pcbnew.uInteger
|
|
uBool = pcbnew.uBool
|
|
uRadians = pcbnew.uRadians
|
|
uDegrees = pcbnew.uDegrees
|
|
uPercent = pcbnew.uPercent
|
|
uString = pcbnew.uString
|
|
|
|
def __init__(self):
|
|
pcbnew.FootprintWizardPlugin.__init__(self)
|
|
self.GenerateParameterList()
|
|
|
|
def GetName(self):
|
|
"""!
|
|
Return the name of the footprint wizard
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def GetDescription(self):
|
|
"""!
|
|
Return the footprint wizard description
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def GetValue(self):
|
|
"""!
|
|
Return the value (name) of the generated footprint
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def GenerateParameterList(self):
|
|
"""!
|
|
Footprint parameter specification is done here
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def CheckParameters(self):
|
|
"""!
|
|
Any custom parameter checking should be performed here
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def BuildThisFootprint(self):
|
|
"""!
|
|
Draw the footprint.
|
|
|
|
This is specific to each footprint class, you need to implement
|
|
this to draw what you want
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
# Do not override this method!
|
|
def BuildFootprint(self):
|
|
"""!
|
|
Actually make the footprint. We defer all but the set-up to
|
|
the implementing class
|
|
"""
|
|
|
|
self.buildmessages = ""
|
|
self.module = pcbnew.FOOTPRINT(None) # create a new module
|
|
|
|
# Perform default checks on all parameters
|
|
for p in self.params:
|
|
p.ClearErrors()
|
|
p.Check() # use defaults
|
|
|
|
self.CheckParameters() # User error checks
|
|
|
|
if self.AnyErrors(): # Errors were detected!
|
|
|
|
self.buildmessages = ("Cannot build footprint: "
|
|
"Parameters have errors:\n")
|
|
|
|
for p in self.params:
|
|
if len(p.error_list) > 0:
|
|
self.buildmessages += "['{page}']['{name}']:\n".format(
|
|
page=p.page, name=p.name)
|
|
|
|
for error in p.error_list:
|
|
self.buildmessages += "\t" + error + "\n"
|
|
|
|
return
|
|
|
|
self.buildmessages = (
|
|
"Building new {name} footprint with the following parameters:\n"
|
|
.format(name=self.name))
|
|
|
|
self.buildmessages += self.Show()
|
|
|
|
self.draw = FootprintWizardDrawingAids(
|
|
self.module)
|
|
|
|
self.module.SetValue(self.GetValue())
|
|
self.module.SetReference("%s**" % self.GetReferencePrefix())
|
|
|
|
fpid = pcbnew.LIB_ID("", self.module.GetValue()) # the lib name (empty) and the name in library
|
|
self.module.SetFPID(fpid)
|
|
|
|
self.SetModule3DModel() # add a 3D module if specified
|
|
|
|
thick = self.GetTextThickness()
|
|
|
|
self.module.Reference().SetTextThickness(thick)
|
|
self.module.Value().SetTextThickness(thick)
|
|
|
|
self.BuildThisFootprint() # implementer's build function
|
|
|
|
return
|
|
|
|
def SetModule3DModel(self):
|
|
"""!
|
|
If your plug-in sets a 3D model, override this function
|
|
"""
|
|
pass
|
|
|
|
def GetTextSize(self):
|
|
"""!
|
|
Get the default text size for the footprint. Override to change it.
|
|
|
|
Defaults to IPC nominal of 1.0mm
|
|
"""
|
|
return pcbnew.FromMM(1.0)
|
|
|
|
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.15)
|
|
|
|
|
|
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 thickness and layer. The DC also contains a
|
|
"transform stack", which allows easy positioning and transforming of
|
|
drawn elements without lots of geometric book-keeping.
|
|
"""
|
|
|
|
# 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 # no flip transform
|
|
flipX = 1 # flip X values, i.e. about the Y-axis
|
|
flipY = 2 # flip Y values, i.e. about the X-axis
|
|
flipBoth = 3 # flip X and Y values, equivalent to a 180-degree rotation
|
|
|
|
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
|
|
|
|
@param mat: the transform matrix to add to the stack
|
|
"""
|
|
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
|
|
|
|
@param num: the number of transforms to pop from the stack.
|
|
@return the last popped 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-multiplication with the identity matrix.
|
|
|
|
@param mats: list of matrices to compose
|
|
@return: the composed transform matrix
|
|
"""
|
|
|
|
x = self.xfrmIDENTITY
|
|
|
|
for mat in mats:
|
|
# Pre-compose 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 translation
|
|
optionally pushing onto the stack
|
|
|
|
( 1 0 x )
|
|
( 0 1 y )
|
|
|
|
@param x: translation in x-direction
|
|
@param y: translation in y-direction
|
|
@param push: add this transform to the current stack
|
|
@return the generated transform matrix
|
|
"""
|
|
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
|
|
|
|
@param flip: one of flipNone, flipX, flipY, flipBoth
|
|
@param push: add this transform to the current stack
|
|
@return the generated transform matrix
|
|
"""
|
|
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.
|
|
|
|
@param x: the x coordinate of the flip point
|
|
@param y: the y coordinate of the flip point
|
|
@param flip: one of flipNone, flipX, flipY, flipBoth
|
|
@param push: add this transform to the current stack
|
|
@return the generated transform matrix
|
|
"""
|
|
mats = [self.TransformTranslate(x, y, push=False),
|
|
self.TransformFlipOrigin(flip, push=False),
|
|
self.TransformTranslate(-x, -y, push=False)]
|
|
|
|
# Distil 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 )
|
|
|
|
@param rot: the rotation angle in degrees
|
|
@param push: add this transform to the current stack
|
|
@return the generated transform matrix
|
|
"""
|
|
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
|
|
|
|
@param x: the x coordinate of the rotation centre
|
|
@param y: the y coordinate of the rotation centre
|
|
@param rot: the rotation angle in degrees
|
|
@param push: add this transform to the current stack
|
|
@return the generated transform matrix
|
|
"""
|
|
|
|
mats = [self.TransformTranslate(x, y, push=False),
|
|
self.TransformRotationOrigin(rot, push=False),
|
|
self.TransformTranslate(-x, -y, push=False)]
|
|
|
|
# Distil 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 )
|
|
|
|
@param sx: the scale factor in the x direction
|
|
@param sy: the scale factor in the y direction
|
|
@param push: add this transform to the current stack
|
|
@return the generated transform matrix
|
|
"""
|
|
|
|
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
|
|
|
|
@param x: the x coordinate of the point to transform
|
|
@param y: the y coordinate of the point to transform
|
|
@param mat: the transform matrix to use or None to use the current DC's
|
|
@return: the transformed point as a VECTOR2I
|
|
"""
|
|
|
|
if not mat:
|
|
mat = self.dc['transform']
|
|
|
|
return pcbnew.VECTOR2I( (int)(x * mat[0] + y * mat[1] + mat[2]),
|
|
(int)(x * mat[3] + y * mat[4] + mat[5]) )
|
|
|
|
def SetLineThickness(self, lineThickness):
|
|
"""!
|
|
Set the current pen lineThickness used for subsequent drawing
|
|
operations
|
|
|
|
@param lineThickness: the new line thickness to set
|
|
"""
|
|
self.dc['lineThickness'] = lineThickness
|
|
|
|
def SetLineTickness(self, lineThickness):
|
|
"""!
|
|
Old version of SetLineThickness.
|
|
Does the same thing, but is is only here for compatibility with old
|
|
scripts.
|
|
Set the current pen lineThickness used for subsequent drawing
|
|
operations
|
|
|
|
@param lineThickness: the new line thickness to set
|
|
"""
|
|
self.SetLineThickness(lineThickness)
|
|
|
|
def GetLineThickness(self):
|
|
"""!
|
|
Get the current drawing context line thickness
|
|
"""
|
|
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 for drawing operations
|
|
"""
|
|
return self.dc['layer']
|
|
|
|
def Line(self, x1, y1, x2, y2):
|
|
"""!
|
|
Draw a line from (x1, y1) to (x2, y2)
|
|
"""
|
|
outline = pcbnew.FP_SHAPE(self.module)
|
|
outline.SetWidth(self.GetLineThickness())
|
|
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
|
|
|
|
@param x: the x coordinate of the arc centre
|
|
@param y: the y coordinate of the arc centre
|
|
@param r: the circle's radius
|
|
@param filled: True to draw a filled circle, False to use the current
|
|
DC line thickness
|
|
"""
|
|
|
|
circle = pcbnew.FP_SHAPE(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 MyCmp(self, n1, n2):
|
|
"""
|
|
replace the cmp() of python2
|
|
"""
|
|
if n1 < n2:
|
|
return -1
|
|
if n1 > n2:
|
|
return 1
|
|
return 0
|
|
|
|
def Arc(self, cx, cy, sx, sy, angle):
|
|
"""!
|
|
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 (e.g. a horizontal scale)
|
|
|
|
@param cx: the x coordinate of the arc centre
|
|
@param cy: the y coordinate of the arc centre
|
|
@param sx: the x coordinate of the arc start point
|
|
@param sy: the y coordinate of the arc start point
|
|
@param angle: the arc's central angle (in deci-degrees)
|
|
"""
|
|
arc = pcbnew.FP_SHAPE(self.module)
|
|
arc.SetShape(pcbnew.SHAPE_T_ARC)
|
|
arc.SetWidth(self.dc['lineThickness'])
|
|
|
|
center = self.TransformPoint(cx, cy)
|
|
start = self.TransformPoint(sx, sy)
|
|
|
|
arc.SetLayer(self.dc['layer'])
|
|
|
|
# check if the angle needs to be reverse (a flip scaling)
|
|
if self.MyCmp(self.dc['transform'][0], 0) != self.MyCmp(self.dc['transform'][4], 0):
|
|
angle = -angle
|
|
|
|
arc.SetCenter(center)
|
|
arc.SetStart(start)
|
|
arc.SetArcAngleAndEnd(angle, True)
|
|
arc.SetLocalCoord()
|
|
self.module.Add(arc)
|
|
|
|
def HLine(self, x, y, l):
|
|
"""!
|
|
Draw a horizontal line from (x,y), rightwards
|
|
|
|
@param x: line start x coordinate
|
|
@param y: line start y coordinate
|
|
@param l: line length
|
|
"""
|
|
self.Line(x, y, x + l, y)
|
|
|
|
def VLine(self, x, y, l):
|
|
"""!
|
|
Draw a vertical line from (x1,y1), downwards
|
|
|
|
@param x: line start x coordinate
|
|
@param y: line start y coordinate
|
|
@param l: line length
|
|
"""
|
|
self.Line(x, y, x, y + l)
|
|
|
|
def Polyline(self, pts, mirrorX=None, mirrorY=None):
|
|
"""!
|
|
Draw a polyline, optionally mirroring around the given points
|
|
|
|
@param pts: list of polyline vertices (list of (x, y))
|
|
@param mirrorX: x coordinate of mirror point (None for no x-flip)
|
|
@param mirrorY: y coordinate of mirror point (None for no y-flip)
|
|
"""
|
|
|
|
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 and mirrorY is not None:
|
|
self.TransformFlip(mirrorX, mirrorY, self.flipBoth) # both
|
|
_PolyLineInternal(pts)
|
|
self.PopTransform()
|
|
|
|
elif mirrorX is not None:
|
|
self.TransformFlip(mirrorX, 0, self.flipX)
|
|
_PolyLineInternal(pts)
|
|
self.PopTransform()
|
|
|
|
elif mirrorY is not None:
|
|
self.TransformFlip(0, mirrorY, self.flipY)
|
|
_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
|
|
|
|
@param x: the x position of the reference
|
|
@param y: the y position of the reference
|
|
@param size: the text size (in both directions)
|
|
@param orientation_degree: text orientation in degrees
|
|
"""
|
|
|
|
text_size = pcbnew.VECTOR2I(size, size)
|
|
|
|
self.module.Reference().SetPos0(self.TransformPoint(x, y))
|
|
self.module.Reference().SetPosition(
|
|
self.module.Reference().GetPos0())
|
|
self.module.Reference().SetTextSize(text_size)
|
|
self.module.Reference().SetTextAngle( pcbnew.EDA_ANGLE( orientation_degree, pcbnew.DEGREES_T ) )
|
|
|
|
def Value(self, x, y, size, orientation_degree=0):
|
|
"""!
|
|
As for references, draw the module's value
|
|
|
|
@param x: the x position of the value
|
|
@param y: the y position of the value
|
|
@param size: the text size (in both directions)
|
|
@param orientation_degree: text orientation in degrees
|
|
"""
|
|
text_size = pcbnew.VECTOR2I(size, size)
|
|
|
|
self.module.Value().SetPos0(self.TransformPoint(x, y))
|
|
self.module.Value().SetPosition(self.module.Value().GetPos0())
|
|
self.module.Value().SetTextSize(text_size)
|
|
self.module.Value().SetLayer(self.DefaultTextValueLayer())
|
|
self.module.Value().SetTextAngle( pcbnew.EDA_ANGLE( orientation_degree, pcbnew.DEGREES_T ) )
|
|
|
|
def Box(self, x, y, w, h):
|
|
"""!
|
|
Draw a rectangular box, centred at (x,y), with given width and
|
|
height
|
|
|
|
@param x: the x coordinate of the box's centre
|
|
@param y: the y coordinate of the box's centre
|
|
@param w: the width of the box
|
|
@param h: the height of the box
|
|
"""
|
|
|
|
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 radius 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
|
|
|
|
@param x: the x coordinate of the circle's centre
|
|
@param y: the y coordinate of the circle's centre
|
|
@param r: the radius of the circle
|
|
@param notch_w: the width of the notch
|
|
@param notch_h: the height of the notch
|
|
@param rotate: the rotation of the whole figure, in degrees
|
|
"""
|
|
|
|
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 centre of the top edge
|
|
|
|
@param x: the x coordinate of the circle's centre
|
|
@param y: the y coordinate of the circle's centre
|
|
@param w: the width of the box
|
|
@param h: the height of the box
|
|
@param notchW: the width of the notch
|
|
@param notchH: the height of the notch
|
|
@param rotate: the rotation of the whole figure, in degrees
|
|
"""
|
|
|
|
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.
|
|
|
|
@param x: the x coordinate of the circle's centre
|
|
@param y: the y coordinate of the circle's centre
|
|
@param w: the width of the box
|
|
@param h: the height of the box
|
|
@param setback: the set-back of the diagonal, in both x and y
|
|
@param flip: one of flipNone, flipX, flipY or flipBoth to change the
|
|
diagonal 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
|
|
|
|
@param x: the x coordinate of the circle's centre
|
|
@param y: the y coordinate of the circle's centre
|
|
@param w: the width of the box
|
|
@param h: the height of the box
|
|
@param setback: the set-back of the opening, in both x and y
|
|
@param flip: one of flipNone, flipX, flipY or flipBoth to change the
|
|
open corner position
|
|
"""
|
|
|
|
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 RoundedBox(self, x, y, w, h, rad):
|
|
"""!
|
|
Draw a box with rounded corners (i.e. a 90-degree circular arc)
|
|
|
|
:param x: the x coordinate of the box's centre
|
|
:param y: the y coordinate of the box's centre
|
|
:param w: the width of the box
|
|
:param h: the height of the box
|
|
:param rad: the radius of the corner rounds
|
|
"""
|
|
|
|
x_inner = w - rad * 2
|
|
y_inner = h - rad * 2
|
|
|
|
x_left = x - w / 2
|
|
y_top = y - h / 2
|
|
|
|
# Draw straight sections
|
|
self.HLine(x_left + rad, y_top, x_inner)
|
|
self.HLine(x_left + rad, -y_top, x_inner)
|
|
|
|
self.VLine(x_left, y_top + rad, y_inner)
|
|
self.VLine(-x_left, y_top + rad, y_inner)
|
|
|
|
# corner arcs
|
|
ninety_deg = 90 * 10 # deci-degs
|
|
cx = x - w / 2 + rad
|
|
cy = y - h / 2 + rad
|
|
|
|
# top left
|
|
self.Arc(+cx, +cy, +x_left, +cy, +ninety_deg)
|
|
self.Arc(-cx, +cy, -x_left, +cy, -ninety_deg)
|
|
self.Arc(+cx, -cy, +x_left, -cy, -ninety_deg)
|
|
self.Arc(-cx, -cy, -x_left, -cy, +ninety_deg)
|
|
|
|
def ChamferedBox(self, x, y, w, h, chamfer_x, chamfer_y):
|
|
"""!
|
|
Draw a box with chamfered corners.
|
|
|
|
:param x: the x coordinate of the box's centre
|
|
:param y: the y coordinate of the box's centre
|
|
:param w: the width of the box
|
|
:param h: the height of the box
|
|
:param chamfer_x: the size of the chamfer set-back in the x direction
|
|
:param chamfer_y: the size of the chamfer set-back in the y direction
|
|
"""
|
|
# outermost dimensions
|
|
x_left = x - w / 2
|
|
y_top = y - h / 2
|
|
|
|
# x and y coordinates of inner edges of chamfers
|
|
x_inner = x_left + chamfer_x
|
|
y_inner = y_top + chamfer_y
|
|
|
|
pts = [
|
|
[+x_inner, +y_top],
|
|
[-x_inner, +y_top],
|
|
[-x_left, +y_inner],
|
|
[-x_left, -y_inner],
|
|
[-x_inner, -y_top],
|
|
[+x_inner, -y_top],
|
|
[+x_left, -y_inner],
|
|
[+x_left, +y_inner],
|
|
[+x_inner, +y_top],
|
|
]
|
|
|
|
self.draw.Polyline(pts)
|
|
|
|
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)
|
|
|
|
@param x: x position of the arrow tip
|
|
@param y: y position of the arrow tip
|
|
@param direction: arrow direction in degrees (0 is "north", can use
|
|
dir* shorthands)
|
|
@param width: arrow width
|
|
"""
|
|
|
|
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)
|