1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 1992-2013 jp.charras at wanadoo.fr
5  * Copyright (C) 2013-2017 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
6  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
24  */
25 
26 #include "netlist_exporter_xml.h"
27 
28 #include <build_version.h>
29 #include <common.h>     // for ExpandTextVars
30 #include <sch_base_frame.h>
31 #include <symbol_library.h>
32 #include <string_utils.h>
33 #include <connection_graph.h>
34 #include <string_utils.h>
35 #include <wx/wfstream.h>
36 #include <xnode.h>      // also nests: <wx/xml/xml.h>
37 
38 #include <symbol_lib_table.h>
39 
40 #include <set>
41 
42 static bool sortPinsByNumber( LIB_PIN* aPin1, LIB_PIN* aPin2 );
43 
WriteNetlist(const wxString & aOutFileName,unsigned aNetlistOptions)44 bool NETLIST_EXPORTER_XML::WriteNetlist( const wxString& aOutFileName, unsigned aNetlistOptions )
45 {
46     // output the XML format netlist.
47 
48     // declare the stream ourselves to use the buffered FILE api
49     // instead of letting wx use the syscall variant
50     wxFFileOutputStream stream( aOutFileName );
51 
52     if( !stream.IsOk() )
53         return false;
54 
55     wxXmlDocument       xdoc;
56     xdoc.SetRoot( makeRoot( GNL_ALL | aNetlistOptions ) );
57 
58     return xdoc.Save( stream, 2 /* indent bug, today was ignored by wxXml lib */ );
59 }
60 
61 
makeRoot(unsigned aCtl)62 XNODE* NETLIST_EXPORTER_XML::makeRoot( unsigned aCtl )
63 {
64     XNODE*      xroot = node( "export" );
65 
66     xroot->AddAttribute( "version", "E" );
67 
68     if( aCtl & GNL_HEADER )
69         // add the "design" header
70         xroot->AddChild( makeDesignHeader() );
71 
72     if( aCtl & GNL_SYMBOLS )
73         xroot->AddChild( makeSymbols( aCtl ) );
74 
75     if( aCtl & GNL_PARTS )
76         xroot->AddChild( makeLibParts() );
77 
78     if( aCtl & GNL_LIBRARIES )
79         // must follow makeGenericLibParts()
80         xroot->AddChild( makeLibraries() );
81 
82     if( aCtl & GNL_NETS )
83         xroot->AddChild( makeListOfNets( aCtl ) );
84 
85     return xroot;
86 }
87 
88 
89 /// Holder for multi-unit symbol fields
90 
91 
addSymbolFields(XNODE * aNode,SCH_SYMBOL * aSymbol,SCH_SHEET_PATH * aSheet)92 void NETLIST_EXPORTER_XML::addSymbolFields( XNODE* aNode, SCH_SYMBOL* aSymbol,
93                                             SCH_SHEET_PATH* aSheet )
94 {
95     wxString                     value;
96     wxString                     datasheet;
97     wxString                     footprint;
98     std::map<wxString, wxString> userFields;
99 
100     if( aSymbol->GetUnitCount() > 1 )
101     {
102         // Sadly, each unit of a symbol can have its own unique fields. This
103         // block finds the unit with the lowest number having a non blank field
104         // value and records it.  Therefore user is best off setting fields
105         // into only the first unit.  But this scavenger algorithm will find
106         // any non blank fields in all units and use the first non-blank field
107         // for each unique field name.
108 
109         wxString ref = aSymbol->GetRef( aSheet );
110 
111         SCH_SHEET_LIST sheetList = m_schematic->GetSheets();
112         int minUnit = aSymbol->GetUnit();
113 
114         for( unsigned i = 0;  i < sheetList.size();  i++ )
115         {
116             for( auto item : sheetList[i].LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
117             {
118                 SCH_SYMBOL* symbol2 = (SCH_SYMBOL*) item;
119 
120                 wxString ref2 = symbol2->GetRef( &sheetList[i] );
121 
122                 if( ref2.CmpNoCase( ref ) != 0 )
123                     continue;
124 
125                 int unit = symbol2->GetUnit();
126 
127                 // The lowest unit number wins.  User should only set fields in any one unit.
128                 // remark: IsVoid() returns true for empty strings or the "~" string (empty
129                 // field value)
130                 if( !symbol2->GetValue( &sheetList[i], m_resolveTextVars ).IsEmpty()
131                         && ( unit < minUnit || value.IsEmpty() ) )
132                 {
133                     value = symbol2->GetValue( &sheetList[i], m_resolveTextVars );
134                 }
135 
136                 if( !symbol2->GetFootprint( &sheetList[i], m_resolveTextVars ).IsEmpty()
137                         && ( unit < minUnit || footprint.IsEmpty() ) )
138                 {
139                     footprint = symbol2->GetFootprint( &sheetList[i], m_resolveTextVars );
140                 }
141 
142                 if( !symbol2->GetField( DATASHEET_FIELD )->IsVoid()
143                         && ( unit < minUnit || datasheet.IsEmpty() ) )
144                 {
145                     if( m_resolveTextVars )
146                         datasheet = symbol2->GetField( DATASHEET_FIELD )->GetShownText();
147                     else
148                         datasheet = symbol2->GetField( DATASHEET_FIELD )->GetText();
149                 }
150 
151                 for( int ii = MANDATORY_FIELDS; ii < symbol2->GetFieldCount(); ++ii )
152                 {
153                     const SCH_FIELD& f = symbol2->GetFields()[ ii ];
154 
155                     if( f.GetText().size()
156                         && ( unit < minUnit || userFields.count( f.GetName() ) == 0 ) )
157                     {
158                         if( m_resolveTextVars )
159                             userFields[ f.GetName() ] = f.GetShownText();
160                         else
161                             userFields[ f.GetName() ] = f.GetText();
162                     }
163                 }
164 
165                 minUnit = std::min( unit, minUnit );
166             }
167         }
168     }
169     else
170     {
171         value = aSymbol->GetValue( aSheet, m_resolveTextVars );
172         footprint = aSymbol->GetFootprint( aSheet, m_resolveTextVars );
173 
174         if( m_resolveTextVars )
175             datasheet = aSymbol->GetField( DATASHEET_FIELD )->GetShownText();
176         else
177             datasheet = aSymbol->GetField( DATASHEET_FIELD )->GetText();
178 
179         for( int ii = MANDATORY_FIELDS; ii < aSymbol->GetFieldCount(); ++ii )
180         {
181             const SCH_FIELD& f = aSymbol->GetFields()[ ii ];
182 
183             if( f.GetText().size() )
184             {
185                 if( m_resolveTextVars )
186                     userFields[ f.GetName() ] = f.GetShownText();
187                 else
188                     userFields[ f.GetName() ] = f.GetText();
189             }
190         }
191     }
192 
193     // Do not output field values blank in netlist:
194     if( value.size() )
195         aNode->AddChild( node( "value", UnescapeString( value ) ) );
196     else    // value field always written in netlist
197         aNode->AddChild( node( "value", "~" ) );
198 
199     if( footprint.size() )
200         aNode->AddChild( node( "footprint", UnescapeString( footprint ) ) );
201 
202     if( datasheet.size() )
203         aNode->AddChild( node( "datasheet", UnescapeString( datasheet ) ) );
204 
205     if( userFields.size() )
206     {
207         XNODE* xfields;
208         aNode->AddChild( xfields = node( "fields" ) );
209 
210         // non MANDATORY fields are output alphabetically
211         for( const std::pair<const wxString, wxString>& f : userFields )
212         {
213             XNODE* xfield = node( "field", UnescapeString( f.second ) );
214             xfield->AddAttribute( "name", UnescapeString( f.first ) );
215             xfields->AddChild( xfield );
216         }
217     }
218 }
219 
220 
makeSymbols(unsigned aCtl)221 XNODE* NETLIST_EXPORTER_XML::makeSymbols( unsigned aCtl )
222 {
223     XNODE* xcomps = node( "components" );
224 
225     m_referencesAlreadyFound.Clear();
226     m_libParts.clear();
227 
228     SCH_SHEET_LIST sheetList = m_schematic->GetSheets();
229 
230     // Output is xml, so there is no reason to remove spaces from the field values.
231     // And XML element names need not be translated to various languages.
232 
233     for( unsigned ii = 0; ii < sheetList.size(); ii++ )
234     {
235         SCH_SHEET_PATH sheet = sheetList[ii];
236         m_schematic->SetCurrentSheet( sheet );
237 
238         auto cmp = [sheet]( SCH_SYMBOL* a, SCH_SYMBOL* b )
239                    {
240                        return ( StrNumCmp( a->GetRef( &sheet, false ),
241                                            b->GetRef( &sheet, false ), true ) < 0 );
242                    };
243 
244         std::set<SCH_SYMBOL*, decltype( cmp )> ordered_symbols( cmp );
245         std::multiset<SCH_SYMBOL*, decltype( cmp )> extra_units( cmp );
246 
247         for( SCH_ITEM* item : sheet.LastScreen()->Items().OfType( SCH_SYMBOL_T ) )
248         {
249             SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
250             auto        test = ordered_symbols.insert( symbol );
251 
252             if( !test.second )
253             {
254                 if( ( *( test.first ) )->m_Uuid > symbol->m_Uuid )
255                 {
256                     extra_units.insert( *( test.first ) );
257                     ordered_symbols.erase( test.first );
258                     ordered_symbols.insert( symbol );
259                 }
260                 else
261                 {
262                     extra_units.insert( symbol );
263                 }
264             }
265         }
266 
267         for( EDA_ITEM* item : ordered_symbols )
268         {
269             SCH_SYMBOL* symbol = findNextSymbol( item, &sheet );
270 
271             if( !symbol
272                || ( ( aCtl & GNL_OPT_BOM ) && !symbol->GetIncludeInBom() )
273                || ( ( aCtl & GNL_OPT_KICAD ) && !symbol->GetIncludeOnBoard() ) )
274             {
275                 continue;
276             }
277 
278             // Output the symbol's elements in order of expected access frequency. This may
279             // not always look best, but it will allow faster execution under XSL processing
280             // systems which do sequential searching within an element.
281 
282             XNODE* xcomp;  // current symbol being constructed
283             xcomps->AddChild( xcomp = node( "comp" ) );
284 
285             xcomp->AddAttribute( "ref", symbol->GetRef( &sheet ) );
286             addSymbolFields( xcomp, symbol, &sheetList[ ii ] );
287 
288             XNODE*  xlibsource;
289             xcomp->AddChild( xlibsource = node( "libsource" ) );
290 
291             // "logical" library name, which is in anticipation of a better search algorithm
292             // for parts based on "logical_lib.part" and where logical_lib is merely the library
293             // name minus path and extension.
294             wxString libName;
295             wxString partName;
296 
297             if( symbol->UseLibIdLookup() )
298             {
299                 libName = symbol->GetLibId().GetLibNickname();
300                 partName = symbol->GetLibId().GetLibItemName();
301             }
302             else
303             {
304                 partName = symbol->GetSchSymbolLibraryName();
305             }
306 
307             xlibsource->AddAttribute( "lib", libName );
308 
309             // We only want the symbol name, not the full LIB_ID.
310             xlibsource->AddAttribute( "part", partName );
311 
312             xlibsource->AddAttribute( "description", symbol->GetDescription() );
313 
314             XNODE* xproperty;
315 
316             std::vector<SCH_FIELD>& fields = symbol->GetFields();
317 
318             for( size_t jj = MANDATORY_FIELDS; jj < fields.size(); ++jj )
319             {
320                 xcomp->AddChild( xproperty = node( "property" ) );
321                 xproperty->AddAttribute( "name", fields[jj].GetCanonicalName() );
322                 xproperty->AddAttribute( "value", fields[jj].GetText() );
323             }
324 
325             for( const SCH_FIELD& sheetField : sheet.Last()->GetFields() )
326             {
327                 xcomp->AddChild( xproperty = node( "property" ) );
328                 xproperty->AddAttribute( "name", sheetField.GetCanonicalName() );
329                 xproperty->AddAttribute( "value", sheetField.GetText() );
330             }
331 
332             if( !symbol->GetIncludeInBom() )
333             {
334                 xcomp->AddChild( xproperty = node( "property" ) );
335                 xproperty->AddAttribute( "name", "exclude_from_bom" );
336             }
337 
338             if( !symbol->GetIncludeOnBoard() )
339             {
340                 xcomp->AddChild( xproperty = node( "property" ) );
341                 xproperty->AddAttribute( "name", "exclude_from_board" );
342             }
343 
344             XNODE* xsheetpath;
345             xcomp->AddChild( xsheetpath = node( "sheetpath" ) );
346 
347             xsheetpath->AddAttribute( "names", sheet.PathHumanReadable() );
348             xsheetpath->AddAttribute( "tstamps", sheet.PathAsString() );
349 
350             XNODE* xunits; // Node for extra units
351             xcomp->AddChild( xunits = node( "tstamps" ) );
352 
353             auto range = extra_units.equal_range( symbol );
354 
355             // Output a series of children with all UUIDs associated with the REFDES
356             for( auto it = range.first; it != range.second; ++it )
357             {
358                 wxString uuid = ( *it )->m_Uuid.AsString();
359 
360                 // Add a space between UUIDs, if not in KICAD mode (i.e.
361                 // using wxXmlDocument::Save()).  KICAD MODE has its own XNODE::Format function.
362                 if( !( aCtl & GNL_OPT_KICAD ) )     // i.e. for .xml format
363                     uuid += ' ';
364 
365                 xunits->AddChild( new XNODE( wxXML_TEXT_NODE, wxEmptyString, uuid ) );
366             }
367 
368             // Output the primary UUID
369             xunits->AddChild(
370                     new XNODE( wxXML_TEXT_NODE, wxEmptyString, symbol->m_Uuid.AsString() ) );
371         }
372     }
373 
374     return xcomps;
375 }
376 
377 
makeDesignHeader()378 XNODE* NETLIST_EXPORTER_XML::makeDesignHeader()
379 {
380     SCH_SCREEN* screen;
381     XNODE*      xdesign = node( "design" );
382     XNODE*      xtitleBlock;
383     XNODE*      xsheet;
384     XNODE*      xcomment;
385     XNODE*      xtextvar;
386     wxString    sheetTxt;
387     wxFileName  sourceFileName;
388 
389     // the root sheet is a special sheet, call it source
390     xdesign->AddChild( node( "source", m_schematic->GetFileName() ) );
391 
392     xdesign->AddChild( node( "date", DateAndTime() ) );
393 
394     // which Eeschema tool
395     xdesign->AddChild( node( "tool", wxString( "Eeschema " ) + GetBuildVersion() ) );
396 
397     const std::map<wxString, wxString>& properties = m_schematic->Prj().GetTextVars();
398 
399     for( const std::pair<const wxString, wxString>& prop : properties )
400     {
401         xdesign->AddChild( xtextvar = node( "textvar", prop.second ) );
402         xtextvar->AddAttribute( "name", prop.first );
403     }
404 
405     /*
406      *  Export the sheets information
407      */
408     SCH_SHEET_LIST sheetList = m_schematic->GetSheets();
409 
410     for( unsigned i = 0;  i < sheetList.size();  i++ )
411     {
412         screen = sheetList[i].LastScreen();
413 
414         xdesign->AddChild( xsheet = node( "sheet" ) );
415 
416         // get the string representation of the sheet index number.
417         // Note that sheet->GetIndex() is zero index base and we need to increment the
418         // number by one to make it human readable
419         sheetTxt.Printf( "%u", i + 1 );
420         xsheet->AddAttribute( "number", sheetTxt );
421         xsheet->AddAttribute( "name", sheetList[i].PathHumanReadable() );
422         xsheet->AddAttribute( "tstamps", sheetList[i].PathAsString() );
423 
424         TITLE_BLOCK tb = screen->GetTitleBlock();
425         PROJECT*    prj = &m_schematic->Prj();
426 
427         xsheet->AddChild( xtitleBlock = node( "title_block" ) );
428 
429         xtitleBlock->AddChild( node( "title", ExpandTextVars( tb.GetTitle(), prj ) ) );
430         xtitleBlock->AddChild( node( "company", ExpandTextVars( tb.GetCompany(), prj ) ) );
431         xtitleBlock->AddChild( node( "rev", ExpandTextVars( tb.GetRevision(), prj ) ) );
432         xtitleBlock->AddChild( node( "date", ExpandTextVars( tb.GetDate(), prj ) ) );
433 
434         // We are going to remove the fileName directories.
435         sourceFileName = wxFileName( screen->GetFileName() );
436         xtitleBlock->AddChild( node( "source", sourceFileName.GetFullName() ) );
437 
438         xtitleBlock->AddChild( xcomment = node( "comment" ) );
439         xcomment->AddAttribute( "number", "1" );
440         xcomment->AddAttribute( "value", ExpandTextVars( tb.GetComment( 0 ), prj ) );
441 
442         xtitleBlock->AddChild( xcomment = node( "comment" ) );
443         xcomment->AddAttribute( "number", "2" );
444         xcomment->AddAttribute( "value", ExpandTextVars( tb.GetComment( 1 ), prj ) );
445 
446         xtitleBlock->AddChild( xcomment = node( "comment" ) );
447         xcomment->AddAttribute( "number", "3" );
448         xcomment->AddAttribute( "value", ExpandTextVars( tb.GetComment( 2 ), prj ) );
449 
450         xtitleBlock->AddChild( xcomment = node( "comment" ) );
451         xcomment->AddAttribute( "number", "4" );
452         xcomment->AddAttribute( "value", ExpandTextVars( tb.GetComment( 3 ), prj ) );
453 
454         xtitleBlock->AddChild( xcomment = node( "comment" ) );
455         xcomment->AddAttribute( "number", "5" );
456         xcomment->AddAttribute( "value", ExpandTextVars( tb.GetComment( 4 ), prj ) );
457 
458         xtitleBlock->AddChild( xcomment = node( "comment" ) );
459         xcomment->AddAttribute( "number", "6" );
460         xcomment->AddAttribute( "value", ExpandTextVars( tb.GetComment( 5 ), prj ) );
461 
462         xtitleBlock->AddChild( xcomment = node( "comment" ) );
463         xcomment->AddAttribute( "number", "7" );
464         xcomment->AddAttribute( "value", ExpandTextVars( tb.GetComment( 6 ), prj ) );
465 
466         xtitleBlock->AddChild( xcomment = node( "comment" ) );
467         xcomment->AddAttribute( "number", "8" );
468         xcomment->AddAttribute( "value", ExpandTextVars( tb.GetComment( 7 ), prj ) );
469 
470         xtitleBlock->AddChild( xcomment = node( "comment" ) );
471         xcomment->AddAttribute( "number", "9" );
472         xcomment->AddAttribute( "value", ExpandTextVars( tb.GetComment( 8 ), prj ) );
473     }
474 
475     return xdesign;
476 }
477 
478 
makeLibraries()479 XNODE* NETLIST_EXPORTER_XML::makeLibraries()
480 {
481     XNODE*            xlibs = node( "libraries" );     // auto_ptr
482     SYMBOL_LIB_TABLE* symbolLibTable = m_schematic->Prj().SchSymbolLibTable();
483 
484     for( std::set<wxString>::iterator it = m_libraries.begin(); it!=m_libraries.end();  ++it )
485     {
486         wxString    libNickname = *it;
487         XNODE*      xlibrary;
488 
489         if( symbolLibTable->HasLibrary( libNickname ) )
490         {
491             xlibs->AddChild( xlibrary = node( "library" ) );
492             xlibrary->AddAttribute( "logical", libNickname );
493             xlibrary->AddChild( node( "uri", symbolLibTable->GetFullURI( libNickname ) ) );
494         }
495 
496         // @todo: add more fun stuff here
497     }
498 
499     return xlibs;
500 }
501 
502 
makeLibParts()503 XNODE* NETLIST_EXPORTER_XML::makeLibParts()
504 {
505     XNODE*                  xlibparts = node( "libparts" );   // auto_ptr
506 
507     LIB_PINS                pinList;
508     std::vector<LIB_FIELD*> fieldList;
509 
510     m_libraries.clear();
511 
512     for( auto lcomp : m_libParts )
513     {
514         wxString libNickname = lcomp->GetLibId().GetLibNickname();;
515 
516         // The library nickname will be empty if the cache library is used.
517         if( !libNickname.IsEmpty() )
518             m_libraries.insert( libNickname );  // inserts symbol's library if unique
519 
520         XNODE* xlibpart;
521         xlibparts->AddChild( xlibpart = node( "libpart" ) );
522         xlibpart->AddAttribute( "lib", libNickname );
523         xlibpart->AddAttribute( "part", lcomp->GetName()  );
524 
525         //----- show the important properties -------------------------
526         if( !lcomp->GetDescription().IsEmpty() )
527             xlibpart->AddChild( node( "description", lcomp->GetDescription() ) );
528 
529         if( !lcomp->GetDatasheetField().GetText().IsEmpty() )
530             xlibpart->AddChild( node( "docs",  lcomp->GetDatasheetField().GetText() ) );
531 
532         // Write the footprint list
533         if( lcomp->GetFPFilters().GetCount() )
534         {
535             XNODE*  xfootprints;
536             xlibpart->AddChild( xfootprints = node( "footprints" ) );
537 
538             for( unsigned i = 0; i < lcomp->GetFPFilters().GetCount(); ++i )
539                 xfootprints->AddChild( node( "fp", lcomp->GetFPFilters()[i] ) );
540         }
541 
542         //----- show the fields here ----------------------------------
543         fieldList.clear();
544         lcomp->GetFields( fieldList );
545 
546         XNODE*     xfields;
547         xlibpart->AddChild( xfields = node( "fields" ) );
548 
549         for( const LIB_FIELD* field : fieldList )
550         {
551             if( !field->GetText().IsEmpty() )
552             {
553                 XNODE*     xfield;
554                 xfields->AddChild( xfield = node( "field", field->GetText() ) );
555                 xfield->AddAttribute( "name", field->GetCanonicalName() );
556             }
557         }
558 
559         //----- show the pins here ------------------------------------
560         pinList.clear();
561         lcomp->GetPins( pinList, 0, 0 );
562 
563         /* we must erase redundant Pins references in pinList
564          * These redundant pins exist because some pins
565          * are found more than one time when a symbol has
566          * multiple parts per package or has 2 representations (DeMorgan conversion)
567          * For instance, a 74ls00 has DeMorgan conversion, with different pin shapes,
568          * and therefore each pin  appears 2 times in the list.
569          * Common pins (VCC, GND) can also be found more than once.
570          */
571         sort( pinList.begin(), pinList.end(), sortPinsByNumber );
572         for( int ii = 0; ii < (int)pinList.size()-1; ii++ )
573         {
574             if( pinList[ii]->GetNumber() == pinList[ii+1]->GetNumber() )
575             {   // 2 pins have the same number, remove the redundant pin at index i+1
576                 pinList.erase(pinList.begin() + ii + 1);
577                 ii--;
578             }
579         }
580 
581         if( pinList.size() )
582         {
583             XNODE*     pins;
584 
585             xlibpart->AddChild( pins = node( "pins" ) );
586             for( unsigned i=0; i<pinList.size();  ++i )
587             {
588                 XNODE*     pin;
589 
590                 pins->AddChild( pin = node( "pin" ) );
591                 pin->AddAttribute( "num", pinList[i]->GetShownNumber() );
592                 pin->AddAttribute( "name", pinList[i]->GetShownName() );
593                 pin->AddAttribute( "type", pinList[i]->GetCanonicalElectricalTypeName() );
594 
595                 // caution: construction work site here, drive slowly
596             }
597         }
598     }
599 
600     return xlibparts;
601 }
602 
603 
makeListOfNets(unsigned aCtl)604 XNODE* NETLIST_EXPORTER_XML::makeListOfNets( unsigned aCtl )
605 {
606     XNODE*      xnets = node( "nets" );      // auto_ptr if exceptions ever get used.
607     wxString    netCodeTxt;
608     wxString    netName;
609     wxString    ref;
610 
611     XNODE*      xnet = nullptr;
612 
613     /*  output:
614         <net code="123" name="/cfcard.sch/WAIT#">
615             <node ref="R23" pin="1"/>
616             <node ref="U18" pin="12"/>
617         </net>
618     */
619 
620     struct NET_NODE
621     {
622         NET_NODE( SCH_PIN* aPin, const SCH_SHEET_PATH& aSheet, bool aNoConnect ) :
623                 m_Pin( aPin ),
624                 m_Sheet( aSheet ),
625                 m_NoConnect( aNoConnect )
626         {}
627 
628         SCH_PIN*       m_Pin;
629         SCH_SHEET_PATH m_Sheet;
630         bool           m_NoConnect;
631     };
632 
633     struct NET_RECORD
634     {
635         NET_RECORD( const wxString& aName ) :
636             m_Name( aName )
637         {};
638 
639         wxString              m_Name;
640         std::vector<NET_NODE> m_Nodes;
641     };
642 
643     std::vector<NET_RECORD*> nets;
644 
645     for( const auto& it : m_schematic->ConnectionGraph()->GetNetMap() )
646     {
647         wxString    net_name  = it.first.first;
648         auto        subgraphs = it.second;
649         NET_RECORD* net_record;
650 
651         if( subgraphs.empty() )
652             continue;
653 
654         nets.emplace_back( new NET_RECORD( net_name ) );
655         net_record = nets.back();
656 
657         for( CONNECTION_SUBGRAPH* subgraph : subgraphs )
658         {
659             bool nc = subgraph->m_no_connect && subgraph->m_no_connect->Type() == SCH_NO_CONNECT_T;
660             const SCH_SHEET_PATH& sheet = subgraph->m_sheet;
661 
662             for( SCH_ITEM* item : subgraph->m_items )
663             {
664                 if( item->Type() == SCH_PIN_T )
665                 {
666                     SCH_PIN*    pin = static_cast<SCH_PIN*>( item );
667                     SCH_SYMBOL* symbol = pin->GetParentSymbol();
668 
669                     if( !symbol
670                        || ( ( aCtl & GNL_OPT_BOM ) && !symbol->GetIncludeInBom() )
671                        || ( ( aCtl & GNL_OPT_KICAD ) && !symbol->GetIncludeOnBoard() ) )
672                     {
673                         continue;
674                     }
675 
676                     net_record->m_Nodes.emplace_back( pin, sheet, nc );
677                 }
678             }
679         }
680     }
681 
682     // Netlist ordering: Net name, then ref des, then pin name
683     std::sort( nets.begin(), nets.end(),
684                []( const NET_RECORD* a, const NET_RECORD*b )
685                {
686                    return StrNumCmp( a->m_Name, b->m_Name ) < 0;
687                } );
688 
689     for( int i = 0; i < (int) nets.size(); ++i )
690     {
691         NET_RECORD* net_record = nets[i];
692         bool        added = false;
693         XNODE*      xnode;
694 
695         // Netlist ordering: Net name, then ref des, then pin name
696         std::sort( net_record->m_Nodes.begin(), net_record->m_Nodes.end(),
697                    []( const NET_NODE& a, const NET_NODE& b )
698                    {
699                        wxString refA = a.m_Pin->GetParentSymbol()->GetRef( &a.m_Sheet );
700                        wxString refB = b.m_Pin->GetParentSymbol()->GetRef( &b.m_Sheet );
701 
702                        if( refA == refB )
703                            return a.m_Pin->GetShownNumber() < b.m_Pin->GetShownNumber();
704 
705                        return refA < refB;
706                    } );
707 
708         // Some duplicates can exist, for example on multi-unit parts with duplicated
709         // pins across units.  If the user connects the pins on each unit, they will
710         // appear on separate subgraphs.  Remove those here:
711         net_record->m_Nodes.erase(
712                 std::unique( net_record->m_Nodes.begin(), net_record->m_Nodes.end(),
713                         []( const NET_NODE& a, const NET_NODE& b )
714                         {
715                             wxString refA = a.m_Pin->GetParentSymbol()->GetRef( &a.m_Sheet );
716                             wxString refB = b.m_Pin->GetParentSymbol()->GetRef( &b.m_Sheet );
717 
718                             return refA == refB
719                                         && a.m_Pin->GetShownNumber() == b.m_Pin->GetShownNumber();
720                         } ),
721                 net_record->m_Nodes.end() );
722 
723         for( const NET_NODE& netNode : net_record->m_Nodes )
724         {
725             wxString refText = netNode.m_Pin->GetParentSymbol()->GetRef( &netNode.m_Sheet );
726             wxString pinText = netNode.m_Pin->GetShownNumber();
727 
728             // Skip power symbols and virtual symbols
729             if( refText[0] == wxChar( '#' ) )
730                 continue;
731 
732             if( !added )
733             {
734                 netCodeTxt.Printf( "%d", i + 1 );
735 
736                 xnets->AddChild( xnet = node( "net" ) );
737                 xnet->AddAttribute( "code", netCodeTxt );
738                 xnet->AddAttribute( "name", net_record->m_Name );
739 
740                 added = true;
741             }
742 
743             xnet->AddChild( xnode = node( "node" ) );
744             xnode->AddAttribute( "ref", refText );
745             xnode->AddAttribute( "pin", pinText );
746 
747             wxString pinName = netNode.m_Pin->GetShownName();
748             wxString pinType = netNode.m_Pin->GetCanonicalElectricalTypeName();
749 
750             if( !pinName.IsEmpty() )
751                 xnode->AddAttribute( "pinfunction", pinName );
752 
753             if( netNode.m_NoConnect )
754                 pinType += "+no_connect";
755 
756             xnode->AddAttribute( "pintype", pinType );
757         }
758     }
759 
760     for( NET_RECORD* record : nets )
761         delete record;
762 
763     return xnets;
764 }
765 
766 
node(const wxString & aName,const wxString & aTextualContent)767 XNODE* NETLIST_EXPORTER_XML::node( const wxString& aName,
768                                    const wxString& aTextualContent /* = wxEmptyString*/ )
769 {
770     XNODE* n = new XNODE( wxXML_ELEMENT_NODE, aName );
771 
772     if( aTextualContent.Len() > 0 )     // excludes wxEmptyString, the parameter's default value
773         n->AddChild( new XNODE( wxXML_TEXT_NODE, wxEmptyString, aTextualContent ) );
774 
775     return n;
776 }
777 
778 
sortPinsByNumber(LIB_PIN * aPin1,LIB_PIN * aPin2)779 static bool sortPinsByNumber( LIB_PIN* aPin1, LIB_PIN* aPin2 )
780 {
781     // return "lhs < rhs"
782     return StrNumCmp( aPin1->GetShownNumber(), aPin2->GetShownNumber(), true ) < 0;
783 }
784