altium: import images from SchDoc

This commit is contained in:
Thomas Pointhuber 2021-04-10 21:45:36 +02:00
parent 94afdcb92a
commit f13eb13b9a
6 changed files with 242 additions and 5 deletions

View File

@ -30,6 +30,7 @@
#include <math/util.h>
#include <wx/gdicmn.h>
#include <vector>
namespace CFB
@ -84,6 +85,21 @@ public:
}
}
std::vector<char> ReadVector( size_t aSize )
{
if( aSize > GetRemainingBytes() )
{
m_error = true;
return {};
}
else
{
std::vector<char> data( m_pos, m_pos + aSize );
m_pos += aSize;
return data;
}
}
int32_t ReadKicadUnit()
{
return ConvertToKicadUnit( Read<int32_t>() );

View File

@ -77,6 +77,20 @@ T PropertiesReadEnum( const std::map<wxString, wxString>& aProperties, const wxS
}
ASCH_STORAGE_FILE::ASCH_STORAGE_FILE( ALTIUM_PARSER& aReader )
{
aReader.Skip( 5 );
filename = aReader.ReadWxString();
uint32_t dataSize = aReader.Read<uint32_t>();
data = aReader.ReadVector( dataSize );
if( aReader.HasParsingError() )
{
THROW_IO_ERROR( "Storage stream was not parsed correctly" );
}
}
ASCH_COMPONENT::ASCH_COMPONENT( const std::map<wxString, wxString>& aProperties )
{
wxASSERT( PropertiesReadRecord( aProperties ) == ALTIUM_SCH_RECORD::COMPONENT );
@ -541,6 +555,26 @@ ASCH_JUNCTION::ASCH_JUNCTION( const std::map<wxString, wxString>& aProperties )
-PropertiesReadKiCadUnitFrac( aProperties, "LOCATION.Y" ) );
}
ASCH_IMAGE::ASCH_IMAGE( const std::map<wxString, wxString>& 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<wxString, wxString>& aProperties, int aId )
{
wxASSERT( PropertiesReadRecord( aProperties ) == ALTIUM_SCH_RECORD::SHEET );

View File

@ -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<char> 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<wxString, wxString>& aProperties );
};
struct ASCH_SHEET_FONT
{
wxString fontname;

View File

@ -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

View File

@ -58,6 +58,9 @@
#include <kicad_string.h>
#include <sch_edit_frame.h>
#include <wildcards_and_files_ext.h>
#include <wx/mstream.h>
#include <wx/zstream.h>
#include <wx/wfstream.h>
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<wxString, wxString> 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<wxString, wxString>& aProperties )
{
@ -1874,6 +1930,69 @@ void SCH_ALTIUM_PLUGIN::ParseJunction( const std::map<wxString, wxString>& aProp
}
void SCH_ALTIUM_PLUGIN::ParseImage( const std::map<wxString, wxString>& aProperties )
{
ASCH_IMAGE elem( aProperties );
wxPoint center = ( elem.location + elem.corner ) / 2 + m_sheetOffset;
std::unique_ptr<SCH_BITMAP> bitmap = std::make_unique<SCH_BITMAP>( 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<double>( expectedImageSize.x ) / currentImageSize.x );
double scaleY = std::abs( static_cast<double>( 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<wxString, wxString>& aProperties )
{
m_altiumSheet = std::make_unique<ASCH_SHEET>( aProperties );

View File

@ -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<wxString, wxString>& aProperties );
void ParsePin( const std::map<wxString, wxString>& aProperties );
@ -128,6 +130,7 @@ private:
void ParseBus( const std::map<wxString, wxString>& aProperties );
void ParseWire( const std::map<wxString, wxString>& aProperties );
void ParseJunction( const std::map<wxString, wxString>& aProperties );
void ParseImage( const std::map<wxString, wxString>& aProperties );
void ParseSheet( const std::map<wxString, wxString>& aProperties );
void ParseSheetName( const std::map<wxString, wxString>& aProperties );
void ParseFileName( const std::map<wxString, wxString>& aProperties );
@ -156,6 +159,7 @@ private:
std::map<int, LIB_PART*> m_symbols; // every component has its unique symbol
std::map<wxString, LIB_PART*> m_powerSymbols;
std::vector<ASCH_STORAGE_FILE> m_altiumStorage;
std::map<int, ASCH_COMPONENT> m_altiumComponents;
std::vector<ASCH_PORT> m_altiumPortsCurrentSheet; // we require all connections first