kicad/eeschema/sch_plugins/kicad/sch_sexpr_lib_plugin_cache.cpp

556 lines
19 KiB
C++

/*
* 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 <base_units.h>
#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_fileFormatVersionAtLoad = 0;
}
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.
// Yes, we did this earlier, but it's sadly not thread-safe.
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();
SetFileFormatVersionAtLoad( parser.GetParsedRequiredVersion() );
}
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( const std::pair<const wxString, LIB_SYMBOL*>& 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( const std::pair<const wxString, LIB_SYMBOL*>& 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() != schIUScale.MilsToIU( DEFAULT_PIN_NAME_OFFSET )
|| !aSymbol->ShowPinNames() )
{
aFormatter.Print( 0, " (pin_names" );
if( aSymbol->GetPinNameOffset() != schIUScale.MilsToIU( DEFAULT_PIN_NAME_OFFSET ) )
aFormatter.Print( 0, " (offset %s)",
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, 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( const LIB_SYMBOL_UNIT& 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 );
// if the unit has a display name, write that
if( aSymbol->HasUnitDisplayName( unit.m_unit ) )
{
name = aSymbol->GetUnitDisplayName( unit.m_unit );
aFormatter.Print( aNestLevel + 2, "(unit_name %s)\n",
aFormatter.Quotes( name ).c_str() );
}
// 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( const wxString& 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 = shape->GetStroke();
FILL_T fillMode = shape->GetFillMode();
COLOR4D fillColor = shape->GetFillColor();
bool isPrivate = shape->IsPrivate();
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 (at %s %s %g)",
aFormatter.Quotew( fieldName ).c_str(),
aFormatter.Quotew( aField->GetText() ).c_str(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, aField->GetPosition().x ).c_str(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, aField->GetPosition().y ).c_str(),
aField->GetTextAngle().AsDegrees() );
if( aField->IsNameShown() )
aFormatter.Print( 0, " (show_name)" );
if( !aField->CanAutoplace() )
aFormatter.Print( 0, " (do_not_autoplace)" );
aFormatter.Print( 0, "\n" );
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() ),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, aPin->GetPosition().x ).c_str(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, aPin->GetPosition().y ).c_str(),
EDA_UNIT_UTILS::FormatAngle( getPinAngle( aPin->GetOrientation() ) ).c_str(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, 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(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, aPin->GetNameTextSize() ).c_str(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, aPin->GetNameTextSize() ).c_str() );
aFormatter.Print( aNestLevel + 1, "(number %s (effects (font (size %s %s))))\n",
aFormatter.Quotew( aPin->GetNumber() ).c_str(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, aPin->GetNumberTextSize() ).c_str(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, 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(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, aText->GetPosition().x ).c_str(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, 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() );
VECTOR2I pos = aTextBox->GetStart();
VECTOR2I size = aTextBox->GetEnd() - pos;
aFormatter.Print( aNestLevel + 1, "(at %s %s %s) (size %s %s)\n",
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, pos.x ).c_str(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, pos.y ).c_str(),
EDA_UNIT_UTILS::FormatAngle( aTextBox->GetTextAngle() ).c_str(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, size.x ).c_str(),
EDA_UNIT_UTILS::FormatInternalUnits( schIUScale, size.y ).c_str() );
aTextBox->GetStroke().Format( &aFormatter, schIUScale, 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;
}