From 36253450e72ee1a4852127320f622851219db9ff Mon Sep 17 00:00:00 2001 From: jean-pierre charras Date: Sun, 15 Sep 2019 17:08:57 +0200 Subject: [PATCH] Pcbnew: Add experimental place file (P&P) export in Gerber format. --- common/gbr_metadata.cpp | 87 +++++- include/gbr_metadata.h | 12 +- include/gbr_netlist_metadata.h | 55 +++- pcbnew/CMakeLists.txt | 1 + ...ialog_gen_footprint_position_file_base.cpp | 22 +- ...ialog_gen_footprint_position_file_base.fbp | 21 +- .../dialog_gen_footprint_position_file_base.h | 7 +- pcbnew/exporters/gen_footprints_placefile.cpp | 109 ++++++- pcbnew/exporters/gerber_placefile_writer.cpp | 281 ++++++++++++++++++ pcbnew/exporters/gerber_placefile_writer.h | 102 +++++++ 10 files changed, 669 insertions(+), 28 deletions(-) create mode 100644 pcbnew/exporters/gerber_placefile_writer.cpp create mode 100644 pcbnew/exporters/gerber_placefile_writer.h diff --git a/common/gbr_metadata.cpp b/common/gbr_metadata.cpp index 34e0da3683..94cc3619da 100644 --- a/common/gbr_metadata.cpp +++ b/common/gbr_metadata.cpp @@ -245,9 +245,42 @@ std::string GBR_APERTURE_METADATA::FormatAttribute( GBR_APERTURE_ATTRIB aAttribu attribute_string = "TA.AperFunction,ComponentMain"; break; - case GBR_APERTURE_ATTRIB_CMP_COURTYARD: // print info associated to a component - // print the component courtyard polygon + case GBR_APERTURE_ATTRIB_PAD1_POSITION: // print info associated to a component + // flashed shape at pad 1 position + // (pad 1 is also pad A1 or pad AA1) // in placement files + attribute_string = "TA.AperFunction,ComponentPin"; + break; + + case GBR_APERTURE_ATTRIB_PADOTHER_POSITION: // print info associated to a component + // flashed shape at pads position (all but pad 1) + // in placement files + // Currently: (could be changed later) same as + // GBR_APERTURE_ATTRIB_PADOTHER_POSITION + attribute_string = "TA.AperFunction,ComponentPin"; + break; + + case GBR_APERTURE_ATTRIB_CMP_BODY: // print info associated to a component + // print the component physical body + // polygon in placement files + attribute_string = "TA.AperFunction,ComponentOutline,Body"; + break; + + case GBR_APERTURE_ATTRIB_CMP_LEAD2LEAD: // print info associated to a component + // print the component physical lead to lead + // polygon in placement files + attribute_string = "TA.AperFunction,ComponentOutline,Lead2Lead"; + break; + + case GBR_APERTURE_ATTRIB_CMP_FOOTPRINT: // print info associated to a component + // print the component footprint bounding box + // polygon in placement files + attribute_string = "TA.AperFunction,ComponentOutline,Footprint"; + break; + + case GBR_APERTURE_ATTRIB_CMP_COURTYARD: // print info associated to a component + // print the component courtyard + // polygon in placement files attribute_string = "TA.AperFunction,ComponentOutline,Courtyard"; break; @@ -512,3 +545,53 @@ bool FormatNetAttribute( std::string& aPrintedText, std::string& aLastNetAttribu return true; } + + +/************ class GBR_CMP_PNP_METADATA *************/ + +void GBR_CMP_PNP_METADATA::ClearData() +{ + // Clear all strings + m_Orientation = 0.0; + m_Manufacturer.Clear(); + m_MPN.Clear(); + m_Package.Clear(); + m_Value.Clear(); + m_MountType = MOUNT_TYPE_UNSPECIFIED; +} +/** + * @return a string containing the formated metadata in X2 syntax. + * one line by non empty data + * the orientation is always generated + */ +wxString GBR_CMP_PNP_METADATA::FormatCmpPnPMetadata() +{ + wxString text; + wxString start_of_line( "%TO."); + wxString end_of_line( "*%\n" ); + + wxString mounType[] = + { + "Other", "SMD", "BGA", "TH" + }; + + if( !m_Manufacturer.IsEmpty() ) + text << start_of_line << "CMfr," << m_Manufacturer << end_of_line; + + if( !m_MPN.IsEmpty() ) + text << start_of_line << "CMPN," << m_MPN << end_of_line; + + if( !m_Package.IsEmpty() ) + text << start_of_line << "Cpkg," << m_Package << end_of_line; + + if( !m_Footprint.IsEmpty() ) + text << start_of_line << "CFtp," << m_Footprint << end_of_line; + + if( !m_Value.IsEmpty() ) + text << start_of_line << "CVal," << m_Value << end_of_line; + + text << start_of_line << "CMnt," << mounType[m_MountType] << end_of_line; + text << start_of_line << "CRot," << m_Orientation << end_of_line; + + return text; +} diff --git a/include/gbr_metadata.h b/include/gbr_metadata.h index 39f0b02dda..ea78633ce4 100644 --- a/include/gbr_metadata.h +++ b/include/gbr_metadata.h @@ -94,9 +94,17 @@ public: GBR_APERTURE_ATTRIB_WASHERPAD, ///< aperture used for mechanical pads (NPTH) GBR_APERTURE_ATTRIB_HEATSINKPAD, ///< aperture used for heat sink pad (typically for SMDs) GBR_APERTURE_ATTRIB_VIADRILL, ///< aperture used for via holes in drill files - GBR_APERTURE_ATTRIB_CMP_DRILL, ///< aperture used for pad holes in drill files + GBR_APERTURE_ATTRIB_CMP_DRILL, ///< aperture used for pad holes in drill files GBR_APERTURE_ATTRIB_CMP_OBLONG_DRILL, ///< aperture used for pads oblong holes in drill files - GBR_APERTURE_ATTRIB_CMP_POSITION, ///< aperture used for flashed shape in placement files + GBR_APERTURE_ATTRIB_CMP_POSITION, ///< aperture used for flashed cmp position in placement files + GBR_APERTURE_ATTRIB_PAD1_POSITION, ///< aperture used for flashed pin 1 (or A1 or AA1) position in placement files + GBR_APERTURE_ATTRIB_PADOTHER_POSITION, ///< aperture used for flashed pads position in placement files + GBR_APERTURE_ATTRIB_CMP_BODY, ///< aperture used to draw component physical body outline + ///< (without pins) in placement files + GBR_APERTURE_ATTRIB_CMP_LEAD2LEAD, ///< aperture used to draw component physical body outline + ///< (with pins) in placement files + GBR_APERTURE_ATTRIB_CMP_FOOTPRINT, ///< aperture used to draw component footprint bounding box + ///< in placement files GBR_APERTURE_ATTRIB_CMP_COURTYARD, ///< aperture used to draw component outline courtyard ///< in placement files GBR_APERTURE_ATTRIB_END ///< sentinel: max value diff --git a/include/gbr_netlist_metadata.h b/include/gbr_netlist_metadata.h index ade6d412e5..ec29e80e93 100644 --- a/include/gbr_netlist_metadata.h +++ b/include/gbr_netlist_metadata.h @@ -25,9 +25,62 @@ #ifndef GBR_NETLIST_METADATA_H #define GBR_NETLIST_METADATA_H +/** this class handle info which can be added in a gerber P&P file as attribute + * of a component + * Only applicable to objects having the TA.AperFunction attribute "ComponentMain" + * There are specific attributes defined attached to the component by the %TO command + * %TO.CRot, The rotation angle of the component. + * The rotation angle is consistent with the one for graphics objects. + * Positive rotation is counter- clockwise as viewed from the top side, even if + * the component is on the board side. + * The base orientation of component – no rotation - on the top side is as in IPC-7351. + * Components on the bottom side are of course mirrored. + * The base orientation on the bottom side is the one on the top side + * mirrored around the X axis. + * %TO.CMfr, Manufacturer + * %TO.CMPN, Manufacturer part number + * %TO.Cpkg, Package, as per IPC-7351 + * %TO.CFtp, Footprint name, a string. E.g. LQFP-100_14x14mm_P0.5mm + This is the footprint name coming from the CAD tool libraries. + * %TO.CVal, Value, a string. E.g. 220nF + * %TO.CMnt, Mount type: (SMD|TH|Other) + * %TO.CHgt, Height, a decimal, in the unit of the file. + */ +class GBR_CMP_PNP_METADATA +{ +public: + enum MOUNT_TYPE + { + MOUNT_TYPE_UNSPECIFIED, + MOUNT_TYPE_SMD, + MOUNT_TYPE_BGA, + MOUNT_TYPE_TH + }; + + double m_Orientation; // orientation in degree + wxString m_Manufacturer; // Manufacturer name + wxString m_MPN; // Manufacturer part number + wxString m_Package; // Package, as per IPC-7351 + wxString m_Footprint; // Footprint name, from library + wxString m_Value; // Component value + MOUNT_TYPE m_MountType; // SMD|TH|Other + + GBR_CMP_PNP_METADATA() : + m_Orientation( 0.0 ), m_MountType( MOUNT_TYPE_UNSPECIFIED ) + {} + + void ClearData(); // Clear all strings + /** + * @return a string containing the formated metadata in X2 syntax. + * one line by non empty data + * the orientation is always generated + */ + wxString FormatCmpPnPMetadata(); +}; + /** this class handle info which can be added in a gerber file as attribute - * of an obtect + * of an object * the GBR_INFO_TYPE types can be OR'ed to add 2 (or more) attributes * There are only 3 net attributes defined attached to an object by the %TO command * %TO.P diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 3974935bf4..5b70d8a421 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -219,6 +219,7 @@ set( PCBNEW_EXPORTERS exporters/gendrill_file_writer_base.cpp exporters/gendrill_gerber_writer.cpp exporters/gerber_jobfile_writer.cpp + exporters/gerber_placefile_writer.cpp ) set( PCBNEW_MICROWAVE_SRCS diff --git a/pcbnew/dialogs/dialog_gen_footprint_position_file_base.cpp b/pcbnew/dialogs/dialog_gen_footprint_position_file_base.cpp index e039968e2c..5e30b5c3a6 100644 --- a/pcbnew/dialogs/dialog_gen_footprint_position_file_base.cpp +++ b/pcbnew/dialogs/dialog_gen_footprint_position_file_base.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Dec 1 2018) +// C++ code generated with wxFormBuilder (version Jul 10 2019) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -47,21 +47,17 @@ DIALOG_GEN_FOOTPRINT_POSITION_BASE::DIALOG_GEN_FOOTPRINT_POSITION_BASE( wxWindow wxBoxSizer* bSizerMiddle; bSizerMiddle = new wxBoxSizer( wxHORIZONTAL ); - wxString m_rbFormatChoices[] = { _("ASCII"), _("CSV") }; + wxString m_rbFormatChoices[] = { _("ASCII"), _("CSV"), _("Gerber (very experimental)") }; int m_rbFormatNChoices = sizeof( m_rbFormatChoices ) / sizeof( wxString ); m_rbFormat = new wxRadioBox( this, wxID_ANY, _("Format"), wxDefaultPosition, wxDefaultSize, m_rbFormatNChoices, m_rbFormatChoices, 1, wxRA_SPECIFY_COLS ); m_rbFormat->SetSelection( 0 ); - m_rbFormat->SetMinSize( wxSize( 90,-1 ) ); - - bSizerMiddle->Add( m_rbFormat, 1, wxALL, 5 ); + bSizerMiddle->Add( m_rbFormat, 0, wxALL, 5 ); wxString m_radioBoxUnitsChoices[] = { _("Inches"), _("Millimeters") }; int m_radioBoxUnitsNChoices = sizeof( m_radioBoxUnitsChoices ) / sizeof( wxString ); m_radioBoxUnits = new wxRadioBox( this, wxID_ANY, _("Units"), wxDefaultPosition, wxDefaultSize, m_radioBoxUnitsNChoices, m_radioBoxUnitsChoices, 1, wxRA_SPECIFY_COLS ); m_radioBoxUnits->SetSelection( 0 ); - m_radioBoxUnits->SetMinSize( wxSize( 90,-1 ) ); - - bSizerMiddle->Add( m_radioBoxUnits, 1, wxALL, 5 ); + bSizerMiddle->Add( m_radioBoxUnits, 0, wxALL, 5 ); wxString m_radioBoxFilesCountChoices[] = { _("Separate files for front and back"), _("Single file for board") }; int m_radioBoxFilesCountNChoices = sizeof( m_radioBoxFilesCountChoices ) / sizeof( wxString ); @@ -69,10 +65,10 @@ 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") ); - bSizerMiddle->Add( m_radioBoxFilesCount, 2, wxALL, 5 ); + bSizerMiddle->Add( m_radioBoxFilesCount, 0, wxALL, 5 ); - m_MainSizer->Add( bSizerMiddle, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); + m_MainSizer->Add( bSizerMiddle, 0, wxTOP|wxRIGHT|wxLEFT|wxEXPAND, 5 ); wxBoxSizer* bSizerLower; bSizerLower = new wxBoxSizer( wxVERTICAL ); @@ -105,6 +101,9 @@ DIALOG_GEN_FOOTPRINT_POSITION_BASE::DIALOG_GEN_FOOTPRINT_POSITION_BASE( wxWindow // Connect Events m_browseButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::OnOutputDirectoryBrowseClicked ), NULL, this ); + m_rbFormat->Connect( wxEVT_COMMAND_RADIOBOX_SELECTED, wxCommandEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::onSelectFormat ), NULL, this ); + m_radioBoxUnits->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::onUpdateUIUnits ), NULL, this ); + m_radioBoxFilesCount->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::onUpdateUIFileOpt ), NULL, this ); m_sdbSizerOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::OnGenerate ), NULL, this ); } @@ -112,6 +111,9 @@ 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_rbFormat->Disconnect( wxEVT_COMMAND_RADIOBOX_SELECTED, wxCommandEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::onSelectFormat ), NULL, this ); + m_radioBoxUnits->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::onUpdateUIUnits ), NULL, this ); + m_radioBoxFilesCount->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::onUpdateUIFileOpt ), NULL, this ); m_sdbSizerOK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_GEN_FOOTPRINT_POSITION_BASE::OnGenerate ), NULL, this ); } diff --git a/pcbnew/dialogs/dialog_gen_footprint_position_file_base.fbp b/pcbnew/dialogs/dialog_gen_footprint_position_file_base.fbp index 46b370b3da..07906a848b 100644 --- a/pcbnew/dialogs/dialog_gen_footprint_position_file_base.fbp +++ b/pcbnew/dialogs/dialog_gen_footprint_position_file_base.fbp @@ -14,6 +14,7 @@ dialog_gen_footprint_position_file_base 1000 none + 1 dialog_gen_footprint_positions_base @@ -25,6 +26,7 @@ 1 1 UI + 0 1 0 @@ -45,7 +47,7 @@ -1,-1 DIALOG_GEN_FOOTPRINT_POSITION_BASE - 506,476 + 520,450 wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER DIALOG_SHIM; dialog_shim.h Generate Footprint Position Files @@ -280,7 +282,7 @@ 5 - wxEXPAND|wxTOP|wxRIGHT|wxLEFT + wxTOP|wxRIGHT|wxLEFT|wxEXPAND 0 @@ -290,7 +292,7 @@ 5 wxALL - 1 + 0 1 1 @@ -305,7 +307,7 @@ 1 0 - "ASCII" "CSV" + "ASCII" "CSV" "Gerber (very experimental)" 1 1 @@ -327,7 +329,7 @@ 0 - 90,-1 + -1,-1 1 m_rbFormat 1 @@ -351,12 +353,13 @@ + onSelectFormat 5 wxALL - 1 + 0 1 1 @@ -393,7 +396,7 @@ 0 - 90,-1 + -1,-1 1 m_radioBoxUnits 1 @@ -417,12 +420,13 @@ + onUpdateUIUnits 5 wxALL - 2 + 0 1 1 @@ -483,6 +487,7 @@ + onUpdateUIFileOpt diff --git a/pcbnew/dialogs/dialog_gen_footprint_position_file_base.h b/pcbnew/dialogs/dialog_gen_footprint_position_file_base.h index 1ff55d8efb..a4f203bd2a 100644 --- a/pcbnew/dialogs/dialog_gen_footprint_position_file_base.h +++ b/pcbnew/dialogs/dialog_gen_footprint_position_file_base.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Dec 1 2018) +// C++ code generated with wxFormBuilder (version Jul 10 2019) // http://www.wxformbuilder.org/ // // PLEASE DO *NOT* EDIT THIS FILE! @@ -56,12 +56,15 @@ class DIALOG_GEN_FOOTPRINT_POSITION_BASE : public DIALOG_SHIM // Virtual event handlers, overide them in your derived class virtual void OnOutputDirectoryBrowseClicked( wxCommandEvent& event ) { event.Skip(); } + virtual void onSelectFormat( wxCommandEvent& event ) { event.Skip(); } + virtual void onUpdateUIUnits( wxUpdateUIEvent& event ) { event.Skip(); } + virtual void onUpdateUIFileOpt( wxUpdateUIEvent& event ) { event.Skip(); } virtual void OnGenerate( wxCommandEvent& event ) { event.Skip(); } 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( 506,476 ), 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( 520,450 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); ~DIALOG_GEN_FOOTPRINT_POSITION_BASE(); }; diff --git a/pcbnew/exporters/gen_footprints_placefile.cpp b/pcbnew/exporters/gen_footprints_placefile.cpp index c5bcd66463..e7de07836e 100644 --- a/pcbnew/exporters/gen_footprints_placefile.cpp +++ b/pcbnew/exporters/gen_footprints_placefile.cpp @@ -45,6 +45,7 @@ #include #include #include +#include "gerber_placefile_writer.h" #define PLACEFILE_UNITS_KEY wxT( "PlaceFileUnits" ) @@ -93,7 +94,23 @@ private: void OnOutputDirectoryBrowseClicked( wxCommandEvent& event ) override; void OnGenerate( wxCommandEvent& event ) override; - bool CreateFiles(); + void onUpdateUIUnits( wxUpdateUIEvent& event ) override + { + m_radioBoxUnits->Enable( m_rbFormat->GetSelection() != 2 ); + } + + void onUpdateUIFileOpt( wxUpdateUIEvent& event ) override + { + m_radioBoxFilesCount->Enable( m_rbFormat->GetSelection() != 2 ); + } + + /** Creates files in text or csv format + */ + bool CreateAsciiFiles(); + + /** Creates placement files in gerber format + */ + bool CreateGerberFiles(); // accessors to options: wxString GetOutputDirectory() @@ -135,6 +152,8 @@ void DIALOG_GEN_FOOTPRINT_POSITION::initDialog() // Output directory m_outputDirectoryName->SetValue( m_plotOpts.GetOutputDirectory() ); + + // Update Options m_radioBoxUnits->SetSelection( m_unitsOpt ); m_radioBoxFilesCount->SetSelection( m_fileOpt ); m_rbFormat->SetSelection( m_fileFormat ); @@ -193,11 +212,95 @@ void DIALOG_GEN_FOOTPRINT_POSITION::OnGenerate( wxCommandEvent& event ) m_plotOpts.SetOutputDirectory( dirStr ); m_parent->SetPlotSettings( m_plotOpts ); - CreateFiles(); + if( m_fileFormat == 2 ) + CreateGerberFiles(); + else + CreateAsciiFiles(); +} + +bool DIALOG_GEN_FOOTPRINT_POSITION::CreateGerberFiles() +{ + BOARD* brd = m_parent->GetBoard(); + wxFileName fn; + wxString msg; + int fullcount = 0; + + // 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(); + + m_reporter = &m_messagesPanel->Reporter(); + + if( !EnsureFileDirectoryExists( &outputDir, boardFilename, m_reporter ) ) + { + msg.Printf( _( "Could not write plot files to folder \"%s\"." ), + outputDir.GetPath() ); + DisplayError( this, msg ); + return false; + } + + fn = m_parent->GetBoard()->GetFileName(); + fn.SetPath( outputDir.GetPath() ); + + // Create the the Front or Top side placement file, or a single file + + // Test for any footprint candidate in list, and display the list of forced footprints + // if ForceAllSmd() is true + PLACEFILE_GERBER_WRITER exporter( brd ); + wxString filename = exporter.GetPlaceFileName( fn.GetFullPath(), F_Cu ); + + int fpcount = exporter.CreatePlaceFile( filename, F_Cu ); + + if( fpcount < 0 ) + { + msg.Printf( _( "Unable to create \"%s\"." ), fn.GetFullPath() ); + wxMessageBox( msg ); + m_reporter->Report( msg, REPORTER::RPT_ERROR ); + return false; + } + + msg.Printf( _( "Front side (top side) place file: \"%s\"." ), filename ); + m_reporter->Report( msg, REPORTER::RPT_INFO ); + + msg.Printf( _( "Component count: %d." ), fpcount ); + m_reporter->Report( msg, REPORTER::RPT_INFO ); + + // Create the Back or Bottom side placement file + fullcount = fpcount; + + filename = exporter.GetPlaceFileName( fn.GetFullPath(), B_Cu ); + + fpcount = exporter.CreatePlaceFile( filename, B_Cu ); + + if( fpcount < 0 ) + { + msg.Printf( _( "Unable to create file \"%s\"." ), filename ); + m_reporter->Report( msg, REPORTER::RPT_ERROR ); + wxMessageBox( msg ); + return false; + } + + // Display results + msg.Printf( _( "Back side (bottom side) place file: \"%s\"." ), filename ); + m_reporter->Report( msg, REPORTER::RPT_INFO ); + + msg.Printf( _( "Component count: %d." ), fpcount ); + + m_reporter->Report( msg, REPORTER::RPT_INFO ); + + fullcount += fpcount; + msg.Printf( _( "Full component count: %d\n" ), fullcount ); + m_reporter->Report( msg, REPORTER::RPT_INFO ); + + m_reporter->Report( _( "Component Placement File generation OK." ), REPORTER::RPT_ACTION ); + + return true; } -bool DIALOG_GEN_FOOTPRINT_POSITION::CreateFiles() +bool DIALOG_GEN_FOOTPRINT_POSITION::CreateAsciiFiles() { BOARD * brd = m_parent->GetBoard(); wxFileName fn; diff --git a/pcbnew/exporters/gerber_placefile_writer.cpp b/pcbnew/exporters/gerber_placefile_writer.cpp new file mode 100644 index 0000000000..3379547b24 --- /dev/null +++ b/pcbnew/exporters/gerber_placefile_writer.cpp @@ -0,0 +1,281 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 Jean_Pierre Charras + * Copyright (C) 1992-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 3 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, see . + */ + +/** + * @file gerber_placefile_writer.cpp + * @brief Functions to create place files in gerber X2 format. + */ + +#include +#include "gerber_placefile_writer.h" + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + + +PLACEFILE_GERBER_WRITER::PLACEFILE_GERBER_WRITER( BOARD* aPcb ) +{ + m_pcb = aPcb; + /* Set conversion scale depending on drill file units */ + m_conversionUnits = 1.0 / IU_PER_MM; // Gerber units = mm + m_forceSmdItems = false; + m_plotPad1Marker = true; // Place a marker to pin 1 (or A1) position + m_plotOtherPadsMarker = false; // Place a marker to other pins position +} + + +int PLACEFILE_GERBER_WRITER::CreatePlaceFile( wxString& aFullFilename, + PCB_LAYER_ID aLayer ) +{ + m_layer = aLayer; + + // Collect footprints on the right layer + std::vector fp_list; + + for( MODULE* footprint : m_pcb->Modules() ) + { + if( footprint->GetAttributes() & MOD_VIRTUAL ) + continue; + + if( footprint->GetLayer() == aLayer ) + fp_list.push_back( footprint ); + } + + LOCALE_IO dummy_io; // Use the standard notation for double numbers + + GERBER_PLOTTER plotter; + + // Gerber drill file imply X2 format: + plotter.UseX2format( true ); + plotter.UseX2NetAttributes( true ); + + // Add the standard X2 header, without FileFunction + AddGerberX2Header( &plotter, m_pcb ); + plotter.SetViewport( m_offset, IU_PER_MILS/10, /* scale */ 1.0, /* mirror */false ); + // has meaning only for gerber plotter. Must be called only after SetViewport + plotter.SetGerberCoordinatesFormat( 6 ); + plotter.SetCreator( wxT( "PCBNEW" ) ); + + // Add the standard X2 FileFunction for P&P files + // %TF.FileFunction,Component,Ln,[top][bottom]*% + wxString text; + text.Printf( "%%TF.FileFunction,Component,L%d,%s*%%", + aLayer == B_Cu ? m_pcb->GetCopperLayerCount() : 1, + aLayer == B_Cu ? "Bottom" : "Top" ); + plotter.AddLineToHeader( text ); + + // Add file polarity (positive) + text = "%TF.FilePolarity,Positive*%"; + plotter.AddLineToHeader( text ); + + if( !plotter.OpenFile( aFullFilename ) ) + return -1; + + // We need a BRDITEMS_PLOTTER to plot pads + PCB_PLOT_PARAMS plotOpts; + BRDITEMS_PLOTTER brd_plotter( &plotter, m_pcb, plotOpts ); + brd_plotter.SetLayerSet( LSET( aLayer ) ); + + plotter.StartPlot(); + + int cmp_count = 0; + + for( MODULE* footprint : fp_list ) + { + // Manage the aperture attributes: in drill files 3 attributes can be used: + GBR_METADATA gbr_metadata; + gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CMP_POSITION ); + + // Add object attribute: component reference to flash (mainly usefull for users) + wxString ref = footprint->GetReference(); + + gbr_metadata.SetCmpReference( ref ); + gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP ); + + // Add P&P specific attributes + GBR_CMP_PNP_METADATA pnpAttrib; + + // Add rotation info (rotation is CCW, in degrees): + pnpAttrib.m_Orientation = mapRotationAngle( footprint->GetOrientationDegrees() ); + + // Add component type info (SMD or Through Hole): + bool is_smd_mount = footprint->GetAttributes() & MOD_CMS; + + // Smd footprints can have through holes (thermal vias). + // but if a footprint is not set as SMD, it will be set as SMD + // if it does not have through hole pads + if( !is_smd_mount && !footprint->HasNonSMDPins() ) + is_smd_mount = true; + + pnpAttrib.m_MountType = is_smd_mount ? GBR_CMP_PNP_METADATA::MOUNT_TYPE_SMD + : GBR_CMP_PNP_METADATA::MOUNT_TYPE_TH; + + // Add component value info: + pnpAttrib.m_Value = FormatStringFromGerber( footprint->GetValue() ); + + // Add component footprint info: + wxString fp_name = FROM_UTF8( footprint->GetFPID().GetLibItemName().c_str() ); + pnpAttrib.m_Footprint = FormatStringFromGerber( fp_name ); + + gbr_metadata.m_NetlistMetadata.SetExtraData( pnpAttrib.FormatCmpPnPMetadata() ); + + wxPoint flash_pos = footprint->GetPosition() + m_offset; + + int flash_diam = Millimeter2iu( 0.3 ); // arbitrary but reasonable value + plotter.FlashPadCircle( flash_pos, flash_diam, FILLED, &gbr_metadata ); + gbr_metadata.m_NetlistMetadata.ClearExtraData(); + + if( footprint->BuildPolyCourtyard() ) + { + int thickness = Millimeter2iu( 0.1 ); // arbitrary but reasonable value + gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CMP_COURTYARD ); + + SHAPE_POLY_SET& courtyard = aLayer == B_Cu ? + footprint->GetPolyCourtyardBack(): + footprint->GetPolyCourtyardFront(); + + for( int ii = 0; ii < courtyard.OutlineCount(); ii++ ) + { + SHAPE_LINE_CHAIN poly = courtyard.Outline( ii ); + poly.Move( m_offset ); + plotter.PLOTTER::PlotPoly( poly, NO_FILL, thickness, &gbr_metadata ); + } + } + + D_PAD* pad1 = nullptr; + + if( m_plotPad1Marker ) + { + pad1 = findPad1( footprint ); + + if( pad1 ) + { + gbr_metadata.SetApertureAttrib( + GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_PAD1_POSITION ); + + gbr_metadata.SetPadName( pad1->GetName() ); + gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_PAD ); + + // Flashes a diamond at pad position: use a slightly bigger size than the + // round spot to be able to see these 2 shapes when drawn at the same location + int mark_size = (flash_diam*6)/5; + plotter.FlashRegularPolygon( pad1->GetPosition() + m_offset, mark_size, 4, + 0.0, FILLED, &gbr_metadata ); + } + } + + if( m_plotOtherPadsMarker ) + { + + gbr_metadata.SetApertureAttrib( + GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_PADOTHER_POSITION ); + gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_PAD ); + + for( D_PAD* pad: footprint->Pads() ) + { + if( pad == pad1 ) // Already plotted + continue; + + // Skip also pads not on the current layer, like pads only + // on a tech layer + if( !pad->IsOnLayer( aLayer ) ) + continue; + + gbr_metadata.SetPadName( pad->GetName() ); + + // Flashes a round, 0 sized round shape at pad position + int mark_size = Millimeter2iu( 0.1 ); + plotter.FlashPadCircle( pad->GetPosition() + m_offset, mark_size, + FILLED, &gbr_metadata ); + } + } + + plotter.EndBlock( nullptr ); // Close all .TO attributes + + cmp_count++; + } + + plotter.EndPlot(); + + return cmp_count; +} + + +double PLACEFILE_GERBER_WRITER::mapRotationAngle( double aAngle ) +{ + // convert a kicad footprint orientation to gerber rotation, depending on the layer + // Currently, same notation as kicad + return aAngle; +} + + +D_PAD* PLACEFILE_GERBER_WRITER::findPad1( MODULE* aFootprint ) +{ + // Fint the pad "1" or pad "A1" + // this is possible only if only one pad is found + // Usefull to place a marker in this position + + std::vector pad1_list; + + for( D_PAD* pad : aFootprint->Pads() ) + { + if( !pad->IsOnLayer( m_layer ) ) + continue; + + if( pad->GetName() == "1" || pad->GetName() == "A1") + pad1_list.push_back( pad ); + } + + if( pad1_list.size() == 1 ) + return pad1_list[0]; + + return nullptr; +} + + +const wxString PLACEFILE_GERBER_WRITER::GetPlaceFileName( const wxString& aFullBaseFilename, + PCB_LAYER_ID aLayer ) const +{ + // Gerber files extension is always .gbr. + // Therefore, to mark pnp files, add "-pnp" to the filename, and a layer id. + wxFileName fn = aFullBaseFilename; + + wxString post_id = "-pnp_"; + post_id += aLayer == B_Cu ? "bottom" : "top"; + fn.SetName( fn.GetName() + post_id ); + fn.SetExt( GerberFileExtension ); + + return fn.GetFullPath(); +} diff --git a/pcbnew/exporters/gerber_placefile_writer.h b/pcbnew/exporters/gerber_placefile_writer.h new file mode 100644 index 0000000000..0a20ec3fe4 --- /dev/null +++ b/pcbnew/exporters/gerber_placefile_writer.h @@ -0,0 +1,102 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 Jean_Pierre Charras + * Copyright (C) 1992-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 3 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, see . + */ + +/** + * @file gerber_placefile_writer.h + * @brief Classes used in place file generation. + */ + +#ifndef PLACEFILE_GERBER_WRITER_H +#define PLACEFILE_GERBER_WRITER_H + +#include + +class BOARD; +class MODULE; +class D_PAD; + +/** + * PLACEFILE_GERBER_WRITER is a class mainly used to create Gerber drill files + */ +class PLACEFILE_GERBER_WRITER +{ +public: + PLACEFILE_GERBER_WRITER( BOARD* aPcb ); + + virtual ~PLACEFILE_GERBER_WRITER() + { + } + + + /** + * Function SetOptions + * Initialize internal parameters to match drill options + * note: PTH and NPTH are always separate files in Gerber format + * @param aOffset = drill coordinates offset + */ + void SetOptions( wxPoint aOffset ) + { + m_offset = aOffset; + } + + /** + * Creates an pnp gerber file + * @param aFullFilename = the full filename + * @param aLayer = layer (F_Cu or B_Cu) to generate + * @return component count, or -1 if the file cannot be created + */ + int CreatePlaceFile( wxString& aFullFilename, PCB_LAYER_ID aLayer ); + + /** + * @return a filename which identify the drill file function. + * @param aFullBaseFilename = a full filename. it will be modified + * to add "-pnp" and set the extension + * @param aLayer = layer (F_Cu or B_Cu) to generate + */ + const wxString GetPlaceFileName( const wxString& aFullBaseFilename, + PCB_LAYER_ID aLayer ) const; + +private: + BOARD* m_pcb; + /// The board layer currently used (typically F_Cu or B_Cu) + PCB_LAYER_ID m_layer; + double m_conversionUnits; // scaling factor to convert the board unites to + // Excellon/Gerber units (i.e inches or mm) + wxPoint m_offset; // Drill offset coordinates + bool m_forceSmdItems; + // True to plot a flashed marker shape at pad 1 position + bool m_plotPad1Marker; + // True to plot a marker shape at other pads position + // This is a flashed 0 sized round pad + bool m_plotOtherPadsMarker; + + + /** convert a kicad footprint orientation to gerber rotation + * both are in degrees + */ + double mapRotationAngle( double aAngle ); + + /** Find the pad 1 (or pad "A1") of a footprint + * Usefull to plot a marker at this position + */ + D_PAD* findPad1( MODULE* aFootprint ); +}; + +#endif // #ifndef PLACEFILE_GERBER_WRITER_H