Footprint Wizards Update

This commit is contained in:
Oliver Walters 2017-01-03 16:01:47 +01:00 committed by jean-pierre charras
parent d1278a48b6
commit 66ee44cb1f
23 changed files with 2063 additions and 844 deletions

View File

@ -151,11 +151,17 @@ static void InitKiCadAboutNew( AboutAppInfo& info )
<< HtmlHyperlink( wxT( "https://launchpad.net/kicad" ),
_( "Developer's website on Launchpad" ) )
<< wxT("</li>" );
description << wxT( "<li>" )
<< HtmlHyperlink( wxT( "https://github.com/KiCad/" ),
_( "Our official Repository for component and footprint libraries" ) )
<< wxT( "</li>" );
description << wxT( "<li>" )
<< HtmlHyperlink( wxT( "https://github.com/KiCad/Footprint_Wizards" ),
_( "Footprint wizards info on our official repository " ) )
<< wxT( "</li>" );
description << wxT( "<p><u>" )
<< _( "Non official repositories" )
<< wxT( "</u>" );

View File

@ -33,6 +33,17 @@
#include <vector>
#include <wxPcbStruct.h>
// Allowable parameter types for PCB wizards
const wxString WIZARD_PARAM_UNITS_MM = "mm"; // Millimetres
const wxString WIZARD_PARAM_UNITS_MILS = "mils"; // Mils / thou
const wxString WIZARD_PARAM_UNITS_FLOAT = "float"; // Floating point (dimensionless)
const wxString WIZARD_PARAM_UNITS_INTEGER = "integer"; // Integer (dimensionless)
const wxString WIZARD_PARAM_UNITS_BOOL = "bool"; // Boolean option
const wxString WIZARD_PARAM_UNITS_RADIANS = "radians"; // Angle (radians)
const wxString WIZARD_PARAM_UNITS_DEGREES = "degrees"; // Angle (degrees)
const wxString WIZARD_PARAM_UNITS_PERCENT = "%"; // Percent (0% -> 100%)
const wxString WIZARD_PARAM_UNITS_STRING = "string"; // String
/**
* Class FOOTPRINT_WIZARD
* This is the parent class from where any footprint wizard class must
@ -104,6 +115,20 @@ public:
*/
virtual wxArrayString GetParameterErrors( int aPage ) = 0;
/**
* Function GetParameterHints
* @param aPage is the page we want to know the hints of
* @return an array of hints (if any) for the parameters, empty string for no hints
*/
virtual wxArrayString GetParameterHints( int aPage ) = 0;
/**
* Function GetParamaterDesignators
* @param aPage is the page we want to know the designators of
* @return an array of designators (blank strings for no designators
*/
virtual wxArrayString GetParameterDesignators( int aPage ) = 0;
/**
* Function SetParameterValues
* @param aPage is the page we want to set the parameters in
@ -112,6 +137,12 @@ public:
*/
virtual wxString SetParameterValues( int aPage, wxArrayString& aValues ) = 0;
/**
* Function ResetParameters
* Reset all wizard parameters to default values
*/
virtual void ResetParameters() = 0;
/**
* Function GetModule
* This method builds the module itself and returns it to the caller function

View File

@ -203,6 +203,22 @@ void FOOTPRINT_WIZARD_FRAME::SelectCurrentWizard( wxCommandEvent& event )
SelectFootprintWizard();
}
void FOOTPRINT_WIZARD_FRAME::DefaultParameters( wxCommandEvent& event )
{
FOOTPRINT_WIZARD* footprintWizard = GetMyWizard();
if ( footprintWizard == NULL )
return;
footprintWizard->ResetParameters();
// Reload
ReCreateParameterList();
ReloadFootprint();
DisplayWizardInfos();
}
void FOOTPRINT_WIZARD_FRAME::ParametersUpdated( wxGridEvent& event )
{
@ -223,39 +239,12 @@ void FOOTPRINT_WIZARD_FRAME::ParametersUpdated( wxGridEvent& event )
int count = m_parameterGrid->GetNumberRows();
// Skip extra event, useless
if( event.GetString() == m_parameterGrid->GetCellValue( event.GetRow(), m_columnPrmValue ) )
if( event.GetString() == m_parameterGrid->GetCellValue( event.GetRow(), WIZ_COL_VALUE ) )
return;
for( int prm_id = 0; prm_id < count; ++prm_id )
{
wxString value = m_parameterGrid->GetCellValue( prm_id, m_columnPrmValue );
// if this parameter is expected to be an internal
// unit convert it back from the user format
if( ptList[prm_id]==wxT( "IU" ) )
{
// If our locale is set to use, for decimal point, just change it
// to be scripting compatible
LOCALE_IO toggle;
double dValue;
value.ToDouble( &dValue );
// convert from mils to inches where it's needed
if( g_UserUnit==INCHES )
dValue = dValue / 1000.0;
dValue = From_User_Unit( g_UserUnit, dValue );
// Internal units are int. Print them as int.
value.Printf( "%d", KiROUND( dValue ) );
if( prmValues[prm_id].EndsWith(".0") )
{
prmValues[prm_id].RemoveLast();
prmValues[prm_id].RemoveLast();
}
}
wxString value = m_parameterGrid->GetCellValue( prm_id, WIZ_COL_VALUE);
if( prmValues[prm_id] != value )
{

View File

@ -44,6 +44,8 @@
#include "footprint_wizard_frame.h"
#include <footprint_info.h>
#include <wx/grid.h>
#include <wx/tokenzr.h>
#include <wx/numformatter.h>
#include <hotkeys.h>
#include <wildcards_and_files_ext.h>
@ -61,6 +63,9 @@ BEGIN_EVENT_TABLE( FOOTPRINT_WIZARD_FRAME, EDA_DRAW_FRAME )
EVT_TOOL( ID_FOOTPRINT_WIZARD_SELECT_WIZARD,
FOOTPRINT_WIZARD_FRAME::SelectCurrentWizard )
EVT_TOOL( ID_FOOTPRINT_WIZARD_RESET_TO_DEFAULT,
FOOTPRINT_WIZARD_FRAME::DefaultParameters )
EVT_TOOL( ID_FOOTPRINT_WIZARD_NEXT,
FOOTPRINT_WIZARD_FRAME::Process_Special_Functions )
@ -74,6 +79,7 @@ BEGIN_EVENT_TABLE( FOOTPRINT_WIZARD_FRAME, EDA_DRAW_FRAME )
FOOTPRINT_WIZARD_FRAME::Show3D_Frame )
// listbox events
EVT_LISTBOX( ID_FOOTPRINT_WIZARD_PAGE_LIST, FOOTPRINT_WIZARD_FRAME::ClickOnPageList )
EVT_GRID_CMD_CELL_CHANGED( ID_FOOTPRINT_WIZARD_PARAMETER_LIST,
FOOTPRINT_WIZARD_FRAME::ParametersUpdated )
@ -81,10 +87,6 @@ BEGIN_EVENT_TABLE( FOOTPRINT_WIZARD_FRAME, EDA_DRAW_FRAME )
EVT_MENU( ID_SET_RELATIVE_OFFSET, FOOTPRINT_WIZARD_FRAME::OnSetRelativeOffset )
END_EVENT_TABLE()
// Column index to display parameters in m_parameterGrid
int FOOTPRINT_WIZARD_FRAME::m_columnPrmName = 0;
int FOOTPRINT_WIZARD_FRAME::m_columnPrmValue = 1;
int FOOTPRINT_WIZARD_FRAME::m_columnPrmUnit = 2;
#define FOOTPRINT_WIZARD_FRAME_NAME wxT( "FootprintWizard" )
@ -255,6 +257,13 @@ void FOOTPRINT_WIZARD_FRAME::ExportSelectedFootprint( wxCommandEvent& aEvent )
Close();
}
void FOOTPRINT_WIZARD_FRAME::OnGridSize( wxSizeEvent& aSizeEvent )
{
// Resize the parameter columns
ResizeParamColumns();
aSizeEvent.Skip();
}
void FOOTPRINT_WIZARD_FRAME::OnSize( wxSizeEvent& SizeEv )
{
@ -278,9 +287,10 @@ void FOOTPRINT_WIZARD_FRAME::initParameterGrid()
m_parameterGrid->CreateGrid( 0, 3 );
// Columns
m_parameterGrid->SetColLabelValue( m_columnPrmName, _( "Parameter" ) );
m_parameterGrid->SetColLabelValue( m_columnPrmValue, _( "Value" ) );
m_parameterGrid->SetColLabelValue( m_columnPrmUnit, _( "Units" ) );
m_parameterGrid->SetColLabelValue( WIZ_COL_NAME, _( "Parameter" ) );
m_parameterGrid->SetColLabelValue( WIZ_COL_VALUE, _( "Value" ) );
m_parameterGrid->SetColLabelValue( WIZ_COL_UNITS, _( "Units" ) );
m_parameterGrid->SetColLabelAlignment( wxALIGN_LEFT, wxALIGN_CENTRE );
m_parameterGrid->AutoSizeColumns();
@ -288,6 +298,11 @@ void FOOTPRINT_WIZARD_FRAME::initParameterGrid()
m_parameterGrid->AutoSizeRows();
m_parameterGrid->SetRowLabelSize( 25 );
m_parameterGrid->SetRowLabelAlignment( wxALIGN_CENTRE, wxALIGN_CENTRE );
m_parameterGrid->DisableDragGridSize();
m_parameterGrid->DisableDragColSize();
m_parameterGrid->Connect( wxEVT_SIZE, wxSizeEventHandler(FOOTPRINT_WIZARD_FRAME::OnGridSize), NULL, this );
}
@ -336,63 +351,117 @@ void FOOTPRINT_WIZARD_FRAME::ReCreateParameterList()
m_parameterGrid->ClearGrid();
// Get the list of names, values, and types
wxArrayString fpList = footprintWizard->GetParameterNames( page );
wxArrayString fvList = footprintWizard->GetParameterValues( page );
wxArrayString ptList = footprintWizard->GetParameterTypes( page );
// Get the list of names, values, types, hints and designators
wxArrayString designatorsList = footprintWizard->GetParameterDesignators( page );
wxArrayString namesList = footprintWizard->GetParameterNames( page );
wxArrayString valuesList = footprintWizard->GetParameterValues( page );
wxArrayString typesList = footprintWizard->GetParameterTypes( page );
wxArrayString hintsList = footprintWizard->GetParameterHints( page );
// Dimension the wxGrid
if( m_parameterGrid->GetNumberRows() > 0 )
m_parameterGrid->DeleteRows( 0, m_parameterGrid->GetNumberRows() );
m_parameterGrid->AppendRows( fpList.size() );
m_parameterGrid->AppendRows( namesList.size() );
wxString value, units;
for( unsigned int i = 0; i< fpList.size(); i++ )
wxString designator, name, value, units, hint;
for( unsigned int i = 0; i< namesList.size(); i++ )
{
value = fvList[i];
designator = designatorsList[i];
name = namesList[i];
value = valuesList[i];
units = typesList[i];
hint = hintsList[i];
m_parameterGrid->SetCellValue( i, m_columnPrmName, fpList[i] );
m_parameterGrid->SetReadOnly( i, m_columnPrmName );
m_parameterGrid->SetRowLabelValue( i, designator );
if( ptList[i]==wxT( "IU" ) )
// Set the 'Name'
m_parameterGrid->SetCellValue( i, WIZ_COL_NAME, name );
m_parameterGrid->SetReadOnly( i, WIZ_COL_NAME );
m_parameterGrid->SetCellAlignment( i, WIZ_COL_NAME, wxALIGN_LEFT, wxALIGN_CENTRE );
// Set the editor type of the
// Boolean parameters can be displayed using a checkbox
if ( units == WIZARD_PARAM_UNITS_BOOL )
{
LOCALE_IO toggle;
wxGridCellBoolEditor *boolEditor = new wxGridCellBoolEditor;
boolEditor->UseStringValues("True","False");
m_parameterGrid->SetCellEditor( i, WIZ_COL_VALUE, boolEditor );
// We are handling internal units, so convert them to the current
// system selected units and store into value.
double dValue;
value.ToDouble( &dValue );
dValue = To_User_Unit( g_UserUnit, dValue );
if( g_UserUnit==INCHES ) // we convert inches into mils for more detail
{
dValue = dValue * 1000.0;
units = wxT( "mils" );
}
else if( g_UserUnit==MILLIMETRES )
{
units = wxT( "mm" );
}
// Use Double2Str to build the string, because useless trailing 0
// are removed. The %f format does not remove them
std::string s = Double2Str( dValue );
value = FROM_UTF8( s.c_str() );
m_parameterGrid->SetCellRenderer( i, WIZ_COL_VALUE, new wxGridCellBoolRenderer );
}
else if( ptList[i]==wxT( "UNITS" ) ) // 1,2,3,4,5 ... N
// Parameters that can be selected from a list of multiple options
else if ( units.Contains( "," ) ) // Indicates list of available options
{
wxStringTokenizer tokenizer( units, "," );
wxArrayString options;
while ( tokenizer.HasMoreTokens() )
{
options.Add( tokenizer.GetNextToken() );
}
m_parameterGrid->SetCellEditor( i, WIZ_COL_VALUE, new wxGridCellChoiceEditor( options ) );
units = wxT( "" );
}
// Integer parameters
else if ( units == WIZARD_PARAM_UNITS_INTEGER )
{
m_parameterGrid->SetCellEditor( i, WIZ_COL_VALUE, new wxGridCellNumberEditor );
}
// Non-integer numerical parameters
else if ( ( units == WIZARD_PARAM_UNITS_MM ) ||
( units == WIZARD_PARAM_UNITS_MILS ) ||
( units == WIZARD_PARAM_UNITS_FLOAT ) ||
( units == WIZARD_PARAM_UNITS_RADIANS ) ||
( units == WIZARD_PARAM_UNITS_DEGREES ) ||
( units == WIZARD_PARAM_UNITS_PERCENT ) )
{
m_parameterGrid->SetCellEditor( i, WIZ_COL_VALUE, new wxGridCellFloatEditor );
m_parameterGrid->SetCellValue( i, m_columnPrmValue, value );
m_parameterGrid->SetCellValue( i, m_columnPrmUnit, units );
m_parameterGrid->SetReadOnly( i, m_columnPrmUnit );
// Convert separators to the locale-specific character
value.Replace( ",", wxNumberFormatter::GetDecimalSeparator() );
value.Replace( ".", wxNumberFormatter::GetDecimalSeparator() );
}
// Set the 'Units'
m_parameterGrid->SetCellValue( i, WIZ_COL_UNITS, units );
m_parameterGrid->SetReadOnly( i, WIZ_COL_UNITS );
m_parameterGrid->SetCellAlignment( i, WIZ_COL_UNITS, wxALIGN_LEFT, wxALIGN_CENTRE );
// Set the 'Value'
m_parameterGrid->SetCellValue( i, WIZ_COL_VALUE, value );
m_parameterGrid->SetCellAlignment( i, WIZ_COL_VALUE, wxALIGN_CENTRE, wxALIGN_CENTRE );
}
ResizeParamColumns();
}
void FOOTPRINT_WIZARD_FRAME::ResizeParamColumns()
{
// Parameter grid is not yet configured
if ( ( m_parameterGrid == NULL ) || ( m_parameterGrid->GetNumberCols() == 0 ) )
return;
// first auto-size the columns to ensure enough space around text
m_parameterGrid->AutoSizeColumns();
// Auto-size the value column
int width = m_parameterGrid->GetClientSize().GetWidth() -
m_parameterGrid->GetRowLabelSize() -
m_parameterGrid->GetColSize( WIZ_COL_NAME ) -
m_parameterGrid->GetColSize( WIZ_COL_UNITS );
if ( width > m_parameterGrid->GetColMinimalAcceptableWidth() )
{
m_parameterGrid->SetColSize( WIZ_COL_VALUE, width );
}
}
@ -593,6 +662,13 @@ void FOOTPRINT_WIZARD_FRAME::ReCreateHToolbar()
_( "Select the wizard script to load and run" ) );
m_mainToolBar->AddSeparator();
m_mainToolBar->AddTool( ID_FOOTPRINT_WIZARD_RESET_TO_DEFAULT, wxEmptyString,
KiBitmap( reload_xpm ),
_( "Reset the wizard parameters to default values ") );
m_mainToolBar->AddSeparator();
m_mainToolBar->AddTool( ID_FOOTPRINT_WIZARD_PREVIOUS, wxEmptyString,
KiBitmap( lib_previous_xpm ),
_( "Select previous parameters page" ) );
@ -655,13 +731,13 @@ FOOTPRINT_WIZARD_MESSAGES::FOOTPRINT_WIZARD_MESSAGES( FOOTPRINT_WIZARD_FRAME* aP
wxCAPTION | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT )
{
m_canClose = false;
wxBoxSizer* bSizer = new wxBoxSizer( wxVERTICAL );
SetSizer( bSizer );
wxBoxSizer* bSizer = new wxBoxSizer( wxVERTICAL );
SetSizer( bSizer );
m_messageWindow = new wxTextCtrl( this, wxID_ANY, wxEmptyString,
m_messageWindow = new wxTextCtrl( this, wxID_ANY, wxEmptyString,
wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE|wxTE_READONLY );
bSizer->Add( m_messageWindow, 1, wxEXPAND, 0 );
bSizer->Add( m_messageWindow, 1, wxEXPAND, 0 );
m_config = aCfg;
@ -670,7 +746,7 @@ FOOTPRINT_WIZARD_MESSAGES::FOOTPRINT_WIZARD_MESSAGES( FOOTPRINT_WIZARD_FRAME* aP
SetSize( m_position.x, m_position.y, m_size.x, m_size.y );
m_messageWindow->SetMinSize( wxSize( 350, 250 ) );
Layout();
Layout();
bSizer->SetSizeHints( this );
}

View File

@ -39,9 +39,16 @@ class wxGrid;
class wxGridEvent;
class FOOTPRINT_EDIT_FRAME;
// A helper class to display messages when building a footprin
// A helper class to display messages when building a footprint
class FOOTPRINT_WIZARD_MESSAGES;
enum WizardParameterColumnNames
{
WIZ_COL_NAME = 0,
WIZ_COL_VALUE,
WIZ_COL_UNITS
};
/**
* Class FOOTPRINT_WIZARD_FRAME
*/
@ -54,11 +61,6 @@ private:
int m_parameterGridWidth; ///< size of the grid
FOOTPRINT_WIZARD_MESSAGES* m_messagesFrame;
// Column index to display parameters in m_parameterGrid
static int m_columnPrmName;
static int m_columnPrmValue;
static int m_columnPrmUnit;
protected:
wxString m_wizardName; ///< name of the current wizard
wxString m_wizardDescription; ///< description of the wizard
@ -76,6 +78,8 @@ private:
void OnSize( wxSizeEvent& event ) override;
void OnGridSize( wxSizeEvent& aSizeEvent );
/**
* Function ExportSelectedFootprint();
* will let the caller exit from the wait loop, and get the built footprint
@ -103,6 +107,11 @@ private:
*/
void ReCreateParameterList();
/**
* Expand the 'Value' column to fill available
*/
void ResizeParamColumns();
/**
* Function initParameterGrid
* Prepare the grid where parameters are displayed
@ -168,6 +177,8 @@ private:
void SelectCurrentWizard( wxCommandEvent& event );
void DefaultParameters( wxCommandEvent& event );
/**
* Function ParametersUpdated
* Update the footprint python parameters values from the values in grid

View File

@ -200,7 +200,6 @@ PGM_BASE& Pgm()
#if defined( KICAD_SCRIPTING )
static bool scriptingSetup()
{
wxString path_frag;
#if defined( __WINDOWS__ )
// If our python.exe (in kicad/bin) exists, force our kicad python environment
@ -227,14 +226,6 @@ static bool scriptingSetup()
wxSetEnv( wxT( "PATH" ), kipython );
}
// wizard plugins are stored in ../share/kicad/scripting/plugins.
// so add the base scripting path to python scripting default search paths
// which are ( [KICAD_PATH] is an environment variable to define)
// [KICAD_PATH]/scripting
// [KICAD_PATH]/scripting/plugins
// Add this default search path:
path_frag = Pgm().GetExecutablePath() + wxT( "../share/kicad/scripting" );
#elif defined( __WXMAC__ )
// This path is given to LoadPlugins() from kicadplugins.i, which
@ -278,13 +269,9 @@ static bool scriptingSetup()
wxSetEnv( wxT( "PYTHONPATH" ), pypath );
// Add this default search path:
path_frag = Pgm().GetExecutablePath() + wxT( "../share/kicad/scripting" );
#endif
// path_frag is the path to the bundled scripts and plugins, all other paths are
// determined by the python pcbnew.py initialisation code.
if( !pcbnewInitPythonScripting( TO_UTF8( path_frag ) ) )
if ( !pcbnewInitPythonScripting( TO_UTF8( PyScriptingPath() ) ) )
{
wxLogError( "pcbnewInitPythonScripting() failed." );
return false;

View File

@ -392,6 +392,7 @@ enum pcbnew_ids
ID_FOOTPRINT_WIZARD_PAGES_WINDOW,
ID_FOOTPRINT_WIZARD_PARAMETERS_WINDOW,
ID_FOOTPRINT_WIZARD_SELECT_WIZARD,
ID_FOOTPRINT_WIZARD_RESET_TO_DEFAULT,
ID_FOOTPRINT_WIZARD_EXPORT_TO_BOARD,
ID_UPDATE_PCB_FROM_SCH,

View File

@ -17,10 +17,9 @@
from __future__ import division
import pcbnew
import HelpfulFootprintWizardPlugin as HFPW
import FootprintWizardBase
class FPC_FootprintWizard(HFPW.HelpfulFootprintWizardPlugin):
class FPC_FootprintWizard(FootprintWizardBase.FootprintWizard):
def GetName(self):
return "FPC (SMT connector)"
@ -29,11 +28,11 @@ class FPC_FootprintWizard(HFPW.HelpfulFootprintWizardPlugin):
return "FPC (SMT connector) Footprint Wizard"
def GetValue(self):
pins = self.parameters["Pads"]["*n"]
pins = self.parameters["Pads"]["n"]
return "FPC_%d" % pins
def GenerateParameterList(self):
self.AddParam( "Pads", "n", self.uNatural, 40 )
self.AddParam( "Pads", "n", self.uInteger, 40 )
self.AddParam( "Pads", "pitch", self.uMM, 0.5 )
self.AddParam( "Pads", "width", self.uMM, 0.25 )
self.AddParam( "Pads", "height", self.uMM, 1.6)
@ -56,13 +55,12 @@ class FPC_FootprintWizard(HFPW.HelpfulFootprintWizardPlugin):
return pad
def CheckParameters(self):
p = self.parameters
self.CheckParamInt( "Pads", "*n" ) # not internal units preceded by "*"
#TODO implement custom parameter checking
pass
def BuildThisFootprint(self):
p = self.parameters
pad_count = int(p["Pads"]["*n"])
pad_count = int(p["Pads"]["n"])
pad_width = p["Pads"]["width"]
pad_height = p["Pads"]["height"]
pad_pitch = p["Pads"]["pitch"]

View File

@ -15,10 +15,150 @@
#
from __future__ import division
import pcbnew
import math
# Base class for creating footprint wizards
# Inherit this class to make a new wizard
class FootprintWizard(pcbnew.FootprintWizardPlugin):
# 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
"""
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:
GetValue()
GenerateParameterList()
CheckParameters()
BuildThisFootprint()
GetName()
GetDescription()
"""
def __init__(self):
pcbnew.FootprintWizardPlugin.__init__(self)
self.GenerateParameterList()
def GetName(self):
"""
Retun 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 implment
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 setup to
the implementing class
"""
self.buildmessages = ""
self.module = pcbnew.MODULE(None) # create a new module
# Perform default checks on all params
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 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):
pass
def GetTextSize(self):
"""
IPC nominal
"""
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:
"""
@ -294,6 +434,7 @@ class FootprintWizardDrawingAids:
If filled is true, the thickness and radius of the line will be set
such that the circle appears filled
"""
circle = pcbnew.EDGE_MODULE(self.module)
start = self.TransformPoint(x, y)
@ -362,21 +503,22 @@ class FootprintWizardDrawingAids:
_PolyLineInternal(pts) # original
if mirrorX is not None:
self.TransformFlip(mirrorX, 0, self.flipX)
_PolyLineInternal(pts)
self.PopTransform()
if mirrorY is not None:
self.TransformFlipOrigin(0, mirrorY, self.flipY)
_PolyLineInternal(pts)
self.PopTransform()
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.

View File

@ -1,348 +0,0 @@
# 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
import math
import FootprintWizardDrawingAids
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
uString = 5
def AddParam(self, section, param, unit, default, hint=''):
"""
Add a parameter with some properties.
TODO: Hints are not supported, as there is as yet nowhere to
put them in the KiCAD interface
"""
error = ""
val = None
if unit == self.uMM:
val = pcbnew.FromMM(default)
elif unit == self.uMils:
val = pcbnew.FromMils(default)
elif unit == self.uNatural:
val = default
elif unit == self.uString:
val = str(default)
elif unit == self.uBool:
val = "True" if default else "False" # ugly stringing
else:
error = "Warning: Unknown unit type: %s" % unit
return error
if unit in [self.uNatural, self.uBool, self.uString]:
param = "*%s" % param # star prefix for natural
if section not in self.parameters:
if not hasattr(self, 'page_order'):
self.page_order = []
self.page_order.append(section)
self.parameters[section] = {}
if not hasattr(self, 'parameter_order'):
self.parameter_order = {}
self.parameter_order[section] = []
self.parameters[section][param] = val
self.parameter_order[section].append(param)
return error
def _PrintParameterTable(self):
"""
Pretty-print the parameters we have
"""
message = ""
for name, section in self.parameters.iteritems():
message += " %s:\n" % name
for key, value in section.iteritems():
unit = ""
if ((type(value) is int or type(value) is float)
and not "*" in key):
unit = "mm"
if "*" in key:
key = key[1:]
else:
value = pcbnew.ToMM(value)
message += " %s: %s%s\n" % (key, value, unit)
return message
def _ParametersHaveErrors(self):
"""
Return true if we discovered errors during parameter processing
"""
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
"""
errors = ""
for name, section in self.parameter_errors.iteritems():
printed_section = False
for key, value in section.iteritems():
if value:
if not printed_section:
errors += " %s:" % name
errors += " %s: %s (have %s)\n" % (
key, value, self.parameters[name][key])
return errors
def ProcessParameters(self):
"""
Make sure the parameters we have meet whatever expectations the
footprint wizard has of them
"""
self.ClearErrors()
self.CheckParameters()
if self._ParametersHaveErrors():
return False
return True
#################################################################
# PARAMETER CHECKERS
#################################################################
def CheckParamInt(self, section, param, min_value=1,
max_value=None, is_multiple_of=1):
"""
Make sure a parameter can be made into an int, and enforce
limits if required
"""
try:
self.parameters[section][param] = (
int(self.parameters[section][param]))
except ValueError:
self.parameter_errors[section][param] = (
"Must be a valid integer")
return
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))
return
if max_value is not None and (
self.parameters[section][param] > max_value):
self.parameter_errors[section][param] = (
"Must be less than or equal to %d" % (max_value))
return
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)
return
return
def CheckParamBool(self, section, param):
"""
Make sure a parameter looks like a boolean, convert to native
boolean type if so
"""
if str(self.parameters[section][param]).lower() in [
"true", "t", "y", "yes", "on", "1", "1.0"]:
self.parameters[section][param] = True
return
elif str(self.parameters[section][param]).lower() in [
"false", "f", "n", "no", "off", "0", "0.0"]:
self.parameters[section][param] = False
return
self.parameter_errors[section][param] = "Must be boolean (true/false)"
return
class HelpfulFootprintWizardPlugin(pcbnew.FootprintWizardPlugin,
FootprintWizardParameterManager):
"""
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()
def GetValue(self):
raise NotImplementedError
# this value come from our KiCad Library Convention 1.0
def GetReferencePrefix(self):
return "REF"
def GetImage(self):
return ""
def GetTextSize(self):
"""
IPC nominal
"""
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)
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
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
def BuildThisFootprint(self):
"""
Draw the footprint.
This is specific to each footprint class, you need to implment
this to draw what you want
"""
raise NotImplementedError
def BuildFootprint( self ):
"""
Actually make the footprint. We defer all but the setup to
the implementing class
"""
self.buildmessages = ""
self.module = pcbnew.MODULE(None) # create a new module
# do it first, so if we return early, we don't segfault KiCad
if not self.ProcessParameters():
self.buildmessages = "Cannot build footprint: Parameters have errors:\n"
self.buildmessages += self._PrintParameterErrors()
return
self.buildmessages = ("Building new %s footprint with the following parameters:\n"
% self.name)
self.buildmessages += self._PrintParameterTable()
self.draw = FootprintWizardDrawingAids.FootprintWizardDrawingAids(
self.module)
self.module.SetValue(self.GetValue())
self.module.SetReference("%s**" % self.GetReferencePrefix())
fpid = pcbnew.LIB_ID(self.module.GetValue()) # 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

View File

@ -17,7 +17,7 @@
from __future__ import division
import pcbnew
import HelpfulFootprintWizardPlugin as HFPW
import FootprintWizardBase
import PadArray as PA
@ -29,7 +29,7 @@ class BGAPadGridArray(PA.PadGridArray):
n_x + 1)
class BGAWizard(HFPW.HelpfulFootprintWizardPlugin):
class BGAWizard(FootprintWizardBase.FootprintWizard):
def GetName(self):
return "BGA"
@ -38,35 +38,48 @@ class BGAWizard(HFPW.HelpfulFootprintWizardPlugin):
return "Ball Grid Array Footprint Wizard"
def GenerateParameterList(self):
self.AddParam("Pads", "pad pitch", self.uMM, 1)
self.AddParam("Pads", "pad size", self.uMM, 0.5)
self.AddParam("Pads", "row count", self.uNatural, 5)
self.AddParam("Pads", "column count", self.uNatural, 5)
self.AddParam("Pads", "outline x margin", self.uMM, 1)
self.AddParam("Pads", "outline y margin", self.uMM, 1)
self.AddParam("Pads", "pitch", self.uMM, 1, designator='p')
self.AddParam("Pads", "size", self.uMM, 0.5)
self.AddParam("Pads", "columns", self.uInteger, 5, designator="nx")
self.AddParam("Pads", "rows", self.uInteger, 5, designator="ny")
self.AddParam("Package", "width", self.uMM, 6, designator='X')
self.AddParam("Package", "length", self.uMM, 6, designator='Y')
self.AddParam("Package", "margin", self.uMM, 0.25, min_value=0.2, hint="Courtyard margin")
def CheckParameters(self):
self.CheckParamInt("Pads", "*row count")
self.CheckParamInt("Pads", "*column count")
# check that the package is large enough
width = pcbnew.ToMM(self.parameters['Pads']['pitch'] * self.parameters['Pads']['columns'])
length = pcbnew.ToMM(self.parameters['Pads']['pitch'] * self.parameters['Pads']['rows'])
self.CheckParam('Package','width',min_value=width,info="Package width is too small (< {w}mm)".format(w=width))
self.CheckParam('Package','length',min_value=length,info="Package length is too small (< {l}mm".format(l=length))
def GetValue(self):
pins = (self.parameters["Pads"]["*row count"]
* self.parameters["Pads"]["*column count"])
pins = (self.parameters["Pads"]["rows"] * self.parameters["Pads"]["columns"])
return "BGA_%d" % pins
return "BGA-{n}_{a}x{b}_{x}x{y}mm".format(
n = pins,
a = self.parameters['Pads']['columns'],
b = self.parameters['Pads']['rows'],
x = pcbnew.ToMM(self.parameters['Package']['width']),
y = pcbnew.ToMM(self.parameters['Package']['length'])
)
def BuildThisFootprint(self):
pads = self.parameters["Pads"]
rows = pads["*row count"]
cols = pads["*column count"]
pad_size = pads["pad size"]
rows = pads["rows"]
cols = pads["columns"]
pad_size = pads["size"]
pad_size = pcbnew.wxSize(pad_size, pad_size)
pad_pitch = pads["pad pitch"]
pad_pitch = pads["pitch"]
# add in the pads
pad = PA.PadMaker(self.module).SMTRoundPad(pads["pad size"])
pad = PA.PadMaker(self.module).SMTRoundPad(pads["size"])
pin1_pos = pcbnew.wxPoint(-((cols - 1) * pad_pitch) / 2,
-((rows - 1) * pad_pitch) / 2)
@ -74,21 +87,68 @@ class BGAWizard(HFPW.HelpfulFootprintWizardPlugin):
array = BGAPadGridArray(pad, cols, rows, pad_pitch, pad_pitch)
array.AddPadsToModule(self.draw)
#box
ssx = -pin1_pos.x + pads["outline x margin"]
ssy = -pin1_pos.y + pads["outline y margin"]
# Draw box outline on F.Fab layer
self.draw.SetLayer(pcbnew.F_Fab)
ssx = self.parameters['Package']['width'] / 2
ssy = self.parameters['Package']['length'] / 2
self.draw.BoxWithDiagonalAtCorner(0, 0, ssx*2, ssy*2,
pads["outline x margin"])
# Bevel should be 1mm nominal but we'll allow smaller values
if pcbnew.ToMM(ssx) < 1:
bevel = ssx
else:
bevel = pcbnew.FromMM(1)
# Box with 1mm bevel as per IPC7351C
self.draw.BoxWithDiagonalAtCorner(0, 0, ssx*2, ssy*2, bevel)
# Add IPC markings to F_Silk layer
self.draw.SetLayer(pcbnew.F_SilkS)
offset = pcbnew.FromMM(0.15)
len_x = 0.5 * ssx
len_y = 0.5 * ssy
edge = [
[ ssx + offset - len_x, -ssy - offset],
[ ssx + offset, -ssy - offset],
[ ssx + offset, -ssy - offset + len_y],
]
# Draw three square edges
self.draw.Polyline(edge)
self.draw.Polyline(edge, mirrorY=0)
self.draw.Polyline(edge, mirrorX=0, mirrorY=0)
# Draw pin-1 marker
bevel += offset
pin1 = [
[ -ssx - offset + len_x, -ssy - offset],
[ -ssx - offset + bevel, -ssy - offset],
[ -ssx - offset, -ssy - offset + bevel],
[ -ssx - offset, -ssy - offset + len_y],
]
# Remove lines if the package is too small
if bevel > len_x:
pin1 = pin1[1:]
if bevel > len_y:
pin1 = pin1[:-1]
self.draw.Polyline(pin1)
# Draw a circle in the bevel void
self.draw.Circle( -ssx, -ssy, pcbnew.FromMM(0.2), filled=True)
# Courtyard
cmargin = self.draw.GetLineThickness()
cmargin = self.parameters['Package']['margin']
self.draw.SetLayer(pcbnew.F_CrtYd)
sizex = (ssx + cmargin) * 2
sizey = (ssy + cmargin) * 2
# round size to nearest 0.1mm, rectangle will thus land on a 0.05mm grid
sizex = self.PutOnGridMM(sizex, 0.1)
sizey = self.PutOnGridMM(sizey, 0.1)
sizex = pcbnew.PutOnGridMM(sizex, 0.1)
sizey = pcbnew.PutOnGridMM(sizey, 0.1)
# set courtyard line thickness to the one defined in KLC
self.draw.SetLineThickness(pcbnew.FromMM(0.05))
self.draw.Box(0, 0, sizex, sizey)

View File

@ -18,11 +18,11 @@ from __future__ import division
import math
import pcbnew
import HelpfulFootprintWizardPlugin as HFPW
import FootprintWizardBase
import PadArray as PA
class circular_pad_array_wizard(HFPW.HelpfulFootprintWizardPlugin):
class circular_pad_array_wizard(FootprintWizardBase.FootprintWizard):
def GetName(self):
return "Circular Pad Array"
@ -32,52 +32,84 @@ class circular_pad_array_wizard(HFPW.HelpfulFootprintWizardPlugin):
def GenerateParameterList(self):
self.AddParam("Pads", "n", self.uNatural, 6)
self.AddParam("Pads", "pad width", self.uMM, 1.5)
self.AddParam("Pads", "drill", self.uMM, 1)
self.AddParam("Pads", "circle diameter", self.uMM, 5)
self.AddParam("Pads", "first pad angle", self.uNatural, 0)
self.AddParam("Pads", "number clockwise", self.uBool, True)
self.AddParam("Pads", "first pad number", self.uNatural, 1)
self.AddParam("Pads", "count", self.uInteger, 6, min_value=1, designator='n')
self.AddParam("Pads", "center diameter", self.uMM, 5, min_value=0, designator='r', hint="Centre distance between pads")
self.AddParam("Pads", "diameter", self.uMM, 1.5)
self.AddParam("Pads", "drill", self.uMM, 0.8)
self.AddParam("Pads", "angle", self.uDegrees, 0, designator='a')
self.AddParam("Numbering", "initial", self.uInteger, 1, min_value=1)
#self.AddParam("Numbering", "increment", self.uInteger, 1, min_value=1)
self.AddParam("Numbering", "clockwise", self.uBool, True)
self.AddParam("Outline", "diameter", self.uMM, 7, designator='D')
self.AddParam("Outline", "margin", self.uMM, 0.25, min_value=0.2)
def CheckParameters(self):
self.CheckParamInt("Pads", "*n")
self.CheckParamInt("Pads", "*first pad number")
self.CheckParamBool("Pads", "*number clockwise")
pads = self.parameters['Pads']
numbering = self.parameters['Numbering']
outline = self.parameters['Outline']
# Check that pads do not overlap
pad_dia = pcbnew.ToMM(pads['diameter'])
centres = pcbnew.ToMM(pads['center diameter'])
n_pads = pads['count']
self.CheckParam('Pads','diameter',max_value=centres*math.pi/n_pads,info="Pads overlap")
# Check that the pads fit inside the outline
d_min = pad_dia + centres
self.CheckParam("Outline","diameter",min_value=d_min, info="Outline diameter is too small")
def GetValue(self):
pins = self.parameters["Pads"]["*n"]
pins = self.parameters["Pads"]["count"]
return "CPA_%d" % pins
def BuildThisFootprint(self):
prm = self.parameters['Pads']
pads = self.parameters['Pads']
numbering = self.parameters['Numbering']
outline = self.parameters['Outline']
pad_size = prm['pad width']
pad_size = pads['diameter']
pad = PA.PadMaker(self.module).THPad(
prm['pad width'], prm['pad width'], prm['drill'])
pad = PA.PadMaker(self.module).THPad(pads['diameter'], pads['diameter'], pads['drill'])
array = PA.PadCircleArray(
pad, prm['*n'], prm['circle diameter'] / 2,
angle_offset=prm["*first pad angle"],
pad, pads['count'], pads['center diameter'] / 2,
angle_offset=pads["angle"],
centre=pcbnew.wxPoint(0, 0),
clockwise=prm["*number clockwise"])
clockwise=numbering["clockwise"])
array.SetFirstPadInArray(prm["*first pad number"])
array.SetFirstPadInArray(numbering["initial"])
array.AddPadsToModule(self.draw)
body_radius = (prm['circle diameter'] + prm['pad width'])/2 + self.draw.GetLineThickness()
# Draw the outline
body_radius = outline['diameter'] / 2
self.draw.SetLayer(pcbnew.F_Fab)
self.draw.GetLineThickness()
self.draw.Circle(0, 0, body_radius)
#silkscreen
body_radius += pcbnew.FromMM(0.15)
self.draw.SetLayer(pcbnew.F_SilkS)
self.draw.Circle(0, 0, body_radius)
# courtyard
self.draw.SetLayer(pcbnew.F_CrtYd)
self.draw.SetLineThickness(pcbnew.FromMM(0.05))
self.draw.Circle(0, 0, body_radius + outline['margin'])
# Text size
text_size = self.GetTextSize() # IPC nominal
thickness = self.GetTextThickness()
textposy = body_radius + self.draw.GetLineThickness()/2 + self.GetTextSize()/2 + thickness
textposy = body_radius + self.draw.GetLineThickness()/2 + self.GetTextSize()/2 + thickness + + outline['margin']
self.draw.Value( 0, textposy, text_size )
self.draw.Reference( 0, -textposy, text_size )
circular_pad_array_wizard().register()

View File

@ -17,55 +17,80 @@
from __future__ import division
import pcbnew
import HelpfulFootprintWizardPlugin as HFPW
import pcbnew
import FootprintWizardBase
import PadArray as PA
class QFNWizard(HFPW.HelpfulFootprintWizardPlugin):
class QFNWizard(FootprintWizardBase.FootprintWizard):
def GetName(self):
return "QFN"
def GetDescription(self):
return "Quad Flat No-lead with Exposed Pad footprint wizard"
return "Quad Flat No-lead (QFN) footprint wizard"
def GenerateParameterList(self):
self.AddParam("Pads", "n", self.uNatural, 100)
self.AddParam("Pads", "pad pitch", self.uMM, 0.5)
self.AddParam("Pads", "pad width", self.uMM, 0.25)
self.AddParam("Pads", "pad length", self.uMM, 1.5)
self.AddParam("Pads", "oval", self.uBool, True)
self.AddParam("Pads", "thermal vias", self.uBool, True)
self.AddParam("Pads", "thermal vias drill", self.uMM, 0.3)
self.AddParam("Pads", "epad subdiv x", self.uNatural, 2)
self.AddParam("Pads", "epad subdiv y", self.uNatural, 2)
self.AddParam("Package", "package width", self.uMM, 14)
self.AddParam("Package", "package height", self.uMM, 14)
self.AddParam("Package", "courtyard margin", self.uMM, 1)
#TODO - Allow different number of pads in x and y directions
self.AddParam("Pads", "n", self.uInteger, 100, multiple=4, min_value=4)
self.AddParam("Pads", "pitch", self.uMM, 0.5, designator='e')
self.AddParam("Pads", "width", self.uMM, 0.25, designator='X1')
self.AddParam("Pads", "length", self.uMM, 1.5, designator='Y1')
self.AddParam("Pads", "oval", self.uBool, True)
self.AddParam("EPad", "epad", self.uBool, True)
self.AddParam("EPad", "width", self.uMM, 10, designator="E2")
self.AddParam("EPad", "length", self.uMM, 10, designator="D2")
self.AddParam("EPad", "thermal vias", self.uBool, False)
self.AddParam("EPad", "thermal vias drill", self.uMM, 1, min_value=0.1)
self.AddParam("EPad", "x divisions", self.uInteger, 2, min_value=1)
self.AddParam("EPad", "y divisions", self.uInteger, 2, min_value=1)
self.AddParam("EPad", "paste margin", self.uMM, 0.1)
self.AddParam("Package", "width", self.uMM, 14, designator='E')
self.AddParam("Package", "height", self.uMM, 14, designator='D')
self.AddParam("Package", "margin", self.uMM, 0.25, minValue=0.2)
@property
def pads(self):
return self.parameters['Pads']
@property
def epad(self):
return self.parameters['EPad']
@property
def package(self):
return self.parameters['Package']
def CheckParameters(self):
self.CheckParamInt("Pads", "*n", is_multiple_of=4)
self.CheckParamBool("Pads", "*oval")
self.CheckParamBool("Pads", "*thermal vias")
pass
def GetValue(self):
return "QFN_%d" % self.parameters["Pads"]["*n"]
return "QFN-{n}_{ep}{x:g}x{y:g}_Pitch{p:g}mm".format(
n = self.pads['n'],
ep = "EP_" if self.epad['epad'] else '',
x = pcbnew.ToMM(self.package['width']),
y = pcbnew.ToMM(self.package['height']),
p = pcbnew.ToMM(self.pads['pitch'])
)
def BuildThisFootprint(self):
pads = self.parameters["Pads"]
pad_pitch = pads["pad pitch"]
pad_length = pads["pad length"]
pad_width = pads["pad width"]
pad_pitch = self.pads["pitch"]
pad_length = self.pads["length"]
pad_width = self.pads["width"]
v_pitch = self.parameters["Package"]["package height"]
h_pitch = self.parameters["Package"]["package width"]
v_pitch = self.package["height"]
h_pitch = self.package["width"]
pads_per_row = pads["*n"] // 4
pads_per_row = int(self.pads["n"] // 4)
row_len = (pads_per_row - 1) * pad_pitch
pad_shape = pcbnew.PAD_SHAPE_OVAL if pads["*oval"] else pcbnew.PAD_SHAPE_RECT
pad_shape = pcbnew.PAD_SHAPE_OVAL if self.pads["oval"] else pcbnew.PAD_SHAPE_RECT
h_pad = PA.PadMaker(self.module).SMDPad( pad_length, pad_width,
shape=pad_shape, rot_degree=90.0)
@ -97,79 +122,88 @@ class QFNWizard(HFPW.HelpfulFootprintWizardPlugin):
array.SetFirstPadInArray(3*pads_per_row + 1)
array.AddPadsToModule(self.draw)
lim_x = self.parameters["Package"]["package width"] / 2
lim_y = self.parameters["Package"]["package height"] / 2
lim_x = self.package["width"] / 2
lim_y = self.package["height"] / 2
inner = (row_len / 2) + pad_pitch
# epad
epad_width = self.parameters["Package"]["package height"] - (2*pad_length)
epad_length = self.parameters["Package"]["package width"] - (2*pad_length)
epad_subdv_x = pads["*epad subdiv x"]
epad_subdv_y = pads["*epad subdiv y"]
epad_via_drill = pads["thermal vias drill"]
epad_width = self.epad["width"]
epad_length = self.epad["length"]
if (epad_subdv_y != 0 and epad_subdv_x != 0) and (epad_subdv_y != 1 or epad_subdv_x != 1):
# Create the master pad (one area) on front solder mask, and perhaps of front copper layer
# at location 0,0
emasterpad = PA.PadMaker(self.module).SMDPad( epad_length, epad_width,
shape=pcbnew.PAD_SHAPE_RECT, rot_degree=0.0)
emasterpad.SetLayerSet(pcbnew.LSET(pcbnew.F_Mask)) # currently, only on solder mask
emasterpad.SetPadName(pads["*n"]+1)
self.module.Add(emasterpad)
epad_ny = self.epad["x divisions"]
epad_nx = self.epad["y divisions"]
px = pcbnew.FromMM(0.1); py = pcbnew.FromMM(0.1)
esubpad_size_x = epad_length / epad_subdv_x - px
esubpad_size_y = epad_width / epad_subdv_y - py
epad1_pos = pcbnew.wxPoint(-(esubpad_size_x*(epad_subdv_x-1)/2), -esubpad_size_y*(epad_subdv_y-1)/2)
epad = PA.PadMaker(self.module).SMDPad( esubpad_size_y, esubpad_size_x,
shape=pcbnew.PAD_SHAPE_RECT, rot_degree=0.0)
array = PA.EPADGridArray(epad, epad_subdv_x, epad_subdv_y, esubpad_size_x + px, esubpad_size_y + py, pcbnew.wxPoint(0,0))
array.SetFirstPadInArray(pads["*n"]+1)
array.AddPadsToModule(self.draw)
if pads["*thermal vias"]:
via_diam = min(esubpad_size_y, esubpad_size_x)/3.
thpad = PA.PadMaker(self.module).THRoundPad(via_diam, min(via_diam/2, epad_via_drill))
layerset = pcbnew.LSET.AllCuMask()
layerset.AddLayer(pcbnew.B_Mask)
layerset.AddLayer(pcbnew.F_Mask)
thpad.SetLayerSet(layerset)
array2 = PA.EPADGridArray(thpad, epad_subdv_x, epad_subdv_y, esubpad_size_x + px, esubpad_size_y + py, pcbnew.wxPoint(0,0))
array2.SetFirstPadInArray(pads["*n"]+1)
array2.AddPadsToModule(self.draw)
else:
epad = PA.PadMaker(self.module).SMDPad(epad_length, epad_width)
epad_pos = pcbnew.wxPoint(0,0)
array = PA.PadLineArray(epad, 1, 1, False, epad_pos)
array.SetFirstPadInArray(pads["*n"]+1)
array.AddPadsToModule(self.draw)
if pads["*thermal vias"]:
via_diam = min(epad_length, epad_width)/3.
thpad = PA.PadMaker(self.module).THRoundPad( via_diam, min(via_diam/2, epad_via_drill))
layerset = pcbnew.LSET.AllCuMask()
layerset.AddLayer(pcbnew.B_Mask)
layerset.AddLayer(pcbnew.F_Mask)
thpad.SetLayerSet(layerset)
array2 = PA.PadLineArray(thpad, 1, 1, False, epad_pos)
array2.SetFirstPadInArray(pads["*n"]+1)
array2.AddPadsToModule(self.draw)
epad_via_drill = self.epad["thermal vias drill"]
#top left - diagonal
self.draw.Line(-lim_x, -inner, -inner, -lim_y)
# top right
self.draw.Polyline([(inner, -lim_y), (lim_x, -lim_y), (lim_x, -inner)])
# bottom left
self.draw.Polyline([(-inner, lim_y), (-lim_x, lim_y), (-lim_x, inner)])
# bottom right
self.draw.Polyline([(inner, lim_y), (lim_x, lim_y), (lim_x, inner)])
# Create a central exposed pad?
if self.epad['epad'] == True:
epad_num = self.pads['n'] + 1
epad_w = epad_length / epad_nx
epad_l = epad_width / epad_ny
# Create the epad
epad = PA.PadMaker(self.module).SMDPad( epad_w, epad_l, shape=pcbnew.PAD_SHAPE_RECT )
epad.SetLocalSolderPasteMargin( -1 * self.epad['paste margin'] )
# set pad layers
layers = pcbnew.LSET(pcbnew.F_Mask)
layers.AddLayer(pcbnew.F_Cu)
layers.AddLayer(pcbnew.F_Paste)
epad.SetPadName(epad_num)
array = PA.EPADGridArray( epad, epad_ny, epad_nx, epad_l, epad_w, pcbnew.wxPoint(0,0) )
array.SetFirstPadInArray(epad_num)
array.AddPadsToModule(self.draw)
if self.epad['thermal vias']:
# create the thermal via
via_diam = min(epad_w, epad_l) / 2
via_drill = min(via_diam / 2, epad_via_drill)
via = PA.PadMaker(self.module).THRoundPad(via_diam, via_drill)
layers = pcbnew.LSET.AllCuMask()
layers.AddLayer(pcbnew.B_Mask)
layers.AddLayer(pcbnew.F_Mask)
via.SetLayerSet(layers)
via_array = PA.EPADGridArray(via, epad_ny, epad_nx, epad_l, epad_w, pcbnew.wxPoint(0,0) )
via_array.SetFirstPadInArray(epad_num)
via_array.AddPadsToModule(self.draw)
# Draw the package outline on the F.Fab layer
bevel = min( pcbnew.FromMM(1.0), self.package['width']/2, self.package['height']/2 )
self.draw.SetLayer(pcbnew.F_Fab)
w = self.package['width']
h = self.package['height']
self.draw.BoxWithDiagonalAtCorner(0, 0, w, h, bevel)
# Silkscreen
self.draw.SetLayer(pcbnew.F_SilkS)
offset = self.draw.GetLineThickness()
clip = row_len / 2 + self.pads['pitch']
self.draw.Polyline( [ [ clip, -h/2-offset], [ w/2+offset,-h/2-offset], [ w/2+offset, -clip] ] ) # top right
self.draw.Polyline( [ [ clip, h/2+offset], [ w/2+offset, h/2+offset], [ w/2+offset, clip] ] ) # bottom right
self.draw.Polyline( [ [-clip, h/2+offset], [-w/2-offset, h/2+offset], [-w/2-offset, clip] ] ) # bottom left
# Add pin-1 indication as per IPC-7351C
self.draw.Line(-clip, -h/2-offset, -w/2-pad_length/2, -h/2-offset)
# Courtyard
cmargin = self.parameters["Package"]["courtyard margin"]
cmargin = self.package["margin"]
self.draw.SetLayer(pcbnew.F_CrtYd)
sizex = (lim_x + cmargin) * 2 + pad_length/2.
sizey = (lim_y + cmargin) * 2 + pad_length/2.
sizex = (lim_x + cmargin) * 2 + pad_length
sizey = (lim_y + cmargin) * 2 + pad_length
# round size to nearest 0.1mm, rectangle will thus land on a 0.05mm grid
sizex = self.PutOnGridMM(sizex, 0.1)
sizey = self.PutOnGridMM(sizey, 0.1)
sizex = pcbnew.PutOnGridMM(sizex, 0.1)
sizey = pcbnew.PutOnGridMM(sizey, 0.1)
# set courtyard line thickness to the one defined in KLC
thick = self.draw.GetLineThickness()
self.draw.SetLineThickness(pcbnew.FromMM(0.05))

View File

@ -17,53 +17,64 @@
from __future__ import division
import pcbnew
import HelpfulFootprintWizardPlugin
import FootprintWizardBase
import PadArray as PA
class QFPWizard(HelpfulFootprintWizardPlugin.HelpfulFootprintWizardPlugin):
class QFPWizard(FootprintWizardBase.FootprintWizard):
def GetName(self):
return "QFP"
def GetDescription(self):
return "Quad Flat Package footprint wizard"
return "Quad Flat Package (QFP) footprint wizard"
def GenerateParameterList(self):
self.AddParam("Pads", "n", self.uNatural, 100)
self.AddParam("Pads", "pad pitch", self.uMM, 0.5)
self.AddParam("Pads", "pad width", self.uMM, 0.25)
self.AddParam("Pads", "pad length", self.uMM, 1.5)
self.AddParam("Pads", "vertical pitch", self.uMM, 15)
self.AddParam("Pads", "horizontal pitch", self.uMM, 15)
self.AddParam("Pads", "n", self.uInteger, 100, multiple=4, min_value=4)
self.AddParam("Pads", "pitch", self.uMM, 0.5, designator='e')
self.AddParam("Pads", "width", self.uMM, 0.25, designator='X1')
self.AddParam("Pads", "length", self.uMM, 1.5, designator='Y1')
self.AddParam("Pads", "horizontal spacing", self.uMM, 15, designator='C1')
self.AddParam("Pads", "vertical spacing", self.uMM, 15, designator='C2')
self.AddParam("Pads", "oval", self.uBool, True)
self.AddParam("Package", "package width", self.uMM, 14)
self.AddParam("Package", "package height", self.uMM, 14)
self.AddParam("Package", "courtyard margin", self.uMM, 1)
self.AddParam("Package", "width", self.uMM, 14, designator='D1')
self.AddParam("Package", "height", self.uMM, 14, designator='E1')
self.AddParam("Package", "courtyard margin", self.uMM, 0.25, min_value=0.2)
@property
def pads(self):
return self.parameters['Pads']
@property
def package(self):
return self.parameters['Package']
def CheckParameters(self):
self.CheckParamInt("Pads", "*n", is_multiple_of=4)
self.CheckParamBool("Pads", "*oval")
# todo - custom checking
pass
def GetValue(self):
return "QFP_%d" % self.parameters["Pads"]["*n"]
return "QFP-{n}_{x:g}x{y:g}_Pitch{p:g}mm".format(
n = self.pads['n'],
x = pcbnew.ToMM(self.package['width']),
y = pcbnew.ToMM(self.package['height']),
p = pcbnew.ToMM(self.pads['pitch'])
)
def BuildThisFootprint(self):
pads = self.parameters["Pads"]
pad_pitch = pads["pad pitch"]
pad_length = self.parameters["Pads"]["pad length"]
pad_width = self.parameters["Pads"]["pad width"]
pad_pitch = self.pads["pitch"]
pad_length = self.pads["length"]
pad_width = self.pads["width"]
v_pitch = pads["vertical pitch"]
h_pitch = pads["horizontal pitch"]
v_pitch = self.pads["vertical spacing"]
h_pitch = self.pads["horizontal spacing"]
pads_per_row = pads["*n"] // 4
pads_per_row = int(self.pads["n"] // 4)
row_len = (pads_per_row - 1) * pad_pitch
pad_shape = pcbnew.PAD_SHAPE_OVAL if pads["*oval"] else pcbnew.PAD_SHAPE_RECT
pad_shape = pcbnew.PAD_SHAPE_OVAL if self.pads["oval"] else pcbnew.PAD_SHAPE_RECT
h_pad = PA.PadMaker(self.module).SMDPad( pad_length, pad_width,
shape=pad_shape, rot_degree=90.0)
@ -95,27 +106,49 @@ class QFPWizard(HelpfulFootprintWizardPlugin.HelpfulFootprintWizardPlugin):
array.SetFirstPadInArray(3*pads_per_row + 1)
array.AddPadsToModule(self.draw)
lim_x = self.parameters["Package"]["package width"] / 2
lim_y = self.parameters["Package"]["package height"] / 2
offset = pcbnew.FromMM(0.15)
x = self.parameters["Package"]["width"] / 2 + offset
y = self.parameters["Package"]["height"] / 2 + offset
inner = (row_len / 2) + pad_pitch
#top left - diagonal
self.draw.Line(-lim_x, -inner, -inner, -lim_y)
# Add outline to F_Fab layer
self.draw.SetLayer(pcbnew.F_Fab)
bevel = min( pcbnew.FromMM(1.0), self.package['width']/2, self.package['height']/2 )
w = self.package['width']
h = self.package['height']
# outermost limits of pins
right_edge = (h_pitch + pad_length) / 2
left_edge = -right_edge
bottom_edge = (v_pitch + pad_length) / 2
top_edge = -bottom_edge
self.draw.BoxWithDiagonalAtCorner(0, 0, w, h, bevel)
# Draw silkscreen
self.draw.SetLayer(pcbnew.F_SilkS)
#top left - as per IPC-7351C
self.draw.Polyline([(-inner, -y), (-x, -y), (-x, -inner), (left_edge, -inner)])
# top right
self.draw.Polyline([(inner, -lim_y), (lim_x, -lim_y), (lim_x, -inner)])
self.draw.Polyline([(inner, -y), (x, -y), (x, -inner)])
# bottom left
self.draw.Polyline([(-inner, lim_y), (-lim_x, lim_y), (-lim_x, inner)])
self.draw.Polyline([(-inner, y), (-x, y), (-x, inner)])
# bottom right
self.draw.Polyline([(inner, lim_y), (lim_x, lim_y), (lim_x, inner)])
self.draw.Polyline([(inner, y), (x, y), (x, inner)])
# Courtyard
cmargin = self.parameters["Package"]["courtyard margin"]
self.draw.SetLayer(pcbnew.F_CrtYd)
sizex = (lim_x + cmargin) * 2 + pad_length
sizey = (lim_y + cmargin) * 2 + pad_length
sizex = (right_edge + cmargin) * 2
sizey = (bottom_edge + cmargin) * 2
# round size to nearest 0.1mm, rectangle will thus land on a 0.05mm grid
sizex = self.PutOnGridMM(sizex, 0.1)
sizey = self.PutOnGridMM(sizey, 0.1)
sizex = pcbnew.PutOnGridMM(sizex, 0.1)
sizey = pcbnew.PutOnGridMM(sizey, 0.1)
# set courtyard line thickness to the one defined in KLC
thick = self.draw.GetLineThickness()
self.draw.SetLineThickness(pcbnew.FromMM(0.05))

View File

@ -0,0 +1,827 @@
#
# QR Code Generator for Python
#
# Copyright (c) 2012 Kazuhiko Arase
#
# URL: http://www.d-project.com/
#
# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php
#
# The word 'QR Code' is registered trademark of
# DENSO WAVE INCORPORATED
# http://www.denso-wave.com/qrcode/faqpatent-e.html
#
"""QR Code Generator for Python
from qrcode import QRCode, ErrorCorrectLevel
# generate with explicit type number
qr = QRCode()
qr.setTypeNumber(4)
qr.setErrorCorrectLevel(ErrorCorrectLevel.M)
qr.addData('here comes qr!')
qr.make()
# generate with auto type number
# qr = QRCode.getMinimumQRCode('here comes qr!', ErrorCorrectLevel.M)
# create an image
for r in range(qr.getModuleCount() ):
for c in range(qr.getModuleCount() ):
color = black if qr.isDark(r, c) else white
# set pixel ...
"""
class QRCode:
PAD0 = 0xEC
PAD1 = 0x11
def __init__(self):
self.typeNumber = 1
self.errorCorrectLevel = ErrorCorrectLevel.H
self.qrDataList = []
self.modules = []
self.moduleCount = 0
def getTypeNumber(self):
return self.typeNumber
def setTypeNumber(self, typeNumber):
self.typeNumber = typeNumber
def getErrorCorrectLevel(self):
return self.errorCorrectLevel
def setErrorCorrectLevel(self, errorCorrectLevel):
self.errorCorrectLevel = errorCorrectLevel
def clearData(self):
self.qrDataList = []
def addData(self, data):
self.qrDataList.append(QR8BitByte(data) )
def getDataCount(self):
return len(self.qrDataList)
def getData(self, index):
return self.qrDataList[index]
def isDark(self, row, col):
return (self.modules[row][col] if self.modules[row][col] != None
else False)
def getModuleCount(self):
return self.moduleCount
def make(self):
self._make(False, self._getBestMaskPattern() )
def _getBestMaskPattern(self):
minLostPoint = 0
pattern = 0
for i in range(8):
self._make(True, i)
lostPoint = QRUtil.getLostPoint(self)
if i == 0 or minLostPoint > lostPoint:
minLostPoint = lostPoint
pattern = i
return pattern
def _make(self, test, maskPattern):
self.moduleCount = self.typeNumber * 4 + 17
self.modules = [[None] * self.moduleCount
for i in range(self.moduleCount)]
self._setupPositionProbePattern(0, 0)
self._setupPositionProbePattern(self.moduleCount - 7, 0)
self._setupPositionProbePattern(0, self.moduleCount - 7)
self._setupPositionAdjustPattern()
self._setupTimingPattern()
self._setupTypeInfo(test, maskPattern)
if self.typeNumber >= 7:
self._setupTypeNumber(test)
data = QRCode._createData(
self.typeNumber,
self.errorCorrectLevel,
self.qrDataList)
self._mapData(data, maskPattern)
def _mapData(self, data, maskPattern):
rows = list(range(self.moduleCount) )
cols = [col - 1 if col <= 6 else col
for col in range(self.moduleCount - 1, 0, -2)]
maskFunc = QRUtil.getMaskFunction(maskPattern)
byteIndex = 0
bitIndex = 7
for col in cols:
rows.reverse()
for row in rows:
for c in range(2):
if self.modules[row][col - c] == None:
dark = False
if byteIndex < len(data):
dark = ( (data[byteIndex] >> bitIndex) & 1) == 1
if maskFunc(row, col - c):
dark = not dark
self.modules[row][col - c] = dark
bitIndex -= 1
if bitIndex == -1:
byteIndex += 1
bitIndex = 7
def _setupPositionAdjustPattern(self):
pos = QRUtil.getPatternPosition(self.typeNumber)
for row in pos:
for col in pos:
if self.modules[row][col] != None:
continue
for r in range(-2, 3):
for c in range(-2, 3):
self.modules[row + r][col + c] = (
r == -2 or r == 2 or c == -2 or c == 2
or (r == 0 and c == 0) )
def _setupPositionProbePattern(self, row, col):
for r in range(-1, 8):
for c in range(-1, 8):
if (row + r <= -1 or self.moduleCount <= row + r
or col + c <= -1 or self.moduleCount <= col + c):
continue
self.modules[row + r][col + c] = (
(0 <= r and r <= 6 and (c == 0 or c == 6) )
or (0 <= c and c <= 6 and (r == 0 or r == 6) )
or (2 <= r and r <= 4 and 2 <= c and c <= 4) )
def _setupTimingPattern(self):
for r in range(8, self.moduleCount - 8):
if self.modules[r][6] != None:
continue
self.modules[r][6] = r % 2 == 0
for c in range(8, self.moduleCount - 8):
if self.modules[6][c] != None:
continue
self.modules[6][c] = c % 2 == 0
def _setupTypeNumber(self, test):
bits = QRUtil.getBCHTypeNumber(self.typeNumber)
for i in range(18):
self.modules[i // 3][i % 3 + self.moduleCount - 8 - 3] = (
not test and ( (bits >> i) & 1) == 1)
for i in range(18):
self.modules[i % 3 + self.moduleCount - 8 - 3][i // 3] = (
not test and ( (bits >> i) & 1) == 1)
def _setupTypeInfo(self, test, maskPattern):
data = (self.errorCorrectLevel << 3) | maskPattern
bits = QRUtil.getBCHTypeInfo(data)
# vertical
for i in range(15):
mod = not test and ( (bits >> i) & 1) == 1
if i < 6:
self.modules[i][8] = mod
elif i < 8:
self.modules[i + 1][8] = mod
else:
self.modules[self.moduleCount - 15 + i][8] = mod
# horizontal
for i in range(15):
mod = not test and ( (bits >> i) & 1) == 1
if i < 8:
self.modules[8][self.moduleCount - i - 1] = mod
elif i < 9:
self.modules[8][15 - i - 1 + 1] = mod
else:
self.modules[8][15 - i - 1] = mod
# fixed
self.modules[self.moduleCount - 8][8] = not test
@staticmethod
def _createData(typeNumber, errorCorrectLevel, dataArray):
rsBlocks = RSBlock.getRSBlocks(typeNumber, errorCorrectLevel)
buffer = BitBuffer()
for data in dataArray:
buffer.put(data.getMode(), 4)
buffer.put(data.getLength(), data.getLengthInBits(typeNumber) )
data.write(buffer)
totalDataCount = sum(rsBlock.getDataCount()
for rsBlock in rsBlocks)
if buffer.getLengthInBits() > totalDataCount * 8:
raise Exception('code length overflow. (%s > %s)' %
(buffer.getLengthInBits(), totalDataCount * 8) )
# end code
if buffer.getLengthInBits() + 4 <= totalDataCount * 8:
buffer.put(0, 4)
# padding
while buffer.getLengthInBits() % 8 != 0:
buffer.put(False)
# padding
while True:
if buffer.getLengthInBits() >= totalDataCount * 8:
break
buffer.put(QRCode.PAD0, 8)
if buffer.getLengthInBits() >= totalDataCount * 8:
break
buffer.put(QRCode.PAD1, 8)
return QRCode._createBytes(buffer, rsBlocks)
@staticmethod
def _createBytes(buffer, rsBlocks):
offset = 0
maxDcCount = 0
maxEcCount = 0
dcdata = [None] * len(rsBlocks)
ecdata = [None] * len(rsBlocks)
for r in range(len(rsBlocks) ):
dcCount = rsBlocks[r].getDataCount()
ecCount = rsBlocks[r].getTotalCount() - dcCount
maxDcCount = max(maxDcCount, dcCount)
maxEcCount = max(maxEcCount, ecCount)
dcdata[r] = [0] * dcCount
for i in range(len(dcdata[r] ) ):
dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset]
offset += dcCount
rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount)
rawPoly = Polynomial(dcdata[r], rsPoly.getLength() - 1)
modPoly = rawPoly.mod(rsPoly)
ecdata[r] = [0] * (rsPoly.getLength() - 1)
for i in range(len(ecdata[r]) ):
modIndex = i + modPoly.getLength() - len(ecdata[r])
ecdata[r][i] = modPoly.get(modIndex) if modIndex >= 0 else 0
totalCodeCount = sum(rsBlock.getTotalCount()
for rsBlock in rsBlocks)
data = [0] * totalCodeCount
index = 0
for i in range(maxDcCount):
for r in range(len(rsBlocks) ):
if i < len(dcdata[r] ):
data[index] = dcdata[r][i]
index += 1
for i in range(maxEcCount):
for r in range(len(rsBlocks) ):
if i < len(ecdata[r] ):
data[index] = ecdata[r][i]
index += 1
return data
@staticmethod
def getMinimumQRCode(data, errorCorrectLevel):
mode = Mode.MODE_8BIT_BYTE # fixed to 8bit byte
qr = QRCode()
qr.setErrorCorrectLevel(errorCorrectLevel)
qr.addData(data)
length = qr.getData(0).getLength()
for typeNumber in range(1, 11):
if length <= QRUtil.getMaxLength(
typeNumber, mode, errorCorrectLevel):
qr.setTypeNumber(typeNumber)
break
qr.make()
return qr
class Mode:
MODE_NUMBER = 1 << 0
MODE_ALPHA_NUM = 1 << 1
MODE_8BIT_BYTE = 1 << 2
MODE_KANJI = 1 << 3
class ErrorCorrectLevel:
L = 1 # 7%
M = 0 # 15%
Q = 3 # 25%
H = 2 # 30%
class MaskPattern:
PATTERN000 = 0
PATTERN001 = 1
PATTERN010 = 2
PATTERN011 = 3
PATTERN100 = 4
PATTERN101 = 5
PATTERN110 = 6
PATTERN111 = 7
class QRUtil:
@staticmethod
def getPatternPosition(typeNumber):
return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1]
PATTERN_POSITION_TABLE = [
[],
[6, 18],
[6, 22],
[6, 26],
[6, 30],
[6, 34],
[6, 22, 38],
[6, 24, 42],
[6, 26, 46],
[6, 28, 50],
[6, 30, 54],
[6, 32, 58],
[6, 34, 62],
[6, 26, 46, 66],
[6, 26, 48, 70],
[6, 26, 50, 74],
[6, 30, 54, 78],
[6, 30, 56, 82],
[6, 30, 58, 86],
[6, 34, 62, 90],
[6, 28, 50, 72, 94],
[6, 26, 50, 74, 98],
[6, 30, 54, 78, 102],
[6, 28, 54, 80, 106],
[6, 32, 58, 84, 110],
[6, 30, 58, 86, 114],
[6, 34, 62, 90, 118],
[6, 26, 50, 74, 98, 122],
[6, 30, 54, 78, 102, 126],
[6, 26, 52, 78, 104, 130],
[6, 30, 56, 82, 108, 134],
[6, 34, 60, 86, 112, 138],
[6, 30, 58, 86, 114, 142],
[6, 34, 62, 90, 118, 146],
[6, 30, 54, 78, 102, 126, 150],
[6, 24, 50, 76, 102, 128, 154],
[6, 28, 54, 80, 106, 132, 158],
[6, 32, 58, 84, 110, 136, 162],
[6, 26, 54, 82, 110, 138, 166],
[6, 30, 58, 86, 114, 142, 170]
]
MAX_LENGTH = [
[ [41, 25, 17, 10], [34, 20, 14, 8], [27, 16, 11, 7], [17, 10, 7, 4] ],
[ [77, 47, 32, 20], [63, 38, 26, 16], [48, 29, 20, 12], [34, 20, 14, 8] ],
[ [127, 77, 53, 32], [101, 61, 42, 26], [77, 47, 32, 20], [58, 35, 24, 15] ],
[ [187, 114, 78, 48], [149, 90, 62, 38], [111, 67, 46, 28], [82, 50, 34, 21] ],
[ [255, 154, 106, 65], [202, 122, 84, 52], [144, 87, 60, 37], [106, 64, 44, 27] ],
[ [322, 195, 134, 82], [255, 154, 106, 65], [178, 108, 74, 45], [139, 84, 58, 36] ],
[ [370, 224, 154, 95], [293, 178, 122, 75], [207, 125, 86, 53], [154, 93, 64, 39] ],
[ [461, 279, 192, 118], [365, 221, 152, 93], [259, 157, 108, 66], [202, 122, 84, 52] ],
[ [552, 335, 230, 141], [432, 262, 180, 111], [312, 189, 130, 80], [235, 143, 98, 60] ],
[ [652, 395, 271, 167], [513, 311, 213, 131], [364, 221, 151, 93], [288, 174, 119, 74] ]
]
@staticmethod
def getMaxLength(typeNumber, mode, errorCorrectLevel):
t = typeNumber - 1
e = {
ErrorCorrectLevel.L: 0,
ErrorCorrectLevel.M: 1,
ErrorCorrectLevel.Q: 2,
ErrorCorrectLevel.H: 3
}[errorCorrectLevel]
m = {
Mode.MODE_NUMBER: 0,
Mode.MODE_ALPHA_NUM: 1,
Mode.MODE_8BIT_BYTE: 2,
Mode.MODE_KANJI: 3
}[mode]
return QRUtil.MAX_LENGTH[t][e][m]
@staticmethod
def getErrorCorrectPolynomial(errorCorrectLength):
a = Polynomial([1])
for i in range(errorCorrectLength):
a = a.multiply(Polynomial([1, QRMath.gexp(i)]) )
return a
@staticmethod
def getMaskFunction(maskPattern):
return {
MaskPattern.PATTERN000:
lambda i, j: (i + j) % 2 == 0,
MaskPattern.PATTERN001:
lambda i, j: i % 2 == 0,
MaskPattern.PATTERN010:
lambda i, j: j % 3 == 0,
MaskPattern.PATTERN011:
lambda i, j: (i + j) % 3 == 0,
MaskPattern.PATTERN100:
lambda i, j: (i // 2 + j // 3) % 2 == 0,
MaskPattern.PATTERN101:
lambda i, j: (i * j) % 2 + (i * j) % 3 == 0,
MaskPattern.PATTERN110:
lambda i, j: ( (i * j) % 2 + (i * j) % 3) % 2 == 0,
MaskPattern.PATTERN111:
lambda i, j: ( (i * j) % 3 + (i + j) % 2) % 2 == 0
}[maskPattern]
@staticmethod
def getLostPoint(qrcode):
moduleCount = qrcode.getModuleCount()
lostPoint = 0
# LEVEL1
for row in range(moduleCount):
for col in range(moduleCount):
sameCount = 0
dark = qrcode.isDark(row, col)
for r in range(-1, 2):
if row + r < 0 or moduleCount <= row + r:
continue
for c in range(-1, 2):
if col + c < 0 or moduleCount <= col + c:
continue
if r == 0 and c == 0:
continue
if dark == qrcode.isDark(row + r, col + c):
sameCount += 1
if sameCount > 5:
lostPoint += (3 + sameCount - 5)
# LEVEL2
for row in range(moduleCount - 1):
for col in range(moduleCount - 1):
count = 0
if qrcode.isDark(row, col):
count += 1
if qrcode.isDark(row + 1, col):
count += 1
if qrcode.isDark(row, col + 1):
count += 1
if qrcode.isDark(row + 1, col + 1):
count += 1
if count == 0 or count == 4:
lostPoint += 3
# LEVEL3
for row in range(moduleCount):
for col in range(moduleCount - 6):
if (qrcode.isDark(row, col)
and not qrcode.isDark(row, col + 1)
and qrcode.isDark(row, col + 2)
and qrcode.isDark(row, col + 3)
and qrcode.isDark(row, col + 4)
and not qrcode.isDark(row, col + 5)
and qrcode.isDark(row, col + 6) ):
lostPoint += 40
for col in range(moduleCount):
for row in range(moduleCount - 6):
if (qrcode.isDark(row, col)
and not qrcode.isDark(row + 1, col)
and qrcode.isDark(row + 2, col)
and qrcode.isDark(row + 3, col)
and qrcode.isDark(row + 4, col)
and not qrcode.isDark(row + 5, col)
and qrcode.isDark(row + 6, col) ):
lostPoint += 40
# LEVEL4
darkCount = 0
for col in range(moduleCount):
for row in range(moduleCount):
if qrcode.isDark(row, col):
darkCount += 1
ratio = abs(100 * darkCount // moduleCount // moduleCount - 50) // 5
lostPoint += ratio * 10
return lostPoint
G15 = ( (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) |
(1 << 2) | (1 << 1) | (1 << 0) )
G18 = ( (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) |
(1 << 8) | (1 << 5) | (1 << 2) | (1 << 0) )
G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1)
@staticmethod
def getBCHTypeInfo(data):
d = data << 10
while QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0:
d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) -
QRUtil.getBCHDigit(QRUtil.G15) ) )
return ( (data << 10) | d) ^ QRUtil.G15_MASK
@staticmethod
def getBCHTypeNumber(data):
d = data << 12
while QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0:
d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) -
QRUtil.getBCHDigit(QRUtil.G18) ) )
return (data << 12) | d
@staticmethod
def getBCHDigit(data):
digit = 0
while data != 0:
digit += 1
data >>= 1
return digit
@staticmethod
def stringToBytes(s):
return [ord(c) & 0xff for c in s]
class QR8BitByte:
def __init__(self, data):
self.mode = Mode.MODE_8BIT_BYTE
self.data = data
def getMode(self):
return self.mode
def getData(self):
return self.data
'''
def write(self, buffer): raise Exception('not implemented.')
def getLength(self): raise Exception('not implemented.')
'''
def write(self, buffer):
data = QRUtil.stringToBytes(self.getData() )
for d in data:
buffer.put(d, 8)
def getLength(self):
return len(QRUtil.stringToBytes(self.getData() ) )
def getLengthInBits(self, type):
if 1 <= type and type < 10: # 1 - 9
return {
Mode.MODE_NUMBER: 10,
Mode.MODE_ALPHA_NUM: 9,
Mode.MODE_8BIT_BYTE: 8,
Mode.MODE_KANJI: 8
}[self.mode]
elif type < 27: # 10 - 26
return {
Mode.MODE_NUMBER: 12,
Mode.MODE_ALPHA_NUM: 11,
Mode.MODE_8BIT_BYTE: 16,
Mode.MODE_KANJI: 10
}[self.mode]
elif type < 41: # 27 - 40
return {
Mode.MODE_NUMBER: 14,
Mode.MODE_ALPHA_NUM: 13,
Mode.MODE_8BIT_BYTE: 16,
Mode.MODE_KANJI: 12
}[self.mode]
else:
raise Exception('type:%s' % type)
class QRMath:
EXP_TABLE = None
LOG_TABLE = None
@staticmethod
def _init():
QRMath.EXP_TABLE = [0] * 256
for i in range(256):
QRMath.EXP_TABLE[i] = (1 << i if i < 8 else
QRMath.EXP_TABLE[i - 4] ^ QRMath.EXP_TABLE[i - 5] ^
QRMath.EXP_TABLE[i - 6] ^ QRMath.EXP_TABLE[i - 8])
QRMath.LOG_TABLE = [0] * 256
for i in range(255):
QRMath.LOG_TABLE[QRMath.EXP_TABLE[i] ] = i
@staticmethod
def glog(n):
if n < 1:
raise Exception('log(%s)' % n)
return QRMath.LOG_TABLE[n]
@staticmethod
def gexp(n):
while n < 0:
n += 255
while n >= 256:
n -= 255
return QRMath.EXP_TABLE[n]
# initialize statics
QRMath._init()
class Polynomial:
def __init__(self, num, shift=0):
offset = 0
length = len(num)
while offset < length and num[offset] == 0:
offset += 1
self.num = num[offset:] + [0] * shift
def get(self, index):
return self.num[index]
def getLength(self):
return len(self.num)
def __repr__(self):
return ','.join( [str(self.get(i) )
for i in range(self.getLength() ) ] )
def toLogString(self):
return ','.join( [str(QRMath.glog(self.get(i) ) )
for i in range(self.getLength() ) ] )
def multiply(self, e):
num = [0] * (self.getLength() + e.getLength() - 1)
for i in range(self.getLength() ):
for j in range(e.getLength() ):
num[i + j] ^= QRMath.gexp(QRMath.glog(self.get(i) ) +
QRMath.glog(e.get(j) ) )
return Polynomial(num)
def mod(self, e):
if self.getLength() - e.getLength() < 0:
return self
ratio = QRMath.glog(self.get(0) ) - QRMath.glog(e.get(0) )
num = self.num[:]
for i in range(e.getLength() ):
num[i] ^= QRMath.gexp(QRMath.glog(e.get(i) ) + ratio)
return Polynomial(num).mod(e)
class RSBlock:
RS_BLOCK_TABLE = [
# L
# M
# Q
# H
# 1
[1, 26, 19],
[1, 26, 16],
[1, 26, 13],
[1, 26, 9],
# 2
[1, 44, 34],
[1, 44, 28],
[1, 44, 22],
[1, 44, 16],
# 3
[1, 70, 55],
[1, 70, 44],
[2, 35, 17],
[2, 35, 13],
# 4
[1, 100, 80],
[2, 50, 32],
[2, 50, 24],
[4, 25, 9],
# 5
[1, 134, 108],
[2, 67, 43],
[2, 33, 15, 2, 34, 16],
[2, 33, 11, 2, 34, 12],
# 6
[2, 86, 68],
[4, 43, 27],
[4, 43, 19],
[4, 43, 15],
# 7
[2, 98, 78],
[4, 49, 31],
[2, 32, 14, 4, 33, 15],
[4, 39, 13, 1, 40, 14],
# 8
[2, 121, 97],
[2, 60, 38, 2, 61, 39],
[4, 40, 18, 2, 41, 19],
[4, 40, 14, 2, 41, 15],
# 9
[2, 146, 116],
[3, 58, 36, 2, 59, 37],
[4, 36, 16, 4, 37, 17],
[4, 36, 12, 4, 37, 13],
# 10
[2, 86, 68, 2, 87, 69],
[4, 69, 43, 1, 70, 44],
[6, 43, 19, 2, 44, 20],
[6, 43, 15, 2, 44, 16]
]
def __init__(self, totalCount, dataCount):
self.totalCount = totalCount
self.dataCount = dataCount
def getDataCount(self):
return self.dataCount
def getTotalCount(self):
return self.totalCount
def __repr__(self):
return ('(total=%s,data=%s)' % (self.totalCount, self.dataCount) )
@staticmethod
def getRSBlocks(typeNumber, errorCorrectLevel):
rsBlock = RSBlock.getRsBlockTable(typeNumber, errorCorrectLevel)
length = len(rsBlock) // 3
list = []
for i in range(length):
count = rsBlock[i * 3 + 0]
totalCount = rsBlock[i * 3 + 1]
dataCount = rsBlock[i * 3 + 2]
list += [RSBlock(totalCount, dataCount)] * count
return list
@staticmethod
def getRsBlockTable(typeNumber, errorCorrectLevel):
return {
ErrorCorrectLevel.L:
RSBlock.RS_BLOCK_TABLE[ (typeNumber - 1) * 4 + 0],
ErrorCorrectLevel.M:
RSBlock.RS_BLOCK_TABLE[ (typeNumber - 1) * 4 + 1],
ErrorCorrectLevel.Q:
RSBlock.RS_BLOCK_TABLE[ (typeNumber - 1) * 4 + 2],
ErrorCorrectLevel.H:
RSBlock.RS_BLOCK_TABLE[ (typeNumber - 1) * 4 + 3]
}[errorCorrectLevel]
class BitBuffer:
def __init__(self, inclements=32):
self.inclements = inclements
self.buffer = [0] * self.inclements
self.length = 0
def getBuffer(self):
return self.buffer
def getLengthInBits(self):
return self.length
def get(self, index):
return ( (self.buffer[index // 8] >> (7 - index % 8) ) & 1) == 1
def putBit(self, bit):
if self.length == len(self.buffer) * 8:
self.buffer += [0] * self.inclements
if bit:
self.buffer[self.length // 8] |= (0x80 >> (self.length % 8) )
self.length += 1
def put(self, num, length):
for i in range(length):
self.putBit( ( (num >> (length - i - 1) ) & 1) == 1)
def __repr__(self):
return ''.join('1' if self.get(i) else '0'
for i in range(self.getLengthInBits() ) )

View File

@ -0,0 +1,120 @@
# 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
import FootprintWizardBase
# Additional import for QRCode
# see https://github.com/kazuhikoarase/qrcode-generator/blob/master/python/qrcode.py
import qrcode
class QRCodeWizard(FootprintWizardBase.FootprintWizard):
GetName = lambda self: '2D Barcode QRCode'
GetDescription = lambda self: 'QR Code'
GetReferencePrefix = lambda self: 'QR***'
GetValue = lambda self: self.module.Value().GetText()
def GenerateParameterList(self):
self.AddParam("Barcode", "Pixel Width", self.uMM, 0.5, min_value=0.4)
self.AddParam("Barcode", "Border", self.uInteger, 0)
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.2)
self.AddParam("Caption", "Thickness", self.uMM, 0.12)
def CheckParameters(self):
self.Barcode = str(self.parameters['Barcode']['Contents'])
self.X = self.parameters['Barcode']['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'])
self.textHeight = int(self.parameters['Caption']['Height'])
self.module.Value().SetText(str(self.Barcode) )
# 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()
def _drawPixel(self, xposition, yposition):
# build a rectangular pad: as a dot
pad = pcbnew.D_PAD(self.module)
pad.SetSize(pcbnew.wxSize(self.X, self.X))
pad.SetShape(pcbnew.PAD_SHAPE_RECT)
pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD)
layerset = pcbnew.LSET()
if self.UseCu:
layerset.AddLayer(pcbnew.F_Cu)
layerset.AddLayer(pcbnew.F_Mask)
if self.UseSilkS:
layerset.AddLayer(pcbnew.F_SilkS)
pad.SetLayerSet( layerset )
pad.SetPosition(pcbnew.wxPoint(xposition,yposition))
pad.SetPadName("1")
self.module.Add(pad)
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
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
# Center position of QrCode
yposition = - int(half_number_of_elements * self.X)
for line in arrayToDraw:
xposition = - int(half_number_of_elements * self.X)
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._drawPixel(xposition, yposition)
xposition += self.X
yposition += self.X
#int((5 + half_number_of_elements) * self.X))
textPosition = int((self.textHeight) + ((1 + half_number_of_elements) * self.X))
self.module.Value().SetPosition(pcbnew.wxPoint(0, - textPosition))
self.module.Reference().SetPosition(pcbnew.wxPoint(0, textPosition))
self.module.Value().SetLayer(pcbnew.F_SilkS)
QRCodeWizard().register()

View File

@ -17,7 +17,7 @@
from __future__ import division
import pcbnew
import HelpfulFootprintWizardPlugin as HFPW
import FootprintWizardBase
import PadArray as PA
@ -35,7 +35,7 @@ class RowedGridArray(PA.PadGridArray):
return x+1
class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin):
class RowedFootprint(FootprintWizardBase.FootprintWizard):
pad_count_key = 'pad count'
row_count_key = 'row count'
@ -50,31 +50,25 @@ class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin):
def GenerateParameterList(self):
# defaults for a DIP package
self.AddParam("Pads", self.pad_count_key, self.uNatural, 24)
self.AddParam("Pads", self.row_count_key, self.uNatural, 2)
self.AddParam("Pads", self.pad_count_key, self.uInteger, 24)
self.AddParam("Pads", self.row_count_key, self.uInteger, 2, min_value=1, max_value=2)
self.AddParam("Body", self.silkscreen_inside_key, self.uBool, False)
self.AddParam("Body", self.outline_x_margin_key, self.uMM, 0.5)
self.AddParam("Body", self.outline_y_margin_key, self.uMM, 0.5)
def CheckParameters(self):
self.CheckParamInt("Pads", '*' + self.row_count_key, min_value=1, max_value=2)
self.CheckParamInt(
"Pads", '*' + self.pad_count_key,
is_multiple_of=self.parameters["Pads"]['*' + self.row_count_key])
# can do this internally to parameter manager?
self.CheckParamBool("Body", '*' + self.silkscreen_inside_key)
self.CheckParam("Pads", self.pad_count_key, multiple=self.parameters['Pads'][self.row_count_key], info='Pads must be multiple of row count')
def BuildThisFootprint(self):
pads = self.parameters["Pads"]
body = self.parameters["Body"]
num_pads = pads['*' + self.pad_count_key]
num_pads = pads[self.pad_count_key]
pad_length = pads[self.pad_length_key]
pad_width = pads[self.pad_width_key]
row_pitch = pads[self.row_spacing_key]
pad_pitch = pads[self.pad_pitch_key]
num_rows = pads['*' + self.row_count_key]
num_rows = pads[self.row_count_key]
pads_per_row = num_pads // num_rows
@ -96,7 +90,7 @@ class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin):
ssx_offset = -pad_width / 2 - body[self.outline_x_margin_key]
ssy_offset = -pad_length / 2 - body[self.outline_y_margin_key]
if body['*' + self.silkscreen_inside_key]:
if body[self.silkscreen_inside_key]:
ssy_offset *= -1
ssx = -pin1_posX - ssx_offset
@ -110,8 +104,8 @@ class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin):
sizex = (ssx + cmargin) * 2
sizey = (ssy + cmargin) * 2
# round size to nearest 0.1mm, rectangle will thus land on a 0.05mm grid
sizex = self.PutOnGridMM(sizex, 0.1)
sizey = self.PutOnGridMM(sizey, 0.1)
sizex = pcbnew.PutOnGridMM(sizex, 0.1)
sizey = pcbnew.PutOnGridMM(sizey, 0.1)
# set courtyard line thickness to the one defined in KLC
self.draw.SetLineThickness(pcbnew.FromMM(0.05))
self.draw.Box(0, 0, sizex, sizey)
@ -156,8 +150,8 @@ class SDIPWizard(RowedFootprint):
def GetValue(self):
pads = self.parameters["Pads"]
rows = pads['*' + self.row_count_key]
pad_count = pads['*' + self.pad_count_key]
rows = pads[self.row_count_key]
pad_count = pads[self.pad_count_key]
row_dist_mil = pcbnew.Iu2Mils(int(self.parameters["Pads"][self.row_spacing_key])) #int(self.parameters["Pads"][self.row_spacing_key] / 2.54 * 100)
pad_shape = ""
@ -185,7 +179,7 @@ class SDIPWizard(RowedFootprint):
def DrawBox(self, ssx, ssy):
if self.parameters["Pads"]['*' + self.row_count_key] == 2:
if self.parameters["Pads"][self.row_count_key] == 2:
# ----------
# |8 7 6 5 |
@ -208,7 +202,7 @@ class SDIPWizard(RowedFootprint):
#line between pin1 and pin2
pad_pitch = self.parameters["Pads"][self.pad_pitch_key]
pad_cnt = self.parameters["Pads"]['*' + self.pad_count_key]
pad_cnt = self.parameters["Pads"][self.pad_count_key]
line_x = ( pad_cnt/2 - 1) * pad_pitch
self.draw.VLine(-line_x, -ssy, ssy * 2)
@ -226,7 +220,7 @@ class SOICWizard(RowedFootprint):
return "SOIC, MSOP, SSOP, TSSOP, etc, footprint wizard"
def GetValue(self):
pad_count = self.parameters["Pads"]['*' + self.pad_count_key]
pad_count = self.parameters["Pads"][self.pad_count_key]
return "%s-%d" % ("SOIC", pad_count)
def GenerateParameterList(self):

View File

@ -24,10 +24,10 @@
#
from pcbnew import *
import HelpfulFootprintWizardPlugin as HFPW
import FootprintWizardBase
import pcbnew
class TouchSliderWizard(HFPW.HelpfulFootprintWizardPlugin):
class TouchSliderWizard(FootprintWizardBase.FootprintWizard):
def GetName(self):
"""
@ -44,16 +44,23 @@ class TouchSliderWizard(HFPW.HelpfulFootprintWizardPlugin):
return 'Capacitive Touch Slider wizard'
def GetValue(self):
steps = int(self.parameters["Pads"]["*steps"])
return "TS"+str(steps)
return "TouchSlider-{s}_{x:g}x{y:g}mm".format(
s = self.pads['steps'],
x = pcbnew.ToMM(self.pads['length']),
y = pcbnew.ToMM(self.pads['width'])
)
def GenerateParameterList(self):
self.AddParam("Pads", "steps", self.uNatural, 4)
self.AddParam("Pads", "bands", self.uNatural, 2)
self.AddParam("Pads", "steps", self.uInteger, 4, min_value=2)
self.AddParam("Pads", "bands", self.uInteger, 2, min_value=1)
self.AddParam("Pads", "width", self.uMM, 10)
self.AddParam("Pads", "length", self.uMM, 50)
self.AddParam("Pads", "clearance", self.uMM, 1)
@property
def pads(self):
return self.parameters['Pads']
# build a rectangular pad
def smdRectPad(self,module,size,pos,name):
pad = D_PAD(module)
@ -82,18 +89,8 @@ class TouchSliderWizard(HFPW.HelpfulFootprintWizardPlugin):
# This method checks the parameters provided to wizard and set errors
def CheckParameters(self):
prms = self.parameters["Pads"]
steps = prms["*steps"]
bands = prms["*bands"]
if steps < 1:
self.parameter_errors["Pads"]["*steps"]="steps must be positive"
if bands < 1:
self.parameter_errors["Pads"]["*bands"]="bands must be positive"
touch_width = prms["width"]
touch_length = prms["length"]
touch_clearance = prms["clearance"]
#TODO - implement custom checks
pass
# The start pad is made of a rectangular pad plus a couple of
# triangular pads facing tips on the middle/right of the first
@ -177,18 +174,18 @@ class TouchSliderWizard(HFPW.HelpfulFootprintWizardPlugin):
# build the footprint from parameters
# FIX ME: the X and Y position of the footprint can be better.
def BuildThisFootprint(self):
prm = self.parameters["Pads"]
steps = int(prm["*steps"])
bands = int(prm["*bands"])
touch_width = prm["width"]
touch_length = prm["length"]
touch_clearance = prm["clearance"]
steps = self.pads["steps"]
bands = self.pads["bands"]
touch_width = self.pads["width"]
touch_length = self.pads["length"]
touch_clearance = self.pads["clearance"]
step_length = float(touch_length) / float(steps)
t_size = self.GetTextSize()
w_text = self.draw.GetLineThickness()
ypos = touch_width/(bands*2) + t_size/2 + w_text
ypos = touch_width/2 + t_size/2 + w_text
self.draw.Value(0, -ypos, t_size)
ypos += t_size + w_text*2
self.draw.Reference(0, -ypos, t_size)
@ -197,9 +194,13 @@ class TouchSliderWizard(HFPW.HelpfulFootprintWizardPlugin):
self.module.SetAttributes(MOD_CMS)
# starting pad
pos = wxPointMM(0,0)
band_width = touch_width/bands
xpos = -0.5 * (steps - 1) * step_length
ypos = -0.5 * (bands - 1) * band_width
pos = wxPointMM(pcbnew.ToMM(xpos), pcbnew.ToMM(ypos))
for b in range(bands):
self.AddStrip(pos,steps,band_width,step_length,touch_clearance)
pos += wxPoint(0,band_width)

View File

@ -15,8 +15,7 @@
from __future__ import division
import pcbnew as B
import HelpfulFootprintWizardPlugin
import FootprintWizardBase
'''
Created on Jan 16, 2015
@ -49,7 +48,7 @@ class Uss39:
# Reformated text with start and end characters
return reduce(lambda a1, a2: a1 + [0] + a2, [map(int, ptd[c]) for c in ("*%s*" % self.makePrintable(text))])
class Uss39Wizard(HelpfulFootprintWizardPlugin.HelpfulFootprintWizardPlugin):
class Uss39Wizard(FootprintWizardBase.FootprintWizard):
GetName = lambda self: 'BARCODE USS-39'
GetDescription = lambda self: 'USS-39 Barcode'
GetReferencePrefix = lambda self: 'BARCODE'
@ -61,18 +60,20 @@ class Uss39Wizard(HelpfulFootprintWizardPlugin.HelpfulFootprintWizardPlugin):
self.AddParam("Barcode", "Height", self.uMM, 3.0)
self.AddParam("Barcode", "Margin", self.uMM, 2.0)
self.AddParam("Barcode", "Contents", self.uString, 'BARCODE')
self.AddParam("Caption", "Enabled", self.uBool, True)
self.AddParam("Caption", "Height", self.uMM, 1.2)
self.AddParam("Caption", "Thickness", self.uMM, 0.12)
def CheckParameters(self):
# Reset constants
self.CourtyardLineWidth = B.FromMM(0.05)
# Set bar height to the greater of 6.35mm or 0.15*L
# Set quiet width to 10*X
# User-defined parameters
# Create barcode object
self.Barcode = Uss39('=' + str(self.parameters['Barcode']['*Contents']))
self.Barcode = Uss39('=' + str(self.parameters['Barcode']['Contents']))
self.X = int(self.parameters['Barcode']['Pixel Width'])
self.module.Value().SetText( str(self.Barcode) )
self.C = len(str(self.Barcode))

View File

@ -17,52 +17,41 @@
from __future__ import division
import pcbnew
import HelpfulFootprintWizardPlugin as HFPW
import FootprintWizardBase
import PadArray as PA
class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin):
pad_count_key = '#pad count'
line_count_key = '#line count'
pad_vertical_size_key = 'pad vertical size'
pad_horizontal_size_key = 'pad horizontal size'
line_spacing_key = 'line spacing'
pad_pitch_key = 'pad pitch'
drill_size_key = 'drill size'
courtyard_x_margin_key = 'courtyard x margin'
courtyard_y_margin_key = 'courtyard y margin'
outline_x_margin_key = 'outline x margin'
outline_y_margin_key = 'outline y margin'
silkscreen_inside_key = 'silk screen inside'
class RowedFootprint(FootprintWizardBase.FootprintWizard):
def GenerateParameterList(self):
# defaults for a ZIP package
self.AddParam("Pads", self.pad_count_key, self.uNatural, 24)
self.AddParam("Pads", self.line_count_key, self.uNatural, 2)
self.AddParam("Body", self.silkscreen_inside_key, self.uBool, False)
self.AddParam("Body", self.courtyard_x_margin_key, self.uMM, 1)
self.AddParam("Body", self.courtyard_y_margin_key, self.uMM, 1)
self.AddParam("Pads", "pad count", self.uInteger, 24)
self.AddParam("Pads", "line count", self.uInteger, 2)
self.AddParam("Body", "silkscreen inside", self.uBool, False)
self.AddParam("Body", "courtyard margin", self.uMM, 0.5, min_value=0.2)
@property
def pads(self):
return self.parameters['Pads']
@property
def body(self):
return self.parameters['Body']
def CheckParameters(self):
self.CheckParamInt("Pads", '*' + self.pad_count_key)
self.CheckParamInt("Pads", '*' + self.line_count_key)
# can do this internally to parameter manager?
self.CheckParamBool("Body", '*' + self.silkscreen_inside_key)
# TODO - implement custom checks
pass
def BuildThisFootprint(self):
pads = self.parameters["Pads"]
body = self.parameters["Body"]
pad_count = pads['*' + self.pad_count_key]
pad_Vsize = pads[self.pad_vertical_size_key]
pad_Hsize = pads[self.pad_horizontal_size_key]
line_pitch = pads[self.line_spacing_key]
pad_pitch = pads[self.pad_pitch_key]
line_count = pads['*' + self.line_count_key]
pad_count = self.pads['pad count']
pad_Vsize = self.pads['pad height']
pad_Hsize = self.pads['pad width']
line_pitch = self.pads['line spacing']
pad_pitch = self.pads['pitch']
line_count = self.pads['line count']
if line_count == 1:
singleline = True
@ -78,8 +67,8 @@ class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin):
# draw the Silk Screen
pads_per_line = pad_count // line_count
row_length = pad_pitch * (pads_per_line - 1) # fenceposts
ssx_offset = pad_Hsize / 2 + body[self.outline_x_margin_key]
ssy_offset = pad_Vsize / 2 + body[self.outline_y_margin_key]
ssx_offset = pad_Hsize / 2 + self.body['outline x margin']
ssy_offset = pad_Vsize / 2 + self.body['outline y margin']
pin1posX = pad_pitch * (pad_count - 1) / 2
pin1posY = line_pitch * (line_count - 1) / 2
@ -91,7 +80,7 @@ class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin):
# body inside pads is possible only for 2 rows.
# for other values, there is no room
linew = self.draw.GetLineThickness()
if body['*'+self.silkscreen_inside_key] and line_count == 2:
if self.body['silkscreen inside'] and line_count == 2:
cornery = pin1posY - ssy_offset
if cornery < linew:
cornery = linew
@ -99,15 +88,15 @@ class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin):
self.DrawBox(leftx*2, cornery*2)
# Courtyard
cmarginx = body[self.courtyard_x_margin_key]
cmarginy = body[self.courtyard_y_margin_key]
cmarginx = self.body['courtyard margin']
cmarginy = cmarginx
self.draw.SetLayer(pcbnew.F_CrtYd)
thick = self.draw.GetLineThickness()
sizex = (pin1posX + cmarginx) * 2 + pad_Hsize + thick
sizey = (pin1posY + cmarginy) * 2 + pad_Vsize + thick
# round size to nearest 0.1mm, rectangle will thus land on a 0.05mm grid
sizex = self.PutOnGridMM(sizex, 0.1)
sizey = self.PutOnGridMM(sizey, 0.1)
sizex = pcbnew.PutOnGridMM(sizex, 0.1)
sizey = pcbnew.PutOnGridMM(sizey, 0.1)
# set courtyard line thickness to the one defined in KLC
self.draw.SetLineThickness(pcbnew.FromMM(0.05))
self.draw.Box(0, 0, sizex, sizey)
@ -152,17 +141,17 @@ class ZIPWizard(RowedFootprint):
def GenerateParameterList(self):
RowedFootprint.GenerateParameterList(self)
self.AddParam("Pads", self.pad_pitch_key, self.uMM, 1.27)
self.AddParam("Pads", self.pad_horizontal_size_key, self.uMM, 1.2)
self.AddParam("Pads", self.pad_vertical_size_key, self.uMM, 2)
self.AddParam("Pads", self.line_spacing_key, self.uMM, 2.54)
self.AddParam("Pads", self.drill_size_key, self.uMM, 0.8)
self.AddParam("Body", self.outline_x_margin_key, self.uMM, 1)
self.AddParam("Body", self.outline_y_margin_key, self.uMM, 0.5)
self.AddParam("Pads", "pitch", self.uMM, 1.27)
self.AddParam("Pads", "pad width", self.uMM, 1.2)
self.AddParam("Pads", "pad height", self.uMM, 2)
self.AddParam("Pads", "line spacing", self.uMM, 2.54)
self.AddParam("Pads", "drill size", self.uMM, 0.8)
self.AddParam("Body", 'outline x margin', self.uMM, 1)
self.AddParam("Body", 'outline y margin', self.uMM, 0.5)
def GetValue(self):
rows = self.parameters["Pads"]['*' + self.line_count_key]
pad_cnt = self.parameters["Pads"]['*' + self.pad_count_key]
rows = self.pads['line count']
pad_cnt = self.pads['pad count']
if rows == 1:
name = "SIP"
@ -174,9 +163,9 @@ class ZIPWizard(RowedFootprint):
return "%s-%d" % (name, pad_cnt)
def GetPad(self):
pad_Vsize = self.parameters["Pads"][self.pad_vertical_size_key]
pad_Hsize = self.parameters["Pads"][self.pad_horizontal_size_key]
drill = self.parameters["Pads"][self.drill_size_key]
pad_Vsize = self.pads['pad height']
pad_Hsize = self.pads['pad width']
drill = self.pads['drill size']
return PA.PadMaker(self.module).THPad(
pad_Vsize, pad_Hsize, drill, shape=pcbnew.PAD_SHAPE_OVAL)
@ -192,23 +181,23 @@ class ZOICWizard(RowedFootprint):
return "ZOIC, etc, Footprint Wizard"
def GetValue(self):
return "%s-%d" % ("ZOIC", self.parameters["Pads"]['*' + self.pad_count_key])
return "%s-%d" % ("ZOIC-", self.pads['pad count'])
def GenerateParameterList(self):
RowedFootprint.GenerateParameterList(self)
#and override some of them
self.AddParam("Pads", self.pad_pitch_key, self.uMM, 0.6)
self.AddParam("Pads", self.pad_horizontal_size_key, self.uMM, 0.6)
self.AddParam("Pads", self.pad_vertical_size_key, self.uMM, 1.8)
self.AddParam("Pads", self.line_spacing_key, self.uMM, 5.2)
self.AddParam("Pads", "pitch", self.uMM, 0.6)
self.AddParam("Pads", "pad width", self.uMM, 0.6)
self.AddParam("Pads", "pad height", self.uMM, 1.8)
self.AddParam("Pads", "line spacing", self.uMM, 5.2)
self.AddParam("Body", self.outline_x_margin_key, self.uMM, 0.5)
self.AddParam("Body", self.outline_y_margin_key, self.uMM, 1)
self.AddParam("Body", "outline x margin", self.uMM, 0.5)
self.AddParam("Body", "outline y margin", self.uMM, 1)
def GetPad(self):
pad_Vsize = self.parameters["Pads"][self.pad_vertical_size_key]
pad_Hsize = self.parameters["Pads"][self.pad_horizontal_size_key]
pad_Vsize = self.pads['pad height']
pad_Hsize = self.pads['pad width']
return PA.PadMaker(self.module).SMDPad(
pad_Vsize, pad_Hsize, shape=pcbnew.PAD_SHAPE_RECT)

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013 NBEE Embedded Systems SL, Miguel Angel Ajo <miguelangel@ajo.es>
* Copyright (C) 2013 KiCad Developers, see CHANGELOG.TXT for contributors.
* Copyright (C) 2016 KiCad Developers, see CHANGELOG.TXT for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -237,24 +237,9 @@ wxArrayString PYTHON_FOOTPRINT_WIZARD::GetParameterTypes( int aPage )
PyObject* arglist = Py_BuildValue( "(i)", aPage );
ret = CallRetArrayStrMethod( "GetParameterNames", arglist );
ret = CallRetArrayStrMethod( "GetParameterTypes", arglist );
Py_DECREF( arglist );
for( unsigned i = 0; i<ret.GetCount(); i++ )
{
wxString rest;
wxString item = ret[i];
if( item.StartsWith( wxT( "*" ), &rest ) )
{
ret[i] = wxT( "UNITS" ); // units
}
else
{
ret[i] = wxT( "IU" ); // internal units
}
}
return ret;
}
@ -284,6 +269,29 @@ wxArrayString PYTHON_FOOTPRINT_WIZARD::GetParameterErrors( int aPage )
return ret;
}
wxArrayString PYTHON_FOOTPRINT_WIZARD::GetParameterHints( int aPage )
{
PyLOCK lock;
PyObject* arglist = Py_BuildValue( "(i)", aPage );
wxArrayString ret = CallRetArrayStrMethod( "GetParameterHints", arglist );
Py_DECREF( arglist );
return ret;
}
wxArrayString PYTHON_FOOTPRINT_WIZARD::GetParameterDesignators( int aPage )
{
PyLOCK lock;
PyObject* arglist = Py_BuildValue( "(i)", aPage );
wxArrayString ret = CallRetArrayStrMethod( "GetParameterDesignators", arglist );
Py_DECREF( arglist );
return ret;
}
wxString PYTHON_FOOTPRINT_WIZARD::SetParameterValues( int aPage, wxArrayString& aValues )
{
@ -309,6 +317,13 @@ wxString PYTHON_FOOTPRINT_WIZARD::SetParameterValues( int aPage, wxArrayString&
return res;
}
void PYTHON_FOOTPRINT_WIZARD::ResetParameters()
{
PyLOCK lock;
CallMethod( "ResetWizard", NULL );
}
// this is a SWIG function declaration -from module.i
MODULE* PyModule_to_MODULE( PyObject* obj0 );

View File

@ -45,6 +45,7 @@ class PYTHON_FOOTPRINT_WIZARD : public FOOTPRINT_WIZARD
public:
PYTHON_FOOTPRINT_WIZARD( PyObject* wizard );
~PYTHON_FOOTPRINT_WIZARD();
wxString GetName() override;
wxString GetImage() override;
wxString GetDescription() override;
@ -58,6 +59,10 @@ public:
wxString SetParameterValues( int aPage, wxArrayString& aValues ) override;
MODULE* GetFootprint( wxString * aMessages ) override;
void* GetObject() override;
wxArrayString GetParameterHints( int aPage ) override;
wxArrayString GetParameterDesignators( int aPage = 0) override;
void ResetParameters() override;
};

View File

@ -119,23 +119,19 @@ def LoadPlugins(bundlepath=None):
if bundlepath:
plugin_directories.append(bundlepath)
plugin_directories.append(os.path.join(bundlepath, 'plugins'))
plugin_directories.append(os.path.join(bundlepath, 'plugins', 'wizards'))
if kicad_path:
plugin_directories.append(os.path.join(kicad_path, 'scripting'))
plugin_directories.append(os.path.join(kicad_path, 'scripting', 'plugins'))
plugin_directories.append(os.path.join(kicad_path, 'scripting', 'plugins', 'wizards'))
if config_path:
plugin_directories.append(os.path.join(config_path, 'scripting'))
plugin_directories.append(os.path.join(config_path, 'scripting', 'plugins'))
plugin_directories.append(os.path.join(config_path, 'scripting', 'plugins', 'wizards'))
if sys.platform.startswith('linux'):
plugin_directories.append(os.environ['HOME']+'/.kicad_plugins/')
plugin_directories.append(os.environ['HOME']+'/.kicad/scripting/')
plugin_directories.append(os.environ['HOME']+'/.kicad/scripting/plugins/')
plugin_directories.append(os.environ['HOME']+'/.kicad/scripting/plugins/wizards')
for plugins_dir in plugin_directories:
if not os.path.isdir(plugins_dir):
@ -164,7 +160,6 @@ def LoadPlugins(bundlepath=None):
pass
class KiCadPlugin:
def __init__(self):
pass
@ -203,88 +198,295 @@ class FilePlugin(KiCadPlugin):
from math import ceil, floor, sqrt
class FootprintWizardPlugin(KiCadPlugin):
uMM = "mm" # Millimetres
uMils = "mils" # Mils
uFloat = "float" # Natural number units (dimensionless)
uInteger = "integer" # Integer (no decimals, numeric, dimensionless)
uBool = "bool" # Boolean value
uRadians = "radians" # Angular units (radians)
uDegrees = "degrees" # Angular units (degrees)
uPercent = "%" # Percent (0% -> 100%)
uString = "string" # Raw string
uNumeric = [uMM, uMils, uFloat, uInteger, uDegrees, uRadians, uPercent] # List of numeric types
uUnits = [uMM, uMils, uFloat, uInteger, uBool, uDegrees, uRadians, uPercent, uString] # List of allowable types
class FootprintWizardParameter(object):
_true = ['true','t','y','yes','on','1',1,]
_false = ['false','f','n','no','off','0',0,'',None]
_bools = _true + _false
def __init__(self, page, name, units, default, **kwarg):
self.page = page
self.name = name
self.hint = kwarg.get('hint','') # Parameter hint (shown as mouse-over text)
self.designator = kwarg.get('designator',' ') # Parameter designator such as "e, D, p" (etc)
if units.lower() in uUnits:
self.units = units.lower()
elif units.lower() == 'percent':
self.units = uPercent
elif type(units) in [list, tuple]: # Convert a list of options into a single string
self.units = ",".join([str(el).strip() for el in units])
else:
self.units = units
self.multiple = int(kwarg.get('multiple',1)) # Check integer values are multiples of this number
self.min_value = kwarg.get('min_value',None) # Check numeric values are above or equal to this number
self.max_value = kwarg.get('max_value',None) # Check numeric values are below or equal to this number
self.SetValue(default)
self.default = self.raw_value # Save value as default
def ClearErrors(self):
self.error_list = []
def AddError(self, err, info=None):
if err in self.error_list: # prevent duplicate error messages
return
if info is not None:
err = err + " (" + str(info) + ")"
self.error_list.append(err)
def Check(self, min_value=None, max_value=None, multiple=None, info=None):
if min_value is None:
min_value = self.min_value
if max_value is None:
max_value = self.max_value
if multiple is None:
multiple = self.multiple
if self.units not in uUnits and ',' not in self.units: # Allow either valid units or a list of strings
self.AddError("type '{t}' unknown".format(t=self.units),info)
self.AddError("Allowable types: " + str(self.units),info)
if self.units in uNumeric:
try:
to_num = float(self.raw_value)
if min_value is not None: # Check minimum value if it is present
if to_num < min_value:
self.AddError("value '{v}' is below minimum ({m})".format(v=self.raw_value,m=min_value),info)
if max_value is not None: # Check maximum value if it is present
if to_num > max_value:
self.AddError("value '{v}' is above maximum ({m})".format(v=self.raw_value,m=max_value),info)
except:
self.AddError("value '{v}' is not of type '{t}'".format(v = self.raw_value, t=self.units),info)
if self.units == uInteger: # Perform integer specific checks
try:
to_int = int(self.raw_value)
if multiple is not None and multiple > 1:
if (to_int % multiple) > 0:
self.AddError("value '{v}' is not a multiple of {m}".format(v=self.raw_value,m=multiple),info)
except:
self.AddError("value {'v}' is not an integer".format(v=self.raw_value),info)
if self.units == uBool: # Check that the value is of a correct boolean format
if self.raw_value in [True,False] or str(self.raw_value).lower() in self._bools:
pass
else:
self.AddError("value '{v}' is not a boolean value".format(v = self.raw_value),info)
@property
def value(self): # Return the current value, converted to appropriate units (from string representation) if required
v = str(self.raw_value) # Enforce string type for known starting point
if self.units == uInteger: # Integer values
return int(v)
elif self.units in uNumeric: # Any values that use floating points
v = v.replace(",",".") # Replace "," separators with "."
v = float(v)
if self.units == uMM: # Convert from millimetres to nanometres
return FromMM(v)
elif self.units == uMils: # Convert from mils to nanometres
return FromMils(v)
else: # Any other floating-point values
return v
elif self.units == uBool:
if v.lower() in self._true:
return True
else:
return False
else:
return v
def DefaultValue(self): # Reset the value of the parameter to its default
self.raw_value = str(self.default)
def SetValue(self, new_value): # Update the value
new_value = str(new_value)
if len(new_value.strip()) == 0:
if not self.units in [uString, uBool]:
return # Ignore empty values unless for strings or bools
if self.units == uBool: # Enforce the same boolean representation as is used in KiCad
new_value = "1" if new_value.lower() in self._true else "0"
elif self.units in uNumeric:
new_value = new_value.replace(",", ".") # Enforce decimal point separators
elif ',' in self.units: # Select from a list of values
if new_value not in self.units.split(','):
new_value = self.units.split(',')[0]
self.raw_value = new_value
def __str__(self): # pretty-print the parameter
s = self.name + ": " + str(self.raw_value)
if self.units in [uMM, uMils, uPercent, uRadians, uDegrees]:
s += self.units
elif self.units == uBool: # Special case for Boolean values
s = self.name + ": {b}".format(b = "True" if self.value else "False")
elif self.units == uString:
s = self.name + ": '" + self.raw_value + "'"
return s
class FootprintWizardPlugin(KiCadPlugin, object):
def __init__(self):
KiCadPlugin.__init__(self)
self.defaults()
def defaults(self):
self.module = None
self.parameters = {}
self.parameter_errors={}
self.name = "Undefined Footprint Wizard plugin"
self.description = ""
self.params = [] # List of added parameters that observes addition order
self.name = "KiCad FP Wizard"
self.description = "Undefined Footprint Wizard plugin"
self.image = ""
self.buildmessages = ""
def GetName(self):
def AddParam(self, page, name, unit, default, **kwarg):
if self.GetParam(page,name) is not None: # Param already exists!
return
param = FootprintWizardParameter(page, name, unit, default, **kwarg) # Create a new parameter
self.params.append(param)
@property
def parameters(self): # This is a helper function that returns a nested (unordered) dict of the VALUES of parameters
pages = {} # Page dict
for p in self.params:
if p.page not in pages:
pages[p.page] = {}
pages[p.page][p.name] = p.value # Return the 'converted' value (convert from string to actual useful units)
return pages
@property
def values(self): # Same as above
return self.parameters
def ResetWizard(self): # Reset all parameters to default values
for p in self.params:
p.DefaultValue()
def GetName(self): # Return the name of this wizard
return self.name
def GetImage(self):
def GetImage(self): # Return the filename of the preview image associated with this wizard
return self.image
def GetDescription(self):
def GetDescription(self): # Return the description text
return self.description
def GetValue(self):
raise NotImplementedError
def GetNumParameterPages(self):
return len(self.parameters)
def GetReferencePrefix(self):
return "REF" # Default reference prefix for any footprint
def GetParameterPageName(self,page_n):
return self.page_order[page_n]
def GetParam(self, page, name): # Grab a parameter
for p in self.params:
if p.page == page and p.name == name:
return p
def GetParameterNames(self,page_n):
name = self.GetParameterPageName(page_n)
return self.parameter_order[name]
return None
def GetParameterValues(self,page_n):
name = self.GetParameterPageName(page_n)
names = self.GetParameterNames(page_n)
values = [self.parameters[name][n] for n in names]
return map(lambda x: str(x), values) # list elements as strings
def CheckParam(self, page, name, **kwarg):
self.GetParam(page,name).Check(**kwarg)
def GetParameterErrors(self,page_n):
self.CheckParameters()
name = self.GetParameterPageName(page_n)
names = self.GetParameterNames(page_n)
values = [self.parameter_errors[name][n] for n in names]
return map(lambda x: str(x), values) # list elements as strings
def AnyErrors(self):
return any([len(p.error_list) > 0 for p in self.params])
def CheckParameters(self):
return ""
@property
def pages(self): # Return an (ordered) list of the available page names
page_list = []
for p in self.params:
if p.page not in page_list:
page_list.append(p.page)
def ConvertValue(self,v):
try:
v = float(v)
except:
pass
if type(v) is float:
if ceil(v) == floor(v):
v = int(v)
return v
return page_list
def GetNumParameterPages(self): # Return the number of parameter pages
return len(self.pages)
def SetParameterValues(self,page_n,values):
name = self.GetParameterPageName(page_n)
keys = self.GetParameterNames(page_n)
for n, key in enumerate(keys):
val = self.ConvertValue(values[n])
self.parameters[name][key] = val
def GetParameterPageName(self,page_n): # Return the name of a page at a given index
return self.pages[page_n]
def GetParametersByPageName(self, page_name): # Return a list of parameters on a given page
params = []
def ClearErrors(self):
errs={}
for p in self.params:
if p.page == page_name:
params.append(p)
for page in self.parameters.keys():
page_dict = self.parameters[page]
page_params = {}
for param in page_dict.keys():
page_params[param]=""
return params
errs[page]=page_params
def GetParametersByPageIndex(self, page_index): # Return an ordered list of parameters on a given page
return self.GetParametersByPageName(self.GetParameterPageName(page_index))
self.parameter_errors = errs
def GetParameterDesignators(self, page_index): # Return a list of designators associated with a given page
params = self.GetParametersByPageIndex(page_index)
return [p.designator for p in params]
def GetParameterNames(self,page_index): # Return the list of names associated with a given page
params = self.GetParametersByPageIndex(page_index)
return [p.name for p in params]
def GetParameterValues(self,page_index): # Return the list of values associated with a given page
params = self.GetParametersByPageIndex(page_index)
return [str(p.raw_value) for p in params]
def GetParameterErrors(self,page_index): # Return list of errors associated with a given page
params = self.GetParametersByPageIndex(page_index)
return [str("\n".join(p.error_list)) for p in params]
def GetParameterTypes(self, page_index): # Return list of units associated with a given page
params = self.GetParametersByPageIndex(page_index)
return [str(p.units) for p in params]
def GetParameterHints(self, page_index): # Return a list of units associated with a given page
params = self.GetParametersByPageIndex(page_index)
return [str(p.hint) for p in params]
def GetParameterDesignators(self, page_index): # Return a list of designators associated with a given page
params = self.GetParametersByPageIndex(page_index)
return [str(p.designator) for p in params]
def SetParameterValues(self, page_index, list_of_values): # Update values on a given page
params = self.GetParametersByPageIndex(page_index)
for i, param in enumerate(params):
if i >= len(list_of_values):
break
param.SetValue(list_of_values[i])
def GetFootprint( self ):
self.BuildFootprint()
@ -297,17 +499,30 @@ class FootprintWizardPlugin(KiCadPlugin):
return self.buildmessages
def Show(self):
print "Footprint Wizard Name: ",self.GetName()
print "Footprint Wizard Description: ",self.GetDescription()
text = "Footprint Wizard Name: {name}\n".format(name=self.GetName())
text += "Footprint Wizard Description: {desc}\n".format(desc=self.GetDescription())
n_pages = self.GetNumParameterPages()
print " setup pages: ",n_pages
for page in range(0,n_pages):
name = self.GetParameterPageName(page)
values = self.GetParameterValues(page)
names = self.GetParameterNames(page)
print "page %d) %s"%(page,name)
for n in range (0,len(values)):
print "\t%s\t:\t%s"%(names[n],values[n])
text += "Pages: {n}\n".format(n=n_pages)
for i in range(n_pages):
name = self.GetParameterPageName(i)
params = self.GetParametersByPageName(name)
text += "{name}\n".format(name=name)
for j in range(len(params)):
text += ("\t{param}{err}\n".format(
param = str(params[j]),
err = ' *' if len(params[j].error_list) > 0 else ''
))
if self.AnyErrors():
text += " * Errors exist for these parameters"
return text
class ActionPlugin(KiCadPlugin):
def __init__(self):