/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2012 Wayne Stambaugh * Copyright (C) 1992-2012 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 */ /** * @file build_BOM.cpp * @brief Code used to generate bill of materials. */ #include // to use sort vector #include #include #include #include #include #include #include #include /* Creates the list of components, grouped by values: * One line by value. The format is something like: * value;quantity;references;other fields * 18pF;2;"C404 C405";SM0402 * 22nF/25V;4;"C128 C168 C228 C268";SM0402 * param aFile = the file to write to (will be closed) */ void BOM_LISTER::CreateCsvBOMListByValues( FILE* aFile ) { m_outFile = aFile; SCH_SHEET_LIST sheetList; sheetList.GetComponents( m_cmplist, false ); // sort component list by ref and remove sub components m_cmplist.RemoveSubComponentsFromList(); // sort component list by value m_cmplist.SortByValueOnly(); unsigned int index = 0; while( index < m_cmplist.GetCount() ) { SCH_COMPONENT* component = m_cmplist[index].GetComponent(); wxString referenceListStr; int qty = 1; referenceListStr.append( m_cmplist[index].GetRef() ); for( unsigned int ii = index + 1; ii < m_cmplist.GetCount(); ) { if( *( m_cmplist[ii].GetComponent() ) == *component ) { referenceListStr.append( wxT( " " ) + m_cmplist[ii].GetRef() ); m_cmplist.RemoveItem( ii ); qty++; } else ii++; // Increment index only when current item is not removed from the list } // Write value, quantity and list of references fprintf( m_outFile, "%s%c%d%c\"%s\"", TO_UTF8( component->GetField( VALUE )->GetText() ), m_separatorSymbol, qty, m_separatorSymbol, TO_UTF8( referenceListStr ) ); for( int ii = FOOTPRINT; ii < component->GetFieldCount(); ii++ ) { if( isFieldPrintable( ii ) ) fprintf( m_outFile, "%c%s", m_separatorSymbol, TO_UTF8( component->GetField( ii )->GetText() ) ); } fprintf( m_outFile, "\n" ); index++; } fclose( m_outFile ); m_outFile = NULL; } bool BOM_LISTER::isFieldPrintable( int aFieldId ) { for( unsigned ii = 0; ii < m_fieldIDactive.size(); ii++ ) if( m_fieldIDactive[ii] == aFieldId ) return true; return false; } void BOM_LISTER::AddFieldIdToPrintList( int aFieldId ) { for( unsigned ii = 0; ii < m_fieldIDactive.size(); ii++ ) if( m_fieldIDactive[ii] == aFieldId ) return; m_fieldIDactive.push_back( aFieldId ); } /* compare function for sorting labels by value, then by sheet */ static bool SortLabelsByValue( const BOM_LABEL& obj1, const BOM_LABEL& obj2 ) { int ii = obj1.GetText().CmpNoCase( obj2.GetText() ); if( ii == 0 ) ii = obj1.GetSheetPath().Cmp( obj2.GetSheetPath() ); return ii < 0; } /* compare function for sorting labels by sheet, then by alphabetic order */ static bool SortLabelsBySheet( const BOM_LABEL& obj1, const BOM_LABEL& obj2 ) { int ii = obj1.GetSheetPath().Cmp( obj2.GetSheetPath() ); if( ii == 0 ) ii = obj1.GetText().CmpNoCase( obj2.GetText() ); return ii < 0; } // Creates the flat list of global, hierachycal labels and pin sheets // and populate m_labelList void BOM_LISTER::buildGlobalAndHierarchicalLabelsList() { m_labelList.clear(); // Explore the flat sheet list SCH_SHEET_LIST sheetList; for( SCH_SHEET_PATH* path = sheetList.GetFirst(); path; path = sheetList.GetNext() ) { SCH_ITEM* schItem = (SCH_ITEM*) path->LastDrawList(); for( ; schItem; schItem = schItem->Next() ) { switch( schItem->Type() ) { case SCH_HIERARCHICAL_LABEL_T: case SCH_GLOBAL_LABEL_T: m_labelList.push_back( BOM_LABEL( schItem->Type(), schItem, *path ) ); break; case SCH_SHEET_T: { SCH_SHEET* sheet = (SCH_SHEET*) schItem; BOOST_FOREACH( SCH_SHEET_PIN & sheetPin, sheet->GetPins() ) { m_labelList.push_back( BOM_LABEL( SCH_SHEET_PIN_T, &sheetPin, *path ) ); } } break; default: break; } } } } // Print the flat list of global, hierachycal labels and pin sheets // contained by m_labelList void BOM_LISTER::PrintGlobalAndHierarchicalLabelsList( FILE* aFile, bool aSortBySheet ) { m_outFile = aFile; buildGlobalAndHierarchicalLabelsList(); wxString msg; if( aSortBySheet ) { sort( m_labelList.begin(), m_labelList.end(), SortLabelsBySheet ); msg.Printf( _( "\n#Global, Hierarchical Labels and PinSheets \ ( order = Sheet Number ) count = %d\n" ), m_labelList.size() ); } else { sort( m_labelList.begin(), m_labelList.end(), SortLabelsByValue ); msg.Printf( _( "\n#Global, Hierarchical Labels and PinSheets ( \ order = Alphab. ) count = %d\n\n" ), m_labelList.size() ); } fprintf( m_outFile, "%s", TO_UTF8( msg ) ); SCH_LABEL* label; SCH_SHEET_PIN* pinsheet; wxString sheetpath; wxString labeltype; for( unsigned ii = 0; ii < m_labelList.size(); ii++ ) { switch( m_labelList[ii].GetType() ) { case SCH_HIERARCHICAL_LABEL_T: case SCH_GLOBAL_LABEL_T: label = (SCH_LABEL*) ( m_labelList[ii].GetLabel() ); if( m_labelList[ii].GetType() == SCH_HIERARCHICAL_LABEL_T ) labeltype = wxT( "Hierarchical" ); else labeltype = wxT( "Global " ); sheetpath = m_labelList[ii].GetSheetPath().PathHumanReadable(); msg.Printf( wxT( "> %-28.28s %s %s\n" ), GetChars( label->GetText() ), GetChars( labeltype ), GetChars( returnURLItemLocation( sheetpath, label->m_Pos ) ) ); fputs( TO_UTF8( msg ), m_outFile ); break; case SCH_SHEET_PIN_T: pinsheet = (SCH_SHEET_PIN*) m_labelList[ii].GetLabel(); labeltype = FROM_UTF8( SheetLabelType[pinsheet->GetShape()] ); msg.Printf( _( "> %-28.28s PinSheet %-7.7s %s\n" ), GetChars( pinsheet->GetText() ), GetChars( labeltype ), GetChars( returnURLItemLocation( m_labelList[ii].GetSheetPath(). PathHumanReadable(), pinsheet->m_Pos ) ) ); fputs( TO_UTF8( msg ), m_outFile ); break; default: break; } } msg = _( "#End labels\n" ); fputs( TO_UTF8( msg ), m_outFile ); } /* * Helper function * returns a string containing all selected fields texts, * separated by the csv separator symbol (csv form) or a ; */ const wxString BOM_LISTER::returnFieldsString( SCH_COMPONENT* aComponent ) { wxString outStr; wxString tmpStr; wxString text; for( int ii = FOOTPRINT; ii <= FIELD8; ii++ ) { if( !isFieldPrintable( ii ) ) continue; if( aComponent->GetFieldCount() > ii ) text = aComponent->GetField( ii )->m_Text; else text = wxEmptyString; if( m_csvForm ) tmpStr.Printf( wxT( "%c%s" ), m_separatorSymbol, GetChars( text ) ); else tmpStr.Printf( wxT( "; %-12s" ), GetChars( text ) ); outStr += tmpStr; } return outStr; } /* print the list of components ordered by references, * full component list in human readable form * param aFile = the file to write to (will be NOT closed) */ /* full list in human readable form sample: * #Cmp ( order = Reference )with sub-composants * | C101 47pF Loc /(X=344,170 mm, Y=116,840 mm); C1 ; field1 ; * | C102 47pF Loc /(X=364,490 mm, Y=116,840 mm); C1 ; ; * | C103 47uF Loc /(X=66,040 mm, Y=231,140 mm); CP6 ; ; */ bool BOM_LISTER::PrintComponentsListByReferenceHumanReadable( FILE* aFile ) { m_outFile = aFile; bool addDatasheet = isFieldPrintable( DATASHEET ); // Print component location if needed, but only when // include sub component option is enabled, because for multiple // parts per package there are more than one location per reference bool printLocCmp = m_printLocation && m_includeSubComponents; wxString msg; if( m_cmplist.GetCount() == 0 ) // Build component list { SCH_SHEET_LIST sheetList; sheetList.GetComponents( m_cmplist, false ); // sort component list m_cmplist.SortByReferenceOnly(); if( !m_includeSubComponents ) m_cmplist.RemoveSubComponentsFromList(); } else m_cmplist.SortByReferenceOnly(); // Print comment line: msg = _( "#Cmp ( order = Reference )" ); if( m_includeSubComponents ) msg << _( " (with SubCmp)" ); fprintf( m_outFile, "%s\n", TO_UTF8( msg ) ); wxString subReference; // Unit ident, for mutiple parts per package std::string CmpName; // Print list of items for( unsigned ii = 0; ii < m_cmplist.GetCount(); ii++ ) { EDA_ITEM* item = m_cmplist[ii].GetComponent(); if( item == NULL ) continue; if( item->Type() != SCH_COMPONENT_T ) continue; SCH_COMPONENT* comp = (SCH_COMPONENT*) item; bool isMulti = false; LIB_COMPONENT* entry = CMP_LIBRARY::FindLibraryComponent( comp->GetLibName() ); if( entry ) isMulti = entry->IsMulti(); CmpName = m_cmplist[ii].GetRefStr(); if( isMulti && m_includeSubComponents ) { subReference = LIB_COMPONENT::ReturnSubReference( m_cmplist[ii].GetUnit() ); CmpName += TO_UTF8( subReference ); } fprintf( m_outFile, "| %-10s %-12s", CmpName.c_str(), TO_UTF8( comp->GetField( VALUE )->m_Text ) ); if( addDatasheet ) fprintf( m_outFile, "%-20s", TO_UTF8( comp->GetField( DATASHEET )->m_Text ) ); if( m_includeSubComponents ) { if( printLocCmp ) { msg = returnURLItemLocation( m_cmplist[ii].GetSheetPath().PathHumanReadable(), comp->GetPosition() ); fprintf( m_outFile, "%s", TO_UTF8( msg ) ); } } wxString tmpStr = returnFieldsString( comp ); fprintf( m_outFile, "%s\n", TO_UTF8( tmpStr ) ); } // Print the last line: fputs( "#End Cmp\n", m_outFile ); return true; } /* print the list of components ordered by references. Generate 2 formats: * - full component list in csv form * - "short" component list in csv form, grouped by common fields values * (mainly component value) * param aFile = the file to write to (will be NOT closed) */ /* full csv format sample: * ref;value;sheet path(location);footprint;field1;field2 * C101;47pF;Loc /(X=57,150 mm, Y=74,930 mm);Loc /(X=344,170 mm, Y=116,840 mm));C1;field1; * C102;47pF;Loc /(X=344,170 mm, Y=116,840 mm);Loc /(X=364,490 mm, Y=116,840 mm));C1;; * C103;47uF;Loc /(X=364,490 mm, Y=116,840 mm);Loc /(X=66,040 mm, Y=231,140 mm));CP6;; * C104;47uF;Loc /(X=66,040 mm, Y=231,140 mm);Loc /(X=82,550 mm, Y=231,140 mm));CP6;; */ /* short csv format sample: * ref;value;footprint;Champ1;Champ2 * C101;47pF;C1;field1;;1 * C102;47pF;C1;;;1 * C103..C106;47uF;CP6;;;4 */ bool BOM_LISTER::PrintComponentsListByReferenceCsvForm( FILE* aFile ) { m_outFile = aFile; bool addDatasheet = isFieldPrintable( DATASHEET ); // Set option group references, for components having same field values // (same value, same footprint ...) // obviously, this is possible only when print location // and include Sub Components are not enabled. bool groupRefs = m_groupReferences; bool includeSubComponents = m_includeSubComponents && !groupRefs; // Print component location if needed, but only when // include sub component option is enabled, because for multiple // parts per package there are more than one location per reference bool printLocCmp = m_printLocation && !groupRefs && m_includeSubComponents; wxString msg; if( m_cmplist.GetCount() == 0 ) // Build component list { SCH_SHEET_LIST sheetList; sheetList.GetComponents( m_cmplist, false ); // sort component list m_cmplist.SortByReferenceOnly(); if( !includeSubComponents ) m_cmplist.RemoveSubComponentsFromList(); } else m_cmplist.SortByReferenceOnly(); // Print comment line: msg = wxT( "ref" ); msg << (wxChar)m_separatorSymbol << wxT( "value" ); if( addDatasheet ) msg << (wxChar)m_separatorSymbol << wxT( "datasheet" ); if( printLocCmp ) msg << (wxChar)m_separatorSymbol << wxT( "sheet path(location)" ); if( isFieldPrintable( FOOTPRINT ) ) msg << (wxChar)m_separatorSymbol << wxT( "footprint" ); for( int ii = FIELD1; ii <= FIELD8; ii++ ) { if( isFieldPrintable( ii ) ) msg << (wxChar)m_separatorSymbol << _( "Field" ) << ii - FIELD1 + 1; } if( groupRefs ) msg << (wxChar)m_separatorSymbol << _( "Item count" ); fprintf( m_outFile, "%s\n", TO_UTF8( msg ) ); // Print BOM list wxString strCur; wxString strPred; int amount = 0; // number of items, on the same line wxString cmpName; wxString cmpNameFirst; wxString cmpNameLast; // Print list of items, by reference for( unsigned ii = 0; ii < m_cmplist.GetCount(); ii++ ) { EDA_ITEM* item = m_cmplist[ii].GetComponent(); if( item == NULL ) continue; if( item->Type() != SCH_COMPONENT_T ) continue; SCH_COMPONENT* comp = (SCH_COMPONENT*) item; LIB_COMPONENT* entry = CMP_LIBRARY::FindLibraryComponent( comp->GetLibName() ); bool isMulti = false; if( entry ) isMulti = entry->IsMulti(); cmpName = m_cmplist[ii].GetRef(); if( isMulti && includeSubComponents ) // Add unit ident, for mutiple parts per package cmpName += LIB_COMPONENT::ReturnSubReference( m_cmplist[ii].GetUnit() ); if( groupRefs ) { // Store value and datasheet (will be printed later) strCur.Empty(); strCur << (wxChar)m_separatorSymbol << comp->GetField( VALUE )->m_Text; if( addDatasheet ) strCur << (wxChar)m_separatorSymbol << comp->GetField( DATASHEET )->m_Text; } else { // Print the current component reference, value and datasheet msg = cmpName; msg << (wxChar)m_separatorSymbol << comp->GetField( VALUE )->m_Text; if( addDatasheet ) msg << (wxChar)m_separatorSymbol << comp->GetField( DATASHEET )->m_Text; fprintf( m_outFile, "%s", TO_UTF8( msg ) ); } if( printLocCmp ) // Is allowed only for full list (not grouped) { msg = returnURLItemLocation( m_cmplist[ii].GetSheetPath().PathHumanReadable(), comp->GetPosition() ); fprintf( m_outFile, "%c%s", m_separatorSymbol, TO_UTF8( msg ) ); } if( groupRefs ) { wxString tmpStr = returnFieldsString( comp ); strCur += tmpStr; if( strPred.Len() == 0 ) cmpNameFirst = cmpName; else { // print a BOM line msg.Empty(); if( !strCur.IsSameAs( strPred ) ) { switch( amount ) { case 1: // One reference to print // format C103;47uF;CP6;;;1 msg << cmpNameFirst < * | 10pF C16 Loc /controle/(X=68,580 mm, Y=83,820 mm); */ int BOM_LISTER::PrintComponentsListByValue( FILE* aFile ) { m_outFile = aFile; if( m_cmplist.GetCount() == 0 ) // Build component list { SCH_SHEET_LIST sheetList; sheetList.GetComponents( m_cmplist, false ); if( !m_includeSubComponents ) { // sort component list m_cmplist.SortByReferenceOnly(); m_cmplist.RemoveSubComponentsFromList(); } } m_cmplist.SortByValueOnly(); wxString msg; msg = _( "\n#Cmp ( order = Value )" ); if( m_includeSubComponents ) msg << _( " (with SubCmp)" ); msg << wxT( "\n" ); fputs( TO_UTF8( msg ), m_outFile ); std::string cmpName; for( unsigned ii = 0; ii < m_cmplist.GetCount(); ii++ ) { EDA_ITEM* schItem = m_cmplist[ii].GetComponent(); if( schItem == NULL ) continue; if( schItem->Type() != SCH_COMPONENT_T ) continue; SCH_COMPONENT* drawLibItem = (SCH_COMPONENT*) schItem; bool isMulti = false; LIB_COMPONENT* entry = CMP_LIBRARY::FindLibraryComponent( drawLibItem->GetLibName() ); if( entry ) isMulti = entry->IsMulti(); cmpName = m_cmplist[ii].GetRefStr(); if( isMulti && m_includeSubComponents ) // Add unit ident, for mutiple parts per package cmpName += TO_UTF8( LIB_COMPONENT::ReturnSubReference( m_cmplist[ii].GetUnit() ) ); fprintf( m_outFile, "| %-12s %-10s", TO_UTF8( drawLibItem->GetField( VALUE )->m_Text ), cmpName.c_str() ); // print the sheet path and location if( m_includeSubComponents ) { msg = returnURLItemLocation( m_cmplist[ii].GetSheetPath().PathHumanReadable(), drawLibItem->GetPosition() ); fprintf( m_outFile, "%s", TO_UTF8( msg ) ); } fprintf( m_outFile, "%s\n", TO_UTF8( returnFieldsString( drawLibItem ) ) ); } msg = _( "#End Cmp\n" ); fputs( TO_UTF8( msg ), m_outFile ); return 0; } /* returnURLItemLocation * return a formated string to print the full location: * /( X Y position) * param aPathName = the full sheet name of item * param aPosition = a position (in internal units) to print */ const wxString BOM_LISTER::returnURLItemLocation( const wxString& aPathName, wxPoint aPosition ) { wxString text; text.Printf( wxT( "Loc %s(X=%s, Y=%s)" ), GetChars( aPathName ), GetChars( ReturnStringFromValue( g_UserUnit, aPosition.x, true ) ), GetChars( ReturnStringFromValue( g_UserUnit, aPosition.y, true ) ) ); return text; }