From 9d6f64da9a486c9c9effb1fb7b7fff64b9aa8853 Mon Sep 17 00:00:00 2001 From: Wayne Stambaugh Date: Fri, 28 Feb 2020 09:03:09 -0500 Subject: [PATCH] Implement symbol library s-expression parser. This implements all existing symbol library object support and the ability to save and load symbol library files between file formats for round robin testing. --- eeschema/CMakeLists.txt | 1 + eeschema/class_libentry.cpp | 39 +- eeschema/class_libentry.h | 8 +- eeschema/dialogs/panel_sym_lib_table.cpp | 78 +- eeschema/lib_arc.cpp | 54 + eeschema/lib_arc.h | 4 +- eeschema/sch_eagle_plugin.cpp | 6 + eeschema/sch_eagle_plugin.h | 2 + eeschema/sch_file_versions.h | 32 + eeschema/sch_io_mgr.cpp | 21 +- eeschema/sch_io_mgr.h | 16 +- eeschema/sch_legacy_plugin.h | 5 + eeschema/sch_sexpr_parser.cpp | 1345 ++++++++++++++++++++++ eeschema/sch_sexpr_parser.h | 157 +++ eeschema/sch_sexpr_plugin.cpp | 91 +- eeschema/sch_sexpr_plugin.h | 10 +- eeschema/symbol_lib.keywords | 22 + libs/kimath/include/trigo.h | 53 +- libs/kimath/src/trigo.cpp | 72 +- 19 files changed, 1921 insertions(+), 95 deletions(-) create mode 100644 eeschema/sch_file_versions.h create mode 100644 eeschema/sch_sexpr_parser.cpp create mode 100644 eeschema/sch_sexpr_parser.h diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index 69ef67d594..b20b490f6e 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -199,6 +199,7 @@ set( EESCHEMA_SRCS sch_plugin.cpp sch_preview_panel.cpp sch_screen.cpp + sch_sexpr_parser.cpp sch_sexpr_plugin.cpp sch_sheet.cpp sch_sheet_path.cpp diff --git a/eeschema/class_libentry.cpp b/eeschema/class_libentry.cpp index df1841a177..13d4dd6e9b 100644 --- a/eeschema/class_libentry.cpp +++ b/eeschema/class_libentry.cpp @@ -1011,7 +1011,7 @@ SEARCH_RESULT LIB_PART::Visit( INSPECTOR aInspector, void* aTestData, const KICA } -void LIB_PART::SetUnitCount( int aCount ) +void LIB_PART::SetUnitCount( int aCount, bool aDuplicateDrawItems ) { if( m_unitCount == aCount ) return; @@ -1028,7 +1028,7 @@ void LIB_PART::SetUnitCount( int aCount ) ++i; } } - else + else if( aDuplicateDrawItems ) { int prevCount = m_unitCount; @@ -1067,7 +1067,7 @@ int LIB_PART::GetUnitCount() const } -void LIB_PART::SetConversion( bool aSetConvert ) +void LIB_PART::SetConversion( bool aSetConvert, bool aDuplicatePins ) { if( aSetConvert == HasConversion() ) return; @@ -1075,25 +1075,28 @@ void LIB_PART::SetConversion( bool aSetConvert ) // Duplicate items to create the converted shape if( aSetConvert ) { - std::vector< LIB_ITEM* > tmp; // Temporarily store the duplicated pins here. - - for( LIB_ITEM& item : m_drawings ) + if( aDuplicatePins ) { - // Only pins are duplicated. - if( item.Type() != LIB_PIN_T ) - continue; + std::vector< LIB_ITEM* > tmp; // Temporarily store the duplicated pins here. - if( item.m_Convert == 1 ) + for( LIB_ITEM& item : m_drawings ) { - LIB_ITEM* newItem = (LIB_ITEM*) item.Clone(); - newItem->m_Convert = 2; - tmp.push_back( newItem ); - } - } + // Only pins are duplicated. + if( item.Type() != LIB_PIN_T ) + continue; - // Transfer the new pins to the LIB_PART. - for( unsigned i = 0; i < tmp.size(); i++ ) - m_drawings.push_back( tmp[i] ); + if( item.m_Convert == 1 ) + { + LIB_ITEM* newItem = (LIB_ITEM*) item.Clone(); + newItem->m_Convert = 2; + tmp.push_back( newItem ); + } + } + + // Transfer the new pins to the LIB_PART. + for( unsigned i = 0; i < tmp.size(); i++ ) + m_drawings.push_back( tmp[i] ); + } } else { diff --git a/eeschema/class_libentry.h b/eeschema/class_libentry.h index 12af55b440..d5cd65d9f0 100644 --- a/eeschema/class_libentry.h +++ b/eeschema/class_libentry.h @@ -477,9 +477,10 @@ public: * count is less than the current count, all draw objects for units * greater that count are removed from the part. * - * @param count - Number of units per package. + * @param aCount - Number of units per package. + * @param aDuplicateDrawItems Create duplicate draw items of unit 1 for each additionl unit. */ - void SetUnitCount( int count ); + void SetUnitCount( int aCount, bool aDuplicateDrawItems = true ); int GetUnitCount() const override; /** @@ -538,8 +539,9 @@ public: * added to the part. * * @param aSetConvert - Set or clear the part alternate body style. + * @param aDuplicatePins - Duplicate all pins from original body style if true. */ - void SetConversion( bool aSetConvert ); + void SetConversion( bool aSetConvert, bool aDuplicatePins = true ); /** * Set the offset in mils of the pin name text from the pin symbol. diff --git a/eeschema/dialogs/panel_sym_lib_table.cpp b/eeschema/dialogs/panel_sym_lib_table.cpp index 30b83d11b0..adf6d5c699 100644 --- a/eeschema/dialogs/panel_sym_lib_table.cpp +++ b/eeschema/dialogs/panel_sym_lib_table.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,64 @@ #include #include + +// clang-format off + +/** + * Container that describes file type info for the add a library options + */ +struct supportedFileType +{ + wxString m_Description; ///< Description shown in the file picker dialog + wxString m_FileFilter; ///< Filter used for file pickers if m_IsFile is true + wxString m_FolderSearchExtension; ///< In case of folders it stands for extensions of files stored inside + bool m_IsFile; ///< Whether the library is a folder or a file + SCH_IO_MGR::SCH_FILE_T m_Plugin; +}; + + +/** + * Event IDs for the menu items in the split button menu for add a library + */ +enum { + ID_PANEL_SYM_LIB_KICAD = ID_END_EESCHEMA_ID_LIST, + ID_PANEL_SYM_LIB_LEGACY, +}; + + +/** + * Map with event id as the key to supported file types that will be listed for the add + * a library option. + */ +static const std::map& fileTypes() +{ + static const std::map fileTypes = + { + { ID_PANEL_SYM_LIB_LEGACY, + { + "KiCad legacy symbol library file (*.lib)", + SchematicSymbolFileWildcard(), + "", + true, + SCH_IO_MGR::SCH_LEGACY + } + }, + { + ID_PANEL_SYM_LIB_KICAD, + { + "KiCad s-expression symbol library file (*.kicad_sym)", + KiCadSymbolLibFileWildcard(), + "", + true, + SCH_IO_MGR::SCH_KICAD + } + } + }; + + return fileTypes; +} + + /** * Build a wxGridTableBase by wrapping an #SYMBOL_LIB_TABLE object. */ @@ -184,8 +243,8 @@ PANEL_SYM_LIB_TABLE::PANEL_SYM_LIB_TABLE( DIALOG_EDIT_LIBRARY_TABLES* aParent, wxArrayString pluginChoices; -// pluginChoices.Add( SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_KICAD ) ); pluginChoices.Add( SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_LEGACY ) ); + pluginChoices.Add( SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_KICAD ) ); populateEnvironReadOnlyTable(); @@ -351,8 +410,12 @@ void PANEL_SYM_LIB_TABLE::pageChangedHandler( wxAuiNotebookEvent& event ) void PANEL_SYM_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event ) { + wxString wildcards = SchematicLibraryFileWildcard(); + + wildcards += "|" + KiCadSymbolLibFileWildcard(); + wxFileDialog dlg( this, _( "Select Library" ), m_lastBrowseDir, - wxEmptyString, SchematicLibraryFileWildcard(), + wxEmptyString, wildcards, wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE ); auto result = dlg.ShowModal(); @@ -401,17 +464,15 @@ void PANEL_SYM_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event ) // SCH_IO_MGR needs to provide file extension information for libraries too // auto detect the plugin type - /*for( auto pluginType : SCH_IO_MGR::SCH_FILE_T_vector ) + for( auto pluginType : SCH_IO_MGR::SCH_FILE_T_vector ) { - if( SCH_IO_MGR::GetFileExtension( pluginType ).Lower() == fn.GetExt().Lower() ) + if( SCH_IO_MGR::GetLibraryFileExtension( pluginType ).Lower() == fn.GetExt().Lower() ) { m_cur_grid->SetCellValue( last_row, COL_TYPE, SCH_IO_MGR::ShowType( pluginType ) ); break; } - }*/ - m_cur_grid->SetCellValue( last_row, COL_TYPE, - SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_LEGACY ) ); + } // try to use path normalized to an environmental variable or project path wxString path = NormalizePath( filePath, &envVars, m_projectBasePath ); @@ -441,13 +502,10 @@ void PANEL_SYM_LIB_TABLE::appendRowHandler( wxCommandEvent& event ) if( m_cur_grid->AppendRows( 1 ) ) { int row = m_cur_grid->GetNumberRows() - 1; - // Gives a default type (currently, only one type exists): - m_cur_grid->SetCellValue( row, COL_TYPE, SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_LEGACY ) ); // wx documentation is wrong, SetGridCursor does not make visible. m_cur_grid->MakeCellVisible( row, 0 ); m_cur_grid->SetGridCursor( row, 1 ); - m_cur_grid->EnableCellEditControl( true ); m_cur_grid->ShowCellEditControl(); } diff --git a/eeschema/lib_arc.cpp b/eeschema/lib_arc.cpp index 62d14e80f8..508490d5cd 100644 --- a/eeschema/lib_arc.cpp +++ b/eeschema/lib_arc.cpp @@ -579,3 +579,57 @@ void LIB_ARC::CalcRadiusAngles() if( !IsMoving() ) NORMALIZE_ANGLE_POS( m_t2 ); } + + +VECTOR2I LIB_ARC::CalcMidPoint() const +{ + double radA; + double radB; + VECTOR2D midPoint; + double startAngle = static_cast( m_t1 ) / 10.0; + double endAngle = static_cast( m_t2 ) / 10.0; + + // Normalize the draw angle to always be quadrant 1 to 4 (counter-clockwise). + if( startAngle > endAngle ) + std::swap( startAngle, endAngle ); + + if( startAngle < 0 ) + startAngle += 360.0; + + if( endAngle < 0 ) + endAngle += 360.0; + + bool interceptsNegativeX = InterceptsNegativeX( startAngle, endAngle ); + bool interceptsPositiveX = InterceptsPositiveX( startAngle, endAngle ); + + if( !interceptsPositiveX && !interceptsNegativeX ) + { + radA = 1.0; + radB = -1.0; + } + else if( interceptsPositiveX && !interceptsNegativeX ) + { + radA = 1.0; + radB = 1.0; + } + else if( !interceptsPositiveX && interceptsNegativeX ) + { + radA = -1.0; + radB = -1.0; + } + else + { + radA = -1.0; + radB = 1.0; + } + + double x = ( radA * std::sqrt( (m_Radius + m_ArcStart.x) * (m_Radius + m_ArcEnd.x) ) ) + + ( radB * std::sqrt( (m_Radius - m_ArcStart.x) * (m_Radius - m_ArcEnd.x) ) ); + double y = ( radA * std::sqrt( (m_Radius + m_ArcStart.y) * (m_Radius + m_ArcEnd.y) ) ) + + ( radB * std::sqrt( (m_Radius - m_ArcStart.y) * (m_Radius - m_ArcEnd.y) ) ); + + midPoint.x = KiROUND( x ); + midPoint.y = KiROUND( y ); + + return midPoint; +} diff --git a/eeschema/lib_arc.h b/eeschema/lib_arc.h index 664c2cec77..160b59bcd4 100644 --- a/eeschema/lib_arc.h +++ b/eeschema/lib_arc.h @@ -27,7 +27,7 @@ #define LIB_ARC_H #include - +#include class TRANSFORM; @@ -116,6 +116,8 @@ public: wxPoint GetEnd() const { return m_ArcEnd; } void SetEnd( const wxPoint& aPoint ) { m_ArcEnd = aPoint; } + VECTOR2I CalcMidPoint() const; + /** * Calculate the radius and angle of an arc using the start, end, and center points. */ diff --git a/eeschema/sch_eagle_plugin.cpp b/eeschema/sch_eagle_plugin.cpp index 57e5d3ae39..9df839adeb 100644 --- a/eeschema/sch_eagle_plugin.cpp +++ b/eeschema/sch_eagle_plugin.cpp @@ -375,6 +375,12 @@ const wxString SCH_EAGLE_PLUGIN::GetFileExtension() const } +const wxString SCH_EAGLE_PLUGIN::GetLibraryFileExtension() const +{ + return "lbr"; +} + + int SCH_EAGLE_PLUGIN::GetModifyHash() const { return 0; diff --git a/eeschema/sch_eagle_plugin.h b/eeschema/sch_eagle_plugin.h index ac829439c7..caa5547601 100644 --- a/eeschema/sch_eagle_plugin.h +++ b/eeschema/sch_eagle_plugin.h @@ -87,6 +87,8 @@ public: const wxString GetFileExtension() const override; + const wxString GetLibraryFileExtension() const override; + int GetModifyHash() const override; SCH_SHEET* Load( const wxString& aFileName, KIWAY* aKiway, SCH_SHEET* aAppendToMe = NULL, diff --git a/eeschema/sch_file_versions.h b/eeschema/sch_file_versions.h new file mode 100644 index 0000000000..0efc8d8cb2 --- /dev/null +++ b/eeschema/sch_file_versions.h @@ -0,0 +1,32 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 CERN + * + * @author Wayne Stambaugh + * + * 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 2 + * 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 . + */ + + +/** + * This file contains the file format version information for the s-expression schematic + * and symbol library file formats. + * + * @note Comment out the last version and add the new version as a date time stamp in the + * YYYYMMDD format. Comment the changes to the file format for historical purposes. + * + */ +#define SEXPR_SYMBOL_LIB_FILE_VERSION 20200126 // Initial version. + diff --git a/eeschema/sch_io_mgr.cpp b/eeschema/sch_io_mgr.cpp index e568e37475..7b794b5695 100644 --- a/eeschema/sch_io_mgr.cpp +++ b/eeschema/sch_io_mgr.cpp @@ -91,7 +91,7 @@ const wxString SCH_IO_MGR::ShowType( SCH_FILE_T aType ) return wxString( wxT( "Legacy" ) ); case SCH_KICAD: - return wxString( "KiCad" ); + return wxString( wxT( "KiCad" ) ); case SCH_EAGLE: return wxString( wxT( "EAGLE" ) ); @@ -107,7 +107,7 @@ SCH_IO_MGR::SCH_FILE_T SCH_IO_MGR::EnumFromStr( const wxString& aType ) if( aType == wxT( "Legacy" ) ) return SCH_LEGACY; - else if( aType == "KiCad" ) + else if( aType == wxT( "KiCad" ) ) return SCH_KICAD; else if( aType == wxT( "EAGLE" ) ) return SCH_EAGLE; @@ -133,12 +133,27 @@ const wxString SCH_IO_MGR::GetFileExtension( SCH_FILE_T aFileType ) } +const wxString SCH_IO_MGR::GetLibraryFileExtension( SCH_FILE_T aFileType ) +{ + wxString ext = wxEmptyString; + SCH_PLUGIN* plugin = FindPlugin( aFileType ); + + if( plugin != NULL ) + { + ext = plugin->GetLibraryFileExtension(); + ReleasePlugin( plugin ); + } + + return ext; +} + + SCH_IO_MGR::SCH_FILE_T SCH_IO_MGR::GuessPluginTypeFromLibPath( const wxString& aLibPath ) { SCH_FILE_T ret = SCH_LEGACY; // default guess, unless detected otherwise. wxFileName fn( aLibPath ); - if( fn.GetExt() == SchematicFileExtension ) + if( fn.GetExt() == SchematicLibraryFileExtension ) { ret = SCH_LEGACY; } diff --git a/eeschema/sch_io_mgr.h b/eeschema/sch_io_mgr.h index 87c448c6e1..68eada86c5 100644 --- a/eeschema/sch_io_mgr.h +++ b/eeschema/sch_io_mgr.h @@ -96,7 +96,7 @@ public: static SCH_FILE_T EnumFromStr( const wxString& aFileType ); /** - * Return the file extension for \a aFileType. + * Return the schematic file extension for \a aFileType. * * @param aFileType is the #SCH_FILE_T type. * @@ -104,6 +104,15 @@ public: */ static const wxString GetFileExtension( SCH_FILE_T aFileType ); + /** + * Return the symbol library file extension (if any) for \a aFileType. + * + * @param aFileType is the #SCH_FILE_T type. + * + * @return the file extension for \a aFileType or an empty string if \a aFileType is invalid. + */ + static const wxString GetLibraryFileExtension( SCH_FILE_T aFileType ); + /** * Return a plugin type given a footprint library's libPath. */ @@ -200,6 +209,11 @@ public: */ virtual const wxString GetFileExtension() const = 0; + /** + * Return the library file extension for the #SCH_PLUGIN object. + */ + virtual const wxString GetLibraryFileExtension() const = 0; + /** * Return the modification hash from the library cache. * diff --git a/eeschema/sch_legacy_plugin.h b/eeschema/sch_legacy_plugin.h index 47d563d0ab..e48016f328 100644 --- a/eeschema/sch_legacy_plugin.h +++ b/eeschema/sch_legacy_plugin.h @@ -77,6 +77,11 @@ public: return wxT( "sch" ); } + const wxString GetLibraryFileExtension() const override + { + return wxT( "lib" ); + } + /** * The property used internally by the plugin to enable cache buffering which prevents * the library file from being written every time the cache is changed. This is useful diff --git a/eeschema/sch_sexpr_parser.cpp b/eeschema/sch_sexpr_parser.cpp new file mode 100644 index 0000000000..46fcb0b0f5 --- /dev/null +++ b/eeschema/sch_sexpr_parser.cpp @@ -0,0 +1,1345 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 CERN + * + * @author Wayne Stambaugh + * + * 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 2 + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file sch_sexpr_parser.cpp + * @brief Schematic and symbol library s-expression file format parser implementations. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace TSYMBOL_LIB_T; + + +SCH_SEXPR_PARSER::SCH_SEXPR_PARSER( LINE_READER* aLineReader ) : + SYMBOL_LIB_LEXER( aLineReader ), + m_requiredVersion( 0 ), + m_unit( 1 ), + m_convert( 1 ) +{ +} + + +bool SCH_SEXPR_PARSER::IsTooRecent() const +{ + return m_requiredVersion && m_requiredVersion > SEXPR_SYMBOL_LIB_FILE_VERSION; +} + + +void SCH_SEXPR_PARSER::ParseLib( LIB_PART_MAP& aSymbolLibMap ) +{ + T token; + + NeedLEFT(); + NextTok(); + parseHeader(); + + 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_PART* symbol = ParseSymbol( aSymbolLibMap ); + aSymbolLibMap[symbol->GetName()] = symbol; + } + else + { + Expecting( "symbol" ); + } + } +} + + +LIB_PART* SCH_SEXPR_PARSER::ParseSymbol( LIB_PART_MAP& aSymbolLibMap ) +{ + wxCHECK_MSG( CurTok() == T_symbol, nullptr, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a symbol." ) ); + + T token; + long tmp; + wxString error; + wxString name; + LIB_ITEM* item; + std::unique_ptr symbol( new LIB_PART( wxEmptyString ) ); + + symbol->SetUnitCount( 1 ); + + m_fieldId = MANDATORY_FIELDS; + + token = NextTok(); + + if( !IsSymbol( token ) ) + { + error.Printf( _( "Invalid symbol name in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + name = FromUTF8(); + + if( name.IsEmpty() ) + { + error.Printf( _( "Empty symbol name in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + m_symbolName = name; + symbol->SetName( name ); + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + switch( token ) + { + 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_property: + parseProperty( symbol ); + break; + + case T_extends: + { + token = NextTok(); + + if( !IsSymbol( token ) ) + { + error.Printf( + _( "Invalid symbol extends name in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + name = FromUTF8(); + auto it = aSymbolLibMap.find( name ); + + if( it == aSymbolLibMap.end() ) + { + error.Printf( + _( "No parent for extended symbol %s in\nfile: \"%s\"\nline: %d\noffset: %d" ), + name.c_str(), CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + symbol->SetParent( it->second ); + NeedRIGHT(); + break; + } + + case T_symbol: + { + token = NextTok(); + + if( !IsSymbol( token ) ) + { + error.Printf( + _( "Invalid symbol unit name in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + name = FromUTF8(); + + if( !name.StartsWith( m_symbolName ) ) + { + error.Printf( + _( "Invalid symbol unit name prefix %s in\nfile: \"%s\"\nline: %d\noffset: %d" ), + name.c_str(), CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + name = name.Right( name.Length() - m_symbolName.Length() - 1 ); + + wxStringTokenizer tokenizer( name, "_" ); + + if( tokenizer.CountTokens() != 2 ) + { + error.Printf( + _( "Invalid symbol unit name suffix %s in\nfile: \"%s\"\nline: %d\noffset: %d" ), + name.c_str(), CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + if( !tokenizer.GetNextToken().ToLong( &tmp ) ) + { + error.Printf( + _( "Invalid symbol unit number %s in\nfile: \"%s\"\nline: %d\noffset: %d" ), + name.c_str(), CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + m_unit = static_cast( tmp ); + + if( !tokenizer.GetNextToken().ToLong( &tmp ) ) + { + error.Printf( + _( "Invalid symbol convert number %s in\nfile: \"%s\"\nline: %d\noffset: %d" ), + name.c_str(), CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + m_convert = static_cast( 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_arc: + case T_bezier: + case T_circle: + case T_pin: + case T_polyline: + case T_rectangle: + case T_text: + item = ParseDrawItem(); + + wxCHECK_MSG( item, nullptr, "Invalid draw item pointer." ); + + item->SetParent( symbol.get() ); + symbol->AddDrawItem( item ); + 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: + item = ParseDrawItem(); + + wxCHECK_MSG( item, nullptr, "Invalid draw item pointer." ); + + item->SetParent( symbol.get() ); + symbol->AddDrawItem( item ); + break; + + default: + Expecting( "pin_names, pin_numbers, arc, bezier, circle, pin, polyline, " + "rectangle, or text" ); + } + } + + m_symbolName.clear(); + + return symbol.release(); +} + + +LIB_ITEM* SCH_SEXPR_PARSER::ParseDrawItem() +{ + switch( CurTok() ) + { + case T_arc: + return static_cast( parseArc() ); + break; + + case T_bezier: + return static_cast( parseBezier() ); + break; + + case T_circle: + return static_cast( parseCircle() ); + break; + + case T_pin: + return static_cast( parsePin() ); + break; + + case T_polyline: + return static_cast( parsePolyLine() ); + break; + + case T_rectangle: + return static_cast( parseRectangle() ); + break; + + case T_text: + return static_cast( parseText() ); + break; + + default: + Expecting( "arc, bezier, circle, pin, polyline, rectangle, or text" ); + } + + return nullptr; +} + + +double SCH_SEXPR_PARSER::parseDouble() +{ + char* tmp; + + errno = 0; + + double fval = strtod( CurText(), &tmp ); + + if( errno ) + { + wxString error; + error.Printf( _( "Invalid floating point number in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + + THROW_IO_ERROR( error ); + } + + if( CurText() == tmp ) + { + wxString error; + error.Printf( _( "Missing floating point number in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + + THROW_IO_ERROR( error ); + } + + return fval; +} + + +FILL_T SCH_SEXPR_PARSER::parseFillMode() +{ + wxCHECK_MSG( CurTok() == T_fill, NO_FILL, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as fill." ) ); + + NeedLEFT(); + T token = NextTok(); + + if( token != T_type ) + Expecting( "type" ); + + token = NextTok(); + + FILL_T fillType = NO_FILL; + + switch( token ) + { + case T_none: + fillType = NO_FILL; + break; + + case T_outline: + fillType = FILLED_SHAPE; + break; + + case T_background: + fillType = FILLED_WITH_BG_BODYCOLOR; + break; + + default: + Expecting( "none, outline, or background" ); + } + + NeedRIGHT(); // Closes type token. + NeedRIGHT(); // Closes fill token. + + return fillType; +} + + +void SCH_SEXPR_PARSER::parseEDA_TEXT( EDA_TEXT* aText ) +{ + wxCHECK_RET( aText && CurTok() == T_effects, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as EDA_TEXT." ) ); + + T token; + + 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_size: + { + wxSize sz; + sz.SetHeight( parseInternalUnits( "text height" ) ); + sz.SetWidth( parseInternalUnits( "text width" ) ); + aText->SetTextSize( sz ); + NeedRIGHT(); + break; + } + + case T_thickness: + aText->SetThickness( parseInternalUnits( "text thickness" ) ); + NeedRIGHT(); + break; + + case T_bold: + aText->SetBold( true ); + break; + + case T_italic: + aText->SetItalic( true ); + break; + + default: + Expecting( "size, bold, or italic" ); + } + } + + break; + + case T_justify: + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + switch( token ) + { + case T_left: + aText->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT ); + break; + + case T_right: + aText->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT ); + break; + + case T_top: + aText->SetVertJustify( GR_TEXT_VJUSTIFY_TOP ); + break; + + case T_bottom: + aText->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); + break; + + case T_mirror: + aText->SetMirrored( true ); + break; + + default: + Expecting( "left, right, top, bottom, or mirror" ); + } + } + + break; + + case T_hide: + aText->SetVisible( false ); + break; + + default: + Expecting( "font, justify, or hide" ); + } + } +} + + +void SCH_SEXPR_PARSER::parseHeader() +{ + wxCHECK_RET( CurTok() == T_kicad_symbol_lib, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a header." ) ); + + NeedLEFT(); + + T tok = NextTok(); + + if( tok == T_version ) + { + m_requiredVersion = parseInt( FromUTF8().mb_str( wxConvUTF8 ) ); + NeedRIGHT(); + + // Skip the host name and host build version information. + NeedLEFT(); + NeedSYMBOL(); + NeedSYMBOL(); + NeedSYMBOL(); + NeedRIGHT(); + } + else + { + m_requiredVersion = SEXPR_SYMBOL_LIB_FILE_VERSION; + + // Skip the host name and host build version information. + NeedSYMBOL(); + NeedSYMBOL(); + NeedRIGHT(); + } +} + + +void SCH_SEXPR_PARSER::parsePinNames( std::unique_ptr& aSymbol ) +{ + wxCHECK_RET( CurTok() == T_pin_names, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a pin_name token." ) ); + + wxString error; + + T token = NextTok(); + + if( token == T_LEFT ) + { + token = NextTok(); + + if( token != T_offset ) + Expecting( "offset" ); + + aSymbol->SetPinNameOffset( parseInternalUnits( "pin name offset" ) ); + NeedRIGHT(); + } + else if( token == T_hide ) + { + aSymbol->SetShowPinNames( false ); + } + else + { + error.Printf( + _( "Invalid symbol names definition in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + NeedRIGHT(); +} + + +void SCH_SEXPR_PARSER::parseProperty( std::unique_ptr& aSymbol ) +{ + wxCHECK_RET( CurTok() == T_property, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a property token." ) ); + + wxString error; + wxString name; + wxString value; + std::unique_ptr tmp( new LIB_FIELD( MANDATORY_FIELDS ) ); + + T token = NextTok(); + + if( !IsSymbol( token ) ) + { + error.Printf( _( "Invalid property name in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + name = FromUTF8(); + + if( name.IsEmpty() ) + { + error.Printf( _( "Empty property name in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + token = NextTok(); + + if( !IsSymbol( token ) ) + { + error.Printf( _( "Invalid property value in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + // Empty property values are valid. + value = FromUTF8(); + + LIB_FIELD* field; + + if( name == "ki_reference" ) + { + field = &aSymbol->GetReferenceField(); + field->SetText( value ); + } + else if( name == "ki_value" ) + { + field = &aSymbol->GetValueField(); + field->SetText( value ); + } + else if( name == "ki_footprint" ) + { + field = &aSymbol->GetFootprintField(); + field->SetText( value ); + } + else if( name == "ki_datasheet" ) + { + field = aSymbol->GetField( DATASHEET ); + aSymbol->SetDocFileName( value ); + } + else if( name == "ki_keywords" ) + { + // Not a LIB_FIELD object yet. + aSymbol->SetKeyWords( value ); + field = tmp.get(); + } + else if( name == "ki_description" ) + { + // Not a LIB_FIELD object yet. + aSymbol->SetDescription( value ); + field = tmp.get(); + } + else if( name == "ki_fp_filters" ) + { + // Not a LIB_FIELD object yet. + wxArrayString filters; + wxStringTokenizer tokenizer( value ); + + while( tokenizer.HasMoreTokens() ) + filters.Add( tokenizer.GetNextToken() ); + + aSymbol->SetFootprintFilters( filters ); + field = tmp.get(); + } + else if( name == "ki_locked" ) + { + aSymbol->LockUnits( true ); + field = tmp.get(); + } + else + { + field = aSymbol->FindField( name ); + + if( !field ) + { + field = new LIB_FIELD( m_fieldId, name ); + aSymbol->AddDrawItem( field ); + m_fieldId += 1; + } + + field->SetText( value ); + } + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + switch( token ) + { + case T_at: + field->SetPosition( parseXY() ); + NeedRIGHT(); + break; + + case T_effects: + parseEDA_TEXT( static_cast( field ) ); + break; + + default: + Expecting( "at or effects" ); + } + } +} + + +LIB_ARC* SCH_SEXPR_PARSER::parseArc() +{ + wxCHECK_MSG( CurTok() == T_arc, nullptr, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as an arc token." ) ); + + T token; + wxPoint start; + wxPoint mid; + wxPoint end; + wxPoint pos; + bool hasMidPoint = false; + std::unique_ptr arc( new LIB_ARC( nullptr ) ); + + arc->SetUnit( m_unit ); + arc->SetConvert( m_convert ); + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + switch( token ) + { + case T_start: + start = parseXY(); + NeedRIGHT(); + break; + + case T_mid: + mid = parseXY(); + NeedRIGHT(); + hasMidPoint = true; + break; + + case T_end: + end = 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: + pos = parseXY(); + NeedRIGHT(); + break; + + case T_length: + arc->SetRadius( parseInternalUnits( "radius length" ) ); + NeedRIGHT(); + break; + + case T_angles: + { + int angle1 = KiROUND( parseDouble( "start radius angle" ) * 10.0 ); + int angle2 = KiROUND( parseDouble( "end radius angle" ) * 10.0 ); + + NORMALIZE_ANGLE_POS( angle1 ); + NORMALIZE_ANGLE_POS( angle2 ); + arc->SetFirstRadiusAngle( angle1 ); + arc->SetSecondRadiusAngle( angle2 ); + NeedRIGHT(); + break; + } + + default: + Expecting( "at, length, or angle" ); + } + } + + break; + + case T_stroke: + NeedLEFT(); + token = NextTok(); + + if( token != T_width ) + Expecting( "width" ); + + arc->SetWidth( parseInternalUnits( "stroke width" ) ); + NeedRIGHT(); // Closes width token; + NeedRIGHT(); // Closes stroke token; + break; + + case T_fill: + arc->SetFillMode( parseFillMode() ); + break; + + default: + Expecting( "start, end, radius, stroke, or fill" ); + } + } + + arc->SetPosition( pos ); + arc->SetStart( start ); + arc->SetEnd( end ); + + if( hasMidPoint ) + { + VECTOR2I center = GetArcCenter( arc->GetStart(), mid, arc->GetEnd() ); + + arc->SetPosition( wxPoint( center.x, center.y ) ); + + // @todo Calculate the radius. + + arc->CalcRadiusAngles(); + } + + return arc.release(); +} + + +LIB_BEZIER* SCH_SEXPR_PARSER::parseBezier() +{ + wxCHECK_MSG( CurTok() == T_bezier, nullptr, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a bezier." ) ); + + T token; + std::unique_ptr bezier( new LIB_BEZIER( nullptr ) ); + + bezier->SetUnit( m_unit ); + bezier->SetConvert( m_convert ); + + 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: + NeedLEFT(); + token = NextTok(); + + if( token != T_width ) + Expecting( "width" ); + + bezier->SetWidth( parseInternalUnits( "stroke width" ) ); + NeedRIGHT(); // Closes width token; + NeedRIGHT(); // Closes stroke token; + break; + + case T_fill: + bezier->SetFillMode( parseFillMode() ); + break; + + default: + Expecting( "pts, stroke, or fill" ); + } + } + + return bezier.release(); +} + + +LIB_CIRCLE* SCH_SEXPR_PARSER::parseCircle() +{ + wxCHECK_MSG( CurTok() == T_circle, nullptr, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a circle token." ) ); + + T token; + std::unique_ptr circle( new LIB_CIRCLE( nullptr ) ); + + circle->SetUnit( m_unit ); + circle->SetConvert( m_convert ); + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + switch( token ) + { + case T_center: + circle->SetPosition( parseXY() ); + NeedRIGHT(); + break; + + case T_radius: + circle->SetRadius( parseInternalUnits( "radius length" ) ); + NeedRIGHT(); + break; + + case T_stroke: + NeedLEFT(); + token = NextTok(); + + if( token != T_width ) + Expecting( "width" ); + + circle->SetWidth( parseInternalUnits( "stroke width" ) ); + NeedRIGHT(); // Closes width token; + NeedRIGHT(); // Closes stroke token; + break; + + case T_fill: + circle->SetFillMode( parseFillMode() ); + break; + + default: + Expecting( "start, end, radius, stroke, or fill" ); + } + } + + return circle.release(); +} + + +LIB_PIN* SCH_SEXPR_PARSER::parsePin() +{ + 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 pin( new LIB_PIN( nullptr ) ); + + pin->SetUnit( m_unit ); + pin->SetConvert( m_convert ); + + // Pin electrical type. + token = NextTok(); + + switch( token ) + { + case T_input: + pin->SetType( ELECTRICAL_PINTYPE::PT_INPUT ); + break; + + case T_output: + pin->SetType( ELECTRICAL_PINTYPE::PT_OUTPUT ); + break; + + case T_bidirectional: + pin->SetType( ELECTRICAL_PINTYPE::PT_BIDI ); + break; + + case T_tri_state: + pin->SetType( ELECTRICAL_PINTYPE::PT_TRISTATE ); + break; + + case T_passive: + pin->SetType( ELECTRICAL_PINTYPE::PT_PASSIVE ); + break; + + case T_unspecified: + pin->SetType( ELECTRICAL_PINTYPE::PT_UNSPECIFIED ); + break; + + case T_power_in: + pin->SetType( ELECTRICAL_PINTYPE::PT_POWER_IN ); + break; + + case T_power_out: + pin->SetType( ELECTRICAL_PINTYPE::PT_POWER_OUT ); + break; + + case T_open_collector: + pin->SetType( ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR ); + break; + + case T_open_emitter: + pin->SetType( ELECTRICAL_PINTYPE::PT_OPENEMITTER ); + break; + + case T_unconnected: + pin->SetType( ELECTRICAL_PINTYPE::PT_NC ); + break; + + default: + Expecting( "input, output, bidirectional, tri_state, passive, unspecified, " + "power_in, power_out, open_collector, open_emitter, or unconnected" ); + } + + // Pin shape. + token = NextTok(); + + switch( token ) + { + case T_line: + pin->SetShape( GRAPHIC_PINSHAPE::LINE ); + break; + + case T_inverted: + pin->SetShape( GRAPHIC_PINSHAPE::INVERTED ); + break; + + case T_clock: + pin->SetShape( GRAPHIC_PINSHAPE::CLOCK ); + break; + + case T_inverted_clock: + pin->SetShape( GRAPHIC_PINSHAPE::INVERTED_CLOCK ); + break; + + case T_input_low: + pin->SetShape( GRAPHIC_PINSHAPE::INPUT_LOW ); + break; + + case T_clock_low: + pin->SetShape( GRAPHIC_PINSHAPE::CLOCK_LOW ); + break; + + case T_output_low: + pin->SetShape( GRAPHIC_PINSHAPE::OUTPUT_LOW ); + break; + + case T_edge_clock_high: + pin->SetShape( GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK ); + break; + + case T_non_logic: + pin->SetShape( GRAPHIC_PINSHAPE::NONLOGIC ); + break; + + default: + Expecting( "line, inverted, clock, inverted_clock, input_low, clock_low, " + "output_low, edge_clock_high, non_logic" ); + } + + 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 ) ) + { + error.Printf( _( "Invalid pin name in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + pin->SetName( FromUTF8() ); + 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; + + parseEDA_TEXT( &text ); + pin->SetNameTextSize( text.GetTextHeight() ); + NeedRIGHT(); + } + else if( token != T_RIGHT ) + { + Expecting( ") or effects" ); + } + + break; + + case T_number: + token = NextTok(); + + if( !IsSymbol( token ) ) + { + error.Printf( _( "Invalid pin number in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + pin->SetNumber( FromUTF8() ); + 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; + + parseEDA_TEXT( &text ); + pin->SetNumberTextSize( text.GetTextHeight(), false ); + NeedRIGHT(); + } + else if( token != T_RIGHT ) + { + Expecting( ") or effects" ); + } + + break; + + default: + Expecting( "at, name, number, or length" ); + } + } + + return pin.release(); +} + + +LIB_POLYLINE* SCH_SEXPR_PARSER::parsePolyLine() +{ + wxCHECK_MSG( CurTok() == T_polyline, nullptr, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a polyline." ) ); + + T token; + std::unique_ptr polyLine( new LIB_POLYLINE( nullptr ) ); + + polyLine->SetUnit( m_unit ); + polyLine->SetConvert( m_convert ); + + 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: + NeedLEFT(); + token = NextTok(); + + if( token != T_width ) + Expecting( "width" ); + + polyLine->SetWidth( parseInternalUnits( "stroke width" ) ); + NeedRIGHT(); // Closes width token; + NeedRIGHT(); // Closes stroke token; + break; + + case T_fill: + polyLine->SetFillMode( parseFillMode() ); + break; + + default: + Expecting( "pts, stroke, or fill" ); + } + } + + return polyLine.release(); +} + + +LIB_RECTANGLE* SCH_SEXPR_PARSER::parseRectangle() +{ + wxCHECK_MSG( CurTok() == T_rectangle, nullptr, + wxT( "Cannot parse " ) + GetTokenString( CurTok() ) + wxT( " as a rectangle token." ) ); + + T token; + std::unique_ptr rectangle( new LIB_RECTANGLE( nullptr ) ); + + rectangle->SetUnit( m_unit ); + rectangle->SetConvert( m_convert ); + + 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: + NeedLEFT(); + token = NextTok(); + + if( token != T_width ) + Expecting( "width" ); + + rectangle->SetWidth( parseInternalUnits( "stroke width" ) ); + NeedRIGHT(); // Closes width token; + NeedRIGHT(); // Closes stroke token; + break; + + case T_fill: + rectangle->SetFillMode( parseFillMode() ); + 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; + wxString tmp; + wxString error; + std::unique_ptr text( new LIB_TEXT( nullptr ) ); + + text->SetUnit( m_unit ); + text->SetConvert( m_convert ); + token = NextTok(); + + if( !IsSymbol( token ) ) + { + error.Printf( _( "Invalid text string in\nfile: \"%s\"\nline: %d\noffset: %d" ), + CurSource().c_str(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + 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() ); + text->SetTextAngle( parseDouble( "text angle" ) ); + NeedRIGHT(); + break; + + case T_effects: + parseEDA_TEXT( static_cast( text.get() ) ); + break; + + default: + Expecting( "at or effects" ); + } + } + + return text.release(); +} diff --git a/eeschema/sch_sexpr_parser.h b/eeschema/sch_sexpr_parser.h new file mode 100644 index 0000000000..7afd7adac4 --- /dev/null +++ b/eeschema/sch_sexpr_parser.h @@ -0,0 +1,157 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2020 CERN + * + * @author Wayne Stambaugh + * + * 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 2 + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file sch_sexpr_parser.h + * @brief Schematic and symbol library s-expression file format parser definitions. + */ + +#ifndef __SCH_SEXPR_PARSER_H__ +#define __SCH_SEXPR_PARSER_H__ + +#include // IU_PER_MM +#include // KiROUND, Clamp + +#include +#include + + +class LIB_ARC; +class LIB_BEZIER; +class LIB_CIRCLE; +class LIB_ITEM; +class LIB_PIN; +class LIB_POLYLINE; +class LIB_TEXT; + + +class SCH_SEXPR_PARSER : public SYMBOL_LIB_LEXER +{ + int m_requiredVersion; ///< Set to the symbol library file version required. + int m_fieldId; ///< The current field ID. + int m_unit; ///< The current unit being parsed. + int m_convert; ///< The current body style being parsed. + wxString m_symbolName; ///< The current symbol name. + + void parseHeader(); + + inline int parseInt() + { + return (int)strtol( CurText(), NULL, 10 ); + } + + inline int parseInt( const char* aExpected ) + { + NeedNUMBER( aExpected ); + return parseInt(); + } + + /** + * Parse the current token as an ASCII numeric string with possible leading + * whitespace into a double precision floating point number. + * + * @throw IO_ERROR if an error occurs attempting to convert the current token. + * @return The result of the parsed token. + */ + double parseDouble(); + + inline double parseDouble( const char* aExpected ) + { + NeedNUMBER( aExpected ); + return parseDouble(); + } + + inline double parseDouble( TSYMBOL_LIB_T::T aToken ) + { + return parseDouble( GetTokenText( aToken ) ); + } + + inline int parseInternalUnits() + { + auto retval = parseDouble() * 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. + double int_limit = std::numeric_limits::max() * 0.7071; // 0.7071 = roughly 1/sqrt(2) + + return KiROUND( Clamp( -int_limit, retval, int_limit ) ); + } + + inline int parseInternalUnits( const char* aExpected ) + { + auto retval = parseDouble( aExpected ) * IU_PER_MM; + + double int_limit = std::numeric_limits::max() * 0.7071; + + return KiROUND( Clamp( -int_limit, retval, int_limit ) ); + } + + inline int parseInternalUnits( TSYMBOL_LIB_T::T aToken ) + { + return parseInternalUnits( GetTokenText( aToken ) ); + } + + inline wxPoint parseXY() + { + wxPoint xy; + + xy.x = parseInternalUnits( "X coordinate" ); + xy.y = parseInternalUnits( "Y coordinate" ); + + return xy; + } + + FILL_T parseFillMode(); + + void parseEDA_TEXT( EDA_TEXT* aText ); + void parsePinNames( std::unique_ptr& aSymbol ); + + void parseProperty( std::unique_ptr& aSymbol ); + + LIB_ARC* parseArc(); + LIB_BEZIER* parseBezier(); + LIB_CIRCLE* parseCircle(); + LIB_PIN* parsePin(); + LIB_POLYLINE* parsePolyLine(); + LIB_RECTANGLE* parseRectangle(); + LIB_TEXT* parseText(); + +public: + SCH_SEXPR_PARSER( LINE_READER* aLineReader = nullptr ); + + void ParseLib( LIB_PART_MAP& aSymbolLibMap ); + + LIB_PART* ParseSymbol( LIB_PART_MAP& aSymbolLibMap ); + + LIB_ITEM* ParseDrawItem(); + + /** + * Return whether a version number, if any was parsed, was too recent + */ + bool IsTooRecent() const; +}; + +#endif // __SCH_SEXPR_PARSER_H__ diff --git a/eeschema/sch_sexpr_plugin.cpp b/eeschema/sch_sexpr_plugin.cpp index 57ab99e8a8..34dd9f0c74 100644 --- a/eeschema/sch_sexpr_plugin.cpp +++ b/eeschema/sch_sexpr_plugin.cpp @@ -65,18 +65,16 @@ #include #include #include // for MAX_UNIT_COUNT_PER_PACKAGE definition +#include +#include #include // for PropPowerSymsOnly definintion. #include #include -#include #include using namespace TSYMBOL_LIB_T; -#define SEXPR_SYMBOL_LIB_FILE_VERSION 20200126 // Initial version. - - #define SCH_PARSE_ERROR( text, reader, pos ) \ THROW_PARSE_ERROR( text, reader.GetSource(), reader.Line(), \ reader.LineNumber(), pos - reader.Line() ) @@ -1022,12 +1020,9 @@ void SCH_SEXPR_PLUGIN_CACHE::Load() FILE_LINE_READER reader( m_libFileName.GetFullPath() ); - if( !reader.ReadLine() ) - THROW_IO_ERROR( _( "unexpected end of file" ) ); - - const char* line = reader.Line(); - + SCH_SEXPR_PARSER parser( &reader ); + parser.ParseLib( m_symbols ); ++m_modHash; // Remember the file modification time of library file when the @@ -1232,7 +1227,7 @@ void SCH_SEXPR_PLUGIN_CACHE::SaveSymbol( LIB_PART* aSymbol, OUTPUTFORMATTER& aFo if( aSymbol->GetPinNameOffset() != Iu2Mils( DEFAULT_PIN_NAME_OFFSET ) || !aSymbol->ShowPinNames() ) { - aFormatter.Print( 0, " (pin_name" ); + aFormatter.Print( 0, " (pin_names" ); if( aSymbol->GetPinNameOffset() != Iu2Mils( DEFAULT_PIN_NAME_OFFSET ) ) aFormatter.Print( 0, " (offset %s)", @@ -1257,6 +1252,18 @@ void SCH_SEXPR_PLUGIN_CACHE::SaveSymbol( LIB_PART* aSymbol, OUTPUTFORMATTER& aFo for( auto field : fields ) saveField( &field, aFormatter, aNestLevel + 1 ); + int lastFieldId = fields.back().GetId() + 1; + + // @todo At some point in the future the lock status (all units interchangeable) should + // be set deterministically. For now a custom lock properter is used to preserve the + // locked flag state. + if( aSymbol->UnitsLocked() ) + { + LIB_FIELD locked( lastFieldId, "ki_locked" ); + saveField( &locked, aFormatter, aNestLevel + 1 ); + lastFieldId += 1; + } + saveDcmInfoAsFields( aSymbol, aFormatter, aNestLevel, fields.back().GetId() + 1 ); // Save the draw items grouped by units. @@ -1397,35 +1404,59 @@ void SCH_SEXPR_PLUGIN_CACHE::saveArc( LIB_ARC* aArc, { wxCHECK_RET( aArc && aArc->Type() == LIB_ARC_T, "Invalid LIB_ARC object." ); + int x1 = aArc->GetFirstRadiusAngle(); + + if( x1 > 1800 ) + x1 -= 3600; + + int x2 = aArc->GetSecondRadiusAngle(); + + if( x2 > 1800 ) + x2 -= 3600; + aFormatter.Print( aNestLevel, - "(arc (start %s %s) (end %s %s) (radius (at %s %s) (length %s))", + "(arc (start %s %s) (end %s %s) (radius (at %s %s) (length %s) " + "(angles %g %g))", FormatInternalUnits( aArc->GetStart().x ).c_str(), FormatInternalUnits( aArc->GetStart().y ).c_str(), FormatInternalUnits( aArc->GetEnd().x ).c_str(), FormatInternalUnits( aArc->GetEnd().y ).c_str(), FormatInternalUnits( aArc->GetPosition().x ).c_str(), FormatInternalUnits( aArc->GetPosition().y ).c_str(), - FormatInternalUnits( aArc->GetRadius() ).c_str() ); + FormatInternalUnits( aArc->GetRadius() ).c_str(), + static_cast( x1 ) / 10.0, + static_cast( x2 ) / 10.0 ); bool needsSpace = false; + bool onNewLine = false; if( Iu2Mils( aArc->GetWidth() ) != DEFAULTDRAWLINETHICKNESS && aArc->GetWidth() != 0 ) { - aFormatter.Print( 0, "(stroke (width %s))", + aFormatter.Print( 0, "\n" ); + aFormatter.Print( aNestLevel + 1, "(stroke (width %s))", FormatInternalUnits( aArc->GetWidth() ).c_str() ); needsSpace = true; + onNewLine = true; } if( aArc->GetFillMode() != NO_FILL ) { - if( needsSpace ) + if( !onNewLine || needsSpace ) aFormatter.Print( 0, " " ); FormatFill( static_cast< LIB_ITEM* >( aArc ), aFormatter, 0 ); } - aFormatter.Print( 0, ")\n" ); + if( onNewLine ) + { + aFormatter.Print( 0, "\n" ); + aFormatter.Print( aNestLevel, ")\n" ); + } + else + { + aFormatter.Print( 0, ")\n" ); + } } @@ -1550,26 +1581,15 @@ void SCH_SEXPR_PLUGIN_CACHE::saveField( LIB_FIELD* aField, FormatInternalUnits( aField->GetPosition().x ).c_str(), FormatInternalUnits( aField->GetPosition().y ).c_str() ); - if( aField->IsVisible() && aField->IsDefaultFormatting() ) + if( aField->IsDefaultFormatting() ) { - aFormatter.Print( 0, ")\n" ); // Close property token if visible and no font effects. + aFormatter.Print( 0, ")\n" ); // Close property token if no font effects. } else { - if( !aField->IsVisible() ) - { - if( aField->IsDefaultFormatting() ) - aFormatter.Print( 0, " hide)\n" ); // Close property token if no font effects. - else - aFormatter.Print( 0, " hide" ); - } - - if( !aField->IsDefaultFormatting() ) - { - aFormatter.Print( 0, "\n" ); - aField->Format( &aFormatter, aNestLevel + 1, CTL_OMIT_HIDE ); - aFormatter.Print( aNestLevel, ")\n" ); // Close property token. - } + aFormatter.Print( 0, "\n" ); + aField->Format( &aFormatter, aNestLevel, 0 ); + aFormatter.Print( aNestLevel, ")\n" ); // Close property token. } } @@ -1608,7 +1628,12 @@ void SCH_SEXPR_PLUGIN_CACHE::savePin( LIB_PIN* aPin, aFormatter.Print( 0, " (effects (font (size %s %s)))", FormatInternalUnits( aPin->GetNumberTextSize() ).c_str(), FormatInternalUnits( aPin->GetNumberTextSize() ).c_str() ); - aFormatter.Print( 0, "))\n" ); + aFormatter.Print( 0, ")" ); + + if( !aPin->IsVisible() ) + aFormatter.Print( 0, " hide" ); + + aFormatter.Print( 0, ")\n" ); } @@ -1732,7 +1757,7 @@ void SCH_SEXPR_PLUGIN_CACHE::saveText( LIB_TEXT* aText, FormatInternalUnits( aText->GetPosition().x ).c_str(), FormatInternalUnits( aText->GetPosition().y ).c_str(), aText->GetTextAngle() ); - aText->Format( &aFormatter, aNestLevel + 1, 0 ); + aText->Format( &aFormatter, aNestLevel, 0 ); aFormatter.Print( aNestLevel, ")\n" ); } diff --git a/eeschema/sch_sexpr_plugin.h b/eeschema/sch_sexpr_plugin.h index 679401dfeb..fe9633619d 100644 --- a/eeschema/sch_sexpr_plugin.h +++ b/eeschema/sch_sexpr_plugin.h @@ -47,7 +47,6 @@ class LIB_PART; class PART_LIB; class BUS_ALIAS; - /** * A #SCH_PLUGIN derivation for loading schematic files using the new s-expression * file format. @@ -63,12 +62,17 @@ public: const wxString GetName() const override { - return wxT( "Eeschema-Legacy" ); + return wxT( "Eeschema s-expression" ); } const wxString GetFileExtension() const override { - return wxT( "sch" ); + return wxT( "kicad_sch" ); + } + + const wxString GetLibraryFileExtension() const override + { + return wxT( "kicad_sym" ); } /** diff --git a/eeschema/symbol_lib.keywords b/eeschema/symbol_lib.keywords index a69b3369bb..acb9288272 100644 --- a/eeschema/symbol_lib.keywords +++ b/eeschema/symbol_lib.keywords @@ -1,39 +1,56 @@ alternate anchor +angles arc at atomic +background bezier bidirectional +bold +bottom +center circle clock clock_low color edge_clock_high +effects end extends fill +font +hide hint_alt_swap hint_pin_swap input input_low inverted inverted_clock +italic +justify kicad_symbol_lib +left length line mid +mirror name non_logic +none number +offset open_collector open_emitter +outline output_low unconnected output passive pin pin_del +pin_names +pin_numbers pin_merge pin_rename polyline @@ -46,14 +63,19 @@ pts radius rectangle required +right shape +size start stroke symbol text +thickness +top tri_state type unspecified uuid +version width xy diff --git a/libs/kimath/include/trigo.h b/libs/kimath/include/trigo.h index 8f4713641b..60eab83b09 100644 --- a/libs/kimath/include/trigo.h +++ b/libs/kimath/include/trigo.h @@ -1,7 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 2018 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2018-2019 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 @@ -40,7 +40,7 @@ * @return true if the point P is on the segment S. * faster than TestSegmentHit() because P should be exactly on S * therefore works fine only for H, V and 45 deg segm. - * suitable for busses and wires in eeschema, otherwise use TestSegmentHit() + * suitable for buses and wires in Eeschema, otherwise use TestSegmentHit() */ bool IsPointOnSegment( const wxPoint& aSegStart, const wxPoint& aSegEnd, const wxPoint& aTestPoint ); @@ -106,6 +106,8 @@ void RotatePoint( double *pX, double *pY, double cx, double cy, double angle ); * @return The center of the circle */ const VECTOR2I GetArcCenter( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd ); +const VECTOR2D GetArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd ); +const wxPoint GetArcCenter( const wxPoint& aStart, const wxPoint& aMid, const wxPoint& aEnd ); /* Return the arc tangent of 0.1 degrees coord vector dx, dy * between -1800 and 1800 @@ -214,7 +216,7 @@ inline double RAD2DEG( double rad ) { return rad * 180.0 / M_PI; } inline double DECIDEG2RAD( double deg ) { return deg * M_PI / 1800.0; } inline double RAD2DECIDEG( double rad ) { return rad * 1800.0 / M_PI; } -/* These are templated over T (and not simply double) because eeschema +/* These are templated over T (and not simply double) because Eeschema is still using int for angles in some place */ /// Normalize angle to be >=-360.0 and <= 360.0 @@ -240,7 +242,7 @@ template inline T NormalizeAngle360Min( T Angle ) } /// Normalize angle to be in the 0.0 .. 360.0 range: -/// angle is in 1/10 degees +/// angle is in 1/10 degrees template inline T NormalizeAnglePos( T Angle ) { while( Angle < 0 ) @@ -249,6 +251,7 @@ template inline T NormalizeAnglePos( T Angle ) Angle -= 3600; return Angle; } + template inline void NORMALIZE_ANGLE_POS( T& Angle ) { Angle = NormalizeAnglePos( Angle ); @@ -313,6 +316,7 @@ template inline T NegateAndNormalizeAnglePos( T Angle ) Angle -= 3600; return Angle; } + template inline void NEGATE_AND_NORMALIZE_ANGLE_POS( T& Angle ) { Angle = NegateAndNormalizeAnglePos( Angle ); @@ -328,6 +332,7 @@ template inline T NormalizeAngle90( T Angle ) Angle -= 1800; return Angle; } + template inline void NORMALIZE_ANGLE_90( T& Angle ) { Angle = NormalizeAngle90( Angle ); @@ -343,11 +348,51 @@ template inline T NormalizeAngle180( T Angle ) Angle -= 3600; return Angle; } + template inline void NORMALIZE_ANGLE_180( T& Angle ) { Angle = NormalizeAngle180( Angle ); } +/** + * Test if an arc from \a aStartAngle to \a aEndAngle crosses the positive X axis (0 degrees). + * + * Testing is performed in the quadrant 1 to quadrant 4 direction (counter-clockwise). + * + * @warning Do not use this function, it has not been tested. + * + * @param aStartAngle The arc start angle in degrees. + * @param aEndAngle The arc end angle in degrees. + */ +inline bool InterceptsPositiveX( double aStartAngle, double aEndAngle ) +{ + double end = aEndAngle; + + if( aStartAngle < aEndAngle ) + end += 360.0; + + return aStartAngle < 360.0 && aEndAngle > 360.0; +} + +/** + * Test if an arc from \a aStartAngle to \a aEndAngle crosses the negative X axis (180 degrees). + * + * Testing is performed in the quadrant 1 to quadrant 4 direction (counter-clockwise). + * + * @warning Do not use this function, it has not been tested. + * + * @param aStartAngle The arc start angle in degrees. + * @param aEndAngle The arc end angle in degrees. + */ +inline bool InterceptsNegativeX( double aStartAngle, double aEndAngle ) +{ + double end = aEndAngle; + + if( aStartAngle < aEndAngle ) + end += 360.0; + + return aStartAngle < 180.0 && aEndAngle > 180.0; +} /** * Circle generation utility: computes r * sin(a) diff --git a/libs/kimath/src/trigo.cpp b/libs/kimath/src/trigo.cpp index f24942da9e..58b44a11dd 100644 --- a/libs/kimath/src/trigo.cpp +++ b/libs/kimath/src/trigo.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014 Jean-Pierre Charras, jp.charras at wanadoo.fr - * Copyright (C) 2014 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2014-2019 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 @@ -57,13 +57,13 @@ bool IsPointOnSegment( const wxPoint& aSegStart, const wxPoint& aSegEnd, } -// Returns true if the segment 1 intersectd the segment 2. +// Returns true if the segment 1 intersected the segment 2. bool SegmentIntersectsSegment( const wxPoint &a_p1_l1, const wxPoint &a_p2_l1, const wxPoint &a_p1_l2, const wxPoint &a_p2_l2, wxPoint* aIntersectionPoint ) { - //We are forced to use 64bit ints because the internal units can oveflow 32bit ints when + //We are forced to use 64bit ints because the internal units can overflow 32bit ints when // multiplied with each other, the alternative would be to scale the units down (i.e. divide // by a fixed number). long long dX_a, dY_a, dX_b, dY_b, dX_ab, dY_ab; @@ -338,9 +338,9 @@ void RotatePoint( double* pX, double* pY, double angle ) } -const VECTOR2I GetArcCenter( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd ) +const VECTOR2D GetArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd ) { - VECTOR2I center; + VECTOR2D center; double yDelta_21 = aMid.y - aStart.y; double xDelta_21 = aMid.x - aStart.x; double yDelta_32 = aEnd.y - aMid.y; @@ -352,8 +352,8 @@ const VECTOR2I GetArcCenter( const VECTOR2I& aStart, const VECTOR2I& aMid, const if( ( ( xDelta_21 == 0.0 ) && ( yDelta_32 == 0.0 ) ) || ( ( yDelta_21 == 0.0 ) && ( xDelta_32 == 0.0 ) ) ) { - center.x = KiROUND( ( aStart.x + aEnd.x ) / 2.0 ); - center.y = KiROUND( ( aStart.y + aEnd.y ) / 2.0 ); + center.x = ( aStart.x + aEnd.x ) / 2.0; + center.y = ( aStart.y + aEnd.y ) / 2.0 ; return center; } @@ -383,20 +383,54 @@ const VECTOR2I GetArcCenter( const VECTOR2I& aStart, const VECTOR2I& aMid, const bSlope = -std::numeric_limits::epsilon(); - double result = ( aSlope * bSlope * ( aStart.y - aEnd.y ) + - bSlope * ( aStart.x + aMid.x ) - - aSlope * ( aMid.x + aEnd.x ) ) / ( 2 * ( bSlope - aSlope ) ); + center.x = ( aSlope * bSlope * ( aStart.y - aEnd.y ) + + bSlope * ( aStart.x + aMid.x ) - + aSlope * ( aMid.x + aEnd.x ) ) / ( 2 * ( bSlope - aSlope ) ); - center.x = KiROUND( Clamp( double( std::numeric_limits::min() / 2.0 ), - result, - double( std::numeric_limits::max() / 2.0 ) ) ); - - result = ( ( ( aStart.x + aMid.x ) / 2.0 - center.x ) / aSlope + + center.y = ( ( ( aStart.x + aMid.x ) / 2.0 - center.x ) / aSlope + ( aStart.y + aMid.y ) / 2.0 ); - center.y = KiROUND( Clamp( double( std::numeric_limits::min() / 2.0 ), - result, - double( std::numeric_limits::max() / 2.0 ) ) ); - return center; } + + +const VECTOR2I GetArcCenter( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd ) +{ + VECTOR2D dStart( static_cast( aStart.x ), static_cast( aStart.y ) ); + VECTOR2D dMid( static_cast( aMid.x ), static_cast( aMid.y ) ); + VECTOR2D dEnd( static_cast( aEnd.x ), static_cast( aEnd.y ) ); + VECTOR2D dCenter = GetArcCenter( dStart, dMid, dEnd ); + + VECTOR2I iCenter; + + iCenter.x = KiROUND( Clamp( double( std::numeric_limits::min() / 2.0 ), + dCenter.x, + double( std::numeric_limits::max() / 2.0 ) ) ); + + iCenter.y = KiROUND( Clamp( double( std::numeric_limits::min() / 2.0 ), + dCenter.y, + double( std::numeric_limits::max() / 2.0 ) ) ); + + return iCenter; +} + + +const wxPoint GetArcCenter( const wxPoint& aStart, const wxPoint& aMid, const wxPoint& aEnd ) +{ + VECTOR2D dStart( static_cast( aStart.x ), static_cast( aStart.y ) ); + VECTOR2D dMid( static_cast( aMid.x ), static_cast( aMid.y ) ); + VECTOR2D dEnd( static_cast( aEnd.x ), static_cast( aEnd.y ) ); + VECTOR2D dCenter = GetArcCenter( dStart, dMid, dEnd ); + + wxPoint iCenter; + + iCenter.x = KiROUND( Clamp( double( std::numeric_limits::min() / 2.0 ), + dCenter.x, + double( std::numeric_limits::max() / 2.0 ) ) ); + + iCenter.y = KiROUND( Clamp( double( std::numeric_limits::min() / 2.0 ), + dCenter.y, + double( std::numeric_limits::max() / 2.0 ) ) ); + + return iCenter; +}