diff --git a/common/dialog_about/AboutDialog_main.cpp b/common/dialog_about/AboutDialog_main.cpp index 2fd9e39a79..32d67904ff 100644 --- a/common/dialog_about/AboutDialog_main.cpp +++ b/common/dialog_about/AboutDialog_main.cpp @@ -151,11 +151,17 @@ static void InitKiCadAboutNew( AboutAppInfo& info ) << HtmlHyperlink( wxT( "https://launchpad.net/kicad" ), _( "Developer's website on Launchpad" ) ) << wxT("" ); + description << wxT( "
  • " ) << HtmlHyperlink( wxT( "https://github.com/KiCad/" ), _( "Our official Repository for component and footprint libraries" ) ) << wxT( "
  • " ); + description << wxT( "
  • " ) + << HtmlHyperlink( wxT( "https://github.com/KiCad/Footprint_Wizards" ), + _( "Footprint wizards info on our official repository " ) ) + << wxT( "
  • " ); + description << wxT( "

    " ) << _( "Non official repositories" ) << wxT( "" ); diff --git a/pcbnew/class_footprint_wizard.h b/pcbnew/class_footprint_wizard.h index 20fc1d443f..2ffc65811d 100644 --- a/pcbnew/class_footprint_wizard.h +++ b/pcbnew/class_footprint_wizard.h @@ -33,6 +33,17 @@ #include #include +// 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 diff --git a/pcbnew/footprint_wizard.cpp b/pcbnew/footprint_wizard.cpp index 6e9b17ec79..02b379c667 100644 --- a/pcbnew/footprint_wizard.cpp +++ b/pcbnew/footprint_wizard.cpp @@ -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 ) { diff --git a/pcbnew/footprint_wizard_frame.cpp b/pcbnew/footprint_wizard_frame.cpp index 6112fa6018..bb2e99bb48 100644 --- a/pcbnew/footprint_wizard_frame.cpp +++ b/pcbnew/footprint_wizard_frame.cpp @@ -44,6 +44,8 @@ #include "footprint_wizard_frame.h" #include #include +#include +#include #include #include @@ -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 ); } diff --git a/pcbnew/footprint_wizard_frame.h b/pcbnew/footprint_wizard_frame.h index 97fabe166d..302d74601d 100644 --- a/pcbnew/footprint_wizard_frame.h +++ b/pcbnew/footprint_wizard_frame.h @@ -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 diff --git a/pcbnew/pcbnew.cpp b/pcbnew/pcbnew.cpp index b760c5b202..2767f1d336 100644 --- a/pcbnew/pcbnew.cpp +++ b/pcbnew/pcbnew.cpp @@ -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; diff --git a/pcbnew/pcbnew_id.h b/pcbnew/pcbnew_id.h index 801a4e021c..19614c05d1 100644 --- a/pcbnew/pcbnew_id.h +++ b/pcbnew/pcbnew_id.h @@ -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, diff --git a/pcbnew/python/plugins/FPC_(SMD_type)_footprintwizard.py b/pcbnew/python/plugins/FPC_wizard.py similarity index 93% rename from pcbnew/python/plugins/FPC_(SMD_type)_footprintwizard.py rename to pcbnew/python/plugins/FPC_wizard.py index bf77e93241..7f10b0b042 100644 --- a/pcbnew/python/plugins/FPC_(SMD_type)_footprintwizard.py +++ b/pcbnew/python/plugins/FPC_wizard.py @@ -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"] diff --git a/pcbnew/python/plugins/FootprintWizardDrawingAids.py b/pcbnew/python/plugins/FootprintWizardBase.py similarity index 79% rename from pcbnew/python/plugins/FootprintWizardDrawingAids.py rename to pcbnew/python/plugins/FootprintWizardBase.py index 036f09a616..6a57ac1fe4 100644 --- a/pcbnew/python/plugins/FootprintWizardDrawingAids.py +++ b/pcbnew/python/plugins/FootprintWizardBase.py @@ -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. @@ -529,4 +671,4 @@ class FootprintWizardDrawingAids: [0, 0]] self.Polyline(pts) - self.PopTransform(2) + self.PopTransform(2) \ No newline at end of file diff --git a/pcbnew/python/plugins/HelpfulFootprintWizardPlugin.py b/pcbnew/python/plugins/HelpfulFootprintWizardPlugin.py deleted file mode 100644 index d891b7a462..0000000000 --- a/pcbnew/python/plugins/HelpfulFootprintWizardPlugin.py +++ /dev/null @@ -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 diff --git a/pcbnew/python/plugins/bga_wizard.py b/pcbnew/python/plugins/bga_wizard.py index 8ce17e6a6b..6705450800 100644 --- a/pcbnew/python/plugins/bga_wizard.py +++ b/pcbnew/python/plugins/bga_wizard.py @@ -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) diff --git a/pcbnew/python/plugins/circular_pad_array_wizard.py b/pcbnew/python/plugins/circular_pad_array_wizard.py index 7a42c8fe84..42c08c9c7a 100644 --- a/pcbnew/python/plugins/circular_pad_array_wizard.py +++ b/pcbnew/python/plugins/circular_pad_array_wizard.py @@ -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() diff --git a/pcbnew/python/plugins/qfn_wizard.py b/pcbnew/python/plugins/qfn_wizard.py index d6c3a614b1..f36165bc53 100644 --- a/pcbnew/python/plugins/qfn_wizard.py +++ b/pcbnew/python/plugins/qfn_wizard.py @@ -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)) diff --git a/pcbnew/python/plugins/qfp_wizard.py b/pcbnew/python/plugins/qfp_wizard.py index 04664aa5f4..4c18e89609 100644 --- a/pcbnew/python/plugins/qfp_wizard.py +++ b/pcbnew/python/plugins/qfp_wizard.py @@ -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)) diff --git a/pcbnew/python/plugins/qrcode.py b/pcbnew/python/plugins/qrcode.py new file mode 100644 index 0000000000..1eecc61f4a --- /dev/null +++ b/pcbnew/python/plugins/qrcode.py @@ -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() ) ) diff --git a/pcbnew/python/plugins/qrcode_footprint_wizard.py b/pcbnew/python/plugins/qrcode_footprint_wizard.py new file mode 100644 index 0000000000..f4858ea150 --- /dev/null +++ b/pcbnew/python/plugins/qrcode_footprint_wizard.py @@ -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() diff --git a/pcbnew/python/plugins/sdip_wizard.py b/pcbnew/python/plugins/sdip_wizard.py index fad99f1dbe..ce7afb3937 100644 --- a/pcbnew/python/plugins/sdip_wizard.py +++ b/pcbnew/python/plugins/sdip_wizard.py @@ -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): diff --git a/pcbnew/python/plugins/touch_slider_wizard.py b/pcbnew/python/plugins/touch_slider_wizard.py index e27fffb687..7fc300aed6 100644 --- a/pcbnew/python/plugins/touch_slider_wizard.py +++ b/pcbnew/python/plugins/touch_slider_wizard.py @@ -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) diff --git a/pcbnew/python/plugins/uss39_barcode.py b/pcbnew/python/plugins/uss39_barcode.py index ee867758f1..1c3b234637 100644 --- a/pcbnew/python/plugins/uss39_barcode.py +++ b/pcbnew/python/plugins/uss39_barcode.py @@ -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)) @@ -146,4 +147,4 @@ class Uss39Wizard(HelpfulFootprintWizardPlugin.HelpfulFootprintWizardPlugin): self.draw.Circle(0, 0, B.FromMM(0.25)) self.module.Value().SetLayer(B.F_Fab) -Uss39Wizard().register() +Uss39Wizard().register() \ No newline at end of file diff --git a/pcbnew/python/plugins/zip_wizard.py b/pcbnew/python/plugins/zip_wizard.py index 4ffa7f1523..e348617797 100644 --- a/pcbnew/python/plugins/zip_wizard.py +++ b/pcbnew/python/plugins/zip_wizard.py @@ -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 @@ -74,12 +63,12 @@ class RowedFootprint(HFPW.HelpfulFootprintWizardPlugin): array = PA.PadZGridArray(pad, pad_count, line_count, line_pitch, pad_pitch) array.AddPadsToModule(self.draw) - + # 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) diff --git a/pcbnew/swig/pcbnew_footprint_wizards.cpp b/pcbnew/swig/pcbnew_footprint_wizards.cpp index e0f3854788..3070b7a8df 100644 --- a/pcbnew/swig/pcbnew_footprint_wizards.cpp +++ b/pcbnew/swig/pcbnew_footprint_wizards.cpp @@ -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 - * 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 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):