From f13eb13b9ab7cf235e267dbec2c1d38bef8aed7c Mon Sep 17 00:00:00 2001 From: Thomas Pointhuber Date: Sat, 10 Apr 2021 21:45:36 +0200 Subject: [PATCH] altium: import images from SchDoc --- common/plugins/altium/altium_parser.h | 16 +++ .../sch_plugins/altium/altium_parser_sch.cpp | 34 +++++ .../sch_plugins/altium/altium_parser_sch.h | 27 ++++ .../altium/altium_storage_parser.ksy | 37 +++++ .../sch_plugins/altium/sch_altium_plugin.cpp | 127 +++++++++++++++++- .../sch_plugins/altium/sch_altium_plugin.h | 6 +- 6 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 eeschema/sch_plugins/altium/altium_storage_parser.ksy diff --git a/common/plugins/altium/altium_parser.h b/common/plugins/altium/altium_parser.h index 891b16f878..1424cc3fba 100644 --- a/common/plugins/altium/altium_parser.h +++ b/common/plugins/altium/altium_parser.h @@ -30,6 +30,7 @@ #include #include +#include namespace CFB @@ -84,6 +85,21 @@ public: } } + std::vector ReadVector( size_t aSize ) + { + if( aSize > GetRemainingBytes() ) + { + m_error = true; + return {}; + } + else + { + std::vector data( m_pos, m_pos + aSize ); + m_pos += aSize; + return data; + } + } + int32_t ReadKicadUnit() { return ConvertToKicadUnit( Read() ); diff --git a/eeschema/sch_plugins/altium/altium_parser_sch.cpp b/eeschema/sch_plugins/altium/altium_parser_sch.cpp index 5c3ff702a3..585cee9fde 100644 --- a/eeschema/sch_plugins/altium/altium_parser_sch.cpp +++ b/eeschema/sch_plugins/altium/altium_parser_sch.cpp @@ -77,6 +77,20 @@ T PropertiesReadEnum( const std::map& aProperties, const wxS } +ASCH_STORAGE_FILE::ASCH_STORAGE_FILE( ALTIUM_PARSER& aReader ) +{ + aReader.Skip( 5 ); + filename = aReader.ReadWxString(); + uint32_t dataSize = aReader.Read(); + data = aReader.ReadVector( dataSize ); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( "Storage stream was not parsed correctly" ); + } +} + + ASCH_COMPONENT::ASCH_COMPONENT( const std::map& aProperties ) { wxASSERT( PropertiesReadRecord( aProperties ) == ALTIUM_SCH_RECORD::COMPONENT ); @@ -541,6 +555,26 @@ ASCH_JUNCTION::ASCH_JUNCTION( const std::map& aProperties ) -PropertiesReadKiCadUnitFrac( aProperties, "LOCATION.Y" ) ); } + +ASCH_IMAGE::ASCH_IMAGE( const std::map& aProperties ) +{ + wxASSERT( PropertiesReadRecord( aProperties ) == ALTIUM_SCH_RECORD::IMAGE ); + + indexinsheet = ALTIUM_PARSER::PropertiesReadInt( aProperties, "INDEXINSHEET", 0 ); + ownerpartid = ALTIUM_PARSER::PropertiesReadInt( aProperties, "OWNERPARTID", -1 ); + + filename = ALTIUM_PARSER::PropertiesReadString( aProperties, "FILENAME", "" ); + + location = wxPoint( PropertiesReadKiCadUnitFrac( aProperties, "LOCATION.X" ), + -PropertiesReadKiCadUnitFrac( aProperties, "LOCATION.Y" ) ); + corner = wxPoint( PropertiesReadKiCadUnitFrac( aProperties, "CORNER.X" ), + -PropertiesReadKiCadUnitFrac( aProperties, "CORNER.Y" ) ); + + embedimage = ALTIUM_PARSER::PropertiesReadBool( aProperties, "EMBEDIMAGE", false ); + keepaspect = ALTIUM_PARSER::PropertiesReadBool( aProperties, "KEEPASPECT", false ); +} + + ASCH_SHEET_FONT::ASCH_SHEET_FONT( const std::map& aProperties, int aId ) { wxASSERT( PropertiesReadRecord( aProperties ) == ALTIUM_SCH_RECORD::SHEET ); diff --git a/eeschema/sch_plugins/altium/altium_parser_sch.h b/eeschema/sch_plugins/altium/altium_parser_sch.h index 4aab310fdb..ef5ff7efd5 100644 --- a/eeschema/sch_plugins/altium/altium_parser_sch.h +++ b/eeschema/sch_plugins/altium/altium_parser_sch.h @@ -35,6 +35,17 @@ // this constant specifies a item which is not inside an component const int ALTIUM_COMPONENT_NONE = -1; +class ALTIUM_PARSER; + +struct ASCH_STORAGE_FILE +{ + wxString filename; + std::vector data; + + explicit ASCH_STORAGE_FILE( ALTIUM_PARSER& aReader ); +}; + + enum class ALTIUM_SCH_RECORD { HEADER = 0, @@ -540,6 +551,22 @@ struct ASCH_JUNCTION }; +struct ASCH_IMAGE +{ + int indexinsheet; + int ownerpartid; + + wxString filename; + wxPoint location; + wxPoint corner; + + bool embedimage; + bool keepaspect; + + explicit ASCH_IMAGE( const std::map& aProperties ); +}; + + struct ASCH_SHEET_FONT { wxString fontname; diff --git a/eeschema/sch_plugins/altium/altium_storage_parser.ksy b/eeschema/sch_plugins/altium/altium_storage_parser.ksy new file mode 100644 index 0000000000..5628853aa2 --- /dev/null +++ b/eeschema/sch_plugins/altium/altium_storage_parser.ksy @@ -0,0 +1,37 @@ +# Can be viewed in: https://ide.kaitai.io/ +# +# This file is a formal specification of the binary format used in Altium SchDoc for the Storage file. +# Files need to manually extracted using a program which can read the Microsoft Compound File Format. +# +# While I do not create a parser using this file, it is still very helpful to understand the binary +# format. + +meta: + id: altium_storage + endian: le + encoding: UTF-8 + +seq: + - id: properties_length + type: u4 + - id: properties + type: str + size: properties_length + - id: record + type: record + repeat: eos + + +types: + record: + seq: + - size: 5 + - id: filename_length + type: u1 + - id: filename + type: str + size: filename_length + - id: data_length + type: u4 + - id: data + size: data_length \ No newline at end of file diff --git a/eeschema/sch_plugins/altium/sch_altium_plugin.cpp b/eeschema/sch_plugins/altium/sch_altium_plugin.cpp index 66b257c90b..eb594bc662 100644 --- a/eeschema/sch_plugins/altium/sch_altium_plugin.cpp +++ b/eeschema/sch_plugins/altium/sch_altium_plugin.cpp @@ -58,6 +58,9 @@ #include #include #include +#include +#include +#include const wxPoint GetRelativePosition( const wxPoint& aPosition, const SCH_COMPONENT* aComponent ) @@ -270,7 +273,8 @@ void SCH_ALTIUM_PLUGIN::ParseAltiumSch( const wxString& aFileName ) try { CFB::CompoundFileReader reader( buffer.get(), bytesRead ); - Parse( reader ); + ParseStorage( reader ); // we need this before parsing the FileHeader + ParseFileHeader( reader ); } catch( CFB::CFBException& exception ) { @@ -279,7 +283,39 @@ void SCH_ALTIUM_PLUGIN::ParseAltiumSch( const wxString& aFileName ) } -void SCH_ALTIUM_PLUGIN::Parse( const CFB::CompoundFileReader& aReader ) +void SCH_ALTIUM_PLUGIN::ParseStorage( const CFB::CompoundFileReader& aReader ) +{ + const CFB::COMPOUND_FILE_ENTRY* file = FindStream( aReader, "Storage" ); + + if( file == nullptr ) + return; + + ALTIUM_PARSER reader( aReader, file ); + + std::map properties = reader.ReadProperties(); + wxString header = ALTIUM_PARSER::PropertiesReadString( properties, "HEADER", "" ); + int weight = ALTIUM_PARSER::PropertiesReadInt( properties, "WEIGHT", 0 ); + + if( weight < 0 ) + THROW_IO_ERROR( "Storage weight is negative!" ); + + for( int i = 0; i < weight; i++ ) + { + m_altiumStorage.emplace_back( reader ); + } + + if( reader.HasParsingError() ) + THROW_IO_ERROR( "stream was not parsed correctly!" ); + + // TODO pointhi: is it possible to have multiple headers in one Storage file? Otherwise throw IO Error. + if( reader.GetRemainingBytes() != 0 ) + wxLogError( + wxString::Format( "Storage file was not fully parsed as %d bytes are remaining.", + reader.GetRemainingBytes() ) ); +} + + +void SCH_ALTIUM_PLUGIN::ParseFileHeader( const CFB::CompoundFileReader& aReader ) { const CFB::COMPOUND_FILE_ENTRY* file = FindStream( aReader, "FileHeader" ); @@ -391,8 +427,7 @@ void SCH_ALTIUM_PLUGIN::Parse( const CFB::CompoundFileReader& aReader ) case ALTIUM_SCH_RECORD::JUNCTION: ParseJunction( properties ); break; - case ALTIUM_SCH_RECORD::IMAGE: - break; + case ALTIUM_SCH_RECORD::IMAGE: ParseImage( properties ); break; case ALTIUM_SCH_RECORD::SHEET: ParseSheet( properties ); break; @@ -493,6 +528,27 @@ bool SCH_ALTIUM_PLUGIN::IsComponentPartVisible( int aOwnerindex, int aOwnerpartd } +const ASCH_STORAGE_FILE* SCH_ALTIUM_PLUGIN::GetFileFromStorage( const wxString& aFilename ) const +{ + const ASCH_STORAGE_FILE* nonExactMatch = nullptr; + + for( const ASCH_STORAGE_FILE& file : m_altiumStorage ) + { + if( file.filename.IsSameAs( aFilename ) ) + { + return &file; + } + + if( file.filename.EndsWith( aFilename ) ) + { + nonExactMatch = &file; + } + } + + return nonExactMatch; +} + + void SCH_ALTIUM_PLUGIN::ParseComponent( int aIndex, const std::map& aProperties ) { @@ -1874,6 +1930,69 @@ void SCH_ALTIUM_PLUGIN::ParseJunction( const std::map& aProp } +void SCH_ALTIUM_PLUGIN::ParseImage( const std::map& aProperties ) +{ + ASCH_IMAGE elem( aProperties ); + + wxPoint center = ( elem.location + elem.corner ) / 2 + m_sheetOffset; + std::unique_ptr bitmap = std::make_unique( center ); + + if( elem.embedimage ) + { + const ASCH_STORAGE_FILE* storageFile = GetFileFromStorage( elem.filename ); + + if( !storageFile ) + { + wxLogError( + wxString::Format( "Embedded file not found in storage: %s", elem.filename ) ); + return; + } + + wxString storagePath = wxFileName::CreateTempFileName( "kicad_import_" ); + + // As wxZlibInputStream is not seekable, we need to write a temporary file + wxMemoryInputStream fileStream( storageFile->data.data(), storageFile->data.size() ); + wxZlibInputStream zlibInputStream( fileStream ); + wxFFileOutputStream outputStream( storagePath ); + outputStream.Write( zlibInputStream ); + outputStream.Close(); + + if( !bitmap->ReadImageFile( storagePath ) ) + { + wxLogError( wxString::Format( "Error while reading image: %s", storagePath ) ); + return; + } + + // Remove temporary file + wxRemoveFile( storagePath ); + } + else + { + if( !wxFileExists( elem.filename ) ) + { + wxLogError( wxString::Format( "File not found on disk: %s", elem.filename ) ); + return; + } + + if( !bitmap->ReadImageFile( elem.filename ) ) + { + wxLogError( wxString::Format( "Error while reading image: %s", elem.filename ) ); + return; + } + } + + // we only support one scale, thus we need to select one in case it does not keep aspect ratio + wxSize currentImageSize = bitmap->GetSize(); + wxPoint expectedImageSize = elem.location - elem.corner; + double scaleX = std::abs( static_cast( expectedImageSize.x ) / currentImageSize.x ); + double scaleY = std::abs( static_cast( expectedImageSize.y ) / currentImageSize.y ); + bitmap->SetImageScale( std::min( scaleX, scaleY ) ); + + bitmap->SetFlags( IS_NEW ); + m_currentSheet->GetScreen()->Append( bitmap.release() ); +} + + void SCH_ALTIUM_PLUGIN::ParseSheet( const std::map& aProperties ) { m_altiumSheet = std::make_unique( aProperties ); diff --git a/eeschema/sch_plugins/altium/sch_altium_plugin.h b/eeschema/sch_plugins/altium/sch_altium_plugin.h index 7e70417b80..256a068083 100644 --- a/eeschema/sch_plugins/altium/sch_altium_plugin.h +++ b/eeschema/sch_plugins/altium/sch_altium_plugin.h @@ -104,10 +104,12 @@ public: wxFileName getLibFileName(); void ParseAltiumSch( const wxString& aFileName ); - void Parse( const CFB::CompoundFileReader& aReader ); + void ParseStorage( const CFB::CompoundFileReader& aReader ); + void ParseFileHeader( const CFB::CompoundFileReader& aReader ); private: bool IsComponentPartVisible( int aOwnerindex, int aOwnerpartdisplaymode ) const; + const ASCH_STORAGE_FILE* GetFileFromStorage( const wxString& aFilename ) const; void ParseComponent( int aIndex, const std::map& aProperties ); void ParsePin( const std::map& aProperties ); @@ -128,6 +130,7 @@ private: void ParseBus( const std::map& aProperties ); void ParseWire( const std::map& aProperties ); void ParseJunction( const std::map& aProperties ); + void ParseImage( const std::map& aProperties ); void ParseSheet( const std::map& aProperties ); void ParseSheetName( const std::map& aProperties ); void ParseFileName( const std::map& aProperties ); @@ -156,6 +159,7 @@ private: std::map m_symbols; // every component has its unique symbol std::map m_powerSymbols; + std::vector m_altiumStorage; std::map m_altiumComponents; std::vector m_altiumPortsCurrentSheet; // we require all connections first