From 4918ac63072e83f5a52a29576856be2fbfa17339 Mon Sep 17 00:00:00 2001 From: Alex Shvartzkop Date: Sat, 22 Jul 2023 08:55:49 +0500 Subject: [PATCH] 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 24452f41ad29888c9763d43237279df6ff1df360) --- common/plugins/altium/altium_parser.cpp | 156 +++++++++++++++--- common/plugins/altium/altium_parser.h | 32 +++- .../plugins/altium/altium_designer_plugin.cpp | 21 ++- pcbnew/plugins/altium/altium_parser_pcb.cpp | 6 +- pcbnew/plugins/altium/altium_pcb.cpp | 48 +++++- 5 files changed, 219 insertions(+), 44 deletions(-) diff --git a/common/plugins/altium/altium_parser.cpp b/common/plugins/altium/altium_parser.cpp index d059f54ce1..14a9975472 100644 --- a/common/plugins/altium/altium_parser.cpp +++ b/common/plugins/altium/altium_parser.cpp @@ -2,6 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019-2020 Thomas Pointhuber + * 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 ALTIUM_COMPOUND_FILE::ListLibFootprints() const { + std::map 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 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 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& 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& 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 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& aProps, return aDefault; } + + +wxString ALTIUM_PARSER::ReadUnicodeString( const std::map& 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 ); +} diff --git a/common/plugins/altium/altium_parser.h b/common/plugins/altium/altium_parser.h index f4e4286f06..f6303681a6 100644 --- a/common/plugins/altium/altium_parser.h +++ b/common/plugins/altium/altium_parser.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019-2020 Thomas Pointhuber - * 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 ListLibFootprints() const; + + wxString FindLibFootprintDirName( const wxString& aFpUnicodeName ) const; + const CFB::COMPOUND_FILE_ENTRY* FindStream( const std::vector& 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 m_reader; std::vector m_buffer; @@ -107,24 +115,31 @@ public: return result; } - wxString ReadWxString() + wxScopedCharBuffer ReadCharBuffer() { uint8_t len = Read(); 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 ReadWideStringTable() { std::map table; @@ -225,6 +240,9 @@ public: static wxString ReadString( const std::map& aProps, const wxString& aKey, const wxString& aDefault ); + static wxString ReadUnicodeString( const std::map& aProps, + const wxString& aKey, const wxString& aDefault ); + void Skip( size_t aLength ) { if( GetRemainingBytes() >= aLength ) diff --git a/pcbnew/plugins/altium/altium_designer_plugin.cpp b/pcbnew/plugins/altium/altium_designer_plugin.cpp index 05f25c892a..221fcf9325 100644 --- a/pcbnew/plugins/altium/altium_designer_plugin.cpp +++ b/pcbnew/plugins/altium/altium_designer_plugin.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 Thomas Pointhuber - * 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 patternMap = altiumLibFile.ListLibFootprints(); + const std::vector 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(); } diff --git a/pcbnew/plugins/altium/altium_parser_pcb.cpp b/pcbnew/plugins/altium/altium_parser_pcb.cpp index 613f23704a..ae6d82b0d9 100644 --- a/pcbnew/plugins/altium/altium_parser_pcb.cpp +++ b/pcbnew/plugins/altium/altium_parser_pcb.cpp @@ -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( "" ) ); diff --git a/pcbnew/plugins/altium/altium_pcb.cpp b/pcbnew/plugins/altium/altium_pcb.cpp index 8a9bba3bc5..1c668671ac 100644 --- a/pcbnew/plugins/altium/altium_pcb.cpp +++ b/pcbnew/plugins/altium/altium_pcb.cpp @@ -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 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 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 parametersStreamName{ aFootprintName.ToStdString(), + const std::vector 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 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( parser.Peek() ); @@ -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 );