1#  This program is free software; you can redistribute it and/or modify
2#  it under the terms of the GNU General Public License as published by
3#  the Free Software Foundation; either version 2 of the License, or
4#  (at your option) any later version.
5#
6#  This program is distributed in the hope that it will be useful,
7#  but WITHOUT ANY WARRANTY; without even the implied warranty of
8#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9#  GNU General Public License for more details.
10#
11#  You should have received a copy of the GNU General Public License
12#  along with this program; if not, write to the Free Software
13#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
14#  MA 02110-1301, USA.
15
16#  last change: 2021, Jul 19.
17
18import pcbnew
19import FootprintWizardBase
20
21# Additional import for QRCode
22# see https://github.com/kazuhikoarase/qrcode-generator/blob/master/python/qrcode.py
23import kicad_qrcode as qrcode  # TODO: local qrcode package is preferred, so we renamed it
24
25class QRCodeWizard(FootprintWizardBase.FootprintWizard):
26    GetName = lambda self: '2D Barcode QRCode'
27    GetDescription = lambda self: 'QR Code barcode generator'
28    GetReferencePrefix = lambda self: 'QR***'
29    GetValue = lambda self: self.module.Value().GetText()
30
31    def GenerateParameterList(self):
32        self.AddParam("Barcode", "Qr Pixel Width", self.uMM, 0.5, min_value=0.4)
33        self.AddParam("Barcode", "Border Margin (Px)", self.uInteger, 0)
34        self.AddParam("Barcode", "Contents", self.uString, 'Example')
35        self.AddParam("Barcode", "Negative", self.uBool, False)
36        self.AddParam("Barcode", "Use SilkS layer", self.uBool, False)
37        self.AddParam("Barcode", "Use Cu layer", self.uBool, True)
38        self.AddParam("Caption", "Enabled", self.uBool, True)
39        self.AddParam("Caption", "Height", self.uMM, 1.0)
40        self.AddParam("Caption", "Width", self.uMM, 1.0)
41        self.AddParam("Caption", "Thickness", self.uMM, 0.15)
42
43
44    def CheckParameters(self):
45        # 512 bits maximum in this type of QR code with 2 bytes reserved
46        self.Barcode = str(self.parameters['Barcode']['Contents'])[:61]
47        self.X = self.parameters['Barcode']['Qr Pixel Width']
48        self.negative = self.parameters['Barcode']['Negative']
49        self.UseSilkS = self.parameters['Barcode']['Use SilkS layer']
50        self.UseCu = self.parameters['Barcode']['Use Cu layer']
51        self.border = int(self.parameters['Barcode']['Border Margin (Px)'])
52        self.textHeight = int(self.parameters['Caption']['Height'])
53        self.textThickness = int(self.parameters['Caption']['Thickness'])
54        self.textWidth = int(self.parameters['Caption']['Width'])
55        self.module.Value().SetText(str(self.Barcode))
56
57        if self.border < 0:
58            self.border = 0
59
60        # Build Qrcode
61        self.qr = qrcode.QRCode()
62        self.qr.setTypeNumber(4)
63        # ErrorCorrectLevel: L = 7%, M = 15% Q = 25% H = 30%
64        self.qr.setErrorCorrectLevel(qrcode.ErrorCorrectLevel.M)
65        self.qr.addData(str(self.Barcode))
66        self.qr.make()
67
68    def drawPixelSquareArea( self, layer, size, xposition, yposition):
69        # creates a FP_SHAPE of rectangle type. The rectangle is square
70        rectangle = pcbnew.FP_SHAPE(self.module)
71        rectangle.SetShape(pcbnew.S_RECT)
72        rectangle.SetWidth( 0 )
73        rectangle.SetFilled( True )
74        rectangle.SetLayer(layer)
75        halfsize = int(size/2)
76        rectangle.SetStartX( -halfsize+xposition )
77        rectangle.SetStartY( -halfsize+yposition )
78        rectangle.SetEndX( halfsize+xposition )
79        rectangle.SetEndY( halfsize+yposition )
80        rectangle.SetStart0( rectangle.GetStart() )
81        rectangle.SetEnd0( rectangle.GetEnd() )
82        return rectangle
83
84
85    def _drawQrPixel(self, xposition, yposition):
86        # build a rectangular pad as a dot on copper layer,
87        # and a rectangle (a square) on silkscreen
88        if self.UseCu:
89            pad = pcbnew.PAD(self.module)
90            pad.SetSize(pcbnew.wxSize(self.X, self.X))
91            pad_pos = pcbnew.wxPoint(xposition,yposition)
92            pad.SetPosition(pad_pos)
93            pad.SetPos0(pad_pos)
94            pad.SetShape(pcbnew.PAD_SHAPE_RECT)
95            pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD)
96            pad.SetName("")
97            layerset = pcbnew.LSET()
98            layerset.AddLayer(pcbnew.F_Cu)
99            pad.SetLayerSet( layerset )
100            self.module.Add(pad)
101        if self.UseSilkS:
102            rectangle=self.drawPixelSquareArea(pcbnew.F_SilkS, self.X, xposition, yposition)
103            self.module.Add(rectangle)
104
105
106    def BuildThisFootprint(self):
107        if self.border >= 0:
108            # Adding border: Create a new array larger than the self.qr.modules
109            sz = self.qr.modules.__len__() + (self.border * 2)
110            arrayToDraw = [ [ 0 for a in range(sz) ] for b in range(sz) ]
111            lineposition = self.border
112
113            for i in self.qr.modules:
114                columnposition = self.border
115                for j in i:
116                    arrayToDraw[lineposition][columnposition] = j
117                    columnposition += 1
118                lineposition += 1
119        else:
120            # No border: using array as is
121            arrayToDraw = self.qr.modules
122
123        # used many times...
124        half_number_of_elements = arrayToDraw.__len__() / 2
125        area_width =  arrayToDraw.__len__()*self.X + self.border*2
126
127        # Center position of QrCode
128        yposition = - int(half_number_of_elements * self.X) + int(self.X/2)
129        area_height =  arrayToDraw.__len__()*self.X + self.border*2
130
131        for line in arrayToDraw:
132            xposition = - int(half_number_of_elements * self.X) + int(self.X/2)
133            for pixel in line:
134                # Trust table for drawing a pixel
135                # Negative is a boolean;
136                # each pixel is a boolean (need to draw of not)
137                # Negative | Pixel | Result
138                #        0 |     0 | 0
139                #        0 |     1 | 1
140                #        1 |     0 | 1
141                #        1 |     1 | 0
142                # => Draw as Xor
143                if self.negative != pixel: # Xor...
144                    self._drawQrPixel(xposition, yposition)
145                xposition += self.X
146            yposition += self.X
147
148        # Add value field
149        textPosition = int((self.textHeight) + ((1 + half_number_of_elements) * self.X))
150        pos = pcbnew.wxPoint(0, - textPosition)
151        self.module.Value().SetPosition(pos)
152        self.module.Value().SetPos0(pos)
153        self.module.Value().SetTextHeight(self.textHeight)
154        self.module.Value().SetTextWidth(self.textWidth)
155        self.module.Value().SetTextThickness(self.textThickness)
156
157        # Add Reference field
158        pos = pcbnew.wxPoint(0, textPosition)
159        self.module.Reference().SetPosition(pos)
160        self.module.Reference().SetPos0(pos)
161        self.module.Reference().SetTextHeight(self.textHeight)
162        self.module.Reference().SetTextWidth(self.textWidth)
163        self.module.Reference().SetTextThickness(self.textThickness)
164        self.module.Value().SetLayer(pcbnew.F_SilkS)
165
166        #build the footprint courtyard
167        self.draw.SetLayer( pcbnew.F_CrtYd )
168        self.draw.SetLineThickness( pcbnew.FromMM( 0.05 ) ) #Default per KLC F5.1 as of 12/2018
169        cr_margin = pcbnew.FromMM( 0.1 )
170        self.draw.Box(0, 0, area_width + cr_margin*2, area_height + cr_margin*2)
171
172        #build the footprint solder mask: the solder mask covers all copper pads
173        if self.UseCu:
174            self.draw.SetLineThickness( 0 )
175            sm_margin = pcbnew.FromMM( 0.05 )
176            rectangle=self.drawPixelSquareArea(pcbnew.F_Mask, area_width + sm_margin*2, 0, 0)
177            self.module.Add(rectangle)
178
179
180QRCodeWizard().register()
181