Factor out common s-expression library cache source code.

This commit is contained in:
Wayne Stambaugh 2022-02-09 11:20:14 -05:00
parent 7181f0b40b
commit 3f8f90db9f
5 changed files with 619 additions and 712 deletions

View File

@ -247,6 +247,7 @@ set( EESCHEMA_SRCS
sch_plugins/sch_lib_plugin_cache.cpp
sch_plugins/eagle/sch_eagle_plugin.cpp
sch_plugins/kicad/sch_sexpr_lib_plugin_cache.cpp
sch_plugins/kicad/sch_sexpr_plugin_common.cpp
sch_plugins/kicad/sch_sexpr_parser.cpp
sch_plugins/kicad/sch_sexpr_plugin.cpp

View File

@ -0,0 +1,540 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Wayne Stambaugh <stambaughw@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <lib_field.h>
#include <lib_shape.h>
#include <lib_symbol.h>
#include <lib_text.h>
#include <lib_textbox.h>
#include <locale_io.h>
#include <macros.h>
#include <richio.h>
#include "sch_sexpr_lib_plugin_cache.h"
#include "sch_sexpr_plugin_common.h"
#include "sch_sexpr_parser.h"
#include <string_utils.h>
#include <trace_helpers.h>
SCH_SEXPR_PLUGIN_CACHE::SCH_SEXPR_PLUGIN_CACHE( const wxString& aFullPathAndFileName ) :
SCH_LIB_PLUGIN_CACHE( aFullPathAndFileName )
{
m_versionMajor = -1;
}
SCH_SEXPR_PLUGIN_CACHE::~SCH_SEXPR_PLUGIN_CACHE()
{
}
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() ) );
// The current locale must use period as the decimal point.
wxCHECK2( wxLocale::GetInfo( wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER ) == ".",
LOCALE_IO toggle );
wxLogTrace( traceSchLegacyPlugin, "Loading sexpr symbol library file '%s'",
m_libFileName.GetFullPath() );
FILE_LINE_READER reader( m_libFileName.GetFullPath() );
SCH_SEXPR_PARSER parser( &reader );
parser.ParseLib( m_symbols );
++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::Save( const std::optional<bool>& aOpt )
{
if( !m_isModified )
return;
LOCALE_IO toggle; // toggles on, then off, the C locale.
// Write through symlinks, don't replace them.
wxFileName fn = GetRealFile();
auto formatter = std::make_unique<FILE_OUTPUTFORMATTER>( fn.GetFullPath() );
formatter->Print( 0, "(kicad_symbol_lib (version %d) (generator kicad_symbol_editor)\n",
SEXPR_SYMBOL_LIB_FILE_VERSION );
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<LIB_SYMBOL> 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_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
int aNestLevel, const wxString& aLibName )
{
wxCHECK_RET( aSymbol, "Invalid LIB_SYMBOL pointer." );
// The current locale must use period as the decimal point.
wxCHECK2( wxLocale::GetInfo( wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER ) == ".",
LOCALE_IO toggle );
int nextFreeFieldId = MANDATORY_FIELDS;
std::vector<LIB_FIELD*> fields;
std::string name = aFormatter.Quotew( aSymbol->GetLibId().GetLibItemName().wx_str() );
std::string unitName = aSymbol->GetLibId().GetLibItemName();
if( !aLibName.IsEmpty() )
{
name = aFormatter.Quotew( aLibName );
LIB_ID unitId;
wxCHECK2( unitId.Parse( aLibName ) < 0, /* do nothing */ );
unitName = unitId.GetLibItemName();
}
if( aSymbol->IsRoot() )
{
aFormatter.Print( aNestLevel, "(symbol %s", name.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() != Mils2iu( DEFAULT_PIN_NAME_OFFSET )
|| !aSymbol->ShowPinNames() )
{
aFormatter.Print( 0, " (pin_names" );
if( aSymbol->GetPinNameOffset() != Mils2iu( DEFAULT_PIN_NAME_OFFSET ) )
aFormatter.Print( 0, " (offset %s)",
FormatInternalUnits( aSymbol->GetPinNameOffset() ).c_str() );
if( !aSymbol->ShowPinNames() )
aFormatter.Print( 0, " hide" );
aFormatter.Print( 0, ")" );
}
aFormatter.Print( 0, " (in_bom %s)", ( aSymbol->GetIncludeInBom() ) ? "yes" : "no" );
aFormatter.Print( 0, " (on_board %s)", ( aSymbol->GetIncludeOnBoard() ) ? "yes" : "no" );
// TODO: add atomic token here.
// TODO: add required token here."
aFormatter.Print( 0, "\n" );
aSymbol->GetFields( fields );
for( LIB_FIELD* field : fields )
saveField( field, aFormatter, aNestLevel + 1 );
nextFreeFieldId = aSymbol->GetNextAvailableFieldId();
// @todo At some point in the future the lock status (all units interchangeable) should
// be set deterministically. For now a custom lock property is used to preserve the
// locked flag state.
if( aSymbol->UnitsLocked() )
{
LIB_FIELD locked( nextFreeFieldId, "ki_locked" );
saveField( &locked, aFormatter, aNestLevel + 1 );
nextFreeFieldId += 1;
}
saveDcmInfoAsFields( aSymbol, aFormatter, nextFreeFieldId, aNestLevel );
// Save the draw items grouped by units.
std::vector<LIB_SYMBOL_UNIT> units = aSymbol->GetUnitDrawItems();
std::sort( units.begin(), units.end(),
[]( const LIB_SYMBOL_UNIT& a, const LIB_SYMBOL_UNIT& b )
{
if( a.m_unit == b.m_unit )
return a.m_convert < b.m_convert;
return a.m_unit < b.m_unit;
} );
for( auto unit : units )
{
// Add quotes and escape chars like ") to the UTF8 unitName string
name = aFormatter.Quotes( unitName );
name.pop_back(); // Remove last char: the quote ending the string.
aFormatter.Print( aNestLevel + 1, "(symbol %s_%d_%d\"\n",
name.c_str(), unit.m_unit, unit.m_convert );
// Enforce item ordering
auto cmp =
[]( const LIB_ITEM* a, const LIB_ITEM* b )
{
return *a < *b;
};
std::multiset<LIB_ITEM*, decltype( cmp )> save_map( cmp );
for( LIB_ITEM* item : unit.m_items )
save_map.insert( item );
for( LIB_ITEM* item : save_map )
saveSymbolDrawItem( item, aFormatter, aNestLevel + 2 );
aFormatter.Print( aNestLevel + 1, ")\n" );
}
}
else
{
std::shared_ptr<LIB_SYMBOL> parent = aSymbol->GetParent().lock();
wxASSERT( parent );
aFormatter.Print( aNestLevel, "(symbol %s (extends %s)\n",
name.c_str(),
aFormatter.Quotew( parent->GetName() ).c_str() );
aSymbol->GetFields( fields );
for( LIB_FIELD* field : fields )
saveField( field, aFormatter, aNestLevel + 1 );
nextFreeFieldId = aSymbol->GetNextAvailableFieldId();
saveDcmInfoAsFields( aSymbol, aFormatter, nextFreeFieldId, aNestLevel );
}
aFormatter.Print( aNestLevel, ")\n" );
}
void SCH_SEXPR_PLUGIN_CACHE::saveDcmInfoAsFields( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
int& aNextFreeFieldId, int aNestLevel )
{
wxCHECK_RET( aSymbol, "Invalid LIB_SYMBOL pointer." );
if( !aSymbol->GetKeyWords().IsEmpty() )
{
LIB_FIELD keywords( aNextFreeFieldId, wxString( "ki_keywords" ) );
keywords.SetVisible( false );
keywords.SetText( aSymbol->GetKeyWords() );
saveField( &keywords, aFormatter, aNestLevel + 1 );
aNextFreeFieldId += 1;
}
if( !aSymbol->GetDescription().IsEmpty() )
{
LIB_FIELD description( aNextFreeFieldId, wxString( "ki_description" ) );
description.SetVisible( false );
description.SetText( aSymbol->GetDescription() );
saveField( &description, aFormatter, aNestLevel + 1 );
aNextFreeFieldId += 1;
}
wxArrayString fpFilters = aSymbol->GetFPFilters();
if( !fpFilters.IsEmpty() )
{
wxString tmp;
for( auto filter : fpFilters )
{
// Spaces are not handled in fp filter names so escape spaces if any
wxString curr_filter = EscapeString( filter, ESCAPE_CONTEXT::CTX_NO_SPACE );
if( tmp.IsEmpty() )
tmp = curr_filter;
else
tmp += " " + curr_filter;
}
LIB_FIELD description( aNextFreeFieldId, wxString( "ki_fp_filters" ) );
description.SetVisible( false );
description.SetText( tmp );
saveField( &description, aFormatter, aNestLevel + 1 );
aNextFreeFieldId += 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_SHAPE_T:
{
LIB_SHAPE* shape = static_cast<LIB_SHAPE*>( aItem );
STROKE_PARAMS stroke;
FILL_T fillMode = shape->GetFillMode();
bool isPrivate = shape->IsPrivate();
stroke.SetWidth( shape->GetWidth() );
COLOR4D fillColor = shape->GetFillColor();
switch( shape->GetShape() )
{
case SHAPE_T::ARC:
formatArc( &aFormatter, aNestLevel, shape, isPrivate, stroke, fillMode, fillColor );
break;
case SHAPE_T::CIRCLE:
formatCircle( &aFormatter, aNestLevel, shape, isPrivate, stroke, fillMode, fillColor );
break;
case SHAPE_T::RECT:
formatRect( &aFormatter, aNestLevel, shape, isPrivate, stroke, fillMode, fillColor );
break;
case SHAPE_T::BEZIER:
formatBezier(&aFormatter, aNestLevel, shape, isPrivate, stroke, fillMode, fillColor );
break;
case SHAPE_T::POLY:
formatPoly( &aFormatter, aNestLevel, shape, isPrivate, stroke, fillMode, fillColor );
break;
default:
UNIMPLEMENTED_FOR( shape->SHAPE_T_asString() );
}
break;
}
case LIB_PIN_T:
savePin( static_cast<LIB_PIN*>( aItem ), aFormatter, aNestLevel );
break;
case LIB_TEXT_T:
saveText( static_cast<LIB_TEXT*>( aItem ), aFormatter, aNestLevel );
break;
case LIB_TEXTBOX_T:
saveTextBox( static_cast<LIB_TEXTBOX*>( aItem ), aFormatter, aNestLevel );
break;
default:
UNIMPLEMENTED_FOR( aItem->GetClass() );
}
}
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();
if( aField->GetId() >= 0 && aField->GetId() < MANDATORY_FIELDS )
fieldName = TEMPLATE_FIELDNAME::GetDefaultFieldName( aField->GetId(), false );
aFormatter.Print( aNestLevel, "(property %s %s (id %d) (at %s %s %g)\n",
aFormatter.Quotew( fieldName ).c_str(),
aFormatter.Quotew( aField->GetText() ).c_str(),
aField->GetId(),
FormatInternalUnits( aField->GetPosition().x ).c_str(),
FormatInternalUnits( aField->GetPosition().y ).c_str(),
aField->GetTextAngle().AsDegrees() );
aField->Format( &aFormatter, aNestLevel, 0 );
aFormatter.Print( aNestLevel, ")\n" );
}
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() ) ).c_str(),
FormatInternalUnits( aPin->GetLength() ).c_str() );
if( !aPin->IsVisible() )
aFormatter.Print( 0, " hide\n" );
else
aFormatter.Print( 0, "\n" );
// This follows the EDA_TEXT effects formatting for future expansion.
aFormatter.Print( aNestLevel + 1, "(name %s (effects (font (size %s %s))))\n",
aFormatter.Quotew( aPin->GetName() ).c_str(),
FormatInternalUnits( aPin->GetNameTextSize() ).c_str(),
FormatInternalUnits( aPin->GetNameTextSize() ).c_str() );
aFormatter.Print( aNestLevel + 1, "(number %s (effects (font (size %s %s))))\n",
aFormatter.Quotew( aPin->GetNumber() ).c_str(),
FormatInternalUnits( aPin->GetNumberTextSize() ).c_str(),
FormatInternalUnits( aPin->GetNumberTextSize() ).c_str() );
for( const std::pair<const wxString, LIB_PIN::ALT>& alt : aPin->GetAlternates() )
{
aFormatter.Print( aNestLevel + 1, "(alternate %s %s %s)\n",
aFormatter.Quotew( alt.second.m_Name ).c_str(),
getPinElectricalTypeToken( alt.second.m_Type ),
getPinShapeToken( alt.second.m_Shape ) );
}
aFormatter.Print( aNestLevel, ")\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 %s (at %s %s %g)\n",
aText->IsPrivate() ? " private" : "",
aFormatter.Quotew( aText->GetText() ).c_str(),
FormatInternalUnits( aText->GetPosition().x ).c_str(),
FormatInternalUnits( aText->GetPosition().y ).c_str(),
(double) aText->GetTextAngle().AsTenthsOfADegree() );
aText->EDA_TEXT::Format( &aFormatter, aNestLevel, 0 );
aFormatter.Print( aNestLevel, ")\n" );
}
void SCH_SEXPR_PLUGIN_CACHE::saveTextBox( LIB_TEXTBOX* aTextBox, OUTPUTFORMATTER& aFormatter,
int aNestLevel )
{
wxCHECK_RET( aTextBox && aTextBox->Type() == LIB_TEXTBOX_T, "Invalid LIB_TEXTBOX object." );
aFormatter.Print( aNestLevel, "(text_box%s %s\n",
aTextBox->IsPrivate() ? " private" : "",
aFormatter.Quotew( aTextBox->GetText() ).c_str() );
aFormatter.Print( aNestLevel + 1, "(start %s %s) (end %s %s)\n",
FormatInternalUnits( aTextBox->GetStart().x ).c_str(),
FormatInternalUnits( aTextBox->GetStart().y ).c_str(),
FormatInternalUnits( aTextBox->GetEnd().x ).c_str(),
FormatInternalUnits( aTextBox->GetEnd().y ).c_str() );
aTextBox->GetStroke().Format( &aFormatter, aNestLevel + 1 );
aFormatter.Print( 0, "\n" );
formatFill( &aFormatter, aNestLevel + 1, aTextBox->GetFillMode(), aTextBox->GetFillColor() );
aFormatter.Print( 0, "\n" );
aTextBox->EDA_TEXT::Format( &aFormatter, aNestLevel, 0 );
aFormatter.Print( aNestLevel, ")\n" );
}
void SCH_SEXPR_PLUGIN_CACHE::DeleteSymbol( const wxString& aSymbolName )
{
LIB_SYMBOL_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_SYMBOL* symbol = it->second;
if( symbol->IsRoot() )
{
LIB_SYMBOL* rootSymbol = symbol;
// Remove the root symbol and all its children.
m_symbols.erase( it );
LIB_SYMBOL_MAP::iterator it1 = m_symbols.begin();
while( it1 != m_symbols.end() )
{
if( it1->second->IsAlias()
&& it1->second->GetParent().lock() == rootSymbol->SharedPtr() )
{
delete it1->second;
it1 = m_symbols.erase( it1 );
}
else
{
it1++;
}
}
delete rootSymbol;
}
else
{
// Just remove the alias.
m_symbols.erase( it );
delete symbol;
}
++m_modHash;
m_isModified = true;
}

View File

@ -0,0 +1,76 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Wayne Stambaugh <stambaughw@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _SCH_SEXPR_LIB_PLUGIN_CACHE_
#define _SCH_SEXPR_LIB_PLUGIN_CACHE_
#include "../sch_lib_plugin_cache.h"
class FILE_LINE_READER;
class LIB_FIELD;
class LIB_ITEM;
class LIB_PIN;
class LIB_TEXT;
class LIB_TEXTBOX;
class LINE_READER;
class SCH_SEXPR_PLUGIN;
/**
* A cache assistant for the KiCad s-expression symbol libraries.
*/
class SCH_SEXPR_PLUGIN_CACHE : public SCH_LIB_PLUGIN_CACHE
{
public:
SCH_SEXPR_PLUGIN_CACHE( const wxString& aLibraryPath );
virtual ~SCH_SEXPR_PLUGIN_CACHE();
// 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( const std::optional<bool>& aOpt = std::nullopt ) override;
void Load() override;
void DeleteSymbol( const wxString& aName ) override;
static void SaveSymbol( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
int aNestLevel = 0, const wxString& aLibName = wxEmptyString );
private:
friend SCH_SEXPR_PLUGIN;
static void saveSymbolDrawItem( LIB_ITEM* aItem, OUTPUTFORMATTER& aFormatter,
int aNestLevel );
static void saveField( LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter, int aNestLevel );
static void savePin( LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter, int aNestLevel = 0 );
static void saveText( LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter, int aNestLevel = 0 );
static void saveTextBox( LIB_TEXTBOX* aTextBox, OUTPUTFORMATTER& aFormatter,
int aNestLevel = 0 );
static void saveDcmInfoAsFields( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
int& aNextFreeFieldId, int aNestLevel );
int m_versionMajor;
};
#endif // _SCH_SEXPR_LIB_PLUGIN_CACHE_

View File

@ -49,6 +49,7 @@ class SCH_BITMAP;
class SCH_BUS_WIRE_ENTRY;
class SCH_SYMBOL;
class SCH_FIELD;
class SCH_ITEM;
class SCH_SHAPE;
class SCH_JUNCTION;
class SCH_LINE;

View File

@ -56,6 +56,7 @@
#include <sch_file_versions.h>
#include <schematic_lexer.h>
#include <sch_plugins/kicad/sch_sexpr_parser.h>
#include "sch_sexpr_lib_plugin_cache.h"
#include "sch_sexpr_plugin_common.h"
#include <symbol_lib_table.h> // for PropPowerSymsOnly definition.
#include <ee_selection.h>
@ -71,80 +72,6 @@ using namespace TSCHEMATIC_T;
reader.LineNumber(), pos - reader.Line() )
/**
* A cache assistant for the symbol 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_SYMBOL_MAP m_symbols; // Map of names of #LIB_SYMBOL pointers.
bool m_isWritable;
bool m_isModified;
int m_versionMajor;
SCH_LIB_TYPE m_libType; // Is this cache a symbol or symbol library.
LIB_SYMBOL* removeSymbol( LIB_SYMBOL* aAlias );
static void saveSymbolDrawItem( LIB_ITEM* aItem, OUTPUTFORMATTER& aFormatter,
int aNestLevel );
static void saveField( LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter, int aNestLevel );
static void savePin( LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter, int aNestLevel = 0 );
static void saveText( LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter, int aNestLevel = 0 );
static void saveTextBox( LIB_TEXTBOX* aTextBox, OUTPUTFORMATTER& aFormatter,
int aNestLevel = 0 );
static void saveDcmInfoAsFields( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
int& aNextFreeFieldId, int aNestLevel );
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_SYMBOL* aSymbol );
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 void SaveSymbol( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
int aNestLevel = 0, const wxString& aLibName = wxEmptyString );
};
SCH_SEXPR_PLUGIN::SCH_SEXPR_PLUGIN() :
m_progressReporter( nullptr )
{
@ -1255,644 +1182,6 @@ void SCH_SEXPR_PLUGIN::saveInstances( const std::vector<SCH_SHEET_INSTANCE>& aSh
}
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_libType = SCH_LIB_TYPE::LT_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_SYMBOL_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 );
WX_FILENAME::ResolvePossibleSymlinks( fn );
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_SYMBOL* SCH_SEXPR_PLUGIN_CACHE::removeSymbol( LIB_SYMBOL* aSymbol )
{
wxCHECK_MSG( aSymbol != nullptr, nullptr, "NULL pointer cannot be removed from library." );
LIB_SYMBOL* firstChild = nullptr;
LIB_SYMBOL_MAP::iterator it = m_symbols.find( aSymbol->GetName() );
if( it == m_symbols.end() )
return nullptr;
// 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 == aSymbol, nullptr,
"Pointer mismatch while attempting to remove alias entry <" + aSymbol->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 symbol and make it the new root.
if( aSymbol->IsRoot() )
{
for( auto entry : m_symbols )
{
if( entry.second->IsAlias()
&& entry.second->GetParent().lock() == aSymbol->SharedPtr() )
{
firstChild = entry.second;
break;
}
}
if( firstChild )
{
for( LIB_ITEM& drawItem : aSymbol->GetDrawItems() )
{
if( drawItem.Type() == LIB_FIELD_T )
{
LIB_FIELD& field = static_cast<LIB_FIELD&>( drawItem );
if( firstChild->FindField( field.GetCanonicalName() ) )
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() == aSymbol->SharedPtr() )
entry.second->SetParent( firstChild );
}
}
}
m_symbols.erase( it );
delete aSymbol;
m_isModified = true;
++m_modHash;
return firstChild;
}
void SCH_SEXPR_PLUGIN_CACHE::AddSymbol( const LIB_SYMBOL* aSymbol )
{
// aSymbol is cloned in SYMBOL_LIB::AddSymbol(). The cache takes ownership of aSymbol.
wxString name = aSymbol->GetName();
LIB_SYMBOL_MAP::iterator it = m_symbols.find( name );
if( it != m_symbols.end() )
{
removeSymbol( it->second );
}
m_symbols[ name ] = const_cast< LIB_SYMBOL* >( aSymbol );
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() ) );
// The current locale must use period as the decimal point.
wxCHECK2( wxLocale::GetInfo( wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER ) == ".",
LOCALE_IO toggle );
wxLogTrace( traceSchLegacyPlugin, "Loading sexpr symbol library file '%s'",
m_libFileName.GetFullPath() );
FILE_LINE_READER reader( m_libFileName.GetFullPath() );
SCH_SEXPR_PARSER parser( &reader );
parser.ParseLib( m_symbols );
++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::Save()
{
if( !m_isModified )
return;
LOCALE_IO toggle; // toggles on, then off, the C locale.
// Write through symlinks, don't replace them.
wxFileName fn = GetRealFile();
auto formatter = std::make_unique<FILE_OUTPUTFORMATTER>( fn.GetFullPath() );
formatter->Print( 0, "(kicad_symbol_lib (version %d) (generator kicad_symbol_editor)\n",
SEXPR_SYMBOL_LIB_FILE_VERSION );
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<LIB_SYMBOL> 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_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
int aNestLevel, const wxString& aLibName )
{
wxCHECK_RET( aSymbol, "Invalid LIB_SYMBOL pointer." );
// The current locale must use period as the decimal point.
wxCHECK2( wxLocale::GetInfo( wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER ) == ".",
LOCALE_IO toggle );
int nextFreeFieldId = MANDATORY_FIELDS;
std::vector<LIB_FIELD*> fields;
std::string name = aFormatter.Quotew( aSymbol->GetLibId().GetLibItemName().wx_str() );
std::string unitName = aSymbol->GetLibId().GetLibItemName();
if( !aLibName.IsEmpty() )
{
name = aFormatter.Quotew( aLibName );
LIB_ID unitId;
wxCHECK2( unitId.Parse( aLibName ) < 0, /* do nothing */ );
unitName = unitId.GetLibItemName();
}
if( aSymbol->IsRoot() )
{
aFormatter.Print( aNestLevel, "(symbol %s", name.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() != Mils2iu( DEFAULT_PIN_NAME_OFFSET )
|| !aSymbol->ShowPinNames() )
{
aFormatter.Print( 0, " (pin_names" );
if( aSymbol->GetPinNameOffset() != Mils2iu( DEFAULT_PIN_NAME_OFFSET ) )
aFormatter.Print( 0, " (offset %s)",
FormatInternalUnits( aSymbol->GetPinNameOffset() ).c_str() );
if( !aSymbol->ShowPinNames() )
aFormatter.Print( 0, " hide" );
aFormatter.Print( 0, ")" );
}
aFormatter.Print( 0, " (in_bom %s)", ( aSymbol->GetIncludeInBom() ) ? "yes" : "no" );
aFormatter.Print( 0, " (on_board %s)", ( aSymbol->GetIncludeOnBoard() ) ? "yes" : "no" );
// TODO: add atomic token here.
// TODO: add required token here."
aFormatter.Print( 0, "\n" );
aSymbol->GetFields( fields );
for( LIB_FIELD* field : fields )
saveField( field, aFormatter, aNestLevel + 1 );
nextFreeFieldId = aSymbol->GetNextAvailableFieldId();
// @todo At some point in the future the lock status (all units interchangeable) should
// be set deterministically. For now a custom lock property is used to preserve the
// locked flag state.
if( aSymbol->UnitsLocked() )
{
LIB_FIELD locked( nextFreeFieldId, "ki_locked" );
saveField( &locked, aFormatter, aNestLevel + 1 );
nextFreeFieldId += 1;
}
saveDcmInfoAsFields( aSymbol, aFormatter, nextFreeFieldId, aNestLevel );
// Save the draw items grouped by units.
std::vector<LIB_SYMBOL_UNIT> units = aSymbol->GetUnitDrawItems();
std::sort( units.begin(), units.end(),
[]( const LIB_SYMBOL_UNIT& a, const LIB_SYMBOL_UNIT& b )
{
if( a.m_unit == b.m_unit )
return a.m_convert < b.m_convert;
return a.m_unit < b.m_unit;
} );
for( auto unit : units )
{
// Add quotes and escape chars like ") to the UTF8 unitName string
name = aFormatter.Quotes( unitName );
name.pop_back(); // Remove last char: the quote ending the string.
aFormatter.Print( aNestLevel + 1, "(symbol %s_%d_%d\"\n",
name.c_str(), unit.m_unit, unit.m_convert );
// Enforce item ordering
auto cmp =
[]( const LIB_ITEM* a, const LIB_ITEM* b )
{
return *a < *b;
};
std::multiset<LIB_ITEM*, decltype( cmp )> save_map( cmp );
for( LIB_ITEM* item : unit.m_items )
save_map.insert( item );
for( LIB_ITEM* item : save_map )
saveSymbolDrawItem( item, aFormatter, aNestLevel + 2 );
aFormatter.Print( aNestLevel + 1, ")\n" );
}
}
else
{
std::shared_ptr<LIB_SYMBOL> parent = aSymbol->GetParent().lock();
wxASSERT( parent );
aFormatter.Print( aNestLevel, "(symbol %s (extends %s)\n",
name.c_str(),
aFormatter.Quotew( parent->GetName() ).c_str() );
aSymbol->GetFields( fields );
for( LIB_FIELD* field : fields )
saveField( field, aFormatter, aNestLevel + 1 );
nextFreeFieldId = aSymbol->GetNextAvailableFieldId();
saveDcmInfoAsFields( aSymbol, aFormatter, nextFreeFieldId, aNestLevel );
}
aFormatter.Print( aNestLevel, ")\n" );
}
void SCH_SEXPR_PLUGIN_CACHE::saveDcmInfoAsFields( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
int& aNextFreeFieldId, int aNestLevel )
{
wxCHECK_RET( aSymbol, "Invalid LIB_SYMBOL pointer." );
if( !aSymbol->GetKeyWords().IsEmpty() )
{
LIB_FIELD keywords( aNextFreeFieldId, wxString( "ki_keywords" ) );
keywords.SetVisible( false );
keywords.SetText( aSymbol->GetKeyWords() );
saveField( &keywords, aFormatter, aNestLevel + 1 );
aNextFreeFieldId += 1;
}
if( !aSymbol->GetDescription().IsEmpty() )
{
LIB_FIELD description( aNextFreeFieldId, wxString( "ki_description" ) );
description.SetVisible( false );
description.SetText( aSymbol->GetDescription() );
saveField( &description, aFormatter, aNestLevel + 1 );
aNextFreeFieldId += 1;
}
wxArrayString fpFilters = aSymbol->GetFPFilters();
if( !fpFilters.IsEmpty() )
{
wxString tmp;
for( auto filter : fpFilters )
{
// Spaces are not handled in fp filter names so escape spaces if any
wxString curr_filter = EscapeString( filter, ESCAPE_CONTEXT::CTX_NO_SPACE );
if( tmp.IsEmpty() )
tmp = curr_filter;
else
tmp += " " + curr_filter;
}
LIB_FIELD description( aNextFreeFieldId, wxString( "ki_fp_filters" ) );
description.SetVisible( false );
description.SetText( tmp );
saveField( &description, aFormatter, aNestLevel + 1 );
aNextFreeFieldId += 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_SHAPE_T:
{
LIB_SHAPE* shape = static_cast<LIB_SHAPE*>( aItem );
STROKE_PARAMS stroke;
FILL_T fillMode = shape->GetFillMode();
bool isPrivate = shape->IsPrivate();
stroke.SetWidth( shape->GetWidth() );
COLOR4D fillColor = shape->GetFillColor();
switch( shape->GetShape() )
{
case SHAPE_T::ARC:
formatArc( &aFormatter, aNestLevel, shape, isPrivate, stroke, fillMode, fillColor );
break;
case SHAPE_T::CIRCLE:
formatCircle( &aFormatter, aNestLevel, shape, isPrivate, stroke, fillMode, fillColor );
break;
case SHAPE_T::RECT:
formatRect( &aFormatter, aNestLevel, shape, isPrivate, stroke, fillMode, fillColor );
break;
case SHAPE_T::BEZIER:
formatBezier(&aFormatter, aNestLevel, shape, isPrivate, stroke, fillMode, fillColor );
break;
case SHAPE_T::POLY:
formatPoly( &aFormatter, aNestLevel, shape, isPrivate, stroke, fillMode, fillColor );
break;
default:
UNIMPLEMENTED_FOR( shape->SHAPE_T_asString() );
}
break;
}
case LIB_PIN_T:
savePin( static_cast<LIB_PIN*>( aItem ), aFormatter, aNestLevel );
break;
case LIB_TEXT_T:
saveText( static_cast<LIB_TEXT*>( aItem ), aFormatter, aNestLevel );
break;
case LIB_TEXTBOX_T:
saveTextBox( static_cast<LIB_TEXTBOX*>( aItem ), aFormatter, aNestLevel );
break;
default:
UNIMPLEMENTED_FOR( aItem->GetClass() );
}
}
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();
if( aField->GetId() >= 0 && aField->GetId() < MANDATORY_FIELDS )
fieldName = TEMPLATE_FIELDNAME::GetDefaultFieldName( aField->GetId(), false );
aFormatter.Print( aNestLevel, "(property %s %s (id %d) (at %s %s %g)\n",
aFormatter.Quotew( fieldName ).c_str(),
aFormatter.Quotew( aField->GetText() ).c_str(),
aField->GetId(),
FormatInternalUnits( aField->GetPosition().x ).c_str(),
FormatInternalUnits( aField->GetPosition().y ).c_str(),
aField->GetTextAngle().AsDegrees() );
aField->Format( &aFormatter, aNestLevel, 0 );
aFormatter.Print( aNestLevel, ")\n" );
}
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() ) ).c_str(),
FormatInternalUnits( aPin->GetLength() ).c_str() );
if( !aPin->IsVisible() )
aFormatter.Print( 0, " hide\n" );
else
aFormatter.Print( 0, "\n" );
// This follows the EDA_TEXT effects formatting for future expansion.
aFormatter.Print( aNestLevel + 1, "(name %s (effects (font (size %s %s))))\n",
aFormatter.Quotew( aPin->GetName() ).c_str(),
FormatInternalUnits( aPin->GetNameTextSize() ).c_str(),
FormatInternalUnits( aPin->GetNameTextSize() ).c_str() );
aFormatter.Print( aNestLevel + 1, "(number %s (effects (font (size %s %s))))\n",
aFormatter.Quotew( aPin->GetNumber() ).c_str(),
FormatInternalUnits( aPin->GetNumberTextSize() ).c_str(),
FormatInternalUnits( aPin->GetNumberTextSize() ).c_str() );
for( const std::pair<const wxString, LIB_PIN::ALT>& alt : aPin->GetAlternates() )
{
aFormatter.Print( aNestLevel + 1, "(alternate %s %s %s)\n",
aFormatter.Quotew( alt.second.m_Name ).c_str(),
getPinElectricalTypeToken( alt.second.m_Type ),
getPinShapeToken( alt.second.m_Shape ) );
}
aFormatter.Print( aNestLevel, ")\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 %s (at %s %s %g)\n",
aText->IsPrivate() ? " private" : "",
aFormatter.Quotew( aText->GetText() ).c_str(),
FormatInternalUnits( aText->GetPosition().x ).c_str(),
FormatInternalUnits( aText->GetPosition().y ).c_str(),
(double) aText->GetTextAngle().AsTenthsOfADegree() );
aText->EDA_TEXT::Format( &aFormatter, aNestLevel, 0 );
aFormatter.Print( aNestLevel, ")\n" );
}
void SCH_SEXPR_PLUGIN_CACHE::saveTextBox( LIB_TEXTBOX* aTextBox, OUTPUTFORMATTER& aFormatter,
int aNestLevel )
{
wxCHECK_RET( aTextBox && aTextBox->Type() == LIB_TEXTBOX_T, "Invalid LIB_TEXTBOX object." );
aFormatter.Print( aNestLevel, "(text_box%s %s\n",
aTextBox->IsPrivate() ? " private" : "",
aFormatter.Quotew( aTextBox->GetText() ).c_str() );
aFormatter.Print( aNestLevel + 1, "(start %s %s) (end %s %s)\n",
FormatInternalUnits( aTextBox->GetStart().x ).c_str(),
FormatInternalUnits( aTextBox->GetStart().y ).c_str(),
FormatInternalUnits( aTextBox->GetEnd().x ).c_str(),
FormatInternalUnits( aTextBox->GetEnd().y ).c_str() );
aTextBox->GetStroke().Format( &aFormatter, aNestLevel + 1 );
aFormatter.Print( 0, "\n" );
formatFill( &aFormatter, aNestLevel + 1, aTextBox->GetFillMode(), aTextBox->GetFillColor() );
aFormatter.Print( 0, "\n" );
aTextBox->EDA_TEXT::Format( &aFormatter, aNestLevel, 0 );
aFormatter.Print( aNestLevel, ")\n" );
}
void SCH_SEXPR_PLUGIN_CACHE::DeleteSymbol( const wxString& aSymbolName )
{
LIB_SYMBOL_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_SYMBOL* symbol = it->second;
if( symbol->IsRoot() )
{
LIB_SYMBOL* rootSymbol = symbol;
// Remove the root symbol and all its children.
m_symbols.erase( it );
LIB_SYMBOL_MAP::iterator it1 = m_symbols.begin();
while( it1 != m_symbols.end() )
{
if( it1->second->IsAlias()
&& it1->second->GetParent().lock() == rootSymbol->SharedPtr() )
{
delete it1->second;
it1 = m_symbols.erase( it1 );
}
else
{
it1++;
}
}
delete rootSymbol;
}
else
{
// Just remove the alias.
m_symbols.erase( it );
delete symbol;
}
++m_modHash;
m_isModified = true;
}
void SCH_SEXPR_PLUGIN::cacheLib( const wxString& aLibraryFileName, const PROPERTIES* aProperties )
{
if( !m_cache || !m_cache->IsFile( aLibraryFileName ) || m_cache->IsFileChanged() )