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" );
136 std::set<std::string> refNames;
138 wxString variant =
m_schematic->GetCurrentVariant();
151 cacheDir.AppendDir( wxT(
"ibis" ) );
153 if( !cacheDir.DirExists() )
155 cacheDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
157 if( !cacheDir.DirExists() )
159 wxLogTrace( wxT(
"IBIS_CACHE:" ),
160 wxT(
"%s:%s:%d\n * failed to create ibis cache directory '%s'" ),
161 __FILE__, __FUNCTION__, __LINE__, cacheDir.GetPath() );
168 wxString dirName = cacheDir.GetFullPath();
170 if( !dir.Open( dirName ) )
174 wxArrayString fileList;
175 wxString fileSpec = wxT(
"*.cache" );
177 thisFile.SetPath( dirName );
179 size_t numFilesFound = wxDir::GetAllFiles( dirName, &fileList, fileSpec );
181 for(
size_t ii = 0; ii < numFilesFound; ii++ )
184 thisFile.SetFullName( fileList[ii] );
185 wxRemoveFile( thisFile.GetFullPath() );
190 std::vector<SCH_ITEM*> sheetItems;
193 sheetItems.push_back( item );
195 std::ranges::sort( sheetItems,
218 spiceItem.
fields.back().SetText( symbol->
GetRef( &sheet ) );
220 spiceItem.
fields.back().SetText( field.GetShownText( &sheet,
FOR_NETNAME, variant ) );
223 spiceItem.
fields.back().SetParent(
nullptr );
226 readRefName( sheet, *symbol, spiceItem, refNames );
227 readModel( sheet, *symbol, spiceItem, variant, aReporter );
233 m_items.push_back( std::move( spiceItem ) );
248 const bool literalSlash =
UnescapeString( aNetName->AfterLast(
'/' ) ).Contains(
'/' );
251 std::unique_ptr<MARKUP::NODE> root = markupParser.
Parse();
253 std::function<void(
const std::unique_ptr<MARKUP::NODE>&)> convertMarkup =
254 [&](
const std::unique_ptr<MARKUP::NODE>& aNode )
258 if( !aNode->is_root() )
260 if( aNode->isOverbar() )
265 else if( aNode->isSubscript() || aNode->isSuperscript() )
270 if( aNode->has_content() )
271 *aNetName += aNode->string();
274 for(
const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
275 convertMarkup( child );
279 *aNetName = wxEmptyString;
280 convertMarkup( root );
283 aNetName->Replace(
'%',
'_' );
284 aNetName->Replace(
'(',
'_' );
285 aNetName->Replace(
')',
'_' );
286 aNetName->Replace(
',',
'_' );
287 aNetName->Replace(
'[',
'_' );
288 aNetName->Replace(
']',
'_' );
289 aNetName->Replace(
'<',
'_' );
290 aNetName->Replace(
'>',
'_' );
291 aNetName->Replace(
'~',
'_' );
292 aNetName->Replace(
' ',
'_' );
295 const wxString localName = aNetName->AfterLast(
'/' );
297 if( !literalSlash && ( localName == wxS(
"0" ) || localName.IsSameAs( wxS(
"gnd" ),
false ) )
298 && !aNetName->EndsWith( wxS(
"//" ) + localName ) )
300 aNetName->assign( localName );
305 if( aNetName->StartsWith( wxS(
"//" ) ) )
306 aNetName->Replace( wxS(
"//" ), wxS(
"/root/" ),
false );
313 return item->model->SpiceGenerator().ItemName( *item );
315 return wxEmptyString;
321 const std::string refName = aRefName.ToStdString();
322 const std::list<SPICE_ITEM>& spiceItems =
GetItems();
324 auto it = std::find_if( spiceItems.begin(), spiceItems.end(),
327 return item.refName == refName;
330 if( it != spiceItems.end() )
345 for(
SCH_ITEM* item : sheet.LastScreen()->Items() )
347 if( item->ResolveExcludedFromSim() )
358 wxStringTokenizer tokenizer(
text,
"\r\n", wxTOKEN_STRTOK );
359 bool foundDirective =
false;
362 [](
const wxString& line,
const wxString& dir )
364 return line == dir || line.StartsWith( dir + wxS(
" " ) );
367 while( tokenizer.HasMoreTokens() )
369 wxString line = tokenizer.GetNextToken().Upper();
371 if( line.StartsWith( wxT(
"." ) ) )
373 if( isDirective( line, wxS(
".AC" ) )
374 || isDirective( line, wxS(
".CONTROL" ) )
375 || isDirective( line, wxS(
".CSPARAM" ) )
376 || isDirective( line, wxS(
".DISTO" ) )
377 || isDirective( line, wxS(
".DC" ) )
378 || isDirective( line, wxS(
".ELSE" ) )
379 || isDirective( line, wxS(
".ELSEIF" ) )
380 || isDirective( line, wxS(
".END" ) )
381 || isDirective( line, wxS(
".ENDC" ) )
382 || isDirective( line, wxS(
".ENDIF" ) )
383 || isDirective( line, wxS(
".ENDS" ) )
384 || isDirective( line, wxS(
".FOUR" ) )
385 || isDirective( line, wxS(
".FUNC" ) )
386 || isDirective( line, wxS(
".GLOBAL" ) )
387 || isDirective( line, wxS(
".IC" ) )
388 || isDirective( line, wxS(
".IF" ) )
389 || isDirective( line, wxS(
".INCLUDE" ) )
390 || isDirective( line, wxS(
".LIB" ) )
391 || isDirective( line, wxS(
".MEAS" ) )
392 || isDirective( line, wxS(
".MODEL" ) )
393 || isDirective( line, wxS(
".NODESET" ) )
394 || isDirective( line, wxS(
".NOISE" ) )
395 || isDirective( line, wxS(
".OP" ) )
396 || isDirective( line, wxS(
".OPTIONS" ) )
397 || isDirective( line, wxS(
".PARAM" ) )
398 || isDirective( line, wxS(
".PLOT" ) )
399 || isDirective( line, wxS(
".PRINT" ) )
400 || isDirective( line, wxS(
".PROBE" ) )
401 || isDirective( line, wxS(
".PZ" ) )
402 || isDirective( line, wxS(
".SAVE" ) )
403 || isDirective( line, wxS(
".SENS" ) )
404 || isDirective( line, wxS(
".SP" ) )
405 || isDirective( line, wxS(
".SUBCKT" ) )
406 || isDirective( line, wxS(
".TEMP" ) )
407 || isDirective( line, wxS(
".TF" ) )
408 || isDirective( line, wxS(
".TITLE" ) )
409 || isDirective( line, wxS(
".TRAN" ) )
410 || isDirective( line, wxS(
".WIDTH" ) ) )
412 foundDirective =
true;
416 else if( line.StartsWith( wxT(
"K" ) ) )
419 wxStringTokenizer line_t( line,
" \t", wxTOKEN_STRTOK );
422 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT(
"K" ) ) )
426 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT(
"L" ) ) )
430 if( !line_t.HasMoreTokens() || !line_t.GetNextToken().StartsWith( wxT(
"L" ) ) )
435 if( line_t.HasMoreTokens() )
437 foundDirective =
true;
452 const wxString& aVariantName )
456 return wxEmptyString;
458 wxString ref = aSymbol.
GetRef( &aSheet );
459 std::vector<std::pair<wxString, wxString>> pinList;
460 std::set<wxString> pinNumbers;
463 auto parsePins = [&](
const wxString& aPins )
465 wxStringTokenizer tokenizer( aPins, wxS(
" \t\r\n" ), wxTOKEN_STRTOK );
467 while( tokenizer.HasMoreTokens() )
469 wxString token = tokenizer.GetNextToken();
470 int pos = token.Find( wxS(
'=' ) );
472 if( pos == wxNOT_FOUND )
475 wxString pinNumber = token.Left( pos );
476 wxString modelPin = token.Mid( pos + 1 );
479 if( pinNumbers.insert( pinNumber ).second )
480 pinList.emplace_back( pinNumber, modelPin );
486 parsePins( pinsField->GetShownText( &aSheet,
FOR_NETNAME, aVariantName ) );
495 if( other == &aSymbol )
498 if( other->
GetRef( &sheet ) != ref )
502 parsePins( pinsField->GetShownText( &sheet,
FOR_NETNAME, aVariantName ) );
508 if( pinList.empty() )
509 return wxEmptyString;
514 for(
const auto& [pinNumber, modelPin] : pinList )
516 if( !merged.IsEmpty() )
517 merged += wxS(
" " );
519 merged += pinNumber + wxS(
"=" ) + modelPin;
528 const wxString& aVariantName )
530 std::vector<UNIT_PIN_MAP> unitMaps;
535 wxString ref = aSymbol.
GetRef( &aSheet );
536 std::set<int> seenUnits;
550 if( !seenUnits.insert( aUnit.
GetUnit() ).second )
557 if( !map.
pins.empty() )
558 unitMaps.push_back( std::move( map ) );
563 parseUnit( aSymbol, aSheet );
571 if( other == &aSymbol )
574 if( other->
GetRef( &sheet ).CmpNoCase( ref ) != 0 )
577 parseUnit( *other, sheet );
583 std::sort( unitMaps.begin(), unitMaps.end(),
586 return lhs.unit < rhs.unit;
595 const wxString& aVariantName )
const
601 return field->GetShownText( &aUnitSheet,
FOR_NETNAME, aVariantName );
603 return wxEmptyString;
608 wxString value = read( aSymbol, aSheet );
612 wxString ref = aSymbol.
GetRef( &aSheet );
620 if( other == &aSymbol || other->
GetRef( &sheet ).CmpNoCase( ref ) != 0 )
623 value = read( *other, sheet );
625 if( !value.IsEmpty() )
636 SPICE_ITEM& aItem, std::set<std::string>& aRefNames )
640 [[maybe_unused]]
bool inserted = aRefNames.insert( aItem.
refName ).second;
641 wxASSERT_MSG( inserted, wxT(
"Duplicate refdes encountered; what happened to ReadyToNetlist()?" ) );
646 const wxString& aVariantName,
REPORTER& aReporter )
655 wxString mergedSimPins = repeat ? wxString()
659 aReporter, mergedSimPins );
669 if( libModel.
model.
GetType() != SIM_MODEL::TYPE::SUBCKT || libModel.
name.empty() )
671 THROW_IO_ERRORF(
_(
"Symbol '%s' uses repeat-per-unit decomposition, which requires a named "
672 "subcircuit model." ), aSymbol.
GetRef( &aSheet ) );
675 std::vector<UNIT_PIN_MAP> unitMaps =
collectUnitPinMaps( aSymbol, aSheet, aVariantName );
682 auto wrapper = std::make_unique<SIM_MODEL_MULTIUNIT>( libModel.
model, libModel.
name, unitMaps,
711 const std::vector<PIN_INFO>& aPins )
719 const std::vector<PIN_INFO>& aPins,
int& aNcCounter )
721 for(
const PIN_INFO& pinInfo : aPins )
725 aItem.
pinNetNames.push_back( netName.ToStdString() );
732 std::vector<std::string>& aModifiers )
739 tao::pegtl::string_input<> in( input,
"Sim.NodesFormat field" );
740 std::unique_ptr<tao::pegtl::parse_tree::node> root;
741 std::string singleNodeModifier;
749 for(
const auto& node : root->children )
754 aModifiers.back().append( node->string() );
758 singleNodeModifier.append( node->string() );
763 aModifiers.push_back( singleNodeModifier );
764 singleNodeModifier.erase( singleNodeModifier.begin(), singleNodeModifier.end() );
768 catch(
const tao::pegtl::parse_error& e )
775 std::vector<std::string> xspicePattern;
778 if( xspicePattern.empty() )
781 if( xspicePattern.size() != aItem.
pinNetNames.size() )
783 THROW_IO_ERRORF(
_(
"Error in parsing model '%s', wrong number of nodes '?' in Sim.NodesFormat compared "
784 "to connections" ), aItem.
refName );
789 for( std::string& pattern : xspicePattern )
792 const std::string netName =
" " + *itNetNames +
" ";
793 pattern.replace( pattern.find(
"?" ), 1, netName );
794 *itNetNames = pattern;
800 const wxString& aPath )
806 expandedPath.Replace(
'\\',
'/' );
815 if( fullPath.IsEmpty() )
817 wxLogError(
_(
"Could not find library file '%s'" ), expandedPath );
818 fullPath = expandedPath;
820 else if( wxFileName::GetPathSeparator() ==
'\\' )
823 fullPath.Replace(
'\\',
'/' );
828 fullPath = expandedPath;
831 aFormatter.
Print( 0,
".include \"%s\"\n",
TO_UTF8( fullPath ) );
850 std::set<std::string> emittedWrappers;
854 if( !item.model->IsEnabled() )
860 && !emittedWrappers.insert( item.modelName ).second )
865 aFormatter.
Print( 0,
"%s", item.model->SpiceGenerator().ModelLine( item ).c_str() );
874 if( !item.model->IsEnabled() )
877 aFormatter.
Print( 0,
"%s", item.model->SpiceGenerator().ItemLine( item ).c_str() );
886 aFormatter.
Print( 0,
".save all\n" );
889 aFormatter.
Print( 0,
".probe alli\n" );
897 std::string itemName = item.model->SpiceGenerator().ItemName( item );
899 if( ( item.model->GetPinCount() >= 2 ) && ( itemName.size() > 0 )
900 && ( itemName.c_str()[0] !=
'A' ) )
902 aFormatter.
Print( 0,
".probe p(%s)\n", itemName.c_str() );
908 [](
const wxString& candidate,
const wxString& dir )
910 return candidate == dir || candidate.StartsWith( dir + wxS(
" " ) );
915 bool simCommand =
false;
917 if( directive.StartsWith(
"." ) )
919 wxString candidate = directive.Upper();
921 simCommand = ( isSimCommand( candidate, wxS(
".AC" ) )
922 || isSimCommand( candidate, wxS(
".DC" ) )
923 || isSimCommand( candidate, wxS(
".TRAN" ) )
924 || isSimCommand( candidate, wxS(
".OP" ) )
925 || isSimCommand( candidate, wxS(
".DISTO" ) )
926 || isSimCommand( candidate, wxS(
".NOISE" ) )
927 || isSimCommand( candidate, wxS(
".PZ" ) )
928 || isSimCommand( candidate, wxS(
".SENS" ) )
929 || isSimCommand( candidate, wxS(
".TF" ) ) );
933 aFormatter.
Print( 0,
"%s\n",
UTF8( directive ).c_str() );
939 int& aNcCounter )
const
941 wxString netName = aNetName;
945 if( netName.IsEmpty() )
946 netName.Printf( wxS(
"NC-%d" ), aNcCounter++ );
956 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.
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.
SCH_SHEET_LIST m_exportSheets
std::vector< PIN_INFO > CreatePinList(SCH_SYMBOL *aSymbol, const SCH_SHEET_PATH &aSheetPath)
Find a symbol from the DrawList and builds its pin list.
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)
Convert an escaped schematic net name to SPICE, preserving literal slashes when mapping ground names.
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).
bool writeNetlist(const wxString &aOutFileName, unsigned aNetlistOptions, REPORTER &aReporter) override
Write to specified output file.
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
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, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString, int aDepth=0) 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 IsContainedWithin(const SCH_SHEET_PATH &aSheetPathToTest) const
Check if this path is contained inside aSheetPathToTest.
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_ERRORF(msg,...)
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.