kicad/eeschema/sch_plugins/ltspice/ltspice_schematic.cpp

1104 lines
40 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2022 Chetan Subhash Shinde<chetanshinde2001@gmail.com>
* Copyright (C) 2023 CERN
* Copyright (C) 2022-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @brief Loads the asc file and asy files.
*/
#include <sch_plugins/ltspice/ltspice_schematic.h>
#include <sch_plugins/ltspice/ltspice_sch_parser.h>
#include <sch_screen.h>
#include <wx/log.h>
#include <wx/dir.h>
#include <wildcards_and_files_ext.h>
#include <sch_sheet.h>
#include <schematic.h>
void LTSPICE_SCHEMATIC::Load( SCHEMATIC* aSchematic, SCH_SHEET* aRootSheet,
const wxFileName& aLibraryFileName )
{
std::map<wxString, wxString> mapOfAscFiles;
std::map<wxString, wxString> mapOfAsyFiles;
// Library paths to search (Give highest priority to files contained in same directory)
GetAscAndAsyFilePaths( aLibraryFileName.GetPath(), false, mapOfAscFiles, mapOfAsyFiles );
// TODO: Custom paths go here (non-recursive)
// Default LTspice libs
GetAscAndAsyFilePaths( m_ltspiceDataDir.GetPathWithSep() + wxS( "sub" ), true, mapOfAscFiles,
mapOfAsyFiles );
GetAscAndAsyFilePaths( m_ltspiceDataDir.GetPathWithSep() + wxS( "sym" ), true, mapOfAscFiles,
mapOfAsyFiles );
m_schematic = aSchematic;
std::queue<wxString> ascFileQueue;
ascFileQueue.push( aLibraryFileName.GetName().Lower() );
LTSPICE_FILE rootAscFile( ascFileQueue.front(), { 0, 0 } );
rootAscFile.Sheet = aRootSheet;
rootAscFile.Screen = new SCH_SCREEN();
int parentSheetIndex = 0;
// Asc files who are subschematic in nature
std::vector<LTSPICE_FILE> ascFiles;
ascFiles.push_back( rootAscFile );
while( !ascFileQueue.empty() )
{
SCH_SCREEN* screen = new SCH_SCREEN( m_schematic );
// Reading the .asc file
wxString ascFilePath = mapOfAscFiles[ ascFileQueue.front() ];
wxString buffer = SafeReadFile( ascFilePath, "r" );
std::vector<LTSPICE_FILE> newSubSchematicElements = GetSchematicElements( buffer );
alg::delete_if( newSubSchematicElements,
[&mapOfAscFiles]( const LTSPICE_FILE& ii )
{
return mapOfAscFiles[ii.ElementName].IsEmpty();
} );
for( LTSPICE_FILE& newSubSchematicElement : newSubSchematicElements )
{
wxString asyName = newSubSchematicElement.ElementName;
auto it = mapOfAsyFiles.find( asyName );
if( it == mapOfAsyFiles.end() )
continue;
wxString asyBuffer = SafeReadFile( it->second, "r" );
if( IsAsySubsheet( asyBuffer ) )
{
newSubSchematicElement.ParentIndex = parentSheetIndex;
newSubSchematicElement.Screen = screen;
newSubSchematicElement.Sheet = new SCH_SHEET();
ascFileQueue.push( newSubSchematicElement.ElementName );
ascFiles.push_back( newSubSchematicElement );
}
}
ascFileQueue.pop();
parentSheetIndex++;
}
for( unsigned int i = 0; i < ascFiles.size(); i++ )
{
// Reading the .asc file
wxString buffer = SafeReadFile( mapOfAscFiles[ascFiles[i].ElementName], wxS( "r" ) );
// Getting the keywords to read
std::vector<LTSPICE_FILE> sourceFiles = GetSchematicElements( buffer );
m_fileCache[ wxS( "asyFiles" ) ] = ReadAsyFiles( sourceFiles, mapOfAsyFiles );
m_fileCache[ wxS( "ascFiles" ) ][ wxS( "parentFile" ) ] = buffer;
SCH_SHEET* curSheet;
SCH_SHEET_PATH curSheetPath;
LTSPICE_SCH_PARSER parser( this );
if( i > 0 )
{
std::vector<LTSPICE_FILE> tempVector;
tempVector.push_back( ascFiles[i] );
curSheet = ascFiles[i].Sheet;
std::map tempAsyMap = ReadAsyFiles( tempVector, mapOfAsyFiles );
wxString ascFileName = ascFiles[i].ElementName;
LT_ASC dummyAsc;
LT_SYMBOL tempSymbol = SymbolBuilder( ascFileName, tempAsyMap[ascFileName], dummyAsc );
LIB_SYMBOL tempLibSymbol( ascFiles[i].ElementName );
parser.CreateSymbol( tempSymbol, &tempLibSymbol );
BOX2I bbox = tempLibSymbol.GetBoundingBox();
curSheet->SetSize( bbox.GetSize() );
curSheet->SetPosition( parser.ToKicadCoords( ascFiles[i].Offset ) + bbox.GetOrigin() );
curSheet->SetParent( ascFiles[ascFiles[i].ParentIndex].Sheet );
SCH_FIELD& sheetNameField = curSheet->GetFields()[SHEETNAME];
SCH_FIELD& fileNameSheet = curSheet->GetFields()[SHEETFILENAME];
wxString sheetName = wxString::Format( wxS( "%s-subsheet-%d" ),
ascFiles[i].ElementName,
i );
sheetNameField.SetText( sheetName );
fileNameSheet.SetText( sheetName + ".kicad_sch" );
curSheet->SetScreen( ascFiles[i].Screen );
curSheetPath = ascFiles[ascFiles[i].ParentIndex].SheetPath;
curSheetPath.push_back( curSheet );
ascFiles[i].SheetPath = curSheetPath;
ascFiles[ascFiles[i].ParentIndex].Sheet->GetScreen()->Append( curSheet );
curSheet->GetScreen()->SetFileName( m_schematic->Prj().GetProjectPath() + sheetName
+ ".kicad_sch" );
}
else
{
curSheet = ascFiles[i].Sheet;
ascFiles[i].SheetPath.push_back( curSheet );
curSheetPath = ascFiles[i].SheetPath;
}
std::vector<wxString> subSchematicAsyFiles;
for( const LTSPICE_FILE& ascFile : ascFiles )
subSchematicAsyFiles.push_back( ascFile.ElementName );
std::vector<LTSPICE_SCHEMATIC::LT_ASC> lt_ascs = StructureBuilder();
parser.Parse( &curSheetPath, lt_ascs, subSchematicAsyFiles );
}
}
void LTSPICE_SCHEMATIC::GetAscAndAsyFilePaths( const wxDir& aDir, bool aRecursive,
std::map<wxString, wxString>& aMapOfAscFiles,
std::map<wxString, wxString>& aMapOfAsyFiles,
const wxString& aBase )
{
wxString filename;
{
bool cont = aDir.GetFirst( &filename, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN );
while( cont )
{
wxFileName path( aDir.GetName(), filename );
auto logToMap = [&]( std::map<wxString, wxString>& aMapToLogTo, const wxString& aKey )
{
if( aMapToLogTo.count( aKey ) )
{
if( m_reporter )
{
m_reporter->Report( wxString::Format(
_( "File at '%s' was ignored. Using previously found "
"file at '%s' instead." ),
path.GetFullPath(), aMapToLogTo.at( aKey ) ) );
}
}
else
{
aMapToLogTo.emplace( aKey, path.GetFullPath() );
}
};
wxString elementName1 = ( aBase + path.GetName() ).Lower();
wxString elementName2 = path.GetName().Lower();
wxString extension = path.GetExt().Lower();
if( extension == wxS( "asc" ) )
{
logToMap( aMapOfAscFiles, elementName1 );
if( !aBase.IsEmpty() )
logToMap( aMapOfAscFiles, elementName2 );
}
else if( extension == wxS( "asy" ) )
{
logToMap( aMapOfAsyFiles, elementName1 );
if( !aBase.IsEmpty() )
logToMap( aMapOfAsyFiles, elementName2 );
}
cont = aDir.GetNext( &filename );
}
}
if( aRecursive )
{
bool cont = aDir.GetFirst( &filename, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN );
while( cont )
{
wxFileName path( aDir.GetName(), filename );
wxDir subDir( path.GetFullPath() );
GetAscAndAsyFilePaths( subDir, true, aMapOfAscFiles, aMapOfAsyFiles,
filename + wxS( "/" ) );
cont = aDir.GetNext( &filename );
}
}
}
std::map<wxString, wxString>
LTSPICE_SCHEMATIC::ReadAsyFiles( const std::vector<LTSPICE_FILE>& aSourceFiles,
const std::map<wxString, wxString>& aAsyFileMap )
{
std::map<wxString, wxString> resultantMap;
for( const LTSPICE_FILE& source : aSourceFiles )
{
wxString fileName = source.ElementName;
if( aAsyFileMap.count( fileName ) )
resultantMap[fileName] = SafeReadFile( aAsyFileMap.at( fileName ), wxS( "r" ) );
}
return resultantMap;
}
std::vector<LTSPICE_FILE> LTSPICE_SCHEMATIC::GetSchematicElements( const wxString& aAscFile )
{
std::vector<LTSPICE_FILE> resultantArray;
wxArrayString lines = wxSplit( aAscFile, '\n' );
for( const wxString& line : lines )
{
wxArrayString tokens = wxSplit( line, ' ' );
if( !tokens.IsEmpty() && tokens[0].Upper() == wxS( "SYMBOL" ) )
{
wxString elementName( tokens[1] );
long posX, posY;
tokens[2].ToLong( &posX );
tokens[3].ToLong( &posY );
elementName.Replace( '\\', '/' );
LTSPICE_FILE asyFile( elementName, VECTOR2I( (int) posX, (int) posY ) );
resultantArray.push_back( asyFile );
}
}
return resultantArray;
}
bool LTSPICE_SCHEMATIC::IsAsySubsheet( const wxString& aAsyFile )
{
wxStringTokenizer lines( aAsyFile, "\n" );
while( lines.HasMoreTokens() )
{
wxStringTokenizer parts( lines.GetNextToken(), " " );
if( parts.GetNextToken().IsSameAs( wxS( "SYMATTR" ), false )
&& parts.GetNextToken().IsSameAs( wxS( "Prefix" ), false ) )
{
return false;
}
}
return true;
}
int LTSPICE_SCHEMATIC::integerCheck( const wxString& aToken, int aLineNumber,
const wxString& aFileName )
{
long result;
if( !aToken.ToLong( &result ) )
{
THROW_IO_ERROR( wxString::Format( _( "Expecting integer at line %d in file %s" ),
aLineNumber,
aFileName ) );
}
return (int) result;
}
VECTOR2I LTSPICE_SCHEMATIC::pointCheck( const wxString& aTokenX, const wxString& aTokenY,
int aLineNumber, const wxString& aFileName )
{
return VECTOR2I( integerCheck( aTokenX, aLineNumber, aFileName ),
integerCheck( aTokenY, aLineNumber, aFileName ) );
}
void LTSPICE_SCHEMATIC::tokensSizeRangeCheck( size_t aActualSize, int aExpectedMin,
int aExpectedMax, int aLineNumber,
const wxString& aFileName )
{
if( (int) aActualSize < aExpectedMin )
{
THROW_IO_ERROR( wxString::Format( _( "Expected data missing on line %d in file %s" ),
aLineNumber,
aFileName ) );
}
else if( (int) aActualSize > aExpectedMax )
{
THROW_IO_ERROR( wxString::Format( _( "Extra data found on line %d in file %s" ),
aLineNumber,
aFileName ) );
}
}
void LTSPICE_SCHEMATIC::aggregateAttributeValue( wxArrayString& aTokens, int aIndex )
{
// Merges a value which is across multiple tokens into one token with spaces in between.
for( int i = aIndex + 1; i < (int) aTokens.GetCount(); i++ )
aTokens[ aIndex ] += " " + aTokens[i];
}
LTSPICE_SCHEMATIC::LINESTYLE LTSPICE_SCHEMATIC::getLineStyle( int aValue )
{
std::map<int, LINESTYLE> lineStyleMap;
lineStyleMap[0] = LINESTYLE::SOLID;
lineStyleMap[1] = LINESTYLE::DASH;
lineStyleMap[2] = LINESTYLE::DOT;
lineStyleMap[3] = LINESTYLE::DASHDOT;
lineStyleMap[4] = LINESTYLE::DASHDOTDOT;
if( lineStyleMap.find( aValue ) == lineStyleMap.end() )
THROW_IO_ERROR( _( "Expecting 0, 1, 2, 3 or 4" ) );
return lineStyleMap[ aValue ];
}
LTSPICE_SCHEMATIC::LINEWIDTH LTSPICE_SCHEMATIC::getLineWidth( const wxString& aValue )
{
std::map<wxString, LINEWIDTH> lineWidthMap;
lineWidthMap["NORMAL"] = LINEWIDTH::Normal;
lineWidthMap["WIDE"] = LINEWIDTH::Wide;
if( lineWidthMap.find( aValue.Upper() ) == lineWidthMap.end() )
THROW_IO_ERROR( _( "Expecting NORMAL or WIDE" ) );
return lineWidthMap[ aValue.Upper() ];
}
LTSPICE_SCHEMATIC::POLARITY LTSPICE_SCHEMATIC::getPolarity( const wxString& aValue )
{
std::map<wxString, POLARITY> polarityMap;
polarityMap["I"] = POLARITY::INPUT;
polarityMap["O"] = POLARITY::OUTPUT;
polarityMap["B"] = POLARITY::BIDIR;
polarityMap["IN"] = POLARITY::INPUT;
polarityMap["OUT"] = POLARITY::OUTPUT;
polarityMap["BIDIR"] = POLARITY::BIDIR;
if( polarityMap.find( aValue.Upper() ) == polarityMap.end() )
THROW_IO_ERROR( _( "Expecting I, O, B, IN, OUT or BIDIR" ) );
return polarityMap[ aValue.Upper() ];
}
LTSPICE_SCHEMATIC::ORIENTATION LTSPICE_SCHEMATIC::getSymbolRotationOrMirror( const wxString& aValue )
{
std::map<wxString, ORIENTATION> rotationMirrorMap;
rotationMirrorMap["R0"] = ORIENTATION::R0;
rotationMirrorMap["R90"] = ORIENTATION::R90;
rotationMirrorMap["R180"] = ORIENTATION::R180;
rotationMirrorMap["R270"] = ORIENTATION::R270;
rotationMirrorMap["M0"] = ORIENTATION::M0;
rotationMirrorMap["M90"] = ORIENTATION::M90;
rotationMirrorMap["M180"] = ORIENTATION::M180;
rotationMirrorMap["M270"] = ORIENTATION::M270;
if( rotationMirrorMap.find( aValue.Upper() ) == rotationMirrorMap.end() )
THROW_IO_ERROR( _( "Expecting R0, R90, R18, R270, M0, M90, M180 or M270" ) );
return rotationMirrorMap[ aValue.Upper() ];
}
LTSPICE_SCHEMATIC::JUSTIFICATION LTSPICE_SCHEMATIC::getTextJustification( const wxString& aValue )
{
std::map<wxString, JUSTIFICATION> justificationMap;
justificationMap["LEFT"] = JUSTIFICATION::LEFT;
justificationMap["CENTER"] = JUSTIFICATION::CENTER;
justificationMap["RIGHT"] = JUSTIFICATION::RIGHT;
justificationMap["VLEFT"] = JUSTIFICATION::VLEFT;
justificationMap["VRIGHT"] = JUSTIFICATION::VRIGHT;
justificationMap["VCENTER"] = JUSTIFICATION::VCENTER;
justificationMap["BOTTOM"] = JUSTIFICATION::BOTTOM;
justificationMap["TOP"] = JUSTIFICATION::TOP;
justificationMap["VBOTTOM"] = JUSTIFICATION::VBOTTOM;
justificationMap["VTOP"] = JUSTIFICATION::VTOP;
if( justificationMap.find( aValue.Upper() ) == justificationMap.end() )
THROW_IO_ERROR( _( "Expecting LEFT, CENTER, RIGHT, TOP, BOTTOM, VLEFT, VRIGHT, VCENTER, VTOP or VBOTTOM" ) );
return justificationMap[ aValue.Upper() ];
}
LTSPICE_SCHEMATIC::JUSTIFICATION LTSPICE_SCHEMATIC::getPinJustification( const wxString& aValue )
{
std::map<wxString, JUSTIFICATION> pinJustificationMap;
pinJustificationMap["BOTTOM"] = JUSTIFICATION::BOTTOM;
pinJustificationMap["NONE"] = JUSTIFICATION::NONE;
pinJustificationMap["LEFT"] = JUSTIFICATION::LEFT;
pinJustificationMap["RIGHT"] = JUSTIFICATION::RIGHT;
pinJustificationMap["TOP"] = JUSTIFICATION::TOP;
pinJustificationMap["VBOTTOM"] = JUSTIFICATION::VBOTTOM;
pinJustificationMap["VLEFT"] = JUSTIFICATION::VLEFT;
pinJustificationMap["VRIGHT"] = JUSTIFICATION::VRIGHT;
pinJustificationMap["VCENTER"] = JUSTIFICATION::VCENTER;
pinJustificationMap["VTOP"] = JUSTIFICATION::VTOP;
if( pinJustificationMap.find( aValue.Upper() ) == pinJustificationMap.end() )
THROW_IO_ERROR( _( "Expecting NONE, BOTTOM, TOP, LEFT, RIGHT, VBOTTOM, VTOP, VCENTER, VLEFT or VRIGHT" ) );
return pinJustificationMap[ aValue.Upper() ];
}
LTSPICE_SCHEMATIC::SYMBOLTYPE LTSPICE_SCHEMATIC::getSymbolType( const wxString& aValue )
{
std::map<wxString, SYMBOLTYPE> symbolTypeMap;
symbolTypeMap["CELL"] = SYMBOLTYPE::CELL;
symbolTypeMap["BLOCK"] = SYMBOLTYPE::BLOCK;
if( symbolTypeMap.find( aValue.Upper() ) == symbolTypeMap.end() )
THROW_IO_ERROR( _( "Expecting CELL or BLOCK" ) );
return symbolTypeMap[ aValue.Upper() ];
}
void LTSPICE_SCHEMATIC::removeCarriageReturn( wxString& elementFromLine )
{
if( elementFromLine.EndsWith( '\r' ) )
elementFromLine = elementFromLine.BeforeLast( '\r' );
}
LTSPICE_SCHEMATIC::LT_SYMBOL LTSPICE_SCHEMATIC::SymbolBuilder( const wxString& aAscFileName,
LT_ASC& aAscFile )
{
const std::map<wxString, wxString>& asyFiles = m_fileCache[ wxS( "asyFiles" ) ];
if( !asyFiles.count( aAscFileName.Lower() ) )
THROW_IO_ERROR( wxString::Format( _( "Symbol '%s.asy' not found" ), aAscFileName ) );
return SymbolBuilder( aAscFileName, asyFiles.at( aAscFileName.Lower() ), aAscFile );
}
LTSPICE_SCHEMATIC::LT_SYMBOL LTSPICE_SCHEMATIC::SymbolBuilder( const wxString& aAscFileName,
const wxString& aAsyFileContent,
LT_ASC& aAscFile )
{
LT_SYMBOL lt_symbol;
int lineNumber = 1;
lt_symbol.Name = aAscFileName;
lt_symbol.SymbolType = LTSPICE_SCHEMATIC::SYMBOLTYPE::CELL;
lt_symbol.SymbolOrientation = LTSPICE_SCHEMATIC::ORIENTATION::R0;
for( wxString line : wxSplit( aAsyFileContent, '\n' ) )
{
removeCarriageReturn( line );
wxArrayString tokens = wxSplit( line, ' ' );
if( tokens.IsEmpty() )
continue;
wxString element = tokens[0].Upper();
if( element == "LINE" )
{
tokensSizeRangeCheck( tokens.size(), 6, 7, lineNumber, aAscFileName );
wxString lineWidth = tokens[1];
wxString startPointX = tokens[2];
wxString startPointY = tokens[3];
wxString endPointX = tokens[4];
wxString endPointY = tokens[5];
LINE lt_line;
lt_line.LineWidth = getLineWidth( lineWidth );
lt_line.Start = pointCheck( startPointX, startPointY, lineNumber, aAscFileName );
lt_line.End = pointCheck( endPointX, endPointY, lineNumber, aAscFileName );
int lineStyleNumber = 0; // default
if( tokens.size() == 7 )
{
wxString lineStyle = tokens[6];
lineStyleNumber = integerCheck( lineStyle, lineNumber, aAscFileName );
}
lt_line.LineStyle = getLineStyle( lineStyleNumber );
lt_symbol.Lines.push_back( lt_line );
}
else if( element == "RECTANGLE" )
{
tokensSizeRangeCheck( tokens.size(), 6, 7, lineNumber, aAscFileName );
wxString lineWidth = tokens[1];
wxString botRightX = tokens[2];
wxString botRightY = tokens[3];
wxString topLeftX = tokens[4];
wxString topRightY = tokens[5];
RECTANGLE rect;
rect.LineWidth = getLineWidth( lineWidth );
rect.BotRight = pointCheck( botRightX, botRightY, lineNumber, aAscFileName );
rect.TopLeft = pointCheck( topLeftX, topRightY, lineNumber, aAscFileName );
int lineStyleNumber = 0; // default
if( tokens.size() == 7 )
{
wxString lineStyle = tokens[6];
lineStyleNumber = integerCheck( lineStyle, lineNumber, aAscFileName );
}
rect.LineStyle = getLineStyle( lineStyleNumber );
lt_symbol.Rectangles.push_back( rect );
}
else if( element == "CIRCLE" )
{
/**
* The Circle is enclosed in the square which is represented by bottomRight and
* topLeft coordinates.
*/
tokensSizeRangeCheck( tokens.size(), 6, 7, lineNumber, aAscFileName );
wxString lineWidth = tokens[1];
wxString botRightX = tokens[2];
wxString botRightY = tokens[3];
wxString topLeftX = tokens[4];
wxString topRightY = tokens[5];
CIRCLE circle;
circle.LineWidth = getLineWidth( lineWidth );
circle.BotRight = pointCheck( botRightX, botRightY, lineNumber, aAscFileName );
circle.TopLeft = pointCheck( topLeftX, topRightY, lineNumber, aAscFileName );
int lineStyleNumber = 0; // default
if( tokens.size() == 7 )
{
wxString lineStyle = tokens[6];
lineStyleNumber = integerCheck( lineStyle, lineNumber, aAscFileName );
}
circle.LineStyle = getLineStyle( lineStyleNumber );
lt_symbol.Circles.push_back( circle );
}
else if( element == "ARC" )
{
/**
* The Arc is enclosed in the square given by above coordinates and its start and end
* coordinates are given.
* The arc is drawn counterclockwise from the starting point to the ending point.
*/
tokensSizeRangeCheck( tokens.size(), 10, 11, lineNumber, aAscFileName );
wxString lineWidth = tokens[1];
wxString botRightX = tokens[2];
wxString botRightY = tokens[3];
wxString topLeftX = tokens[4];
wxString topRightY = tokens[5];
wxString arcStartPointX = tokens[6];
wxString arcStartPointY = tokens[7];
wxString arcEndPointX = tokens[8];
wxString arcEndPointY = tokens[9];
ARC arc;
arc.LineWidth = getLineWidth( lineWidth );
arc.BotRight = pointCheck( botRightX, botRightY, lineNumber, aAscFileName );
arc.TopLeft = pointCheck( topLeftX, topRightY, lineNumber, aAscFileName );
arc.ArcStart = pointCheck( arcStartPointX, arcStartPointY, lineNumber, aAscFileName );
arc.ArcEnd = pointCheck( arcEndPointX, arcEndPointY, lineNumber, aAscFileName );
int lineStyleNumber = 0; // default
if( tokens.size() == 11 )
{
wxString lineStyle = tokens[10];
lineStyleNumber = integerCheck( lineStyle, lineNumber, aAscFileName );
}
arc.LineStyle = getLineStyle( lineStyleNumber );
lt_symbol.Arcs.push_back( arc );
}
else if( element == "WINDOW" )
{
tokensSizeRangeCheck( tokens.size(), 6, 6, lineNumber, aAscFileName );
wxString number = tokens[1];
wxString windowPosX = tokens[2];
wxString windowPosY = tokens[3];
wxString justification = tokens[4];
wxString fontSize = tokens[5];
LT_WINDOW window;
window.WindowNumber = integerCheck( number, lineNumber, aAscFileName );
window.Position = pointCheck( windowPosX, windowPosY, lineNumber, aAscFileName );
window.Justification = getTextJustification( justification );
window.FontSize = integerCheck( fontSize, lineNumber, aAscFileName );
// LTSpice appears to ignore hidden property from .asy files
if( window.FontSize == 0 )
window.FontSize = 2;
lt_symbol.Windows.push_back( window );
}
else if( element == "SYMATTR" )
{
aggregateAttributeValue( tokens, 2 );
tokensSizeRangeCheck( tokens.size(), 3, INT_MAX, lineNumber, aAscFileName );
wxString key = tokens[1];
wxString value = tokens[2];
lt_symbol.SymAttributes[ key.Capitalize() ] = value;
}
else if( element == "PIN" )
{
tokensSizeRangeCheck( tokens.size(), 5, 5, lineNumber, aAscFileName );
wxString pinLocationX = tokens[1];
wxString pinLocationY = tokens[2];
wxString Justification = tokens[3];
wxString nameOffSet = tokens[4];
LT_PIN pin;
pin.PinLocation = pointCheck( pinLocationX, pinLocationY, lineNumber,aAscFileName );
pin.PinJustification = getPinJustification( Justification );
pin.NameOffSet = integerCheck( nameOffSet, lineNumber, aAscFileName );
lt_symbol.Pins.push_back( pin );
}
else if( element == "PINATTR" )
{
aggregateAttributeValue( tokens, 2 );
tokensSizeRangeCheck( tokens.size(), 3, INT_MAX, lineNumber, aAscFileName );
wxString name = tokens[1];
wxString Value = tokens[2];
lt_symbol.Pins.back().PinAttribute.insert( { name, Value } );
}
else if( element == "SYMBOLTYPE" )
{
tokensSizeRangeCheck( tokens.size(), 2, 2, lineNumber, aAscFileName );
wxString symbolType = tokens[1];
lt_symbol.SymbolType = getSymbolType( symbolType );
}
lineNumber++;
}
return lt_symbol;
}
std::vector<LTSPICE_SCHEMATIC::LT_ASC> LTSPICE_SCHEMATIC::StructureBuilder()
{
// Initialising Symbol Struct
std::vector<LT_ASC> ascFiles;
for( const auto& [ fileName, contents ] : m_fileCache[ wxS( "ascFiles" ) ] )
{
LT_ASC ascFile;
std::vector<LT_SYMBOL> symbolArray;
ascFile.SheetSize = VECTOR2I( 0, 0 );
ascFile.Symbols = symbolArray;
ascFile.Version = 0;
ascFile.SheetNumber = 0;
int lineNumber = 1;
for( wxString line : wxSplit( contents, '\n' ) )
{
removeCarriageReturn( line );
wxArrayString tokens = wxSplit( line, ' ' );
if( tokens.IsEmpty() )
continue;
wxString element = tokens[0].Upper();
if( element == "SHEET" )
{
tokensSizeRangeCheck( tokens.size(), 4, 4, lineNumber, fileName );
wxString sheetNumber = tokens[1];
wxString sheetWidth = tokens[2];
wxString sheetHeight = tokens[3];
ascFile.SheetNumber = integerCheck( sheetNumber, lineNumber, fileName );
ascFile.SheetSize = pointCheck( sheetWidth, sheetHeight, lineNumber, fileName );
}
else if( element == "SYMBOL" )
{
tokensSizeRangeCheck( tokens.size(), 5, 5, lineNumber, fileName );
wxString symbolName = tokens[1];
wxString posX = tokens[2];
wxString posY = tokens[3];
wxString rotate_mirror_option = tokens[4];
symbolName.Replace( '\\', '/' );
LT_SYMBOL lt_symbol = SymbolBuilder( symbolName, ascFile );
lt_symbol.Offset = pointCheck( posX, posY, lineNumber, fileName );
lt_symbol.SymbolOrientation = getSymbolRotationOrMirror( rotate_mirror_option );
ascFile.Symbols.push_back( lt_symbol );
ascFile.BoundingBox.Merge( lt_symbol.Offset );
}
else if( element == "WIRE" )
{
tokensSizeRangeCheck( tokens.size(), 5, 5, lineNumber, fileName );
wxString startPointX = tokens[1];
wxString startPointY = tokens[2];
wxString endPointX = tokens[3];
wxString endPointY = tokens[4];
WIRE wire;
wire.Start = pointCheck( startPointX, startPointY, lineNumber, fileName );
wire.End = pointCheck( endPointX, endPointY, lineNumber, fileName );
ascFile.Wires.push_back( wire );
ascFile.BoundingBox.Merge( wire.Start );
ascFile.BoundingBox.Merge( wire.End );
}
else if( element == "FLAG" )
{
tokensSizeRangeCheck( tokens.size(), 4, 4, lineNumber, fileName );
wxString posX = tokens[1];
wxString posY = tokens[2];
wxString name = tokens[3];
FLAG flag;
flag.Offset = pointCheck( posX, posY, lineNumber, fileName );
flag.Value = name;
flag.FontSize = 2;
ascFile.Flags.push_back( flag );
ascFile.BoundingBox.Merge( flag.Offset );
}
else if( element == "DATAFLAG" )
{
tokensSizeRangeCheck( tokens.size(), 4, 4, lineNumber, fileName );
wxString posX = tokens[1];
wxString posY = tokens[2];
wxString expression = tokens[3];
DATAFLAG flag;
flag.Offset = pointCheck( posX, posY, lineNumber, fileName );
flag.Expression = expression;
flag.FontSize = 2;
ascFile.DataFlags.push_back( flag );
ascFile.BoundingBox.Merge( flag.Offset );
}
else if( element == "WINDOW" )
{
tokensSizeRangeCheck( tokens.size(), 6, 6, lineNumber, fileName );
wxString number = tokens[1];
wxString windowPosX = tokens[2];
wxString windowPosY = tokens[3];
wxString justification = tokens[4];
wxString fontSize = tokens[5];
int windowNumber = integerCheck( number, lineNumber, fileName );
LT_WINDOW* window = nullptr;
// Overwrite an existing window from the symbol definition
for( LT_WINDOW& candidate : ascFile.Symbols.back().Windows )
{
if( candidate.WindowNumber == windowNumber )
{
window = &candidate;
break;
}
}
if( !window )
{
ascFile.Symbols.back().Windows.emplace_back( LT_WINDOW() );
window = &ascFile.Symbols.back().Windows.back();
}
window->WindowNumber = windowNumber;
window->Position = pointCheck( windowPosX, windowPosY, lineNumber, fileName );
window->Justification = getTextJustification( justification );
window->FontSize = integerCheck( fontSize, lineNumber, fileName );
ascFile.BoundingBox.Merge( window->Position );
}
else if( element == "SYMATTR" )
{
tokensSizeRangeCheck( tokens.size(), 3, INT_MAX, lineNumber, fileName );
aggregateAttributeValue( tokens, 2 );
wxString name = tokens[1];
wxString value = tokens[2];
ascFile.Symbols.back().SymAttributes[ name.Upper() ] = value;
}
else if( element == "LINE" )
{
tokensSizeRangeCheck( tokens.size(), 6, 7, lineNumber, fileName );
wxString lineWidth = tokens[1];
wxString startPointX = tokens[2];
wxString startPointY = tokens[3];
wxString endPointX = tokens[4];
wxString endPointY = tokens[5];
LINE lt_line;
lt_line.LineWidth = getLineWidth( lineWidth );
lt_line.Start = pointCheck( startPointX, startPointY, lineNumber, fileName );
lt_line.End = pointCheck( endPointX, endPointY, lineNumber, fileName );
int lineStyleNumber = 0; // default
if( tokens.size() == 7 )
{
wxString lineStyle = tokens[6];
lineStyleNumber = integerCheck( lineStyle, lineNumber, fileName );
}
lt_line.LineStyle = getLineStyle( lineStyleNumber );
ascFile.Lines.push_back( lt_line );
ascFile.BoundingBox.Merge( lt_line.Start );
ascFile.BoundingBox.Merge( lt_line.End );
}
else if( element == "RECTANGLE" )
{
tokensSizeRangeCheck( tokens.size(), 6, 7, lineNumber, fileName );
wxString lineWidth = tokens[1];
wxString botRightX = tokens[2];
wxString botRightY = tokens[3];
wxString topLeftX = tokens[4];
wxString topRightY = tokens[5];
RECTANGLE rect;
rect.LineWidth = getLineWidth( tokens[1] );
rect.BotRight = pointCheck( botRightX, botRightY, lineNumber, fileName );
rect.TopLeft = pointCheck( topLeftX, topRightY, lineNumber, fileName );
int lineStyleNumber = 0; // default
if( tokens.size() == 7 )
{
wxString lineStyle = tokens[6];
lineStyleNumber = integerCheck( lineStyle, lineNumber, fileName );
}
rect.LineStyle = getLineStyle( lineStyleNumber );
ascFile.Rectangles.push_back( rect );
ascFile.BoundingBox.Merge( rect.TopLeft );
ascFile.BoundingBox.Merge( rect.BotRight );
}
else if( element == "CIRCLE" )
{
tokensSizeRangeCheck( tokens.size(), 6, 7, lineNumber, fileName );
wxString lineWidth = tokens[1];
wxString botRightX = tokens[2];
wxString botRightY = tokens[3];
wxString topLeftX = tokens[4];
wxString topRightY = tokens[5];
CIRCLE circle;
circle.LineWidth = getLineWidth( lineWidth );
circle.BotRight = pointCheck( botRightX, botRightY, lineNumber, fileName );
circle.TopLeft = pointCheck( topLeftX, topRightY, lineNumber, fileName );
int lineStyleNumber = 0; // default
if( tokens.size() == 7 )
{
wxString lineStyle = tokens[6];
lineStyleNumber = integerCheck( lineStyle, lineNumber, fileName );
}
circle.LineStyle = getLineStyle( lineStyleNumber );
ascFile.Circles.push_back( circle );
ascFile.BoundingBox.Merge( circle.TopLeft );
ascFile.BoundingBox.Merge( circle.BotRight );
}
else if( element == "ARC" )
{
/**
* The Arc is enclosed in the square given by above coordinates and its start and
* end coordinates are given.
* The arc is drawn counterclockwise from the starting point to the ending point.
*/
tokensSizeRangeCheck( tokens.size(), 10, 11, lineNumber, fileName );
wxString lineWidth = tokens[1];
wxString botRightX = tokens[2];
wxString botRightY = tokens[3];
wxString topLeftX = tokens[4];
wxString topRightY = tokens[5];
wxString arcStartPointX = tokens[6];
wxString arcStartPointY = tokens[7];
wxString arcEndPointX = tokens[8];
wxString arcEndPointY = tokens[9];
ARC arc;
arc.LineWidth = getLineWidth( lineWidth );
arc.BotRight = pointCheck( botRightX, botRightY, lineNumber, fileName );
arc.TopLeft = pointCheck( topLeftX, topRightY, lineNumber, fileName );
arc.ArcEnd = pointCheck( arcStartPointX, arcStartPointY, lineNumber, fileName );
arc.ArcStart = pointCheck( arcEndPointX, arcEndPointY, lineNumber, fileName );
int lineStyleNumber = 0; // default
if( tokens.size() == 11 )
{
wxString lineStyle = tokens[10];
lineStyleNumber = integerCheck( lineStyle, lineNumber, fileName );
}
arc.LineStyle = getLineStyle( lineStyleNumber );
ascFile.Arcs.push_back( arc );
ascFile.BoundingBox.Merge( arc.TopLeft );
ascFile.BoundingBox.Merge( arc.BotRight );
}
else if( element == "IOPIN" )
{
tokensSizeRangeCheck( tokens.size(), 4, 4, lineNumber, fileName );
wxString pinLocationX = tokens[1];
wxString pinLocationY = tokens[2];
wxString pinPolarity = tokens[3];
IOPIN iopin;
iopin.Location = pointCheck( pinLocationX, pinLocationY, lineNumber, fileName );
iopin.Polarity = getPolarity( pinPolarity );
ascFile.Iopins.push_back( iopin );
ascFile.BoundingBox.Merge( iopin.Location );
}
else if( element == "TEXT" )
{
aggregateAttributeValue( tokens, 5 );
tokensSizeRangeCheck( tokens.size(), 6, INT_MAX, lineNumber, fileName );
wxString positionX = tokens[1];
wxString positionY = tokens[2];
wxString justification = tokens[3];
wxString fontSize = tokens[4];
wxString value = tokens[5];
TEXT text;
text.Offset = pointCheck( positionX, positionY, lineNumber, fileName );
text.Justification = getTextJustification( justification );
text.FontSize = integerCheck( fontSize, lineNumber, fileName );
if( value.StartsWith( wxS( "!" ), &text.Value ) )
text.Value.Replace( wxS( "! " ), wxS( "\n" ) ); // replace subsequent ! with \n
else if( value.StartsWith( wxS( ";" ), &text.Value ) )
text.Value.Replace( wxS( "; " ), wxS( "\n" ) ); // replace subsequent ; with \n
else
text.Value = value;
text.Value.Replace( wxS( "\\n" ), wxS( "\n" ) );
ascFile.Texts.push_back( text );
ascFile.BoundingBox.Merge( text.Offset );
}
else if( element == "BUSTAP" )
{
tokensSizeRangeCheck( tokens.size(), 5, 5, lineNumber, fileName );
wxString startPointX = tokens[1];
wxString startPointY = tokens[2];
wxString endPointX = tokens[3];
wxString endPointY = tokens[4];
BUSTAP bustap;
bustap.Start = pointCheck( startPointX, startPointY, lineNumber, fileName );
bustap.End = pointCheck( endPointX, endPointY, lineNumber, fileName );
ascFile.Bustap.push_back( bustap );
ascFile.BoundingBox.Merge( bustap.Start );
ascFile.BoundingBox.Merge( bustap.End );
}
else if( element == "VERSION" )
{
wxString versionNumber = tokens[1];
ascFile.Version = integerCheck( versionNumber, lineNumber, fileName );
}
lineNumber++;
}
ascFiles.push_back( ascFile );
}
return ascFiles;
}