1022                               wxString* aModelType, wxString* aModelParams, wxString* aPinMap )
 
 1051    auto convertNotation =
 
 1052            [&]( 
const wxString& units ) -> wxString
 
 1056                if( units == wxS( 
"µ" ) || units == wxS( 
"μ" ) )
 
 1061                    if( units == wxT( 
"M" ) )
 
 1062                        return wxT( 
"Meg" );
 
 1066                    if( units.Capitalize() == wxT( 
"Meg" ) )
 
 1074            []( wxString* mantissa )
 
 1076                mantissa->Replace( wxS( 
" " ), wxEmptyString );
 
 1078                wxChar ambiguousSeparator = 
'?';
 
 1079                wxChar thousandsSeparator = 
'?';
 
 1080                bool   thousandsSeparatorFound = 
false;
 
 1081                wxChar decimalSeparator = 
'?';
 
 1082                bool   decimalSeparatorFound = 
false;
 
 1085                for( 
int ii = (
int) mantissa->length() - 1; ii >= 0; --ii )
 
 1087                    wxChar c = mantissa->GetChar( ii );
 
 1089                    if( c >= 
'0' && c <= 
'9' )
 
 1093                    else if( c == 
'.' || c == 
',' )
 
 1095                        if( decimalSeparator != 
'?' || thousandsSeparator != 
'?' )
 
 1099                            if( c == decimalSeparator )
 
 1101                                if( thousandsSeparatorFound )
 
 1103                                else if( decimalSeparatorFound )
 
 1106                                    decimalSeparatorFound = 
true;
 
 1108                            else if( c == thousandsSeparator )
 
 1113                                    thousandsSeparatorFound = 
true;
 
 1116                        else if( ambiguousSeparator != 
'?' )
 
 1121                            if( c == ambiguousSeparator )
 
 1124                                thousandsSeparator = ambiguousSeparator;
 
 1125                                thousandsSeparatorFound = 
true;
 
 1126                                decimalSeparator = c == 
'.' ? 
',' : 
'.';
 
 1132                                decimalSeparator = ambiguousSeparator;
 
 1133                                decimalSeparatorFound = 
true;
 
 1134                                thousandsSeparator = c;
 
 1135                                thousandsSeparatorFound = 
true;
 
 1148                            if( ( ii == 1 && mantissa->GetChar( 0 ) == 
'0' ) || digits != 3 )
 
 1150                                decimalSeparator = c;
 
 1151                                decimalSeparatorFound = 
true;
 
 1152                                thousandsSeparator = c == 
'.' ? 
',' : 
'.';
 
 1156                                ambiguousSeparator = c;
 
 1169                if( decimalSeparator == 
'?' && thousandsSeparator == 
'?' )
 
 1171                    decimalSeparator = 
'.';
 
 1172                    thousandsSeparator = 
',';
 
 1175                mantissa->Replace( thousandsSeparator, wxEmptyString );
 
 1176                mantissa->Replace( decimalSeparator, 
'.' );
 
 1181    wxString              prefix = aSymbol.GetPrefix();
 
 1185    std::vector<SCH_PIN*> pins = aSymbol.GetPins();
 
 1190    std::sort( pins.begin(), pins.end(),
 
 1193                   return a->GetNumber() < b->GetNumber();
 
 1201    if( pins.size() != 2 )
 
 1204    if(   ( ( *aDeviceType == 
"R" || *aDeviceType == 
"L" || *aDeviceType == 
"C" )
 
 1205            && aModelType->IsEmpty() )
 
 1207          ( 
library.IsEmpty() && modelName.IsEmpty()
 
 1208            && aDeviceType->IsEmpty()
 
 1209            && aModelType->IsEmpty()
 
 1211            && ( prefix.StartsWith( 
"R" ) || prefix.StartsWith( 
"L" ) || prefix.StartsWith( 
"C" ) ) ) )
 
 1213        if( aModelParams->IsEmpty() )
 
 1215            wxRegEx idealVal( wxT( 
"^" 
 1217                                   "([fFpPnNuUmMkKgGtTμµ𝛍𝜇𝝁 ]|M(e|E)(g|G))?" 
 1218                                   "([fFhHΩΩ𝛀𝛺𝝮rR]|ohm)?" 
 1220                                   "([fFhHΩΩ𝛀𝛺𝝮rR]|ohm)?" 
 1223            if( idealVal.Matches( value ) )      
 
 1225                wxString valueMantissa( idealVal.GetMatch( value, 1 ) );
 
 1226                wxString valueExponent( idealVal.GetMatch( value, 2 ) );
 
 1227                wxString valueFraction( idealVal.GetMatch( value, 6 ) );
 
 1232                if( valueMantissa.Contains( wxT( 
"." ) ) || valueFraction.IsEmpty() )
 
 1234                    aModelParams->Printf( wxT( 
"%s=\"%s%s\"" ),
 
 1235                                          prefix.Left(1).Lower(),
 
 1236                                          std::move( valueMantissa ),
 
 1237                                          convertNotation( valueExponent ) );
 
 1241                    aModelParams->Printf( wxT( 
"%s=\"%s.%s%s\"" ),
 
 1242                                          prefix.Left(1).Lower(),
 
 1243                                          std::move( valueMantissa ),
 
 1244                                          std::move( valueFraction ),
 
 1245                                          convertNotation( valueExponent ) );
 
 1250                *aModelType = wxT( 
"=" );
 
 1251                aModelParams->Printf( wxT( 
"%s=\"%s\"" ), prefix.Left(1).Lower(), std::move( value ) );
 
 1255        if( aDeviceType->IsEmpty() )
 
 1256            *aDeviceType = prefix.Left( 1 );
 
 1258        if( aPinMap->IsEmpty() )
 
 1259            aPinMap->Printf( wxT( 
"%s=+ %s=-" ), pins[0]->GetNumber(), pins[1]->GetNumber() );
 
 1264    if(   ( ( *aDeviceType == wxT( 
"V" ) || *aDeviceType == wxT( 
"I" ) )
 
 1265            && ( aModelType->IsEmpty() || *aModelType == wxT( 
"DC" ) ) )
 
 1267          ( aDeviceType->IsEmpty()
 
 1268            && aModelType->IsEmpty()
 
 1270            && ( prefix.StartsWith( 
"V" ) || prefix.StartsWith( 
"I" ) ) )  )
 
 1272        if( !value.IsEmpty() )
 
 1274            wxString param = 
"dc";
 
 1276            if( value.StartsWith( wxT( 
"DC " ) ) )
 
 1278                value = value.Right( value.Length() - 3 );
 
 1280            else if( value.StartsWith( wxT( 
"AC " ) ) )
 
 1282                value = value.Right( value.Length() - 3 );
 
 1286            wxRegEx sourceVal( wxT( 
"^" 
 1288                                    "([fFpPnNuUmMkKgGtTμµ𝛍𝜇𝝁 ]|M(e|E)(g|G))?" 
 1294            if( sourceVal.Matches( value ) )
 
 1296                wxString valueMantissa( sourceVal.GetMatch( value, 1 ) );
 
 1297                wxString valueExponent( sourceVal.GetMatch( value, 2 ) );
 
 1298                wxString valueFraction( sourceVal.GetMatch( value, 6 ) );
 
 1303                if( valueMantissa.Contains( wxT( 
"." ) ) || valueFraction.IsEmpty() )
 
 1305                    aModelParams->Printf( wxT( 
"%s=\"%s%s\" %s" ),
 
 1307                                          std::move( valueMantissa ),
 
 1308                                          convertNotation( valueExponent ),
 
 1313                    aModelParams->Printf( wxT( 
"%s=\"%s.%s%s\" %s" ),
 
 1315                                          std::move( valueMantissa ),
 
 1316                                          std::move( valueFraction ),
 
 1317                                          convertNotation( valueExponent ),
 
 1323                aModelParams->Printf( wxT( 
"%s=\"%s\" %s" ),
 
 1330        if( aDeviceType->IsEmpty() )
 
 1331            *aDeviceType = prefix.Left( 1 );
 
 1333        if( aModelType->IsEmpty() )
 
 1334            *aModelType = wxT( 
"DC" );
 
 1336        if( aPinMap->IsEmpty() )
 
 1337            aPinMap->Printf( wxT( 
"%s=+ %s=-" ), pins[0]->GetNumber(), pins[1]->GetNumber() );
 
 
 1371        FIELD_INFO( 
const wxString& aText, 
SCH_FIELD* aField ) :
 
 1378        bool IsEmpty()
 const { 
return m_Text.IsEmpty(); }
 
 1380        SCH_FIELD CreateField( 
T* aSymbol, 
const wxString& aFieldName )
 
 1404    wxString existing_deviceSubtype;
 
 1406    if( existing_deviceSubtypeField )
 
 1407        existing_deviceSubtype = existing_deviceSubtypeField->
GetShownText( 
false ).Upper();
 
 1409    if( existing_deviceField
 
 1410        || existing_deviceSubtypeField
 
 1411        || existing_pinsField
 
 1412        || existing_paramsField )
 
 1418        if( existing_deviceSubtype == wxS( 
"POT" ) )
 
 1420            if( existing_pinsField )
 
 1422                wxString pinMap = existing_pinsField->
GetText();
 
 1423                pinMap.Replace( wxS( 
"=+" ), wxS( 
"=r1" ) );
 
 1424                pinMap.Replace( wxS( 
"=-" ), wxS( 
"=r0" ) );
 
 1425                existing_pinsField->
SetText( pinMap );
 
 1430        if( existing_deviceSubtype.StartsWith( wxS( 
"RAND" ) ) )
 
 1434            existing_deviceSubtype = existing_deviceSubtypeField->
GetText().Upper();
 
 1436            if( existing_deviceSubtype.Replace( wxS( 
"NORMAL" ), wxS( 
"GAUSSIAN" ) ) )
 
 1437                existing_deviceSubtypeField->
SetText( existing_deviceSubtype );
 
 1439            if( existing_paramsField )
 
 1441                wxString params = existing_paramsField->
GetText().Lower();
 
 1448                count += params.Replace( wxS( 
"min=0 " ), wxEmptyString );
 
 1449                count += params.Replace( wxS( 
"max=0 " ), wxEmptyString );
 
 1452                count += params.Replace( wxS( 
"dt=" ), wxS( 
"ts=" ) );
 
 1455                    existing_paramsField->
SetText( params );
 
 1461        if( existing_deviceSubtype == wxS( 
"MUTUAL" ) )
 
 1463            if( existing_deviceSubtypeField )   
 
 1464                aSymbol.RemoveField( existing_deviceSubtypeField );
 
 1466            if( existing_deviceField )
 
 1468                existing_deviceField->
SetText( wxS( 
"K" ) );
 
 1472                FIELD_INFO deviceFieldInfo;
 
 1473                deviceFieldInfo.m_Text = wxS( 
"K" );
 
 1476                aSymbol.AddField( deviceField );
 
 1487                    return wxString( wxEmptyString );
 
 1489                wxRegEx  regex( wxT( 
"([^a-z])(M)(e|E)(g|G)($|[^a-z])" ) );
 
 1490                wxString value = aField->GetText();
 
 1493                regex.ReplaceAll( &value, wxT( 
"\\1\\2\\5" ) );
 
 1498    auto generateDefaultPinMapFromSymbol =
 
 1499            []( 
const std::vector<SCH_PIN*>& sourcePins )
 
 1506                for( 
unsigned ii = 0; ii < sourcePins.size(); ++ii )
 
 1509                        pinMap.Append( wxS( 
" " ) );
 
 1511                    pinMap.Append( wxString::Format( wxT( 
"%s=%u" ),
 
 1512                                                     sourcePins[ii]->GetNumber(),
 
 1519    wxString              prefix = aSymbol.GetPrefix();
 
 1521    std::vector<SCH_PIN*> sourcePins = aSymbol.GetPins();
 
 1522    bool                  sourcePinsSorted = 
false;
 
 1524    auto lazySortSourcePins =
 
 1525            [&sourcePins, &sourcePinsSorted]()
 
 1527                if( !sourcePinsSorted )
 
 1529                    std::sort( sourcePins.begin(), sourcePins.end(),
 
 1532                                   return StrNumCmp( lhs->GetNumber(), rhs->GetNumber(), true ) < 0;
 
 1536                sourcePinsSorted = 
true;
 
 1539    FIELD_INFO deviceInfo;
 
 1540    FIELD_INFO modelInfo;
 
 1541    FIELD_INFO deviceSubtypeInfo;
 
 1543    FIELD_INFO spiceParamsInfo;
 
 1544    FIELD_INFO pinMapInfo;
 
 1545    bool       modelFromValueField = 
false;
 
 1555            deviceInfo = FIELD_INFO( primitiveField->GetText(), primitiveField );
 
 1556            aSymbol.RemoveField( primitiveField );
 
 1561            const wxString  delimiters( 
"{:,; }" );
 
 1562            const wxString& nodeSequence = nodeSequenceField->GetText();
 
 1565            if( nodeSequence != 
"" )
 
 1567                wxStringTokenizer tkz( nodeSequence, delimiters );
 
 1569                for( 
long modelPinNumber = 1; tkz.HasMoreTokens(); ++modelPinNumber )
 
 1571                    long symbolPinNumber = 1;
 
 1572                    tkz.GetNextToken().ToLong( &symbolPinNumber );
 
 1574                    if( modelPinNumber != 1 )
 
 1575                        pinMap.Append( 
" " );
 
 1577                    pinMap.Append( wxString::Format( 
"%ld=%ld", symbolPinNumber, modelPinNumber ) );
 
 1581            pinMapInfo = FIELD_INFO( pinMap, nodeSequenceField );
 
 1582            aSymbol.RemoveField( nodeSequenceField );
 
 1587            modelInfo = FIELD_INFO( getSIValue( modelField ), modelField );
 
 1588            aSymbol.RemoveField( modelField );
 
 1590        else if( valueField )
 
 1592            modelInfo = FIELD_INFO( getSIValue( valueField ), valueField );
 
 1593            modelFromValueField = 
true;
 
 1598            libInfo = FIELD_INFO( libFileField->GetText(), libFileField );
 
 1599            aSymbol.RemoveField( libFileField );
 
 1606        if( 
SCH_FIELD* legacyType = aSymbol.GetField( wxT( 
"Sim_Type" ) ) )
 
 1611        if( 
SCH_FIELD* legacyDevice = aSymbol.GetField( wxT( 
"Sim_Device" ) ) )
 
 1616        if( 
SCH_FIELD* legacyPins = aSymbol.GetField( wxT( 
"Sim_Pins" ) ) )
 
 1618            bool isPassive =   prefix.StartsWith( wxT( 
"R" ) )
 
 1619                            || prefix.StartsWith( wxT( 
"L" ) )
 
 1620                            || prefix.StartsWith( wxT( 
"C" ) );
 
 1624            wxArrayString pinIndexes;
 
 1628            lazySortSourcePins();
 
 1630            if( isPassive && pinIndexes.size() == 2 && sourcePins.size() == 2 )
 
 1632                if( pinIndexes[0] == wxT( 
"2" ) )
 
 1634                    pinMap.Printf( wxT( 
"%s=- %s=+" ),
 
 1635                                   sourcePins[0]->GetNumber(),
 
 1636                                   sourcePins[1]->GetNumber() );
 
 1640                    pinMap.Printf( wxT( 
"%s=+ %s=-" ),
 
 1641                                   sourcePins[0]->GetNumber(),
 
 1642                                   sourcePins[1]->GetNumber() );
 
 1647                for( 
unsigned ii = 0; ii < pinIndexes.size() && ii < sourcePins.size(); ++ii )
 
 1650                        pinMap.Append( wxS( 
" " ) );
 
 1652                    pinMap.Append( wxString::Format( wxT( 
"%s=%s" ),
 
 1653                                                     sourcePins[ii]->GetNumber(),
 
 1654                                                     pinIndexes[ ii ] ) );
 
 1659            legacyPins->SetText( pinMap );
 
 1662        if( 
SCH_FIELD* legacyParams = aSymbol.GetField( wxT( 
"Sim_Params" ) ) )
 
 1670    wxString device = deviceInfo.m_Text.Trim( 
true ).Trim( 
false );
 
 1671    wxString lib = libInfo.m_Text.Trim( 
true ).Trim( 
false );
 
 1672    wxString model = modelInfo.m_Text.Trim( 
true ).Trim( 
false );
 
 1673    wxString modelLineParams;
 
 1675    bool libraryModel = 
false;
 
 1676    bool inferredModel = 
false;
 
 1677    bool internalModel = 
false;
 
 1679    if( !lib.IsEmpty() )
 
 1683        std::vector<SCH_FIELD>       emptyFields;
 
 1684        std::vector<EMBEDDED_FILES*> embeddedFilesStack;
 
 1686        if constexpr (std::is_same_v<T, SCH_SYMBOL>)
 
 1692        if( 
EMBEDDED_FILES* symbolEmbeddedFiles = aSymbol.GetEmbeddedFiles() )
 
 1693            embeddedFilesStack.push_back( symbolEmbeddedFiles );
 
 1698        model = model.BeforeFirst( 
' ', &modelLineParams );
 
 1699        modelInfo.m_Text = model;
 
 1701        lazySortSourcePins();
 
 1704                                                          emptyFields, 
false, 0,
 
 1705                                                          sourcePins, reporter );
 
 1708            libraryModel = 
false;    
 
 1710            libraryModel = 
true;
 
 1712        if( pinMapInfo.IsEmpty() )
 
 1718            if( pinMapInfo.IsEmpty() )
 
 1719                pinMapInfo.m_Text = generateDefaultPinMapFromSymbol( sourcePins );
 
 1722    else if( ( device == wxS( 
"R" )
 
 1723               || device == wxS( 
"L" )
 
 1724               || device == wxS( 
"C" )
 
 1725               || device == wxS( 
"V" )
 
 1726               || device == wxS( 
"I" ) )
 
 1727            && prefix.StartsWith( device )
 
 1728            && modelFromValueField )
 
 1730        inferredModel = 
true;
 
 1732    else if( device == wxS( 
"V" ) || device == wxS( 
"I" ) )
 
 1737        wxStringTokenizer tokenizer( model, wxT( 
"() " ), wxTOKEN_STRTOK );
 
 1739        if( tokenizer.HasMoreTokens() )
 
 1741            deviceSubtypeInfo.m_Text = tokenizer.GetNextToken();
 
 1742            deviceSubtypeInfo.m_Text.MakeUpper();
 
 1744            for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() )
 
 1753                        if( deviceSubtypeInfo.m_Text == wxT( 
"DC" ) && tokenizer.CountTokens() == 1 )
 
 1755                            wxCHECK( valueField,  );
 
 1756                            valueField->
SetText( tokenizer.GetNextToken() );
 
 1757                            modelFromValueField = 
false;
 
 1761                            for( 
int ii = 0; tokenizer.HasMoreTokens(); ++ii )
 
 1763                                simModel->SetParamValue( ii, tokenizer.GetNextToken().ToStdString(),
 
 1769                            spiceParamsInfo = modelInfo;
 
 1770                            spiceParamsInfo.m_Text = wxString( simModel->Serializer().GenerateParams() );
 
 1773                        internalModel = 
true;
 
 1775                        if( pinMapInfo.IsEmpty() )
 
 1777                            lazySortSourcePins();
 
 1780                            simModel->createPins( sourcePins );
 
 1781                            pinMapInfo.m_Text = wxString( simModel->Serializer().GeneratePins() );
 
 1798        aSymbol.AddField( libField );
 
 1801        aSymbol.AddField( nameField );
 
 1803        if( !modelLineParams.IsEmpty() )
 
 1805            spiceParamsInfo = modelInfo;
 
 1807            spiceParamsInfo.m_Text = modelLineParams;
 
 1810            int   nameWidth = nameBBox.
GetWidth();
 
 1816                spiceParamsInfo.m_Pos.x -= nameWidth;
 
 1818                spiceParamsInfo.m_Pos.x += nameWidth;
 
 1821            aSymbol.AddField( paramsField );
 
 1824        if( modelFromValueField )
 
 1825            valueField->
SetText( wxT( 
"${SIM.NAME}" ) );
 
 1827    else if( inferredModel )
 
 1832    else if( internalModel )
 
 1835        aSymbol.AddField( deviceField );
 
 1837        if( !deviceSubtypeInfo.m_Text.IsEmpty() )
 
 1840            aSymbol.AddField( subtypeField );
 
 1843        if( !spiceParamsInfo.IsEmpty() )
 
 1846            aSymbol.AddField( paramsField );
 
 1849        if( modelFromValueField )
 
 1850            valueField->
SetText( wxT( 
"${SIM.PARAMS}" ) );
 
 1854        if( device.IsEmpty() && lib.IsEmpty() )
 
 1856            spiceParamsInfo = modelInfo;
 
 1860            spiceParamsInfo.m_Text.Printf( wxT( 
"type=\"%s\" model=\"%s\" lib=\"%s\"" ), device,
 
 1867        aSymbol.AddField( deviceField );
 
 1870        aSymbol.AddField( paramsField );
 
 1872        if( modelFromValueField )
 
 1878                valueField->
SetText( wxT( 
"${SIM.PARAMS}" ) );
 
 1884        if( pinMapInfo.IsEmpty() )
 
 1886            lazySortSourcePins();
 
 1887            pinMapInfo.m_Text = generateDefaultPinMapFromSymbol( sourcePins );
 
 1891    if( !pinMapInfo.IsEmpty() )
 
 1894        aSymbol.AddField( pinsField );