2017-01-03 15:01:47 +00:00
|
|
|
# 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.
|
|
|
|
|
2021-07-19 16:36:44 +00:00
|
|
|
# last change: 2021, Jul 19.
|
2017-01-04 14:03:43 +00:00
|
|
|
|
2017-01-03 15:01:47 +00:00
|
|
|
import pcbnew
|
|
|
|
import FootprintWizardBase
|
|
|
|
|
|
|
|
# Additional import for QRCode
|
|
|
|
# see https://github.com/kazuhikoarase/qrcode-generator/blob/master/python/qrcode.py
|
2021-06-09 19:32:58 +00:00
|
|
|
import kicad_qrcode as qrcode # TODO: local qrcode package is preferred, so we renamed it
|
2017-01-03 15:01:47 +00:00
|
|
|
|
|
|
|
class QRCodeWizard(FootprintWizardBase.FootprintWizard):
|
|
|
|
GetName = lambda self: '2D Barcode QRCode'
|
2017-01-04 14:03:43 +00:00
|
|
|
GetDescription = lambda self: 'QR Code barcode generator'
|
2017-01-03 15:01:47 +00:00
|
|
|
GetReferencePrefix = lambda self: 'QR***'
|
|
|
|
GetValue = lambda self: self.module.Value().GetText()
|
|
|
|
|
|
|
|
def GenerateParameterList(self):
|
2019-05-13 11:02:58 +00:00
|
|
|
self.AddParam("Barcode", "Qr Pixel Width", self.uMM, 0.5, min_value=0.4)
|
|
|
|
self.AddParam("Barcode", "Border Margin (Px)", self.uInteger, 0)
|
2017-01-03 15:01:47 +00:00
|
|
|
self.AddParam("Barcode", "Contents", self.uString, 'Example')
|
|
|
|
self.AddParam("Barcode", "Negative", self.uBool, False)
|
|
|
|
self.AddParam("Barcode", "Use SilkS layer", self.uBool, False)
|
|
|
|
self.AddParam("Barcode", "Use Cu layer", self.uBool, True)
|
|
|
|
self.AddParam("Caption", "Enabled", self.uBool, True)
|
2018-12-10 11:13:12 +00:00
|
|
|
self.AddParam("Caption", "Height", self.uMM, 1.0)
|
|
|
|
self.AddParam("Caption", "Width", self.uMM, 1.0)
|
|
|
|
self.AddParam("Caption", "Thickness", self.uMM, 0.15)
|
2017-01-03 15:01:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
def CheckParameters(self):
|
2019-11-01 13:30:06 +00:00
|
|
|
# 512 bits maximum in this type of QR code with 2 bytes reserved
|
|
|
|
self.Barcode = str(self.parameters['Barcode']['Contents'])[:61]
|
2019-05-13 11:02:58 +00:00
|
|
|
self.X = self.parameters['Barcode']['Qr Pixel Width']
|
2017-01-03 15:01:47 +00:00
|
|
|
self.negative = self.parameters['Barcode']['Negative']
|
|
|
|
self.UseSilkS = self.parameters['Barcode']['Use SilkS layer']
|
|
|
|
self.UseCu = self.parameters['Barcode']['Use Cu layer']
|
2019-05-13 11:02:58 +00:00
|
|
|
self.border = int(self.parameters['Barcode']['Border Margin (Px)'])
|
2017-01-03 15:01:47 +00:00
|
|
|
self.textHeight = int(self.parameters['Caption']['Height'])
|
2018-03-15 14:37:36 +00:00
|
|
|
self.textThickness = int(self.parameters['Caption']['Thickness'])
|
|
|
|
self.textWidth = int(self.parameters['Caption']['Width'])
|
|
|
|
self.module.Value().SetText(str(self.Barcode))
|
2017-01-03 15:01:47 +00:00
|
|
|
|
2019-05-13 11:02:58 +00:00
|
|
|
if self.border < 0:
|
|
|
|
self.border = 0
|
|
|
|
|
2017-01-03 15:01:47 +00:00
|
|
|
# Build Qrcode
|
|
|
|
self.qr = qrcode.QRCode()
|
|
|
|
self.qr.setTypeNumber(4)
|
|
|
|
# ErrorCorrectLevel: L = 7%, M = 15% Q = 25% H = 30%
|
|
|
|
self.qr.setErrorCorrectLevel(qrcode.ErrorCorrectLevel.M)
|
|
|
|
self.qr.addData(str(self.Barcode))
|
|
|
|
self.qr.make()
|
|
|
|
|
2019-05-13 11:02:58 +00:00
|
|
|
def drawPixelSquareArea( self, layer, size, xposition, yposition):
|
2021-07-19 16:36:44 +00:00
|
|
|
# creates a FP_SHAPE of rectangle type. The rectangle is square
|
|
|
|
rectangle = pcbnew.FP_SHAPE(self.module)
|
|
|
|
rectangle.SetShape(pcbnew.PCB_SHAPE_TYPE_RECT)
|
|
|
|
rectangle.SetWidth( 0 )
|
|
|
|
rectangle.SetFilled( True )
|
|
|
|
rectangle.SetLayer(layer)
|
2019-11-01 13:30:06 +00:00
|
|
|
halfsize = int(size/2)
|
2021-07-19 16:36:44 +00:00
|
|
|
rectangle.SetStartX( -halfsize+xposition )
|
|
|
|
rectangle.SetStartY( -halfsize+yposition )
|
|
|
|
rectangle.SetEndX( halfsize+xposition )
|
|
|
|
rectangle.SetEndY( halfsize+yposition )
|
|
|
|
rectangle.SetStart0( rectangle.GetStart() )
|
|
|
|
rectangle.SetEnd0( rectangle.GetEnd() )
|
|
|
|
return rectangle
|
2017-01-04 14:03:43 +00:00
|
|
|
|
|
|
|
|
2019-05-13 11:02:58 +00:00
|
|
|
def _drawQrPixel(self, xposition, yposition):
|
2017-01-04 14:03:43 +00:00
|
|
|
# build a rectangular pad as a dot on copper layer,
|
2021-07-19 16:36:44 +00:00
|
|
|
# and a rectangle (a square) on silkscreen
|
2017-01-03 15:01:47 +00:00
|
|
|
if self.UseCu:
|
2020-11-14 01:06:05 +00:00
|
|
|
pad = pcbnew.PAD(self.module)
|
2017-12-21 07:16:36 +00:00
|
|
|
pad.SetSize(pcbnew.wxSize(self.X, self.X))
|
2019-05-13 11:02:58 +00:00
|
|
|
pad_pos = pcbnew.wxPoint(xposition,yposition)
|
|
|
|
pad.SetPosition(pad_pos)
|
|
|
|
pad.SetPos0(pad_pos)
|
2017-12-21 07:16:36 +00:00
|
|
|
pad.SetShape(pcbnew.PAD_SHAPE_RECT)
|
|
|
|
pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD)
|
|
|
|
pad.SetName("")
|
|
|
|
layerset = pcbnew.LSET()
|
2017-01-03 15:01:47 +00:00
|
|
|
layerset.AddLayer(pcbnew.F_Cu)
|
2017-01-04 14:03:43 +00:00
|
|
|
pad.SetLayerSet( layerset )
|
|
|
|
self.module.Add(pad)
|
2017-01-03 15:01:47 +00:00
|
|
|
if self.UseSilkS:
|
2021-07-19 16:36:44 +00:00
|
|
|
rectangle=self.drawPixelSquareArea(pcbnew.F_SilkS, self.X, xposition, yposition)
|
|
|
|
self.module.Add(rectangle)
|
2017-01-03 15:01:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
def BuildThisFootprint(self):
|
|
|
|
if self.border >= 0:
|
|
|
|
# Adding border: Create a new array larger than the self.qr.modules
|
|
|
|
sz = self.qr.modules.__len__() + (self.border * 2)
|
|
|
|
arrayToDraw = [ [ 0 for a in range(sz) ] for b in range(sz) ]
|
|
|
|
lineposition = self.border
|
2019-05-13 11:02:58 +00:00
|
|
|
|
2017-01-03 15:01:47 +00:00
|
|
|
for i in self.qr.modules:
|
|
|
|
columnposition = self.border
|
|
|
|
for j in i:
|
|
|
|
arrayToDraw[lineposition][columnposition] = j
|
|
|
|
columnposition += 1
|
|
|
|
lineposition += 1
|
|
|
|
else:
|
|
|
|
# No border: using array as is
|
|
|
|
arrayToDraw = self.qr.modules
|
|
|
|
|
|
|
|
# used many times...
|
|
|
|
half_number_of_elements = arrayToDraw.__len__() / 2
|
2019-05-13 11:02:58 +00:00
|
|
|
area_width = arrayToDraw.__len__()*self.X + self.border*2
|
2017-01-03 15:01:47 +00:00
|
|
|
|
|
|
|
# Center position of QrCode
|
2021-07-19 16:36:44 +00:00
|
|
|
yposition = - int(half_number_of_elements * self.X) + int(self.X/2)
|
2019-05-13 11:02:58 +00:00
|
|
|
area_height = arrayToDraw.__len__()*self.X + self.border*2
|
|
|
|
|
2017-01-03 15:01:47 +00:00
|
|
|
for line in arrayToDraw:
|
2021-07-19 16:36:44 +00:00
|
|
|
xposition = - int(half_number_of_elements * self.X) + int(self.X/2)
|
2017-01-03 15:01:47 +00:00
|
|
|
for pixel in line:
|
|
|
|
# Trust table for drawing a pixel
|
|
|
|
# Negative is a boolean;
|
|
|
|
# each pixel is a boolean (need to draw of not)
|
|
|
|
# Negative | Pixel | Result
|
|
|
|
# 0 | 0 | 0
|
|
|
|
# 0 | 1 | 1
|
|
|
|
# 1 | 0 | 1
|
|
|
|
# 1 | 1 | 0
|
|
|
|
# => Draw as Xor
|
|
|
|
if self.negative != pixel: # Xor...
|
2019-05-13 11:02:58 +00:00
|
|
|
self._drawQrPixel(xposition, yposition)
|
2017-01-03 15:01:47 +00:00
|
|
|
xposition += self.X
|
|
|
|
yposition += self.X
|
2019-05-13 11:02:58 +00:00
|
|
|
|
|
|
|
# Add value field
|
2017-01-03 15:01:47 +00:00
|
|
|
textPosition = int((self.textHeight) + ((1 + half_number_of_elements) * self.X))
|
2019-05-13 11:02:58 +00:00
|
|
|
pos = pcbnew.wxPoint(0, - textPosition)
|
|
|
|
self.module.Value().SetPosition(pos)
|
|
|
|
self.module.Value().SetPos0(pos)
|
2018-03-15 14:37:36 +00:00
|
|
|
self.module.Value().SetTextHeight(self.textHeight)
|
|
|
|
self.module.Value().SetTextWidth(self.textWidth)
|
2020-09-02 13:45:20 +00:00
|
|
|
self.module.Value().SetTextThickness(self.textThickness)
|
2019-05-13 11:02:58 +00:00
|
|
|
|
|
|
|
# Add Reference field
|
|
|
|
pos = pcbnew.wxPoint(0, textPosition)
|
|
|
|
self.module.Reference().SetPosition(pos)
|
|
|
|
self.module.Reference().SetPos0(pos)
|
2018-03-15 14:37:36 +00:00
|
|
|
self.module.Reference().SetTextHeight(self.textHeight)
|
|
|
|
self.module.Reference().SetTextWidth(self.textWidth)
|
2020-04-20 22:24:55 +00:00
|
|
|
self.module.Reference().SetTextThickness(self.textThickness)
|
2017-01-03 15:01:47 +00:00
|
|
|
self.module.Value().SetLayer(pcbnew.F_SilkS)
|
|
|
|
|
2019-05-13 11:02:58 +00:00
|
|
|
#build the footprint courtyard
|
|
|
|
self.draw.SetLayer( pcbnew.F_CrtYd )
|
|
|
|
self.draw.SetLineThickness( pcbnew.FromMM( 0.05 ) ) #Default per KLC F5.1 as of 12/2018
|
|
|
|
cr_margin = pcbnew.FromMM( 0.1 )
|
|
|
|
self.draw.Box(0, 0, area_width + cr_margin*2, area_height + cr_margin*2)
|
|
|
|
|
|
|
|
#build the footprint solder mask: the solder mask covers all copper pads
|
|
|
|
if self.UseCu:
|
|
|
|
self.draw.SetLineThickness( 0 )
|
|
|
|
sm_margin = pcbnew.FromMM( 0.05 )
|
2021-07-19 16:36:44 +00:00
|
|
|
rectangle=self.drawPixelSquareArea(pcbnew.F_Mask, area_width + sm_margin*2, 0, 0)
|
|
|
|
self.module.Add(rectangle)
|
2019-05-13 11:02:58 +00:00
|
|
|
|
|
|
|
|
2017-01-03 15:01:47 +00:00
|
|
|
QRCodeWizard().register()
|