diff --git a/common/wildcards_and_files_ext.cpp b/common/wildcards_and_files_ext.cpp index dfc2e858fe..f492086d2e 100644 --- a/common/wildcards_and_files_ext.cpp +++ b/common/wildcards_and_files_ext.cpp @@ -145,6 +145,7 @@ const std::string LtspiceSchematicExtension( "asc" ); const std::string CadstarNetlistFileExtension( "frp" ); const std::string OrCadPcb2NetlistFileExtension( "net" ); const std::string NetlistFileExtension( "net" ); +const std::string AllegroNetlistFileExtension( "txt" ); const std::string FootprintAssignmentFileExtension( "cmp" ); const std::string GerberFileExtension( "gbr" ); const std::string GerberJobFileExtension( "gbrjob" ); @@ -330,6 +331,13 @@ wxString NetlistFileWildcard() } +wxString AllegroNetlistFileWildcard() +{ + return _( "Allegro netlist files" ) + + AddFileExtListToFilter( { AllegroNetlistFileExtension } ); +} + + wxString LegacyPcbFileWildcard() { return _( "KiCad printed circuit board files" ) + AddFileExtListToFilter( { "brd" } ); diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index 103a08cb7b..bf9b12c284 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -355,6 +355,7 @@ set( EESCHEMA_SRCS toolbars_sch_editor.cpp toolbars_symbol_viewer.cpp + netlist_exporters/netlist_exporter_allegro.cpp netlist_exporters/netlist_exporter_base.cpp netlist_exporters/netlist_exporter_cadstar.cpp netlist_exporters/netlist_exporter_kicad.cpp diff --git a/eeschema/dialogs/dialog_export_netlist.cpp b/eeschema/dialogs/dialog_export_netlist.cpp index 0286185669..59b086ecb3 100644 --- a/eeschema/dialogs/dialog_export_netlist.cpp +++ b/eeschema/dialogs/dialog_export_netlist.cpp @@ -59,14 +59,16 @@ /* panel (notebook page) identifiers */ -enum panel_netlist_index { - PANELPCBNEW = 0, /* Handle Netlist format Pcbnew */ - PANELORCADPCB2, /* Handle Netlist format OracdPcb2 */ - PANELCADSTAR, /* Handle Netlist format CadStar */ - PANELSPICE, /* Handle Netlist format Spice */ - PANELSPICEMODEL, /* Handle Netlist format Spice Model (subcircuit) */ - PANELCUSTOMBASE /* First auxiliary panel (custom netlists). - * others use PANELCUSTOMBASE+1, PANELCUSTOMBASE+2.. */ +enum panel_netlist_index +{ + PANELPCBNEW = 0, /* Handle Netlist format Pcbnew */ + PANELORCADPCB2, /* Handle Netlist format OracdPcb2 */ + PANELALLEGRO, /* Handle Netlist format Allegro */ + PANELCADSTAR, /* Handle Netlist format CadStar */ + PANELSPICE, /* Handle Netlist format Spice */ + PANELSPICEMODEL, /* Handle Netlist format Spice Model (subcircuit) */ + PANELCUSTOMBASE /* First auxiliary panel (custom netlists). + * others use PANELCUSTOMBASE+1, PANELCUSTOMBASE+2.. */ }; @@ -245,14 +247,17 @@ DIALOG_EXPORT_NETLIST::DIALOG_EXPORT_NETLIST( SCH_EDIT_FRAME* parent ) : page = nullptr; // Add notebook pages: - m_PanelNetType[PANELPCBNEW] = new EXPORT_NETLIST_PAGE( m_NoteBook, wxT( "KiCad" ), - NET_TYPE_PCBNEW, false ); + m_PanelNetType[PANELPCBNEW] = + new EXPORT_NETLIST_PAGE( m_NoteBook, wxT( "KiCad" ), NET_TYPE_PCBNEW, false ); - m_PanelNetType[PANELORCADPCB2] = new EXPORT_NETLIST_PAGE( m_NoteBook, wxT( "OrcadPCB2" ), - NET_TYPE_ORCADPCB2, false ); + m_PanelNetType[PANELORCADPCB2] = + new EXPORT_NETLIST_PAGE( m_NoteBook, wxT( "OrcadPCB2" ), NET_TYPE_ORCADPCB2, false ); - m_PanelNetType[PANELCADSTAR] = new EXPORT_NETLIST_PAGE( m_NoteBook, wxT( "CadStar" ), - NET_TYPE_CADSTAR, false ); + m_PanelNetType[PANELALLEGRO] = + new EXPORT_NETLIST_PAGE( m_NoteBook, wxT( "Allegro" ), NET_TYPE_ALLEGRO, false ); + + m_PanelNetType[PANELCADSTAR] = + new EXPORT_NETLIST_PAGE( m_NoteBook, wxT( "CadStar" ), NET_TYPE_CADSTAR, false ); InstallPageSpice(); InstallPageSpiceModel(); @@ -496,6 +501,9 @@ bool DIALOG_EXPORT_NETLIST::TransferDataFromWindow() case NET_TYPE_ORCADPCB2: break; + case NET_TYPE_ALLEGRO: + break; + default: // custom, NET_TYPE_CUSTOM1 and greater { title.Printf( _( "%s Export" ), currPage->m_TitleStringCtrl->GetValue() ); @@ -629,6 +637,11 @@ bool DIALOG_EXPORT_NETLIST::FilenamePrms( NETLIST_TYPE_ID aType, wxString * aExt fileWildcard = NetlistFileWildcard(); break; + case NET_TYPE_ALLEGRO: + fileExt = AllegroNetlistFileExtension; + fileWildcard = AllegroNetlistFileWildcard(); + break; + default: // custom, NET_TYPE_CUSTOM1 and greater fileWildcard = AllFilesWildcard(); ret = false; diff --git a/eeschema/lib_symbol.cpp b/eeschema/lib_symbol.cpp index 1933d1a996..6197dfa6d3 100644 --- a/eeschema/lib_symbol.cpp +++ b/eeschema/lib_symbol.cpp @@ -1242,24 +1242,42 @@ LIB_FIELD* LIB_SYMBOL::GetFieldById( int aId ) const } -LIB_FIELD* LIB_SYMBOL::FindField( const wxString& aFieldName ) +LIB_FIELD* LIB_SYMBOL::FindField( const wxString& aFieldName, bool aCaseInsensitive ) { for( LIB_ITEM& item : m_drawings[ LIB_FIELD_T ] ) { - if( static_cast( &item )->GetCanonicalName() == aFieldName ) - return static_cast( &item ); + if( aCaseInsensitive ) + { + if( static_cast( &item )->GetCanonicalName().Upper() == aFieldName.Upper() ) + return static_cast( &item ); + } + else + { + if( static_cast( &item )->GetCanonicalName() == aFieldName ) + return static_cast( &item ); + } } return nullptr; } -const LIB_FIELD* LIB_SYMBOL::FindField( const wxString& aFieldName ) const +const LIB_FIELD* LIB_SYMBOL::FindField( const wxString& aFieldName, + const bool aCaseInsensitive ) const { for( const LIB_ITEM& item : m_drawings[ LIB_FIELD_T ] ) { - if( static_cast( &item )->GetCanonicalName() == aFieldName ) - return static_cast( &item ); + if( aCaseInsensitive ) + { + if( static_cast( &item )->GetCanonicalName().Upper() + == aFieldName.Upper() ) + return static_cast( &item ); + } + else + { + if( static_cast( &item )->GetCanonicalName() == aFieldName ) + return static_cast( &item ); + } } return nullptr; diff --git a/eeschema/lib_symbol.h b/eeschema/lib_symbol.h index 9412cbf142..affabf80c8 100644 --- a/eeschema/lib_symbol.h +++ b/eeschema/lib_symbol.h @@ -292,10 +292,15 @@ public: /** * Find a field within this symbol matching \a aFieldName and returns it * or NULL if not found. + * @param aFieldName is the name of the field to find. + * @param aCaseInsensitive ignore the filed name case if true. + * + * @return the field if found or NULL if the field was not found. */ - LIB_FIELD* FindField( const wxString& aFieldName ); + LIB_FIELD* FindField( const wxString& aFieldName, bool aCaseInsensitive = false ); - const LIB_FIELD* FindField( const wxString& aFieldName ) const; + const LIB_FIELD* FindField( const wxString& aFieldName, + const bool aCaseInsensitive = false ) const; /** * Return pointer to the requested field. diff --git a/eeschema/netlist_exporters/netlist.h b/eeschema/netlist_exporters/netlist.h index 872eaba8d0..2d5687a785 100644 --- a/eeschema/netlist_exporters/netlist.h +++ b/eeschema/netlist_exporters/netlist.h @@ -40,6 +40,7 @@ enum NETLIST_TYPE_ID { NET_TYPE_CADSTAR, NET_TYPE_SPICE, NET_TYPE_SPICE_MODEL, + NET_TYPE_ALLEGRO, NET_TYPE_CUSTOM1, /* NET_TYPE_CUSTOM1 * is the first id for user netlist format * NET_TYPE_CUSTOM1+CUSTOMPANEL_COUNTMAX-1 diff --git a/eeschema/netlist_exporters/netlist_exporter_allegro.cpp b/eeschema/netlist_exporters/netlist_exporter_allegro.cpp new file mode 100644 index 0000000000..e5b6d0041d --- /dev/null +++ b/eeschema/netlist_exporters/netlist_exporter_allegro.cpp @@ -0,0 +1,797 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2018 jp.charras at wanadoo.fr + * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck + * Copyright (C) 1992-2021 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 "netlist_exporter_allegro.h" +#include "netlist_exporter_xml.h" +#include + + +bool NETLIST_EXPORTER_ALLEGRO::WriteNetlist( const wxString& aOutFileName, + unsigned /* aNetlistOptions */, + REPORTER& aReporter ) +{ + m_f = nullptr; + wxString field; + wxString footprint; + int ret = 0; // zero now, OR in the sign bit on error + wxString netName; + + // Create the devices directory + m_exportPath = wxFileName( aOutFileName ).GetPath( wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR ) + + wxString( "devices" ); + if( !wxDirExists( m_exportPath ) ) + { + if( !wxMkdir( m_exportPath, wxS_DIR_DEFAULT ) ) + { + wxString msg = wxString::Format( _( "Failed to create directory 'devices' ." ) ); + aReporter.Report( msg, RPT_SEVERITY_ERROR ); + return false; + } + } + + // Write the netlist file + if( ( m_f = wxFopen( aOutFileName, wxT( "wt" ) ) ) == nullptr ) + { + wxString msg = wxString::Format( _( "Failed to create file '%s'." ), aOutFileName ); + aReporter.Report( msg, RPT_SEVERITY_ERROR ); + return false; + } + + ret |= fprintf( m_f, "(Source: %s)\n", TO_UTF8( m_schematic->GetFileName() ) ); + ret |= fprintf( m_f, "(Date: %s)\n", TO_UTF8( DateAndTime() ) ); + + m_packageProperties.clear(); + m_componentGroups.clear(); + m_orderedSymbolsSheetpath.clear(); + m_netNameNodes.clear(); + + extractComponentsInfo(); + + // Start with package definitions, which we create from component groups. + toAllegroPackages(); + + // Write out nets + toAllegroNets(); + + // Write out package properties. NOTE: Allegro doesn't recognize much... + toAllegroPackageProperties(); + + // Done with the netlist + fclose( m_f ); + + m_f = nullptr; + + return ret >= 0; +} + + +bool NETLIST_EXPORTER_ALLEGRO::CompareSymbolSheetpath( + const std::pair& aItem1, + const std::pair& aItem2 ) +{ + wxString refText1 = aItem1.first->GetRef( &aItem1.second ); + wxString refText2 = aItem2.first->GetRef( &aItem2.second ); + + if( refText1 == refText2 ) + { + return aItem1.second.PathHumanReadable() < aItem2.second.PathHumanReadable(); + } + + return CompareSymbolRef( refText1, refText2 ); +} + + +bool NETLIST_EXPORTER_ALLEGRO::CompareSymbolRef( const wxString& aRefText1, + const wxString& aRefText2 ) +{ + if( removeTailDigits( aRefText1 ) == removeTailDigits( aRefText2 ) ) + { + return extractTailNumber( aRefText1 ) < extractTailNumber( aRefText2 ); + } + + return aRefText1 < aRefText2; +} + + +bool NETLIST_EXPORTER_ALLEGRO::CompareLibPin( const LIB_PIN* aPin1, const LIB_PIN* aPin2 ) +{ + // return "lhs < rhs" + return StrNumCmp( aPin1->GetShownNumber(), aPin2->GetShownNumber(), true ) < 0; +} + + +void NETLIST_EXPORTER_ALLEGRO::extractComponentsInfo() +{ + m_referencesAlreadyFound.Clear(); + m_libParts.clear(); + + SCH_SHEET_LIST sheetList = m_schematic->GetSheets(); + + for( unsigned ii = 0; ii < sheetList.size(); ii++ ) + { + SCH_SHEET_PATH sheet = sheetList[ii]; + m_schematic->SetCurrentSheet( sheet ); + + auto cmp = [sheet]( SCH_SYMBOL* a, SCH_SYMBOL* b ) + { + return ( StrNumCmp( a->GetRef( &sheet, false ), + b->GetRef( &sheet, false ), true ) < 0 ); + }; + + std::set ordered_symbols( cmp ); + std::multiset extra_units( cmp ); + + for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) ) + { + SCH_SYMBOL* symbol = static_cast( item ); + auto test = ordered_symbols.insert( symbol ); + + if( !test.second ) + { + if( ( *( test.first ) )->m_Uuid > symbol->m_Uuid ) + { + extra_units.insert( *( test.first ) ); + ordered_symbols.erase( test.first ); + ordered_symbols.insert( symbol ); + } + else + { + extra_units.insert( symbol ); + } + } + } + + for( EDA_ITEM* item : ordered_symbols ) + { + SCH_SYMBOL* symbol = findNextSymbol( item, &sheet ); + + if( !symbol || symbol->GetExcludedFromBoard() ) + { + continue; + } + + LIB_PINS pinList; + pinList.clear(); + symbol->GetLibPins(pinList); + + if( !pinList.size() ) + { + continue; + } + + m_packageProperties.insert(std::pair(sheet.PathHumanReadable(), symbol->GetRef(&sheet))); + m_orderedSymbolsSheetpath.push_back(std::pair(symbol, sheet)); + } + } + + wxString netCodeTxt; + wxString netName; + wxString ref; + + struct NET_RECORD + { + NET_RECORD( const wxString& aName ) : + m_Name( aName ) + {}; + + wxString m_Name; + std::vector m_Nodes; + }; + + std::vector nets; + + for( const auto& it : m_schematic->ConnectionGraph()->GetNetMap() ) + { + wxString net_name = it.first.Name; + const std::vector& subgraphs = it.second; + NET_RECORD* net_record = nullptr; + + if( subgraphs.empty() ) + continue; + + nets.emplace_back( new NET_RECORD( net_name ) ); + net_record = nets.back(); + + for( CONNECTION_SUBGRAPH* subgraph : subgraphs ) + { + bool nc = subgraph->GetNoConnect() && subgraph->GetNoConnect()->Type() == SCH_NO_CONNECT_T; + const SCH_SHEET_PATH& sheet = subgraph->GetSheet(); + + for( SCH_ITEM* item : subgraph->GetItems() ) + { + if( item->Type() == SCH_PIN_T ) + { + SCH_PIN* pin = static_cast( item ); + SCH_SYMBOL* symbol = pin->GetParentSymbol(); + + if( !symbol || symbol->GetExcludedFromBoard() ) + { + continue; + } + + net_record->m_Nodes.emplace_back( pin, sheet, nc ); + } + } + } + } + + // Netlist ordering: Net name, then ref des, then pin name + std::sort( nets.begin(), nets.end(), + []( const NET_RECORD* a, const NET_RECORD*b ) + { + return StrNumCmp( a->m_Name, b->m_Name ) < 0; + } ); + + for( int i = 0; i < (int) nets.size(); ++i ) + { + NET_RECORD* net_record = nets[i]; + + // Netlist ordering: Net name, then ref des, then pin name + std::sort( net_record->m_Nodes.begin(), net_record->m_Nodes.end(), + []( const NET_NODE& a, const NET_NODE& b ) + { + wxString refA = a.m_Pin->GetParentSymbol()->GetRef( &a.m_Sheet ); + wxString refB = b.m_Pin->GetParentSymbol()->GetRef( &b.m_Sheet ); + + if( refA == refB ) + return a.m_Pin->GetShownNumber() < b.m_Pin->GetShownNumber(); + + return refA < refB; + } ); + + // Some duplicates can exist, for example on multi-unit parts with duplicated pins across + // units. If the user connects the pins on each unit, they will appear on separate + // subgraphs. Remove those here: + alg::remove_duplicates( net_record->m_Nodes, + []( const NET_NODE& a, const NET_NODE& b ) + { + wxString refA = a.m_Pin->GetParentSymbol()->GetRef( &a.m_Sheet ); + wxString refB = b.m_Pin->GetParentSymbol()->GetRef( &b.m_Sheet ); + + return refA == refB && a.m_Pin->GetShownNumber() == b.m_Pin->GetShownNumber(); + } ); + + for( const NET_NODE& netNode : net_record->m_Nodes ) + { + wxString refText = netNode.m_Pin->GetParentSymbol()->GetRef( &netNode.m_Sheet ); + wxString pinText = netNode.m_Pin->GetShownNumber(); + + // Skip power symbols and virtual symbols + if( refText[0] == wxChar( '#' ) ) + { + continue; + } + + m_netNameNodes.insert( std::pair( net_record->m_Name, netNode ) ); + } + } + + for( NET_RECORD* record : nets ) + delete record; +} + + +void NETLIST_EXPORTER_ALLEGRO::toAllegroPackages() +{ + int ret = 0; // zero now, OR in the sign bit on error + int groupCount = 1; + wxString deviceFileCreatingError = wxString( "" ); + + //Group the components...... + while(!m_orderedSymbolsSheetpath.empty()) + { + std::pair first_ele = m_orderedSymbolsSheetpath.front(); + m_orderedSymbolsSheetpath.pop_front(); + m_componentGroups.insert( + std::pair>( groupCount, first_ele ) ); + + for( auto it = m_orderedSymbolsSheetpath.begin(); it != m_orderedSymbolsSheetpath.end(); + ++it ) + { + if( it->first->GetValueFieldText( false, &it->second, false ) + != first_ele.first->GetValueFieldText( false, &first_ele.second, false ) ) + { + continue; + } + + if( it->first->GetFootprintFieldText( false, &it->second, false ) + != first_ele.first->GetFootprintFieldText( false, &first_ele.second, false ) ) + { + continue; + } + + wxString ref1 = it->first->GetRef( &it->second ); + wxString ref2 = first_ele.first->GetRef( &first_ele.second ); + if( removeTailDigits( ref1 ) == removeTailDigits( ref2 ) ) + { + m_componentGroups.insert( std::pair>( + groupCount, ( *it ) ) ); + it = m_orderedSymbolsSheetpath.erase( it ); + if( std::distance( it, m_orderedSymbolsSheetpath.begin() ) > 0 ) + it--; + else if( it == m_orderedSymbolsSheetpath.end() ) + break; + } + } + groupCount++; + } + + ret |= fprintf( m_f, "%s\n", "$PACKAGES" ); + + struct COMP_PACKAGE_STRUCT + { + wxString m_value; + wxString m_tolerance; + std::vector> m_symbolSheetpaths; + }; + + COMP_PACKAGE_STRUCT compPackageStruct; + std::map compPackageMap; + + for( int groupIndex = 1; groupIndex < groupCount; groupIndex++ ) + { + auto pairIter = m_componentGroups.equal_range(groupIndex); + auto beginIter = pairIter.first; + auto endIter = pairIter.second; + + SCH_SYMBOL* sym = (beginIter->second).first; + SCH_SHEET_PATH sheetPath = (beginIter->second).second; + + wxString valueText = sym->GetValueFieldText( false, &sheetPath, false ); + wxString footprintText = sym->GetFootprintFieldText( false, &sheetPath, false); + wxString deviceType = valueText + wxString("_") + footprintText; + + while( deviceType.GetChar(deviceType.Length()-1) == '_' ) + { + deviceType.RemoveLast(); + } + + deviceType = formatDevice( deviceType ); + + wxArrayString fieldArray; + fieldArray.Add("Spice_Model"); + fieldArray.Add("VALUE"); + + wxString value = getGroupField( groupIndex, fieldArray ); + + fieldArray.clear(); + fieldArray.Add("TOLERANCE"); + fieldArray.Add("TOL"); + wxString tol = getGroupField( groupIndex, fieldArray ); + + std::vector> symbolSheetpaths; + + for( auto iter = beginIter; iter != endIter; iter++ ) + { + symbolSheetpaths.push_back( std::pair( + iter->second.first, iter->second.second ) ); + } + + std::stable_sort( symbolSheetpaths.begin(), symbolSheetpaths.end(), + NETLIST_EXPORTER_ALLEGRO::CompareSymbolSheetpath ); + + compPackageStruct.m_value = value; + compPackageStruct.m_tolerance = tol; + compPackageStruct.m_symbolSheetpaths = symbolSheetpaths; + compPackageMap.insert( std::pair( deviceType, compPackageStruct ) ); + + // Write out the corresponding device file + FILE* d = nullptr; + wxString deviceFileName = wxFileName(m_exportPath, deviceType, wxString("txt")).GetFullPath(); + if( ( d = wxFopen( deviceFileName, wxT( "wt" ) ) ) == nullptr ) + { + wxString msg; + msg.Printf( _( "Failed to create file '%s'.\n" ), deviceFileName ); + deviceFileCreatingError += msg; + continue; + } + + footprintText = footprintText.AfterLast( ':' ); + + wxArrayString footprintAlt; + wxArrayString footprintArray = sym->GetLibSymbolRef()->GetFPFilters(); + + for( auto fp : footprintArray ) + { + if( ( fp.Find( '*' ) != wxNOT_FOUND ) || ( fp.Find( '?' ) != wxNOT_FOUND ) ) + { + continue; + } + + footprintAlt.Add( fp.AfterLast( ':' ) ); + } + + if( footprintText.IsEmpty() ) + { + if( !footprintAlt.IsEmpty() ) + { + footprintText = footprintAlt[0]; + footprintAlt.RemoveAt( 0 ); + } + else + { + footprintText = deviceType; + } + } + + fprintf( d, "PACKAGE '%s'\n", TO_UTF8( formatDevice( footprintText ) ) ); + fprintf( d, "CLASS IC\n" ); + + LIB_PINS pinList; + sym->GetLibSymbolRef()->GetPins( pinList, 0, 0 ); + + /* + * We must erase redundant Pins references in pinList + * These redundant pins exist because some pins are found more than one time when a + * symbol has multiple parts per package or has 2 representations (DeMorgan conversion). + * For instance, a 74ls00 has DeMorgan conversion, with different pin shapes, and + * therefore each pin appears 2 times in the list. Common pins (VCC, GND) can also be + * found more than once. + */ + sort( pinList.begin(), pinList.end(), NETLIST_EXPORTER_ALLEGRO::CompareLibPin ); + for( int ii = 0; ii < (int) pinList.size() - 1; ii++ ) + { + if( pinList[ii]->GetNumber() == pinList[ii + 1]->GetNumber() ) + { // 2 pins have the same number, remove the redundant pin at index i+1 + pinList.erase( pinList.begin() + ii + 1 ); + ii--; + } + } + + unsigned int pinCount = pinList.size(); + fprintf( d, "PINCOUNT %u\n", pinCount ); + + if( pinCount > 0 ) + { + fprintf( d, "%s", TO_UTF8( formatFunction( "main", pinList ) ) ); + } + + if( !value.IsEmpty() ) + { + fprintf( d, "PACKAGEPROP VALUE %s\n", TO_UTF8( value ) ); + } + + if( !tol.IsEmpty() ) + { + fprintf( d, "PACKAGEPROP TOL %s\n", TO_UTF8( tol ) ); + } + + if( !footprintAlt.IsEmpty() ) + { + fprintf( d, "PACKAGEPROP ALT_SYMBOLS '(" ); + + wxString footprintAltSymbolsText; + for( auto fp : footprintAlt ) + { + footprintAltSymbolsText += fp + wxString( "," ); + } + footprintAltSymbolsText.Truncate( footprintAltSymbolsText.Length() - 1 ); + fprintf( d, "%s)'\n", TO_UTF8( footprintAltSymbolsText ) ); + } + + wxArrayString propArray; + propArray.Add( "PART_NUMBER" ); + propArray.Add( "mpn" ); + propArray.Add( "mfr_pn" ); + wxString data = getGroupField( groupIndex, propArray ); + if(!data.IsEmpty()) + { + fprintf( d, "PACKAGEPROP %s %s\n", TO_UTF8( propArray[0] ), TO_UTF8( data ) ); + } + + propArray.clear(); + propArray.Add( "HEIGHT" ); + data = getGroupField( groupIndex, propArray ); + if(!data.IsEmpty()) + { + fprintf( d, "PACKAGEPROP %s %s\n", TO_UTF8( propArray[0] ), TO_UTF8( data ) ); + } + + fprintf( d, "END\n" ); + + fclose( d ); + } + + for( auto iter = compPackageMap.begin(); iter != compPackageMap.end(); iter++ ) + { + wxString deviceType = iter->first; + wxString value = iter->second.m_value; + wxString tolerance = iter->second.m_tolerance; + + if( value.IsEmpty() && tolerance.IsEmpty() ) + { + ret |= fprintf( m_f, "!%s;", TO_UTF8( deviceType ) ); + } + else if( tolerance.IsEmpty() ) + { + ret |= fprintf( m_f, "!%s!%s;", TO_UTF8( deviceType ), TO_UTF8( value ) ); + } + else + { + ret |= fprintf( m_f, "!%s!%s!%s;", TO_UTF8( deviceType ), TO_UTF8( value ), + TO_UTF8( tolerance ) ); + } + + std::vector> symbolSheetpaths = + iter->second.m_symbolSheetpaths; + + for( auto it = symbolSheetpaths.begin(); it != symbolSheetpaths.end(); it++ ) + { + SCH_SYMBOL* sym = it->first; + SCH_SHEET_PATH sheetPath = it->second; + wxString refText = sym->GetRef( &sheetPath ); + ret |= fprintf( m_f, ",\n\t%s", TO_UTF8( refText ) ); + } + ret |= fprintf( m_f, "\n" ); + } + + if( !deviceFileCreatingError.IsEmpty() ) + { + DisplayError( nullptr, deviceFileCreatingError ); + } +} + + +wxString NETLIST_EXPORTER_ALLEGRO::formatText( wxString aString ) +{ + if( aString.IsEmpty() ) + { + return wxString( "" ); + } + + aString.Replace( "\u03BC", "u" ); + + std::regex reg( "[!']|[^ -~]" ); + wxString processedString = wxString( std::regex_replace( std::string( aString ), reg, "?" ) ); + + std::regex search_reg( "[^a-zA-Z0-9_/]" ); + if( std::regex_search( std::string( processedString ), search_reg ) ) + { + return wxString( "'" ) + processedString + wxString( "'" ); + } + + return processedString; +} + + +wxString NETLIST_EXPORTER_ALLEGRO::formatPin( const LIB_PIN& aPin ) +{ + wxString pinName4Telesis = aPin.GetName() + wxString( "__" ) + aPin.GetNumber(); + std::regex reg( "[^A-Za-z0-9_+?/-]" ); + return wxString( std::regex_replace( std::string( pinName4Telesis ), reg, "?" ) ); +} + + +wxString NETLIST_EXPORTER_ALLEGRO::formatFunction( wxString aName, LIB_PINS aPinList ) +{ + aName.MakeUpper(); + std::list pinNameList; + + std::stable_sort( aPinList.begin(), aPinList.end(), NETLIST_EXPORTER_ALLEGRO::CompareLibPin ); + + for( auto pin : aPinList ) + { + pinNameList.push_back( formatPin( *pin ) ); + } + + wxString out_str = ""; + wxString str; + + out_str.Printf( wxT( "PINORDER %s " ), TO_UTF8( aName ) ); + + for( auto pinName : pinNameList ) + { + str.Printf( ",\n\t%s", TO_UTF8( pinName ) ); + out_str += str; + } + out_str += wxString( "\n" ); + + str.Printf( wxT( "FUNCTION %s %s " ), TO_UTF8( aName ), TO_UTF8( aName ) ); + out_str += str; + + for( auto pin : aPinList ) + { + str.Printf( ",\n\t%s", TO_UTF8( pin->GetNumber() ) ); + out_str += str; + } + out_str += wxString( "\n" ); + + return out_str; +} + + +wxString NETLIST_EXPORTER_ALLEGRO::getGroupField( int aGroupIndex, const wxArrayString& aFieldArray, + bool aSanitize ) +{ + auto pairIter = m_componentGroups.equal_range( aGroupIndex ); + + for( auto iter = pairIter.first; iter != pairIter.second; ++iter ) + { + SCH_SYMBOL* sym = ( iter->second ).first; + SCH_SHEET_PATH sheetPath = ( iter->second ).second; + + for( auto field : aFieldArray ) + { + SCH_FIELD* fld = sym->FindField( field, true, true ); + if( fld != NULL ) + { + wxString fieldText = fld->GetShownText( &sheetPath, true ); + if( !fieldText.IsEmpty() ) + { + if( aSanitize ) + { + return formatText( fieldText ); + } + else + { + return fieldText; + } + } + } + } + } + + for( auto iter = pairIter.first; iter != pairIter.second; ++iter ) + { + SCH_SYMBOL* sym = ( iter->second ).first; + SCH_SHEET_PATH sheetPath = ( iter->second ).second; + + for( auto field : aFieldArray ) + { + LIB_FIELD* fld = sym->GetLibSymbolRef()->FindField( field, true ); + if( fld != NULL ) + { + wxString fieldText = fld->GetShownText( &sheetPath, true ); + if( !fieldText.IsEmpty() ) + { + if( aSanitize ) + { + return formatText( fieldText ); + } + else + { + return fieldText; + } + } + } + } + } + + return wxString( "" ); +} + +wxString NETLIST_EXPORTER_ALLEGRO::formatDevice( wxString aString ) +{ + aString.MakeLower(); + std::regex reg( "[^a-z0-9_-]" ); + return wxString( std::regex_replace( std::string( aString ), reg, "_" ) ); +} + +void NETLIST_EXPORTER_ALLEGRO::toAllegroPackageProperties() +{ + int ret = 0; // zero now, OR in the sign bit on error + + ret |= fprintf( m_f, "%s\n", "$PACKAGES" ); + ret |= fprintf( m_f, "%s\n", "$A_PROPERTIES" ); + + while( !m_packageProperties.empty() ) + { + std::multimap::iterator iter = m_packageProperties.begin(); + wxString sheetPathText = iter->first; + + ret |= fprintf( m_f, "ROOM %s;", TO_UTF8( formatText( sheetPathText ) ) ); + + std::vector refTexts; + + auto pairIter = m_packageProperties.equal_range( sheetPathText ); + for( iter = pairIter.first; iter != pairIter.second; ++iter ) + { + wxString refText = iter->second; + refTexts.push_back( refText ); + } + m_packageProperties.erase( pairIter.first, pairIter.second ); + + std::stable_sort( refTexts.begin(), refTexts.end(), + NETLIST_EXPORTER_ALLEGRO::CompareSymbolRef ); + + for( auto it = refTexts.begin(); it != refTexts.end(); it++ ) + { + ret |= fprintf( m_f, ",\n\t%s", TO_UTF8( *it ) ); + } + ret |= fprintf( m_f, "\n" ); + } +} + +void NETLIST_EXPORTER_ALLEGRO::toAllegroNets() +{ + int ret = 0; // zero now, OR in the sign bit on error + + ret |= fprintf( m_f, "%s\n", "$NETS" ); + + while( !m_netNameNodes.empty() ) + { + std::multimap::iterator iter = m_netNameNodes.begin(); + std::vector netNodes; + + wxString netName = iter->first; + + ret |= fprintf( m_f, "%s;", TO_UTF8( formatText( netName ).MakeUpper() ) ); + + auto pairIter = m_netNameNodes.equal_range( netName ); + for( iter = pairIter.first; iter != pairIter.second; ++iter ) + { + NET_NODE netNode = iter->second; + netNodes.push_back( netNode ); + } + m_netNameNodes.erase( pairIter.first, pairIter.second ); + + std::stable_sort( netNodes.begin(), netNodes.end() ); + + for( auto it = netNodes.begin(); it != netNodes.end(); it++ ) + { + NET_NODE netNode = *it; + wxString refText = netNode.m_Pin->GetParentSymbol()->GetRef( &netNode.m_Sheet ); + wxString pinText = netNode.m_Pin->GetShownNumber(); + ret |= fprintf( m_f, ",\n\t%s.%s", TO_UTF8( refText ), TO_UTF8( pinText ) ); + } + ret |= fprintf( m_f, "\n" ); + } +} + +wxString NETLIST_EXPORTER_ALLEGRO::removeTailDigits( wxString aString ) +{ + while( ( aString.GetChar( aString.Length() - 1 ) >= '0' ) + && ( aString.GetChar( aString.Length() - 1 ) <= '9' ) ) + { + aString.RemoveLast(); + } + return aString; +} + +unsigned int NETLIST_EXPORTER_ALLEGRO::extractTailNumber( wxString aString ) +{ + wxString numString; + while( ( aString.GetChar( aString.Length() - 1 ) >= '0' ) + && ( aString.GetChar( aString.Length() - 1 ) <= '9' ) ) + { + numString.insert( 0, aString.GetChar( aString.Length() - 1 ) ); + aString.RemoveLast(); + } + + unsigned long val; + //From wxWidgets 3.1.6, here we can use ToUInt instead of ToULong function. + numString.ToULong( &val ); + return (unsigned int) val; +} diff --git a/eeschema/netlist_exporters/netlist_exporter_allegro.h b/eeschema/netlist_exporters/netlist_exporter_allegro.h new file mode 100644 index 0000000000..ef15635a9e --- /dev/null +++ b/eeschema/netlist_exporters/netlist_exporter_allegro.h @@ -0,0 +1,215 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2013 jp.charras at wanadoo.fr + * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck + * Copyright (C) 1992-2021 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 NETLIST_EXPORTER_ALLEGRO_H +#define NETLIST_EXPORTER_ALLEGRO_H + +#include "netlist_exporter_base.h" + +/** + * Generate a netlist compatible with Allegro. + */ +class NETLIST_EXPORTER_ALLEGRO : public NETLIST_EXPORTER_BASE +{ +public: + NETLIST_EXPORTER_ALLEGRO( SCHEMATIC* aSchematic ) : + NETLIST_EXPORTER_BASE( aSchematic ) + { + } + + /** + * Write netlist to \a aOutFileName. + * Generate the Allegro netlist format supported by Allegro. + */ + bool WriteNetlist( const wxString& aOutFileName, unsigned aNetlistOptions, + REPORTER& aReporter ) override; + + /** + * Compare two std::pair variables. + * + * @param aItem1 comparing object 1 + * @param aItem2 comparing object 2 + * @return true if aItem1 < aItem2 + */ + static bool CompareSymbolSheetpath( const std::pair& aItem1, + const std::pair& aItem2 ); + + /** + * Compare two wxString variables. + * + * @param aRefText1 + * @param aRefText2 + * @return bool value + */ + static bool CompareSymbolRef( const wxString& aRefText1, const wxString& aRefText2 ); + + /** + * Compare two LIB_PIN* variables. + * + * @param aPin1 + * @param aPin2 + * @return bool value + */ + static bool CompareLibPin( const LIB_PIN* aPin1, const LIB_PIN* aPin2 ); + +private: + void extractComponentsInfo(); + + /** + * Write the $PACKAGES section + * + */ + void toAllegroPackages(); + + /** + * Write the $NETS section + * + */ + void toAllegroNets(); + + /** + * Write $A_PROPERTIES section + * + */ + void toAllegroPackageProperties(); + + /** + * Converts a string into one safe for a Telesis device name. + * These are all lowercase and have a more restricted set of characters. + * FIXME: replace unsupported characters with an encoding instead + * + * @param aString wxString to be formatted. + * @return a formatted wxString. + */ + wxString formatDevice( wxString aString ); + + /** + * Convert a string into Telesis-safe format. Unsupported characters are + * replaced with ?'s, and the string is quoted if necessary. + * FIXME: replace unsupported characters with an encoding instead, to avoid + * having similar strings mapped to each other. + * + * @param aString + * @return wxString + */ + wxString formatText( wxString aString ); + + /** + * Generates a Telesis-compatible pin name from a pin node. + * Telesis requires all pin names to be unique, and doesn't have separate + * fields for pin number and pin name/function, so we combine them together to + * make a unique name that still describes its function if you check pin info. + * FIXME: replace unsupported characters with an encoding instead + * + * @param aPin + * @return wxString + */ + wxString formatPin( const LIB_PIN& aPin ); + + /** + * Generates the definition of a function in Telesis format, which consists of + * multiple declarations (PINORDER, PINSWAP, and FUNCTIONs). + * + * @param aName + * @param aPinList + * @return wxString + */ + wxString formatFunction( wxString aName, LIB_PINS aPinList ); + + /** + * Look up a field for a component group, which may have mismatched case, or + * the component group may not have the field defined and instead the library + * entry has to be searched. Returns None if no fields exist. + * + * @param aGroupIndex the component group index to query + * @param aFieldArray one or more (equivalent) fields to query, in the order specified. + * first field that exists is returned. + * @param aSanitize if true (default), will format/escape the field for Telesis output + * @return return the found field, or return wxString("") if no field exist. + */ + wxString getGroupField( int aGroupIndex, const wxArrayString& aFieldArray, + bool aSanitize = true ); + + /** + * Remove the str's tailing digits. + * + * @param aString + * @return wxString + */ + static wxString removeTailDigits( wxString aString ); + + /** + * Extract the str's tailing number. + * + * @param aString + * @return unsigned int + */ + static unsigned int extractTailNumber( wxString aString ); + + struct NET_NODE + { + NET_NODE( SCH_PIN* aPin, const SCH_SHEET_PATH& aSheet, bool aNoConnect ) : + m_Pin( aPin ), + m_Sheet( aSheet ), + m_NoConnect( aNoConnect ) + {} + + bool operator<( const NET_NODE& aNetNode ) const + { + wxString refText1 = m_Pin->GetParentSymbol()->GetRef( &m_Sheet ); + wxString refText2 = aNetNode.m_Pin->GetParentSymbol()->GetRef( &aNetNode.m_Sheet ); + if( refText1 == refText2 ) + { + unsigned long val1, val2; + //From wxWidgets 3.1.6, the function ToULong can be repalced with ToUInt + bool convertingResult = m_Pin->GetShownNumber().ToULong( &val1 ); + convertingResult &= aNetNode.m_Pin->GetShownNumber().ToULong( &val2 ); + if( convertingResult ) + { + return val1 < val2; + } + else + { + return m_Pin->GetShownNumber() < aNetNode.m_Pin->GetShownNumber(); + } + } + return CompareSymbolRef( refText1, refText2 ); + } + + SCH_PIN* m_Pin; + SCH_SHEET_PATH m_Sheet; + bool m_NoConnect; + }; + + FILE* m_f; ///< file pointer for netlist file writing operation + wxString m_exportPath; ///< directory to store device files + std::multimap m_packageProperties; + std::multimap > m_componentGroups; ///< Store the component group + std::list> + m_orderedSymbolsSheetpath; ///< Store the ordered symbols with sheetpath + std::multimap m_netNameNodes; ///< Store the NET_NODE with the net name +}; + +#endif \ No newline at end of file diff --git a/eeschema/netlist_exporters/netlist_generator.cpp b/eeschema/netlist_exporters/netlist_generator.cpp index 64fa88936b..923c96055e 100644 --- a/eeschema/netlist_exporters/netlist_generator.cpp +++ b/eeschema/netlist_exporters/netlist_generator.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include @@ -88,6 +89,10 @@ bool SCH_EDIT_FRAME::WriteNetListFile( int aFormat, const wxString& aFullFileNam helper = new NETLIST_EXPORTER_SPICE_MODEL( sch ); break; + case NET_TYPE_ALLEGRO: + helper = new NETLIST_EXPORTER_ALLEGRO( sch ); + break; + case NET_TYPE_BOM: // When generating the BOM, we have a bare filename so don't strip // the extension or you might string a '.' from the middle of the filename diff --git a/eeschema/sch_symbol.cpp b/eeschema/sch_symbol.cpp index 478829652e..49d2987a21 100644 --- a/eeschema/sch_symbol.cpp +++ b/eeschema/sch_symbol.cpp @@ -953,14 +953,23 @@ void SCH_SYMBOL::RemoveField( const wxString& aFieldName ) } -SCH_FIELD* SCH_SYMBOL::FindField( const wxString& aFieldName, bool aIncludeDefaultFields ) +SCH_FIELD* SCH_SYMBOL::FindField( const wxString& aFieldName, bool aIncludeDefaultFields, + bool aCaseInsensitive ) { unsigned start = aIncludeDefaultFields ? 0 : MANDATORY_FIELDS; for( unsigned i = start; i < m_fields.size(); ++i ) { - if( aFieldName == m_fields[i].GetName( false ) ) - return &m_fields[i]; + if( aCaseInsensitive ) + { + if( aFieldName.Upper() == m_fields[i].GetName( false ).Upper() ) + return &m_fields[i]; + } + else + { + if( aFieldName == m_fields[i].GetName( false ) ) + return &m_fields[i]; + } } return nullptr; diff --git a/eeschema/sch_symbol.h b/eeschema/sch_symbol.h index 4db9862c3a..074af17f90 100644 --- a/eeschema/sch_symbol.h +++ b/eeschema/sch_symbol.h @@ -458,10 +458,12 @@ public: * * @param aFieldName is the name of the field to find. * @param aIncludeDefaultFields searches the library symbol default fields if true. + * @param aCaseInsensitive ignore the filed name case if true. * * @return the field if found or NULL if the field was not found. */ - SCH_FIELD* FindField( const wxString& aFieldName, bool aIncludeDefaultFields = true ); + SCH_FIELD* FindField( const wxString& aFieldName, bool aIncludeDefaultFields = true, + bool aCaseInsensitive = false ); const wxString GetValueFieldText( bool aResolve, const SCH_SHEET_PATH* aPath, bool aAllowExtraText ) const; diff --git a/include/wildcards_and_files_ext.h b/include/wildcards_and_files_ext.h index 6d06c12521..99250f4f6e 100644 --- a/include/wildcards_and_files_ext.h +++ b/include/wildcards_and_files_ext.h @@ -128,6 +128,7 @@ extern const std::string SpiceFileExtension; extern const std::string CadstarNetlistFileExtension; extern const std::string OrCadPcb2NetlistFileExtension; extern const std::string NetlistFileExtension; +extern const std::string AllegroNetlistFileExtension; extern const std::string GerberFileExtension; extern const std::string GerberJobFileExtension; extern const std::string HtmlFileExtension; @@ -211,6 +212,7 @@ extern wxString LegacySchematicFileWildcard(); extern wxString BoardFileWildcard(); extern wxString OrCadPcb2NetlistFileWildcard(); extern wxString NetlistFileWildcard(); +extern wxString AllegroNetlistFileWildcard(); extern wxString HtmlFileWildcard(); extern wxString CsvFileWildcard(); extern wxString LegacyPcbFileWildcard();