From 2cc9517cfdbb2e04fa1bd32b85bab9b73129cf69 Mon Sep 17 00:00:00 2001 From: Thomas Pointhuber Date: Thu, 6 Jan 2022 15:01:05 +0100 Subject: [PATCH] altium: Allow extraction of names of footprints stored in *.PcbLib file See: https://gitlab.com/kicad/code/kicad/-/issues/10274 --- common/plugins/altium/altium_parser.h | 4 +- common/wildcards_and_files_ext.cpp | 7 ++ include/wildcards_and_files_ext.h | 2 + pcbnew/dialogs/panel_fp_lib_table.cpp | 7 ++ pcbnew/io_mgr.cpp | 4 ++ .../plugins/altium/altium_designer_plugin.cpp | 34 ++++++++++ .../plugins/altium/altium_designer_plugin.h | 17 +++-- pcbnew/plugins/altium/altium_pcb.cpp | 67 +++++++++++++++++-- pcbnew/plugins/altium/altium_pcb.h | 8 +++ 9 files changed, 137 insertions(+), 13 deletions(-) diff --git a/common/plugins/altium/altium_parser.h b/common/plugins/altium/altium_parser.h index 1302d97c72..a77f040dab 100644 --- a/common/plugins/altium/altium_parser.h +++ b/common/plugins/altium/altium_parser.h @@ -54,7 +54,8 @@ public: template Type Read() { - if( GetRemainingBytes() >= sizeof( Type ) ) + const size_t remainingBytes = GetRemainingBytes(); + if( remainingBytes >= sizeof( Type ) ) { Type val = *(Type*) ( m_pos ); m_pos += sizeof( Type ); @@ -62,6 +63,7 @@ public: } else { + m_pos += remainingBytes; // Ensure remaining bytes are zero m_error = true; return 0; } diff --git a/common/wildcards_and_files_ext.cpp b/common/wildcards_and_files_ext.cpp index 66bb282ad9..a6da68fc15 100644 --- a/common/wildcards_and_files_ext.cpp +++ b/common/wildcards_and_files_ext.cpp @@ -156,6 +156,7 @@ const std::string FootprintPlaceFileExtension( "pos" ); const std::string KiCadFootprintLibPathExtension( "pretty" ); // this is a directory const std::string LegacyFootprintLibPathExtension( "mod" ); // this is a file +const std::string AltiumFootprintLibPathExtension( "PcbLib" ); // this is a file const std::string EagleFootprintLibPathExtension( "lbr" ); // this is a file const std::string GedaPcbFootprintLibFileExtension( "fp" ); // this is a file @@ -361,6 +362,12 @@ wxString LegacyFootprintLibPathWildcard() } +wxString AltiumFootprintLibPathWildcard() +{ + return _( "Altium PCB footprint library files" ) + AddFileExtListToFilter( { "PcbLib" } ); +} + + wxString EagleFootprintLibPathWildcard() { return _( "Eagle ver. 6.x XML library files" ) + AddFileExtListToFilter( { "lbr" } ); diff --git a/include/wildcards_and_files_ext.h b/include/wildcards_and_files_ext.h index c842ce33df..537055ee4f 100644 --- a/include/wildcards_and_files_ext.h +++ b/include/wildcards_and_files_ext.h @@ -147,6 +147,7 @@ extern const std::string ReportFileExtension; extern const std::string FootprintPlaceFileExtension; extern const std::string KiCadFootprintFileExtension; extern const std::string KiCadFootprintLibPathExtension; +extern const std::string AltiumFootprintLibPathExtension; extern const std::string GedaPcbFootprintLibFileExtension; extern const std::string EagleFootprintLibPathExtension; extern const std::string DrawingSheetFileExtension; @@ -223,6 +224,7 @@ extern wxString DocModulesFileName(); extern wxString LegacyFootprintLibPathWildcard(); extern wxString KiCadFootprintLibFileWildcard(); extern wxString KiCadFootprintLibPathWildcard(); +extern wxString AltiumFootprintLibPathWildcard(); extern wxString GedaPcbFootprintLibFileWildcard(); extern wxString EagleFootprintLibPathWildcard(); extern wxString TextFileWildcard(); diff --git a/pcbnew/dialogs/panel_fp_lib_table.cpp b/pcbnew/dialogs/panel_fp_lib_table.cpp index 6532c33b60..64df20bbd5 100644 --- a/pcbnew/dialogs/panel_fp_lib_table.cpp +++ b/pcbnew/dialogs/panel_fp_lib_table.cpp @@ -84,6 +84,7 @@ struct SUPPORTED_FILE_TYPE */ enum { ID_PANEL_FPLIB_ADD_KICADMOD = ID_PCBNEW_END_LIST, + ID_PANEL_FPLIB_ADD_ALTIUM, ID_PANEL_FPLIB_ADD_EAGLE6, ID_PANEL_FPLIB_ADD_KICADLEGACY, ID_PANEL_FPLIB_ADD_GEDA, @@ -111,6 +112,11 @@ static const std::map& fileTypes() false, IO_MGR::KICAD_SEXP } }, + { ID_PANEL_FPLIB_ADD_ALTIUM, + { + "Altium (*.PcbLib)", AltiumFootprintLibPathWildcard(), "", true, IO_MGR::ALTIUM_DESIGNER + } + }, { ID_PANEL_FPLIB_ADD_EAGLE6, { "Eagle 6.x (*.lbr)", EagleFootprintLibPathWildcard(), "", true, IO_MGR::EAGLE @@ -369,6 +375,7 @@ PANEL_FP_LIB_TABLE::PANEL_FP_LIB_TABLE( DIALOG_EDIT_LIBRARY_TABLES* aParent, choices.Add( IO_MGR::ShowType( IO_MGR::KICAD_SEXP ) ); choices.Add( IO_MGR::ShowType( IO_MGR::LEGACY ) ); + choices.Add( IO_MGR::ShowType( IO_MGR::ALTIUM_DESIGNER ) ); choices.Add( IO_MGR::ShowType( IO_MGR::EAGLE ) ); choices.Add( IO_MGR::ShowType( IO_MGR::GEDA_PCB ) ); diff --git a/pcbnew/io_mgr.cpp b/pcbnew/io_mgr.cpp index c9783ef871..11b6256728 100644 --- a/pcbnew/io_mgr.cpp +++ b/pcbnew/io_mgr.cpp @@ -138,6 +138,10 @@ IO_MGR::PCB_FILE_T IO_MGR::GuessPluginTypeFromLibPath( const wxString& aLibPath { ret = EAGLE; } + else if( fn.GetExt() == AltiumFootprintLibPathExtension ) + { + ret = ALTIUM_DESIGNER; + } // Test this one anyways, even though it's the default guess, to avoid // the wxURI instantiation below. diff --git a/pcbnew/plugins/altium/altium_designer_plugin.cpp b/pcbnew/plugins/altium/altium_designer_plugin.cpp index 430a63a4e2..e603105c10 100644 --- a/pcbnew/plugins/altium/altium_designer_plugin.cpp +++ b/pcbnew/plugins/altium/altium_designer_plugin.cpp @@ -104,3 +104,37 @@ BOARD* ALTIUM_DESIGNER_PLUGIN::Load( const wxString& aFileName, BOARD* aAppendTo return m_board; } + +long long ALTIUM_DESIGNER_PLUGIN::GetLibraryTimestamp( const wxString& aLibraryPath ) const +{ + // File hasn't been loaded yet. + if( aLibraryPath.IsEmpty() ) + { + return 0; + } + + wxFileName fn( aLibraryPath ); + + if( fn.IsFileReadable() ) + { + return fn.GetModificationTime().GetValue().GetValue(); + } + else + { + return 0; + } +} + +void ALTIUM_DESIGNER_PLUGIN::FootprintEnumerate( wxArrayString& aFootprintNames, + const wxString& aLibraryPath, bool aBestEfforts, + const PROPERTIES* aProperties ) +{ + ParseAltiumPcbLibFootprintNames( aFootprintNames, aLibraryPath ); +} + +FOOTPRINT* ALTIUM_DESIGNER_PLUGIN::FootprintLoad( const wxString& aLibraryPath, + const wxString& aFootprintName, bool aKeepUUID, + const PROPERTIES* aProperties ) +{ + return nullptr; // TODO: implement +} diff --git a/pcbnew/plugins/altium/altium_designer_plugin.h b/pcbnew/plugins/altium/altium_designer_plugin.h index 27528977e9..c058baa3f9 100644 --- a/pcbnew/plugins/altium/altium_designer_plugin.h +++ b/pcbnew/plugins/altium/altium_designer_plugin.h @@ -46,11 +46,18 @@ public: const wxString GetFileExtension() const override; - long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override - { - // TODO? - return 0; - } + long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override; + + void FootprintEnumerate( wxArrayString& aFootprintNames, const wxString& aLibraryPath, + bool aBestEfforts, const PROPERTIES* aProperties = nullptr ) override; + + FOOTPRINT* FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, + bool aKeepUUID = false, + const PROPERTIES* aProperties = nullptr ) override; + + //bool FootprintExists( const wxString& aLibraryPath, const wxString& aFootprintName, const PROPERTIES* aProperties = nullptr ); + + bool IsFootprintLibWritable( const wxString& aLibraryPath ) override { return false; } // ------------------------------------------------------ diff --git a/pcbnew/plugins/altium/altium_pcb.cpp b/pcbnew/plugins/altium/altium_pcb.cpp index 15748e9332..87e58f5cf7 100644 --- a/pcbnew/plugins/altium/altium_pcb.cpp +++ b/pcbnew/plugins/altium/altium_pcb.cpp @@ -58,16 +58,14 @@ constexpr double BOLD_FACTOR = 1.75; // CSS font-weight-normal is 400; bold is 700 -void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName, PROGRESS_REPORTER* aProgressReporter, - const std::map& aFileMapping ) +static std::vector ReadFileIntoVector( const wxString& aFileName ) { // Open file FILE* fp = wxFopen( aFileName, "rb" ); if( fp == nullptr ) { - wxLogError( _( "Cannot open file '%s'." ), aFileName ); - return; + THROW_IO_ERROR( wxString::Format( _( "Cannot open file '%s'." ), aFileName ) ); } fseek( fp, 0, SEEK_END ); @@ -79,10 +77,11 @@ void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName, PROGRESS_REPORTER THROW_IO_ERROR( _( "Error reading file: cannot determine length." ) ); } - std::unique_ptr buffer( new unsigned char[len] ); + std::vector buffer( len ); + fseek( fp, 0, SEEK_SET ); - size_t bytesRead = fread( buffer.get(), sizeof( unsigned char ), len, fp ); + size_t bytesRead = fread( buffer.data(), sizeof( unsigned char ), len, fp ); fclose( fp ); if( static_cast( len ) != bytesRead ) @@ -90,9 +89,18 @@ void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName, PROGRESS_REPORTER THROW_IO_ERROR( _( "Error reading file." ) ); } + return buffer; +} + + +void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName, PROGRESS_REPORTER* aProgressReporter, + const std::map& aFileMapping ) +{ + std::vector buffer = ReadFileIntoVector( aFileName ); + try { - CFB::CompoundFileReader reader( buffer.get(), bytesRead ); + CFB::CompoundFileReader reader( buffer.data(), buffer.size() ); // Parse File ALTIUM_PCB pcb( aBoard, aProgressReporter ); @@ -105,6 +113,51 @@ void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName, PROGRESS_REPORTER } +void ParseAltiumPcbLibFootprintNames( wxArrayString& aFootprintNames, const wxString& aLibraryPath ) +{ + std::vector buffer = ReadFileIntoVector( aLibraryPath ); + + try + { + CFB::CompoundFileReader reader( buffer.data(), buffer.size() ); + + std::string streamName = "Library\\Data"; + const CFB::COMPOUND_FILE_ENTRY* libraryData = FindStream( reader, streamName ); + if( libraryData == nullptr ) + { + THROW_IO_ERROR( wxString::Format( _( "File not found: '%s'." ), streamName ) ); + } + + ALTIUM_PARSER parser( reader, libraryData ); + + std::map properties = parser.ReadProperties(); + + uint32_t numberOfFootprints = parser.Read(); + aFootprintNames.Alloc( numberOfFootprints ); + for( size_t i = 0; i < numberOfFootprints; i++ ) + { + parser.ReadAndSetSubrecordLength(); + wxString footprintName = parser.ReadWxString(); + aFootprintNames.Add( footprintName ); + } + + if( parser.HasParsingError() ) + { + THROW_IO_ERROR( wxString::Format( "%s stream was not parsed correctly", streamName ) ); + } + + if( parser.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( wxString::Format( "%s stream is not fully parsed", streamName ) ); + } + } + catch( CFB::CFBException& exception ) + { + THROW_IO_ERROR( exception.what() ); + } +} + + bool IsAltiumLayerCopper( ALTIUM_LAYER aLayer ) { return aLayer >= ALTIUM_LAYER::TOP_LAYER && aLayer <= ALTIUM_LAYER::BOTTOM_LAYER; diff --git a/pcbnew/plugins/altium/altium_pcb.h b/pcbnew/plugins/altium/altium_pcb.h index b24cb5ae7d..4bee02efc4 100644 --- a/pcbnew/plugins/altium/altium_pcb.h +++ b/pcbnew/plugins/altium/altium_pcb.h @@ -100,6 +100,14 @@ class PROGRESS_REPORTER; void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName, PROGRESS_REPORTER* aProgressReporter, const std::map& aFileMapping ); +/** + * Helper method to get all footprint names in a given library + * + * @param aFootprintNames footprint names to populate + * @param aLibraryPath path to PcbLib + */ +void ParseAltiumPcbLibFootprintNames( wxArrayString& aFootprintNames, + const wxString& aLibraryPath ); namespace CFB {