44#include <wx/tokenzr.h>
51 std::string
name = aProposedName;
57 name = fmt::format(
"{}#{}", aProposedName, ii++ );
67 std::vector<EMBEDDED_FILES*> embeddedFilesStack;
69 m_libMgr.SetFilesStack( std::move( embeddedFilesStack ) );
121 aFormatter.
Print( 0,
".title KiCad schematic\n" );
127 aFormatter.
Print( 0,
".end\n" );
134 std::set<std::string> refNames;
136 wxString variant =
m_schematic->GetCurrentVariant();
149 cacheDir.AppendDir( wxT(
"ibis" ) );
151 if( !cacheDir.DirExists() )
153 cacheDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
155 if( !cacheDir.DirExists() )
157 wxLogTrace( wxT(
"IBIS_CACHE:" ),
158 wxT(
"%s:%s:%d\n * failed to create ibis cache directory '%s'" ),
159 __FILE__, __FUNCTION__, __LINE__, cacheDir.GetPath() );
166 wxString dirName = cacheDir.GetFullPath();
168 if( !dir.Open( dirName ) )
172 wxArrayString fileList;
173 wxString fileSpec = wxT(
"*.cache" );
175 thisFile.SetPath( dirName );
177 size_t numFilesFound = wxDir::GetAllFiles( dirName, &fileList, fileSpec );
179 for(
size_t ii = 0; ii < numFilesFound; ii++ )
182 thisFile.SetFullName( fileList[ii] );
183 wxRemoveFile( thisFile.GetFullPath() );
198 std::vector<PIN_INFO> pins =
CreatePinList( symbol, sheet,
true );
205 spiceItem.
fields.back().SetText( symbol->
GetRef( &sheet ) );
207 spiceItem.
fields.back().SetText( field.GetShownText( &sheet,
false, 0, variant ) );
210 readRefName( sheet, *symbol, spiceItem, refNames );
211 readModel( sheet, *symbol, spiceItem, variant, aReporter );
217 m_items.push_back( std::move( spiceItem ) );
233 std::unique_ptr<MARKUP::NODE> root = markupParser.
Parse();
235 std::function<void(
const std::unique_ptr<MARKUP::NODE>&)> convertMarkup =
236 [&](
const std::unique_ptr<MARKUP::NODE>& aNode )
240 if( !aNode->is_root() )
242 if( aNode->isOverbar() )
247 else if( aNode->isSubscript() || aNode->isSuperscript() )
252 if( aNode->has_content() )
253 *aNetName += aNode->string();
256 for(
const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
257 convertMarkup( child );
261 *aNetName = wxEmptyString;
262 convertMarkup( root );
265 aNetName->Replace(
'%',
'_' );
266 aNetName->Replace(
'(',
'_' );
267 aNetName->Replace(
')',
'_' );
268 aNetName->Replace(
',',
'_' );
269 aNetName->Replace(
'[',
'_' );
270 aNetName->Replace(
']',
'_' );
271 aNetName->Replace(
'<',
'_' );
272 aNetName->Replace(
'>',
'_' );
273 aNetName->Replace(
'~',
'_' );
274 aNetName->Replace(
' ',
'_' );
278 if( aNetName->EndsWith( wxS(
"/0" ) ) && !aNetName->EndsWith( wxS(
"//0" ) ) )
279 aNetName->assign( wxS(
"0" ) );
283 if( aNetName->IsSameAs( wxS(
"/gnd" ),
false ) )
284 aNetName->assign( aNetName->Mid( 1 ) );
288 if( aNetName->StartsWith( wxS(
"//" ) ) )
289 aNetName->Replace( wxS(
"//" ), wxS(
"/root/" ),
false );
296 return item->model->SpiceGenerator().ItemName( *item );
298 return wxEmptyString;
304 const std::string refName = aRefName.ToStdString();
305 const std::list<SPICE_ITEM>& spiceItems =
GetItems();
307 auto it = std::find_if( spiceItems.begin(), spiceItems.end(),
310 return item.refName == refName;
313 if( it != spiceItems.end() )
328 for(
SCH_ITEM* item : sheet.LastScreen()->Items() )
330 if( item->ResolveExcludedFromSim() )
334 text =
static_cast<SCH_TEXT*
>( item )->GetShownText( &sheet,
false );
336 text =
static_cast<SCH_TEXTBOX*
>( item )->GetShownText(
nullptr, &sheet,
false );
341 wxStringTokenizer tokenizer(
text,
"\r\n", wxTOKEN_STRTOK );
342 bool foundDirective =
false;
345 [](
const wxString& line,
const wxString& dir )
347 return line == dir || line.StartsWith( dir + wxS(
" " ) );
350 while( tokenizer.HasMoreTokens() )
352 wxString line = tokenizer.GetNextToken().Upper();
354 if( line.StartsWith( wxT(
"." ) ) )
356 if( isDirective( line, wxS(
".AC" ) )
357 || isDirective( line, wxS(
".CONTROL" ) )
358 || isDirective( line, wxS(
".CSPARAM" ) )
359 || isDirective( line, wxS(
".DISTO" ) )
360 || isDirective( line, wxS(
".DC" ) )
361 || isDirective( line, wxS(
".ELSE" ) )
362 || isDirective( line, wxS(
".ELSEIF" ) )
363 || isDirective( line, wxS(
".END" ) )
364 || isDirective( line, wxS(
".ENDC" ) )
365 || isDirective( line, wxS(
".ENDIF" ) )
366 || isDirective( line, wxS(
".ENDS" ) )
367 || isDirective( line, wxS(
".FOUR" ) )
368 || isDirective( line, wxS(
".FUNC" ) )
369 || isDirective( line, wxS(
".GLOBAL" ) )
370 || isDirective( line, wxS(
".IC" ) )
371 || isDirective( line, wxS(
".IF" ) )
372 || isDirective( line, wxS(
".INCLUDE" ) )
373 || isDirective( line, wxS(
".LIB" ) )
374 || isDirective( line, wxS(
".MEAS" ) )
375 || isDirective( line, wxS(
".MODEL" ) )
376 || isDirective( line, wxS(
".NODESET" ) )
377 || isDirective( line, wxS(
".NOISE" ) )
378 || isDirective( line, wxS(
".OP" ) )
379 || isDirective( line, wxS(
".OPTIONS" ) )
380 || isDirective( line, wxS(
".PARAM" ) )
381 || isDirective( line, wxS(
".PLOT" ) )
382 || isDirective( line, wxS(
".PRINT" ) )
383 || isDirective( line, wxS(
".PROBE" ) )
384 || isDirective( line, wxS(
".PZ" ) )
385 || isDirective( line, wxS(
".SAVE" ) )
386 || isDirective( line, wxS(
".SENS" ) )
387 || isDirective( line, wxS(
".SP" ) )
388 || isDirective( line, wxS(
".SUBCKT" ) )
389 || isDirective( line, wxS(
".TEMP" ) )
390 || isDirective( line, wxS(
".TF" ) )
391 || isDirective( line, wxS(
".TITLE" ) )
392 || isDirective( line, wxS(
".TRAN" ) )
393 || isDirective( line, wxS(
".WIDTH" ) ) )
395 foundDirective =
true;
399 else if( line.StartsWith( wxT(
"K" ) ) )
402 wxStringTokenizer line_t( line,
" \t", wxTOKEN_STRTOK );
405 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT(
"K" ) ) )
409 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT(
"L" ) ) )
413 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT(
"L" ) ) )
418 if( line_t.HasMoreTokens() )
420 foundDirective =
true;
435 const wxString& aVariantName )
439 return wxEmptyString;
441 wxString ref = aSymbol.
GetRef( &aSheet );
442 std::vector<std::pair<wxString, wxString>> pinList;
443 std::set<wxString> pinNumbers;
446 auto parsePins = [&](
const wxString& aPins )
448 wxStringTokenizer tokenizer( aPins, wxS(
" \t\r\n" ), wxTOKEN_STRTOK );
450 while( tokenizer.HasMoreTokens() )
452 wxString token = tokenizer.GetNextToken();
453 int pos = token.Find( wxS(
'=' ) );
455 if( pos == wxNOT_FOUND )
458 wxString pinNumber = token.Left( pos );
459 wxString modelPin = token.Mid( pos + 1 );
462 if( pinNumbers.insert( pinNumber ).second )
463 pinList.emplace_back( pinNumber, modelPin );
469 parsePins( pinsField->GetShownText( &aSheet,
false, 0, aVariantName ) );
478 if( other == &aSymbol )
481 if( other->
GetRef( &sheet ) != ref )
485 parsePins( pinsField->GetShownText( &sheet,
false, 0, aVariantName ) );
491 if( pinList.empty() )
492 return wxEmptyString;
497 for(
const auto& [pinNumber, modelPin] : pinList )
499 if( !merged.IsEmpty() )
500 merged += wxS(
" " );
502 merged += pinNumber + wxS(
"=" ) + modelPin;
511 const wxString& aVariantName )
513 std::vector<UNIT_PIN_MAP> unitMaps;
518 wxString ref = aSymbol.
GetRef( &aSheet );
519 std::set<int> seenUnits;
529 wxString pins = pinsField->
GetShownText( &aUnitSheet,
false, 0, aVariantName );
533 if( !seenUnits.insert( aUnit.
GetUnit() ).second )
540 if( !map.
pins.empty() )
541 unitMaps.push_back( std::move( map ) );
546 parseUnit( aSymbol, aSheet );
554 if( other == &aSymbol )
557 if( other->
GetRef( &sheet ).CmpNoCase( ref ) != 0 )
560 parseUnit( *other, sheet );
566 std::sort( unitMaps.begin(), unitMaps.end(),
569 return lhs.unit < rhs.unit;
578 const wxString& aVariantName )
const
584 return field->GetShownText( &aUnitSheet,
false, 0, aVariantName );
586 return wxEmptyString;
591 wxString value = read( aSymbol, aSheet );
595 wxString ref = aSymbol.
GetRef( &aSheet );
603 if( other == &aSymbol || other->
GetRef( &sheet ).CmpNoCase( ref ) != 0 )
606 value = read( *other, sheet );
608 if( !value.IsEmpty() )
619 SPICE_ITEM& aItem, std::set<std::string>& aRefNames )
623 [[maybe_unused]]
bool inserted = aRefNames.insert( aItem.
refName ).second;
624 wxASSERT_MSG( inserted, wxT(
"Duplicate refdes encountered; what happened to ReadyToNetlist()?" ) );
629 const wxString& aVariantName,
REPORTER& aReporter )
638 wxString mergedSimPins = repeat ? wxString()
642 aReporter, mergedSimPins );
652 if( libModel.
model.
GetType() != SIM_MODEL::TYPE::SUBCKT || libModel.
name.empty() )
655 _(
"Symbol '%s' uses repeat-per-unit decomposition, which requires a named "
656 "subcircuit model." ),
657 aSymbol.
GetRef( &aSheet ) ) );
660 std::vector<UNIT_PIN_MAP> unitMaps =
collectUnitPinMaps( aSymbol, aSheet, aVariantName );
667 auto wrapper = std::make_unique<SIM_MODEL_MULTIUNIT>( libModel.
model, libModel.
name, unitMaps,
696 const std::vector<PIN_INFO>& aPins )
704 const std::vector<PIN_INFO>& aPins,
int& aNcCounter )
706 for(
const PIN_INFO& pinInfo : aPins )
710 aItem.
pinNetNames.push_back( netName.ToStdString() );
717 std::vector<std::string>& aModifiers )
724 tao::pegtl::string_input<> in( input,
"Sim.NodesFormat field" );
725 std::unique_ptr<tao::pegtl::parse_tree::node> root;
726 std::string singleNodeModifier;
734 for(
const auto& node : root->children )
739 aModifiers.back().append( node->string() );
743 singleNodeModifier.append( node->string() );
748 aModifiers.push_back( singleNodeModifier );
749 singleNodeModifier.erase( singleNodeModifier.begin(), singleNodeModifier.end() );
753 catch(
const tao::pegtl::parse_error& e )
755 THROW_IO_ERROR( wxString::Format(
_(
"Error in parsing model '%s', error: '%s'" ),
761 std::vector<std::string> xspicePattern;
764 if( xspicePattern.empty() )
767 if( xspicePattern.size() != aItem.
pinNetNames.size() )
769 THROW_IO_ERROR( wxString::Format(
_(
"Error in parsing model '%s', wrong number of nodes "
770 "'?' in Sim.NodesFormat compared to connections" ),
777 for( std::string& pattern : xspicePattern )
780 const std::string netName =
" " + *itNetNames +
" ";
781 pattern.replace( pattern.find(
"?" ), 1, netName );
782 *itNetNames = pattern;
788 const wxString& aPath )
794 expandedPath.Replace(
'\\',
'/' );
803 if( fullPath.IsEmpty() )
805 wxLogError(
_(
"Could not find library file '%s'" ), expandedPath );
806 fullPath = expandedPath;
808 else if( wxFileName::GetPathSeparator() ==
'\\' )
811 fullPath.Replace(
'\\',
'/' );
816 fullPath = expandedPath;
819 aFormatter.
Print( 0,
".include \"%s\"\n",
TO_UTF8( fullPath ) );
838 std::set<std::string> emittedWrappers;
842 if( !item.model->IsEnabled() )
848 && !emittedWrappers.insert( item.modelName ).second )
853 aFormatter.
Print( 0,
"%s", item.model->SpiceGenerator().ModelLine( item ).c_str() );
862 if( !item.model->IsEnabled() )
865 aFormatter.
Print( 0,
"%s", item.model->SpiceGenerator().ItemLine( item ).c_str() );
874 aFormatter.
Print( 0,
".save all\n" );
877 aFormatter.
Print( 0,
".probe alli\n" );
885 std::string itemName = item.model->SpiceGenerator().ItemName( item );
887 if( ( item.model->GetPinCount() >= 2 ) && ( itemName.size() > 0 )
888 && ( itemName.c_str()[0] !=
'A' ) )
890 aFormatter.
Print( 0,
".probe p(%s)\n", itemName.c_str() );
896 [](
const wxString& candidate,
const wxString& dir )
898 return candidate == dir || candidate.StartsWith( dir + wxS(
" " ) );
903 bool simCommand =
false;
905 if( directive.StartsWith(
"." ) )
907 wxString candidate = directive.Upper();
909 simCommand = ( isSimCommand( candidate, wxS(
".AC" ) )
910 || isSimCommand( candidate, wxS(
".DC" ) )
911 || isSimCommand( candidate, wxS(
".TRAN" ) )
912 || isSimCommand( candidate, wxS(
".OP" ) )
913 || isSimCommand( candidate, wxS(
".DISTO" ) )
914 || isSimCommand( candidate, wxS(
".NOISE" ) )
915 || isSimCommand( candidate, wxS(
".PZ" ) )
916 || isSimCommand( candidate, wxS(
".SENS" ) )
917 || isSimCommand( candidate, wxS(
".TF" ) ) );
921 aFormatter.
Print( 0,
"%s\n",
UTF8( directive ).c_str() );
927 int& aNcCounter )
const
933 if( netName.IsEmpty() )
934 netName.Printf( wxS(
"NC-%d" ), aNcCounter++ );
949 std::erase_if( sheets,
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
int GetUnitCount() const override
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
std::unique_ptr< NODE > Parse()
std::unordered_set< std::string > m_names
std::string Generate(const std::string &aProposedName)
SCHEMATIC * m_schematic
The schematic we're generating a netlist for.
std::vector< PIN_INFO > CreatePinList(SCH_SYMBOL *aSymbol, const SCH_SHEET_PATH &aSheetPath, bool aKeepUnconnectedPins)
Find a symbol from the DrawList and builds its pin list.
SCH_SYMBOL * findNextSymbol(EDA_ITEM *aItem, const SCH_SHEET_PATH &aSheetPath)
Check if the given symbol should be processed for netlisting.
std::set< LIB_SYMBOL *, LIB_SYMBOL_LESS_THAN > m_libParts
unique library symbols used. LIB_SYMBOL items are sorted by names
NETLIST_EXPORTER_BASE(SCHEMATIC *aSchematic)
UNIQUE_STRINGS m_referencesAlreadyFound
Used for "multiple symbols per package" symbols to avoid processing a lib symbol more than once.
void readPinNetNames(SCH_SYMBOL &aSymbol, SPICE_ITEM &aItem, const std::vector< PIN_INFO > &aPins, int &aNcCounter)
void writeModels(OUTPUTFORMATTER &aFormatter)
void writeIncludes(OUTPUTFORMATTER &aFormatter, unsigned aNetlistOptions)
std::list< SPICE_ITEM > m_items
Owns the synthesized repeat-per-unit wrappers referenced by m_items.
void getNodePattern(SPICE_ITEM &aItem, std::vector< std::string > &aModifiers)
static void ConvertToSpiceMarkup(wxString *aNetName)
Remove formatting wrappers and replace illegal spice net name characters with underscores.
void ReadDirectives(unsigned aNetlistOptions)
@ OPTION_SAVE_ALL_CURRENTS
@ OPTION_SAVE_ALL_VOLTAGES
@ OPTION_SAVE_ALL_DISSIPATIONS
@ OPTION_ADJUST_INCLUDE_PATHS
@ OPTION_CUR_SHEET_AS_ROOT
SCH_SHEET_LIST BuildSheetList(unsigned aNetlistOptions=0) const
Return the paths of exported sheets (either all or the current one).
virtual wxString GenerateItemPinNetName(const wxString &aNetName, int &aNcCounter) const
std::set< wxString > m_nets
Items representing schematic symbols in Spice world.
void readRefName(SCH_SHEET_PATH &aSheet, SCH_SYMBOL &aSymbol, SPICE_ITEM &aItem, std::set< std::string > &aRefNames)
wxString GetItemName(const wxString &aRefName) const
Return name of Spice device corresponding to a schematic symbol.
void writeItems(OUTPUTFORMATTER &aFormatter)
std::vector< wxString > m_directives
Spice directives found in the schematic sheet.
virtual void WriteHead(OUTPUTFORMATTER &aFormatter, unsigned aNetlistOptions)
Write the netlist head (title and so on).
void writeInclude(OUTPUTFORMATTER &aFormatter, unsigned aNetlistOptions, const wxString &aPath)
virtual void WriteDirectives(const wxString &aSimCommand, unsigned aSimOptions, OUTPUTFORMATTER &candidate) const
NETLIST_EXPORTER_SPICE(SCHEMATIC *aSchematic)
wxString collectMergedSimPins(SCH_SYMBOL &aSymbol, const SCH_SHEET_PATH &aSheet, const wxString &aVariantName)
Collect merged Sim.Pins from all units of a multi-unit symbol.
SIM_LIB_MGR m_libMgr
Holds libraries and models.
const SPICE_ITEM * FindItem(const wxString &aRefName) const
Find and return the item corresponding to aRefName.
virtual bool ReadSchematicAndLibraries(unsigned aNetlistOptions, REPORTER &aReporter)
Process the schematic and Spice libraries to create net mapping and a list of SPICE_ITEMs.
SIM_DECOMPOSITION getDecomposition(SCH_SYMBOL &aSymbol, const SCH_SHEET_PATH &aSheet, const wxString &aVariantName) const
Read and parse the Sim.Decomposition field from the primary unit.
bool DoWriteNetlist(const wxString &aSimCommand, unsigned aSimOptions, OUTPUTFORMATTER &aFormatter, REPORTER &aReporter)
Write the netlist in aFormatter.
void readPinNumbers(SCH_SYMBOL &aSymbol, SPICE_ITEM &aItem, const std::vector< PIN_INFO > &aPins)
const std::list< SPICE_ITEM > & GetItems() const
Return the list of items representing schematic symbols in the Spice world.
void readNodePattern(SPICE_ITEM &aItem)
std::set< wxString > m_rawIncludes
include directives found in symbols
void readModel(SCH_SHEET_PATH &aSheet, SCH_SYMBOL &aSymbol, SPICE_ITEM &aItem, const wxString &aVariantName, REPORTER &aReporter)
virtual void WriteTail(OUTPUTFORMATTER &aFormatter, unsigned aNetlistOptions)
Write the tail (.end).
NAME_GENERATOR m_modelNameGenerator
Generates unique model names.
std::vector< std::unique_ptr< SIM_MODEL_MULTIUNIT > > m_multiunitModels
bool WriteNetlist(const wxString &aOutFileName, unsigned aNetlistOptions, REPORTER &aReporter) override
Write to specified output file.
std::vector< UNIT_PIN_MAP > collectUnitPinMaps(SCH_SYMBOL &aSymbol, const SCH_SHEET_PATH &aSheet, const wxString &aVariantName)
Gather every unit's Sim.Pins mapping for a multi-unit symbol, one entry per unit.
static wxString GetUserCachePath()
Gets the stock (install) 3d viewer plugins path.
A pure virtual class used to derive REPORTER objects from.
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
virtual bool HasMessageOfSeverity(int aSeverityMask) const
Returns true if the reporter has one or more messages matching the specified severity mask.
Holds all the data relating to one schematic.
EMBEDDED_FILES * GetEmbeddedFiles() override
wxString GetShownText(const SCH_SHEET_PATH *aPath, bool aAllowExtraText, int aDepth=0, const wxString &aVariantName=wxEmptyString) const
Base class for any item which can be embedded within the SCHEMATIC container class,...
bool ResolveExcludedFromSim(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
bool GetExcludedFromSim() const
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly) const override
Populate a std::vector with SCH_FIELDs, sorted in ordinal order.
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
Wraps a resolved single-unit base model and presents it as one component-level SPICE device.
virtual bool requiresSpiceModelLine(const SPICE_ITEM &aItem) const
const SPICE_GENERATOR & SpiceGenerator() const
virtual std::vector< wxString > GetSpiceIncludes(const SPICE_ITEM &aItem, SCHEMATIC *aSchematic, REPORTER &aReporter) const
Return the external files this model must pull into the netlist as .include directives,...
virtual std::string ModelName(const SPICE_ITEM &aItem) const
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
This file is part of the common library.
wxString ResolveFile(const wxString &aFileName, const ENV_VAR_MAP *aEnvVars, const PROJECT *aProject)
Search the default paths trying to find one with the requested file.
Helper functions to substitute paths with environmental variables.
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
must_if< error >::control< Rule > control
PGM_BASE & Pgm()
The global program "get" accessor.
wxString GetFieldValue(const std::vector< SCH_FIELD > *aFields, FIELD_T aFieldType)
#define SIM_NODES_FORMAT_FIELD
#define SIM_DECOMPOSITION_FIELD
std::vector< std::pair< wxString, wxString > > ParseSimPinsTokens(const wxString &aPins, const wxString &aRef)
Parse one unit's Sim.Pins text into (symbolPinNumber -> modelPinName) pairs, preserving the written o...
std::vector< FAB_LAYER_COLOR > dummy
wxString UnescapeString(const wxString &aSource)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Per-component decomposition descriptor stored in the Sim.Decomposition field.
static SIM_DECOMPOSITION Parse(const wxString &aField)
std::vector< wxString > sharedModelPins
Notes: spaces are allowed everywhere in any number ~ can only be before ?
std::vector< SCH_FIELD > fields
std::vector< std::string > pinNetNames
std::string baseModelName
std::vector< std::string > pinNumbers
One functional unit's pin map, gathered from its Sim.Pins field.
std::vector< std::pair< wxString, wxString > > pins
@ USER
The field ID hasn't been set yet; field is invalid.
@ REFERENCE
Field Reference of part, i.e. "IC21".
wxString result
Test unit parsing edge cases and error handling.