Pcbnew, export_footprints_placefile.cpp: code refactor.
Make the data generation code separate from the dialog code. The dialog itself does not include anymore the data generation code. Non SMD footprints forced in list are now displayed in the dialog. The attribute MOD_CMS is no longer silently modified (without undo command) by the dialog.
This commit is contained in:
parent
9328ec8b52
commit
978c4fd7d3
|
@ -204,6 +204,7 @@ set( PCBNEW_EXPORTERS
|
|||
exporters/export_gencad.cpp
|
||||
exporters/export_idf.cpp
|
||||
exporters/export_vrml.cpp
|
||||
exporters/export_footprints_placefile.cpp
|
||||
exporters/gen_drill_report_files.cpp
|
||||
exporters/gen_footprints_placefile.cpp
|
||||
exporters/gendrill_Excellon_writer.cpp
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
///////////////////////////////////////////////////////////////////////////
|
||||
// C++ code generated with wxFormBuilder (version Feb 10 2019)
|
||||
// C++ code generated with wxFormBuilder (version Dec 1 2018)
|
||||
// http://www.wxformbuilder.org/
|
||||
//
|
||||
// PLEASE DO *NOT* EDIT THIS FILE!
|
||||
|
@ -31,7 +31,7 @@ DIALOG_GEN_FOOTPRINT_POSITION_BASE::DIALOG_GEN_FOOTPRINT_POSITION_BASE( wxWindow
|
|||
m_outputDirectoryName->SetToolTip( _("Target directory for plot files. Can be absolute or relative to the board file location.") );
|
||||
m_outputDirectoryName->SetMinSize( wxSize( 350,-1 ) );
|
||||
|
||||
bSizerdirBrowse->Add( m_outputDirectoryName, 1, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 );
|
||||
bSizerdirBrowse->Add( m_outputDirectoryName, 0, wxALIGN_CENTER_VERTICAL, 5 );
|
||||
|
||||
m_browseButton = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 );
|
||||
m_browseButton->SetMinSize( wxSize( 30,28 ) );
|
||||
|
@ -39,13 +39,13 @@ DIALOG_GEN_FOOTPRINT_POSITION_BASE::DIALOG_GEN_FOOTPRINT_POSITION_BASE( wxWindow
|
|||
bSizerdirBrowse->Add( m_browseButton, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
|
||||
|
||||
|
||||
bUpperSizer->Add( bSizerdirBrowse, 1, wxBOTTOM|wxEXPAND|wxLEFT|wxRIGHT|wxTOP, 10 );
|
||||
bUpperSizer->Add( bSizerdirBrowse, 0, wxBOTTOM|wxEXPAND|wxLEFT|wxRIGHT|wxTOP, 10 );
|
||||
|
||||
|
||||
m_MainSizer->Add( bUpperSizer, 0, wxEXPAND, 2 );
|
||||
|
||||
wxBoxSizer* bSizer71;
|
||||
bSizer71 = new wxBoxSizer( wxHORIZONTAL );
|
||||
wxBoxSizer* bSizerMiddle;
|
||||
bSizerMiddle = new wxBoxSizer( wxHORIZONTAL );
|
||||
|
||||
wxString m_rbFormatChoices[] = { _("ASCII"), _("CSV") };
|
||||
int m_rbFormatNChoices = sizeof( m_rbFormatChoices ) / sizeof( wxString );
|
||||
|
@ -53,7 +53,7 @@ DIALOG_GEN_FOOTPRINT_POSITION_BASE::DIALOG_GEN_FOOTPRINT_POSITION_BASE( wxWindow
|
|||
m_rbFormat->SetSelection( 0 );
|
||||
m_rbFormat->SetMinSize( wxSize( 90,-1 ) );
|
||||
|
||||
bSizer71->Add( m_rbFormat, 1, wxALL, 5 );
|
||||
bSizerMiddle->Add( m_rbFormat, 1, wxALL, 5 );
|
||||
|
||||
wxString m_radioBoxUnitsChoices[] = { _("Inches"), _("Millimeters") };
|
||||
int m_radioBoxUnitsNChoices = sizeof( m_radioBoxUnitsChoices ) / sizeof( wxString );
|
||||
|
@ -61,7 +61,7 @@ DIALOG_GEN_FOOTPRINT_POSITION_BASE::DIALOG_GEN_FOOTPRINT_POSITION_BASE( wxWindow
|
|||
m_radioBoxUnits->SetSelection( 0 );
|
||||
m_radioBoxUnits->SetMinSize( wxSize( 90,-1 ) );
|
||||
|
||||
bSizer71->Add( m_radioBoxUnits, 1, wxALL, 5 );
|
||||
bSizerMiddle->Add( m_radioBoxUnits, 1, wxALL, 5 );
|
||||
|
||||
wxString m_radioBoxFilesCountChoices[] = { _("Separate files for front and back"), _("Single file for board") };
|
||||
int m_radioBoxFilesCountNChoices = sizeof( m_radioBoxFilesCountChoices ) / sizeof( wxString );
|
||||
|
@ -69,50 +69,49 @@ DIALOG_GEN_FOOTPRINT_POSITION_BASE::DIALOG_GEN_FOOTPRINT_POSITION_BASE( wxWindow
|
|||
m_radioBoxFilesCount->SetSelection( 0 );
|
||||
m_radioBoxFilesCount->SetToolTip( _("Creates 2 files: one for each board side or\nCreates only one file containing all footprints to place\n") );
|
||||
|
||||
bSizer71->Add( m_radioBoxFilesCount, 2, wxALL, 5 );
|
||||
bSizerMiddle->Add( m_radioBoxFilesCount, 2, wxALL, 5 );
|
||||
|
||||
|
||||
m_MainSizer->Add( bSizer71, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 );
|
||||
m_MainSizer->Add( bSizerMiddle, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 );
|
||||
|
||||
wxBoxSizer* bSizer7;
|
||||
bSizer7 = new wxBoxSizer( wxVERTICAL );
|
||||
wxBoxSizer* bSizerLower;
|
||||
bSizerLower = new wxBoxSizer( wxVERTICAL );
|
||||
|
||||
m_forceSMDOpt = new wxCheckBox( this, wxID_ANY, _("Include footprints with SMD pads even if not marked Surface Mount"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
bSizer7->Add( m_forceSMDOpt, 0, wxALL, 5 );
|
||||
bSizerLower->Add( m_forceSMDOpt, 0, wxALL, 5 );
|
||||
|
||||
m_messagesPanel = new WX_HTML_REPORT_PANEL( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
|
||||
m_messagesPanel->SetMinSize( wxSize( 300,150 ) );
|
||||
m_messagesPanel->SetMinSize( wxSize( 350,300 ) );
|
||||
|
||||
bSizer7->Add( m_messagesPanel, 1, wxEXPAND | wxALL, 5 );
|
||||
bSizerLower->Add( m_messagesPanel, 1, wxEXPAND | wxALL, 5 );
|
||||
|
||||
|
||||
m_MainSizer->Add( bSizer7, 1, wxEXPAND|wxRIGHT|wxLEFT, 5 );
|
||||
m_MainSizer->Add( bSizerLower, 1, wxEXPAND|wxRIGHT|wxLEFT, 5 );
|
||||
|
||||
m_sdbSizer1 = new wxStdDialogButtonSizer();
|
||||
m_sdbSizer1OK = new wxButton( this, wxID_OK );
|
||||
m_sdbSizer1->AddButton( m_sdbSizer1OK );
|
||||
m_sdbSizer1Cancel = new wxButton( this, wxID_CANCEL );
|
||||
m_sdbSizer1->AddButton( m_sdbSizer1Cancel );
|
||||
m_sdbSizer1->Realize();
|
||||
m_sdbSizer = new wxStdDialogButtonSizer();
|
||||
m_sdbSizerOK = new wxButton( this, wxID_OK );
|
||||
m_sdbSizer->AddButton( m_sdbSizerOK );
|
||||
m_sdbSizerCancel = new wxButton( this, wxID_CANCEL );
|
||||
m_sdbSizer->AddButton( m_sdbSizerCancel );
|
||||
m_sdbSizer->Realize();
|
||||
|
||||
m_MainSizer->Add( m_sdbSizer1, 0, wxALL|wxEXPAND, 5 );
|
||||
m_MainSizer->Add( m_sdbSizer, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
this->SetSizer( m_MainSizer );
|
||||
this->Layout();
|
||||
m_MainSizer->Fit( this );
|
||||
|
||||
this->Centre( wxBOTH );
|
||||
|
||||
// Connect Events
|
||||
m_browseButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::OnOutputDirectoryBrowseClicked ), NULL, this );
|
||||
m_sdbSizer1OK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::OnGenerate ), NULL, this );
|
||||
m_sdbSizerOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::OnGenerate ), NULL, this );
|
||||
}
|
||||
|
||||
DIALOG_GEN_FOOTPRINT_POSITION_BASE::~DIALOG_GEN_FOOTPRINT_POSITION_BASE()
|
||||
{
|
||||
// Disconnect Events
|
||||
m_browseButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::OnOutputDirectoryBrowseClicked ), NULL, this );
|
||||
m_sdbSizer1OK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::OnGenerate ), NULL, this );
|
||||
m_sdbSizerOK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::OnGenerate ), NULL, this );
|
||||
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
<property name="file">dialog_gen_footprint_position_file_base</property>
|
||||
<property name="first_id">1000</property>
|
||||
<property name="help_provider">none</property>
|
||||
<property name="image_path_wrapper_function_name"></property>
|
||||
<property name="indent_with_spaces"></property>
|
||||
<property name="internationalize">1</property>
|
||||
<property name="name">dialog_gen_footprint_positions_base</property>
|
||||
|
@ -26,7 +25,6 @@
|
|||
<property name="skip_php_events">1</property>
|
||||
<property name="skip_python_events">1</property>
|
||||
<property name="ui_table">UI</property>
|
||||
<property name="use_array_enum">0</property>
|
||||
<property name="use_enum">1</property>
|
||||
<property name="use_microsoft_bom">0</property>
|
||||
<object class="Dialog" expanded="1">
|
||||
|
@ -47,7 +45,7 @@
|
|||
<property name="minimum_size">-1,-1</property>
|
||||
<property name="name">DIALOG_GEN_FOOTPRINT_POSITION_BASE</property>
|
||||
<property name="pos"></property>
|
||||
<property name="size">-1,-1</property>
|
||||
<property name="size">506,476</property>
|
||||
<property name="style">wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER</property>
|
||||
<property name="subclass">DIALOG_SHIM; dialog_shim.h</property>
|
||||
<property name="title">Generate Footprint Position Files</property>
|
||||
|
@ -72,7 +70,7 @@
|
|||
<object class="sizeritem" expanded="1">
|
||||
<property name="border">10</property>
|
||||
<property name="flag">wxBOTTOM|wxEXPAND|wxLEFT|wxRIGHT|wxTOP</property>
|
||||
<property name="proportion">1</property>
|
||||
<property name="proportion">0</property>
|
||||
<object class="wxBoxSizer" expanded="1">
|
||||
<property name="minimum_size"></property>
|
||||
<property name="name">bSizerdirBrowse</property>
|
||||
|
@ -141,8 +139,8 @@
|
|||
</object>
|
||||
<object class="sizeritem" expanded="0">
|
||||
<property name="border">5</property>
|
||||
<property name="flag">wxALIGN_CENTER_VERTICAL|wxEXPAND</property>
|
||||
<property name="proportion">1</property>
|
||||
<property name="flag">wxALIGN_CENTER_VERTICAL</property>
|
||||
<property name="proportion">0</property>
|
||||
<object class="wxTextCtrl" expanded="0">
|
||||
<property name="BottomDockable">1</property>
|
||||
<property name="LeftDockable">1</property>
|
||||
|
@ -286,7 +284,7 @@
|
|||
<property name="proportion">0</property>
|
||||
<object class="wxBoxSizer" expanded="1">
|
||||
<property name="minimum_size"></property>
|
||||
<property name="name">bSizer71</property>
|
||||
<property name="name">bSizerMiddle</property>
|
||||
<property name="orient">wxHORIZONTAL</property>
|
||||
<property name="permission">none</property>
|
||||
<object class="sizeritem" expanded="1">
|
||||
|
@ -495,7 +493,7 @@
|
|||
<property name="proportion">1</property>
|
||||
<object class="wxBoxSizer" expanded="1">
|
||||
<property name="minimum_size"></property>
|
||||
<property name="name">bSizer7</property>
|
||||
<property name="name">bSizerLower</property>
|
||||
<property name="orient">wxVERTICAL</property>
|
||||
<property name="permission">none</property>
|
||||
<object class="sizeritem" expanded="1">
|
||||
|
@ -599,7 +597,7 @@
|
|||
<property name="maximum_size"></property>
|
||||
<property name="min_size"></property>
|
||||
<property name="minimize_button">0</property>
|
||||
<property name="minimum_size">300,150</property>
|
||||
<property name="minimum_size">350,300</property>
|
||||
<property name="moveable">1</property>
|
||||
<property name="name">m_messagesPanel</property>
|
||||
<property name="pane_border">1</property>
|
||||
|
@ -635,7 +633,7 @@
|
|||
<property name="Save">0</property>
|
||||
<property name="Yes">0</property>
|
||||
<property name="minimum_size"></property>
|
||||
<property name="name">m_sdbSizer1</property>
|
||||
<property name="name">m_sdbSizer</property>
|
||||
<property name="permission">protected</property>
|
||||
<event name="OnOKButtonClick">OnGenerate</event>
|
||||
</object>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
///////////////////////////////////////////////////////////////////////////
|
||||
// C++ code generated with wxFormBuilder (version Feb 10 2019)
|
||||
// C++ code generated with wxFormBuilder (version Dec 1 2018)
|
||||
// http://www.wxformbuilder.org/
|
||||
//
|
||||
// PLEASE DO *NOT* EDIT THIS FILE!
|
||||
|
@ -50,9 +50,9 @@ class DIALOG_GEN_FOOTPRINT_POSITION_BASE : public DIALOG_SHIM
|
|||
wxRadioBox* m_radioBoxFilesCount;
|
||||
wxCheckBox* m_forceSMDOpt;
|
||||
WX_HTML_REPORT_PANEL* m_messagesPanel;
|
||||
wxStdDialogButtonSizer* m_sdbSizer1;
|
||||
wxButton* m_sdbSizer1OK;
|
||||
wxButton* m_sdbSizer1Cancel;
|
||||
wxStdDialogButtonSizer* m_sdbSizer;
|
||||
wxButton* m_sdbSizerOK;
|
||||
wxButton* m_sdbSizerCancel;
|
||||
|
||||
// Virtual event handlers, overide them in your derived class
|
||||
virtual void OnOutputDirectoryBrowseClicked( wxCommandEvent& event ) { event.Skip(); }
|
||||
|
@ -61,7 +61,7 @@ class DIALOG_GEN_FOOTPRINT_POSITION_BASE : public DIALOG_SHIM
|
|||
|
||||
public:
|
||||
|
||||
DIALOG_GEN_FOOTPRINT_POSITION_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Generate Footprint Position Files"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
|
||||
DIALOG_GEN_FOOTPRINT_POSITION_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Generate Footprint Position Files"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 506,476 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
|
||||
~DIALOG_GEN_FOOTPRINT_POSITION_BASE();
|
||||
|
||||
};
|
||||
|
|
|
@ -0,0 +1,419 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2015-2019 KiCad Developers, see AUTHORS.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
|
||||
* 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, you may find one here:
|
||||
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||||
* or you may search the http://www.gnu.org website for the version 2 license,
|
||||
* or you may write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* 1 - create ascii/csv files for automatic placement of smd components
|
||||
* 2 - create a module report (pos and module descr) (ascii file)
|
||||
*/
|
||||
|
||||
#include <fctsys.h>
|
||||
#include <kicad_string.h>
|
||||
#include <build_version.h>
|
||||
#include <macros.h>
|
||||
#include <export_footprints_placefile.h>
|
||||
|
||||
|
||||
class LIST_MOD // An helper class used to build a list of useful footprints.
|
||||
{
|
||||
public:
|
||||
MODULE* m_Module; // Link to the actual footprint
|
||||
wxString m_Reference; // Its schematic reference
|
||||
wxString m_Value; // Its schematic value
|
||||
LAYER_NUM m_Layer; // its side (B_Cu, or F_Cu)
|
||||
};
|
||||
|
||||
|
||||
// Defined values to write coordinates using inches or mm:
|
||||
static const double conv_unit_inch = 0.001 / IU_PER_MILS ; // units = INCHES
|
||||
static const char unit_text_inch[] = "## Unit = inches, Angle = deg.\n";
|
||||
|
||||
static const double conv_unit_mm = 1.0 / IU_PER_MM; // units = mm
|
||||
static const char unit_text_mm[] = "## Unit = mm, Angle = deg.\n";
|
||||
|
||||
// Sort function use by GenereModulesPosition()
|
||||
// sort is made by side (layer) top layer first
|
||||
// then by reference increasing order
|
||||
static bool sortFPlist( const LIST_MOD& ref, const LIST_MOD& tst )
|
||||
{
|
||||
if( ref.m_Layer == tst.m_Layer )
|
||||
return StrNumCmp( ref.m_Reference, tst.m_Reference ) < 0;
|
||||
|
||||
return ref.m_Layer > tst.m_Layer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function HasNonSMDPins
|
||||
* returns true if the given module has any non smd pins, such as through hole
|
||||
* and therefore cannot be placed automatically.
|
||||
*/
|
||||
static bool HasNonSMDPins( MODULE* aModule )
|
||||
{
|
||||
for( auto pad : aModule->Pads() )
|
||||
{
|
||||
if( pad->GetAttribute() != PAD_ATTRIB_SMD )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum SELECT_SIDE
|
||||
{
|
||||
PCB_NO_SIDE,
|
||||
PCB_BACK_SIDE,
|
||||
PCB_FRONT_SIDE,
|
||||
PCB_BOTH_SIDES
|
||||
};
|
||||
|
||||
PLACE_FILE_EXPORTER::PLACE_FILE_EXPORTER( BOARD* aBoard, bool aUnitsMM,
|
||||
bool aForceSmdItems, bool aTopSide,
|
||||
bool aBottomSide, bool aFormatCSV )
|
||||
{
|
||||
m_board = aBoard;
|
||||
m_unitsMM = aUnitsMM;
|
||||
m_forceSmdItems = aForceSmdItems;
|
||||
|
||||
if( aTopSide && aBottomSide )
|
||||
m_side = PCB_BOTH_SIDES;
|
||||
else if( aTopSide )
|
||||
m_side = PCB_FRONT_SIDE;
|
||||
else if( aBottomSide )
|
||||
m_side = PCB_BACK_SIDE;
|
||||
else
|
||||
m_side = PCB_NO_SIDE;
|
||||
|
||||
m_formatCSV = aFormatCSV;
|
||||
}
|
||||
|
||||
|
||||
std::string PLACE_FILE_EXPORTER::GenPositionData()
|
||||
{
|
||||
std::string buffer;
|
||||
char line[1024]; // A line to print intermediate data
|
||||
|
||||
// Minimal text lengths:
|
||||
m_fpCount = 0;
|
||||
int lenRefText = 8;
|
||||
int lenValText = 8;
|
||||
int lenPkgText = 16;
|
||||
|
||||
m_place_Offset = m_board->GetAuxOrigin();
|
||||
|
||||
// Calculating the number of useful footprints (CMS attribute, not VIRTUAL)
|
||||
m_fpCount = 0;
|
||||
|
||||
// Select units:
|
||||
double conv_unit = m_unitsMM ? conv_unit_mm : conv_unit_inch;
|
||||
const char *unit_text = m_unitsMM ? unit_text_mm : unit_text_inch;
|
||||
|
||||
// Build and sort the list of footprints alphabetically
|
||||
std::vector<LIST_MOD> list;
|
||||
m_smdFootprintsNotLabeledSMD.clear();
|
||||
|
||||
for( auto footprint : m_board->Modules() )
|
||||
{
|
||||
if( m_side != PCB_BOTH_SIDES )
|
||||
{
|
||||
if( footprint->GetLayer() == B_Cu && m_side != PCB_BACK_SIDE )
|
||||
continue;
|
||||
if( footprint->GetLayer() == F_Cu && m_side != PCB_FRONT_SIDE )
|
||||
continue;
|
||||
}
|
||||
|
||||
if( footprint->GetAttributes() & MOD_VIRTUAL )
|
||||
continue;
|
||||
|
||||
if( ( footprint->GetAttributes() & MOD_CMS ) == 0 )
|
||||
{
|
||||
if( m_forceSmdItems ) // true to fix a bunch of mis-labeled footprints:
|
||||
{
|
||||
if( !HasNonSMDPins( footprint ) )
|
||||
{
|
||||
// all footprint's pins are SMD, mark the part for pick and place
|
||||
// Note: they are not necessary to pick and place,
|
||||
// but the probability is high
|
||||
m_smdFootprintsNotLabeledSMD.push_back( footprint );
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
m_fpCount++;
|
||||
|
||||
LIST_MOD item;
|
||||
item.m_Module = footprint;
|
||||
item.m_Reference = footprint->GetReference();
|
||||
item.m_Value = footprint->GetValue();
|
||||
item.m_Layer = footprint->GetLayer();
|
||||
list.push_back( item );
|
||||
|
||||
lenRefText = std::max( lenRefText, int(item.m_Reference.length()) );
|
||||
lenValText = std::max( lenValText, int(item.m_Value.length()) );
|
||||
lenPkgText = std::max( lenPkgText, int(item.m_Module->GetFPID().GetLibItemName().length()) );
|
||||
}
|
||||
|
||||
if( list.size() > 1 )
|
||||
sort( list.begin(), list.end(), sortFPlist );
|
||||
|
||||
// Switch the locale to standard C (needed to print floating point numbers)
|
||||
LOCALE_IO toggle;
|
||||
|
||||
if( m_formatCSV )
|
||||
{
|
||||
wxChar csv_sep = ',';
|
||||
|
||||
// Set first line:;
|
||||
sprintf( line, "Ref%cVal%cPackage%cPosX%cPosY%cRot%cSide\n",
|
||||
csv_sep, csv_sep, csv_sep, csv_sep, csv_sep, csv_sep );
|
||||
|
||||
buffer += line;
|
||||
|
||||
for( int ii = 0; ii < m_fpCount; ii++ )
|
||||
{
|
||||
wxPoint footprint_pos;
|
||||
footprint_pos = list[ii].m_Module->GetPosition();
|
||||
footprint_pos -= m_place_Offset;
|
||||
|
||||
LAYER_NUM layer = list[ii].m_Module->GetLayer();
|
||||
wxASSERT( layer == F_Cu || layer == B_Cu );
|
||||
|
||||
wxString tmp = "\"" + list[ii].m_Reference;
|
||||
tmp << "\"" << csv_sep;
|
||||
tmp << "\"" << list[ii].m_Value;
|
||||
tmp << "\"" << csv_sep;
|
||||
tmp << "\"" << list[ii].m_Module->GetFPID().GetLibItemName().wx_str();
|
||||
tmp << "\"" << csv_sep;
|
||||
|
||||
tmp << wxString::Format( "%f%c%f%c%f",
|
||||
footprint_pos.x * conv_unit, csv_sep,
|
||||
// Keep the Y axis oriented from bottom to top,
|
||||
// ( change y coordinate sign )
|
||||
-footprint_pos.y * conv_unit, csv_sep,
|
||||
list[ii].m_Module->GetOrientation() / 10.0 );
|
||||
tmp << csv_sep;
|
||||
|
||||
tmp << ( (layer == F_Cu ) ? PLACE_FILE_EXPORTER::GetFrontSideName()
|
||||
: PLACE_FILE_EXPORTER::GetBackSideName() );
|
||||
tmp << '\n';
|
||||
|
||||
buffer += TO_UTF8( tmp );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write file header
|
||||
sprintf( line, "### Module positions - created on %s ###\n", TO_UTF8( DateAndTime() ) );
|
||||
|
||||
buffer += line;
|
||||
|
||||
wxString Title = GetBuildVersion();
|
||||
sprintf( line, "### Printed by Pcbnew version %s\n", TO_UTF8( Title ) );
|
||||
buffer += line;
|
||||
|
||||
buffer += unit_text;
|
||||
buffer += "## Side : ";
|
||||
|
||||
if( m_side == PCB_BACK_SIDE )
|
||||
buffer += GetBackSideName().c_str();
|
||||
else if( m_side == PCB_FRONT_SIDE )
|
||||
buffer += GetFrontSideName().c_str();
|
||||
else if( m_side == PCB_BOTH_SIDES )
|
||||
buffer += "All";
|
||||
else
|
||||
buffer += "---";
|
||||
|
||||
buffer += "\n";
|
||||
|
||||
sprintf(line, "%-*s %-*s %-*s %9.9s %9.9s %8.8s %s\n",
|
||||
int(lenRefText), "# Ref",
|
||||
int(lenValText), "Val",
|
||||
int(lenPkgText), "Package",
|
||||
"PosX", "PosY", "Rot", "Side" );
|
||||
buffer += line;
|
||||
|
||||
for( int ii = 0; ii < m_fpCount; ii++ )
|
||||
{
|
||||
wxPoint footprint_pos;
|
||||
footprint_pos = list[ii].m_Module->GetPosition();
|
||||
footprint_pos -= m_place_Offset;
|
||||
|
||||
LAYER_NUM layer = list[ii].m_Module->GetLayer();
|
||||
wxASSERT( layer == F_Cu || layer == B_Cu );
|
||||
|
||||
if( layer == B_Cu )
|
||||
footprint_pos.x = - footprint_pos.x;
|
||||
|
||||
wxString ref = list[ii].m_Reference;
|
||||
wxString val = list[ii].m_Value;
|
||||
wxString pkg = list[ii].m_Module->GetFPID().GetLibItemName();
|
||||
ref.Replace( wxT( " " ), wxT( "_" ) );
|
||||
val.Replace( wxT( " " ), wxT( "_" ) );
|
||||
pkg.Replace( wxT( " " ), wxT( "_" ) );
|
||||
sprintf(line, "%-*s %-*s %-*s %9.4f %9.4f %8.4f %s\n",
|
||||
lenRefText, TO_UTF8( ref ),
|
||||
lenValText, TO_UTF8( val ),
|
||||
lenPkgText, TO_UTF8( pkg ),
|
||||
footprint_pos.x * conv_unit,
|
||||
// Keep the coordinates in the first quadrant,
|
||||
// (i.e. change y sign
|
||||
-footprint_pos.y * conv_unit,
|
||||
list[ii].m_Module->GetOrientation() / 10.0,
|
||||
(layer == F_Cu ) ? GetFrontSideName().c_str() : GetBackSideName().c_str() );
|
||||
buffer += line;
|
||||
}
|
||||
|
||||
// Write EOF
|
||||
buffer += "## End\n";
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
std::string PLACE_FILE_EXPORTER::GenReportData()
|
||||
{
|
||||
std::string buffer;
|
||||
wxPoint module_pos;
|
||||
|
||||
m_place_Offset = wxPoint( 0, 0 );
|
||||
|
||||
// Select units:
|
||||
double conv_unit = m_unitsMM ? conv_unit_mm : conv_unit_inch;
|
||||
const char *unit_text = m_unitsMM ? unit_text_mm : unit_text_inch;
|
||||
|
||||
LOCALE_IO toggle;
|
||||
|
||||
// Generate header file comments.)
|
||||
char line[1024];
|
||||
sprintf( line, "## Footprint report - date %s\n", TO_UTF8( DateAndTime() ) );
|
||||
buffer += line;
|
||||
|
||||
wxString Title = GetBuildVersion();
|
||||
sprintf( line, "## Created by Pcbnew version %s\n", TO_UTF8( Title ) );
|
||||
buffer += line;
|
||||
|
||||
buffer += unit_text;
|
||||
|
||||
buffer += "\n$BeginDESCRIPTION\n";
|
||||
|
||||
EDA_RECT bbbox = m_board->ComputeBoundingBox();
|
||||
|
||||
buffer += "\n$BOARD\n";
|
||||
|
||||
sprintf( line, "upper_left_corner %9.6f %9.6f\n",
|
||||
bbbox.GetX() * conv_unit, bbbox.GetY() * conv_unit );
|
||||
buffer += line;
|
||||
|
||||
sprintf( line, "lower_right_corner %9.6f %9.6f\n",
|
||||
bbbox.GetRight() * conv_unit, bbbox.GetBottom() * conv_unit );
|
||||
buffer += line;
|
||||
|
||||
buffer += "$EndBOARD\n\n";
|
||||
|
||||
for( auto Module : m_board->Modules() )
|
||||
{
|
||||
sprintf( line, "$MODULE %s\n", EscapedUTF8( Module->GetReference() ).c_str() );
|
||||
buffer += line;
|
||||
|
||||
sprintf( line, "reference %s\n", EscapedUTF8( Module->GetReference() ).c_str() );
|
||||
sprintf( line, "value %s\n", EscapedUTF8( Module->GetValue() ).c_str() );
|
||||
sprintf( line, "footprint %s\n",
|
||||
EscapedUTF8( FROM_UTF8( Module->GetFPID().Format().c_str() ) ).c_str() );
|
||||
buffer += line;
|
||||
|
||||
buffer += "attribut";
|
||||
|
||||
if( Module->GetAttributes() & MOD_VIRTUAL )
|
||||
buffer += " virtual";
|
||||
|
||||
if( Module->GetAttributes() & MOD_CMS )
|
||||
buffer += " smd";
|
||||
|
||||
if( ( Module->GetAttributes() & (MOD_VIRTUAL | MOD_CMS) ) == 0 )
|
||||
buffer += " none";
|
||||
|
||||
buffer += "\n";
|
||||
|
||||
module_pos = Module->GetPosition();
|
||||
module_pos -= m_place_Offset;
|
||||
|
||||
sprintf( line, "position %9.6f %9.6f orientation %.2f\n",
|
||||
module_pos.x * conv_unit, module_pos.y * conv_unit,
|
||||
Module->GetOrientation() / 10.0 );
|
||||
buffer += line;
|
||||
|
||||
if( Module->GetLayer() == F_Cu )
|
||||
buffer += "layer front\n";
|
||||
else if( Module->GetLayer() == B_Cu )
|
||||
buffer += "layer back\n";
|
||||
else
|
||||
buffer += "layer other\n";
|
||||
|
||||
for( auto pad : Module->Pads() )
|
||||
{
|
||||
sprintf( line, "$PAD \"%s\"\n", TO_UTF8( pad->GetName() ) );
|
||||
buffer += line;
|
||||
|
||||
int layer = 0;
|
||||
|
||||
if( pad->GetLayerSet()[B_Cu] )
|
||||
layer = 1;
|
||||
|
||||
if( pad->GetLayerSet()[F_Cu] )
|
||||
layer |= 2;
|
||||
|
||||
static const char* layer_name[4] = { "nocopper", "back", "front", "both" };
|
||||
sprintf( line, "Shape %s Layer %s\n", TO_UTF8( pad->ShowPadShape() ), layer_name[layer] );
|
||||
buffer += line;
|
||||
|
||||
sprintf( line, "position %9.6f %9.6f size %9.6f %9.6f orientation %.2f\n",
|
||||
pad->GetPos0().x * conv_unit, pad->GetPos0().y * conv_unit,
|
||||
pad->GetSize().x * conv_unit, pad->GetSize().y * conv_unit,
|
||||
(pad->GetOrientation() - Module->GetOrientation()) / 10.0 );
|
||||
buffer += line;
|
||||
|
||||
sprintf( line, "drill %9.6f\n", pad->GetDrillSize().x * conv_unit );
|
||||
buffer += line;
|
||||
|
||||
sprintf( line, "shape_offset %9.6f %9.6f\n",
|
||||
pad->GetOffset().x * conv_unit, pad->GetOffset().y * conv_unit );
|
||||
buffer += line;
|
||||
|
||||
buffer += "$EndPAD\n";
|
||||
}
|
||||
|
||||
sprintf( line, "$EndMODULE %s\n\n", TO_UTF8 (Module->GetReference() ) );
|
||||
buffer += line;
|
||||
}
|
||||
|
||||
// Generate EOF.
|
||||
buffer += "$EndDESCRIPTION\n";
|
||||
|
||||
return buffer;
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2015-2019 KiCad Developers, see AUTHORS.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
|
||||
* 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, you may find one here:
|
||||
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||||
* or you may search the http://www.gnu.org website for the version 2 license,
|
||||
* or you may write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#ifndef EXPORT_FOOTPRINTS_PLACEFILE_H
|
||||
#define EXPORT_FOOTPRINTS_PLACEFILE_H
|
||||
|
||||
|
||||
#include <class_board.h>
|
||||
#include <class_module.h>
|
||||
|
||||
/**
|
||||
* The ASCII format of the kicad place file is:
|
||||
* ### Module positions - created on 04/12/2012 15:24:24 ###
|
||||
* ### Printed by Pcbnew version pcbnew (2012-11-30 BZR 3828)-testing
|
||||
* ## Unit = inches, Angle = deg.
|
||||
* or
|
||||
* ## Unit = mm, Angle = deg.
|
||||
* ## Side : top
|
||||
* or
|
||||
* ## Side : bottom
|
||||
* or
|
||||
* ## Side : all
|
||||
* # Ref Val Package PosX PosY Rot Side
|
||||
* C123 0,1uF/50V SM0603 1.6024 -2.6280 180.0 Front
|
||||
* C124 0,1uF/50V SM0603 1.6063 -2.7579 180.0 Front
|
||||
* C125 0,1uF/50V SM0603 1.6010 -2.8310 180.0 Front
|
||||
* ## End
|
||||
*/
|
||||
|
||||
|
||||
class PLACE_FILE_EXPORTER
|
||||
{
|
||||
public:
|
||||
|
||||
/** Create a PLACE_FILE_EXPORTER
|
||||
* @param aBoard is the board
|
||||
* @param aUnitsMM is the unit option: true foo mm, false for inches
|
||||
* @param aForceSmdItems true to force not virtual and not flagged smd footprints
|
||||
* but having only smd pads to be in list
|
||||
* @param aTopSide true to generate top side info
|
||||
* @param aBottomSide true to generate bottom side info
|
||||
* @param aFormatCSV true to generate a csv format info, false to generate a ascii info
|
||||
*/
|
||||
PLACE_FILE_EXPORTER( BOARD* aBoard, bool aUnitsMM,
|
||||
bool aForceSmdItems, bool aTopSide, bool aBottomSide, bool aFormatCSV );
|
||||
|
||||
/**
|
||||
* build a string filled with the position data
|
||||
*/
|
||||
std::string GenPositionData();
|
||||
|
||||
/**
|
||||
* build a string filled with the pad report data
|
||||
* This report does not used options aForceSmdItems,aTopSide, aBottomSide
|
||||
* and aFormatCSV.
|
||||
* All footprints and their pads on board are reported.
|
||||
*/
|
||||
std::string GenReportData();
|
||||
|
||||
/** @return the footprint count found on board by GenPositionData()
|
||||
* must be called only after GenPositionData() is run
|
||||
*/
|
||||
int GetFootprintCount() { return m_fpCount; }
|
||||
|
||||
/**
|
||||
* @return the list of not virtual footprints with MOD_CMS flag not set
|
||||
* but having only smd pads.
|
||||
* This list can be used to force this flag.
|
||||
* it is filled only if forceSmdItems is true
|
||||
*/
|
||||
std::vector<MODULE*>& GetSmdFootprintsNotLabeledSMD()
|
||||
{
|
||||
return m_smdFootprintsNotLabeledSMD;
|
||||
}
|
||||
|
||||
// Use standard board side name. do not translate them,
|
||||
// they are keywords in place file
|
||||
static std::string GetFrontSideName() { return std::string( "top" ); }
|
||||
static std::string GetBackSideName() { return std::string( "bottom" ); }
|
||||
|
||||
private:
|
||||
BOARD* m_board;
|
||||
bool m_unitsMM; // true for mm, false for inches
|
||||
bool m_forceSmdItems; // If true, non virtual fp with the flag MOD_CMD not set but
|
||||
// having only smd pads will be in list
|
||||
// and will be added in m_smdFootprintsNotLabeledSMD
|
||||
int m_side; // PCB_BACK_SIDE, PCB_FRONT_SIDE, PCB_BOTH_SIDES
|
||||
bool m_formatCSV; // true for csv format, false for ascii (utf8) format
|
||||
int m_fpCount; // Number of footprints in list, for info
|
||||
wxPoint m_place_Offset; // Offset for coordinates in generated data.
|
||||
|
||||
// A list of footprints with MOD_CMS flag not set but having only smd pads.
|
||||
// This list can be used to force this flag.
|
||||
std::vector<MODULE*> m_smdFootprintsNotLabeledSMD;
|
||||
};
|
||||
|
||||
#endif // #ifndef EXPORT_FOOTPRINTS_PLACEFILE_H
|
|
@ -44,45 +44,14 @@
|
|||
#include <kiface_i.h>
|
||||
#include <wx_html_report_panel.h>
|
||||
#include <dialog_gen_footprint_position_file_base.h>
|
||||
#include <export_footprints_placefile.h>
|
||||
|
||||
/*
|
||||
* The ASCII format of the kicad place file is:
|
||||
* ### Module positions - created on 04/12/2012 15:24:24 ###
|
||||
* ### Printed by Pcbnew version pcbnew (2012-11-30 BZR 3828)-testing
|
||||
* ## Unit = inches, Angle = deg.
|
||||
* or
|
||||
* ## Unit = mm, Angle = deg.
|
||||
* ## Side : top
|
||||
* or
|
||||
* ## Side : bottom
|
||||
* or
|
||||
* ## Side : all
|
||||
* # Ref Val Package PosX PosY Rot Side
|
||||
* C123 0,1uF/50V SM0603 1.6024 -2.6280 180.0 Front
|
||||
* C124 0,1uF/50V SM0603 1.6063 -2.7579 180.0 Front
|
||||
* C125 0,1uF/50V SM0603 1.6010 -2.8310 180.0 Front
|
||||
* ## End
|
||||
*/
|
||||
|
||||
#define PLACEFILE_UNITS_KEY wxT( "PlaceFileUnits" )
|
||||
#define PLACEFILE_OPT_KEY wxT( "PlaceFileOpts" )
|
||||
#define PLACEFILE_FORMAT_KEY wxT( "PlaceFileFormat" )
|
||||
|
||||
|
||||
#define PCB_BACK_SIDE 0
|
||||
#define PCB_FRONT_SIDE 1
|
||||
#define PCB_BOTH_SIDES 2
|
||||
|
||||
class LIST_MOD // An helper class used to build a list of useful footprints.
|
||||
{
|
||||
public:
|
||||
MODULE* m_Module; // Link to the actual footprint
|
||||
wxString m_Reference; // Its schematic reference
|
||||
wxString m_Value; // Its schematic value
|
||||
LAYER_NUM m_Layer; // its side (B_Cu, or F_Cu)
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The dialog to create footprint position files and choose options (one or 2 files, units
|
||||
* and force all SMD footprints in list)
|
||||
|
@ -100,11 +69,11 @@ public:
|
|||
|
||||
// We use a sdbSizer to get platform-dependent ordering of the action buttons, but
|
||||
// that requires us to correct the button labels here.
|
||||
m_sdbSizer1OK->SetLabel( _( "Generate Position File" ) );
|
||||
m_sdbSizer1Cancel->SetLabel( _( "Close" ) );
|
||||
m_sdbSizer1->Layout();
|
||||
m_sdbSizerOK->SetLabel( _( "Generate Position File" ) );
|
||||
m_sdbSizerCancel->SetLabel( _( "Close" ) );
|
||||
m_sdbSizer->Layout();
|
||||
|
||||
m_sdbSizer1OK->SetDefault();
|
||||
m_sdbSizerOK->SetDefault();
|
||||
|
||||
GetSizer()->SetSizeHints(this);
|
||||
Centre();
|
||||
|
@ -154,10 +123,6 @@ int DIALOG_GEN_FOOTPRINT_POSITION::m_unitsOpt = 0;
|
|||
int DIALOG_GEN_FOOTPRINT_POSITION::m_fileOpt = 0;
|
||||
int DIALOG_GEN_FOOTPRINT_POSITION::m_fileFormat = 0;
|
||||
|
||||
// Use standard board side name. do not translate them,
|
||||
// they are keywords in place file
|
||||
const wxString frontSideName = wxT( "top" );
|
||||
const wxString backSideName = wxT( "bottom" );
|
||||
|
||||
void DIALOG_GEN_FOOTPRINT_POSITION::initDialog()
|
||||
{
|
||||
|
@ -229,8 +194,6 @@ void DIALOG_GEN_FOOTPRINT_POSITION::OnGenerate( wxCommandEvent& event )
|
|||
m_parent->SetPlotSettings( m_plotOpts );
|
||||
|
||||
CreateFiles();
|
||||
|
||||
// the dialog is not closed here.
|
||||
}
|
||||
|
||||
|
||||
|
@ -242,19 +205,36 @@ bool DIALOG_GEN_FOOTPRINT_POSITION::CreateFiles()
|
|||
bool singleFile = OneFileOnly();
|
||||
bool useCSVfmt = m_fileFormat == 1;
|
||||
int fullcount = 0;
|
||||
int top_side = true;
|
||||
int bottom_side = true;
|
||||
|
||||
// Count the footprints to place, do not yet create a file
|
||||
int fpcount = m_parent->DoGenFootprintsPositionFile( wxEmptyString, UnitsMM(),
|
||||
ForceAllSmd(), PCB_BOTH_SIDES,
|
||||
useCSVfmt );
|
||||
if( fpcount == 0)
|
||||
// Test for any footprint candidate in list, and display the list of forced footprints
|
||||
// if ForceAllSmd() is true
|
||||
{
|
||||
wxMessageBox( _( "No footprint for automated placement." ) );
|
||||
return false;
|
||||
PLACE_FILE_EXPORTER exporter( brd, UnitsMM(), ForceAllSmd(), top_side, bottom_side, useCSVfmt );
|
||||
exporter.GenPositionData();
|
||||
|
||||
if( exporter.GetFootprintCount() == 0)
|
||||
{
|
||||
wxMessageBox( _( "No footprint for automated placement." ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( ForceAllSmd() )
|
||||
{
|
||||
std::vector<MODULE*>& fp_no_smd_list = exporter.GetSmdFootprintsNotLabeledSMD();
|
||||
|
||||
for( MODULE* item : fp_no_smd_list )
|
||||
{
|
||||
msg.Printf( _( "footprint %s (not set as SMD) forced in list" ), item->GetReference() );
|
||||
m_reporter->Report( msg, REPORTER::RPT_INFO );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create output directory if it does not exist (also transform it in
|
||||
// absolute path). Bail if it fails
|
||||
// Create output directory if it does not exist.
|
||||
// Also transform it in absolute path.
|
||||
// Bail if it fails
|
||||
wxFileName outputDir = wxFileName::DirName( m_plotOpts.GetOutputDirectory() );
|
||||
wxString boardFilename = m_parent->GetBoard()->GetFileName();
|
||||
|
||||
|
@ -271,17 +251,17 @@ bool DIALOG_GEN_FOOTPRINT_POSITION::CreateFiles()
|
|||
fn = m_parent->GetBoard()->GetFileName();
|
||||
fn.SetPath( outputDir.GetPath() );
|
||||
|
||||
// Create the the Front or Top side placement file,
|
||||
// or the single file
|
||||
int side = PCB_FRONT_SIDE;
|
||||
// Create the the Front or Top side placement file, or a single file
|
||||
top_side = true;
|
||||
bottom_side = false;
|
||||
|
||||
if( singleFile )
|
||||
{
|
||||
side = PCB_BOTH_SIDES;
|
||||
bottom_side = true;
|
||||
fn.SetName( fn.GetName() + wxT( "-" ) + wxT("all") );
|
||||
}
|
||||
else
|
||||
fn.SetName( fn.GetName() + wxT( "-" ) + frontSideName );
|
||||
fn.SetName( fn.GetName() + wxT( "-" ) + PLACE_FILE_EXPORTER::GetFrontSideName().c_str() );
|
||||
|
||||
|
||||
if( useCSVfmt )
|
||||
|
@ -292,21 +272,22 @@ bool DIALOG_GEN_FOOTPRINT_POSITION::CreateFiles()
|
|||
else
|
||||
fn.SetExt( FootprintPlaceFileExtension );
|
||||
|
||||
fpcount = m_parent->DoGenFootprintsPositionFile( fn.GetFullPath(), UnitsMM(),
|
||||
ForceAllSmd(), side, useCSVfmt );
|
||||
int fpcount = m_parent->DoGenFootprintsPositionFile( fn.GetFullPath(), UnitsMM(),
|
||||
ForceAllSmd(),
|
||||
top_side, bottom_side, useCSVfmt );
|
||||
if( fpcount < 0 )
|
||||
{
|
||||
msg.Printf( _( "Unable to create \"%s\"." ), GetChars( fn.GetFullPath() ) );
|
||||
msg.Printf( _( "Unable to create \"%s\"." ), fn.GetFullPath() );
|
||||
wxMessageBox( msg );
|
||||
m_reporter->Report( msg, REPORTER::RPT_ERROR );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( singleFile )
|
||||
msg.Printf( _( "Place file: \"%s\"." ), GetChars( fn.GetFullPath() ) );
|
||||
msg.Printf( _( "Place file: \"%s\"." ), fn.GetFullPath() );
|
||||
else
|
||||
msg.Printf( _( "Front side (top side) place file: \"%s\"." ),
|
||||
GetChars( fn.GetFullPath() ) );
|
||||
fn.GetFullPath() );
|
||||
m_reporter->Report( msg, REPORTER::RPT_INFO );
|
||||
|
||||
msg.Printf( _( "Component count: %d." ), fpcount );
|
||||
|
@ -320,10 +301,11 @@ bool DIALOG_GEN_FOOTPRINT_POSITION::CreateFiles()
|
|||
|
||||
// Create the Back or Bottom side placement file
|
||||
fullcount = fpcount;
|
||||
side = PCB_BACK_SIDE;
|
||||
top_side = false;
|
||||
bottom_side = true;
|
||||
fn = brd->GetFileName();
|
||||
fn.SetPath( outputDir.GetPath() );
|
||||
fn.SetName( fn.GetName() + wxT( "-" ) + backSideName );
|
||||
fn.SetName( fn.GetName() + wxT( "-" ) + PLACE_FILE_EXPORTER::GetBackSideName().c_str() );
|
||||
|
||||
if( useCSVfmt )
|
||||
{
|
||||
|
@ -334,11 +316,11 @@ bool DIALOG_GEN_FOOTPRINT_POSITION::CreateFiles()
|
|||
fn.SetExt( FootprintPlaceFileExtension );
|
||||
|
||||
fpcount = m_parent->DoGenFootprintsPositionFile( fn.GetFullPath(), UnitsMM(),
|
||||
ForceAllSmd(), side, useCSVfmt );
|
||||
ForceAllSmd(), top_side, bottom_side, useCSVfmt );
|
||||
|
||||
if( fpcount < 0 )
|
||||
{
|
||||
msg.Printf( _( "Unable to create file \"%s\"." ), GetChars( fn.GetFullPath() ) );
|
||||
msg.Printf( _( "Unable to create file \"%s\"." ), fn.GetFullPath() );
|
||||
m_reporter->Report( msg, REPORTER::RPT_ERROR );
|
||||
wxMessageBox( msg );
|
||||
return false;
|
||||
|
@ -347,7 +329,7 @@ bool DIALOG_GEN_FOOTPRINT_POSITION::CreateFiles()
|
|||
// Display results
|
||||
if( !singleFile )
|
||||
{
|
||||
msg.Printf( _( "Back side (bottom side) place file: \"%s\"." ), GetChars( fn.GetFullPath() ) );
|
||||
msg.Printf( _( "Back side (bottom side) place file: \"%s\"." ), fn.GetFullPath() );
|
||||
m_reporter->Report( msg, REPORTER::RPT_INFO );
|
||||
|
||||
msg.Printf( _( "Component count: %d." ), fpcount );
|
||||
|
@ -367,249 +349,48 @@ bool DIALOG_GEN_FOOTPRINT_POSITION::CreateFiles()
|
|||
return true;
|
||||
}
|
||||
|
||||
// Defined values to write coordinates using inches or mm:
|
||||
static const double conv_unit_inch = 0.001 / IU_PER_MILS ; // units = INCHES
|
||||
static const char unit_text_inch[] = "## Unit = inches, Angle = deg.\n";
|
||||
|
||||
static const double conv_unit_mm = 1.0 / IU_PER_MM; // units = mm
|
||||
static const char unit_text_mm[] = "## Unit = mm, Angle = deg.\n";
|
||||
|
||||
static wxPoint File_Place_Offset; // Offset coordinates for generated file.
|
||||
|
||||
|
||||
// Sort function use by GenereModulesPosition()
|
||||
// sort is made by side (layer) top layer first
|
||||
// then by reference increasing order
|
||||
static bool sortFPlist( const LIST_MOD& ref, const LIST_MOD& tst )
|
||||
{
|
||||
if( ref.m_Layer == tst.m_Layer )
|
||||
return StrNumCmp( ref.m_Reference, tst.m_Reference ) < 0;
|
||||
|
||||
return ref.m_Layer > tst.m_Layer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function HasNonSMDPins
|
||||
* returns true if the given module has any non smd pins, such as through hole
|
||||
* and therefore cannot be placed automatically.
|
||||
*/
|
||||
static bool HasNonSMDPins( MODULE* aModule )
|
||||
{
|
||||
for( auto pad : aModule->Pads() )
|
||||
{
|
||||
if( pad->GetAttribute() != PAD_ATTRIB_SMD )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int PCB_EDITOR_CONTROL::GeneratePosFile( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
PCB_EDIT_FRAME* editFrame = getEditFrame<PCB_EDIT_FRAME>();
|
||||
DIALOG_GEN_FOOTPRINT_POSITION dlg( editFrame );
|
||||
|
||||
|
||||
dlg.ShowModal();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a footprint position file
|
||||
* aSide = 0 -> Back (bottom) side)
|
||||
* aSide = 1 -> Front (top) side)
|
||||
* aSide = 2 -> both sides
|
||||
* if aFullFileName is empty, the file is not created, only the
|
||||
* count of footprints to place is returned
|
||||
*/
|
||||
|
||||
int PCB_EDIT_FRAME::DoGenFootprintsPositionFile( const wxString& aFullFileName, bool aUnitsMM,
|
||||
bool aForceSmdItems, int aSide, bool aFormatCSV )
|
||||
bool aForceSmdItems, bool aTopSide, bool BottomSide, bool aFormatCSV )
|
||||
{
|
||||
// Minimal text lengths:
|
||||
int lenRefText = 8;
|
||||
int lenValText = 8;
|
||||
int lenPkgText = 16;
|
||||
FILE * file = NULL;
|
||||
|
||||
File_Place_Offset = GetAuxOrigin();
|
||||
|
||||
// Calculating the number of useful footprints (CMS attribute, not VIRTUAL)
|
||||
int footprintCount = 0;
|
||||
|
||||
// Select units:
|
||||
double conv_unit = aUnitsMM ? conv_unit_mm : conv_unit_inch;
|
||||
const char *unit_text = aUnitsMM ? unit_text_mm : unit_text_inch;
|
||||
|
||||
// Build and sort the list of footprints alphabetically
|
||||
std::vector<LIST_MOD> list;
|
||||
list.reserve( footprintCount );
|
||||
|
||||
for( auto footprint : GetBoard()->Modules() )
|
||||
if( !aFullFileName.IsEmpty() )
|
||||
{
|
||||
if( aSide != PCB_BOTH_SIDES )
|
||||
{
|
||||
if( footprint->GetLayer() == B_Cu && aSide == PCB_FRONT_SIDE)
|
||||
continue;
|
||||
if( footprint->GetLayer() == F_Cu && aSide == PCB_BACK_SIDE)
|
||||
continue;
|
||||
}
|
||||
file = wxFopen( aFullFileName, wxT( "wt" ) );
|
||||
|
||||
if( footprint->GetAttributes() & MOD_VIRTUAL )
|
||||
{
|
||||
DBG( printf( "skipping footprint %s because it's virtual\n",
|
||||
TO_UTF8( footprint->GetReference() ) );)
|
||||
continue;
|
||||
}
|
||||
|
||||
if( ( footprint->GetAttributes() & MOD_CMS ) == 0 )
|
||||
{
|
||||
if( aForceSmdItems ) // true to fix a bunch of mis-labeled footprints:
|
||||
{
|
||||
if( !HasNonSMDPins( footprint ) )
|
||||
{
|
||||
// all footprint's pins are SMD, mark the part for pick and place
|
||||
footprint->SetAttributes( footprint->GetAttributes() | MOD_CMS );
|
||||
OnModify();
|
||||
}
|
||||
else
|
||||
{
|
||||
DBG( printf( "skipping %s because it is not marked CMS and has non-SMD pins\n",
|
||||
TO_UTF8( footprint->GetReference() ) ) );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
footprintCount++;
|
||||
|
||||
LIST_MOD item;
|
||||
item.m_Module = footprint;
|
||||
item.m_Reference = footprint->GetReference();
|
||||
item.m_Value = footprint->GetValue();
|
||||
item.m_Layer = footprint->GetLayer();
|
||||
list.push_back( item );
|
||||
|
||||
lenRefText = std::max( lenRefText, int(item.m_Reference.length()) );
|
||||
lenValText = std::max( lenValText, int(item.m_Value.length()) );
|
||||
lenPkgText = std::max( lenPkgText, int(item.m_Module->GetFPID().GetLibItemName().length()) );
|
||||
if( file == NULL )
|
||||
return -1;
|
||||
}
|
||||
|
||||
if( aFullFileName.IsEmpty() )
|
||||
return footprintCount;
|
||||
std::string data;
|
||||
PLACE_FILE_EXPORTER exporter( GetBoard(), aUnitsMM, aForceSmdItems,
|
||||
aTopSide, BottomSide, aFormatCSV );
|
||||
data = exporter.GenPositionData();
|
||||
|
||||
FILE * file = wxFopen( aFullFileName, wxT( "wt" ) );
|
||||
|
||||
if( file == NULL )
|
||||
return -1;
|
||||
|
||||
if( list.size() > 1 )
|
||||
sort( list.begin(), list.end(), sortFPlist );
|
||||
|
||||
// Switch the locale to standard C (needed to print floating point numbers)
|
||||
LOCALE_IO toggle;
|
||||
|
||||
if( aFormatCSV )
|
||||
// if aFullFileName is empty, the file is not created, only the
|
||||
// count of footprints to place is returned
|
||||
if( file )
|
||||
{
|
||||
wxChar csv_sep = ',';
|
||||
|
||||
// Set first line:;
|
||||
fprintf( file, "Ref%cVal%cPackage%cPosX%cPosY%cRot%cSide\n",
|
||||
csv_sep, csv_sep, csv_sep, csv_sep, csv_sep, csv_sep );
|
||||
|
||||
for( int ii = 0; ii < footprintCount; ii++ )
|
||||
{
|
||||
wxPoint footprint_pos;
|
||||
footprint_pos = list[ii].m_Module->GetPosition();
|
||||
footprint_pos -= File_Place_Offset;
|
||||
|
||||
LAYER_NUM layer = list[ii].m_Module->GetLayer();
|
||||
wxASSERT( layer == F_Cu || layer == B_Cu );
|
||||
|
||||
wxString line = "\"" + list[ii].m_Reference;
|
||||
line << "\"" << csv_sep;
|
||||
line << "\"" << list[ii].m_Value;
|
||||
line << "\"" << csv_sep;
|
||||
line << "\"" << list[ii].m_Module->GetFPID().GetLibItemName().wx_str();
|
||||
line << "\"" << csv_sep;
|
||||
|
||||
line << wxString::Format( "%f%c%f%c%f",
|
||||
footprint_pos.x * conv_unit, csv_sep,
|
||||
// Keep the Y axis oriented from bottom to top,
|
||||
// ( change y coordinate sign )
|
||||
-footprint_pos.y * conv_unit, csv_sep,
|
||||
list[ii].m_Module->GetOrientation() / 10.0 );
|
||||
line << csv_sep;
|
||||
|
||||
line << ( (layer == F_Cu ) ? frontSideName : backSideName );
|
||||
line << '\n';
|
||||
|
||||
fputs( TO_UTF8( line ), file );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write file header
|
||||
fprintf( file, "### Module positions - created on %s ###\n", TO_UTF8( DateAndTime() ) );
|
||||
|
||||
wxString Title = Pgm().App().GetAppName() + wxT( " " ) + GetBuildVersion();
|
||||
fprintf( file, "### Printed by Pcbnew version %s\n", TO_UTF8( Title ) );
|
||||
|
||||
fputs( unit_text, file );
|
||||
|
||||
fputs( "## Side : ", file );
|
||||
|
||||
if( aSide == PCB_BACK_SIDE )
|
||||
fputs( TO_UTF8( backSideName ), file );
|
||||
else if( aSide == PCB_FRONT_SIDE )
|
||||
fputs( TO_UTF8( frontSideName ), file );
|
||||
else
|
||||
fputs( "All", file );
|
||||
|
||||
fputs( "\n", file );
|
||||
|
||||
fprintf(file, "%-*s %-*s %-*s %9.9s %9.9s %8.8s %s\n",
|
||||
int(lenRefText), "# Ref",
|
||||
int(lenValText), "Val",
|
||||
int(lenPkgText), "Package",
|
||||
"PosX", "PosY", "Rot", "Side" );
|
||||
|
||||
for( int ii = 0; ii < footprintCount; ii++ )
|
||||
{
|
||||
wxPoint footprint_pos;
|
||||
footprint_pos = list[ii].m_Module->GetPosition();
|
||||
footprint_pos -= File_Place_Offset;
|
||||
|
||||
LAYER_NUM layer = list[ii].m_Module->GetLayer();
|
||||
wxASSERT( layer == F_Cu || layer == B_Cu );
|
||||
|
||||
if( layer == B_Cu )
|
||||
footprint_pos.x = - footprint_pos.x;
|
||||
|
||||
wxString ref = list[ii].m_Reference;
|
||||
wxString val = list[ii].m_Value;
|
||||
wxString pkg = list[ii].m_Module->GetFPID().GetLibItemName();
|
||||
ref.Replace( wxT( " " ), wxT( "_" ) );
|
||||
val.Replace( wxT( " " ), wxT( "_" ) );
|
||||
pkg.Replace( wxT( " " ), wxT( "_" ) );
|
||||
fprintf(file, "%-*s %-*s %-*s %9.4f %9.4f %8.4f %s\n",
|
||||
lenRefText, TO_UTF8( ref ),
|
||||
lenValText, TO_UTF8( val ),
|
||||
lenPkgText, TO_UTF8( pkg ),
|
||||
footprint_pos.x * conv_unit,
|
||||
// Keep the coordinates in the first quadrant,
|
||||
// (i.e. change y sign
|
||||
-footprint_pos.y * conv_unit,
|
||||
list[ii].m_Module->GetOrientation() / 10.0,
|
||||
(layer == F_Cu ) ? TO_UTF8( frontSideName ) : TO_UTF8( backSideName ));
|
||||
}
|
||||
|
||||
// Write EOF
|
||||
fputs( "## End\n", file );
|
||||
// Creates a footprint position file
|
||||
// aSide = 0 -> Back (bottom) side)
|
||||
// aSide = 1 -> Front (top) side)
|
||||
// aSide = 2 -> both sides
|
||||
fputs( data.c_str(), file );
|
||||
fclose( file );
|
||||
}
|
||||
|
||||
fclose( file );
|
||||
return footprintCount;
|
||||
return exporter.GetFootprintCount();
|
||||
}
|
||||
|
||||
|
||||
|
@ -633,14 +414,13 @@ void PCB_EDIT_FRAME::GenFootprintsReport( wxCommandEvent& event )
|
|||
wxString msg;
|
||||
if( success )
|
||||
{
|
||||
msg.Printf( _( "Footprint report file created:\n\"%s\"" ),
|
||||
GetChars( fn.GetFullPath() ) );
|
||||
msg.Printf( _( "Footprint report file created:\n\"%s\"" ), fn.GetFullPath() );
|
||||
wxMessageBox( msg, _( "Footprint Report" ), wxICON_INFORMATION );
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
msg.Printf( _( "Unable to create \"%s\"" ), GetChars( fn.GetFullPath() ) );
|
||||
msg.Printf( _( "Unable to create \"%s\"" ), fn.GetFullPath() );
|
||||
DisplayError( this, msg );
|
||||
}
|
||||
}
|
||||
|
@ -653,114 +433,16 @@ bool PCB_EDIT_FRAME::DoGenFootprintsReport( const wxString& aFullFilename, bool
|
|||
FILE* rptfile;
|
||||
wxPoint module_pos;
|
||||
|
||||
File_Place_Offset = wxPoint( 0, 0 );
|
||||
|
||||
rptfile = wxFopen( aFullFilename, wxT( "wt" ) );
|
||||
|
||||
if( rptfile == NULL )
|
||||
return false;
|
||||
|
||||
// Select units:
|
||||
double conv_unit = aUnitsMM ? conv_unit_mm : conv_unit_inch;
|
||||
const char *unit_text = aUnitsMM ? unit_text_mm : unit_text_inch;
|
||||
std::string data;
|
||||
PLACE_FILE_EXPORTER exporter ( GetBoard(), aUnitsMM, false, true, true, false );
|
||||
data = exporter.GenReportData();
|
||||
|
||||
LOCALE_IO toggle;
|
||||
|
||||
// Generate header file comments.)
|
||||
fprintf( rptfile, "## Footprint report - date %s\n", TO_UTF8( DateAndTime() ) );
|
||||
|
||||
wxString Title = Pgm().App().GetAppName() + wxT( " " ) + GetBuildVersion();
|
||||
fprintf( rptfile, "## Created by Pcbnew version %s\n", TO_UTF8( Title ) );
|
||||
fputs( unit_text, rptfile );
|
||||
|
||||
fputs( "\n$BeginDESCRIPTION\n", rptfile );
|
||||
|
||||
EDA_RECT bbbox = GetBoard()->ComputeBoundingBox();
|
||||
|
||||
fputs( "\n$BOARD\n", rptfile );
|
||||
|
||||
fprintf( rptfile, "upper_left_corner %9.6f %9.6f\n",
|
||||
bbbox.GetX() * conv_unit,
|
||||
bbbox.GetY() * conv_unit );
|
||||
|
||||
fprintf( rptfile, "lower_right_corner %9.6f %9.6f\n",
|
||||
bbbox.GetRight() * conv_unit,
|
||||
bbbox.GetBottom() * conv_unit );
|
||||
|
||||
fputs( "$EndBOARD\n\n", rptfile );
|
||||
|
||||
for( auto Module : GetBoard()->Modules() )
|
||||
{
|
||||
fprintf( rptfile, "$MODULE %s\n", EscapedUTF8( Module->GetReference() ).c_str() );
|
||||
|
||||
fprintf( rptfile, "reference %s\n", EscapedUTF8( Module->GetReference() ).c_str() );
|
||||
fprintf( rptfile, "value %s\n", EscapedUTF8( Module->GetValue() ).c_str() );
|
||||
fprintf( rptfile, "footprint %s\n",
|
||||
EscapedUTF8( FROM_UTF8( Module->GetFPID().Format().c_str() ) ).c_str() );
|
||||
|
||||
msg = wxT( "attribut" );
|
||||
|
||||
if( Module->GetAttributes() & MOD_VIRTUAL )
|
||||
msg += wxT( " virtual" );
|
||||
|
||||
if( Module->GetAttributes() & MOD_CMS )
|
||||
msg += wxT( " smd" );
|
||||
|
||||
if( ( Module->GetAttributes() & (MOD_VIRTUAL | MOD_CMS) ) == 0 )
|
||||
msg += wxT( " none" );
|
||||
|
||||
msg += wxT( "\n" );
|
||||
fputs( TO_UTF8( msg ), rptfile );
|
||||
|
||||
module_pos = Module->GetPosition();
|
||||
module_pos.x -= File_Place_Offset.x;
|
||||
module_pos.y -= File_Place_Offset.y;
|
||||
|
||||
fprintf( rptfile, "position %9.6f %9.6f orientation %.2f\n",
|
||||
module_pos.x * conv_unit,
|
||||
module_pos.y * conv_unit,
|
||||
Module->GetOrientation() / 10.0 );
|
||||
|
||||
if( Module->GetLayer() == F_Cu )
|
||||
fputs( "layer front\n", rptfile );
|
||||
else if( Module->GetLayer() == B_Cu )
|
||||
fputs( "layer back\n", rptfile );
|
||||
else
|
||||
fputs( "layer other\n", rptfile );
|
||||
|
||||
for( auto pad : Module->Pads() )
|
||||
{
|
||||
fprintf( rptfile, "$PAD \"%s\"\n", TO_UTF8( pad->GetName() ) );
|
||||
int layer = 0;
|
||||
|
||||
if( pad->GetLayerSet()[B_Cu] )
|
||||
layer = 1;
|
||||
|
||||
if( pad->GetLayerSet()[F_Cu] )
|
||||
layer |= 2;
|
||||
|
||||
static const char* layer_name[4] = { "nocopper", "back", "front", "both" };
|
||||
fprintf( rptfile, "Shape %s Layer %s\n", TO_UTF8( pad->ShowPadShape() ), layer_name[layer] );
|
||||
|
||||
fprintf( rptfile, "position %9.6f %9.6f size %9.6f %9.6f orientation %.2f\n",
|
||||
pad->GetPos0().x * conv_unit, pad->GetPos0().y * conv_unit,
|
||||
pad->GetSize().x * conv_unit, pad->GetSize().y * conv_unit,
|
||||
(pad->GetOrientation() - Module->GetOrientation()) / 10.0 );
|
||||
|
||||
fprintf( rptfile, "drill %9.6f\n", pad->GetDrillSize().x * conv_unit );
|
||||
|
||||
fprintf( rptfile, "shape_offset %9.6f %9.6f\n",
|
||||
pad->GetOffset().x * conv_unit,
|
||||
pad->GetOffset().y * conv_unit );
|
||||
|
||||
fprintf( rptfile, "$EndPAD\n" );
|
||||
}
|
||||
|
||||
fprintf( rptfile, "$EndMODULE %s\n\n", TO_UTF8 (Module->GetReference() ) );
|
||||
}
|
||||
|
||||
// Generate EOF.
|
||||
fputs( "$EndDESCRIPTION\n", rptfile );
|
||||
fputs( data.c_str(), rptfile );
|
||||
fclose( rptfile );
|
||||
|
||||
return true;
|
||||
|
|
|
@ -564,15 +564,15 @@ public:
|
|||
* @param aUnitsMM = false to use inches, true to use mm in coordinates
|
||||
* @param aForceSmdItems = true to force all footprints with smd pads in list
|
||||
* = false to put only footprints with option "INSERT" in list
|
||||
* @param aSide = 0 to list footprints on BACK side,
|
||||
* 1 to list footprints on FRONT side
|
||||
* 2 to list footprints on both sides
|
||||
* @param aTopSide true to list footprints on front (top) side,
|
||||
* @param BottomSide true to list footprints on back (bottom) side,
|
||||
* if aTopSide and aTopSide are true, list footprints on both sides
|
||||
* @param aFormatCSV = true to use a comma separated file (CSV) format; defautl = false
|
||||
* @return the number of footprints found on aSide side,
|
||||
* or -1 if the file could not be created
|
||||
*/
|
||||
int DoGenFootprintsPositionFile( const wxString& aFullFileName, bool aUnitsMM,
|
||||
bool aForceSmdItems, int aSide, bool aFormatCSV = false );
|
||||
bool aForceSmdItems, bool aTopSide, bool BottomSide, bool aFormatCSV = false );
|
||||
|
||||
/**
|
||||
* Function GenFootprintsReport
|
||||
|
@ -1008,7 +1008,7 @@ public:
|
|||
void UpdateTitle();
|
||||
|
||||
void On3DShapeLibWizard( wxCommandEvent& event );
|
||||
|
||||
|
||||
/**
|
||||
* Allows Pcbnew to install its preferences panel into the preferences dialog.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue