From 8b0d76bb5d44474ebc564aa96f3d06fb0e8fda30 Mon Sep 17 00:00:00 2001 From: Martin Thomas Date: Fri, 31 Aug 2018 10:01:23 +0200 Subject: [PATCH 1/1] * now the BOM contains a sortet lists of References * The Parts can get distinguished by sided (top/bottom) * a switch to choose between the lists is prepared * TODO the GUI needs to get switch to choose between the lists Signed-off-by: Martin Thomas --- pcbnew/build_BOM_from_board.cpp | 135 ++++------ pcbnew/build_BOM_from_board_properties.h | 406 +++++++++++++++++++++++++++++++ 2 files changed, 453 insertions(+), 88 deletions(-) create mode 100644 pcbnew/build_BOM_from_board_properties.h diff --git a/pcbnew/build_BOM_from_board.cpp b/pcbnew/build_BOM_from_board.cpp index 99f11c8..9b94423 100644 --- a/pcbnew/build_BOM_from_board.cpp +++ b/pcbnew/build_BOM_from_board.cpp @@ -2,7 +2,8 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2009-2014 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr - * Copyright (C) 1992-2017 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2018 Martin Thomas, mthomas@conet.de * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -33,16 +34,19 @@ #include #include #include -#include #include #include #include +#include -/* creates a BOM list rom board +#include "build_BOM_from_board_properties.h" + + +/* creates a BOM list from board * The format is: - * "Id";"Designator";"Package";"Number";"Designation";"Supplier and ref"; + * "Id";"Designator";"Package";"Number";"Designation";"Supplier and ref";"side"; * 1;"P1";"DB25FC";1;"DB25FEMELLE";;; * 2;"U9";"PGA120";1;"4003APG120";;; * 3;"JP1";"pin_array_8x2";1;"CONN_8X2";;; @@ -60,27 +64,17 @@ const wxString CsvFileExtension( wxT( "csv" ) ); // BOM file extension -class cmp -{ -public: - wxString m_Ref; - wxString m_Val; - LIB_ID m_fpid; - int m_Id; - int m_CmpCount; -}; -WX_DECLARE_LIST( cmp, CmpList ); - -WX_DEFINE_LIST( CmpList ) - void PCB_EDIT_FRAME::RecreateBOMFileFromBoard( wxCommandEvent& aEvent ) { wxFileName fn; FILE* fp_bom; - MODULE* module = GetBoard()->m_Modules; + MODULE* footprint = GetBoard()->m_Modules; wxString msg; + // added var csv separator + wxChar csv_sep = ';'; + - if( module == NULL ) + if( footprint == NULL ) { DisplayError( this, _( "Cannot export BOM: there are no footprints in the PCB" ) ); return; @@ -110,77 +104,42 @@ void PCB_EDIT_FRAME::RecreateBOMFileFromBoard( wxCommandEvent& aEvent ) return; } - // Write header: - msg = wxT( "\"" ); - msg << _( "Id" ) << wxT( "\";\"" ); - msg << _( "Designator" ) << wxT( "\";\"" ); - msg << _( "Package" ) << wxT( "\";\"" ); - msg << _( "Quantity" ) << wxT( "\";\"" ); - msg << _( "Designation" ) << wxT( "\";\"" ); - msg << _( "Supplier and ref" ) << wxT( "\";\n" ); - fprintf( fp_bom, "%s", TO_UTF8( msg ) ); - - // Build list - CmpList list; - cmp* comp = NULL; - CmpList::iterator iter; - int i = 1; - - while( module != NULL ) - { - bool valExist = false; - - // try to find component in existing list - for( iter = list.begin(); iter != list.end(); ++iter ) - { - cmp* current = *iter; - - if( (current->m_Val == module->GetValue()) && (current->m_fpid == module->GetFPID()) ) - { - current->m_Ref.Append( wxT( ", " ), 1 ); - current->m_Ref.Append( module->GetReference() ); - current->m_CmpCount++; - - valExist = true; - break; - } - } - - // If component does not exist yet, create new one and append it to the list. - if( valExist == false ) - { - comp = new cmp(); - comp->m_Id = i++; - comp->m_Val = module->GetValue(); - comp->m_Ref = module->GetReference(); - comp->m_fpid = module->GetFPID(); - comp->m_CmpCount = 1; - list.Append( comp ); - } - - // increment module - module = module->Next(); - } - // Print list. Also delete temporary created objects. - for( size_t ii = list.GetCount(); ii > 0; ii-- ) + ElementManager em; + + for( footprint = GetBoard()->m_Modules; footprint; footprint = footprint->Next() ) { - cmp* current = *list.begin(); // Because the first object will be removed - // from list, all objects will be get here - - msg.Empty(); - - msg << current->m_Id << wxT( ";\"" ); - msg << current->m_Ref << wxT( "\";\"" ); - msg << FROM_UTF8( current->m_fpid.GetLibItemName().c_str() ) << wxT( "\";" ); - msg << current->m_CmpCount << wxT( ";\"" ); - msg << current->m_Val << wxT( "\";;;\n" ); - fprintf( fp_bom, "%s", TO_UTF8( msg ) ); - - // We do not need this object, now: remove it from list and delete it - list.DeleteObject( current ); - delete (current); + if( footprint->GetAttributes() & MOD_VIRTUAL ) + { + DBG( printf( "skipping footprint %s because it's virtual\n", + TO_UTF8( footprint->GetReference() ) );) + // left for debug purposes + //std::cout << footprint->GetReference() << "***skipped***\n"; + continue; + } + + if( ( footprint->GetAttributes() & MOD_CMS ) == 0 ) + { + footprint->SetAttributes( footprint->GetAttributes() | MOD_CMS ); + OnModify(); + } + Element e(footprint->GetReference() ); + ElementType t(footprint->GetValue(),footprint->GetFPID(),footprint->GetLayer()); + em.insertElement(t,e); } + /* + * TODO Add here the choice from the form. Currently 1 is seleced as it is the well known list + * but with sorted References and without "Supplier and ref" as it is not a property of the board + */ + int choice = 1; + + // sorted by "Footprint, Value, Layer and Reference" + if (choice == 1) + fputs( TO_UTF8( em.output(FVLR, csv_sep) ), fp_bom ); + // sorted by "Footprint, Value and Reference" + else if (choice == 2 ) + fputs( TO_UTF8( em.output(FVR, csv_sep) ), fp_bom ); + fclose( fp_bom ); -} +} \ No newline at end of file diff --git a/pcbnew/build_BOM_from_board_properties.h b/pcbnew/build_BOM_from_board_properties.h new file mode 100644 index 0000000..7e0c8cc --- /dev/null +++ b/pcbnew/build_BOM_from_board_properties.h @@ -0,0 +1,406 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018 Martin Thomas, mthomas@conet.de + * Copyright (C) 1992-2018 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +using namespace std; + +enum SORT_BY { FVR, FVLR}; + +class Element +{ + public: + Element() + { + } + Element(wxString _Ref):m_Ref(_Ref) + { + } + virtual ~Element() + { + } + // copy constructor + Element(const Element& rhs) + { + m_Ref = rhs.m_Ref; + } + + // output method + wxString output() const + { + return m_Ref; + } + public: + wxString m_Ref; + }; +// Sort the Reference accending (normal sort) +bool operator<(const Element& _L, const Element& _E) +{ + return StrNumCmp( _L.m_Ref, _E.m_Ref, 16 ) < 0; +} +bool operator>(const Element& _L, const Element& _E) +{ + return StrNumCmp( _L.m_Ref, _E.m_Ref, 16 ) > 0; +} +bool operator==(const Element& _L, const Element& _E) +{ + return StrNumCmp( _L.m_Ref, _E.m_Ref, 16 ) == 0; +} +// This property has only this particular element. +class Elements { + public: + Elements() + { + } + virtual ~Elements() + { + } + Elements(const Elements& rhs) + { + elements = rhs.elements; + } + void addElement(const Element& e) + { + // check if it is in the list already + for( const auto& element : elements) + { + if( element == e) + return; + } + elements.push_back(e); + } + + std::vector getReference() const + { + std::vector elements_tmp = elements; + + // sort the List of References ascending + std::sort(elements_tmp.begin(), elements_tmp.end(), + [](const Element& lhs, const Element& rhs) + { + return StrNumCmp( lhs.m_Ref, rhs.m_Ref, 16 ) < 0; + }); + return elements_tmp; + } + + int size() const + { + return elements.size(); + } + + friend bool operator<(const Element& _L, const Element& _E); + friend bool operator>(const Element& _L, const Element& _E); + friend bool operator==(const Element& _L, const Element& _E); + private: + std::vector elements; +}; + +// Attributes which all elements have (m_fpid, m_Val, m_Layer) +class ElementType { + public: + ElementType() {} + ElementType(wxString _val, LIB_ID _fpid, LAYER_NUM _Layer): m_Val(_val), m_fpid(_fpid),m_Layer(_Layer) {} + virtual ~ElementType() {} + + // copy constructor + ElementType(const ElementType& rhs) + { + m_Val = rhs.m_Val; + m_fpid = rhs.m_fpid; + m_Layer = rhs.m_Layer; + } + friend bool operator<(const ElementType& _L, const ElementType& _E); + friend bool operator==(const ElementType& _L, const ElementType& _E); + friend bool operator!=(const ElementType& _L, const ElementType& _E); + + wxString getvalue() const + { + wxString e; + e.append(m_Val); + return e; + } + + wxString getLayer() const + { + const wxString frontSideName = wxT( "top" ); + const wxString backSideName = wxT( "bottom" ); + wxString e; + if ( m_Layer == F_Cu ) + e.append(frontSideName); + else if (m_Layer == B_Cu ) + e.append(backSideName); + return e; + } + + LIB_ID getFP() const + { + return m_fpid; + } + + public: + wxString m_Val; + LIB_ID m_fpid; + LAYER_NUM m_Layer; + private: +}; + +// sort the map Footprint first, Value second and Layer third +bool operator==(const ElementType& _L, const ElementType& _E) +{ + return StrNumCmp( _L.m_Val, _E.m_Val, 16 ) == 0 && _L.m_fpid == _E.m_fpid && _L.m_Layer == _E.m_Layer ; +} +bool operator!=(const ElementType& _L, const ElementType& _E) +{ + return !(StrNumCmp( _L.m_Val, _E.m_Val, 16 ) == 0 && _L.m_fpid == _E.m_fpid && _L.m_Layer == _E.m_Layer) ; +} +// here is the actual sort of the map is made: +// Footprint first, Reverence second, Value third and layer as fourh +// Thaks to "StrNumCmp" with natural sort. +bool operator<(const ElementType& _L, const ElementType& _E) +{ + if ( _L.m_fpid == _E.m_fpid ) + { + if(StrNumCmp( _L.m_Val, _E.m_Val, 16 ) == 0 ) + { + if (_L.m_Layer == _E.m_Layer) + { + return true; + } + return _L.m_Layer < _E.m_Layer; + } + return StrNumCmp( _L.m_Val, _E.m_Val, 16 ) < 0; + } + return _L.m_fpid <_E.m_fpid; +} + +/* + * The ElementManager contains the methods are accessed and + * creates the BOM + */ +class ElementManager { + public: + ElementManager() {} + virtual ~ElementManager() {} + //copy constructor + ElementManager(const ElementManager& rhs) + { + elementTypes_ = rhs.elementTypes_; + } + // e contains the Reference of a Part + // t contains the Value, Footprint and Layer of a Part. + void insertElement(const ElementType& t, const Element& _e) + { + + for( auto& et : elementTypes_ ) + { + // Append new Element to Type + if (et.first == t ) + { + et.second.addElement(_e); + return; + } + } + // if not equal create new Type and add Element + Elements elements; + elements.addElement(_e); + elementTypes_.insert( std::pair(t,elements) ); + } + + wxString output(SORT_BY _sortby, wxChar csv_sep) const + { + std::map elementTypes = elementTypes_; + wxString e; + // First Line + e.append("Id"); + e.append(csv_sep); + e.append("Footprint"); + e.append(csv_sep); + e.append("Designator"); + e.append(csv_sep); + e.append("Designation"); + e.append(csv_sep); + e.append("Quantity"); + + int i = 0; + std::vector ref; + + // get the list with attention on the layer + if (_sortby == FVLR ) + { + // the rest of the first line + e.append(csv_sep); + e.append("Side"); + e.append("\n"); + + for( const auto& type : elementTypes) + { + ref = type.second.getReference(); + + e.append(std::to_string(++i)); + e.append(csv_sep); + e.append('"'); + e.append( FROM_UTF8( type.first.getFP().GetLibItemName().c_str() ) ); + e.append('"'); + e.append(csv_sep); + e.append('"'); + for( const auto& r : ref) + { + e.append(r.m_Ref); + e.append(','); + } + // remove last "," + e = e.substr(0, (e.size() - 1)); + e.append('"'); + e.append(csv_sep); + e.append('"'); + e.append(type.first.getvalue()); + e.append('"'); + e.append(csv_sep); + // Quantity of elements + e.append(std::to_string(ref.size()) ); + e.append(csv_sep); + e.append(type.first.getLayer()); + e.append("\n"); + } + } + + //get the list without the layer. + else if (_sortby == FVR ) + { + bool tmp=true; + std::vector ref_tmp; + // the rest of the first line + e.append("\n"); + + for( const auto& type : elementTypes) + { + ref = type.second.getReference(); + if (tmp) + { + e.append(std::to_string(++i)); + e.append(csv_sep); + e.append('"'); + e.append( FROM_UTF8( type.first.getFP().GetLibItemName().c_str() ) ); + e.append('"'); + e.append(csv_sep); + e.append('"'); + } + for( const auto& r : ref) + { + ref_tmp.push_back(r.m_Ref); + } + + // if the a part with same footprint and value appears on both sides + // continue and gather the rest ind the next loop + if ( (ValOnBothLayer(type.first.getvalue()) == 2) && tmp ) + { + tmp=false; + continue; + } + else + tmp=true; + + // sort reference list + std::sort(ref_tmp.begin(), ref_tmp.end(), + [](const Element& lhs, const Element& rhs) + { + return StrNumCmp( lhs.m_Ref, rhs.m_Ref, 16 ) < 0; + }); + for( const auto& r : ref_tmp) + { + e.append(r.m_Ref); + e.append(','); + } + // remove last "," + e = e.substr(0, (e.size() - 1)); + e.append('"'); + e.append(csv_sep); + e.append('"'); + e.append(type.first.getvalue()); + e.append('"'); + e.append(csv_sep); + e.append(std::to_string(ref_tmp.size()) ); + e.append("\n"); + //clear the vector for the next patrs + ref_tmp.clear(); + } + } + return e; + } + + int ValOnBothLayer(wxString _val) const + { + int count = 0; + for(auto& elements : elementTypes_ ) + { + if( elements.first.m_Val == _val ) + count++; + } + return count; + } + + // currently not used: + /* + int quantityOfElementTypes() const + { + return elementTypes_.size(); + } + int quantityOfElements() const + { + int count = 0; + for(auto& elements : elementTypes_ ) + { + count += elements.second.size(); + } + return count; + } + int quantityOfElementsWithValueOf(wxString _val) const + { + int count = 0; + for(auto& elements : elementTypes_ ) + { + if( elements.first.m_Val == _val ) + count += elements.second.size(); + } + return count; + } + */ + private: + std::map elementTypes_; +}; -- 2.7.4