7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2024-11-27 22:26:10 +00:00
kicad/eeschema/sch_io/ltspice/ltspice_schematic.cpp
John Beard 3d6d8b9946 Strip richio.h from headers that don't need them
Like the DSNLEXER header, this has visibility in over 700
files, whereas well under half actually use any of it
(quite a bit, but not all, of it actually via DSNLEXER)

Many places already forward-declare the OUTPUTFORMATTER type,
by doing that for the others, it still possible to use the
non-IO methods without having to see richio.h.
2024-10-04 18:06:18 +01:00

1138 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_io/ltspice/ltspice_schematic.h"
#include <sch_io/ltspice/sch_io_ltspice_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>
#include <project.h>
#include <richio.h>
void LTSPICE_SCHEMATIC::Load( SCHEMATIC* aSchematic, SCH_SHEET* aRootSheet,
const wxFileName& aLibraryFileName, REPORTER* aReporter )
{
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 = nullptr;
// 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 ) )
{
if( !screen )
screen = new SCH_SCREEN( m_schematic );
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;
for( const LTSPICE_FILE& file : sourceFiles )
wxASSERT( file.Sheet == nullptr && file.Screen == nullptr );
}
SCH_SHEET_PATH curSheetPath;
SCH_IO_LTSPICE_PARSER parser( this );
if( i > 0 )
{
SCH_SHEET* curSheet = ascFiles[i].Sheet;
std::map tempAsyMap = ReadAsyFile( ascFiles[i], 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
{
SCH_SHEET* 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 );
try
{
std::vector<LTSPICE_SCHEMATIC::LT_ASC> lt_ascs = StructureBuilder();
parser.Parse( &curSheetPath, lt_ascs, subSchematicAsyFiles );
}
catch( IO_ERROR& e )
{
aReporter->Report( e.What(), RPT_SEVERITY_ERROR );
}
}
}
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::ReadAsyFile( const LTSPICE_FILE& aSourceFile,
const std::map<wxString, wxString>& aAsyFileMap )
{
std::map<wxString, wxString> resultantMap;
wxString fileName = aSourceFile.ElementName;
if( aAsyFileMap.count( fileName ) )
resultantMap[fileName] = SafeReadFile( aAsyFileMap.at( fileName ), wxS( "r" ) );
return resultantMap;
}
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];
if( value == wxS( "\"\"" ) )
value = wxEmptyString;
lt_symbol.SymAttributes[ key.Upper() ] = 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];
if( value == wxS( "\"\"" ) )
value = wxEmptyString;
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;
}