This repository has been archived by the owner on May 9, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
FootprintWizardBase.py
878 lines (696 loc) · 27.4 KB
/
FootprintWizardBase.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
# 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 foot-print 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.MODULE(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().SetThickness(thick)
self.module.Value().SetThickness(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 co-ordinate of the flip point
@param y: the y co-ordinate 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 co-ordinate of the rotation centre
@param y: the y co-ordinate 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 co-ordinate of the point to transform
@param y: the y co-ordinate 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 wxPoint
"""
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 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.EDGE_MODULE(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 co-ordinate of the arc centre
@param y: the y co-ordinate 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.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 (e.g. a horizontal scale)
@param cx: the x co-ordinate of the arc centre
@param cy: the y co-ordinate of the arc centre
@param sx: the x co-ordinate of the arc start point
@param sy: the y co-ordinate of the arc start point
@param a: the arc's central angle (in deci-degrees)
"""
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)
def HLine(self, x, y, l):
"""!
Draw a horizontal line from (x,y), rightwards
@param x: line start x co-ordinate
@param y: line start y co-ordinate
@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 co-ordinate
@param y: line start y co-ordinate
@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 co-ordinate of mirror point (None for no x-flip)
@param mirrorY: y co-ordinate 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.wxSize(size, size)
self.module.Reference().SetPos0(self.TransformPoint(x, y))
self.module.Reference().SetPosition(
self.module.Reference().GetPos0())
self.module.Reference().SetTextSize(text_size)
# internal angles are in 0.1 deg
self.module.Reference().SetTextAngle(orientation_degree * 10)
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.wxSize(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())
# internal angles are in 0.1 deg
self.module.Value().SetTextAngle(orientation_degree * 10)
def Box(self, x, y, w, h):
"""!
Draw a rectangular box, centred at (x,y), with given width and
height
@param x: the x co-ordinate of the box's centre
@param y: the y co-ordinate 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 co-ordinate of the circle's centre
@param y: the y co-ordinate 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 co-ordinate of the circle's centre
@param y: the y co-ordinate 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 co-ordinate of the circle's centre
@param y: the y co-ordinate 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 co-ordinate of the circle's centre
@param y: the y co-ordinate 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 co-ordinate of the box's centre
:param y: the y co-ordinate 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 co-ordinate of the box's centre
:param y: the y co-ordinate 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 co-ordinates 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)