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.
This commit is contained in:
Wayne Stambaugh 2020-02-28 09:03:09 -05:00
parent 738a93db68
commit 9d6f64da9a
19 changed files with 1921 additions and 95 deletions

View File

@ -199,6 +199,7 @@ set( EESCHEMA_SRCS
sch_plugin.cpp sch_plugin.cpp
sch_preview_panel.cpp sch_preview_panel.cpp
sch_screen.cpp sch_screen.cpp
sch_sexpr_parser.cpp
sch_sexpr_plugin.cpp sch_sexpr_plugin.cpp
sch_sheet.cpp sch_sheet.cpp
sch_sheet_path.cpp sch_sheet_path.cpp

View File

@ -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 ) if( m_unitCount == aCount )
return; return;
@ -1028,7 +1028,7 @@ void LIB_PART::SetUnitCount( int aCount )
++i; ++i;
} }
} }
else else if( aDuplicateDrawItems )
{ {
int prevCount = m_unitCount; 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() ) if( aSetConvert == HasConversion() )
return; return;
@ -1075,25 +1075,28 @@ void LIB_PART::SetConversion( bool aSetConvert )
// Duplicate items to create the converted shape // Duplicate items to create the converted shape
if( aSetConvert ) if( aSetConvert )
{ {
std::vector< LIB_ITEM* > tmp; // Temporarily store the duplicated pins here. if( aDuplicatePins )
for( LIB_ITEM& item : m_drawings )
{ {
// Only pins are duplicated. std::vector< LIB_ITEM* > tmp; // Temporarily store the duplicated pins here.
if( item.Type() != LIB_PIN_T )
continue;
if( item.m_Convert == 1 ) for( LIB_ITEM& item : m_drawings )
{ {
LIB_ITEM* newItem = (LIB_ITEM*) item.Clone(); // Only pins are duplicated.
newItem->m_Convert = 2; if( item.Type() != LIB_PIN_T )
tmp.push_back( newItem ); continue;
}
}
// Transfer the new pins to the LIB_PART. if( item.m_Convert == 1 )
for( unsigned i = 0; i < tmp.size(); i++ ) {
m_drawings.push_back( tmp[i] ); 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 else
{ {

View File

@ -477,9 +477,10 @@ public:
* count is less than the current count, all draw objects for units * count is less than the current count, all draw objects for units
* greater that count are removed from the part. * 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; int GetUnitCount() const override;
/** /**
@ -538,8 +539,9 @@ public:
* added to the part. * added to the part.
* *
* @param aSetConvert - Set or clear the part alternate body style. * @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. * Set the offset in mils of the pin name text from the pin symbol.

View File

@ -34,6 +34,7 @@
#include <lib_table_grid.h> #include <lib_table_grid.h>
#include <wildcards_and_files_ext.h> #include <wildcards_and_files_ext.h>
#include <env_paths.h> #include <env_paths.h>
#include <eeschema_id.h>
#include <lib_edit_frame.h> #include <lib_edit_frame.h>
#include <lib_view_frame.h> #include <lib_view_frame.h>
#include <sch_edit_frame.h> #include <sch_edit_frame.h>
@ -43,6 +44,64 @@
#include <widgets/grid_readonly_text_helpers.h> #include <widgets/grid_readonly_text_helpers.h>
#include <widgets/grid_text_button_helpers.h> #include <widgets/grid_text_button_helpers.h>
// 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<int, supportedFileType>& fileTypes()
{
static const std::map<int, supportedFileType> 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. * 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; 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_LEGACY ) );
pluginChoices.Add( SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_KICAD ) );
populateEnvironReadOnlyTable(); populateEnvironReadOnlyTable();
@ -351,8 +410,12 @@ void PANEL_SYM_LIB_TABLE::pageChangedHandler( wxAuiNotebookEvent& event )
void PANEL_SYM_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event ) void PANEL_SYM_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event )
{ {
wxString wildcards = SchematicLibraryFileWildcard();
wildcards += "|" + KiCadSymbolLibFileWildcard();
wxFileDialog dlg( this, _( "Select Library" ), m_lastBrowseDir, wxFileDialog dlg( this, _( "Select Library" ), m_lastBrowseDir,
wxEmptyString, SchematicLibraryFileWildcard(), wxEmptyString, wildcards,
wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE ); wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE );
auto result = dlg.ShowModal(); 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 // SCH_IO_MGR needs to provide file extension information for libraries too
// auto detect the plugin type // 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, m_cur_grid->SetCellValue( last_row, COL_TYPE,
SCH_IO_MGR::ShowType( pluginType ) ); SCH_IO_MGR::ShowType( pluginType ) );
break; 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 // try to use path normalized to an environmental variable or project path
wxString path = NormalizePath( filePath, &envVars, m_projectBasePath ); 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 ) ) if( m_cur_grid->AppendRows( 1 ) )
{ {
int row = m_cur_grid->GetNumberRows() - 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. // wx documentation is wrong, SetGridCursor does not make visible.
m_cur_grid->MakeCellVisible( row, 0 ); m_cur_grid->MakeCellVisible( row, 0 );
m_cur_grid->SetGridCursor( row, 1 ); m_cur_grid->SetGridCursor( row, 1 );
m_cur_grid->EnableCellEditControl( true ); m_cur_grid->EnableCellEditControl( true );
m_cur_grid->ShowCellEditControl(); m_cur_grid->ShowCellEditControl();
} }

View File

@ -579,3 +579,57 @@ void LIB_ARC::CalcRadiusAngles()
if( !IsMoving() ) if( !IsMoving() )
NORMALIZE_ANGLE_POS( m_t2 ); NORMALIZE_ANGLE_POS( m_t2 );
} }
VECTOR2I LIB_ARC::CalcMidPoint() const
{
double radA;
double radB;
VECTOR2D midPoint;
double startAngle = static_cast<double>( m_t1 ) / 10.0;
double endAngle = static_cast<double>( 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;
}

View File

@ -27,7 +27,7 @@
#define LIB_ARC_H #define LIB_ARC_H
#include <lib_item.h> #include <lib_item.h>
#include <math/vector2d.h>
class TRANSFORM; class TRANSFORM;
@ -116,6 +116,8 @@ public:
wxPoint GetEnd() const { return m_ArcEnd; } wxPoint GetEnd() const { return m_ArcEnd; }
void SetEnd( const wxPoint& aPoint ) { m_ArcEnd = aPoint; } 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. * Calculate the radius and angle of an arc using the start, end, and center points.
*/ */

View File

@ -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 int SCH_EAGLE_PLUGIN::GetModifyHash() const
{ {
return 0; return 0;

View File

@ -87,6 +87,8 @@ public:
const wxString GetFileExtension() const override; const wxString GetFileExtension() const override;
const wxString GetLibraryFileExtension() const override;
int GetModifyHash() const override; int GetModifyHash() const override;
SCH_SHEET* Load( const wxString& aFileName, KIWAY* aKiway, SCH_SHEET* aAppendToMe = NULL, SCH_SHEET* Load( const wxString& aFileName, KIWAY* aKiway, SCH_SHEET* aAppendToMe = NULL,

View File

@ -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 <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 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 <http://www.gnu.org/licenses/>.
*/
/**
* 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.

View File

@ -91,7 +91,7 @@ const wxString SCH_IO_MGR::ShowType( SCH_FILE_T aType )
return wxString( wxT( "Legacy" ) ); return wxString( wxT( "Legacy" ) );
case SCH_KICAD: case SCH_KICAD:
return wxString( "KiCad" ); return wxString( wxT( "KiCad" ) );
case SCH_EAGLE: case SCH_EAGLE:
return wxString( wxT( "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" ) ) if( aType == wxT( "Legacy" ) )
return SCH_LEGACY; return SCH_LEGACY;
else if( aType == "KiCad" ) else if( aType == wxT( "KiCad" ) )
return SCH_KICAD; return SCH_KICAD;
else if( aType == wxT( "EAGLE" ) ) else if( aType == wxT( "EAGLE" ) )
return SCH_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_IO_MGR::SCH_FILE_T SCH_IO_MGR::GuessPluginTypeFromLibPath( const wxString& aLibPath )
{ {
SCH_FILE_T ret = SCH_LEGACY; // default guess, unless detected otherwise. SCH_FILE_T ret = SCH_LEGACY; // default guess, unless detected otherwise.
wxFileName fn( aLibPath ); wxFileName fn( aLibPath );
if( fn.GetExt() == SchematicFileExtension ) if( fn.GetExt() == SchematicLibraryFileExtension )
{ {
ret = SCH_LEGACY; ret = SCH_LEGACY;
} }

View File

@ -96,7 +96,7 @@ public:
static SCH_FILE_T EnumFromStr( const wxString& aFileType ); 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. * @param aFileType is the #SCH_FILE_T type.
* *
@ -104,6 +104,15 @@ public:
*/ */
static const wxString GetFileExtension( SCH_FILE_T aFileType ); 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. * Return a plugin type given a footprint library's libPath.
*/ */
@ -200,6 +209,11 @@ public:
*/ */
virtual const wxString GetFileExtension() const = 0; 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. * Return the modification hash from the library cache.
* *

View File

@ -77,6 +77,11 @@ public:
return wxT( "sch" ); 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 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 * the library file from being written every time the cache is changed. This is useful

File diff suppressed because it is too large Load Diff

157
eeschema/sch_sexpr_parser.h Normal file
View File

@ -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 <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 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 <convert_to_biu.h> // IU_PER_MM
#include <math/util.h> // KiROUND, Clamp
#include <class_library.h>
#include <symbol_lib_lexer.h>
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<int>::max() * 0.7071; // 0.7071 = roughly 1/sqrt(2)
return KiROUND( Clamp<double>( -int_limit, retval, int_limit ) );
}
inline int parseInternalUnits( const char* aExpected )
{
auto retval = parseDouble( aExpected ) * IU_PER_MM;
double int_limit = std::numeric_limits<int>::max() * 0.7071;
return KiROUND( Clamp<double>( -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<LIB_PART>& aSymbol );
void parseProperty( std::unique_ptr<LIB_PART>& 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__

View File

@ -65,18 +65,16 @@
#include <pin_shape.h> #include <pin_shape.h>
#include <pin_type.h> #include <pin_type.h>
#include <eeschema_id.h> // for MAX_UNIT_COUNT_PER_PACKAGE definition #include <eeschema_id.h> // for MAX_UNIT_COUNT_PER_PACKAGE definition
#include <sch_file_versions.h>
#include <sch_sexpr_parser.h>
#include <symbol_lib_table.h> // for PropPowerSymsOnly definintion. #include <symbol_lib_table.h> // for PropPowerSymsOnly definintion.
#include <symbol_lib_lexer.h> #include <symbol_lib_lexer.h>
#include <confirm.h> #include <confirm.h>
#include <symbol_lib_lexer.h>
#include <tool/selection.h> #include <tool/selection.h>
using namespace TSYMBOL_LIB_T; using namespace TSYMBOL_LIB_T;
#define SEXPR_SYMBOL_LIB_FILE_VERSION 20200126 // Initial version.
#define SCH_PARSE_ERROR( text, reader, pos ) \ #define SCH_PARSE_ERROR( text, reader, pos ) \
THROW_PARSE_ERROR( text, reader.GetSource(), reader.Line(), \ THROW_PARSE_ERROR( text, reader.GetSource(), reader.Line(), \
reader.LineNumber(), pos - reader.Line() ) reader.LineNumber(), pos - reader.Line() )
@ -1022,12 +1020,9 @@ void SCH_SEXPR_PLUGIN_CACHE::Load()
FILE_LINE_READER reader( m_libFileName.GetFullPath() ); FILE_LINE_READER reader( m_libFileName.GetFullPath() );
if( !reader.ReadLine() ) SCH_SEXPR_PARSER parser( &reader );
THROW_IO_ERROR( _( "unexpected end of file" ) );
const char* line = reader.Line();
parser.ParseLib( m_symbols );
++m_modHash; ++m_modHash;
// Remember the file modification time of library file when the // 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 ) if( aSymbol->GetPinNameOffset() != Iu2Mils( DEFAULT_PIN_NAME_OFFSET )
|| !aSymbol->ShowPinNames() ) || !aSymbol->ShowPinNames() )
{ {
aFormatter.Print( 0, " (pin_name" ); aFormatter.Print( 0, " (pin_names" );
if( aSymbol->GetPinNameOffset() != Iu2Mils( DEFAULT_PIN_NAME_OFFSET ) ) if( aSymbol->GetPinNameOffset() != Iu2Mils( DEFAULT_PIN_NAME_OFFSET ) )
aFormatter.Print( 0, " (offset %s)", aFormatter.Print( 0, " (offset %s)",
@ -1257,6 +1252,18 @@ void SCH_SEXPR_PLUGIN_CACHE::SaveSymbol( LIB_PART* aSymbol, OUTPUTFORMATTER& aFo
for( auto field : fields ) for( auto field : fields )
saveField( &field, aFormatter, aNestLevel + 1 ); 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 ); saveDcmInfoAsFields( aSymbol, aFormatter, aNestLevel, fields.back().GetId() + 1 );
// Save the draw items grouped by units. // 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." ); 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, 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().x ).c_str(),
FormatInternalUnits( aArc->GetStart().y ).c_str(), FormatInternalUnits( aArc->GetStart().y ).c_str(),
FormatInternalUnits( aArc->GetEnd().x ).c_str(), FormatInternalUnits( aArc->GetEnd().x ).c_str(),
FormatInternalUnits( aArc->GetEnd().y ).c_str(), FormatInternalUnits( aArc->GetEnd().y ).c_str(),
FormatInternalUnits( aArc->GetPosition().x ).c_str(), FormatInternalUnits( aArc->GetPosition().x ).c_str(),
FormatInternalUnits( aArc->GetPosition().y ).c_str(), FormatInternalUnits( aArc->GetPosition().y ).c_str(),
FormatInternalUnits( aArc->GetRadius() ).c_str() ); FormatInternalUnits( aArc->GetRadius() ).c_str(),
static_cast<double>( x1 ) / 10.0,
static_cast<double>( x2 ) / 10.0 );
bool needsSpace = false; bool needsSpace = false;
bool onNewLine = false;
if( Iu2Mils( aArc->GetWidth() ) != DEFAULTDRAWLINETHICKNESS if( Iu2Mils( aArc->GetWidth() ) != DEFAULTDRAWLINETHICKNESS
&& aArc->GetWidth() != 0 ) && 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() ); FormatInternalUnits( aArc->GetWidth() ).c_str() );
needsSpace = true; needsSpace = true;
onNewLine = true;
} }
if( aArc->GetFillMode() != NO_FILL ) if( aArc->GetFillMode() != NO_FILL )
{ {
if( needsSpace ) if( !onNewLine || needsSpace )
aFormatter.Print( 0, " " ); aFormatter.Print( 0, " " );
FormatFill( static_cast< LIB_ITEM* >( aArc ), aFormatter, 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().x ).c_str(),
FormatInternalUnits( aField->GetPosition().y ).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 else
{ {
if( !aField->IsVisible() ) aFormatter.Print( 0, "\n" );
{ aField->Format( &aFormatter, aNestLevel, 0 );
if( aField->IsDefaultFormatting() ) aFormatter.Print( aNestLevel, ")\n" ); // Close property token.
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.
}
} }
} }
@ -1608,7 +1628,12 @@ void SCH_SEXPR_PLUGIN_CACHE::savePin( LIB_PIN* aPin,
aFormatter.Print( 0, " (effects (font (size %s %s)))", aFormatter.Print( 0, " (effects (font (size %s %s)))",
FormatInternalUnits( aPin->GetNumberTextSize() ).c_str(), FormatInternalUnits( aPin->GetNumberTextSize() ).c_str(),
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().x ).c_str(),
FormatInternalUnits( aText->GetPosition().y ).c_str(), FormatInternalUnits( aText->GetPosition().y ).c_str(),
aText->GetTextAngle() ); aText->GetTextAngle() );
aText->Format( &aFormatter, aNestLevel + 1, 0 ); aText->Format( &aFormatter, aNestLevel, 0 );
aFormatter.Print( aNestLevel, ")\n" ); aFormatter.Print( aNestLevel, ")\n" );
} }

View File

@ -47,7 +47,6 @@ class LIB_PART;
class PART_LIB; class PART_LIB;
class BUS_ALIAS; class BUS_ALIAS;
/** /**
* A #SCH_PLUGIN derivation for loading schematic files using the new s-expression * A #SCH_PLUGIN derivation for loading schematic files using the new s-expression
* file format. * file format.
@ -63,12 +62,17 @@ public:
const wxString GetName() const override const wxString GetName() const override
{ {
return wxT( "Eeschema-Legacy" ); return wxT( "Eeschema s-expression" );
} }
const wxString GetFileExtension() const override const wxString GetFileExtension() const override
{ {
return wxT( "sch" ); return wxT( "kicad_sch" );
}
const wxString GetLibraryFileExtension() const override
{
return wxT( "kicad_sym" );
} }
/** /**

View File

@ -1,39 +1,56 @@
alternate alternate
anchor anchor
angles
arc arc
at at
atomic atomic
background
bezier bezier
bidirectional bidirectional
bold
bottom
center
circle circle
clock clock
clock_low clock_low
color color
edge_clock_high edge_clock_high
effects
end end
extends extends
fill fill
font
hide
hint_alt_swap hint_alt_swap
hint_pin_swap hint_pin_swap
input input
input_low input_low
inverted inverted
inverted_clock inverted_clock
italic
justify
kicad_symbol_lib kicad_symbol_lib
left
length length
line line
mid mid
mirror
name name
non_logic non_logic
none
number number
offset
open_collector open_collector
open_emitter open_emitter
outline
output_low output_low
unconnected unconnected
output output
passive passive
pin pin
pin_del pin_del
pin_names
pin_numbers
pin_merge pin_merge
pin_rename pin_rename
polyline polyline
@ -46,14 +63,19 @@ pts
radius radius
rectangle rectangle
required required
right
shape shape
size
start start
stroke stroke
symbol symbol
text text
thickness
top
tri_state tri_state
type type
unspecified unspecified
uuid uuid
version
width width
xy xy

View File

@ -1,7 +1,7 @@
/* /*
* This program source code file is part of KiCad, a free EDA CAD application. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * 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. * @return true if the point P is on the segment S.
* faster than TestSegmentHit() because P should be exactly on S * faster than TestSegmentHit() because P should be exactly on S
* therefore works fine only for H, V and 45 deg segm. * 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, bool IsPointOnSegment( const wxPoint& aSegStart, const wxPoint& aSegEnd,
const wxPoint& aTestPoint ); 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 * @return The center of the circle
*/ */
const VECTOR2I GetArcCenter( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd ); 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 /* Return the arc tangent of 0.1 degrees coord vector dx, dy
* between -1800 and 1800 * 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 DECIDEG2RAD( double deg ) { return deg * M_PI / 1800.0; }
inline double RAD2DECIDEG( double rad ) { return rad * 1800.0 / M_PI; } 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 */ is still using int for angles in some place */
/// Normalize angle to be >=-360.0 and <= 360.0 /// Normalize angle to be >=-360.0 and <= 360.0
@ -240,7 +242,7 @@ template <class T> inline T NormalizeAngle360Min( T Angle )
} }
/// Normalize angle to be in the 0.0 .. 360.0 range: /// Normalize angle to be in the 0.0 .. 360.0 range:
/// angle is in 1/10 degees /// angle is in 1/10 degrees
template <class T> inline T NormalizeAnglePos( T Angle ) template <class T> inline T NormalizeAnglePos( T Angle )
{ {
while( Angle < 0 ) while( Angle < 0 )
@ -249,6 +251,7 @@ template <class T> inline T NormalizeAnglePos( T Angle )
Angle -= 3600; Angle -= 3600;
return Angle; return Angle;
} }
template <class T> inline void NORMALIZE_ANGLE_POS( T& Angle ) template <class T> inline void NORMALIZE_ANGLE_POS( T& Angle )
{ {
Angle = NormalizeAnglePos( Angle ); Angle = NormalizeAnglePos( Angle );
@ -313,6 +316,7 @@ template <class T> inline T NegateAndNormalizeAnglePos( T Angle )
Angle -= 3600; Angle -= 3600;
return Angle; return Angle;
} }
template <class T> inline void NEGATE_AND_NORMALIZE_ANGLE_POS( T& Angle ) template <class T> inline void NEGATE_AND_NORMALIZE_ANGLE_POS( T& Angle )
{ {
Angle = NegateAndNormalizeAnglePos( Angle ); Angle = NegateAndNormalizeAnglePos( Angle );
@ -328,6 +332,7 @@ template <class T> inline T NormalizeAngle90( T Angle )
Angle -= 1800; Angle -= 1800;
return Angle; return Angle;
} }
template <class T> inline void NORMALIZE_ANGLE_90( T& Angle ) template <class T> inline void NORMALIZE_ANGLE_90( T& Angle )
{ {
Angle = NormalizeAngle90( Angle ); Angle = NormalizeAngle90( Angle );
@ -343,11 +348,51 @@ template <class T> inline T NormalizeAngle180( T Angle )
Angle -= 3600; Angle -= 3600;
return Angle; return Angle;
} }
template <class T> inline void NORMALIZE_ANGLE_180( T& Angle ) template <class T> inline void NORMALIZE_ANGLE_180( T& Angle )
{ {
Angle = NormalizeAngle180( 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) * Circle generation utility: computes r * sin(a)

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application. * 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 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * 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, bool SegmentIntersectsSegment( const wxPoint &a_p1_l1, const wxPoint &a_p2_l1,
const wxPoint &a_p1_l2, const wxPoint &a_p2_l2, const wxPoint &a_p1_l2, const wxPoint &a_p2_l2,
wxPoint* aIntersectionPoint ) 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 // multiplied with each other, the alternative would be to scale the units down (i.e. divide
// by a fixed number). // by a fixed number).
long long dX_a, dY_a, dX_b, dY_b, dX_ab, dY_ab; 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 yDelta_21 = aMid.y - aStart.y;
double xDelta_21 = aMid.x - aStart.x; double xDelta_21 = aMid.x - aStart.x;
double yDelta_32 = aEnd.y - aMid.y; 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 ) ) || if( ( ( xDelta_21 == 0.0 ) && ( yDelta_32 == 0.0 ) ) ||
( ( yDelta_21 == 0.0 ) && ( xDelta_32 == 0.0 ) ) ) ( ( yDelta_21 == 0.0 ) && ( xDelta_32 == 0.0 ) ) )
{ {
center.x = KiROUND( ( aStart.x + aEnd.x ) / 2.0 ); center.x = ( aStart.x + aEnd.x ) / 2.0;
center.y = KiROUND( ( aStart.y + aEnd.y ) / 2.0 ); center.y = ( aStart.y + aEnd.y ) / 2.0 ;
return center; return center;
} }
@ -383,20 +383,54 @@ const VECTOR2I GetArcCenter( const VECTOR2I& aStart, const VECTOR2I& aMid, const
bSlope = -std::numeric_limits<double>::epsilon(); bSlope = -std::numeric_limits<double>::epsilon();
double result = ( aSlope * bSlope * ( aStart.y - aEnd.y ) + center.x = ( aSlope * bSlope * ( aStart.y - aEnd.y ) +
bSlope * ( aStart.x + aMid.x ) - bSlope * ( aStart.x + aMid.x ) -
aSlope * ( aMid.x + aEnd.x ) ) / ( 2 * ( bSlope - aSlope ) ); aSlope * ( aMid.x + aEnd.x ) ) / ( 2 * ( bSlope - aSlope ) );
center.x = KiROUND( Clamp<double>( double( std::numeric_limits<int>::min() / 2.0 ), center.y = ( ( ( aStart.x + aMid.x ) / 2.0 - center.x ) / aSlope +
result,
double( std::numeric_limits<int>::max() / 2.0 ) ) );
result = ( ( ( aStart.x + aMid.x ) / 2.0 - center.x ) / aSlope +
( aStart.y + aMid.y ) / 2.0 ); ( aStart.y + aMid.y ) / 2.0 );
center.y = KiROUND( Clamp<double>( double( std::numeric_limits<int>::min() / 2.0 ),
result,
double( std::numeric_limits<int>::max() / 2.0 ) ) );
return center; return center;
} }
const VECTOR2I GetArcCenter( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
{
VECTOR2D dStart( static_cast<double>( aStart.x ), static_cast<double>( aStart.y ) );
VECTOR2D dMid( static_cast<double>( aMid.x ), static_cast<double>( aMid.y ) );
VECTOR2D dEnd( static_cast<double>( aEnd.x ), static_cast<double>( aEnd.y ) );
VECTOR2D dCenter = GetArcCenter( dStart, dMid, dEnd );
VECTOR2I iCenter;
iCenter.x = KiROUND( Clamp<double>( double( std::numeric_limits<int>::min() / 2.0 ),
dCenter.x,
double( std::numeric_limits<int>::max() / 2.0 ) ) );
iCenter.y = KiROUND( Clamp<double>( double( std::numeric_limits<int>::min() / 2.0 ),
dCenter.y,
double( std::numeric_limits<int>::max() / 2.0 ) ) );
return iCenter;
}
const wxPoint GetArcCenter( const wxPoint& aStart, const wxPoint& aMid, const wxPoint& aEnd )
{
VECTOR2D dStart( static_cast<double>( aStart.x ), static_cast<double>( aStart.y ) );
VECTOR2D dMid( static_cast<double>( aMid.x ), static_cast<double>( aMid.y ) );
VECTOR2D dEnd( static_cast<double>( aEnd.x ), static_cast<double>( aEnd.y ) );
VECTOR2D dCenter = GetArcCenter( dStart, dMid, dEnd );
wxPoint iCenter;
iCenter.x = KiROUND( Clamp<double>( double( std::numeric_limits<int>::min() / 2.0 ),
dCenter.x,
double( std::numeric_limits<int>::max() / 2.0 ) ) );
iCenter.y = KiROUND( Clamp<double>( double( std::numeric_limits<int>::min() / 2.0 ),
dCenter.y,
double( std::numeric_limits<int>::max() / 2.0 ) ) );
return iCenter;
}