Improvements to Altium PCB/footprint importer:

- Support unicode footprint names
- Set default footprint text size and thickness to match KLC
- Position Reference at the top, Value at the bottom of fp bounding box

(cherry picked from commit 24452f41ad)
This commit is contained in:
Alex Shvartzkop 2023-07-22 08:55:49 +05:00
parent acc03e91f3
commit 4918ac6307
5 changed files with 219 additions and 44 deletions

View File

@ -2,6 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2019-2020 Thomas Pointhuber <thomas.pointhuber@gmx.at>
* Copyright (C) 2023 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
@ -88,26 +89,106 @@ ALTIUM_COMPOUND_FILE::ALTIUM_COMPOUND_FILE( const wxString& aFilePath )
}
static const CFB::COMPOUND_FILE_ENTRY*
FindStreamSingleLevel( const CFB::CompoundFileReader& aReader,
const CFB::COMPOUND_FILE_ENTRY* aEntry, const std::string aName,
const bool aIsStream )
std::map<wxString, wxString> ALTIUM_COMPOUND_FILE::ListLibFootprints() const
{
std::map<wxString, wxString> patternMap;
if( !m_reader )
return patternMap;
const CFB::COMPOUND_FILE_ENTRY* root = m_reader->GetRootEntry();
if( !root )
return patternMap;
m_reader->EnumFiles( root, 2,
[&]( const CFB::COMPOUND_FILE_ENTRY* entry, const CFB::utf16string& dir,
int level ) -> void
{
std::wstring dirName = UTF16ToWstring( dir.data(), dir.size() );
std::wstring fileName = UTF16ToWstring( entry->name, entry->nameLen );
if( m_reader->IsStream( entry ) && fileName == L"Parameters" )
{
ALTIUM_PARSER parametersReader( *this, entry );
std::map<wxString, wxString> parameterProperties =
parametersReader.ReadProperties();
wxString key = ALTIUM_PARSER::ReadString(
parameterProperties, wxT( "PATTERN" ), wxT( "" ) );
wxString fpName = ALTIUM_PARSER::ReadUnicodeString(
parameterProperties, wxT( "PATTERN" ), wxT( "" ) );
patternMap.emplace( key, fpName );
}
} );
return patternMap;
}
wxString ALTIUM_COMPOUND_FILE::FindLibFootprintDirName( const wxString& aFpUnicodeName ) const
{
if( !m_reader )
return wxEmptyString;
const CFB::COMPOUND_FILE_ENTRY* root = m_reader->GetRootEntry();
if( !root )
return wxEmptyString;
wxString ret;
m_reader->EnumFiles( root, 2,
[&]( const CFB::COMPOUND_FILE_ENTRY* entry, const CFB::utf16string& dir,
int level ) -> void
{
std::wstring dirName = UTF16ToWstring( dir.data(), dir.size() );
std::wstring fileName = UTF16ToWstring( entry->name, entry->nameLen );
if( m_reader->IsStream( entry ) && fileName == L"Parameters" )
{
ALTIUM_PARSER parametersReader( *this, entry );
std::map<wxString, wxString> parameterProperties =
parametersReader.ReadProperties();
wxString fpName = ALTIUM_PARSER::ReadUnicodeString(
parameterProperties, wxT( "PATTERN" ), wxT( "" ) );
if( fpName == aFpUnicodeName )
{
ret = dirName;
}
}
} );
return ret;
}
const CFB::COMPOUND_FILE_ENTRY*
ALTIUM_COMPOUND_FILE::FindStreamSingleLevel( const CFB::COMPOUND_FILE_ENTRY* aEntry,
const std::string aName, const bool aIsStream ) const
{
if( !m_reader || !aEntry )
return nullptr;
const CFB::COMPOUND_FILE_ENTRY* ret = nullptr;
aReader.EnumFiles( aEntry, 1,
[&]( const CFB::COMPOUND_FILE_ENTRY* entry, const CFB::utf16string& dir,
int level ) -> void
{
if( aReader.IsStream( entry ) == aIsStream )
{
std::string name = UTF16ToUTF8( entry->name );
if( name == aName.c_str() )
{
ret = entry;
}
}
} );
m_reader->EnumFiles( aEntry, 1,
[&]( const CFB::COMPOUND_FILE_ENTRY* entry, const CFB::utf16string& dir,
int level ) -> void
{
if( m_reader->IsStream( entry ) == aIsStream )
{
std::string name = UTF16ToUTF8( entry->name );
if( name == aName.c_str() )
{
ret = entry;
}
}
} );
return ret;
}
@ -116,9 +197,7 @@ const CFB::COMPOUND_FILE_ENTRY*
ALTIUM_COMPOUND_FILE::FindStream( const std::vector<std::string>& aStreamPath ) const
{
if( !m_reader )
{
return nullptr;
}
const CFB::COMPOUND_FILE_ENTRY* currentDirEntry = m_reader->GetRootEntry();
@ -129,12 +208,11 @@ ALTIUM_COMPOUND_FILE::FindStream( const std::vector<std::string>& aStreamPath )
if( ++it == aStreamPath.cend() )
{
return FindStreamSingleLevel( *m_reader.get(), currentDirEntry, name, true );
return FindStreamSingleLevel( currentDirEntry, name, true );
}
else
{
currentDirEntry =
FindStreamSingleLevel( *m_reader.get(), currentDirEntry, name, false );
currentDirEntry = FindStreamSingleLevel( currentDirEntry, name, false );
}
}
@ -232,9 +310,12 @@ std::map<wxString, wxString> ALTIUM_PARSER::ReadProperties()
else
value = wxString( valueS.c_str(), wxConvISO8859_1 );
// Breathless hack because I haven't a clue what the story is here (but this character
// appears in a lot of radial dimensions and is rendered by Altium as a space).
value.Replace( wxT( "ÿ" ), wxT( " " ) );
if( canonicalKey != wxS( "PATTERN" ) && canonicalKey != wxS( "SOURCEFOOTPRINTLIBRARY" ) )
{
// Breathless hack because I haven't a clue what the story is here (but this character
// appears in a lot of radial dimensions and is rendered by Altium as a space).
value.Replace( wxT( "ÿ" ), wxT( " " ) );
}
if( canonicalKey == wxT( "DESIGNATOR" )
|| canonicalKey == wxT( "NAME" )
@ -341,3 +422,28 @@ wxString ALTIUM_PARSER::ReadString( const std::map<wxString, wxString>& aProps,
return aDefault;
}
wxString ALTIUM_PARSER::ReadUnicodeString( const std::map<wxString, wxString>& aProps,
const wxString& aKey, const wxString& aDefault )
{
const auto& unicodeFlag = aProps.find( wxS( "UNICODE" ) );
if( unicodeFlag != aProps.end() && unicodeFlag->second.Contains( wxS( "EXISTS" ) ) )
{
const auto& unicodeValue = aProps.find( wxString( "UNICODE__" ) + aKey );
if( unicodeValue != aProps.end() )
{
wxArrayString arr = wxSplit( unicodeValue->second, ',', '\0' );
wxString out;
for( wxString part : arr )
out += wxString( wchar_t( wxAtoi( part ) ) );
return out;
}
}
return ReadString( aProps, aKey, aDefault );
}

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2019-2020 Thomas Pointhuber <thomas.pointhuber@gmx.at>
* Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2020-2023 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
@ -63,8 +63,16 @@ public:
const CFB::CompoundFileReader& GetCompoundFileReader() const { return *m_reader; }
std::map<wxString, wxString> ListLibFootprints() const;
wxString FindLibFootprintDirName( const wxString& aFpUnicodeName ) const;
const CFB::COMPOUND_FILE_ENTRY* FindStream( const std::vector<std::string>& aStreamPath ) const;
const CFB::COMPOUND_FILE_ENTRY* FindStreamSingleLevel( const CFB::COMPOUND_FILE_ENTRY* aEntry,
const std::string aName,
const bool aIsStream ) const;
private:
std::unique_ptr<CFB::CompoundFileReader> m_reader;
std::vector<char> m_buffer;
@ -107,24 +115,31 @@ public:
return result;
}
wxString ReadWxString()
wxScopedCharBuffer ReadCharBuffer()
{
uint8_t len = Read<uint8_t>();
if( GetRemainingBytes() >= len )
{
// TODO: Identify where the actual code page is stored. For now, this default code page
// has limited impact, because recent Altium files come with a UTF16 string table
wxString val = wxString( m_pos, wxConvISO8859_1, len );
char* buf = new char[len];
memcpy( buf, m_pos, len );
m_pos += len;
return val;
return wxScopedCharBuffer::CreateOwned( buf, len );
}
else
{
m_error = true;
return wxString( "" );
return wxScopedCharBuffer();
}
}
wxString ReadWxString()
{
// TODO: Identify where the actual code page is stored. For now, this default code page
// has limited impact, because recent Altium files come with a UTF16 string table
return wxString( ReadCharBuffer(), wxConvISO8859_1 );
}
std::map<uint32_t, wxString> ReadWideStringTable()
{
std::map<uint32_t, wxString> table;
@ -225,6 +240,9 @@ public:
static wxString ReadString( const std::map<wxString, wxString>& aProps,
const wxString& aKey, const wxString& aDefault );
static wxString ReadUnicodeString( const std::map<wxString, wxString>& aProps,
const wxString& aKey, const wxString& aDefault );
void Skip( size_t aLength )
{
if( GetRemainingBytes() >= aLength )

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2019 Thomas Pointhuber <thomas.pointhuber@gmx.at>
* Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2020-2023 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
@ -144,6 +144,9 @@ void ALTIUM_DESIGNER_PLUGIN::FootprintEnumerate( wxArrayString& aFootprintNames
try
{
// Map code-page-dependent names to unicode names
std::map<wxString, wxString> patternMap = altiumLibFile.ListLibFootprints();
const std::vector<std::string> streamName = { "Library", "Data" };
const CFB::COMPOUND_FILE_ENTRY* libraryData = altiumLibFile.FindStream( streamName );
if( libraryData == nullptr )
@ -161,8 +164,20 @@ void ALTIUM_DESIGNER_PLUGIN::FootprintEnumerate( wxArrayString& aFootprintNames
for( size_t i = 0; i < numberOfFootprints; i++ )
{
parser.ReadAndSetSubrecordLength();
wxString footprintName = parser.ReadWxString();
aFootprintNames.Add( footprintName );
wxScopedCharBuffer charBuffer = parser.ReadCharBuffer();
wxString fpPattern( charBuffer, wxConvISO8859_1 );
auto it = patternMap.find( fpPattern );
if( it != patternMap.end() )
{
aFootprintNames.Add( it->second ); // Proper unicode name
}
else
{
THROW_IO_ERROR( wxString::Format( "Component name not found: '%s'", fpPattern ) );
}
parser.SkipSubrecord();
}

View File

@ -331,8 +331,10 @@ ACOMPONENT6::ACOMPONENT6( ALTIUM_PARSER& aReader )
nameon = ALTIUM_PARSER::ReadBool( props, wxT( "NAMEON" ), true );
commenton = ALTIUM_PARSER::ReadBool( props, wxT( "COMMENTON" ), false );
sourcedesignator = ALTIUM_PARSER::ReadString( props, wxT( "SOURCEDESIGNATOR" ), wxT( "" ) );
sourcefootprintlibrary = ALTIUM_PARSER::ReadString( props, wxT( "SOURCEFOOTPRINTLIBRARY" ), wxT( "" ) );
pattern = ALTIUM_PARSER::ReadString( props, wxT( "PATTERN" ), wxT( "" ) );
sourcefootprintlibrary =
ALTIUM_PARSER::ReadUnicodeString( props, wxT( "SOURCEFOOTPRINTLIBRARY" ), wxT( "" ) );
pattern = ALTIUM_PARSER::ReadUnicodeString( props, wxT( "PATTERN" ), wxT( "" ) );
sourcecomponentlibrary = ALTIUM_PARSER::ReadString( props, wxT( "SOURCECOMPONENTLIBRARY" ), wxT( "" ) );
sourcelibreference = ALTIUM_PARSER::ReadString( props, wxT( "SOURCELIBREFERENCE" ), wxT( "" ) );

View File

@ -666,6 +666,7 @@ FOOTPRINT* ALTIUM_PCB::ParseFootprint( const ALTIUM_COMPOUND_FILE& altiumLibFile
m_unicodeStrings.clear();
m_extendedPrimitiveInformationMaps.clear();
// TODO: WideStrings are stored as parameterMap in the case of footprints, not as binary
// std::string unicodeStringsStreamName = aFootprintName.ToStdString() + "\\WideStrings";
// const CFB::COMPOUND_FILE_ENTRY* unicodeStringsData = altiumLibFile.FindStream( unicodeStringsStreamName );
@ -674,7 +675,15 @@ FOOTPRINT* ALTIUM_PCB::ParseFootprint( const ALTIUM_COMPOUND_FILE& altiumLibFile
// ParseWideStrings6Data( altiumLibFile, unicodeStringsData );
// }
const std::vector<std::string> streamName{ aFootprintName.ToStdString(), "Data" };
wxString fpDirName = altiumLibFile.FindLibFootprintDirName(aFootprintName);
if( fpDirName.IsEmpty() )
{
THROW_IO_ERROR(
wxString::Format( _( "Footprint directory not found: '%s'." ), aFootprintName ) );
}
const std::vector<std::string> streamName{ fpDirName.ToStdString(), "Data" };
const CFB::COMPOUND_FILE_ENTRY* footprintData = altiumLibFile.FindStream( streamName );
if( footprintData == nullptr )
@ -686,13 +695,13 @@ FOOTPRINT* ALTIUM_PCB::ParseFootprint( const ALTIUM_COMPOUND_FILE& altiumLibFile
ALTIUM_PARSER parser( altiumLibFile, footprintData );
parser.ReadAndSetSubrecordLength();
wxString footprintName = parser.ReadWxString();
//wxString footprintName = parser.ReadWxString(); // Not used (single-byte char set)
parser.SkipSubrecord();
LIB_ID fpID = AltiumToKiCadLibID( "", footprintName ); // TODO: library name
LIB_ID fpID = AltiumToKiCadLibID( "", aFootprintName ); // TODO: library name
footprint->SetFPID( fpID );
const std::vector<std::string> parametersStreamName{ aFootprintName.ToStdString(),
const std::vector<std::string> parametersStreamName{ fpDirName.ToStdString(),
"Parameters" };
const CFB::COMPOUND_FILE_ENTRY* parametersData =
altiumLibFile.FindStream( parametersStreamName );
@ -713,7 +722,7 @@ FOOTPRINT* ALTIUM_PCB::ParseFootprint( const ALTIUM_COMPOUND_FILE& altiumLibFile
}
const std::vector<std::string> extendedPrimitiveInformationStreamName{
aFootprintName.ToStdString(), "ExtendedPrimitiveInformation", "Data"
fpDirName.ToStdString(), "ExtendedPrimitiveInformation", "Data"
};
const CFB::COMPOUND_FILE_ENTRY* extendedPrimitiveInformationData =
altiumLibFile.FindStream( extendedPrimitiveInformationStreamName );
@ -722,10 +731,19 @@ FOOTPRINT* ALTIUM_PCB::ParseFootprint( const ALTIUM_COMPOUND_FILE& altiumLibFile
ParseExtendedPrimitiveInformationData( altiumLibFile, extendedPrimitiveInformationData );
footprint->SetReference( wxT( "REF**" ) );
footprint->SetValue( footprintName );
footprint->SetValue( aFootprintName );
footprint->Reference().SetVisible( true ); // TODO: extract visibility information
footprint->Value().SetVisible( true );
const VECTOR2I defaultTextSize( pcbIUScale.mmToIU( 1.0 ), pcbIUScale.mmToIU( 1.0 ) );
const int defaultTextThickness( pcbIUScale.mmToIU( 0.15 ) );
footprint->Reference().SetTextSize( defaultTextSize );
footprint->Reference().SetTextThickness( defaultTextThickness );
footprint->Value().SetTextSize( defaultTextSize );
footprint->Value().SetTextThickness( defaultTextThickness );
for( int primitiveIndex = 0; parser.GetRemainingBytes() >= 4; primitiveIndex++ )
{
ALTIUM_RECORD recordtype = static_cast<ALTIUM_RECORD>( parser.Peek<uint8_t>() );
@ -785,6 +803,22 @@ FOOTPRINT* ALTIUM_PCB::ParseFootprint( const ALTIUM_COMPOUND_FILE& altiumLibFile
}
}
// Auto-position reference and value
BOX2I bbox = footprint.get()->GetBoundingBox( false, false );
bbox.Inflate( pcbIUScale.mmToIU( 0.2 ) ); // Gap between graphics and text
if( footprint->Reference().GetPosition() == VECTOR2I( 0, 0 ) )
{
footprint->Reference().SetX( bbox.GetCenter().x );
footprint->Reference().SetY( bbox.GetTop() - footprint->Reference().GetTextSize().y / 2 );
}
if( footprint->Value().GetPosition() == VECTOR2I( 0, 0 ) )
{
footprint->Value().SetX( bbox.GetCenter().x );
footprint->Value().SetY( bbox.GetBottom() + footprint->Value().GetTextSize().y / 2 );
}
if( parser.HasParsingError() )
{
THROW_IO_ERROR( wxString::Format( wxT( "%s stream was not parsed correctly" ),
@ -2158,7 +2192,7 @@ void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItemOnLayer( FOOTPRINT*
}
else
{
PCB_SHAPE* shape = new PCB_SHAPE( aFootprint, SHAPE_T::POLY );
FP_SHAPE* shape = new FP_SHAPE( aFootprint, SHAPE_T::POLY );
shape->SetPolyShape( polySet );
shape->SetFilled( true );