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