kicad/eeschema/sch_plugins/kicad/sch_sexpr_parser.cpp

4098 lines
116 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 CERN
* Copyright (C) 2022-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Wayne Stambaugh <stambaughw@gmail.com>
*
* 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/>.
*/
/**
* @file sch_sexpr_parser.cpp
* @brief Schematic and symbol library s-expression file format parser implementations.
*/
// For some reason wxWidgets is built with wxUSE_BASE64 unset so expose the wxWidgets
// base64 code.
#include <charconv>
#define wxUSE_BASE64 1
#include <wx/base64.h>
#include <wx/mstream.h>
#include <wx/tokenzr.h>
#include <base_units.h>
#include <lib_id.h>
#include <lib_shape.h>
#include <lib_pin.h>
#include <lib_text.h>
#include <lib_textbox.h>
#include <math/util.h> // KiROUND, Clamp
#include <font/font.h>
#include <string_utils.h>
#include <sch_bitmap.h>
#include <sch_bus_entry.h>
#include <sch_symbol.h>
#include <sch_edit_frame.h> // SYM_ORIENT_XXX
#include <sch_field.h>
#include <sch_line.h>
#include <sch_textbox.h>
#include <sch_label.h>
#include <sch_junction.h>
#include <sch_no_connect.h>
#include <sch_screen.h>
#include <sch_sheet_pin.h>
#include <sch_plugins/kicad/sch_sexpr_parser.h>
#include <template_fieldnames.h>
#include <trigo.h>
#include <progress_reporter.h>
#include <sch_shape.h>
using namespace TSCHEMATIC_T;
SCH_SEXPR_PARSER::SCH_SEXPR_PARSER( LINE_READER* aLineReader, PROGRESS_REPORTER* aProgressReporter,
unsigned aLineCount, SCH_SHEET* aRootSheet,
bool aIsAppending ) :
SCHEMATIC_LEXER( aLineReader ),
m_requiredVersion( 0 ),
m_unit( 1 ),
m_convert( 1 ),
m_appending( aIsAppending ),
m_progressReporter( aProgressReporter ),
m_lineReader( aLineReader ),
m_lastProgressLine( 0 ),
m_lineCount( aLineCount ),
m_rootSheet( aRootSheet )
{
}
void SCH_SEXPR_PARSER::checkpoint()
{
const unsigned PROGRESS_DELTA = 250;
if( m_progressReporter )
{
unsigned curLine = m_lineReader->LineNumber();
if( curLine > m_lastProgressLine + PROGRESS_DELTA )
{
m_progressReporter->SetCurrentProgress( ( (double) curLine )
/ std::max( 1U, m_lineCount ) );
if( !m_progressReporter->KeepRefreshing() )
THROW_IO_ERROR( ( "Open cancelled by user." ) );
m_lastProgressLine = curLine;
}
}
}
KIID SCH_SEXPR_PARSER::parseKIID()
{
KIID id( FromUTF8() );
while( m_uuids.count( id ) )
id.Increment();
m_uuids.insert( id );
return id;
}
bool SCH_SEXPR_PARSER::parseBool()
{
T token = NextTok();
if( token == T_yes )
return true;
else if( token == T_no )
return false;
else
Expecting( "yes or no" );
return false;
}
void SCH_SEXPR_PARSER::ParseLib( LIB_SYMBOL_MAP& aSymbolLibMap )
{
T token;
NeedLEFT();
NextTok();
parseHeader( T_kicad_symbol_lib, SEXPR_SYMBOL_LIB_FILE_VERSION );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
if( token == T_symbol )
{
m_unit = 1;
m_convert = 1;
LIB_SYMBOL* symbol = parseLibSymbol( aSymbolLibMap );
aSymbolLibMap[symbol->GetName()] = symbol;
}
else
{
Expecting( "symbol" );
}
}
}
LIB_SYMBOL* SCH_SEXPR_PARSER::ParseSymbol( LIB_SYMBOL_MAP& aSymbolLibMap, int aFileVersion )
{
LIB_SYMBOL* newSymbol = nullptr;
NextTok();
// If there actually isn't anything here, don't throw just return a nullptr
if( CurTok() == T_LEFT )
{
NextTok();
if( CurTok() == T_symbol )
{
m_requiredVersion = aFileVersion;
newSymbol = parseLibSymbol( aSymbolLibMap );
}
else
{
wxString msg = wxString::Format( _( "Cannot parse %s as a symbol" ),
GetTokenString( CurTok() ) );
THROW_PARSE_ERROR( msg, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
}
}
return newSymbol;
}
LIB_SYMBOL* SCH_SEXPR_PARSER::parseLibSymbol( LIB_SYMBOL_MAP& aSymbolLibMap )
{
wxCHECK_MSG( CurTok() == T_symbol, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a symbol." ) );
T token;
long tmp;
wxString name;
wxString error;
wxString unitDisplayName;
LIB_ITEM* item;
std::unique_ptr<LIB_SYMBOL> symbol = std::make_unique<LIB_SYMBOL>( wxEmptyString );
symbol->SetUnitCount( 1 );
m_fieldIDsRead.clear();
// Make sure the mandatory field IDs are reserved as already read,
// the field parser will set the field IDs to the correct value if
// the field name matches a mandatory field name
for( int i = 0; i < MANDATORY_FIELDS; i++ )
m_fieldIDsRead.insert( i );
token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid symbol name" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
name = FromUTF8();
LIB_ID id;
int bad_pos = id.Parse( name );
if( bad_pos >= 0 )
{
if( static_cast<int>( name.size() ) > bad_pos )
{
wxString msg = wxString::Format(
_( "Symbol %s contains invalid character '%c'" ), name,
name[bad_pos] );
THROW_PARSE_ERROR( msg, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
}
THROW_PARSE_ERROR( _( "Invalid library identifier" ), CurSource(), CurLine(),
CurLineNumber(), CurOffset() );
}
m_symbolName = id.GetLibItemName().wx_str();
symbol->SetName( m_symbolName );
symbol->SetLibId( id );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_power:
symbol->SetPower();
NeedRIGHT();
break;
case T_pin_names:
parsePinNames( symbol );
break;
case T_pin_numbers:
token = NextTok();
if( token != T_hide )
Expecting( "hide" );
symbol->SetShowPinNumbers( false );
NeedRIGHT();
break;
case T_in_bom:
symbol->SetExcludedFromBOM( !parseBool() );
NeedRIGHT();
break;
case T_on_board:
symbol->SetExcludedFromBoard( !parseBool() );
NeedRIGHT();
break;
case T_property:
parseProperty( symbol );
break;
case T_extends:
{
token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid parent symbol name" ), CurSource(), CurLine(),
CurLineNumber(), CurOffset() );
}
name = FromUTF8();
auto it = aSymbolLibMap.find( name );
if( it == aSymbolLibMap.end() )
{
error.Printf( _( "No parent for extended symbol %s" ), name.c_str() );
THROW_PARSE_ERROR( error, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
}
symbol->SetParent( it->second );
NeedRIGHT();
break;
}
case T_symbol:
{
token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid symbol unit name" ), CurSource(), CurLine(),
CurLineNumber(), CurOffset() );
}
name = FromUTF8();
if( !name.StartsWith( m_symbolName ) )
{
error.Printf( _( "Invalid symbol unit name prefix %s" ), name.c_str() );
THROW_PARSE_ERROR( error, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
}
name = name.Right( name.Length() - m_symbolName.Length() - 1 );
wxStringTokenizer tokenizer( name, "_" );
if( tokenizer.CountTokens() != 2 )
{
error.Printf( _( "Invalid symbol unit name suffix %s" ), name.c_str() );
THROW_PARSE_ERROR( error, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
}
if( !tokenizer.GetNextToken().ToLong( &tmp ) )
{
error.Printf( _( "Invalid symbol unit number %s" ), name.c_str() );
THROW_PARSE_ERROR( error, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
}
m_unit = static_cast<int>( tmp );
if( !tokenizer.GetNextToken().ToLong( &tmp ) )
{
error.Printf( _( "Invalid symbol convert number %s" ), name.c_str() );
THROW_PARSE_ERROR( error, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
}
m_convert = static_cast<int>( tmp );
if( m_convert > 1 )
symbol->SetConversion( true, false );
if( m_unit > symbol->GetUnitCount() )
symbol->SetUnitCount( m_unit, false );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_unit_name:
token = NextTok();
if( IsSymbol( token ) )
{
unitDisplayName = FromUTF8();
symbol->SetUnitDisplayName( m_unit, unitDisplayName );
}
NeedRIGHT();
break;
case T_arc:
case T_bezier:
case T_circle:
case T_pin:
case T_polyline:
case T_rectangle:
case T_text:
case T_text_box:
item = ParseDrawItem();
wxCHECK_MSG( item, nullptr, "Invalid draw item pointer." );
item->SetParent( symbol.get() );
symbol->AddDrawItem( item, false );
break;
default:
Expecting( "arc, bezier, circle, pin, polyline, rectangle, or text" );
};
}
m_unit = 1;
m_convert = 1;
break;
}
case T_arc:
case T_bezier:
case T_circle:
case T_pin:
case T_polyline:
case T_rectangle:
case T_text:
case T_text_box:
item = ParseDrawItem();
wxCHECK_MSG( item, nullptr, "Invalid draw item pointer." );
item->SetParent( symbol.get() );
symbol->AddDrawItem( item, false );
break;
default:
Expecting( "pin_names, pin_numbers, arc, bezier, circle, pin, polyline, "
"rectangle, or text" );
}
}
symbol->GetDrawItems().sort();
m_symbolName.clear();
return symbol.release();
}
LIB_ITEM* SCH_SEXPR_PARSER::ParseDrawItem()
{
switch( CurTok() )
{
case T_arc:
return parseArc();
break;
case T_bezier:
return parseBezier();
break;
case T_circle:
return parseCircle();
break;
case T_pin:
return parsePin();
break;
case T_polyline:
return parsePolyLine();
break;
case T_rectangle:
return parseRectangle();
break;
case T_text:
return parseText();
break;
case T_text_box:
return parseTextBox();
break;
default:
Expecting( "arc, bezier, circle, pin, polyline, rectangle, or text" );
}
return nullptr;
}
int SCH_SEXPR_PARSER::parseInternalUnits()
{
auto retval = parseDouble() * schIUScale.IU_PER_MM;
// Schematic internal units are represented as integers. Any values that are
// larger or smaller than the schematic units represent undefined behavior for
// the system. Limit values to the largest that can be displayed on the screen.
constexpr double int_limit = std::numeric_limits<int>::max() * 0.7071; // 0.7071 = roughly 1/sqrt(2)
return KiROUND( Clamp<double>( -int_limit, retval, int_limit ) );
}
int SCH_SEXPR_PARSER::parseInternalUnits( const char* aExpected )
{
auto retval = parseDouble( aExpected ) * schIUScale.IU_PER_MM;
constexpr double int_limit = std::numeric_limits<int>::max() * 0.7071;
return KiROUND( Clamp<double>( -int_limit, retval, int_limit ) );
}
void SCH_SEXPR_PARSER::parseStroke( STROKE_PARAMS& aStroke )
{
STROKE_PARAMS_PARSER strokeParser( reader, schIUScale.IU_PER_MM );
strokeParser.SyncLineReaderWith( *this );
strokeParser.ParseStroke( aStroke );
SyncLineReaderWith( strokeParser );
}
void SCH_SEXPR_PARSER::parseFill( FILL_PARAMS& aFill )
{
wxCHECK_RET( CurTok() == T_fill, "Cannot parse " + GetTokenString( CurTok() ) + " as a fill." );
aFill.m_FillType = FILL_T::NO_FILL;
aFill.m_Color = COLOR4D::UNSPECIFIED;
T token;
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_type:
{
token = NextTok();
switch( token )
{
case T_none: aFill.m_FillType = FILL_T::NO_FILL; break;
case T_outline: aFill.m_FillType = FILL_T::FILLED_SHAPE; break;
case T_background: aFill.m_FillType = FILL_T::FILLED_WITH_BG_BODYCOLOR; break;
case T_color: aFill.m_FillType = FILL_T::FILLED_WITH_COLOR; break;
default: Expecting( "none, outline, color or background" );
}
NeedRIGHT();
break;
}
case T_color:
{
COLOR4D color;
color.r = parseInt( "red" ) / 255.0;
color.g = parseInt( "green" ) / 255.0;
color.b = parseInt( "blue" ) / 255.0;
color.a = Clamp( parseDouble( "alpha" ), 0.0, 1.0 );
aFill.m_Color = color;
NeedRIGHT();
break;
}
default:
Expecting( "type or color" );
}
}
}
void SCH_SEXPR_PARSER::parseEDA_TEXT( EDA_TEXT* aText, bool aConvertOverbarSyntax )
{
wxCHECK_RET( aText && ( CurTok() == T_effects || CurTok() == T_href ),
"Cannot parse " + GetTokenString( CurTok() ) + " as an EDA_TEXT." );
// In version 20210606 the notation for overbars was changed from `~...~` to `~{...}`.
// We need to convert the old syntax to the new one.
if( aConvertOverbarSyntax && m_requiredVersion < 20210606 )
aText->SetText( ConvertToNewOverbarNotation( aText->GetText() ) );
T token;
wxString faceName;
COLOR4D color = COLOR4D::UNSPECIFIED;
// Various text objects (text boxes, schematic text, etc.) all have their own defaults,
// but the file format default is {center,center} so we have to set that before parsing.
aText->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
aText->SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token == T_LEFT )
token = NextTok();
switch( token )
{
case T_font:
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token == T_LEFT )
token = NextTok();
switch( token )
{
case T_face:
NeedSYMBOL();
faceName = FromUTF8();
NeedRIGHT();
break;
case T_size:
{
VECTOR2I sz;
sz.y = parseInternalUnits( "text height" );
sz.x = parseInternalUnits( "text width" );
aText->SetTextSize( sz );
NeedRIGHT();
break;
}
case T_thickness:
aText->SetTextThickness( parseInternalUnits( "text thickness" ) );
NeedRIGHT();
break;
case T_bold:
aText->SetBold( true );
break;
case T_italic:
aText->SetItalic( true );
break;
case T_color:
color.r = parseInt( "red" ) / 255.0;
color.g = parseInt( "green" ) / 255.0;
color.b = parseInt( "blue" ) / 255.0;
color.a = Clamp( parseDouble( "alpha" ), 0.0, 1.0 );
aText->SetTextColor( color );
NeedRIGHT();
break;
case T_line_spacing:
aText->SetLineSpacing( parseDouble( "line spacing" ) );
NeedRIGHT();
break;
default:
Expecting( "face, size, thickness, line_spacing, bold, or italic" );
}
}
if( !faceName.IsEmpty() )
{
aText->SetFont( KIFONT::FONT::GetFont( faceName, aText->IsBold(),
aText->IsItalic() ) );
}
break;
case T_justify:
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
switch( token )
{
case T_left: aText->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break;
case T_right: aText->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break;
case T_top: aText->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); break;
case T_bottom: aText->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); break;
// Do not set mirror property for schematic text elements
case T_mirror: break;
default: Expecting( "left, right, top, bottom, or mirror" );
}
}
break;
case T_href:
{
NeedSYMBOL();
wxString hyperlink = FromUTF8();
if( !aText->ValidateHyperlink( hyperlink ) )
{
THROW_PARSE_ERROR( wxString::Format( _( "Invalid hyperlink url '%s'" ), hyperlink ),
CurSource(), CurLine(), CurLineNumber(), CurOffset() );
}
else
{
aText->SetHyperlink( hyperlink );
}
NeedRIGHT();
}
break;
case T_hide:
aText->SetVisible( false );
break;
default:
Expecting( "font, justify, hide or href" );
}
}
}
void SCH_SEXPR_PARSER::parseHeader( TSCHEMATIC_T::T aHeaderType, int aFileVersion )
{
wxCHECK_RET( CurTok() == aHeaderType,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a header." ) );
NeedLEFT();
T tok = NextTok();
if( tok == T_version )
{
m_requiredVersion = parseInt( FromUTF8().mb_str( wxConvUTF8 ) );
if( m_requiredVersion > aFileVersion )
throw FUTURE_FORMAT_ERROR( FromUTF8() );
NeedRIGHT();
// Skip the host name and host build version information.
NeedLEFT();
NeedSYMBOL();
NeedSYMBOL();
if( m_requiredVersion < 20200827 )
NeedSYMBOL();
NeedRIGHT();
}
else
{
m_requiredVersion = aFileVersion;
// Skip the host name and host build version information.
NeedSYMBOL();
NeedSYMBOL();
NeedRIGHT();
}
}
void SCH_SEXPR_PARSER::parsePinNames( std::unique_ptr<LIB_SYMBOL>& aSymbol )
{
wxCHECK_RET( CurTok() == T_pin_names,
"Cannot parse " + GetTokenString( CurTok() ) + " as a pin_name token." );
T token = NextTok();
if( token == T_LEFT )
{
token = NextTok();
if( token != T_offset )
Expecting( "offset" );
aSymbol->SetPinNameOffset( parseInternalUnits( "pin name offset" ) );
NeedRIGHT();
token = NextTok(); // Either ) or hide
}
if( token == T_hide )
{
aSymbol->SetShowPinNames( false );
NeedRIGHT();
}
else if( token != T_RIGHT )
{
THROW_PARSE_ERROR( _( "Invalid pin names definition" ), CurSource(), CurLine(),
CurLineNumber(), CurOffset() );
}
}
LIB_FIELD* SCH_SEXPR_PARSER::parseProperty( std::unique_ptr<LIB_SYMBOL>& aSymbol )
{
wxCHECK_MSG( CurTok() == T_property, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a property." ) );
wxCHECK( aSymbol, nullptr );
wxString name;
wxString value;
std::unique_ptr<LIB_FIELD> field = std::make_unique<LIB_FIELD>( aSymbol.get(),
MANDATORY_FIELDS );
T token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid property name" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
name = FromUTF8();
if( name.IsEmpty() )
{
THROW_PARSE_ERROR( _( "Empty property name" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
field->SetName( name );
// Correctly set the ID based on canonical (untranslated) field name
// If ID is stored in the file (old versions), it will overwrite this
for( int ii = 0; ii < MANDATORY_FIELDS; ++ii )
{
if( !name.CmpNoCase( TEMPLATE_FIELDNAME::GetDefaultFieldName( ii ) ) )
{
field->SetId( ii );
break;
}
}
token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid property value" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
// Empty property values are valid.
value = FromUTF8();
field->SetText( value );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
// I am not sure we should even support parsing these IDs any more
case T_id:
{
int id = parseInt( "field ID" );
// Only set an ID that isn't a MANDATORY_FIELDS ID
if( id >= MANDATORY_FIELDS )
field->SetId( id );
NeedRIGHT();
}
break;
case T_at:
field->SetPosition( parseXY() );
field->SetTextAngle( EDA_ANGLE( parseDouble( "text angle" ), DEGREES_T ) );
NeedRIGHT();
break;
case T_effects:
parseEDA_TEXT( static_cast<EDA_TEXT*>( field.get() ), field->GetId() == VALUE_FIELD );
break;
case T_show_name:
field->SetNameShown();
NeedRIGHT();
break;
case T_do_not_autoplace:
field->SetCanAutoplace( false );
NeedRIGHT();
break;
default:
Expecting( "id, at, show_name, do_not_autoplace, or effects" );
}
}
// Due to an bug when in #LIB_SYMBOL::Flatten, duplicate ids slipped through
// when writing files. This section replaces duplicate #LIB_FIELD indices on
// load.
if( ( field->GetId() >= MANDATORY_FIELDS ) && m_fieldIDsRead.count( field->GetId() ) )
{
int nextAvailableId = field->GetId() + 1;
while( m_fieldIDsRead.count( nextAvailableId ) )
nextAvailableId += 1;
field->SetId( nextAvailableId );
}
LIB_FIELD* existingField;
if( field->GetId() < MANDATORY_FIELDS )
{
existingField = aSymbol->GetFieldById( field->GetId() );
*existingField = *field;
m_fieldIDsRead.insert( field->GetId() );
return existingField;
}
else if( name == "ki_keywords" )
{
// Not a LIB_FIELD object yet.
aSymbol->SetKeyWords( value );
return nullptr;
}
// In v7 and earlier the description field didn't exist and was a key/value
else if( name == "ki_description" )
{
aSymbol->SetDescription( value );
return nullptr;
}
else if( name == "ki_fp_filters" )
{
// Not a LIB_FIELD object yet.
wxArrayString filters;
wxStringTokenizer tokenizer( value );
while( tokenizer.HasMoreTokens() )
{
wxString curr_token = UnescapeString( tokenizer.GetNextToken() );
filters.Add( curr_token );
}
aSymbol->SetFPFilters( filters );
return nullptr;
}
else if( name == "ki_locked" )
{
// This is a temporary LIB_FIELD object until interchangeable units are determined on
// the fly.
aSymbol->LockUnits( true );
return nullptr;
}
else
{
// At this point, a user field is read.
existingField = aSymbol->FindField( field->GetCanonicalName() );
#if 1 // Enable it to modify the name of the field to add if already existing
// Disable it to skip the field having the same name as previous field
if( existingField )
{
// We cannot handle 2 fields with the same name, so because the field name
// is already in use, try to build a new name (oldname_x)
wxString base_name = field->GetCanonicalName();
// Arbitrary limit 10 attempts to find a new name
for( int ii = 1; ii < 10 && existingField; ii++ )
{
wxString newname = base_name;
newname << '_' << ii;
existingField = aSymbol->FindField( newname );
if( !existingField ) // the modified name is not found, use it
field->SetName( newname );
}
}
#endif
if( !existingField )
{
aSymbol->AddDrawItem( field.get(), false );
m_fieldIDsRead.insert( field->GetId() );
return field.release();
}
else
{
// We cannot handle 2 fields with the same name, so skip this one
return nullptr;
}
}
}
LIB_SHAPE* SCH_SEXPR_PARSER::parseArc()
{
wxCHECK_MSG( CurTok() == T_arc, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as an arc." ) );
T token;
VECTOR2I startPoint( 1, 0 ); // Initialize to a non-degenerate arc just for safety
VECTOR2I midPoint( 1, 1 );
VECTOR2I endPoint( 0, 1 );
bool hasMidPoint = false;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
// Parameters for legacy format
VECTOR2I center( 0, 0 );
EDA_ANGLE startAngle = ANGLE_0;
EDA_ANGLE endAngle = ANGLE_90;
bool hasAngles = false;
std::unique_ptr<LIB_SHAPE> arc = std::make_unique<LIB_SHAPE>( nullptr, SHAPE_T::ARC );
arc->SetUnit( m_unit );
arc->SetConvert( m_convert );
token = NextTok();
if( token == T_private )
{
arc->SetPrivate( true );
token = NextTok();
}
for( ; token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_start:
startPoint = parseXY();
NeedRIGHT();
break;
case T_mid:
midPoint = parseXY();
NeedRIGHT();
hasMidPoint = true;
break;
case T_end:
endPoint = parseXY();
NeedRIGHT();
break;
case T_radius:
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_at:
center = parseXY();
NeedRIGHT();
break;
case T_length:
parseInternalUnits( "radius length" );
NeedRIGHT();
break;
case T_angles:
{
startAngle = EDA_ANGLE( parseDouble( "start radius angle" ), DEGREES_T );
endAngle = EDA_ANGLE( parseDouble( "end radius angle" ), DEGREES_T );
startAngle.Normalize();
endAngle.Normalize();
NeedRIGHT();
hasAngles = true;
break;
}
default:
Expecting( "at, length, or angles" );
}
}
break;
case T_stroke:
parseStroke( stroke );
arc->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
arc->SetFillMode( fill.m_FillType );
arc->SetFillColor( fill.m_Color );
break;
default:
Expecting( "start, mid, end, radius, stroke, or fill" );
}
}
if( hasMidPoint )
{
arc->SetArcGeometry( startPoint, midPoint, endPoint );
#if 1
// Should be not required. Unfortunately it is needed because some bugs created
// incorrect data after conversion of old libraries to the new arc format using
// startPoint, midPoint, endPoint
// Until now, in Eeschema the arc angle should be <= 180 deg.
// If > 180 (bug...) we need to swap arc ends.
// However arc angle == 180 deg can also create issues in some cases (plotters, hittest)
// so also avoid arc == 180 deg
EDA_ANGLE arc_start, arc_end, arc_angle;
arc->CalcArcAngles( arc_start, arc_end );
arc_angle = arc_end - arc_start;
if( arc_angle > ANGLE_180 )
{
// Change arc to its complement (360deg - arc_angle)
arc->SetStart( endPoint );
arc->SetEnd( startPoint );
VECTOR2I new_center = CalcArcCenter( arc->GetStart(), arc->GetEnd(),
ANGLE_360 - arc_angle );
arc->SetCenter( new_center );
}
else if( arc_angle == ANGLE_180 )
{
VECTOR2I new_center = CalcArcCenter( arc->GetStart(), arc->GetEnd(),
EDA_ANGLE( 179.5, DEGREES_T ) );
arc->SetCenter( new_center );
}
#endif
}
else if( hasAngles )
{
arc->SetCenter( center );
/*
* Older versions stored start-end with an implied winding, but the winding was different
* between LibEdit and PCBNew. Since we now use a common class (EDA_SHAPE) for both we
* need to flip one of them. LibEdit drew the short straw.
*/
arc->SetStart( endPoint );
arc->SetEnd( startPoint );
// Like previously, 180 degrees arcs that create issues are just modified
// to be < 180 degrees to do not break some other functions ( Draw, Plot, HitTest)
EDA_ANGLE arc_start, arc_end, arc_angle;
arc->CalcArcAngles( arc_start, arc_end );
arc_angle = arc_end - arc_start;
// The arc angle should be <= 180 deg.
// If > 180 we need to swap arc ends (the first choice was not good)
if( arc_angle > ANGLE_180 )
{
arc->SetStart( startPoint );
arc->SetEnd( endPoint );
}
else if( arc_angle == ANGLE_180 )
{
arc->SetStart( startPoint );
arc->SetEnd( endPoint );
VECTOR2I new_center = CalcArcCenter( arc->GetStart(), arc->GetEnd(),
EDA_ANGLE( 179.5, DEGREES_T ) );
arc->SetCenter( new_center );
}
}
else
{
wxFAIL_MSG( "Setting arc without either midpoint or angles not implemented." );
}
return arc.release();
}
LIB_SHAPE* SCH_SEXPR_PARSER::parseBezier()
{
wxCHECK_MSG( CurTok() == T_bezier, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a bezier." ) );
T token;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
std::unique_ptr<LIB_SHAPE> bezier = std::make_unique<LIB_SHAPE>( nullptr, SHAPE_T::BEZIER );
bezier->SetUnit( m_unit );
bezier->SetConvert( m_convert );
token = NextTok();
if( token == T_private )
{
bezier->SetPrivate( true );
token = NextTok();
}
for( ; token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_pts:
{
int ii = 0;
for( token = NextTok(); token != T_RIGHT; token = NextTok(), ++ii )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
if( token != T_xy )
Expecting( "xy" );
switch( ii )
{
case 0: bezier->SetStart( parseXY() ); break;
case 1: bezier->SetBezierC1( parseXY() ); break;
case 2: bezier->SetBezierC2( parseXY() ); break;
case 3: bezier->SetEnd( parseXY() ); break;
default: Unexpected( "control point" ); break;
}
NeedRIGHT();
}
}
break;
case T_stroke:
parseStroke( stroke );
bezier->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
bezier->SetFillMode( fill.m_FillType );
bezier->SetFillColor( fill.m_Color );
break;
default:
Expecting( "pts, stroke, or fill" );
}
}
bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() );
return bezier.release();
}
LIB_SHAPE* SCH_SEXPR_PARSER::parseCircle()
{
wxCHECK_MSG( CurTok() == T_circle, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a circle." ) );
T token;
VECTOR2I center( 0, 0 );
int radius = 1; // defaulting to 0 could result in troublesome math....
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
std::unique_ptr<LIB_SHAPE> circle = std::make_unique<LIB_SHAPE>( nullptr, SHAPE_T::CIRCLE );
circle->SetUnit( m_unit );
circle->SetConvert( m_convert );
token = NextTok();
if( token == T_private )
{
circle->SetPrivate( true );
token = NextTok();
}
for( ; token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_center:
center = parseXY();
NeedRIGHT();
break;
case T_radius:
radius = parseInternalUnits( "radius length" );
NeedRIGHT();
break;
case T_stroke:
parseStroke( stroke );
circle->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
circle->SetFillMode( fill.m_FillType );
circle->SetFillColor( fill.m_Color );
break;
default:
Expecting( "center, radius, stroke, or fill" );
}
}
circle->SetCenter( center );
circle->SetEnd( VECTOR2I( center.x + radius, center.y ) );
return circle.release();
}
LIB_PIN* SCH_SEXPR_PARSER::parsePin()
{
auto parseType = [&]( T token ) -> ELECTRICAL_PINTYPE
{
switch( token )
{
case T_input: return ELECTRICAL_PINTYPE::PT_INPUT;
case T_output: return ELECTRICAL_PINTYPE::PT_OUTPUT;
case T_bidirectional: return ELECTRICAL_PINTYPE::PT_BIDI;
case T_tri_state: return ELECTRICAL_PINTYPE::PT_TRISTATE;
case T_passive: return ELECTRICAL_PINTYPE::PT_PASSIVE;
case T_unspecified: return ELECTRICAL_PINTYPE::PT_UNSPECIFIED;
case T_power_in: return ELECTRICAL_PINTYPE::PT_POWER_IN;
case T_power_out: return ELECTRICAL_PINTYPE::PT_POWER_OUT;
case T_open_collector: return ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR;
case T_open_emitter: return ELECTRICAL_PINTYPE::PT_OPENEMITTER;
case T_unconnected:
case T_no_connect: return ELECTRICAL_PINTYPE::PT_NC;
case T_free: return ELECTRICAL_PINTYPE::PT_NIC;
default:
Expecting( "input, output, bidirectional, tri_state, passive, "
"unspecified, power_in, power_out, open_collector, "
"open_emitter, free or no_connect" );
return ELECTRICAL_PINTYPE::PT_UNSPECIFIED;
}
};
auto parseShape = [&]( T token ) -> GRAPHIC_PINSHAPE
{
switch( token )
{
case T_line: return GRAPHIC_PINSHAPE::LINE;
case T_inverted: return GRAPHIC_PINSHAPE::INVERTED;
case T_clock: return GRAPHIC_PINSHAPE::CLOCK;
case T_inverted_clock: return GRAPHIC_PINSHAPE::INVERTED_CLOCK;
case T_input_low: return GRAPHIC_PINSHAPE::INPUT_LOW;
case T_clock_low: return GRAPHIC_PINSHAPE::CLOCK_LOW;
case T_output_low: return GRAPHIC_PINSHAPE::OUTPUT_LOW;
case T_edge_clock_high: return GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK;
case T_non_logic: return GRAPHIC_PINSHAPE::NONLOGIC;
default:
Expecting( "line, inverted, clock, inverted_clock, input_low, "
"clock_low, output_low, edge_clock_high, non_logic" );
return GRAPHIC_PINSHAPE::LINE;
}
};
wxCHECK_MSG( CurTok() == T_pin, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a pin token." ) );
T token;
wxString tmp;
wxString error;
std::unique_ptr<LIB_PIN> pin = std::make_unique<LIB_PIN>( nullptr );
pin->SetUnit( m_unit );
pin->SetConvert( m_convert );
// Pin electrical type.
token = NextTok();
pin->SetType( parseType( token ) );
// Pin shape.
token = NextTok();
pin->SetShape( parseShape( token ) );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token == T_hide )
{
pin->SetVisible( false );
continue;
}
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_at:
pin->SetPosition( parseXY() );
switch( parseInt( "pin orientation" ) )
{
case 0: pin->SetOrientation( PIN_RIGHT ); break;
case 90: pin->SetOrientation( PIN_UP ); break;
case 180: pin->SetOrientation( PIN_LEFT ); break;
case 270: pin->SetOrientation( PIN_DOWN ); break;
default: Expecting( "0, 90, 180, or 270" );
}
NeedRIGHT();
break;
case T_length:
pin->SetLength( parseInternalUnits( "pin length" ) );
NeedRIGHT();
break;
case T_name:
token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid pin name" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
if( m_requiredVersion < 20210606 )
pin->SetName( ConvertToNewOverbarNotation( FromUTF8() ) );
else
pin->SetName( FromUTF8() );
token = NextTok();
if( token != T_RIGHT )
{
token = NextTok();
if( token == T_effects )
{
// The EDA_TEXT font effects formatting is used so use and EDA_TEXT object
// so duplicate parsing is not required.
EDA_TEXT text( schIUScale.MilsToIU( DEFAULT_SIZE_TEXT ) );
parseEDA_TEXT( &text, true );
pin->SetNameTextSize( text.GetTextHeight() );
NeedRIGHT();
}
else
{
Expecting( "effects" );
}
}
break;
case T_number:
token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid pin number" ), CurSource(), CurLine(),
CurLineNumber(), CurOffset() );
}
pin->SetNumber( FromUTF8() );
token = NextTok();
if( token != T_RIGHT )
{
token = NextTok();
if( token == T_effects )
{
// The EDA_TEXT font effects formatting is used so use and EDA_TEXT object
// so duplicate parsing is not required.
EDA_TEXT text( schIUScale.MilsToIU( DEFAULT_SIZE_TEXT ) );
parseEDA_TEXT( &text, false );
pin->SetNumberTextSize( text.GetTextHeight() );
NeedRIGHT();
}
else
{
Expecting( "effects" );
}
}
break;
case T_alternate:
{
LIB_PIN::ALT alt;
token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid alternate pin name" ), CurSource(), CurLine(),
CurLineNumber(), CurOffset() );
}
alt.m_Name = FromUTF8();
token = NextTok();
alt.m_Type = parseType( token );
token = NextTok();
alt.m_Shape = parseShape( token );
pin->GetAlternates()[ alt.m_Name ] = alt;
NeedRIGHT();
break;
}
default:
Expecting( "at, name, number, length, or alternate" );
}
}
return pin.release();
}
LIB_SHAPE* SCH_SEXPR_PARSER::parsePolyLine()
{
wxCHECK_MSG( CurTok() == T_polyline, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a poly." ) );
T token;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
std::unique_ptr<LIB_SHAPE> poly = std::make_unique<LIB_SHAPE>( nullptr, SHAPE_T::POLY );
poly->SetUnit( m_unit );
poly->SetConvert( m_convert );
token = NextTok();
if( token == T_private )
{
poly->SetPrivate( true );
token = NextTok();
}
for( ; token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_pts:
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
if( token != T_xy )
Expecting( "xy" );
poly->AddPoint( parseXY() );
NeedRIGHT();
}
break;
case T_stroke:
parseStroke( stroke );
poly->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
poly->SetFillMode( fill.m_FillType );
poly->SetFillColor( fill.m_Color );
break;
default:
Expecting( "pts, stroke, or fill" );
}
}
return poly.release();
}
LIB_SHAPE* SCH_SEXPR_PARSER::parseRectangle()
{
wxCHECK_MSG( CurTok() == T_rectangle, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a rectangle." ) );
T token;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
std::unique_ptr<LIB_SHAPE> rectangle = std::make_unique<LIB_SHAPE>( nullptr, SHAPE_T::RECT );
rectangle->SetUnit( m_unit );
rectangle->SetConvert( m_convert );
token = NextTok();
if( token == T_private )
{
rectangle->SetPrivate( true );
token = NextTok();
}
for( ; token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_start:
rectangle->SetPosition( parseXY() );
NeedRIGHT();
break;
case T_end:
rectangle->SetEnd( parseXY() );
NeedRIGHT();
break;
case T_stroke:
parseStroke( stroke );
rectangle->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
rectangle->SetFillMode( fill.m_FillType );
rectangle->SetFillColor( fill.m_Color );
break;
default:
Expecting( "start, end, stroke, or fill" );
}
}
return rectangle.release();
}
LIB_TEXT* SCH_SEXPR_PARSER::parseText()
{
wxCHECK_MSG( CurTok() == T_text, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a text token." ) );
T token;
std::unique_ptr<LIB_TEXT> text = std::make_unique<LIB_TEXT>( nullptr );
text->SetUnit( m_unit );
text->SetConvert( m_convert );
token = NextTok();
if( token == T_private )
{
text->SetPrivate( true );
token = NextTok();
}
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid text string" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
text->SetText( FromUTF8() );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_at:
text->SetPosition( parseXY() );
// Yes, LIB_TEXT is really decidegrees even though all the others are degrees. :(
text->SetTextAngle( EDA_ANGLE( parseDouble( "text angle" ), TENTHS_OF_A_DEGREE_T ) );
NeedRIGHT();
break;
case T_effects:
parseEDA_TEXT( static_cast<EDA_TEXT*>( text.get() ), true );
break;
default:
Expecting( "at or effects" );
}
}
return text.release();
}
LIB_TEXTBOX* SCH_SEXPR_PARSER::parseTextBox()
{
wxCHECK_MSG( CurTok() == T_text_box, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a text box." ) );
T token;
VECTOR2I pos;
VECTOR2I end;
VECTOR2I size;
bool foundEnd = false;
bool foundSize = false;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
std::unique_ptr<LIB_TEXTBOX> textBox = std::make_unique<LIB_TEXTBOX>( nullptr );
token = NextTok();
if( token == T_private )
{
textBox->SetPrivate( true );
token = NextTok();
}
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid text string" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
textBox->SetText( FromUTF8() );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_start: // Legacy token during 6.99 development; fails to handle angle
pos = parseXY();
NeedRIGHT();
break;
case T_end: // Legacy token during 6.99 development; fails to handle angle
end = parseXY();
foundEnd = true;
NeedRIGHT();
break;
case T_at:
pos = parseXY();
textBox->SetTextAngle( EDA_ANGLE( parseDouble( "textbox angle" ), DEGREES_T ) );
NeedRIGHT();
break;
case T_size:
size = parseXY();
foundSize = true;
NeedRIGHT();
break;
case T_stroke:
parseStroke( stroke );
textBox->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
textBox->SetFillMode( fill.m_FillType );
textBox->SetFillColor( fill.m_Color );
break;
case T_effects:
parseEDA_TEXT( static_cast<EDA_TEXT*>( textBox.get() ), false );
break;
default:
Expecting( "at, size, stroke, fill or effects" );
}
}
textBox->SetPosition( pos );
if( foundEnd )
textBox->SetEnd( end );
else if( foundSize )
textBox->SetEnd( pos + size );
else
Expecting( "size" );
return textBox.release();
}
void SCH_SEXPR_PARSER::parsePAGE_INFO( PAGE_INFO& aPageInfo )
{
wxCHECK_RET( ( CurTok() == T_page && m_requiredVersion <= 20200506 ) || CurTok() == T_paper,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a PAGE_INFO." ) );
T token;
NeedSYMBOL();
wxString pageType = FromUTF8();
if( !aPageInfo.SetType( pageType ) )
{
THROW_PARSE_ERROR( _( "Invalid page type" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
if( pageType == PAGE_INFO::Custom )
{
int width = EDA_UNIT_UTILS::Mm2mils( parseDouble( "width" ) ); // width stored in mm so we convert to mils
// Perform some controls to avoid crashes if the size is edited by hands
if( width < MIN_PAGE_SIZE_MILS )
width = MIN_PAGE_SIZE_MILS;
else if( width > MAX_PAGE_SIZE_EESCHEMA_MILS )
width = MAX_PAGE_SIZE_EESCHEMA_MILS;
int height = EDA_UNIT_UTILS::Mm2mils( parseDouble( "height" ) ); // height stored in mm so we convert to mils
if( height < MIN_PAGE_SIZE_MILS )
height = MIN_PAGE_SIZE_MILS;
else if( height > MAX_PAGE_SIZE_EESCHEMA_MILS )
height = MAX_PAGE_SIZE_EESCHEMA_MILS;
aPageInfo.SetWidthMils( width );
aPageInfo.SetHeightMils( height );
}
token = NextTok();
if( token == T_portrait )
{
aPageInfo.SetPortrait( true );
NeedRIGHT();
}
else if( token != T_RIGHT )
{
Expecting( "portrait" );
}
}
void SCH_SEXPR_PARSER::parseTITLE_BLOCK( TITLE_BLOCK& aTitleBlock )
{
wxCHECK_RET( CurTok() == T_title_block,
"Cannot parse " + GetTokenString( CurTok() ) + " as a TITLE_BLOCK." );
T token;
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_title:
NextTok();
aTitleBlock.SetTitle( FromUTF8() );
break;
case T_date:
NextTok();
aTitleBlock.SetDate( FromUTF8() );
break;
case T_rev:
NextTok();
aTitleBlock.SetRevision( FromUTF8() );
break;
case T_company:
NextTok();
aTitleBlock.SetCompany( FromUTF8() );
break;
case T_comment:
{
int commentNumber = parseInt( "comment" );
switch( commentNumber )
{
case 1:
NextTok();
aTitleBlock.SetComment( 0, FromUTF8() );
break;
case 2:
NextTok();
aTitleBlock.SetComment( 1, FromUTF8() );
break;
case 3:
NextTok();
aTitleBlock.SetComment( 2, FromUTF8() );
break;
case 4:
NextTok();
aTitleBlock.SetComment( 3, FromUTF8() );
break;
case 5:
NextTok();
aTitleBlock.SetComment( 4, FromUTF8() );
break;
case 6:
NextTok();
aTitleBlock.SetComment( 5, FromUTF8() );
break;
case 7:
NextTok();
aTitleBlock.SetComment( 6, FromUTF8() );
break;
case 8:
NextTok();
aTitleBlock.SetComment( 7, FromUTF8() );
break;
case 9:
NextTok();
aTitleBlock.SetComment( 8, FromUTF8() );
break;
default:
THROW_PARSE_ERROR( _( "Invalid title block comment number" ), CurSource(),
CurLine(), CurLineNumber(), CurOffset() );
}
break;
}
default:
Expecting( "title, date, rev, company, or comment" );
}
NeedRIGHT();
}
}
SCH_FIELD* SCH_SEXPR_PARSER::parseSchField( SCH_ITEM* aParent )
{
wxCHECK_MSG( CurTok() == T_property, nullptr,
"Cannot parse " + GetTokenString( CurTok() ) + " as a property token." );
T token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid property name" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
wxString name = FromUTF8();
if( name.IsEmpty() )
{
THROW_PARSE_ERROR( _( "Empty property name" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid property value" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
// Empty property values are valid.
wxString value = FromUTF8();
int mandatoryFieldCount = 0;
if( aParent->Type() == SCH_SYMBOL_T )
mandatoryFieldCount = MANDATORY_FIELDS;
else if( aParent->Type() == SCH_SHEET_T )
mandatoryFieldCount = SHEET_MANDATORY_FIELDS;
std::unique_ptr<SCH_FIELD> field =
std::make_unique<SCH_FIELD>( VECTOR2I( -1, -1 ), mandatoryFieldCount, aParent, name );
field->SetText( value );
field->SetVisible( true );
// Correctly set the ID based on canonical (untranslated) field name
// If ID is stored in the file (old versions), it will overwrite this
if( aParent->Type() == SCH_SYMBOL_T )
{
for( int ii = 0; ii < MANDATORY_FIELDS; ++ii )
{
if( name == TEMPLATE_FIELDNAME::GetDefaultFieldName( ii, false ) )
{
field->SetId( ii );
break;
}
}
}
else if( aParent->Type() == SCH_SHEET_T )
{
for( int ii = 0; ii < SHEET_MANDATORY_FIELDS; ++ii )
{
if( name == SCH_SHEET::GetDefaultFieldName( ii, false ) )
{
field->SetId( ii );
break;
}
// Legacy support for old field names
else if( !name.CmpNoCase( wxT( "Sheet name" ) ) )
{
field->SetId( SHEETNAME );
break;
}
else if( !name.CmpNoCase( wxT( "Sheet file" ) ) )
{
field->SetId( SHEETFILENAME );
break;
}
}
}
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
// I am not sure we should even support parsing these IDs any more
case T_id:
{
int id = parseInt( "field ID" );
// Only set an ID that isn't a MANDATORY_FIELDS ID
if( id >= mandatoryFieldCount )
field->SetId( id );
NeedRIGHT();
}
break;
case T_at:
field->SetPosition( parseXY() );
field->SetTextAngle( EDA_ANGLE( parseDouble( "text angle" ), DEGREES_T ) );
NeedRIGHT();
break;
case T_effects:
parseEDA_TEXT( static_cast<EDA_TEXT*>( field.get() ), field->GetId() == VALUE_FIELD );
break;
case T_show_name:
field->SetNameShown();
NeedRIGHT();
break;
case T_do_not_autoplace:
field->SetCanAutoplace( false );
NeedRIGHT();
break;
default:
Expecting( "id, at, show_name, do_not_autoplace or effects" );
}
}
return field.release();
}
SCH_SHEET_PIN* SCH_SEXPR_PARSER::parseSchSheetPin( SCH_SHEET* aSheet )
{
wxCHECK_MSG( aSheet != nullptr, nullptr, "" );
wxCHECK_MSG( CurTok() == T_pin, nullptr,
"Cannot parse " + GetTokenString( CurTok() ) + " as a sheet pin token." );
T token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid sheet pin name" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
wxString name = FromUTF8();
if( name.IsEmpty() )
{
THROW_PARSE_ERROR( _( "Empty sheet pin name" ), CurSource(), CurLine(), CurLineNumber(),
CurOffset() );
}
auto sheetPin = std::make_unique<SCH_SHEET_PIN>( aSheet, VECTOR2I( 0, 0 ), name );
token = NextTok();
switch( token )
{
case T_input: sheetPin->SetShape( LABEL_FLAG_SHAPE::L_INPUT ); break;
case T_output: sheetPin->SetShape( LABEL_FLAG_SHAPE::L_OUTPUT ); break;
case T_bidirectional: sheetPin->SetShape( LABEL_FLAG_SHAPE::L_BIDI ); break;
case T_tri_state: sheetPin->SetShape( LABEL_FLAG_SHAPE::L_TRISTATE ); break;
case T_passive: sheetPin->SetShape( LABEL_FLAG_SHAPE::L_UNSPECIFIED ); break;
default:
Expecting( "input, output, bidirectional, tri_state, or passive" );
}
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_at:
{
sheetPin->SetPosition( parseXY() );
double angle = parseDouble( "sheet pin angle (side)" );
if( angle == 0.0 )
sheetPin->SetSide( SHEET_SIDE::RIGHT );
else if( angle == 90.0 )
sheetPin->SetSide( SHEET_SIDE::TOP );
else if( angle == 180.0 )
sheetPin->SetSide( SHEET_SIDE::LEFT );
else if( angle == 270.0 )
sheetPin->SetSide( SHEET_SIDE::BOTTOM );
else
Expecting( "0, 90, 180, or 270" );
NeedRIGHT();
break;
}
case T_effects:
parseEDA_TEXT( static_cast<EDA_TEXT*>( sheetPin.get() ), true );
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( sheetPin->m_Uuid ) = parseKIID();
NeedRIGHT();
break;
default:
Expecting( "at, uuid or effects" );
}
}
return sheetPin.release();
}
void SCH_SEXPR_PARSER::parseSchSheetInstances( SCH_SHEET* aRootSheet, SCH_SCREEN* aScreen )
{
wxCHECK_RET( CurTok() == T_sheet_instances,
"Cannot parse " + GetTokenString( CurTok() ) + " as an instances token." );
wxCHECK( aScreen, /* void */ );
T token;
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_path:
{
NeedSYMBOL();
SCH_SHEET_INSTANCE instance;
instance.m_Path = KIID_PATH( FromUTF8() );
if( ( !m_appending && aRootSheet->GetScreen() == aScreen ) &&
( aScreen->GetFileFormatVersionAtLoad() < 20221002 ) )
instance.m_Path.insert( instance.m_Path.begin(), m_rootUuid );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
std::vector<wxString> whitespaces = { wxT( "\r" ), wxT( "\n" ), wxT( "\t" ),
wxT( " " ) };
size_t numReplacements = 0;
switch( token )
{
case T_page:
NeedSYMBOL();
instance.m_PageNumber = FromUTF8();
// Empty page numbers are not permitted
if( instance.m_PageNumber.IsEmpty() )
{
// Use hash character instead
instance.m_PageNumber = wxT( "#" );
numReplacements++;
}
else
{
// Whitespaces are not permitted
for( wxString ch : whitespaces )
numReplacements += instance.m_PageNumber.Replace( ch, wxEmptyString );
}
// Set the file as modified so the user can be warned.
if( numReplacements > 0 )
aScreen->SetContentModified();
NeedRIGHT();
break;
default:
Expecting( "path or page" );
}
}
if( ( aScreen->GetFileFormatVersionAtLoad() >= 20221110 )
&& ( instance.m_Path.empty() ) )
{
SCH_SHEET_PATH rootSheetPath;
rootSheetPath.push_back( aRootSheet );
rootSheetPath.SetPageNumber( instance.m_PageNumber );
}
else
{
aScreen->m_sheetInstances.emplace_back( instance );
}
break;
}
default:
Expecting( "path" );
}
}
}
void SCH_SEXPR_PARSER::parseSchSymbolInstances( SCH_SCREEN* aScreen )
{
wxCHECK_RET( CurTok() == T_symbol_instances,
"Cannot parse " + GetTokenString( CurTok() ) + " as an instances token." );
wxCHECK( aScreen, /* void */ );
wxCHECK( m_rootUuid != NilUuid(), /* void */ );
T token;
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_path:
{
NeedSYMBOL();
SCH_SYMBOL_INSTANCE instance;
instance.m_Path = KIID_PATH( FromUTF8() );
if( !m_appending )
instance.m_Path.insert( instance.m_Path.begin(), m_rootUuid );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_reference:
NeedSYMBOL();
instance.m_Reference = FromUTF8();
NeedRIGHT();
break;
case T_unit:
instance.m_Unit = parseInt( "symbol unit" );
NeedRIGHT();
break;
case T_value:
NeedSYMBOL();
instance.m_Value = FromUTF8();
NeedRIGHT();
break;
case T_footprint:
NeedSYMBOL();
instance.m_Footprint = FromUTF8();
NeedRIGHT();
break;
default:
Expecting( "path, unit, value or footprint" );
}
}
aScreen->m_symbolInstances.emplace_back( instance );
break;
}
default:
Expecting( "path" );
}
}
}
void SCH_SEXPR_PARSER::ParseSchematic( SCH_SHEET* aSheet, bool aIsCopyableOnly, int aFileVersion )
{
wxCHECK( aSheet != nullptr, /* void */ );
SCH_SCREEN* screen = aSheet->GetScreen();
wxCHECK( screen != nullptr, /* void */ );
if( aIsCopyableOnly )
m_requiredVersion = aFileVersion;
bool fileHasUuid = false;
T token;
if( !aIsCopyableOnly )
{
NeedLEFT();
NextTok();
if( CurTok() != T_kicad_sch )
Expecting( "kicad_sch" );
parseHeader( T_kicad_sch, SEXPR_SCHEMATIC_FILE_VERSION );
// Prior to schematic file version 20210406, schematics did not have UUIDs so we need
// to generate one for the root schematic for instance paths.
if( m_requiredVersion < 20210406 )
m_rootUuid = screen->GetUuid();
}
screen->SetFileFormatVersionAtLoad( m_requiredVersion );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( aIsCopyableOnly && token == T_EOF )
break;
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
checkpoint();
if( !aIsCopyableOnly && token == T_page && m_requiredVersion <= 20200506 )
token = T_paper;
switch( token )
{
case T_uuid:
NeedSYMBOL();
screen->m_uuid = parseKIID();
// Set the root sheet UUID with the schematic file UUID. Root sheets are virtual
// and always get a new UUID so this prevents file churn now that the root UUID
// is saved in the symbol instance path.
if( aSheet == m_rootSheet )
{
const_cast<KIID&>( aSheet->m_Uuid ) = screen->GetUuid();
m_rootUuid = screen->GetUuid();
fileHasUuid = true;
}
NeedRIGHT();
break;
case T_paper:
{
if( aIsCopyableOnly )
Unexpected( T_paper );
PAGE_INFO pageInfo;
parsePAGE_INFO( pageInfo );
screen->SetPageSettings( pageInfo );
break;
}
case T_page:
{
if( aIsCopyableOnly )
Unexpected( T_page );
// Only saved for top-level sniffing in Kicad Manager frame and other external
// tool usage with flat hierarchies
NeedSYMBOLorNUMBER();
NeedSYMBOLorNUMBER();
NeedRIGHT();
break;
}
case T_title_block:
{
if( aIsCopyableOnly )
Unexpected( T_title_block );
TITLE_BLOCK tb;
parseTITLE_BLOCK( tb );
screen->SetTitleBlock( tb );
break;
}
case T_lib_symbols:
{
// Dummy map. No derived symbols are allowed in the library cache.
LIB_SYMBOL_MAP symbolLibMap;
LIB_SYMBOL* symbol;
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_symbol:
symbol = parseLibSymbol( symbolLibMap );
symbol->UpdateFieldOrdinals();
screen->AddLibSymbol( symbol );
break;
default:
Expecting( "symbol" );
}
}
break;
}
case T_symbol:
screen->Append( static_cast<SCH_ITEM*>( parseSchematicSymbol() ) );
break;
case T_image:
screen->Append( static_cast<SCH_ITEM*>( parseImage() ) );
break;
case T_sheet:
{
SCH_SHEET* sheet = parseSheet();
// Set the parent to aSheet. This effectively creates a method to find
// the root sheet from any sheet so a pointer to the root sheet does not
// need to be stored globally. Note: this is not the same as a hierarchy.
// Complex hierarchies can have multiple copies of a sheet. This only
// provides a simple tree to find the root sheet.
sheet->SetParent( aSheet );
screen->Append( sheet );
break;
}
case T_junction:
screen->Append( parseJunction() );
break;
case T_no_connect:
screen->Append( parseNoConnect() );
break;
case T_bus_entry:
screen->Append( parseBusEntry() );
break;
case T_polyline:
{
// polyline keyword is used in eeschema both for SCH_SHAPE and SCH_LINE items.
// In symbols it describes a polygon, having n corners and can be filled
// In schematic it describes a line (with no fill descr), but could be extended to a
// polygon (for instance when importing files) because the schematic handles all
// SCH_SHAPE.
// parseSchPolyLine() returns always a SCH_SHAPE, io convert it to a simple SCH_LINE
// For compatibility reasons, keep SCH_SHAPE for a polygon and convert to SCH_LINE
// when the item has only 2 corners, similar to a SCH_LINE
SCH_SHAPE* poly = parseSchPolyLine();
if( poly->GetPointCount() > 2 )
{
screen->Append( poly );
}
else
{
// For SCH_SHAPE having only 2 points, this is a "old" SCH_LINE entity.
// So convert the SCH_SHAPE to a simple SCH_LINE
SCH_LINE* line = new SCH_LINE( VECTOR2I(), LAYER_NOTES );
SHAPE_LINE_CHAIN& outline = poly->GetPolyShape().Outline(0);
line->SetStartPoint( outline.CPoint(0) );
line->SetEndPoint( outline.CPoint(1) );
line->SetStroke( poly->GetStroke() );
const_cast<KIID&>( line->m_Uuid ) = poly->m_Uuid;
screen->Append( line );
delete poly;
}
}
break;
case T_bus:
case T_wire:
screen->Append( parseLine() );
break;
case T_arc:
screen->Append( parseSchArc() );
break;
case T_circle:
screen->Append( parseSchCircle() );
break;
case T_rectangle:
screen->Append( parseSchRectangle() );
break;
case T_bezier:
screen->Append( parseSchBezier() );
break;
case T_netclass_flag: // present only during early development of 7.0
KI_FALLTHROUGH;
case T_text:
case T_label:
case T_global_label:
case T_hierarchical_label:
case T_directive_label:
screen->Append( parseSchText() );
break;
case T_text_box:
screen->Append( parseSchTextBox() );
break;
case T_sheet_instances:
parseSchSheetInstances( aSheet, screen );
break;
case T_symbol_instances:
parseSchSymbolInstances( screen );
break;
case T_bus_alias:
if( aIsCopyableOnly )
Unexpected( T_bus_alias );
parseBusAlias( screen );
break;
default:
Expecting( "symbol, paper, page, title_block, bitmap, sheet, junction, no_connect, "
"bus_entry, line, bus, text, label, class_label, global_label, "
"hierarchical_label, symbol_instances, or bus_alias" );
}
}
// Older s-expression schematics may not have a UUID so use the one automatically generated
// as the virtual root sheet UUID.
if( ( aSheet == m_rootSheet ) && !fileHasUuid )
{
const_cast<KIID&>( aSheet->m_Uuid ) = screen->GetUuid();
m_rootUuid = screen->GetUuid();
}
screen->UpdateLocalLibSymbolLinks();
if( m_requiredVersion < 20200828 )
screen->SetLegacySymbolInstanceData();
}
SCH_SYMBOL* SCH_SEXPR_PARSER::parseSchematicSymbol()
{
wxCHECK_MSG( CurTok() == T_symbol, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a symbol." ) );
T token;
wxString libName;
SCH_FIELD* field;
std::unique_ptr<SCH_SYMBOL> symbol = std::make_unique<SCH_SYMBOL>();
TRANSFORM transform;
std::set<int> fieldIDsRead;
// We'll reset this if we find a fields_autoplaced token
symbol->ClearFieldsAutoplaced();
m_fieldIDsRead.clear();
// Make sure the mandatory field IDs are reserved as already read,
// the field parser will set the field IDs to the correct value if
// the field name matches a mandatory field name
for( int i = 0; i < MANDATORY_FIELDS; i++ )
m_fieldIDsRead.insert( i );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_lib_name:
{
LIB_ID libId;
token = NextTok();
if( !IsSymbol( token ) )
{
THROW_PARSE_ERROR( _( "Invalid symbol library name" ), CurSource(), CurLine(),
CurLineNumber(), CurOffset() );
}
libName = FromUTF8();
NeedRIGHT();
break;
}
case T_lib_id:
{
token = NextTok();
if( !IsSymbol( token ) && token != T_NUMBER )
Expecting( "symbol|number" );
LIB_ID libId;
wxString name = FromUTF8();
int bad_pos = libId.Parse( name );
if( bad_pos >= 0 )
{
if( static_cast<int>( name.size() ) > bad_pos )
{
wxString msg = wxString::Format(
_( "Symbol %s contains invalid character '%c'" ), name,
name[bad_pos] );
THROW_PARSE_ERROR( msg, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
}
THROW_PARSE_ERROR( _( "Invalid symbol library ID" ), CurSource(), CurLine(),
CurLineNumber(), CurOffset() );
}
symbol->SetLibId( libId );
NeedRIGHT();
break;
}
case T_at:
symbol->SetPosition( parseXY() );
switch( static_cast<int>( parseDouble( "symbol orientation" ) ) )
{
case 0: transform = TRANSFORM(); break;
case 90: transform = TRANSFORM( 0, -1, -1, 0 ); break;
case 180: transform = TRANSFORM( -1, 0, 0, 1 ); break;
case 270: transform = TRANSFORM( 0, 1, 1, 0 ); break;
default: Expecting( "0, 90, 180, or 270" );
}
symbol->SetTransform( transform );
NeedRIGHT();
break;
case T_mirror:
token = NextTok();
if( token == T_x )
symbol->SetOrientation( SYM_MIRROR_X );
else if( token == T_y )
symbol->SetOrientation( SYM_MIRROR_Y );
else
Expecting( "x or y" );
NeedRIGHT();
break;
case T_unit:
symbol->SetUnit( parseInt( "symbol unit" ) );
NeedRIGHT();
break;
case T_convert:
symbol->SetConvert( parseInt( "symbol convert" ) );
NeedRIGHT();
break;
case T_in_bom:
symbol->SetExcludedFromBOM( !parseBool() );
NeedRIGHT();
break;
case T_on_board:
symbol->SetExcludedFromBoard( !parseBool() );
NeedRIGHT();
break;
case T_dnp:
symbol->SetDNP( parseBool() );
NeedRIGHT();
break;
case T_fields_autoplaced:
symbol->SetFieldsAutoplaced();
NeedRIGHT();
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( symbol->m_Uuid ) = parseKIID();
NeedRIGHT();
break;
case T_default_instance:
{
SCH_SYMBOL_INSTANCE defaultInstance;
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_reference:
NeedSYMBOL();
defaultInstance.m_Reference = FromUTF8();
NeedRIGHT();
break;
case T_unit:
defaultInstance.m_Unit = parseInt( "symbol unit" );
NeedRIGHT();
break;
case T_value:
NeedSYMBOL();
symbol->SetValueFieldText( FromUTF8() );
NeedRIGHT();
break;
case T_footprint:
NeedSYMBOL();
symbol->SetFootprintFieldText( FromUTF8() );
NeedRIGHT();
break;
default:
Expecting( "reference, unit, value or footprint" );
}
}
break;
}
case T_instances:
{
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
if( token != T_project )
Expecting( "project" );
NeedSYMBOL();
wxString projectName = FromUTF8();
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
if( token != T_path )
Expecting( "path" );
SCH_SYMBOL_INSTANCE instance;
instance.m_ProjectName = projectName;
NeedSYMBOL();
instance.m_Path = KIID_PATH( FromUTF8() );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_reference:
NeedSYMBOL();
instance.m_Reference = FromUTF8();
NeedRIGHT();
break;
case T_unit:
instance.m_Unit = parseInt( "symbol unit" );
NeedRIGHT();
break;
case T_value:
NeedSYMBOL();
symbol->SetValueFieldText( FromUTF8() );
NeedRIGHT();
break;
case T_footprint:
NeedSYMBOL();
symbol->SetFootprintFieldText( FromUTF8() );
NeedRIGHT();
break;
default:
Expecting( "reference, unit, value or footprint" );
}
symbol->AddHierarchicalReference( instance );
}
}
}
break;
}
case T_property:
// The field parent symbol must be set and its orientation must be set before
// the field positions are set.
field = parseSchField( symbol.get() );
if( ( field->GetId() >= MANDATORY_FIELDS ) && m_fieldIDsRead.count( field->GetId() ) )
{
int nextAvailableId = field->GetId() + 1;
while( m_fieldIDsRead.count( nextAvailableId ) )
nextAvailableId += 1;
field->SetId( nextAvailableId );
}
if( symbol->GetFieldById( field->GetId() ) )
*symbol->GetFieldById( field->GetId() ) = *field;
else
symbol->AddField( *field );
if( field->GetId() == REFERENCE_FIELD )
symbol->UpdatePrefix();
m_fieldIDsRead.insert( field->GetId() );
delete field;
break;
case T_pin:
{
// Read an alternate pin designation
wxString number;
KIID uuid;
wxString alt;
NeedSYMBOL();
number = FromUTF8();
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_alternate:
NeedSYMBOL();
alt = FromUTF8();
NeedRIGHT();
break;
case T_uuid:
NeedSYMBOL();
// First version to write out pin uuids accidentally wrote out the symbol's
// uuid for each pin, so ignore uuids coming from that version.
if( m_requiredVersion >= 20210126 )
uuid = parseKIID();
NeedRIGHT();
break;
default:
Expecting( "alternate or uuid" );
}
}
symbol->GetRawPins().emplace_back( std::make_unique<SCH_PIN>( symbol.get(),
number, alt ) );
const_cast<KIID&>( symbol->GetRawPins().back()->m_Uuid ) = uuid;
}
break;
default:
Expecting( "lib_id, lib_name, at, mirror, uuid, on_board, in_bom, dnp, "
"default_instance, property, pin, or instances" );
}
}
if( !libName.IsEmpty() && ( symbol->GetLibId().Format().wx_str() != libName ) )
symbol->SetSchSymbolLibraryName( libName );
// Ensure edit/status flags are cleared after these initializations:
symbol->ClearFlags();
return symbol.release();
}
SCH_BITMAP* SCH_SEXPR_PARSER::parseImage()
{
wxCHECK_MSG( CurTok() == T_image, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as an image." ) );
T token;
std::unique_ptr<SCH_BITMAP> bitmap = std::make_unique<SCH_BITMAP>();
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_at:
bitmap->SetPosition( parseXY() );
NeedRIGHT();
break;
case T_scale:
bitmap->GetImage()->SetScale( parseDouble( "image scale factor" ) );
if( !std::isnormal( bitmap->GetImage()->GetScale() ) )
bitmap->GetImage()->SetScale( 1.0 );
NeedRIGHT();
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( bitmap->m_Uuid ) = parseKIID();
NeedRIGHT();
break;
case T_data:
{
token = NextTok();
wxString data;
// Reserve 128K because most image files are going to be larger than the default
// 1K that wxString reserves.
data.reserve( 1 << 17 );
while( token != T_RIGHT )
{
if( !IsSymbol( token ) )
Expecting( "base64 image data" );
data += FromUTF8();
token = NextTok();
}
wxMemoryBuffer buffer = wxBase64Decode( data );
wxMemoryOutputStream stream( buffer.GetData(), buffer.GetBufSize() );
wxImage* image = new wxImage();
wxMemoryInputStream istream( stream );
image->LoadFile( istream, wxBITMAP_TYPE_PNG );
bitmap->SetImage( image );
break;
}
default:
Expecting( "at, scale, uuid or data" );
}
}
// 20230121 or older file format versions assumed 300 image PPI at load/save.
// Let's keep compatibility by changing image scale.
if( m_requiredVersion <= 20230121 )
{
BITMAP_BASE* image = bitmap->GetImage();
image->SetScale( image->GetScale() * image->GetPPI() / 300.0 );
}
return bitmap.release();
}
SCH_SHEET* SCH_SEXPR_PARSER::parseSheet()
{
wxCHECK_MSG( CurTok() == T_sheet, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a sheet." ) );
T token;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
SCH_FIELD* field;
std::vector<SCH_FIELD> fields;
std::unique_ptr<SCH_SHEET> sheet = std::make_unique<SCH_SHEET>();
std::set<int> fieldIDsRead;
// We'll reset this if we find a fields_autoplaced token
sheet->ClearFieldsAutoplaced();
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_at:
sheet->SetPosition( parseXY() );
NeedRIGHT();
break;
case T_size:
{
VECTOR2I size;
size.x = parseInternalUnits( "sheet width" );
size.y = parseInternalUnits( "sheet height" );
sheet->SetSize( size );
NeedRIGHT();
break;
}
case T_fields_autoplaced:
sheet->SetFieldsAutoplaced();
NeedRIGHT();
break;
case T_stroke:
parseStroke( stroke );
sheet->SetBorderWidth( stroke.GetWidth() );
sheet->SetBorderColor( stroke.GetColor() );
break;
case T_fill:
parseFill( fill );
sheet->SetBackgroundColor( fill.m_Color );
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( sheet->m_Uuid ) = parseKIID();
NeedRIGHT();
break;
case T_property:
field = parseSchField( sheet.get() );
if( m_requiredVersion <= 20200310 )
{
// Earlier versions had the wrong ids (and names) saved for sheet fields.
// Fortunately they only saved the sheetname and sheetfilepath (and always
// in that order), so we can hack in a recovery.
if( fields.empty() )
field->SetId( SHEETNAME );
else
field->SetId( SHEETFILENAME );
}
// It would appear the problem persists past 20200310, but this time with the
// earlier ids being re-used for later (user) fields. The easiest (and most
// complete) solution is to disallow multiple instances of the same id (for all
// files since the source of the error *might* in fact be hand-edited files).
//
// While no longer used, -1 is still a valid id for user field. We convert it to
// the first available ID after the mandatory fields
if( field->GetId() < 0 )
field->SetId( SHEET_MANDATORY_FIELDS );
while( !fieldIDsRead.insert( field->GetId() ).second )
field->SetId( field->GetId() + 1 );
fields.emplace_back( *field );
delete field;
break;
case T_pin:
sheet->AddPin( parseSchSheetPin( sheet.get() ) );
break;
case T_instances:
{
std::vector<SCH_SHEET_INSTANCE> instances;
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
if( token != T_project )
Expecting( "project" );
NeedSYMBOL();
wxString projectName = FromUTF8();
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
if( token != T_path )
Expecting( "path" );
SCH_SHEET_INSTANCE instance;
instance.m_ProjectName = projectName;
NeedSYMBOL();
instance.m_Path = KIID_PATH( FromUTF8() );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_page:
{
NeedSYMBOL();
instance.m_PageNumber = FromUTF8();
// Empty page numbers are not permitted
if( instance.m_PageNumber.IsEmpty() )
{
// Use hash character instead
instance.m_PageNumber = wxT( "#" );
}
else
{
// Whitespaces are not permitted
std::vector<wxString> whitespaces = { wxT( "\r" ), wxT( "\n" ),
wxT( "\t" ), wxT( " " ) };
for( wxString ch : whitespaces )
instance.m_PageNumber.Replace( ch, wxEmptyString );
}
NeedRIGHT();
break;
}
default:
Expecting( "page" );
}
}
instances.emplace_back( instance );
}
}
sheet->setInstances( instances );
break;
}
default:
Expecting( "at, size, stroke, background, instances, uuid, property, or pin" );
}
}
sheet->SetFields( fields );
return sheet.release();
}
SCH_JUNCTION* SCH_SEXPR_PARSER::parseJunction()
{
wxCHECK_MSG( CurTok() == T_junction, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a junction." ) );
T token;
std::unique_ptr<SCH_JUNCTION> junction = std::make_unique<SCH_JUNCTION>();
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_at:
junction->SetPosition( parseXY() );
NeedRIGHT();
break;
case T_diameter:
junction->SetDiameter( parseInternalUnits( "junction diameter" ) );
NeedRIGHT();
break;
case T_color:
{
COLOR4D color;
color.r = parseInt( "red" ) / 255.0;
color.g = parseInt( "green" ) / 255.0;
color.b = parseInt( "blue" ) / 255.0;
color.a = Clamp( parseDouble( "alpha" ), 0.0, 1.0 );
junction->SetColor( color );
NeedRIGHT();
break;
}
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( junction->m_Uuid ) = parseKIID();
NeedRIGHT();
break;
default:
Expecting( "at, diameter, color or uuid" );
}
}
return junction.release();
}
SCH_NO_CONNECT* SCH_SEXPR_PARSER::parseNoConnect()
{
wxCHECK_MSG( CurTok() == T_no_connect, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a no connect." ) );
T token;
std::unique_ptr<SCH_NO_CONNECT> no_connect = std::make_unique<SCH_NO_CONNECT>();
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_at:
no_connect->SetPosition( parseXY() );
NeedRIGHT();
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( no_connect->m_Uuid ) = parseKIID();
NeedRIGHT();
break;
default:
Expecting( "at or uuid" );
}
}
return no_connect.release();
}
SCH_BUS_WIRE_ENTRY* SCH_SEXPR_PARSER::parseBusEntry()
{
wxCHECK_MSG( CurTok() == T_bus_entry, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a bus entry." ) );
T token;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
std::unique_ptr<SCH_BUS_WIRE_ENTRY> busEntry = std::make_unique<SCH_BUS_WIRE_ENTRY>();
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_at:
busEntry->SetPosition( parseXY() );
NeedRIGHT();
break;
case T_size:
{
VECTOR2I size;
size.x = parseInternalUnits( "bus entry height" );
size.y = parseInternalUnits( "bus entry width" );
busEntry->SetSize( size );
NeedRIGHT();
break;
}
case T_stroke:
parseStroke( stroke );
busEntry->SetStroke( stroke );
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( busEntry->m_Uuid ) = parseKIID();
NeedRIGHT();
break;
default:
Expecting( "at, size, uuid or stroke" );
}
}
return busEntry.release();
}
SCH_SHAPE* SCH_SEXPR_PARSER::parseSchPolyLine()
{
T token;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
int layer = LAYER_NOTES;
std::unique_ptr<SCH_SHAPE> polyline = std::make_unique<SCH_SHAPE>( SHAPE_T::POLY, layer );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_pts:
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
if( token != T_xy )
Expecting( "xy" );
polyline->AddPoint( parseXY() );
NeedRIGHT();
}
break;
case T_stroke:
parseStroke( stroke );
polyline->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
polyline->SetFillMode( fill.m_FillType );
polyline->SetFillColor( fill.m_Color );
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( polyline->m_Uuid ) = parseKIID();
NeedRIGHT();
break;
default:
Expecting( "pts, uuid, stroke, or fill" );
}
}
return polyline.release();
}
SCH_LINE* SCH_SEXPR_PARSER::parseLine()
{
// Note: T_polyline is deprecated in this code: it is now handled by
// parseSchPolyLine() that can handle true polygons, and not only one segment.
T token;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
int layer;
switch( CurTok() )
{
case T_polyline: layer = LAYER_NOTES; break;
case T_wire: layer = LAYER_WIRE; break;
case T_bus: layer = LAYER_BUS; break;
default:
wxCHECK_MSG( false, nullptr, "Cannot parse " + GetTokenString( CurTok() ) + " as a line." );
}
std::unique_ptr<SCH_LINE> line = std::make_unique<SCH_LINE>( VECTOR2I(), layer );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_pts:
NeedLEFT();
token = NextTok();
if( token != T_xy )
Expecting( "xy" );
line->SetStartPoint( parseXY() );
NeedRIGHT();
NeedLEFT();
token = NextTok();
if( token != T_xy )
Expecting( "xy" );
line->SetEndPoint( parseXY() );
NeedRIGHT();
NeedRIGHT();
break;
case T_stroke:
parseStroke( stroke );
line->SetStroke( stroke );
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( line->m_Uuid ) = parseKIID();
NeedRIGHT();
break;
default:
Expecting( "at, uuid or stroke" );
}
}
return line.release();
}
SCH_SHAPE* SCH_SEXPR_PARSER::parseSchArc()
{
wxCHECK_MSG( CurTok() == T_arc, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as an arc." ) );
T token;
VECTOR2I startPoint;
VECTOR2I midPoint;
VECTOR2I endPoint;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
std::unique_ptr<SCH_SHAPE> arc = std::make_unique<SCH_SHAPE>( SHAPE_T::ARC );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_start:
startPoint = parseXY();
NeedRIGHT();
break;
case T_mid:
midPoint = parseXY();
NeedRIGHT();
break;
case T_end:
endPoint = parseXY();
NeedRIGHT();
break;
case T_stroke:
parseStroke( stroke );
arc->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
arc->SetFillMode( fill.m_FillType );
arc->SetFillColor( fill.m_Color );
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( arc->m_Uuid ) = KIID( FromUTF8() );
NeedRIGHT();
break;
default:
Expecting( "start, mid, end, stroke, fill or uuid" );
}
}
arc->SetArcGeometry( startPoint, midPoint, endPoint );
return arc.release();
}
SCH_SHAPE* SCH_SEXPR_PARSER::parseSchCircle()
{
wxCHECK_MSG( CurTok() == T_circle, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a circle." ) );
T token;
VECTOR2I center;
int radius = 0;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
std::unique_ptr<SCH_SHAPE> circle = std::make_unique<SCH_SHAPE>( SHAPE_T::CIRCLE );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_center:
center = parseXY();
NeedRIGHT();
break;
case T_radius:
radius = parseInternalUnits( "radius length" );
NeedRIGHT();
break;
case T_stroke:
parseStroke( stroke );
circle->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
circle->SetFillMode( fill.m_FillType );
circle->SetFillColor( fill.m_Color );
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( circle->m_Uuid ) = KIID( FromUTF8() );
NeedRIGHT();
break;
default:
Expecting( "center, radius, stroke, fill or uuid" );
}
}
circle->SetCenter( center );
circle->SetEnd( VECTOR2I( center.x + radius, center.y ) );
return circle.release();
}
SCH_SHAPE* SCH_SEXPR_PARSER::parseSchRectangle()
{
wxCHECK_MSG( CurTok() == T_rectangle, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a rectangle." ) );
T token;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
std::unique_ptr<SCH_SHAPE> rectangle = std::make_unique<SCH_SHAPE>( SHAPE_T::RECT );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_start:
rectangle->SetPosition( parseXY() );
NeedRIGHT();
break;
case T_end:
rectangle->SetEnd( parseXY() );
NeedRIGHT();
break;
case T_stroke:
parseStroke( stroke );
rectangle->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
rectangle->SetFillMode( fill.m_FillType );
rectangle->SetFillColor( fill.m_Color );
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( rectangle->m_Uuid ) = KIID( FromUTF8() );
NeedRIGHT();
break;
default:
Expecting( "start, end, stroke, fill or uuid" );
}
}
return rectangle.release();
}
SCH_SHAPE* SCH_SEXPR_PARSER::parseSchBezier()
{
wxCHECK_MSG( CurTok() == T_bezier, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a bezier." ) );
T token;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
std::unique_ptr<SCH_SHAPE> bezier = std::make_unique<SCH_SHAPE>( SHAPE_T::BEZIER );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_pts:
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
if( token != T_xy )
Expecting( "xy" );
bezier->AddPoint( parseXY() );
NeedRIGHT();
}
break;
case T_stroke:
parseStroke( stroke );
bezier->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
bezier->SetFillMode( fill.m_FillType );
bezier->SetFillColor( fill.m_Color );
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( bezier->m_Uuid ) = KIID( FromUTF8() );
NeedRIGHT();
break;
default:
Expecting( "pts, stroke, fill or uuid" );
}
}
return bezier.release();
}
SCH_TEXT* SCH_SEXPR_PARSER::parseSchText()
{
T token;
std::unique_ptr<SCH_TEXT> text;
switch( CurTok() )
{
case T_text: text = std::make_unique<SCH_TEXT>(); break;
case T_label: text = std::make_unique<SCH_LABEL>(); break;
case T_global_label: text = std::make_unique<SCH_GLOBALLABEL>(); break;
case T_hierarchical_label: text = std::make_unique<SCH_HIERLABEL>(); break;
case T_netclass_flag: text = std::make_unique<SCH_DIRECTIVE_LABEL>(); break;
case T_directive_label: text = std::make_unique<SCH_DIRECTIVE_LABEL>(); break;
default:
wxCHECK_MSG( false, nullptr, "Cannot parse " + GetTokenString( CurTok() ) + " as text." );
}
// We'll reset this if we find a fields_autoplaced token
text->ClearFieldsAutoplaced();
NeedSYMBOL();
text->SetText( FromUTF8() );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_exclude_from_sim:
text->SetExcludeFromSim( parseBool() );
NeedRIGHT();
break;
case T_at:
text->SetPosition( parseXY() );
switch( static_cast<int>( parseDouble( "text angle" ) ) )
{
case 0: text->SetTextSpinStyle( TEXT_SPIN_STYLE::RIGHT ); break;
case 90: text->SetTextSpinStyle( TEXT_SPIN_STYLE::UP ); break;
case 180: text->SetTextSpinStyle( TEXT_SPIN_STYLE::LEFT ); break;
case 270: text->SetTextSpinStyle( TEXT_SPIN_STYLE::BOTTOM ); break;
default:
wxFAIL;
text->SetTextSpinStyle( TEXT_SPIN_STYLE::RIGHT );
break;
}
NeedRIGHT();
break;
case T_shape:
{
if( text->Type() == SCH_TEXT_T || text->Type() == SCH_LABEL_T )
Unexpected( T_shape );
SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( text.get() );
token = NextTok();
switch( token )
{
case T_input: label->SetShape( LABEL_FLAG_SHAPE::L_INPUT ); break;
case T_output: label->SetShape( LABEL_FLAG_SHAPE::L_OUTPUT ); break;
case T_bidirectional: label->SetShape( LABEL_FLAG_SHAPE::L_BIDI ); break;
case T_tri_state: label->SetShape( LABEL_FLAG_SHAPE::L_TRISTATE ); break;
case T_passive: label->SetShape( LABEL_FLAG_SHAPE::L_UNSPECIFIED ); break;
case T_dot: label->SetShape( LABEL_FLAG_SHAPE::F_DOT ); break;
case T_round: label->SetShape( LABEL_FLAG_SHAPE::F_ROUND ); break;
case T_diamond: label->SetShape( LABEL_FLAG_SHAPE::F_DIAMOND ); break;
case T_rectangle: label->SetShape( LABEL_FLAG_SHAPE::F_RECTANGLE ); break;
default:
Expecting( "input, output, bidirectional, tri_state, passive, dot, round, diamond"
"or rectangle" );
}
NeedRIGHT();
break;
}
case T_length:
{
if( text->Type() != SCH_DIRECTIVE_LABEL_T )
Unexpected( T_length );
SCH_DIRECTIVE_LABEL* label = static_cast<SCH_DIRECTIVE_LABEL*>( text.get() );
label->SetPinLength( parseInternalUnits( "pin length" ) );
NeedRIGHT();
}
break;
case T_fields_autoplaced:
text->SetFieldsAutoplaced();
NeedRIGHT();
break;
case T_effects:
parseEDA_TEXT( static_cast<EDA_TEXT*>( text.get() ), true );
// Spin style is defined differently for graphical text (#SCH_TEXT) objects.
if( text->Type() == SCH_TEXT_T )
{
if( text->GetHorizJustify() == GR_TEXT_H_ALIGN_RIGHT
&& text->GetTextAngle().IsVertical() )
{
// The vertically aligned text angle is always 90 (labels use 270 for the
// down direction) combined with the text justification flags.
text->SetTextSpinStyle( TEXT_SPIN_STYLE::BOTTOM );
}
else if( text->GetHorizJustify() == GR_TEXT_H_ALIGN_RIGHT
&& text->GetTextAngle().IsHorizontal() )
{
// The horizontally aligned text angle is always 0 (labels use 180 for the
// left direction) combined with the text justification flags.
text->SetTextSpinStyle( TEXT_SPIN_STYLE::LEFT );
}
}
break;
case T_iref: // legacy format; current is a T_property (aka SCH_FIELD)
if( text->Type() == SCH_GLOBAL_LABEL_T )
{
SCH_GLOBALLABEL* label = static_cast<SCH_GLOBALLABEL*>( text.get() );
SCH_FIELD* field = &label->GetFields()[0];
field->SetTextPos( parseXY() );
NeedRIGHT();
field->SetVisible( true );
}
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( text->m_Uuid ) = parseKIID();
NeedRIGHT();
break;
case T_property:
{
if( text->Type() == SCH_TEXT_T )
Unexpected( T_property );
SCH_FIELD* field = parseSchField( text.get() );
// If the field is a Intersheetrefs it is not handled like other fields:
// It always exists and is the first in list
if( text->Type() == SCH_GLOBAL_LABEL_T
&& ( field->GetInternalName() == wxT( "Intersheet References" ) // old name in V6.0
|| field->GetInternalName() == wxT( "Intersheetrefs" ) ) ) // Current name
{
SCH_GLOBALLABEL* label = static_cast<SCH_GLOBALLABEL*>( text.get() );
// Ensure the Id of this special and first field is 0, needed by
// SCH_FIELD::IsHypertext() test
field->SetId( 0 );
label->GetFields()[0] = *field;
}
else
{
static_cast<SCH_LABEL_BASE*>( text.get() )->GetFields().emplace_back( *field );
}
delete field;
break;
}
default:
Expecting( "at, shape, iref, uuid or effects" );
}
}
SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( text.get() );
if( label && label->GetFields().empty() )
label->SetFieldsAutoplaced();
return text.release();
}
SCH_TEXTBOX* SCH_SEXPR_PARSER::parseSchTextBox()
{
wxCHECK_MSG( CurTok() == T_text_box, nullptr,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a text box." ) );
T token;
VECTOR2I pos;
VECTOR2I end;
VECTOR2I size;
bool foundEnd = false;
bool foundSize = false;
STROKE_PARAMS stroke( schIUScale.MilsToIU( DEFAULT_LINE_WIDTH_MILS ), PLOT_DASH_TYPE::DEFAULT );
FILL_PARAMS fill;
std::unique_ptr<SCH_TEXTBOX> textBox = std::make_unique<SCH_TEXTBOX>();
NeedSYMBOL();
textBox->SetText( FromUTF8() );
for( token = NextTok(); token != T_RIGHT; token = NextTok() )
{
if( token != T_LEFT )
Expecting( T_LEFT );
token = NextTok();
switch( token )
{
case T_exclude_from_sim:
textBox->SetExcludeFromSim( parseBool() );
NeedRIGHT();
break;
case T_start: // Legacy token during 6.99 development; fails to handle angle
pos = parseXY();
NeedRIGHT();
break;
case T_end: // Legacy token during 6.99 development; fails to handle angle
end = parseXY();
foundEnd = true;
NeedRIGHT();
break;
case T_at:
pos = parseXY();
textBox->SetTextAngle( EDA_ANGLE( parseDouble( "textbox angle" ), DEGREES_T ) );
NeedRIGHT();
break;
case T_size:
size = parseXY();
foundSize = true;
NeedRIGHT();
break;
case T_stroke:
parseStroke( stroke );
textBox->SetStroke( stroke );
break;
case T_fill:
parseFill( fill );
textBox->SetFillMode( fill.m_FillType );
textBox->SetFillColor( fill.m_Color );
break;
case T_effects:
parseEDA_TEXT( static_cast<EDA_TEXT*>( textBox.get() ), false );
break;
case T_uuid:
NeedSYMBOL();
const_cast<KIID&>( textBox->m_Uuid ) = KIID( FromUTF8() );
NeedRIGHT();
break;
default:
Expecting( "at, size, stroke, fill, effects or uuid" );
}
}
textBox->SetPosition( pos );
if( foundEnd )
textBox->SetEnd( end );
else if( foundSize )
textBox->SetEnd( pos + size );
else
Expecting( "size" );
return textBox.release();
}
void SCH_SEXPR_PARSER::parseBusAlias( SCH_SCREEN* aScreen )
{
wxCHECK_RET( CurTok() == T_bus_alias,
wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a bus alias." ) );
wxCHECK( aScreen, /* void */ );
T token;
std::shared_ptr<BUS_ALIAS> busAlias = std::make_shared<BUS_ALIAS>( aScreen );
wxString alias;
wxString member;
NeedSYMBOL();
alias = FromUTF8();
if( m_requiredVersion < 20210621 )
alias = ConvertToNewOverbarNotation( alias );
busAlias->SetName( alias );
NeedLEFT();
token = NextTok();
if( token != T_members )
Expecting( "members" );
token = NextTok();
while( token != T_RIGHT )
{
if( !IsSymbol( token ) )
Expecting( "quoted string" );
member = FromUTF8();
if( m_requiredVersion < 20210621 )
member = ConvertToNewOverbarNotation( member );
busAlias->Members().emplace_back( member );
token = NextTok();
}
NeedRIGHT();
aScreen->AddBusAlias( busAlias );
}