mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2024-11-25 16:35:01 +00:00
d7173dd6d1
QR codes allow 4 different levels of error correction. By increasing the level, larger numbers of data bytes can be corrupted without invalidating the code
238 lines
9.6 KiB
Python
238 lines
9.6 KiB
Python
# 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.
|
|
|
|
# last change: 2021, Jul 19.
|
|
|
|
import pcbnew
|
|
import FootprintWizardBase
|
|
|
|
# Additional import for QRCode
|
|
# see https://github.com/kazuhikoarase/qrcode-generator/blob/master/python/qrcode.py
|
|
import kicad_qrcode as qrcode # TODO: local qrcode package is preferred, so we renamed it
|
|
|
|
# Max type number for the QRCode is 10, this is restricted by the upstream library
|
|
# https://github.com/kazuhikoarase/qrcode-generator
|
|
_PYTHON_QR_LIB_MAX_TYPE_NO = len(qrcode.QRUtil.MAX_LENGTH)
|
|
|
|
class QRCodeWizard(FootprintWizardBase.FootprintWizard):
|
|
GetName = lambda self: '2D Barcode QRCode'
|
|
GetDescription = lambda self: 'QR Code barcode generator'
|
|
GetReferencePrefix = lambda self: 'QR***'
|
|
GetValue = lambda self: self.module.Value().GetText()
|
|
|
|
def GenerateParameterList(self):
|
|
# TODO: add params for ECLevel and qrcode mode (num/alnum/bytes)
|
|
self.AddParam("Barcode", "Qr Pixel Width", self.uMM, 0.5)
|
|
self.AddParam(
|
|
"Barcode",
|
|
"Border Margin (Px)",
|
|
self.uInteger,
|
|
0,
|
|
min_value=0,
|
|
)
|
|
# ErrorCorrectLevel: L = 7%, M = 15% Q = 25% H = 30%
|
|
self.AddParam(
|
|
"Barcode",
|
|
"Error Correction Level",
|
|
self.uString,
|
|
"M",
|
|
hint="One of L(7%), M(15%), Q(25%), H(30%)"
|
|
)
|
|
self.AddParam(
|
|
"Barcode",
|
|
"Type Number",
|
|
self.uInteger,
|
|
0,
|
|
min_value=0,
|
|
max_value=_PYTHON_QR_LIB_MAX_TYPE_NO,
|
|
hint="Set to 0 for autodetect (will use the smallest possible)",
|
|
)
|
|
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)
|
|
self.AddParam("Caption", "Height", self.uMM, 1.0)
|
|
self.AddParam("Caption", "Width", self.uMM, 1.0)
|
|
self.AddParam("Caption", "Thickness", self.uMM, 0.15)
|
|
|
|
|
|
def CheckParameters(self):
|
|
self.Barcode = self.parameters['Barcode']['Contents']
|
|
self.RawECLevel = str(self.parameters['Barcode']['Error Correction Level']).upper()
|
|
self.TypeNumber = self.parameters['Barcode']['Type Number']
|
|
self.X = self.parameters['Barcode']['Qr Pixel Width']
|
|
self.negative = self.parameters['Barcode']['Negative']
|
|
self.UseSilkS = self.parameters['Barcode']['Use SilkS layer']
|
|
self.UseCu = self.parameters['Barcode']['Use Cu layer']
|
|
self.border = int(self.parameters['Barcode']['Border Margin (Px)'])
|
|
self.textHeight = int(self.parameters['Caption']['Height'])
|
|
self.textThickness = int(self.parameters['Caption']['Thickness'])
|
|
self.textWidth = int(self.parameters['Caption']['Width'])
|
|
self.module.Value().SetText(str(self.Barcode))
|
|
|
|
|
|
if not (len(self.RawECLevel) == 1 and self.RawECLevel in "LMQH"):
|
|
self.GetParam("Barcode", "Error Correction Level").AddError(
|
|
'"Error Correction Level" must be one of L(7%), M(15%), Q(25%), H(30%)'
|
|
)
|
|
self.ECLevel = qrcode.ErrorCorrectLevel.M
|
|
else:
|
|
self.ECLevel = getattr(qrcode.ErrorCorrectLevel, self.RawECLevel)
|
|
|
|
|
|
# Check if the content is too long
|
|
# technically we don't need this conversion (TypeNumber=0 will be
|
|
# regarded as max) but this isn't part of the documented API, so we'll
|
|
# convert to be safe
|
|
max_type_num = (
|
|
_PYTHON_QR_LIB_MAX_TYPE_NO
|
|
if self.TypeNumber == 0
|
|
else self.TypeNumber
|
|
)
|
|
max_length = qrcode.QRUtil.getMaxLength(
|
|
max_type_num,
|
|
qrcode.Mode.MODE_8BIT_BYTE,
|
|
self.ECLevel
|
|
)
|
|
if len(self.Barcode) > max_length:
|
|
self.GetParam("Barcode", "Contents").AddError(
|
|
f"Content too long ({len(self.Barcode)} > {max_length}) for"
|
|
" the provided parameters."
|
|
)
|
|
|
|
|
|
def drawPixelSquareArea( self, layer, size, xposition, yposition):
|
|
# creates a PCB_SHAPE of rectangle type. The rectangle is square
|
|
rectangle = pcbnew.PCB_SHAPE(self.module)
|
|
rectangle.SetShape(pcbnew.S_RECT)
|
|
rectangle.SetWidth( 0 )
|
|
rectangle.SetFilled( True )
|
|
rectangle.SetLayer(layer)
|
|
halfsize = int(size/2)
|
|
rectangle.SetStartX( -halfsize+xposition )
|
|
rectangle.SetStartY( -halfsize+yposition )
|
|
rectangle.SetEndX( halfsize+xposition )
|
|
rectangle.SetEndY( halfsize+yposition )
|
|
return rectangle
|
|
|
|
|
|
def _drawQrPixel(self, xposition, yposition):
|
|
# build a rectangular pad as a dot on copper layer,
|
|
# and a rectangle (a square) on silkscreen
|
|
if self.UseCu:
|
|
pad = pcbnew.PAD(self.module)
|
|
pad.SetSize(pcbnew.VECTOR2I(self.X, self.X))
|
|
pad_pos = pcbnew.VECTOR2I(xposition,yposition)
|
|
pad.SetPosition(pad_pos)
|
|
pad.SetShape(pcbnew.PAD_SHAPE_RECT)
|
|
pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD)
|
|
pad.SetName("")
|
|
layerset = pcbnew.LSET()
|
|
layerset.AddLayer(pcbnew.F_Cu)
|
|
pad.SetLayerSet( layerset )
|
|
self.module.Add(pad)
|
|
if self.UseSilkS:
|
|
rectangle=self.drawPixelSquareArea(pcbnew.F_SilkS, self.X, xposition, yposition)
|
|
self.module.Add(rectangle)
|
|
|
|
|
|
def BuildThisFootprint(self):
|
|
# Build QRCode
|
|
# Auto detect type number
|
|
if self.TypeNumber == 0:
|
|
self.qr = qrcode.QRCode.getMinimumQRCode(self.Barcode, self.ECLevel)
|
|
# Manually specified type number
|
|
else:
|
|
self.qr = qrcode.QRCode()
|
|
self.qr.setTypeNumber(self.TypeNumber)
|
|
self.qr.setErrorCorrectLevel(self.ECLevel)
|
|
self.qr.addData(self.Barcode)
|
|
self.qr.make()
|
|
|
|
# render QRCode
|
|
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
|
|
|
|
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
|
|
area_width = arrayToDraw.__len__()*self.X + self.border*2
|
|
|
|
# Center position of QrCode
|
|
yposition = - int(half_number_of_elements * self.X) + int(self.X/2)
|
|
area_height = arrayToDraw.__len__()*self.X + self.border*2
|
|
|
|
for line in arrayToDraw:
|
|
xposition = - int(half_number_of_elements * self.X) + int(self.X/2)
|
|
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...
|
|
self._drawQrPixel(xposition, yposition)
|
|
xposition += self.X
|
|
yposition += self.X
|
|
|
|
# Add value field
|
|
textPosition = int((self.textHeight) + ((1 + half_number_of_elements) * self.X))
|
|
pos = pcbnew.VECTOR2I(0, - textPosition)
|
|
self.module.Value().SetPosition(pos)
|
|
self.module.Value().SetTextHeight(self.textHeight)
|
|
self.module.Value().SetTextWidth(self.textWidth)
|
|
self.module.Value().SetTextThickness(self.textThickness)
|
|
|
|
# Add Reference field
|
|
pos = pcbnew.VECTOR2I(0, textPosition)
|
|
self.module.Reference().SetPosition(pos)
|
|
self.module.Reference().SetTextHeight(self.textHeight)
|
|
self.module.Reference().SetTextWidth(self.textWidth)
|
|
self.module.Reference().SetTextThickness(self.textThickness)
|
|
self.module.Value().SetLayer(pcbnew.F_SilkS)
|
|
|
|
#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 )
|
|
rectangle=self.drawPixelSquareArea(pcbnew.F_Mask, area_width + sm_margin*2, 0, 0)
|
|
self.module.Add(rectangle)
|
|
|
|
|
|
QRCodeWizard().register()
|