diff --git a/common/base_units.cpp b/common/base_units.cpp index 0c42fd4a82..318cb9fe48 100644 --- a/common/base_units.cpp +++ b/common/base_units.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2012 CERN - * Copyright (C) 1992-2018 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 1992-2020 KiCad Developers, see change_log.txt for contributors. * * * This program is free software; you can redistribute it and/or @@ -181,7 +181,7 @@ wxString MessageTextFromValue( EDA_UNITS aUnits, double aValue, bool aUseMils, E else { #if defined( EESCHEMA ) - format = wxT( "%.2f" ); + format = wxT( "%.4f" ); #else format = wxT( "%.3f" ); #endif @@ -526,9 +526,7 @@ std::string FormatInternalUnits( int aValue ) double engUnits = aValue; int len; -#ifndef EESCHEMA engUnits /= IU_PER_MM; -#endif if( engUnits != 0.0 && fabs( engUnits ) <= 0.0001 ) { @@ -537,11 +535,9 @@ std::string FormatInternalUnits( int aValue ) while( --len > 0 && buf[len] == '0' ) buf[len] = '\0'; -#ifndef EESCHEMA if( buf[len] == '.' ) buf[len] = '\0'; else -#endif ++len; } else diff --git a/common/wildcards_and_files_ext.cpp b/common/wildcards_and_files_ext.cpp index 8ccd3c9c4a..71444ff26c 100644 --- a/common/wildcards_and_files_ext.cpp +++ b/common/wildcards_and_files_ext.cpp @@ -3,7 +3,7 @@ * * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2008 Wayne Stambaugh - * Copyright (C) 1992-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2020 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 @@ -114,6 +114,7 @@ wxString AddFileExtListToFilter( const std::vector& aExts ) } +const std::string KiCadSymbolLibFileExtension( "kicad_sym" ); const std::string SchematicSymbolFileExtension( "sym" ); const std::string SchematicLibraryFileExtension( "lib" ); @@ -172,9 +173,15 @@ wxString SchematicSymbolFileWildcard() } +wxString KiCadSymbolLibFileWildcard() +{ + return _( "KiCad symbol library files" ) + AddFileExtListToFilter( { "kicad_sym" } ); +} + + wxString SchematicLibraryFileWildcard() { - return _( "KiCad symbol library files" ) + AddFileExtListToFilter( { "lib" } ); + return _( "KiCad legacy symbol library files" ) + AddFileExtListToFilter( { "lib" } ); } diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index 00eb51870e..69ef67d594 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_plugin.cpp sch_sheet.cpp sch_sheet_path.cpp sch_sheet_pin.cpp @@ -476,4 +477,12 @@ make_lexer( T_BOMCFG_T ) +make_lexer( + eeschema_kiface_objects + symbol_lib.keywords + symbol_lib_lexer.h + symbol_lib_keywords.cpp + TSYMBOL_LIB_T + ) + add_subdirectory( plugins ) diff --git a/eeschema/class_libentry.cpp b/eeschema/class_libentry.cpp index 5b3189712c..df1841a177 100644 --- a/eeschema/class_libentry.cpp +++ b/eeschema/class_libentry.cpp @@ -90,7 +90,7 @@ LIB_PART::LIB_PART( const wxString& aName, LIB_PART* aParent, PART_LIB* aLibrary { m_dateLastEdition = 0; m_unitCount = 1; - m_pinNameOffset = Mils2iu( 40 ); + m_pinNameOffset = Mils2iu( DEFAULT_PIN_NAME_OFFSET ); m_options = ENTRY_NORMAL; m_unitsLocked = false; m_showPinNumbers = true; @@ -1123,3 +1123,161 @@ void LIB_PART::SetSubpartIdNotation( int aSep, int aFirstId ) if( aFirstId == '1' && aSep != 0 ) m_subpartFirstId = aFirstId; } + + +std::vector LIB_PART::GetUnitItems( int aUnit, int aConvert ) +{ + std::vector unitItems; + + for( LIB_ITEM& item : m_drawings ) + { + if( item.Type() == LIB_FIELD_T ) + continue; + + if( ( aConvert == -1 && item.GetUnit() == aUnit ) + || ( aUnit == -1 && item.GetConvert() == aConvert ) + || ( aUnit == item.GetUnit() && aConvert == item.GetConvert() ) ) + unitItems.push_back( &item ); + } + + return unitItems; +} + + +std::vector LIB_PART::GetUnitDrawItems() +{ + std::vector units; + + for( LIB_ITEM& item : m_drawings ) + { + if( item.Type() == LIB_FIELD_T ) + continue; + + int unit = item.GetUnit(); + int convert = item.GetConvert(); + + auto it = std::find_if( units.begin(), units.end(), + [unit, convert] ( const auto& a ) { + return a.m_unit == unit && a.m_convert == convert; + } ); + + if( it == units.end() ) + { + struct PART_UNITS newUnit; + newUnit.m_unit = item.GetUnit(); + newUnit.m_convert = item.GetConvert(); + newUnit.m_items.push_back( &item ); + units.emplace_back( newUnit ); + } + else + { + it->m_items.push_back( &item ); + } + } + + return units; +} + + +std::vector LIB_PART::GetUniqueUnits() +{ + int unitNum; + size_t i; + struct PART_UNITS unit; + std::vector compareDrawItems; + std::vector currentDrawItems; + std::vector uniqueUnits; + + // The first unit is guarenteed to be unique so always include it. + unit.m_unit = 1; + unit.m_convert = 1; + unit.m_items = GetUnitItems( 1, 1 ); + + // There are no unique units if there are no draw items other than fields. + if( unit.m_items.size() == 0 ) + return uniqueUnits; + + uniqueUnits.emplace_back( unit ); + + if( ( GetUnitCount() == 1 || UnitsLocked() ) && !HasConversion() ) + return uniqueUnits; + + currentDrawItems = unit.m_items; + + for( unitNum = 2; unitNum <= GetUnitCount(); unitNum++ ) + { + compareDrawItems = GetUnitItems( unitNum, 1 ); + + wxCHECK2_MSG( compareDrawItems.size() != 0, continue, + "Multiple unit symbol defined with empty units." ); + + if( currentDrawItems.size() != compareDrawItems.size() ) + { + unit.m_unit = unitNum; + unit.m_convert = 1; + unit.m_items = compareDrawItems; + uniqueUnits.emplace_back( unit ); + } + else + { + for( i = 0; i < currentDrawItems.size(); i++ ) + { + if( currentDrawItems[i]->compare( *compareDrawItems[i], + LIB_ITEM::COMPARE_FLAGS::UNIT ) != 0 ) + { + unit.m_unit = unitNum; + unit.m_convert = 1; + unit.m_items = compareDrawItems; + uniqueUnits.emplace_back( unit ); + } + } + } + } + + if( HasConversion() ) + { + currentDrawItems = GetUnitItems( 1, 2 ); + + if( ( GetUnitCount() == 1 || UnitsLocked() ) ) + { + unit.m_unit = 1; + unit.m_convert = 2; + unit.m_items = currentDrawItems; + uniqueUnits.emplace_back( unit ); + + return uniqueUnits; + } + + for( unitNum = 2; unitNum <= GetUnitCount(); unitNum++ ) + { + compareDrawItems = GetUnitItems( unitNum, 2 ); + + wxCHECK2_MSG( compareDrawItems.size() != 0, continue, + "Multiple unit symbol defined with empty units." ); + + if( currentDrawItems.size() != compareDrawItems.size() ) + { + unit.m_unit = unitNum; + unit.m_convert = 2; + unit.m_items = compareDrawItems; + uniqueUnits.emplace_back( unit ); + } + else + { + for( i = 0; i < currentDrawItems.size(); i++ ) + { + if( currentDrawItems[i]->compare( *compareDrawItems[i], + LIB_ITEM::COMPARE_FLAGS::UNIT ) != 0 ) + { + unit.m_unit = unitNum; + unit.m_convert = 2; + unit.m_items = compareDrawItems; + uniqueUnits.emplace_back( unit ); + } + } + } + } + } + + return uniqueUnits; +} diff --git a/eeschema/class_libentry.h b/eeschema/class_libentry.h index 238269f618..12af55b440 100644 --- a/eeschema/class_libentry.h +++ b/eeschema/class_libentry.h @@ -3,7 +3,7 @@ * * Copyright (C) 2004-2015 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2008 Wayne Stambaugh - * Copyright (C) 2004-2019 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2004-2020 KiCad Developers, see change_log.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 @@ -82,6 +82,14 @@ struct PART_DRAW_OPTIONS }; +struct PART_UNITS +{ + int m_unit; ///< The unit number. + int m_convert; ///< The alternate body style of the unit. + std::vector m_items; ///< The items unique to this unit and alternate body style. +}; + + /** * Define a library symbol object. * @@ -584,6 +592,36 @@ public: */ std::unique_ptr< LIB_PART > Flatten() const; + /** + * Return a list of LIB_ITEM objects separated by unit and convert number. + * + * @note This does not include LIB_FIELD objects since they are not associated with + * unit and/or convert numbers. + */ + std::vector GetUnitDrawItems(); + + /** + * Return a list of unit numbers that are unique to this symbol. + * + * If the symbol is inherited (alias), the unique units of the parent symbol are returned. + * When comparing pins, the pin number is ignored. + * + * @return a list of unique unit numbers and their associated draw items. + */ + std::vector GetUniqueUnits(); + + /** + * Return a list of item pointers for \a aUnit and \a aConvert for this symbol. + * + * @note #LIB_FIELD objects are not included. + * + * @param aUnit is the unit number of the item, -1 includes all units. + * @param aConvert is the alternate body styple of the item, -1 includes all body styles. + * + * @return a list of unit items. + */ + std::vector GetUnitItems( int aUnit, int aConvert ); + #if defined(DEBUG) void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); } #endif diff --git a/eeschema/lib_arc.cpp b/eeschema/lib_arc.cpp index f4fa305621..62d14e80f8 100644 --- a/eeschema/lib_arc.cpp +++ b/eeschema/lib_arc.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr - * Copyright (C) 2004-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2004-2020 KiCad Developers, see AUTHORS.txt for contributors. * Copyright (C) 2019 CERN * * This program is free software; you can redistribute it and/or @@ -74,7 +74,7 @@ bool LIB_ARC::HitTest( const wxPoint& aRefPoint, int aAccuracy ) const // We are on the circle, ensure we are only on the arc, i.e. between // m_ArcStart and m_ArcEnd - wxPoint startEndVector = twoPointVector( m_ArcStart, m_ArcEnd); + wxPoint startEndVector = twoPointVector( m_ArcStart, m_ArcEnd ); wxPoint startRelativePositionVector = twoPointVector( m_ArcStart, relativePosition ); wxPoint centerStartVector = twoPointVector( m_Pos, m_ArcStart ); @@ -132,10 +132,15 @@ EDA_ITEM* LIB_ARC::Clone() const } -int LIB_ARC::compare( const LIB_ITEM& aOther ) const +int LIB_ARC::compare( const LIB_ITEM& aOther, LIB_ITEM::COMPARE_FLAGS aCompareFlags ) const { wxASSERT( aOther.Type() == LIB_ARC_T ); + int retv = LIB_ITEM::compare( aOther ); + + if( retv ) + return retv; + const LIB_ARC* tmp = ( LIB_ARC* ) &aOther; if( m_Pos.x != tmp->m_Pos.x ) diff --git a/eeschema/lib_arc.h b/eeschema/lib_arc.h index 10ce460536..664c2cec77 100644 --- a/eeschema/lib_arc.h +++ b/eeschema/lib_arc.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2004 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com - * Copyright (C) 2004-2019 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2004-2020 KiCad Developers, see change_log.txt for contributors. * Copyright (C) 2019 CERN * * This program is free software; you can redistribute it and/or @@ -139,7 +139,8 @@ private: * - Arc start angle. * - Arc end angle. */ - int compare( const LIB_ITEM& aOther ) const override; + int compare( const LIB_ITEM& aOther, + LIB_ITEM::COMPARE_FLAGS aCompareFlags = LIB_ITEM::COMPARE_FLAGS::NORMAL ) const override; }; diff --git a/eeschema/lib_bezier.cpp b/eeschema/lib_bezier.cpp index 0c2ee97946..8e9e81d22b 100644 --- a/eeschema/lib_bezier.cpp +++ b/eeschema/lib_bezier.cpp @@ -1,7 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 2004-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2004-2020 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 @@ -51,10 +51,15 @@ EDA_ITEM* LIB_BEZIER::Clone() const } -int LIB_BEZIER::compare( const LIB_ITEM& aOther ) const +int LIB_BEZIER::compare( const LIB_ITEM& aOther, LIB_ITEM::COMPARE_FLAGS aCompareFlags ) const { wxASSERT( aOther.Type() == LIB_BEZIER_T ); + int retv = LIB_ITEM::compare( aOther ); + + if( retv ) + return retv; + const LIB_BEZIER* tmp = ( LIB_BEZIER* ) &aOther; if( m_BezierPoints.size() != tmp->m_BezierPoints.size() ) diff --git a/eeschema/lib_bezier.h b/eeschema/lib_bezier.h index 290f8e8c35..f328f60649 100644 --- a/eeschema/lib_bezier.h +++ b/eeschema/lib_bezier.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2004 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com - * Copyright (C) 2004-2019 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2004-2020 KiCad Developers, see change_log.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 @@ -29,8 +29,7 @@ /** - * LIB_BEZIER - * defines bezier curve graphic body item. + * Define a bezier curve graphic body item. */ class LIB_BEZIER : public LIB_ITEM { @@ -80,7 +79,7 @@ public: void MoveTo( const wxPoint& aPosition ) override; - wxPoint GetPosition() const override; + wxPoint GetPosition() const override; void MirrorHorizontal( const wxPoint& aCenter ) override; void MirrorVertical( const wxPoint& aCenter ) override; @@ -107,7 +106,8 @@ private: * - Bezier horizontal (X) point position. * - Bezier vertical (Y) point position. */ - int compare( const LIB_ITEM& aOther ) const override; + int compare( const LIB_ITEM& aOther, + LIB_ITEM::COMPARE_FLAGS aCompareFlags = LIB_ITEM::COMPARE_FLAGS::NORMAL ) const override; }; diff --git a/eeschema/lib_circle.cpp b/eeschema/lib_circle.cpp index 25a91023e5..cae301a324 100644 --- a/eeschema/lib_circle.cpp +++ b/eeschema/lib_circle.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr - * Copyright (C) 2004-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2004-2020 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 @@ -90,10 +90,15 @@ EDA_ITEM* LIB_CIRCLE::Clone() const } -int LIB_CIRCLE::compare( const LIB_ITEM& aOther ) const +int LIB_CIRCLE::compare( const LIB_ITEM& aOther, LIB_ITEM::COMPARE_FLAGS aCompareFlags ) const { wxASSERT( aOther.Type() == LIB_CIRCLE_T ); + int retv = LIB_ITEM::compare( aOther, aCompareFlags ); + + if( retv ) + return retv; + const LIB_CIRCLE* tmp = ( LIB_CIRCLE* ) &aOther; if( m_Pos.x != tmp->m_Pos.x ) diff --git a/eeschema/lib_circle.h b/eeschema/lib_circle.h index 653fa76ed8..8deb833a03 100644 --- a/eeschema/lib_circle.h +++ b/eeschema/lib_circle.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2004 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com - * Copyright (C) 2004-2019 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2004-2020 KiCad Developers, see change_log.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 @@ -107,7 +107,8 @@ private: * - Circle vertical (Y) position. * - Circle radius. */ - int compare( const LIB_ITEM& aOther ) const override; + int compare( const LIB_ITEM& aOther, + LIB_ITEM::COMPARE_FLAGS aCompareFlags = LIB_ITEM::COMPARE_FLAGS::NORMAL ) const override; }; diff --git a/eeschema/lib_field.cpp b/eeschema/lib_field.cpp index e14eb0add3..c2fac92953 100644 --- a/eeschema/lib_field.cpp +++ b/eeschema/lib_field.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr - * Copyright (C) 2004-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2004-2020 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 @@ -55,7 +55,7 @@ LIB_FIELD::LIB_FIELD( int idfield ) : } -LIB_FIELD::LIB_FIELD( int aID, wxString& aName ) : +LIB_FIELD::LIB_FIELD( int aID, const wxString& aName ) : LIB_ITEM( LIB_FIELD_T, NULL ) { Init( aID ); @@ -184,11 +184,16 @@ void LIB_FIELD::Copy( LIB_FIELD* aTarget ) const } -int LIB_FIELD::compare( const LIB_ITEM& other ) const +int LIB_FIELD::compare( const LIB_ITEM& aOther, LIB_ITEM::COMPARE_FLAGS aCompareFlags ) const { - wxASSERT( other.Type() == LIB_FIELD_T ); + wxASSERT( aOther.Type() == LIB_FIELD_T ); - const LIB_FIELD* tmp = ( LIB_FIELD* ) &other; + int retv = LIB_ITEM::compare( aOther, aCompareFlags ); + + if( retv ) + return retv; + + const LIB_FIELD* tmp = ( LIB_FIELD* ) &aOther; if( m_id != tmp->m_id ) return m_id - tmp->m_id; @@ -449,3 +454,9 @@ BITMAP_DEF LIB_FIELD::GetMenuImage() const { return move_xpm; } + + +bool LIB_FIELD::IsMandatory() const +{ + return m_id < MANDATORY_FIELDS; +} diff --git a/eeschema/lib_field.h b/eeschema/lib_field.h index 58a8656ea7..eb6acdcada 100644 --- a/eeschema/lib_field.h +++ b/eeschema/lib_field.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr - * Copyright (C) 2004-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2004-2020 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 @@ -84,7 +84,7 @@ public: LIB_FIELD( int idfield = 2 ); - LIB_FIELD( int aID, wxString& aName ); + LIB_FIELD( int aID, const wxString& aName ); LIB_FIELD( LIB_PART * aParent, int idfield = 2 ); @@ -198,6 +198,8 @@ public: EDA_ITEM* Clone() const override; + bool IsMandatory() const; + private: /** @@ -212,7 +214,8 @@ private: * - Field width. * - Field height. */ - int compare( const LIB_ITEM& aOther ) const override; + int compare( const LIB_ITEM& aOther, + LIB_ITEM::COMPARE_FLAGS aCompareFlags = LIB_ITEM::COMPARE_FLAGS::NORMAL ) const override; }; typedef std::vector< LIB_FIELD > LIB_FIELDS; diff --git a/eeschema/lib_item.cpp b/eeschema/lib_item.cpp index ee0297c4c3..9db4020cd6 100644 --- a/eeschema/lib_item.cpp +++ b/eeschema/lib_item.cpp @@ -73,12 +73,25 @@ void LIB_ITEM::GetMsgPanelInfo( EDA_UNITS aUnits, MSG_PANEL_ITEMS& aList ) } +int LIB_ITEM::compare( const LIB_ITEM& aOther, LIB_ITEM::COMPARE_FLAGS aCompareFlags ) const +{ + if( Type() != aOther.Type() ) + return Type() - aOther.Type(); + + // When comparing unit LIB_ITEM objects, we ignore the unit number. + if( !( aCompareFlags & COMPARE_FLAGS::UNIT ) && m_Unit != aOther.m_Unit ) + return m_Unit - aOther.m_Unit; + + if( !( aCompareFlags & COMPARE_FLAGS::UNIT ) && m_Convert != aOther.m_Convert ) + return m_Convert - m_Convert; + + return 0; +} + + bool LIB_ITEM::operator==( const LIB_ITEM& aOther ) const { - return ( ( Type() == aOther.Type() ) - && ( m_Unit == aOther.m_Unit ) - && ( m_Convert == aOther.m_Convert ) - && compare( aOther ) == 0 ); + return compare( aOther ) == 0; } diff --git a/eeschema/lib_item.h b/eeschema/lib_item.h index 348316244d..4ae95633a6 100644 --- a/eeschema/lib_item.h +++ b/eeschema/lib_item.h @@ -3,7 +3,7 @@ * * Copyright (C) 2015 Jean-Pierre Charras, jaen-pierre.charras at wanadoo.fr * Copyright (C) 2015 Wayne Stambaugh - * Copyright (C) 2004-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2004-2020 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 @@ -106,6 +106,15 @@ public: // Define the enums for basic enum LIB_CONVERT : int { BASE = 1, DEMORGAN = 2 }; + /** + * The list of flags used by the #compare function. + * + * - NORMAL This compares everthing between two #LIB_ITEM objects. + * - UNIT This compare flag ignores unit and convert and pin number information when + * comparing #LIB_ITEM objects for unit comparison. + */ + enum COMPARE_FLAGS : int { NORMAL = 0x00, UNIT = 0x01 }; + /** * Provide a user-consumable name of the object type. Perform localization when * called so that run-time language selection works. @@ -309,7 +318,7 @@ public: void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); } #endif -private: +protected: /** * Provide the draw object specific comparison called by the == and < operators. @@ -321,12 +330,18 @@ private: * - KICAD_T enum value. * - Result of derived classes comparison. * + * @note Make sure you call down to #LIB_ITEM::compare before doing any derived object + * comparisons or you will break the sorting using the symbol library file format. + * * @param aOther A reference to the other #LIB_ITEM to compare the arc against. + * @param aCompareFlags The flags used to perform the comparison. + * * @return An integer value less than 0 if the object is less than \a aOther ojbect, * zero if the object is equal to \a aOther object, or greater than 0 if the * object is greater than \a aOther object. */ - virtual int compare( const LIB_ITEM& aOther ) const = 0; + virtual int compare( const LIB_ITEM& aOther, + LIB_ITEM::COMPARE_FLAGS aCompareFlags = LIB_ITEM::COMPARE_FLAGS::NORMAL ) const; }; diff --git a/eeschema/lib_pin.cpp b/eeschema/lib_pin.cpp index 9acde7dff7..ed5476c846 100644 --- a/eeschema/lib_pin.cpp +++ b/eeschema/lib_pin.cpp @@ -1237,18 +1237,25 @@ EDA_ITEM* LIB_PIN::Clone() const } -int LIB_PIN::compare( const LIB_ITEM& other ) const +int LIB_PIN::compare( const LIB_ITEM& aOther, LIB_ITEM::COMPARE_FLAGS aCompareFlags ) const { - wxASSERT( other.Type() == LIB_PIN_T ); + wxASSERT( aOther.Type() == LIB_PIN_T ); - const LIB_PIN* tmp = (LIB_PIN*) &other; + int retv = LIB_ITEM::compare( aOther, aCompareFlags ); - if( m_number != tmp->m_number ) + if( retv ) + return retv; + + const LIB_PIN* tmp = (LIB_PIN*) &aOther; + + // When comparing units, we do not compare the part numbers. If everything else is + // identical, then we can just renumber the parts for the inherited symbol. + if( !( aCompareFlags & COMPARE_FLAGS::UNIT ) && m_number != tmp->m_number ) return m_number.Cmp( tmp->m_number ); int result = m_name.CmpNoCase( tmp->m_name ); - if( result != 0 ) + if( result ) return result; if( m_position.x != tmp->m_position.x ) @@ -1257,6 +1264,30 @@ int LIB_PIN::compare( const LIB_ITEM& other ) const if( m_position.y != tmp->m_position.y ) return m_position.y - tmp->m_position.y; + if( m_length != tmp->m_length ) + return m_length - tmp->m_length; + + if( m_orientation != tmp->m_orientation ) + return m_orientation - tmp->m_orientation; + + if( m_shape != tmp->m_shape ) + return static_cast( m_shape ) - static_cast( tmp->m_shape ); + + if( m_type != tmp->m_type ) + return static_cast( m_type ) - static_cast( tmp->m_type ); + + if( m_attributes != tmp->m_attributes ) + return m_attributes - tmp->m_attributes; + + if( m_width != tmp->m_width ) + return m_width - tmp->m_width; + + if( m_numTextSize != tmp->m_numTextSize ) + return m_numTextSize - tmp->m_numTextSize; + + if( m_nameTextSize != tmp->m_nameTextSize ) + return m_nameTextSize - tmp->m_nameTextSize; + return 0; } diff --git a/eeschema/lib_pin.h b/eeschema/lib_pin.h index 7ef1b7d139..7e70f8621d 100644 --- a/eeschema/lib_pin.h +++ b/eeschema/lib_pin.h @@ -3,7 +3,7 @@ * * Copyright (C) 2015 Jean-Pierre Charras, jaen-pierre.charras at wanadoo.fr * Copyright (C) 2015 Wayne Stambaugh - * Copyright (C) 2004-2019 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2004-2020 KiCad Developers, see change_log.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 @@ -39,6 +39,9 @@ class SCH_COMPONENT; #include "pin_type.h" #include "class_libentry.h" +/// The offset of the pin name string from the end of the pin in mils. +#define DEFAULT_PIN_NAME_OFFSET 40 + // Circle diameter drawn at the active end of pins: #define TARGET_PIN_RADIUS Mils2iu( 15 ) @@ -472,7 +475,8 @@ private: * - Pin horizontal (X) position. * - Pin vertical (Y) position. */ - int compare( const LIB_ITEM& aOther ) const override; + int compare( const LIB_ITEM& aOther, + LIB_ITEM::COMPARE_FLAGS aCompareFlags = LIB_ITEM::COMPARE_FLAGS::NORMAL ) const override; }; diff --git a/eeschema/lib_polyline.cpp b/eeschema/lib_polyline.cpp index 6bc089b01e..93f9c8d198 100644 --- a/eeschema/lib_polyline.cpp +++ b/eeschema/lib_polyline.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr - * Copyright (C) 2004-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2004-2020 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 @@ -52,10 +52,15 @@ EDA_ITEM* LIB_POLYLINE::Clone() const } -int LIB_POLYLINE::compare( const LIB_ITEM& aOther ) const +int LIB_POLYLINE::compare( const LIB_ITEM& aOther, LIB_ITEM::COMPARE_FLAGS aCompareFlags ) const { wxASSERT( aOther.Type() == LIB_POLYLINE_T ); + int retv = LIB_ITEM::compare( aOther ); + + if( retv ) + return retv; + const LIB_POLYLINE* tmp = (LIB_POLYLINE*) &aOther; if( m_PolyPoints.size() != tmp->m_PolyPoints.size() ) diff --git a/eeschema/lib_polyline.h b/eeschema/lib_polyline.h index ed6e45555b..dea4453011 100644 --- a/eeschema/lib_polyline.h +++ b/eeschema/lib_polyline.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2004 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com - * Copyright (C) 2004-2019 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2004-2020 KiCad Developers, see change_log.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 @@ -119,7 +119,8 @@ private: * - Line segment point horizontal (X) position. * - Line segment point vertical (Y) position. */ - int compare( const LIB_ITEM& aOther ) const override; + int compare( const LIB_ITEM& aOther, + LIB_ITEM::COMPARE_FLAGS aCompareFlags = LIB_ITEM::COMPARE_FLAGS::NORMAL ) const override; }; diff --git a/eeschema/lib_rectangle.cpp b/eeschema/lib_rectangle.cpp index 15c30e7ad4..b0fb774be5 100644 --- a/eeschema/lib_rectangle.cpp +++ b/eeschema/lib_rectangle.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr - * Copyright (C) 2004-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2004-2020 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 @@ -52,10 +52,15 @@ EDA_ITEM* LIB_RECTANGLE::Clone() const } -int LIB_RECTANGLE::compare( const LIB_ITEM& aOther ) const +int LIB_RECTANGLE::compare( const LIB_ITEM& aOther, LIB_ITEM::COMPARE_FLAGS aCompareFlags ) const { wxASSERT( aOther.Type() == LIB_RECTANGLE_T ); + int retv = LIB_ITEM::compare( aOther ); + + if( retv ) + return retv; + const LIB_RECTANGLE* tmp = ( LIB_RECTANGLE* ) &aOther; if( m_Pos.x != tmp->m_Pos.x ) diff --git a/eeschema/lib_rectangle.h b/eeschema/lib_rectangle.h index 93c8156b19..4ef345542b 100644 --- a/eeschema/lib_rectangle.h +++ b/eeschema/lib_rectangle.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2004 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com - * Copyright (C) 2004-2019 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2004-2020 KiCad Developers, see change_log.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 @@ -105,7 +105,8 @@ private: * - Rectangle horizontal (X) end position. * - Rectangle vertical (Y) end position. */ - int compare( const LIB_ITEM& aOther ) const override; + int compare( const LIB_ITEM& aOther, + LIB_ITEM::COMPARE_FLAGS aCompareFlags = LIB_ITEM::COMPARE_FLAGS::NORMAL ) const override; }; diff --git a/eeschema/lib_text.cpp b/eeschema/lib_text.cpp index bdea2c6466..7b16df8df1 100644 --- a/eeschema/lib_text.cpp +++ b/eeschema/lib_text.cpp @@ -1,7 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 2004-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2004-2020 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 @@ -89,11 +89,16 @@ EDA_ITEM* LIB_TEXT::Clone() const } -int LIB_TEXT::compare( const LIB_ITEM& other ) const +int LIB_TEXT::compare( const LIB_ITEM& aOther, LIB_ITEM::COMPARE_FLAGS aCompareFlags ) const { - wxASSERT( other.Type() == LIB_TEXT_T ); + wxASSERT( aOther.Type() == LIB_TEXT_T ); - const LIB_TEXT* tmp = ( LIB_TEXT* ) &other; + int retv = LIB_ITEM::compare( aOther, aCompareFlags ); + + if( retv ) + return retv; + + const LIB_TEXT* tmp = ( LIB_TEXT* ) &aOther; int result = GetText().CmpNoCase( tmp->GetText() ); diff --git a/eeschema/lib_text.h b/eeschema/lib_text.h index 92acc0c401..828d1b3824 100644 --- a/eeschema/lib_text.h +++ b/eeschema/lib_text.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2004 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com - * Copyright (C) 2004-2017 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2004-2020 KiCad Developers, see change_log.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 @@ -115,7 +115,8 @@ private: * - Text width. * - Text height. */ - int compare( const LIB_ITEM& aOther ) const override; + int compare( const LIB_ITEM& aOther, + LIB_ITEM::COMPARE_FLAGS aCompareFlags = LIB_ITEM::COMPARE_FLAGS::NORMAL ) const override; }; diff --git a/eeschema/libedit/lib_manager.cpp b/eeschema/libedit/lib_manager.cpp index 15b697760a..41054ec210 100644 --- a/eeschema/libedit/lib_manager.cpp +++ b/eeschema/libedit/lib_manager.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2017 CERN - * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2019-2020 KiCad Developers, see AUTHORS.txt for contributors. * @author Maciej Suminski * * This program is free software; you can redistribute it and/or @@ -31,8 +31,9 @@ #include #include #include -#include +#include #include +#include #include @@ -129,12 +130,13 @@ SYMBOL_LIB_TABLE_ROW* LIB_MANAGER::GetLibrary( const wxString& aLibrary ) const } -bool LIB_MANAGER::SaveLibrary( const wxString& aLibrary, const wxString& aFileName ) +bool LIB_MANAGER::SaveLibrary( const wxString& aLibrary, const wxString& aFileName, + SCH_IO_MGR::SCH_FILE_T aFileType ) { wxCHECK( LibraryExists( aLibrary ), false ); wxFileName fn( aFileName ); wxCHECK( !fn.FileExists() || fn.IsFileWritable(), false ); - SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) ); + SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( aFileType ) ); bool res = true; // assume all libraries are successfully saved auto it = m_libs.find( aLibrary ); diff --git a/eeschema/libedit/lib_manager.h b/eeschema/libedit/lib_manager.h index 3cdc11834f..fc58f61b4d 100644 --- a/eeschema/libedit/lib_manager.h +++ b/eeschema/libedit/lib_manager.h @@ -33,6 +33,7 @@ #include #include #include +#include #include class LIB_PART; @@ -235,7 +236,8 @@ public: * @param aFileName is the target file name. * @return True on success, false otherwise. */ - bool SaveLibrary( const wxString& aLibrary, const wxString& aFileName ); + bool SaveLibrary( const wxString& aLibrary, const wxString& aFileName, + SCH_IO_MGR::SCH_FILE_T aFileType = SCH_IO_MGR::SCH_FILE_T::SCH_LEGACY ); /** * Reverts unsaved changes for a particular part. diff --git a/eeschema/libedit/libedit.cpp b/eeschema/libedit/libedit.cpp index e7e5203a4e..29f7612432 100644 --- a/eeschema/libedit/libedit.cpp +++ b/eeschema/libedit/libedit.cpp @@ -3,7 +3,7 @@ * * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2008 Wayne Stambaugh - * Copyright (C) 2004-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2004-2020 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 @@ -789,6 +789,7 @@ bool LIB_EDIT_FRAME::saveLibrary( const wxString& aLibrary, bool aNewFile ) { wxFileName fn; wxString msg; + SCH_IO_MGR::SCH_FILE_T aFileType = SCH_IO_MGR::SCH_FILE_T::SCH_LEGACY; PROJECT& prj = Prj(); m_toolManager->RunAction( ACTIONS::cancelInteractive, true ); @@ -812,8 +813,11 @@ bool LIB_EDIT_FRAME::saveLibrary( const wxString& aLibrary, bool aNewFile ) fn.SetName( aLibrary ); fn.SetExt( SchematicLibraryFileExtension ); + wxString wildcards = SchematicLibraryFileWildcard(); + wildcards += "|" + KiCadSymbolLibFileWildcard(); + wxFileDialog dlg( this, wxString::Format( _( "Save Library \"%s\" As..." ), aLibrary ), - default_path, fn.GetFullName(), SchematicLibraryFileWildcard(), + default_path, fn.GetFullName(), wildcards, wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if( dlg.ShowModal() == wxID_CANCEL ) @@ -821,10 +825,16 @@ bool LIB_EDIT_FRAME::saveLibrary( const wxString& aLibrary, bool aNewFile ) fn = dlg.GetPath(); - // The GTK file chooser doesn't return the file extension added to - // file name so add it here. - if( fn.GetExt().IsEmpty() ) + // Update the file extension and plugin if a different library type was selected. + if( dlg.GetFilterIndex() == 0 ) + { fn.SetExt( SchematicLibraryFileExtension ); + } + else + { + fn.SetExt( KiCadSymbolLibFileExtension ); + aFileType = SCH_IO_MGR::SCH_FILE_T::SCH_KICAD; + } } else { @@ -848,7 +858,7 @@ bool LIB_EDIT_FRAME::saveLibrary( const wxString& aLibrary, bool aNewFile ) if( !backupFile( docFileName, "bck" ) ) return false; - if( !m_libMgr->SaveLibrary( aLibrary, fn.GetFullPath() ) ) + if( !m_libMgr->SaveLibrary( aLibrary, fn.GetFullPath(), aFileType ) ) { msg.Printf( _( "Failed to save changes to symbol library file \"%s\"" ), fn.GetFullPath() ); diff --git a/eeschema/sch_base_frame.cpp b/eeschema/sch_base_frame.cpp index c5abfc731f..f9a2cc81a8 100644 --- a/eeschema/sch_base_frame.cpp +++ b/eeschema/sch_base_frame.cpp @@ -196,8 +196,8 @@ void SCH_BASE_FRAME::UpdateStatusBar() break; case EDA_UNITS::MILLIMETRES: - absformatter = "X %.2f Y %.2f"; - locformatter = "dx %.2f dy %.2f dist %.2f"; + absformatter = "X %.4f Y %.4f"; + locformatter = "dx %.4f dy %.4f dist %.4f"; break; case EDA_UNITS::UNSCALED: diff --git a/eeschema/sch_io_mgr.cpp b/eeschema/sch_io_mgr.cpp index 32327e2b8f..e568e37475 100644 --- a/eeschema/sch_io_mgr.cpp +++ b/eeschema/sch_io_mgr.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2016 CERN - * Copyright (C) 2016-2017 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2016-2020 KiCad Developers, see change_log.txt for contributors. * * @author Wayne Stambaugh * @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -55,6 +56,8 @@ SCH_PLUGIN* SCH_IO_MGR::FindPlugin( SCH_FILE_T aFileType ) { case SCH_LEGACY: return new SCH_LEGACY_PLUGIN(); + case SCH_KICAD: + return new SCH_SEXPR_PLUGIN(); case SCH_EAGLE: return new SCH_EAGLE_PLUGIN(); } @@ -87,6 +90,9 @@ const wxString SCH_IO_MGR::ShowType( SCH_FILE_T aType ) case SCH_LEGACY: return wxString( wxT( "Legacy" ) ); + case SCH_KICAD: + return wxString( "KiCad" ); + case SCH_EAGLE: return wxString( wxT( "EAGLE" ) ); } @@ -101,6 +107,8 @@ SCH_IO_MGR::SCH_FILE_T SCH_IO_MGR::EnumFromStr( const wxString& aType ) if( aType == wxT( "Legacy" ) ) return SCH_LEGACY; + else if( aType == "KiCad" ) + return SCH_KICAD; else if( aType == wxT( "EAGLE" ) ) return SCH_EAGLE; @@ -134,6 +142,10 @@ SCH_IO_MGR::SCH_FILE_T SCH_IO_MGR::GuessPluginTypeFromLibPath( const wxString& a { ret = SCH_LEGACY; } + else if( fn.GetExt() == KiCadSymbolLibFileExtension ) + { + ret = SCH_KICAD; + } return ret; } diff --git a/eeschema/sch_io_mgr.h b/eeschema/sch_io_mgr.h index 0be6fa21b1..87c448c6e1 100644 --- a/eeschema/sch_io_mgr.h +++ b/eeschema/sch_io_mgr.h @@ -5,7 +5,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2016 CERN - * Copyright (C) 2016-2017 KiCad Developers, see CHANGELOG.TXT for contributors. + * Copyright (C) 2016-2020 KiCad Developers, see CHANGELOG.TXT for contributors. * * @author Wayne Stambaugh * @@ -53,7 +53,7 @@ public: DEFINE_ENUM_VECTOR( SCH_FILE_T, { SCH_LEGACY, ///< Legacy Eeschema file formats prior to s-expression. -// SCH_KICAD, ///< The s-expression version of the schematic file formats. + SCH_KICAD, ///< The s-expression version of the schematic file formats. SCH_EAGLE, ///< Autodesk Eagle file format // Add your schematic type here. diff --git a/eeschema/sch_sexpr_plugin.cpp b/eeschema/sch_sexpr_plugin.cpp new file mode 100644 index 0000000000..cea118aa78 --- /dev/null +++ b/eeschema/sch_sexpr_plugin.cpp @@ -0,0 +1,2025 @@ +/* + * 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 . + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for MAX_UNIT_COUNT_PER_PACKAGE definition +#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() ) + + +static const char* emptyString = ""; + +/** + * Fill token formatting helper. + */ +static void FormatFill( const LIB_ITEM* aItem, OUTPUTFORMATTER& aFormatter, int aNestLevel ) +{ + wxCHECK_RET( aItem && aItem->IsFillable() && aItem->GetFillMode() != NO_FILL, + "Invalid fill item." ); + + aFormatter.Print( aNestLevel, "(fill (type %s))", + aItem->GetFillMode() == FILLED_SHAPE ? "outline" : "background" ); +} + + +static const char* GetPinElectricalTypeToken( ELECTRICAL_PINTYPE aType ) +{ + switch( aType ) + { + case ELECTRICAL_PINTYPE::PT_INPUT: + return SYMBOL_LIB_LEXER::TokenName( T_input ); + + case ELECTRICAL_PINTYPE::PT_OUTPUT: + return SYMBOL_LIB_LEXER::TokenName( T_output ); + + case ELECTRICAL_PINTYPE::PT_BIDI: + return SYMBOL_LIB_LEXER::TokenName( T_bidirectional ); + + case ELECTRICAL_PINTYPE::PT_TRISTATE: + return SYMBOL_LIB_LEXER::TokenName( T_tri_state ); + + case ELECTRICAL_PINTYPE::PT_PASSIVE: + return SYMBOL_LIB_LEXER::TokenName( T_passive ); + + case ELECTRICAL_PINTYPE::PT_UNSPECIFIED: + return SYMBOL_LIB_LEXER::TokenName( T_unspecified ); + + case ELECTRICAL_PINTYPE::PT_POWER_IN: + return SYMBOL_LIB_LEXER::TokenName( T_power_in ); + + case ELECTRICAL_PINTYPE::PT_POWER_OUT: + return SYMBOL_LIB_LEXER::TokenName( T_power_out ); + + case ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR: + return SYMBOL_LIB_LEXER::TokenName( T_open_collector ); + + case ELECTRICAL_PINTYPE::PT_OPENEMITTER: + return SYMBOL_LIB_LEXER::TokenName( T_open_emitter ); + + case ELECTRICAL_PINTYPE::PT_NC: + return SYMBOL_LIB_LEXER::TokenName( T_unconnected ); + + default: + wxFAIL_MSG( "Missing symbol library pin connection type" ); + } + + return emptyString; +} + + +static const char* GetPinShapeToken( GRAPHIC_PINSHAPE aShape ) +{ + switch( aShape ) + { + case GRAPHIC_PINSHAPE::LINE: + return SYMBOL_LIB_LEXER::TokenName( T_line ); + + case GRAPHIC_PINSHAPE::INVERTED: + return SYMBOL_LIB_LEXER::TokenName( T_inverted ); + + case GRAPHIC_PINSHAPE::CLOCK: + return SYMBOL_LIB_LEXER::TokenName( T_clock ); + + case GRAPHIC_PINSHAPE::INVERTED_CLOCK: + return SYMBOL_LIB_LEXER::TokenName( T_inverted_clock ); + + case GRAPHIC_PINSHAPE::INPUT_LOW: + return SYMBOL_LIB_LEXER::TokenName( T_input_low ); + + case GRAPHIC_PINSHAPE::CLOCK_LOW: + return SYMBOL_LIB_LEXER::TokenName( T_clock_low ); + + case GRAPHIC_PINSHAPE::OUTPUT_LOW: + return SYMBOL_LIB_LEXER::TokenName( T_output_low ); + + case GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK: + return SYMBOL_LIB_LEXER::TokenName( T_edge_clock_high ); + + case GRAPHIC_PINSHAPE::NONLOGIC: + return SYMBOL_LIB_LEXER::TokenName( T_non_logic ); + + default: + wxFAIL_MSG( "Missing symbol library pin shape type" ); + } + + return emptyString; +} + + +static float GetPinAngle( int aOrientation ) +{ + switch( aOrientation ) + { + case PIN_RIGHT: + return 0.0; + + case PIN_LEFT: + return 180.0; + + case PIN_UP: + return 90.0; + + case PIN_DOWN: + return 270.0; + + default: + wxFAIL_MSG( "Missing symbol library pin orientation type" ); + + return 0.0; + } +} + + +/** + * A cache assistant for the part library portion of the #SCH_PLUGIN API, and only for the + * #SCH_SEXPR_PLUGIN, so therefore is private to this implementation file, i.e. not placed + * into a header. + */ +class SCH_SEXPR_PLUGIN_CACHE +{ + static int m_modHash; // Keep track of the modification status of the library. + + wxString m_fileName; // Absolute path and file name. + wxFileName m_libFileName; // Absolute path and file name is required here. + wxDateTime m_fileModTime; + LIB_PART_MAP m_symbols; // Map of names of #LIB_PART pointers. + bool m_isWritable; + bool m_isModified; + int m_versionMajor; + int m_versionMinor; + int m_libType; // Is this cache a component or symbol library. + + void loadHeader( FILE_LINE_READER& aReader ); + static void loadField( std::unique_ptr& aPart, LINE_READER& aReader ); + static void loadDrawEntries( std::unique_ptr& aPart, LINE_READER& aReader, + int aMajorVersion, int aMinorVersion ); + static void loadFootprintFilters( std::unique_ptr& aPart, + LINE_READER& aReader ); + static LIB_ARC* loadArc( std::unique_ptr& aPart, LINE_READER& aReader ); + static LIB_CIRCLE* loadCircle( std::unique_ptr& aPart, LINE_READER& aReader ); + static LIB_TEXT* loadText( std::unique_ptr& aPart, LINE_READER& aReader, + int aMajorVersion, int aMinorVersion ); + static LIB_RECTANGLE* loadRectangle( std::unique_ptr& aPart, + LINE_READER& aReader ); + static LIB_PIN* loadPin( std::unique_ptr& aPart, LINE_READER& aReader ); + static LIB_POLYLINE* loadPolyLine( std::unique_ptr& aPart, LINE_READER& aReader ); + static LIB_BEZIER* loadBezier( std::unique_ptr& aPart, LINE_READER& aReader ); + + static FILL_T parseFillMode( LINE_READER& aReader, const char* aLine, + const char** aOutput ); + LIB_PART* removeSymbol( LIB_PART* aAlias ); + + static void saveSymbolDrawItem( LIB_ITEM* aItem, OUTPUTFORMATTER& aFormatter, + int aNestLevel ); + static void saveArc( LIB_ARC* aArc, OUTPUTFORMATTER& aFormatter, int aNestLevel = 0 ); + static void saveBezier( LIB_BEZIER* aBezier, OUTPUTFORMATTER& aFormatter, + int aNestLevel = 0 ); + static void saveCircle( LIB_CIRCLE* aCircle, OUTPUTFORMATTER& aFormatter, + int aNestLevel = 0 ); + static void saveField( LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter, int aNestLevel = 0 ); + static void savePin( LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter, int aNestLevel = 0 ); + static void savePolyLine( LIB_POLYLINE* aPolyLine, OUTPUTFORMATTER& aFormatter, + int aNestLevel = 0 ); + static void saveRectangle( LIB_RECTANGLE* aRectangle, OUTPUTFORMATTER& aFormatter, + int aNestLevel = 0 ); + static void saveText( LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter, int aNestLevel = 0 ); + + static void saveDcmInfoAsFields( LIB_PART* aSymbol, OUTPUTFORMATTER& aFormatter, + int aNestLevel = 0, int aFirstId = MANDATORY_FIELDS ); + + friend SCH_SEXPR_PLUGIN; + +public: + SCH_SEXPR_PLUGIN_CACHE( const wxString& aLibraryPath ); + ~SCH_SEXPR_PLUGIN_CACHE(); + + int GetModifyHash() const { return m_modHash; } + + // Most all functions in this class throw IO_ERROR exceptions. There are no + // error codes nor user interface calls from here, nor in any SCH_PLUGIN objects. + // Catch these exceptions higher up please. + + /// Save the entire library to file m_libFileName; + void Save(); + + void Load(); + + void AddSymbol( const LIB_PART* aPart ); + + void DeleteSymbol( const wxString& aName ); + + // If m_libFileName is a symlink follow it to the real source file + wxFileName GetRealFile() const; + + wxDateTime GetLibModificationTime(); + + bool IsFile( const wxString& aFullPathAndFileName ) const; + + bool IsFileChanged() const; + + void SetModified( bool aModified = true ) { m_isModified = aModified; } + + wxString GetLogicalName() const { return m_libFileName.GetName(); } + + void SetFileName( const wxString& aFileName ) { m_libFileName = aFileName; } + + wxString GetFileName() const { return m_libFileName.GetFullPath(); } + + static LIB_PART* LoadPart( LINE_READER& aReader, int aMajorVersion, int aMinorVersion, + LIB_PART_MAP* aMap = nullptr ); + static void SaveSymbol( LIB_PART* aSymbol, OUTPUTFORMATTER& aFormatter, + int aNestLevel = 0, LIB_PART_MAP* aMap = nullptr ); +}; + + +SCH_SEXPR_PLUGIN::SCH_SEXPR_PLUGIN() +{ + init( NULL ); +} + + +SCH_SEXPR_PLUGIN::~SCH_SEXPR_PLUGIN() +{ + delete m_cache; +} + + +void SCH_SEXPR_PLUGIN::init( KIWAY* aKiway, const PROPERTIES* aProperties ) +{ + m_version = 0; + m_rootSheet = NULL; + m_props = aProperties; + m_kiway = aKiway; + m_cache = NULL; + m_out = NULL; +} + + +SCH_SHEET* SCH_SEXPR_PLUGIN::Load( const wxString& aFileName, KIWAY* aKiway, + SCH_SHEET* aAppendToMe, const PROPERTIES* aProperties ) +{ + wxASSERT( !aFileName || aKiway != NULL ); + + LOCALE_IO toggle; // toggles on, then off, the C locale. + SCH_SHEET* sheet; + + wxFileName fn = aFileName; + + // Unfortunately child sheet file names the legacy schematic file format are not fully + // qualified and are always appended to the project path. The aFileName attribute must + // always be an absolute path so the project path can be used for load child sheet files. + wxASSERT( fn.IsAbsolute() ); + + if( aAppendToMe ) + { + wxLogTrace( traceSchLegacyPlugin, "Append \"%s\" to sheet \"%s\".", + aFileName, aAppendToMe->GetFileName() ); + + wxFileName normedFn = aAppendToMe->GetFileName(); + + if( !normedFn.IsAbsolute() ) + { + if( aFileName.Right( normedFn.GetFullPath().Length() ) == normedFn.GetFullPath() ) + m_path = aFileName.Left( aFileName.Length() - normedFn.GetFullPath().Length() ); + } + + if( m_path.IsEmpty() ) + m_path = aKiway->Prj().GetProjectPath(); + + wxLogTrace( traceSchLegacyPlugin, "Normalized append path \"%s\".", m_path ); + } + else + { + m_path = aKiway->Prj().GetProjectPath(); + } + + m_currentPath.push( m_path ); + init( aKiway, aProperties ); + + if( aAppendToMe == NULL ) + { + // Clean up any allocated memory if an exception occurs loading the schematic. + std::unique_ptr< SCH_SHEET > newSheet( new SCH_SHEET ); + newSheet->SetFileName( aFileName ); + m_rootSheet = newSheet.get(); + loadHierarchy( newSheet.get() ); + + // If we got here, the schematic loaded successfully. + sheet = newSheet.release(); + } + else + { + m_rootSheet = aAppendToMe->GetRootSheet(); + wxASSERT( m_rootSheet != NULL ); + sheet = aAppendToMe; + loadHierarchy( sheet ); + } + + wxASSERT( m_currentPath.size() == 1 ); // only the project path should remain + + return sheet; +} + + +// Everything below this comment is recursive. Modify with care. + +void SCH_SEXPR_PLUGIN::loadHierarchy( SCH_SHEET* aSheet ) +{ + SCH_SCREEN* screen = NULL; + + if( !aSheet->GetScreen() ) + { + // SCH_SCREEN objects store the full path and file name where the SCH_SHEET object only + // stores the file name and extension. Add the project path to the file name and + // extension to compare when calling SCH_SHEET::SearchHierarchy(). + wxFileName fileName = aSheet->GetFileName(); + + if( !fileName.IsAbsolute() ) + fileName.MakeAbsolute( m_currentPath.top() ); + + // Save the current path so that it gets restored when decending and ascending the + // sheet hierarchy which allows for sheet schematic files to be nested in folders + // relative to the last path a schematic was loaded from. + wxLogTrace( traceSchLegacyPlugin, "Saving path \"%s\"", m_currentPath.top() ); + m_currentPath.push( fileName.GetPath() ); + wxLogTrace( traceSchLegacyPlugin, "Current path \"%s\"", m_currentPath.top() ); + wxLogTrace( traceSchLegacyPlugin, "Loading \"%s\"", fileName.GetFullPath() ); + + m_rootSheet->SearchHierarchy( fileName.GetFullPath(), &screen ); + + if( screen ) + { + aSheet->SetScreen( screen ); + + // Do not need to load the sub-sheets - this has already been done. + } + else + { + aSheet->SetScreen( new SCH_SCREEN( m_kiway ) ); + aSheet->GetScreen()->SetFileName( fileName.GetFullPath() ); + + try + { + loadFile( fileName.GetFullPath(), aSheet->GetScreen() ); + + for( auto aItem : aSheet->GetScreen()->Items().OfType( SCH_SHEET_T ) ) + { + assert( aItem->Type() == SCH_SHEET_T ); + auto sheet = static_cast( aItem ); + + // Set the parent to aSheet. This effectively creates a method to find + // the root sheet from any sheet so a pointer to the root sheet does not + // need to be stored globally. Note: this is not the same as a hierarchy. + // Complex hierarchies can have multiple copies of a sheet. This only + // provides a simple tree to find the root sheet. + sheet->SetParent( aSheet ); + + // Recursion starts here. + loadHierarchy( sheet ); + } + } + catch( const IO_ERROR& ioe ) + { + // If there is a problem loading the root sheet, there is no recovery. + if( aSheet == m_rootSheet ) + throw( ioe ); + + // For all subsheets, queue up the error message for the caller. + if( !m_error.IsEmpty() ) + m_error += "\n"; + + m_error += ioe.What(); + } + } + + m_currentPath.pop(); + wxLogTrace( traceSchLegacyPlugin, "Restoring path \"%s\"", m_currentPath.top() ); + } +} + + +void SCH_SEXPR_PLUGIN::loadFile( const wxString& aFileName, SCH_SCREEN* aScreen ) +{ + FILE_LINE_READER reader( aFileName ); + + loadHeader( reader, aScreen ); + + LoadContent( reader, aScreen, m_version ); +} + + +void SCH_SEXPR_PLUGIN::LoadContent( LINE_READER& aReader, SCH_SCREEN* aScreen, int version ) +{ + m_version = version; +} + + +void SCH_SEXPR_PLUGIN::loadHeader( LINE_READER& aReader, SCH_SCREEN* aScreen ) +{ + const char* line = aReader.ReadLine(); +} + + +void SCH_SEXPR_PLUGIN::loadPageSettings( LINE_READER& aReader, SCH_SCREEN* aScreen ) +{ + wxASSERT( aScreen != NULL ); + + wxString buf; + const char* line = aReader.Line(); + + PAGE_INFO pageInfo; + TITLE_BLOCK tb; + +} + + +SCH_SHEET* SCH_SEXPR_PLUGIN::loadSheet( LINE_READER& aReader ) +{ + std::unique_ptr< SCH_SHEET > sheet( new SCH_SHEET() ); + + sheet->SetTimeStamp( GetNewTimeStamp() ); + + + return sheet.release(); +} + + +SCH_BITMAP* SCH_SEXPR_PLUGIN::loadBitmap( LINE_READER& aReader ) +{ + std::unique_ptr< SCH_BITMAP > bitmap( new SCH_BITMAP ); + + const char* line = aReader.Line(); + + return bitmap.release(); +} + + +SCH_JUNCTION* SCH_SEXPR_PLUGIN::loadJunction( LINE_READER& aReader ) +{ + std::unique_ptr< SCH_JUNCTION > junction( new SCH_JUNCTION ); + + const char* line = aReader.Line(); + + return junction.release(); +} + + +SCH_NO_CONNECT* SCH_SEXPR_PLUGIN::loadNoConnect( LINE_READER& aReader ) +{ + std::unique_ptr< SCH_NO_CONNECT > no_connect( new SCH_NO_CONNECT ); + + const char* line = aReader.Line(); + + return no_connect.release(); +} + + +SCH_LINE* SCH_SEXPR_PLUGIN::loadWire( LINE_READER& aReader ) +{ + std::unique_ptr< SCH_LINE > wire( new SCH_LINE ); + + const char* line = aReader.Line(); + + return wire.release(); +} + + +SCH_BUS_ENTRY_BASE* SCH_SEXPR_PLUGIN::loadBusEntry( LINE_READER& aReader ) +{ + std::unique_ptr< SCH_BUS_ENTRY_BASE > busEntry; + + const char* line = aReader.Line(); + + return busEntry.release(); +} + + +SCH_TEXT* SCH_SEXPR_PLUGIN::loadText( LINE_READER& aReader ) +{ + std::unique_ptr< SCH_TEXT> text; + + const char* line = aReader.Line(); + + return text.release(); +} + + +SCH_COMPONENT* SCH_SEXPR_PLUGIN::loadComponent( LINE_READER& aReader ) +{ + std::unique_ptr< SCH_COMPONENT > component( new SCH_COMPONENT() ); + + const char* line = aReader.Line(); + + line = aReader.ReadLine(); + + return component.release(); +} + + +std::shared_ptr SCH_SEXPR_PLUGIN::loadBusAlias( LINE_READER& aReader, + SCH_SCREEN* aScreen ) +{ + auto busAlias = std::make_shared< BUS_ALIAS >( aScreen ); + + return busAlias; +} + + +void SCH_SEXPR_PLUGIN::Save( const wxString& aFileName, SCH_SCREEN* aScreen, KIWAY* aKiway, + const PROPERTIES* aProperties ) +{ + wxCHECK_RET( aScreen != NULL, "NULL SCH_SCREEN object." ); + wxCHECK_RET( !aFileName.IsEmpty(), "No schematic file name defined." ); + + LOCALE_IO toggle; // toggles on, then off, the C locale, to write floating point values. + + init( aKiway, aProperties ); + + wxFileName fn = aFileName; + + // File names should be absolute. Don't assume everything relative to the project path + // works properly. + wxASSERT( fn.IsAbsolute() ); + + FILE_OUTPUTFORMATTER formatter( fn.GetFullPath() ); + + m_out = &formatter; // no ownership + + Format( aScreen ); +} + + +void SCH_SEXPR_PLUGIN::Format( SCH_SCREEN* aScreen ) +{ + wxCHECK_RET( aScreen != NULL, "NULL SCH_SCREEN* object." ); + wxCHECK_RET( m_kiway != NULL, "NULL KIWAY* object." ); + + // Write the header + + for( const auto& alias : aScreen->GetBusAliases() ) + { + saveBusAlias( alias ); + } + + for( auto item : aScreen->Items() ) + { + switch( item->Type() ) + { + case SCH_COMPONENT_T: + saveComponent( static_cast( item ) ); + break; + case SCH_BITMAP_T: + saveBitmap( static_cast( item ) ); + break; + case SCH_SHEET_T: + saveSheet( static_cast( item ) ); + break; + case SCH_JUNCTION_T: + saveJunction( static_cast( item ) ); + break; + case SCH_NO_CONNECT_T: + saveNoConnect( static_cast( item ) ); + break; + case SCH_BUS_WIRE_ENTRY_T: + case SCH_BUS_BUS_ENTRY_T: + saveBusEntry( static_cast( item ) ); + break; + case SCH_LINE_T: + saveLine( static_cast( item ) ); + break; + case SCH_TEXT_T: + case SCH_LABEL_T: + case SCH_GLOBAL_LABEL_T: + case SCH_HIER_LABEL_T: + saveText( static_cast( item ) ); + break; + default: + wxASSERT( "Unexpected schematic object type in SCH_SEXPR_PLUGIN::Format()" ); + } + } + + m_out->Print( 0, ")\n" ); +} + + +void SCH_SEXPR_PLUGIN::Format( SELECTION* aSelection, OUTPUTFORMATTER* aFormatter ) +{ + m_out = aFormatter; + + for( unsigned i = 0; i < aSelection->GetSize(); ++i ) + { + SCH_ITEM* item = (SCH_ITEM*) aSelection->GetItem( i ); + + switch( item->Type() ) + { + case SCH_COMPONENT_T: + saveComponent( static_cast< SCH_COMPONENT* >( item ) ); + break; + case SCH_BITMAP_T: + saveBitmap( static_cast< SCH_BITMAP* >( item ) ); + break; + case SCH_SHEET_T: + saveSheet( static_cast< SCH_SHEET* >( item ) ); + break; + case SCH_JUNCTION_T: + saveJunction( static_cast< SCH_JUNCTION* >( item ) ); + break; + case SCH_NO_CONNECT_T: + saveNoConnect( static_cast< SCH_NO_CONNECT* >( item ) ); + break; + case SCH_BUS_WIRE_ENTRY_T: + case SCH_BUS_BUS_ENTRY_T: + saveBusEntry( static_cast< SCH_BUS_ENTRY_BASE* >( item ) ); + break; + case SCH_LINE_T: + saveLine( static_cast< SCH_LINE* >( item ) ); + break; + case SCH_TEXT_T: + case SCH_LABEL_T: + case SCH_GLOBAL_LABEL_T: + case SCH_HIER_LABEL_T: + saveText( static_cast< SCH_TEXT* >( item ) ); + break; + default: + wxASSERT( "Unexpected schematic object type in SCH_SEXPR_PLUGIN::Format()" ); + } + } +} + + +void SCH_SEXPR_PLUGIN::saveComponent( SCH_COMPONENT* aComponent ) +{ + std::string name1; + std::string name2; + wxArrayString reference_fields; + + static wxString delimiters( wxT( " " ) ); + + // This is redundant with the AR entries below, but it makes the files backwards-compatible. + if( aComponent->GetPathsAndReferences().GetCount() > 0 ) + { + reference_fields = wxStringTokenize( aComponent->GetPathsAndReferences()[0], delimiters ); + name1 = toUTFTildaText( reference_fields[1] ); + } + else + { + if( aComponent->GetField( REFERENCE )->GetText().IsEmpty() ) + name1 = toUTFTildaText( aComponent->GetPrefix() ); + else + name1 = toUTFTildaText( aComponent->GetField( REFERENCE )->GetText() ); + } + + wxString part_name = aComponent->GetLibId().Format(); + + if( part_name.size() ) + { + name2 = toUTFTildaText( part_name ); + } + else + { + name2 = "_NONAME_"; + } +} + + +void SCH_SEXPR_PLUGIN::saveField( SCH_FIELD* aField ) +{ +} + + +void SCH_SEXPR_PLUGIN::saveBitmap( SCH_BITMAP* aBitmap ) +{ + wxCHECK_RET( aBitmap != NULL, "SCH_BITMAP* is NULL" ); + + const wxImage* image = aBitmap->GetImage()->GetImageData(); + + wxCHECK_RET( image != NULL, "wxImage* is NULL" ); + + m_out->Print( 0, "$Bitmap\n" ); + m_out->Print( 0, "Pos %-4d %-4d\n", + Iu2Mils( aBitmap->GetPosition().x ), + Iu2Mils( aBitmap->GetPosition().y ) ); + m_out->Print( 0, "Scale %f\n", aBitmap->GetImage()->GetScale() ); + m_out->Print( 0, "Data\n" ); + + wxMemoryOutputStream stream; + + image->SaveFile( stream, wxBITMAP_TYPE_PNG ); + + // Write binary data in hexadecimal form (ASCII) + wxStreamBuffer* buffer = stream.GetOutputStreamBuffer(); + char* begin = (char*) buffer->GetBufferStart(); + + for( int ii = 0; begin < buffer->GetBufferEnd(); begin++, ii++ ) + { + if( ii >= 32 ) + { + ii = 0; + + m_out->Print( 0, "\n" ); + } + + m_out->Print( 0, "%2.2X ", *begin & 0xFF ); + } + + m_out->Print( 0, "\nEndData\n" ); + m_out->Print( 0, "$EndBitmap\n" ); +} + + +void SCH_SEXPR_PLUGIN::saveSheet( SCH_SHEET* aSheet ) +{ + wxCHECK_RET( aSheet != NULL, "SCH_SHEET* is NULL" ); +} + + +void SCH_SEXPR_PLUGIN::saveJunction( SCH_JUNCTION* aJunction ) +{ + wxCHECK_RET( aJunction != NULL, "SCH_JUNCTION* is NULL" ); +} + + +void SCH_SEXPR_PLUGIN::saveNoConnect( SCH_NO_CONNECT* aNoConnect ) +{ + wxCHECK_RET( aNoConnect != NULL, "SCH_NOCONNECT* is NULL" ); +} + + +void SCH_SEXPR_PLUGIN::saveBusEntry( SCH_BUS_ENTRY_BASE* aBusEntry ) +{ + wxCHECK_RET( aBusEntry != NULL, "SCH_BUS_ENTRY_BASE* is NULL" ); +} + + +void SCH_SEXPR_PLUGIN::saveLine( SCH_LINE* aLine ) +{ + wxCHECK_RET( aLine != NULL, "SCH_LINE* is NULL" ); +} + + +void SCH_SEXPR_PLUGIN::saveText( SCH_TEXT* aText ) +{ + wxCHECK_RET( aText != NULL, "SCH_TEXT* is NULL" ); +} + + +void SCH_SEXPR_PLUGIN::saveBusAlias( std::shared_ptr aAlias ) +{ + wxCHECK_RET( aAlias != NULL, "BUS_ALIAS* is NULL" ); + + wxString members = boost::algorithm::join( aAlias->Members(), " " ); +} + + +int SCH_SEXPR_PLUGIN_CACHE::m_modHash = 1; // starts at 1 and goes up + + +SCH_SEXPR_PLUGIN_CACHE::SCH_SEXPR_PLUGIN_CACHE( const wxString& aFullPathAndFileName ) : + m_fileName( aFullPathAndFileName ), + m_libFileName( aFullPathAndFileName ), + m_isWritable( true ), + m_isModified( false ) +{ + m_versionMajor = -1; + m_versionMinor = -1; + m_libType = LIBRARY_TYPE_EESCHEMA; +} + + +SCH_SEXPR_PLUGIN_CACHE::~SCH_SEXPR_PLUGIN_CACHE() +{ + // When the cache is destroyed, all of the alias objects on the heap should be deleted. + for( LIB_PART_MAP::iterator it = m_symbols.begin(); it != m_symbols.end(); ++it ) + delete it->second; + + m_symbols.clear(); +} + + +// If m_libFileName is a symlink follow it to the real source file +wxFileName SCH_SEXPR_PLUGIN_CACHE::GetRealFile() const +{ + wxFileName fn( m_libFileName ); + +#ifndef __WINDOWS__ + if( fn.Exists( wxFILE_EXISTS_SYMLINK ) ) + { + char buffer[ PATH_MAX + 1 ]; + ssize_t pathLen = readlink( TO_UTF8( fn.GetFullPath() ), buffer, PATH_MAX ); + + if( pathLen > 0 ) + { + buffer[ pathLen ] = '\0'; + fn.Assign( fn.GetPath() + wxT( "/" ) + wxString::FromUTF8( buffer ) ); + fn.Normalize(); + } + } +#endif + + return fn; +} + + +wxDateTime SCH_SEXPR_PLUGIN_CACHE::GetLibModificationTime() +{ + wxFileName fn = GetRealFile(); + + // update the writable flag while we have a wxFileName, in a network this + // is possibly quite dynamic anyway. + m_isWritable = fn.IsFileWritable(); + + return fn.GetModificationTime(); +} + + +bool SCH_SEXPR_PLUGIN_CACHE::IsFile( const wxString& aFullPathAndFileName ) const +{ + return m_fileName == aFullPathAndFileName; +} + + +bool SCH_SEXPR_PLUGIN_CACHE::IsFileChanged() const +{ + wxFileName fn = GetRealFile(); + + if( m_fileModTime.IsValid() && fn.IsOk() && fn.FileExists() ) + return fn.GetModificationTime() != m_fileModTime; + + return false; +} + + +LIB_PART* SCH_SEXPR_PLUGIN_CACHE::removeSymbol( LIB_PART* aPart ) +{ + wxCHECK_MSG( aPart != NULL, NULL, "NULL pointer cannot be removed from library." ); + + LIB_PART* firstChild = NULL; + LIB_PART_MAP::iterator it = m_symbols.find( aPart->GetName() ); + + if( it == m_symbols.end() ) + return NULL; + + // If the entry pointer doesn't match the name it is mapped to in the library, we + // have done something terribly wrong. + wxCHECK_MSG( *it->second == aPart, NULL, + "Pointer mismatch while attempting to remove alias entry <" + aPart->GetName() + + "> from library cache <" + m_libFileName.GetName() + ">." ); + + // If the symbol is a root symbol used by other symbols find the first alias that uses + // the root part and make it the new root. + if( aPart->IsRoot() ) + { + for( auto entry : m_symbols ) + { + if( entry.second->IsAlias() + && entry.second->GetParent().lock() == aPart->SharedPtr() ) + { + firstChild = entry.second; + break; + } + } + + if( firstChild ) + { + for( LIB_ITEM& drawItem : aPart->GetDrawItems() ) + { + if( drawItem.Type() == LIB_FIELD_T ) + { + LIB_FIELD& field = static_cast( drawItem ); + + if( firstChild->FindField( field.GetName() ) ) + continue; + } + + LIB_ITEM* newItem = (LIB_ITEM*) drawItem.Clone(); + drawItem.SetParent( firstChild ); + firstChild->AddDrawItem( newItem ); + } + + // Reparent the remaining aliases. + for( auto entry : m_symbols ) + { + if( entry.second->IsAlias() + && entry.second->GetParent().lock() == aPart->SharedPtr() ) + entry.second->SetParent( firstChild ); + } + } + } + + m_symbols.erase( it ); + delete aPart; + m_isModified = true; + ++m_modHash; + return firstChild; +} + + +void SCH_SEXPR_PLUGIN_CACHE::AddSymbol( const LIB_PART* aPart ) +{ + // aPart is cloned in PART_LIB::AddPart(). The cache takes ownership of aPart. + wxString name = aPart->GetName(); + LIB_PART_MAP::iterator it = m_symbols.find( name ); + + if( it != m_symbols.end() ) + { + removeSymbol( it->second ); + } + + m_symbols[ name ] = const_cast< LIB_PART* >( aPart ); + m_isModified = true; + ++m_modHash; +} + + +void SCH_SEXPR_PLUGIN_CACHE::Load() +{ + if( !m_libFileName.FileExists() ) + { + THROW_IO_ERROR( wxString::Format( _( "Library file \"%s\" not found." ), + m_libFileName.GetFullPath() ) ); + } + + wxCHECK_RET( m_libFileName.IsAbsolute(), + wxString::Format( "Cannot use relative file paths in sexpr plugin to " + "open library \"%s\".", m_libFileName.GetFullPath() ) ); + + wxLogTrace( traceSchLegacyPlugin, "Loading sexpr symbol library file \"%s\"", + m_libFileName.GetFullPath() ); + + FILE_LINE_READER reader( m_libFileName.GetFullPath() ); + + if( !reader.ReadLine() ) + THROW_IO_ERROR( _( "unexpected end of file" ) ); + + const char* line = reader.Line(); + + + ++m_modHash; + + // Remember the file modification time of library file when the + // cache snapshot was made, so that in a networked environment we will + // reload the cache as needed. + m_fileModTime = GetLibModificationTime(); +} + + +void SCH_SEXPR_PLUGIN_CACHE::loadHeader( FILE_LINE_READER& aReader ) +{ + const char* line = aReader.Line(); + +} + + +LIB_PART* SCH_SEXPR_PLUGIN_CACHE::LoadPart( LINE_READER& aReader, int aMajorVersion, + int aMinorVersion, LIB_PART_MAP* aMap ) +{ + const char* line = aReader.Line(); + + std::unique_ptr< LIB_PART > part( new LIB_PART( wxEmptyString ) ); + + return part.release(); +} + + +void SCH_SEXPR_PLUGIN_CACHE::loadField( std::unique_ptr& aPart, + LINE_READER& aReader ) +{ + const char* line = aReader.Line(); +} + + +void SCH_SEXPR_PLUGIN_CACHE::loadDrawEntries( std::unique_ptr& aPart, + LINE_READER& aReader, + int aMajorVersion, + int aMinorVersion ) +{ + const char* line = aReader.Line(); +} + + +FILL_T SCH_SEXPR_PLUGIN_CACHE::parseFillMode( LINE_READER& aReader, const char* aLine, + const char** aOutput ) +{ +} + + +LIB_ARC* SCH_SEXPR_PLUGIN_CACHE::loadArc( std::unique_ptr& aPart, + LINE_READER& aReader ) +{ + const char* line = aReader.Line(); + + LIB_ARC* arc = new LIB_ARC( aPart.get() ); + + return arc; +} + + +LIB_CIRCLE* SCH_SEXPR_PLUGIN_CACHE::loadCircle( std::unique_ptr& aPart, + LINE_READER& aReader ) +{ + const char* line = aReader.Line(); + + LIB_CIRCLE* circle = new LIB_CIRCLE( aPart.get() ); + + return circle; +} + + +LIB_TEXT* SCH_SEXPR_PLUGIN_CACHE::loadText( std::unique_ptr& aPart, + LINE_READER& aReader, + int aMajorVersion, + int aMinorVersion ) +{ + const char* line = aReader.Line(); + + LIB_TEXT* text = new LIB_TEXT( aPart.get() ); + + return text; +} + + +LIB_RECTANGLE* SCH_SEXPR_PLUGIN_CACHE::loadRectangle( std::unique_ptr& aPart, + LINE_READER& aReader ) +{ + const char* line = aReader.Line(); + + LIB_RECTANGLE* rectangle = new LIB_RECTANGLE( aPart.get() ); + + return rectangle; +} + + +LIB_PIN* SCH_SEXPR_PLUGIN_CACHE::loadPin( std::unique_ptr& aPart, + LINE_READER& aReader ) +{ + const char* line = aReader.Line(); + + LIB_PIN* pin = new LIB_PIN( aPart.get() ); + + return pin; +} + + +LIB_POLYLINE* SCH_SEXPR_PLUGIN_CACHE::loadPolyLine( std::unique_ptr& aPart, + LINE_READER& aReader ) +{ + const char* line = aReader.Line(); + + LIB_POLYLINE* polyLine = new LIB_POLYLINE( aPart.get() ); + + return polyLine; +} + + +LIB_BEZIER* SCH_SEXPR_PLUGIN_CACHE::loadBezier( std::unique_ptr& aPart, + LINE_READER& aReader ) +{ + const char* line = aReader.Line(); + + LIB_BEZIER* bezier = new LIB_BEZIER( aPart.get() ); + + return bezier; +} + + +void SCH_SEXPR_PLUGIN_CACHE::loadFootprintFilters( std::unique_ptr& aPart, + LINE_READER& aReader ) +{ + const char* line = aReader.Line(); +} + + +void SCH_SEXPR_PLUGIN_CACHE::Save() +{ + if( !m_isModified ) + return; + + // Write through symlinks, don't replace them. + wxFileName fn = GetRealFile(); + + std::unique_ptr< FILE_OUTPUTFORMATTER > formatter( new FILE_OUTPUTFORMATTER( fn.GetFullPath() ) ); + + formatter->Print( 0, "(kicad_symbol_lib (version %d) (host kicad_symbol_editor %s)\n", + SEXPR_SYMBOL_LIB_FILE_VERSION, + formatter->Quotew( GetBuildVersion() ).c_str() ); + + for( auto parent : m_symbols ) + { + // Save the root symbol first so alias can inherit from them. + if( parent.second->IsRoot() ) + { + SaveSymbol( parent.second, *formatter.get(), 1 ); + + // Save all of the aliases associated with the current root symbol. + for( auto alias : m_symbols ) + { + if( !alias.second->IsAlias() ) + continue; + + std::shared_ptr aliasParent = alias.second->GetParent().lock(); + + if( aliasParent.get() != parent.second ) + continue; + + SaveSymbol( alias.second, *formatter.get(), 1 ); + } + } + } + + formatter->Print( 0, ")\n" ); + + formatter.reset(); + + m_fileModTime = fn.GetModificationTime(); + m_isModified = false; +} + + +void SCH_SEXPR_PLUGIN_CACHE::SaveSymbol( LIB_PART* aSymbol, OUTPUTFORMATTER& aFormatter, + int aNestLevel, LIB_PART_MAP* aMap ) +{ + wxCHECK_RET( aSymbol, "Invalid LIB_PART pointer." ); + + if( aSymbol->IsRoot() ) + { + aFormatter.Print( aNestLevel, "(symbol %s", + aFormatter.Quotew( aSymbol->GetName() ).c_str() ); + + if( aSymbol->IsPower() ) + aFormatter.Print( 0, " power" ); + + // TODO: add uuid token here. + + // TODO: add anchor position token here. + + if( !aSymbol->ShowPinNumbers() ) + aFormatter.Print( 0, " (pin_numbers hide)" ); + + if( aSymbol->GetPinNameOffset() != Iu2Mils( DEFAULT_PIN_NAME_OFFSET ) + || !aSymbol->ShowPinNames() ) + { + aFormatter.Print( 0, " (pin_name" ); + + if( aSymbol->GetPinNameOffset() != Iu2Mils( DEFAULT_PIN_NAME_OFFSET ) ) + aFormatter.Print( 0, " (offset %s)", + FormatInternalUnits( aSymbol->GetPinNameOffset() ).c_str() ); + + if( !aSymbol->ShowPinNames() ) + aFormatter.Print( 0, " hide" ); + + aFormatter.Print( 0, ")" ); + } + + // TODO: add atomic token here. + + // TODO: add required token here." + + aFormatter.Print( 0, "\n" ); + + LIB_FIELDS fields; + + aSymbol->GetFields( fields ); + + for( auto field : fields ) + saveField( &field, aFormatter, aNestLevel + 1 ); + + saveDcmInfoAsFields( aSymbol, aFormatter, aNestLevel, fields.back().GetId() + 1 ); + + // Save the draw items grouped by units. + std::vector units = aSymbol->GetUnitDrawItems(); + + for( auto unit : units ) + { + aFormatter.Print( aNestLevel + 1, "(symbol \"%s_%d_%d\"\n", + TO_UTF8( aSymbol->GetName() ), + unit.m_unit, unit.m_convert ); + + for( auto item : unit.m_items ) + saveSymbolDrawItem( item, aFormatter, aNestLevel + 2 ); + + aFormatter.Print( aNestLevel + 1, ")\n" ); + } + } + else + { + std::shared_ptr parent = aSymbol->GetParent().lock(); + + wxASSERT( parent ); + + aFormatter.Print( aNestLevel, "(symbol %s (extends %s)\n", + aFormatter.Quotew( aSymbol->GetName() ).c_str(), + aFormatter.Quotew( parent->GetName() ).c_str() ); + + LIB_FIELD tmp = parent->GetValueField(); + tmp.SetText( aSymbol->GetName() ); + saveField( &tmp, aFormatter, aNestLevel + 1 ); + + if( !aSymbol->GetDocFileName().IsEmpty() ) + { + tmp = *aSymbol->GetField( DATASHEET ); + tmp.SetText( aSymbol->GetDocFileName() ); + saveField( &tmp, aFormatter, aNestLevel + 1 ); + } + + saveDcmInfoAsFields( aSymbol, aFormatter, aNestLevel, MANDATORY_FIELDS ); + } + + aFormatter.Print( aNestLevel, ")\n" ); +} + + +void SCH_SEXPR_PLUGIN_CACHE::saveDcmInfoAsFields( LIB_PART* aSymbol, OUTPUTFORMATTER& aFormatter, + int aNestLevel, int aFirstId ) +{ + wxCHECK_RET( aSymbol, "Invalid LIB_PART pointer." ); + + int id = aFirstId; + + if( !aSymbol->GetKeyWords().IsEmpty() ) + { + LIB_FIELD keywords( id, wxString( "ki_keywords" ) ); + keywords.SetVisible( false ); + keywords.SetText( aSymbol->GetKeyWords() ); + saveField( &keywords, aFormatter, aNestLevel + 1 ); + id += 1; + } + + if( !aSymbol->GetDescription().IsEmpty() ) + { + LIB_FIELD description( id, wxString( "ki_description" ) ); + description.SetVisible( false ); + description.SetText( aSymbol->GetDescription() ); + saveField( &description, aFormatter, aNestLevel + 1 ); + id += 1; + } + + wxArrayString fpFilters = aSymbol->GetFootprints(); + + if( !fpFilters.IsEmpty() ) + { + wxString tmp; + + for( auto filter : fpFilters ) + { + if( tmp.IsEmpty() ) + tmp = filter; + else + tmp += "\n" + filter; + } + + LIB_FIELD description( id, wxString( "ki_fp_filters" ) ); + description.SetVisible( false ); + description.SetText( tmp ); + saveField( &description, aFormatter, aNestLevel + 1 ); + id += 1; + } +} + + +void SCH_SEXPR_PLUGIN_CACHE::saveSymbolDrawItem( LIB_ITEM* aItem, OUTPUTFORMATTER& aFormatter, + int aNestLevel ) +{ + wxCHECK_RET( aItem, "Invalid LIB_ITEM pointer." ); + + switch( aItem->Type() ) + { + case LIB_ARC_T: + saveArc( (LIB_ARC*) aItem, aFormatter, aNestLevel ); + break; + + case LIB_BEZIER_T: + saveBezier( (LIB_BEZIER*) aItem, aFormatter, aNestLevel ); + break; + + case LIB_CIRCLE_T: + saveCircle( ( LIB_CIRCLE* ) aItem, aFormatter, aNestLevel ); + break; + + case LIB_PIN_T: + savePin( (LIB_PIN* ) aItem, aFormatter, aNestLevel ); + break; + + case LIB_POLYLINE_T: + savePolyLine( ( LIB_POLYLINE* ) aItem, aFormatter, aNestLevel ); + break; + + case LIB_RECTANGLE_T: + saveRectangle( ( LIB_RECTANGLE* ) aItem, aFormatter, aNestLevel ); + break; + + case LIB_TEXT_T: + saveText( ( LIB_TEXT* ) aItem, aFormatter, aNestLevel ); + break; + + default: + ; + } +} + + +void SCH_SEXPR_PLUGIN_CACHE::saveArc( LIB_ARC* aArc, + OUTPUTFORMATTER& aFormatter, + int aNestLevel ) +{ + wxCHECK_RET( aArc && aArc->Type() == LIB_ARC_T, "Invalid LIB_ARC object." ); + + aFormatter.Print( aNestLevel, + "(arc (start %s %s) (end %s %s) (radius (at %s %s) (length %s))", + 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() ); + + bool needsSpace = false; + + if( Iu2Mils( aArc->GetWidth() ) != DEFAULTDRAWLINETHICKNESS + && aArc->GetWidth() != 0 ) + { + aFormatter.Print( 0, "(stroke (width %s))", + FormatInternalUnits( aArc->GetWidth() ).c_str() ); + needsSpace = true; + } + + if( aArc->GetFillMode() != NO_FILL ) + { + if( needsSpace ) + aFormatter.Print( 0, " " ); + + FormatFill( static_cast< LIB_ITEM* >( aArc ), aFormatter, 0 ); + } + + aFormatter.Print( 0, ")\n" ); +} + + +void SCH_SEXPR_PLUGIN_CACHE::saveBezier( LIB_BEZIER* aBezier, + OUTPUTFORMATTER& aFormatter, + int aNestLevel ) +{ + wxCHECK_RET( aBezier && aBezier->Type() == LIB_BEZIER_T, "Invalid LIB_BEZIER object." ); + + int newLine = 0; + int lineCount = 1; + aFormatter.Print( aNestLevel, "(bezier\n" ); + aFormatter.Print( aNestLevel + 1, "(pts " ); + + for( const auto& pt : aBezier->GetPoints() ) + { + if( newLine == 4 ) + { + aFormatter.Print( 0, "\n" ); + aFormatter.Print( aNestLevel + 2, "(xy %s %s)", + FormatInternalUnits( pt.x ).c_str(), + FormatInternalUnits( pt.y ).c_str() ); + } + else + { + aFormatter.Print( 0, " (xy %s %s)", + FormatInternalUnits( pt.x ).c_str(), + FormatInternalUnits( pt.y ).c_str() ); + } + + if( newLine < 4 ) + { + newLine += 1; + } + else + { + newLine = 0; + lineCount += 1; + } + } + + if( lineCount == 1 ) + { + aFormatter.Print( 0, ")\n" ); // Closes pts token on same line. + } + else + { + aFormatter.Print( 0, "\n" ); + aFormatter.Print( aNestLevel + 1, ")\n" ); // Closes pts token with multiple lines. + } + + bool needsSpace = false; + + if( Iu2Mils( aBezier->GetWidth() ) != DEFAULTDRAWLINETHICKNESS + && aBezier->GetWidth() != 0 ) + { + aFormatter.Print( aNestLevel + 1, "(stroke (width %s))", + FormatInternalUnits( aBezier->GetWidth() ).c_str() ); + needsSpace = true; + + if( aBezier->GetFillMode() == NO_FILL ) + aFormatter.Print( 0, "\n" ); + } + + if( aBezier->GetFillMode() != NO_FILL ) + { + if( needsSpace ) + { + aFormatter.Print( 0, " " ); + FormatFill( static_cast< LIB_ITEM* >( aBezier ), aFormatter, 0 ); + } + else + { + FormatFill( static_cast< LIB_ITEM* >( aBezier ), aFormatter, aNestLevel + 1 ); + } + + aFormatter.Print( 0, "\n" ); + } + + aFormatter.Print( aNestLevel, ")\n" ); +} + + +void SCH_SEXPR_PLUGIN_CACHE::saveCircle( LIB_CIRCLE* aCircle, + OUTPUTFORMATTER& aFormatter, + int aNestLevel ) +{ + wxCHECK_RET( aCircle && aCircle->Type() == LIB_CIRCLE_T, "Invalid LIB_CIRCLE object." ); + + aFormatter.Print( aNestLevel, "(circle (center %s %s) (radius %s)", + FormatInternalUnits( aCircle->GetPosition().x ).c_str(), + FormatInternalUnits( aCircle->GetPosition().y ).c_str(), + FormatInternalUnits( aCircle->GetRadius() ).c_str() ); + + if( Iu2Mils( aCircle->GetWidth() ) != DEFAULTDRAWLINETHICKNESS + && aCircle->GetWidth() != 0 ) + { + aFormatter.Print( 0, " (stroke (width %s))", + FormatInternalUnits( aCircle->GetWidth() ).c_str() ); + } + + if( aCircle->GetFillMode() != NO_FILL ) + { + aFormatter.Print( 0, " " ); + FormatFill( static_cast< LIB_ITEM* >( aCircle ), aFormatter, 0 ); + } + + aFormatter.Print( 0, ")\n" ); +} + + +void SCH_SEXPR_PLUGIN_CACHE::saveField( LIB_FIELD* aField, + OUTPUTFORMATTER& aFormatter, + int aNestLevel ) +{ + wxCHECK_RET( aField && aField->Type() == LIB_FIELD_T, "Invalid LIB_FIELD object." ); + + wxString fieldName = aField->GetName(); + + // When saving legacy fields, prefix the field name with "ki_" to prevent name clashes + // with exisiting user defined fields. + if( aField->IsMandatory() && !fieldName.StartsWith( "ki_" ) ) + fieldName = "ki_" + fieldName.Lower(); + + aFormatter.Print( aNestLevel, "(property %s %s (at %s %s)", + aFormatter.Quotew( fieldName ).c_str(), + aFormatter.Quotew( aField->GetText() ).c_str(), + FormatInternalUnits( aField->GetPosition().x ).c_str(), + FormatInternalUnits( aField->GetPosition().y ).c_str() ); + + if( aField->IsVisible() && aField->IsDefaultFormatting() ) + { + aFormatter.Print( 0, ")\n" ); // Close property token if visible and 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. + } + } +} + + +void SCH_SEXPR_PLUGIN_CACHE::savePin( LIB_PIN* aPin, + OUTPUTFORMATTER& aFormatter, + int aNestLevel ) +{ + wxCHECK_RET( aPin && aPin->Type() == LIB_PIN_T, "Invalid LIB_PIN object." ); + + aPin->ClearFlags( IS_CHANGED ); + + aFormatter.Print( aNestLevel, "(pin %s %s (at %s %s %s) (length %s)", + GetPinElectricalTypeToken( aPin->GetType() ), + GetPinShapeToken( aPin->GetShape() ), + FormatInternalUnits( aPin->GetPosition().x ).c_str(), + FormatInternalUnits( aPin->GetPosition().y ).c_str(), + FormatAngle( GetPinAngle( aPin->GetOrientation() ) * 10.0 ).c_str(), + FormatInternalUnits( aPin->GetLength() ).c_str() ); + + aFormatter.Print( 0, " (name %s", + aFormatter.Quotew( aPin->GetName() ).c_str() ); + + // This follows the EDA_TEXT effects formatting for future expansion. + if( aPin->GetNameTextSize() != Mils2iu( DEFAULTPINNAMESIZE ) ) + aFormatter.Print( 0, " (effects (font (size %s %s)))", + FormatInternalUnits( aPin->GetNameTextSize() ).c_str(), + FormatInternalUnits( aPin->GetNameTextSize() ).c_str() ); + + aFormatter.Print( 0, ")" ); + aFormatter.Print( 0, " (number %s", + aFormatter.Quotew( aPin->GetNumber() ).c_str() ); + + // This follows the EDA_TEXT effects formatting for future expansion. + if( aPin->GetNumberTextSize() != Mils2iu( DEFAULTPINNUMSIZE ) ) + aFormatter.Print( 0, " (effects (font (size %s %s)))", + FormatInternalUnits( aPin->GetNumberTextSize() ).c_str(), + FormatInternalUnits( aPin->GetNumberTextSize() ).c_str() ); + aFormatter.Print( 0, "))\n" ); +} + + +void SCH_SEXPR_PLUGIN_CACHE::savePolyLine( LIB_POLYLINE* aPolyLine, + OUTPUTFORMATTER& aFormatter, + int aNestLevel ) +{ + wxCHECK_RET( aPolyLine && aPolyLine->Type() == LIB_POLYLINE_T, "Invalid LIB_POLYLINE object." ); + + int newLine = 0; + int lineCount = 1; + aFormatter.Print( aNestLevel, "(polyline\n" ); + aFormatter.Print( aNestLevel + 1, "(pts" ); + + for( const auto& pt : aPolyLine->GetPolyPoints() ) + { + if( newLine == 4 ) + { + aFormatter.Print( 0, "\n" ); + aFormatter.Print( aNestLevel + 2, "(xy %s %s)", + FormatInternalUnits( pt.x ).c_str(), + FormatInternalUnits( pt.y ).c_str() ); + } + else + { + aFormatter.Print( 0, " (xy %s %s)", + FormatInternalUnits( pt.x ).c_str(), + FormatInternalUnits( pt.y ).c_str() ); + } + + if( newLine < 4 ) + { + newLine += 1; + } + else + { + newLine = 0; + lineCount += 1; + } + } + + if( lineCount == 1 ) + { + aFormatter.Print( 0, ")\n" ); // Closes pts token on same line. + } + else + { + aFormatter.Print( 0, "\n" ); + aFormatter.Print( aNestLevel + 1, ")\n" ); // Closes pts token with multiple lines. + } + + bool needsSpace = false; + + if( Iu2Mils( aPolyLine->GetWidth() ) != DEFAULTDRAWLINETHICKNESS + && aPolyLine->GetWidth() != 0 ) + { + aFormatter.Print( aNestLevel + 1, "(stroke (width %s))", + FormatInternalUnits( aPolyLine->GetWidth() ).c_str() ); + needsSpace = true; + + if( aPolyLine->GetFillMode() == NO_FILL ) + aFormatter.Print( 0, "\n" ); + } + + if( aPolyLine->GetFillMode() != NO_FILL ) + { + if( needsSpace ) + { + aFormatter.Print( 0, " " ); + FormatFill( static_cast< LIB_ITEM* >( aPolyLine ), aFormatter, 0 ); + } + else + { + FormatFill( static_cast< LIB_ITEM* >( aPolyLine ), aFormatter, aNestLevel + 1 ); + } + + aFormatter.Print( 0, "\n" ); + } + + aFormatter.Print( aNestLevel, ")\n" ); +} + + +void SCH_SEXPR_PLUGIN_CACHE::saveRectangle( LIB_RECTANGLE* aRectangle, + OUTPUTFORMATTER& aFormatter, + int aNestLevel ) +{ + wxCHECK_RET( aRectangle && aRectangle->Type() == LIB_RECTANGLE_T, + "Invalid LIB_RECTANGLE object." ); + + aFormatter.Print( aNestLevel, "(rectangle (start %s %s) (end %s %s)", + FormatInternalUnits( aRectangle->GetPosition().x ).c_str(), + FormatInternalUnits( aRectangle->GetPosition().y ).c_str(), + FormatInternalUnits( aRectangle->GetEnd().x ).c_str(), + FormatInternalUnits( aRectangle->GetEnd().y ).c_str() ); + + bool needsSpace = false; + + if( Iu2Mils( aRectangle->GetWidth() ) != DEFAULTDRAWLINETHICKNESS + && aRectangle->GetWidth() != 0 ) + { + aFormatter.Print( 0, " (stroke (width %s))", + FormatInternalUnits( aRectangle->GetWidth() ).c_str() ); + needsSpace = true; + } + + if( aRectangle->GetFillMode() != NO_FILL ) + { + if( needsSpace ) + aFormatter.Print( 0, " " ); + + FormatFill( static_cast< LIB_ITEM* >( aRectangle ), aFormatter, 0 ); + } + + aFormatter.Print( 0, ")\n" ); +} + + +void SCH_SEXPR_PLUGIN_CACHE::saveText( LIB_TEXT* aText, + OUTPUTFORMATTER& aFormatter, + int aNestLevel ) +{ + wxCHECK_RET( aText && aText->Type() == LIB_TEXT_T, "Invalid LIB_TEXT object." ); + + aFormatter.Print( aNestLevel, "(text %s (at %s %s %g)\n", + aFormatter.Quotew( aText->GetText() ).c_str(), + FormatInternalUnits( aText->GetPosition().x ).c_str(), + FormatInternalUnits( aText->GetPosition().y ).c_str(), + aText->GetTextAngle() ); + aText->Format( &aFormatter, aNestLevel + 1, 0 ); + aFormatter.Print( aNestLevel, ")\n" ); +} + + +void SCH_SEXPR_PLUGIN_CACHE::DeleteSymbol( const wxString& aSymbolName ) +{ + LIB_PART_MAP::iterator it = m_symbols.find( aSymbolName ); + + if( it == m_symbols.end() ) + THROW_IO_ERROR( wxString::Format( _( "library %s does not contain a symbol named %s" ), + m_libFileName.GetFullName(), aSymbolName ) ); + + LIB_PART* part = it->second; + + if( part->IsRoot() ) + { + LIB_PART* rootPart = part; + + // Remove the root symbol and all it's children. + m_symbols.erase( it ); + + LIB_PART_MAP::iterator it1 = m_symbols.begin(); + + while( it1 != m_symbols.end() ) + { + if( it1->second->IsAlias() && it1->second->GetParent().lock() == rootPart->SharedPtr() ) + { + delete it1->second; + it1 = m_symbols.erase( it1 ); + } + else + { + it1++; + } + } + + delete rootPart; + } + else + { + // Just remove the alias. + m_symbols.erase( it ); + delete part; + } + + ++m_modHash; + m_isModified = true; +} + + +void SCH_SEXPR_PLUGIN::cacheLib( const wxString& aLibraryFileName ) +{ + if( !m_cache || !m_cache->IsFile( aLibraryFileName ) || m_cache->IsFileChanged() ) + { + // a spectacular episode in memory management: + delete m_cache; + m_cache = new SCH_SEXPR_PLUGIN_CACHE( aLibraryFileName ); + + // Because m_cache is rebuilt, increment PART_LIBS::s_modify_generation + // to modify the hash value that indicate component to symbol links + // must be updated. + PART_LIBS::s_modify_generation++; + + if( !isBuffering( m_props ) ) + m_cache->Load(); + } +} + + +bool SCH_SEXPR_PLUGIN::isBuffering( const PROPERTIES* aProperties ) +{ + return ( aProperties && aProperties->Exists( SCH_SEXPR_PLUGIN::PropBuffering ) ); +} + + +int SCH_SEXPR_PLUGIN::GetModifyHash() const +{ + if( m_cache ) + return m_cache->GetModifyHash(); + + // If the cache hasn't been loaded, it hasn't been modified. + return 0; +} + + +void SCH_SEXPR_PLUGIN::EnumerateSymbolLib( wxArrayString& aSymbolNameList, + const wxString& aLibraryPath, + const PROPERTIES* aProperties ) +{ + LOCALE_IO toggle; // toggles on, then off, the C locale. + + m_props = aProperties; + + bool powerSymbolsOnly = ( aProperties && + aProperties->find( SYMBOL_LIB_TABLE::PropPowerSymsOnly ) != aProperties->end() ); + cacheLib( aLibraryPath ); + + const LIB_PART_MAP& symbols = m_cache->m_symbols; + + for( LIB_PART_MAP::const_iterator it = symbols.begin(); it != symbols.end(); ++it ) + { + if( !powerSymbolsOnly || it->second->IsPower() ) + aSymbolNameList.Add( it->first ); + } +} + + +void SCH_SEXPR_PLUGIN::EnumerateSymbolLib( std::vector& aSymbolList, + const wxString& aLibraryPath, + const PROPERTIES* aProperties ) +{ + LOCALE_IO toggle; // toggles on, then off, the C locale. + + m_props = aProperties; + + bool powerSymbolsOnly = ( aProperties && + aProperties->find( SYMBOL_LIB_TABLE::PropPowerSymsOnly ) != aProperties->end() ); + cacheLib( aLibraryPath ); + + const LIB_PART_MAP& symbols = m_cache->m_symbols; + + for( LIB_PART_MAP::const_iterator it = symbols.begin(); it != symbols.end(); ++it ) + { + if( !powerSymbolsOnly || it->second->IsPower() ) + aSymbolList.push_back( it->second ); + } +} + + +LIB_PART* SCH_SEXPR_PLUGIN::LoadSymbol( const wxString& aLibraryPath, const wxString& aSymbolName, + const PROPERTIES* aProperties ) +{ + LOCALE_IO toggle; // toggles on, then off, the C locale. + + m_props = aProperties; + + cacheLib( aLibraryPath ); + + LIB_PART_MAP::const_iterator it = m_cache->m_symbols.find( aSymbolName ); + + if( it == m_cache->m_symbols.end() ) + return nullptr; + + return it->second; +} + + +void SCH_SEXPR_PLUGIN::SaveSymbol( const wxString& aLibraryPath, const LIB_PART* aSymbol, + const PROPERTIES* aProperties ) +{ + m_props = aProperties; + + cacheLib( aLibraryPath ); + + m_cache->AddSymbol( aSymbol ); + + if( !isBuffering( aProperties ) ) + m_cache->Save(); +} + + +void SCH_SEXPR_PLUGIN::DeleteSymbol( const wxString& aLibraryPath, const wxString& aSymbolName, + const PROPERTIES* aProperties ) +{ + m_props = aProperties; + + cacheLib( aLibraryPath ); + + m_cache->DeleteSymbol( aSymbolName ); + + if( !isBuffering( aProperties ) ) + m_cache->Save(); +} + + +void SCH_SEXPR_PLUGIN::CreateSymbolLib( const wxString& aLibraryPath, + const PROPERTIES* aProperties ) +{ + if( wxFileExists( aLibraryPath ) ) + { + THROW_IO_ERROR( wxString::Format( + _( "symbol library \"%s\" already exists, cannot create a new library" ), + aLibraryPath.GetData() ) ); + } + + LOCALE_IO toggle; + + m_props = aProperties; + + delete m_cache; + m_cache = new SCH_SEXPR_PLUGIN_CACHE( aLibraryPath ); + m_cache->SetModified(); + m_cache->Save(); + m_cache->Load(); // update m_writable and m_mod_time +} + + +bool SCH_SEXPR_PLUGIN::DeleteSymbolLib( const wxString& aLibraryPath, + const PROPERTIES* aProperties ) +{ + wxFileName fn = aLibraryPath; + + if( !fn.FileExists() ) + return false; + + // Some of the more elaborate wxRemoveFile() crap puts up its own wxLog dialog + // we don't want that. we want bare metal portability with no UI here. + if( wxRemove( aLibraryPath ) ) + { + THROW_IO_ERROR( wxString::Format( _( "library \"%s\" cannot be deleted" ), + aLibraryPath.GetData() ) ); + } + + if( m_cache && m_cache->IsFile( aLibraryPath ) ) + { + delete m_cache; + m_cache = 0; + } + + return true; +} + + +void SCH_SEXPR_PLUGIN::SaveLibrary( const wxString& aLibraryPath, const PROPERTIES* aProperties ) +{ + if( !m_cache ) + m_cache = new SCH_SEXPR_PLUGIN_CACHE( aLibraryPath ); + + wxString oldFileName = m_cache->GetFileName(); + + if( !m_cache->IsFile( aLibraryPath ) ) + { + m_cache->SetFileName( aLibraryPath ); + } + + // This is a forced save. + m_cache->SetModified(); + m_cache->Save(); + m_cache->SetFileName( oldFileName ); +} + + +bool SCH_SEXPR_PLUGIN::CheckHeader( const wxString& aFileName ) +{ + // Open file and check first line + wxTextFile tempFile; + + tempFile.Open( aFileName ); + wxString firstline; + // read the first line + firstline = tempFile.GetFirstLine(); + tempFile.Close(); + + return firstline.StartsWith( "EESchema" ); +} + + +bool SCH_SEXPR_PLUGIN::IsSymbolLibWritable( const wxString& aLibraryPath ) +{ + return wxFileName::IsFileWritable( aLibraryPath ); +} + + +LIB_PART* SCH_SEXPR_PLUGIN::ParsePart( LINE_READER& reader, int aMajorVersion, + int aMinorVersion ) +{ + return SCH_SEXPR_PLUGIN_CACHE::LoadPart( reader, aMajorVersion, aMinorVersion ); +} + + +void SCH_SEXPR_PLUGIN::FormatPart( LIB_PART* part, OUTPUTFORMATTER & formatter ) +{ + SCH_SEXPR_PLUGIN_CACHE::SaveSymbol( part, formatter ); +} + + +const char* SCH_SEXPR_PLUGIN::PropBuffering = "buffering"; diff --git a/eeschema/sch_sexpr_plugin.h b/eeschema/sch_sexpr_plugin.h new file mode 100644 index 0000000000..679401dfeb --- /dev/null +++ b/eeschema/sch_sexpr_plugin.h @@ -0,0 +1,171 @@ +#ifndef _SCH_SEXPR_PLUGIN_H_ +#define _SCH_SEXPR_PLUGIN_H_ + +/* + * 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 . + */ + +#include +#include +#include +#include + + +class KIWAY; +class LINE_READER; +class SCH_SCREEN; +class SCH_SHEET; +class SCH_BITMAP; +class SCH_JUNCTION; +class SCH_NO_CONNECT; +class SCH_LINE; +class SCH_BUS_ENTRY_BASE; +class SCH_TEXT; +class SCH_COMPONENT; +class SCH_FIELD; +class PROPERTIES; +class SELECTION; +class SCH_SEXPR_PLUGIN_CACHE; +class LIB_PART; +class PART_LIB; +class BUS_ALIAS; + + +/** + * A #SCH_PLUGIN derivation for loading schematic files using the new s-expression + * file format. + * + * As with all SCH_PLUGINs there is no UI dependencies i.e. windowing calls allowed. + */ +class SCH_SEXPR_PLUGIN : public SCH_PLUGIN +{ +public: + + SCH_SEXPR_PLUGIN(); + virtual ~SCH_SEXPR_PLUGIN(); + + const wxString GetName() const override + { + return wxT( "Eeschema-Legacy" ); + } + + const wxString GetFileExtension() const override + { + return wxT( "sch" ); + } + + /** + * 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 + * when writing the schematic cache library file or saving a library to a new file name. + */ + static const char* PropBuffering; + + int GetModifyHash() const override; + + SCH_SHEET* Load( const wxString& aFileName, KIWAY* aKiway, + SCH_SHEET* aAppendToMe = nullptr, + const PROPERTIES* aProperties = nullptr ) override; + + void LoadContent( LINE_READER& aReader, SCH_SCREEN* aScreen, + int version = EESCHEMA_VERSION ); + + void Save( const wxString& aFileName, SCH_SCREEN* aScreen, KIWAY* aKiway, + const PROPERTIES* aProperties = nullptr ) override; + + void Format( SCH_SCREEN* aScreen ); + + void Format( SELECTION* aSelection, OUTPUTFORMATTER* aFormatter ); + + void EnumerateSymbolLib( wxArrayString& aSymbolNameList, + const wxString& aLibraryPath, + const PROPERTIES* aProperties = nullptr ) override; + void EnumerateSymbolLib( std::vector& aSymbolList, + const wxString& aLibraryPath, + const PROPERTIES* aProperties = nullptr ) override; + LIB_PART* LoadSymbol( const wxString& aLibraryPath, const wxString& aAliasName, + const PROPERTIES* aProperties = nullptr ) override; + void SaveSymbol( const wxString& aLibraryPath, const LIB_PART* aSymbol, + const PROPERTIES* aProperties = nullptr ) override; + void DeleteSymbol( const wxString& aLibraryPath, const wxString& aSymbolName, + const PROPERTIES* aProperties = nullptr ) override; + void CreateSymbolLib( const wxString& aLibraryPath, + const PROPERTIES* aProperties = nullptr ) override; + bool DeleteSymbolLib( const wxString& aLibraryPath, + const PROPERTIES* aProperties = nullptr ) override; + void SaveLibrary( const wxString& aLibraryPath, + const PROPERTIES* aProperties = nullptr ) override; + + bool CheckHeader( const wxString& aFileName ) override; + bool IsSymbolLibWritable( const wxString& aLibraryPath ) override; + + const wxString& GetError() const override { return m_error; } + + static LIB_PART* ParsePart( LINE_READER& aReader, int majorVersion = 0, int minorVersion = 0 ); + static void FormatPart( LIB_PART* aPart, OUTPUTFORMATTER& aFormatter ); + +private: + void loadHierarchy( SCH_SHEET* aSheet ); + void loadHeader( LINE_READER& aReader, SCH_SCREEN* aScreen ); + void loadPageSettings( LINE_READER& aReader, SCH_SCREEN* aScreen ); + void loadFile( const wxString& aFileName, SCH_SCREEN* aScreen ); + SCH_SHEET* loadSheet( LINE_READER& aReader ); + SCH_BITMAP* loadBitmap( LINE_READER& aReader ); + SCH_JUNCTION* loadJunction( LINE_READER& aReader ); + SCH_NO_CONNECT* loadNoConnect( LINE_READER& aReader ); + SCH_LINE* loadWire( LINE_READER& aReader ); + SCH_BUS_ENTRY_BASE* loadBusEntry( LINE_READER& aReader ); + SCH_TEXT* loadText( LINE_READER& aReader ); + SCH_COMPONENT* loadComponent( LINE_READER& aReader ); + std::shared_ptr loadBusAlias( LINE_READER& aReader, SCH_SCREEN* aScreen ); + + void saveComponent( SCH_COMPONENT* aComponent ); + void saveField( SCH_FIELD* aField ); + void saveBitmap( SCH_BITMAP* aBitmap ); + void saveSheet( SCH_SHEET* aSheet ); + void saveJunction( SCH_JUNCTION* aJunction ); + void saveNoConnect( SCH_NO_CONNECT* aNoConnect ); + void saveBusEntry( SCH_BUS_ENTRY_BASE* aBusEntry ); + void saveLine( SCH_LINE* aLine ); + void saveText( SCH_TEXT* aText ); + void saveBusAlias( std::shared_ptr aAlias ); + + void cacheLib( const wxString& aLibraryFileName ); + bool isBuffering( const PROPERTIES* aProperties ); + +protected: + int m_version; ///< Version of file being loaded. + + /** For throwing exceptions or errors on partial schematic loads. */ + wxString m_error; + + wxString m_path; ///< Root project path for loading child sheets. + std::stack m_currentPath;///< Stack to maintain nested sheet paths + const PROPERTIES* m_props; ///< Passed via Save() or Load(), no ownership, may be nullptr. + KIWAY* m_kiway; ///< Required for path to legacy component libraries. + SCH_SHEET* m_rootSheet; ///< The root sheet of the schematic being loaded.. + OUTPUTFORMATTER* m_out; ///< The output formatter for saving SCH_SCREEN objects. + SCH_SEXPR_PLUGIN_CACHE* m_cache; + + /// initialize PLUGIN like a constructor would. + void init( KIWAY* aKiway, const PROPERTIES* aProperties = nullptr ); +}; + +#endif // _SCH_SEXPR_PLUGIN_H_ diff --git a/eeschema/symbol_lib.keywords b/eeschema/symbol_lib.keywords new file mode 100644 index 0000000000..a69b3369bb --- /dev/null +++ b/eeschema/symbol_lib.keywords @@ -0,0 +1,59 @@ +alternate +anchor +arc +at +atomic +bezier +bidirectional +circle +clock +clock_low +color +edge_clock_high +end +extends +fill +hint_alt_swap +hint_pin_swap +input +input_low +inverted +inverted_clock +kicad_symbol_lib +length +line +mid +name +non_logic +number +open_collector +open_emitter +output_low +unconnected +output +passive +pin +pin_del +pin_merge +pin_rename +polyline +power +power_in +power_out +property +property_del +pts +radius +rectangle +required +shape +start +stroke +symbol +text +tri_state +type +unspecified +uuid +width +xy diff --git a/include/wildcards_and_files_ext.h b/include/wildcards_and_files_ext.h index ac94d8b18c..b5e5e90556 100644 --- a/include/wildcards_and_files_ext.h +++ b/include/wildcards_and_files_ext.h @@ -4,7 +4,7 @@ * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2007-2012 SoftPLC Corporation, Dick Hollenbeck * Copyright (C) 2008 Wayne Stambaugh - * Copyright (C) 1992-2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-2020 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 @@ -125,6 +125,7 @@ extern const std::string EquFileExtension; extern const std::string LegacyPcbFileExtension; extern const std::string KiCadPcbFileExtension; #define PcbFileExtension KiCadPcbFileExtension // symlink choice +extern const std::string KiCadSymbolLibFileExtension; extern const std::string PageLayoutDescrFileExtension; extern const std::string LegacyFootprintLibPathExtension; @@ -170,6 +171,7 @@ extern wxString AllFilesWildcard(); extern wxString ComponentFileWildcard(); extern wxString PageLayoutDescrFileWildcard(); extern wxString SchematicSymbolFileWildcard(); +extern wxString KiCadSymbolLibFileWildcard(); extern wxString SchematicLibraryFileWildcard(); extern wxString ProjectFileWildcard(); extern wxString SchematicFileWildcard(); diff --git a/qa/eeschema/test_lib_part.cpp b/qa/eeschema/test_lib_part.cpp index f2f2bd347c..045ced9b29 100644 --- a/qa/eeschema/test_lib_part.cpp +++ b/qa/eeschema/test_lib_part.cpp @@ -1,7 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 2019 KiCad Developers, see CHANGELOG.TXT for contributors. + * Copyright (C) 2019-2020 KiCad Developers, see CHANGELOG.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 @@ -39,7 +39,8 @@ class TEST_LIB_PART_FIXTURE { public: - TEST_LIB_PART_FIXTURE() : m_part_no_data( "part_name", nullptr ) + TEST_LIB_PART_FIXTURE() : + m_part_no_data( "part_name", nullptr ) { } @@ -368,6 +369,68 @@ BOOST_AUTO_TEST_CASE( Compare ) } +/** + * Check the fetch unit items code. + */ +BOOST_AUTO_TEST_CASE( GetUnitItems ) +{ + // There are no unit draw items in the empty LIB_PART object. + BOOST_CHECK( m_part_no_data.GetUnitItems( 1, 1 ).size() == 0 ); + + // A single unique unit with 1 pin common to all units and all body styles. + LIB_PIN* pin1 = new LIB_PIN( &m_part_no_data ); + m_part_no_data.AddDrawItem( pin1 ); + BOOST_CHECK( m_part_no_data.GetUnitItems( 0, 0 ).size() == 1 ); + + // A single unique unit with 1 pin in unit 1 and common to all body styles. + pin1->SetUnit( 1 ); + BOOST_CHECK( m_part_no_data.GetUnitItems( 1, 0 ).size() == 1 ); + + // A single unique unit with 1 pin in unit 1 and body style 1. + pin1->SetConvert( 1 ); + BOOST_CHECK( m_part_no_data.GetUnitItems( 1, 1 ).size() == 1 ); + + // Two unique units with pin 1 assigned to unit 1 and body style 1 and pin 2 assinged to + // unit 2 and body style 1. + LIB_PIN* pin2 = new LIB_PIN( &m_part_no_data ); + m_part_no_data.SetUnitCount( 2 ); + pin2->SetUnit( 2 ); + pin2->SetConvert( 2 ); + pin2->SetNumber( "4" ); + m_part_no_data.AddDrawItem( pin2 ); + BOOST_CHECK( m_part_no_data.GetUnitItems( 2, 2 ).size() == 1 ); + + // Make pin 1 body style common to all units. + pin1->SetConvert( 0 ); + BOOST_CHECK( m_part_no_data.GetUnitItems( 1, 1 ).size() == 0 ); + BOOST_CHECK( m_part_no_data.GetUnitItems( 2, 1 ).size() == 1 ); + + m_part_no_data.RemoveDrawItem( pin2 ); + m_part_no_data.RemoveDrawItem( pin1 ); + m_part_no_data.RemoveDrawItem( m_part_no_data.GetNextDrawItem() ); +} + + +/** + * Check the fetch unit draw items code. + */ +BOOST_AUTO_TEST_CASE( GetUnitDrawItems ) +{ + // There are no unit draw items in the empty LIB_PART object. + BOOST_CHECK( m_part_no_data.GetUnitDrawItems().size() == 0 ); + + // A single unique unit with 1 pin common to all units and all body styles. + LIB_PIN* pin1 = new LIB_PIN( &m_part_no_data ); + pin1->SetNumber( "1" ); + m_part_no_data.AddDrawItem( pin1 ); + std::vector units = m_part_no_data.GetUnitDrawItems(); + BOOST_CHECK( units.size() == 1 ); + BOOST_CHECK( units[0].m_unit == 0 ); + BOOST_CHECK( units[0].m_convert == 0 ); + BOOST_CHECK( units[0].m_items[0] == pin1 ); +} + + /** * Check inheritance support. */