2014-04-23 16:36:31 +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.
|
|
|
|
#
|
|
|
|
|
|
|
|
import pcbnew
|
2014-09-14 16:29:10 +00:00
|
|
|
import math
|
2014-04-23 16:36:31 +00:00
|
|
|
import FootprintWizardDrawingAids
|
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
|
2014-04-23 16:36:31 +00:00
|
|
|
class FootprintWizardParameterManager:
|
|
|
|
"""
|
|
|
|
Functions for helpfully managing parameters to a KiCAD Footprint
|
|
|
|
Wizard.
|
|
|
|
|
|
|
|
Abstracts away from whatever structure is used by pcbnew's footprint
|
|
|
|
wizard class
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.parameters = {}
|
|
|
|
self.GenerateParameterList()
|
|
|
|
|
|
|
|
def GenerateParameterList(self):
|
|
|
|
"""
|
|
|
|
Construct parameters here, or leave out to have no parameters
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
def CheckParameters(self):
|
|
|
|
"""
|
|
|
|
Implement this to make checks on parameter values, filling
|
|
|
|
parameter_errors (or using the checker routines)
|
|
|
|
|
|
|
|
Subclasses can implment their own and override the parent
|
|
|
|
defaults and add new ones
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
uMM = 1
|
|
|
|
uMils = 2
|
|
|
|
uNatural = 3
|
|
|
|
uBool = 4
|
2014-09-14 16:29:10 +00:00
|
|
|
uString = 5
|
2014-04-23 16:36:31 +00:00
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
def AddParam(self, section, param, unit, default, hint=''):
|
2014-04-23 16:36:31 +00:00
|
|
|
"""
|
|
|
|
Add a parameter with some properties.
|
|
|
|
|
|
|
|
TODO: Hints are not supported, as there is as yet nowhere to
|
|
|
|
put them in the KiCAD interface
|
|
|
|
"""
|
2015-09-05 12:10:54 +00:00
|
|
|
error = ""
|
2014-04-23 16:36:31 +00:00
|
|
|
val = None
|
|
|
|
if unit == self.uMM:
|
|
|
|
val = pcbnew.FromMM(default)
|
|
|
|
elif unit == self.uMils:
|
|
|
|
val = pcbnew.FromMils(default)
|
|
|
|
elif unit == self.uNatural:
|
|
|
|
val = default
|
2014-09-14 16:29:10 +00:00
|
|
|
elif unit == self.uString:
|
|
|
|
val = str(default)
|
2014-04-23 16:36:31 +00:00
|
|
|
elif unit == self.uBool:
|
2014-09-14 16:29:10 +00:00
|
|
|
val = "True" if default else "False" # ugly stringing
|
2014-04-23 16:36:31 +00:00
|
|
|
else:
|
2015-09-05 12:10:54 +00:00
|
|
|
error = "Warning: Unknown unit type: %s" % unit
|
|
|
|
return error
|
2014-04-23 16:36:31 +00:00
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
if unit in [self.uNatural, self.uBool, self.uString]:
|
|
|
|
param = "*%s" % param # star prefix for natural
|
2014-04-23 16:36:31 +00:00
|
|
|
|
|
|
|
if section not in self.parameters:
|
2015-10-31 11:54:48 +00:00
|
|
|
if not hasattr(self, 'page_order'):
|
|
|
|
self.page_order = []
|
|
|
|
self.page_order.append(section)
|
2014-04-23 16:36:31 +00:00
|
|
|
self.parameters[section] = {}
|
2015-10-31 11:54:48 +00:00
|
|
|
if not hasattr(self, 'parameter_order'):
|
|
|
|
self.parameter_order = {}
|
|
|
|
self.parameter_order[section] = []
|
2014-04-23 16:36:31 +00:00
|
|
|
|
|
|
|
self.parameters[section][param] = val
|
2015-10-31 11:54:48 +00:00
|
|
|
self.parameter_order[section].append(param)
|
2014-04-23 16:36:31 +00:00
|
|
|
|
2015-09-05 12:10:54 +00:00
|
|
|
return error
|
|
|
|
|
|
|
|
|
2014-04-23 16:36:31 +00:00
|
|
|
def _PrintParameterTable(self):
|
|
|
|
"""
|
|
|
|
Pretty-print the parameters we have
|
|
|
|
"""
|
2015-09-05 12:10:54 +00:00
|
|
|
message = ""
|
|
|
|
|
2014-04-23 16:36:31 +00:00
|
|
|
for name, section in self.parameters.iteritems():
|
2015-10-31 11:54:48 +00:00
|
|
|
message += " %s:\n" % name
|
2014-04-23 16:36:31 +00:00
|
|
|
|
|
|
|
for key, value in section.iteritems():
|
|
|
|
unit = ""
|
2014-09-14 16:29:10 +00:00
|
|
|
if ((type(value) is int or type(value) is float)
|
|
|
|
and not "*" in key):
|
2014-04-23 16:36:31 +00:00
|
|
|
unit = "mm"
|
|
|
|
|
|
|
|
if "*" in key:
|
|
|
|
key = key[1:]
|
|
|
|
else:
|
|
|
|
value = pcbnew.ToMM(value)
|
|
|
|
|
2015-09-05 12:10:54 +00:00
|
|
|
message += " %s: %s%s\n" % (key, value, unit)
|
|
|
|
|
|
|
|
return message
|
|
|
|
|
2014-04-23 16:36:31 +00:00
|
|
|
|
|
|
|
def _ParametersHaveErrors(self):
|
|
|
|
"""
|
2014-09-14 16:29:10 +00:00
|
|
|
Return true if we discovered errors during parameter processing
|
2014-04-23 16:36:31 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
for name, section in self.parameter_errors.iteritems():
|
|
|
|
for k, v in section.iteritems():
|
|
|
|
if v:
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _PrintParameterErrors(self):
|
|
|
|
"""
|
|
|
|
Pretty-print parameters with errors
|
|
|
|
"""
|
2015-09-05 12:10:54 +00:00
|
|
|
errors = ""
|
2014-04-23 16:36:31 +00:00
|
|
|
|
|
|
|
for name, section in self.parameter_errors.iteritems():
|
|
|
|
printed_section = False
|
|
|
|
|
|
|
|
for key, value in section.iteritems():
|
|
|
|
if value:
|
|
|
|
if not printed_section:
|
2015-09-05 12:10:54 +00:00
|
|
|
errors += " %s:" % name
|
2014-04-23 16:36:31 +00:00
|
|
|
|
2015-09-05 12:10:54 +00:00
|
|
|
errors += " %s: %s (have %s)\n" % (
|
2014-09-14 16:29:10 +00:00
|
|
|
key, value, self.parameters[name][key])
|
2014-04-23 16:36:31 +00:00
|
|
|
|
2015-09-05 12:10:54 +00:00
|
|
|
return errors
|
|
|
|
|
2014-04-23 16:36:31 +00:00
|
|
|
def ProcessParameters(self):
|
|
|
|
"""
|
|
|
|
Make sure the parameters we have meet whatever expectations the
|
|
|
|
footprint wizard has of them
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.ClearErrors()
|
2014-09-14 16:29:10 +00:00
|
|
|
self.CheckParameters()
|
2014-04-23 16:36:31 +00:00
|
|
|
|
|
|
|
if self._ParametersHaveErrors():
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
#################################################################
|
|
|
|
# PARAMETER CHECKERS
|
|
|
|
#################################################################
|
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
def CheckParamInt(self, section, param, min_value=1,
|
|
|
|
max_value=None, is_multiple_of=1):
|
2014-04-23 16:36:31 +00:00
|
|
|
"""
|
|
|
|
Make sure a parameter can be made into an int, and enforce
|
|
|
|
limits if required
|
|
|
|
"""
|
|
|
|
|
|
|
|
try:
|
2014-09-14 16:29:10 +00:00
|
|
|
self.parameters[section][param] = (
|
|
|
|
int(self.parameters[section][param]))
|
2014-04-23 16:36:31 +00:00
|
|
|
except ValueError:
|
2014-09-14 16:29:10 +00:00
|
|
|
self.parameter_errors[section][param] = (
|
|
|
|
"Must be a valid integer")
|
2014-04-23 16:36:31 +00:00
|
|
|
return
|
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
if min_value is not None and (
|
|
|
|
self.parameters[section][param] < min_value):
|
|
|
|
self.parameter_errors[section][param] = (
|
|
|
|
"Must be greater than or equal to %d" % (min_value))
|
2014-04-23 16:36:31 +00:00
|
|
|
return
|
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
if max_value is not None and (
|
2015-10-29 17:35:52 +00:00
|
|
|
self.parameters[section][param] > max_value):
|
2014-09-14 16:29:10 +00:00
|
|
|
self.parameter_errors[section][param] = (
|
|
|
|
"Must be less than or equal to %d" % (max_value))
|
2014-04-23 16:36:31 +00:00
|
|
|
return
|
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
if is_multiple_of > 1 and (
|
|
|
|
self.parameters[section][param] % is_multiple_of) > 0:
|
|
|
|
self.parameter_errors[section][param] = (
|
|
|
|
"Must be a multiple of %d" % is_multiple_of)
|
2014-04-23 16:36:31 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
def CheckParamBool(self, section, param):
|
|
|
|
"""
|
|
|
|
Make sure a parameter looks like a boolean, convert to native
|
|
|
|
boolean type if so
|
|
|
|
"""
|
2014-09-14 16:29:10 +00:00
|
|
|
if str(self.parameters[section][param]).lower() in [
|
|
|
|
"true", "t", "y", "yes", "on", "1", "1.0"]:
|
|
|
|
self.parameters[section][param] = True
|
2014-04-23 16:36:31 +00:00
|
|
|
return
|
2014-09-14 16:29:10 +00:00
|
|
|
elif str(self.parameters[section][param]).lower() in [
|
|
|
|
"false", "f", "n", "no", "off", "0", "0.0"]:
|
|
|
|
self.parameters[section][param] = False
|
2014-04-23 16:36:31 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
self.parameter_errors[section][param] = "Must be boolean (true/false)"
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
class HelpfulFootprintWizardPlugin(pcbnew.FootprintWizardPlugin,
|
2014-09-14 16:29:10 +00:00
|
|
|
FootprintWizardParameterManager):
|
2014-04-23 16:36:31 +00:00
|
|
|
"""
|
|
|
|
A class to simplify many aspects of footprint creation, leaving only
|
|
|
|
the foot-print specific routines to the wizards themselves
|
|
|
|
|
|
|
|
Generally, you need to implement:
|
|
|
|
GetReference()
|
|
|
|
GetValue()
|
|
|
|
GenerateParameterList()
|
|
|
|
CheckParameters()
|
|
|
|
BuildThisFootprint()
|
|
|
|
GetName()
|
|
|
|
GetDescription()
|
|
|
|
"""
|
|
|
|
def __init__(self):
|
|
|
|
pcbnew.FootprintWizardPlugin.__init__(self)
|
|
|
|
FootprintWizardParameterManager.__init__(self)
|
|
|
|
|
|
|
|
self.name = self.GetName()
|
|
|
|
self.decription = self.GetDescription()
|
|
|
|
self.image = self.GetImage()
|
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
def GetValue(self):
|
2014-04-23 16:36:31 +00:00
|
|
|
raise NotImplementedError
|
|
|
|
|
2016-02-14 09:47:07 +00:00
|
|
|
# this value come from our KiCad Library Convention 1.0
|
2014-09-14 16:29:10 +00:00
|
|
|
def GetReferencePrefix(self):
|
2015-10-29 17:35:52 +00:00
|
|
|
return "REF"
|
2014-04-23 16:36:31 +00:00
|
|
|
|
|
|
|
def GetImage(self):
|
|
|
|
return ""
|
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
def GetTextSize(self):
|
|
|
|
"""
|
|
|
|
IPC nominal
|
|
|
|
"""
|
2016-02-14 09:47:07 +00:00
|
|
|
return pcbnew.FromMM(1.0)
|
2014-09-14 16:29:10 +00:00
|
|
|
|
|
|
|
def GetTextThickness(self):
|
|
|
|
"""
|
|
|
|
Thicker than IPC guidelines (10% of text height = 0.12mm)
|
|
|
|
as 5 wires/mm is a common silk screen limitation
|
|
|
|
"""
|
2015-10-29 17:35:52 +00:00
|
|
|
return pcbnew.FromMM(0.15)
|
2014-09-14 16:29:10 +00:00
|
|
|
|
|
|
|
def SetModule3DModel(self):
|
|
|
|
"""
|
|
|
|
Set a 3D model for the module
|
|
|
|
|
|
|
|
Default is to do nothing, you need to implement this if you have
|
|
|
|
a model to set
|
|
|
|
|
|
|
|
FIXME: This doesn't seem to be enabled yet?
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2016-02-18 17:53:39 +00:00
|
|
|
def PutOnGridMM(self, value, gridSizeMM=0.05):
|
|
|
|
"""
|
|
|
|
Round the value (in KiCAD internal units 1nm) according to the
|
|
|
|
provided gridSize in mm.
|
|
|
|
"""
|
|
|
|
thresh = pcbnew.FromMM(gridSizeMM)
|
|
|
|
res = round(value/thresh)*thresh
|
|
|
|
return res
|
|
|
|
|
|
|
|
def PutOnGridMils(self, value, gridSizeMil=2):
|
|
|
|
"""
|
|
|
|
Round the value (in KiCAD internal units 1nm) according to the
|
|
|
|
provided gridSize in mil.
|
|
|
|
"""
|
|
|
|
thresh = pcbnew.FromMils(gridSizeMil)
|
|
|
|
res = round(value/thresh)*thresh
|
|
|
|
return res
|
|
|
|
|
2014-04-23 16:36:31 +00:00
|
|
|
def BuildThisFootprint(self):
|
2014-09-14 16:29:10 +00:00
|
|
|
"""
|
|
|
|
Draw the footprint.
|
|
|
|
|
|
|
|
This is specific to each footprint class, you need to implment
|
|
|
|
this to draw what you want
|
|
|
|
"""
|
2014-04-23 16:36:31 +00:00
|
|
|
raise NotImplementedError
|
|
|
|
|
2015-09-05 12:10:54 +00:00
|
|
|
def BuildFootprint( self ):
|
2014-04-23 16:36:31 +00:00
|
|
|
"""
|
|
|
|
Actually make the footprint. We defer all but the setup to
|
2015-09-05 12:10:54 +00:00
|
|
|
the implementing class
|
2014-04-23 16:36:31 +00:00
|
|
|
"""
|
|
|
|
|
2015-09-05 12:10:54 +00:00
|
|
|
self.buildmessages = ""
|
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
self.module = pcbnew.MODULE(None) # create a new module
|
|
|
|
# do it first, so if we return early, we don't segfault KiCad
|
|
|
|
|
2014-04-23 16:36:31 +00:00
|
|
|
if not self.ProcessParameters():
|
2015-09-05 12:10:54 +00:00
|
|
|
self.buildmessages = "Cannot build footprint: Parameters have errors:\n"
|
|
|
|
self.buildmessages += self._PrintParameterErrors()
|
2014-04-23 16:36:31 +00:00
|
|
|
return
|
|
|
|
|
2015-09-05 12:10:54 +00:00
|
|
|
self.buildmessages = ("Building new %s footprint with the following parameters:\n"
|
|
|
|
% self.name)
|
|
|
|
|
|
|
|
self.buildmessages += self._PrintParameterTable()
|
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
self.draw = FootprintWizardDrawingAids.FootprintWizardDrawingAids(
|
|
|
|
self.module)
|
|
|
|
|
|
|
|
self.module.SetValue(self.GetValue())
|
|
|
|
self.module.SetReference("%s**" % self.GetReferencePrefix())
|
|
|
|
|
|
|
|
fpid = pcbnew.FPID(self.module.GetValue()) # the name in library
|
|
|
|
self.module.SetFPID(fpid)
|
2014-04-23 16:36:31 +00:00
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
self.SetModule3DModel() # add a 3d module if specified
|
2014-04-23 16:36:31 +00:00
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
thick = self.GetTextThickness()
|
2014-04-23 16:36:31 +00:00
|
|
|
|
2014-09-14 16:29:10 +00:00
|
|
|
self.module.Reference().SetThickness(thick)
|
|
|
|
self.module.Value().SetThickness(thick)
|
2015-01-06 07:38:58 +00:00
|
|
|
|
|
|
|
self.BuildThisFootprint() # implementer's build function
|
2015-09-05 12:10:54 +00:00
|
|
|
|
|
|
|
return
|