From 41a67919281388d668ec3afc88077617432dfea5 Mon Sep 17 00:00:00 2001 From: Thomas Pointhuber Date: Tue, 21 Apr 2020 15:34:06 +0200 Subject: [PATCH] altium: initial code to import STEP models For now, they are extracted into the same directory as the project file. Furthermore, there seems to be a problem with rotation / positioning of some models. --- .../altium_circuit_maker_plugin.cpp | 2 + .../altium_circuit_studio_plugin.cpp | 2 + .../altium_designer_plugin.cpp | 2 + .../altium2kicadpcb_plugin/altium_parser.ksy | 22 ++++ .../altium_parser_pcb.cpp | 65 +++++++++- .../altium_parser_pcb.h | 27 ++++ pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp | 122 ++++++++++++++++++ pcbnew/altium2kicadpcb_plugin/altium_pcb.h | 5 + 8 files changed, 246 insertions(+), 1 deletion(-) diff --git a/pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.cpp b/pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.cpp index e9d073dcaf..768399bf63 100644 --- a/pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.cpp +++ b/pcbnew/altium2kicadpcb_plugin/altium_circuit_maker_plugin.cpp @@ -81,8 +81,10 @@ BOARD* ALTIUM_CIRCUIT_MAKER_PLUGIN::Load( { ALTIUM_PCB_DIR::BOARDREGIONS, "E3A544335C30403A991912052C936F\\Data" }, { ALTIUM_PCB_DIR::CLASSES6, "4F71DD45B09143988210841EA1C28D\\Data" }, { ALTIUM_PCB_DIR::COMPONENTS6, "F9D060ACC7DD4A85BC73CB785BAC81\\Data" }, + { ALTIUM_PCB_DIR::COMPONENTBODIES6, "44D9487C98CE4F0EB46AB6E9CDAF40\\Data" }, // or: A0DB41FBCB0D49CE8C32A271AA7EF5 ? { ALTIUM_PCB_DIR::DIMENSIONS6, "068B9422DBB241258BA2DE9A6BA1A6\\Data" }, { ALTIUM_PCB_DIR::FILLS6, "6FFE038462A940E9B422EFC8F5D85E\\Data" }, + { ALTIUM_PCB_DIR::MODELS, "0DB009C021D946C88F1B3A32DAE94B\\Data" }, { ALTIUM_PCB_DIR::NETS6, "35D7CF51BB9B4875B3A138B32D80DC\\Data" }, { ALTIUM_PCB_DIR::PADS6, "4F501041A9BC4A06BDBDAB67D3820E\\Data" }, { ALTIUM_PCB_DIR::POLYGONS6, "A1931C8B0B084A61AA45146575FDD3\\Data" }, diff --git a/pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.cpp b/pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.cpp index a7c600f114..c504f6d67e 100644 --- a/pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.cpp +++ b/pcbnew/altium2kicadpcb_plugin/altium_circuit_studio_plugin.cpp @@ -81,8 +81,10 @@ BOARD* ALTIUM_CIRCUIT_STUDIO_PLUGIN::Load( { ALTIUM_PCB_DIR::BOARDREGIONS, "8957CF30F167408D9D263D23FE7C89\\Data" }, { ALTIUM_PCB_DIR::CLASSES6, "847EFBF87A5149B1AA326A52AD6357\\Data" }, { ALTIUM_PCB_DIR::COMPONENTS6, "465416896A15486999A39C643935D2\\Data" }, + { ALTIUM_PCB_DIR::COMPONENTBODIES6, "1849D9B5512D452A93EABF4E40B122\\Data" }, // or B6AD30D75241498BA2536EBF001752 ? { ALTIUM_PCB_DIR::DIMENSIONS6, "16C81DBC13C447FF8B42A426677F3C\\Data" }, { ALTIUM_PCB_DIR::FILLS6, "4E83BDC3253747F08E9006D7F57020\\Data" }, + { ALTIUM_PCB_DIR::MODELS, "C0F7599ECC6A4D648DF5BB557679AF\\Data" }, { ALTIUM_PCB_DIR::NETS6, "D95A0DA2FE9047779A5194C127F30B\\Data" }, { ALTIUM_PCB_DIR::PADS6, "47D69BC5107A4B8DB8DAA23E39C238\\Data" }, { ALTIUM_PCB_DIR::POLYGONS6, "D7038392280E4E229B9D9B5426B295\\Data" }, diff --git a/pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.cpp b/pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.cpp index 30a04828bb..88e8d82b2b 100644 --- a/pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.cpp +++ b/pcbnew/altium2kicadpcb_plugin/altium_designer_plugin.cpp @@ -81,8 +81,10 @@ BOARD* ALTIUM_DESIGNER_PLUGIN::Load( { ALTIUM_PCB_DIR::BOARDREGIONS, "BoardRegions\\Data" }, { ALTIUM_PCB_DIR::CLASSES6, "Classes6\\Data" }, { ALTIUM_PCB_DIR::COMPONENTS6, "Components6\\Data" }, + { ALTIUM_PCB_DIR::COMPONENTBODIES6, "ComponentBodies6\\Data" }, { ALTIUM_PCB_DIR::DIMENSIONS6, "Dimensions6\\Data" }, { ALTIUM_PCB_DIR::FILLS6, "Fills6\\Data" }, + { ALTIUM_PCB_DIR::MODELS, "Models\\Data" }, { ALTIUM_PCB_DIR::NETS6, "Nets6\\Data" }, { ALTIUM_PCB_DIR::PADS6, "Pads6\\Data" }, { ALTIUM_PCB_DIR::POLYGONS6, "Polygons6\\Data" }, diff --git a/pcbnew/altium2kicadpcb_plugin/altium_parser.ksy b/pcbnew/altium2kicadpcb_plugin/altium_parser.ksy index 7e2791ee6b..246a6d50ec 100644 --- a/pcbnew/altium2kicadpcb_plugin/altium_parser.ksy +++ b/pcbnew/altium2kicadpcb_plugin/altium_parser.ksy @@ -34,6 +34,7 @@ types: record_id::text6: text record_id::fill6: fill record_id::region6: region + record_id::componentbody6: componentbody arc: seq: @@ -570,6 +571,26 @@ types: # repeat-expr: vertices_num+1 # type: xyf2 + componentbody: + seq: + - id: sub1_len + type: u4 + - id: data + type: componentbody_sub1 + size: sub1_len + + componentbody_sub1: + seq: + - size: 7 + - id: component + type: u2 + - size: 9 + - id: propterties_len + type: u4 + - id: properties + size: propterties_len + type: str + xy: seq: - id: x @@ -609,6 +630,7 @@ enums: 0x05: text6 0x06: fill6 0x0b: region6 + 0x0c: componentbody6 boolean: 0: false diff --git a/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.cpp b/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.cpp index 3ec8ad9d9b..b5a8a2c128 100644 --- a/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.cpp +++ b/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.cpp @@ -26,7 +26,6 @@ #include #include -#include #include "altium_parser.h" #include "altium_parser_pcb.h" @@ -378,6 +377,28 @@ ADIMENSION6::ADIMENSION6( ALTIUM_PARSER& aReader ) } } +AMODEL::AMODEL( ALTIUM_PARSER& aReader ) +{ + std::map properties = aReader.ReadProperties(); + if( properties.empty() ) + { + THROW_IO_ERROR( "Classes6 stream has no properties!" ); + } + + name = ALTIUM_PARSER::PropertiesReadString( properties, "NAME", "" ); + id = ALTIUM_PARSER::PropertiesReadString( properties, "ID", "" ); + isEmbedded = ALTIUM_PARSER::PropertiesReadBool( properties, "EMBED", false ); + + rotation.x = ALTIUM_PARSER::PropertiesReadDouble( properties, "ROTX", 0. ); + rotation.y = ALTIUM_PARSER::PropertiesReadDouble( properties, "ROTY", 0. ); + rotation.z = ALTIUM_PARSER::PropertiesReadDouble( properties, "ROTZ", 0. ); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( "Classes6 stream was not parsed correctly" ); + } +} + ANET6::ANET6( ALTIUM_PARSER& aReader ) { std::map properties = aReader.ReadProperties(); @@ -569,6 +590,48 @@ AARC6::AARC6( ALTIUM_PARSER& aReader ) } } +ACOMPONENTBODY6::ACOMPONENTBODY6( ALTIUM_PARSER& aReader ) +{ + ALTIUM_RECORD recordtype = static_cast( aReader.Read() ); + if( recordtype != ALTIUM_RECORD::MODEL ) + { + THROW_IO_ERROR( "ComponentsBodies6 stream has invalid recordtype" ); + } + + aReader.ReadAndSetSubrecordLength(); + + aReader.Skip( 7 ); + component = aReader.Read(); + aReader.Skip( 9 ); + + std::map properties = aReader.ReadProperties(); + if( properties.empty() ) + { + THROW_IO_ERROR( "ComponentsBodies6 stream has no properties" ); + } + + modelName = ALTIUM_PARSER::PropertiesReadString( properties, "MODEL.NAME", "" ); + modelId = ALTIUM_PARSER::PropertiesReadString( properties, "MODELID", "" ); + modelIsEmbedded = ALTIUM_PARSER::PropertiesReadBool( properties, "MODEL.EMBED", false ); + + modelPosition.x = ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "MODEL.2D.X", "0mil" ); + modelPosition.y = -ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "MODEL.2D.Y", "0mil" ); + modelPosition.z = ALTIUM_PARSER::PropertiesReadKicadUnit( properties, "MODEL.3D.DZ", "0mil" ); + + modelRotation.x = ALTIUM_PARSER::PropertiesReadDouble( properties, "MODEL.3D.ROTX", 0. ); + modelRotation.y = ALTIUM_PARSER::PropertiesReadDouble( properties, "MODEL.3D.ROTY", 0. ); + modelRotation.z = ALTIUM_PARSER::PropertiesReadDouble( properties, "MODEL.3D.ROTZ", 0. ); + + rotation = ALTIUM_PARSER::PropertiesReadDouble( properties, "MODEL.2D.ROTATION", 0. ); + + aReader.SkipSubrecord(); + + if( aReader.HasParsingError() ) + { + THROW_IO_ERROR( "Components6 stream was not parsed correctly" ); + } +} + APAD6::APAD6( ALTIUM_PARSER& aReader ) { ALTIUM_RECORD recordtype = static_cast( aReader.Read() ); diff --git a/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.h b/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.h index c601e5fcff..2858cfb44b 100644 --- a/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.h +++ b/pcbnew/altium2kicadpcb_plugin/altium_parser_pcb.h @@ -29,6 +29,7 @@ #include #include +#include #include // tthis constant specifies an unconnected net @@ -395,6 +396,17 @@ struct ADIMENSION6 explicit ADIMENSION6( ALTIUM_PARSER& aReader ); }; +struct AMODEL +{ + wxString name; + wxString id; + bool isEmbedded; + + MODULE_3D_SETTINGS::VECTOR3D rotation; + + explicit AMODEL( ALTIUM_PARSER& aReader ); +}; + struct ANET6 { wxString name; @@ -486,6 +498,21 @@ struct AARC6 explicit AARC6( ALTIUM_PARSER& aReader ); }; +struct ACOMPONENTBODY6 +{ + uint16_t component; + + wxString modelName; + wxString modelId; + bool modelIsEmbedded; + + MODULE_3D_SETTINGS::VECTOR3D modelPosition; + MODULE_3D_SETTINGS::VECTOR3D modelRotation; + double rotation; + + explicit ACOMPONENTBODY6( ALTIUM_PARSER& aReader ); +}; + struct APAD6_SIZE_AND_SHAPE { ALTIUM_PAD_HOLE_SHAPE holeshape; diff --git a/pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp b/pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp index c937615619..5f203d7f09 100644 --- a/pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp +++ b/pcbnew/altium2kicadpcb_plugin/altium_pcb.cpp @@ -38,8 +38,13 @@ #include #include +#include #include #include +#include +#include +#include +#include void ParseAltiumPcb( BOARD* aBoard, const wxString& aFileName, @@ -350,6 +355,16 @@ void ALTIUM_PCB::Parse( const CFB::CompoundFileReader& aReader, [this]( auto aReader, auto fileHeader ) { this->ParseComponents6Data( aReader, fileHeader ); } }, + { true, ALTIUM_PCB_DIR::MODELS, + [this, aFileMapping]( auto aReader, auto fileHeader ) { + wxString dir( aFileMapping.at( ALTIUM_PCB_DIR::MODELS ) ); + dir.RemoveLast( 4 ); // Remove "Data" from the path + this->ParseModelsData( aReader, fileHeader, dir ); + } }, + { true, ALTIUM_PCB_DIR::COMPONENTBODIES6, + [this]( auto aReader, auto fileHeader ) { + this->ParseComponentsBodies6Data( aReader, fileHeader ); + } }, { true, ALTIUM_PCB_DIR::NETS6, [this]( auto aReader, auto fileHeader ) { this->ParseNets6Data( aReader, fileHeader ); @@ -449,6 +464,9 @@ void ALTIUM_PCB::Parse( const CFB::CompoundFileReader& aReader, { module->CalculateBoundingBox(); } + + // Otherwise we cannot save the imported board + m_board->SetModified(); } int ALTIUM_PCB::GetNetCode( uint16_t aId ) const @@ -783,6 +801,66 @@ void ALTIUM_PCB::ParseComponents6Data( } +void ALTIUM_PCB::ParseComponentsBodies6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + ACOMPONENTBODY6 elem( reader ); // TODO: implement + + if( elem.component == ALTIUM_COMPONENT_NONE ) + { + continue; // TODO: we do not support components for the board yet + } + + if( m_components.size() <= elem.component ) + { + THROW_IO_ERROR( wxString::Format( + "ComponentsBodies6 stream tries to access component id %d of %d existing components", + elem.component, m_components.size() ) ); + } + + if( !elem.modelIsEmbedded ) + { + continue; + } + + auto modelTuple = m_models.find( elem.modelId ); + if( modelTuple == m_models.end() ) + { + THROW_IO_ERROR( wxString::Format( + "ComponentsBodies6 stream tries to access model id %s which does not exist", + elem.modelId ) ); + } + + MODULE* module = m_components.at( elem.component ); + const wxPoint& modulePosition = module->GetPosition(); + + MODULE_3D_SETTINGS modelSettings; + + modelSettings.m_Filename = modelTuple->second; + + modelSettings.m_Offset.x = Iu2Millimeter( (int) elem.modelPosition.x - modulePosition.x ); + modelSettings.m_Offset.y = -Iu2Millimeter( (int) elem.modelPosition.y - modulePosition.y ); + modelSettings.m_Offset.z = Iu2Millimeter( (int) elem.modelPosition.z ); + + modelSettings.m_Rotation.x = NormalizeAngleDegrees( elem.modelRotation.x, -180, 180 ); + modelSettings.m_Rotation.y = NormalizeAngleDegrees( elem.modelRotation.y + 180, -180, 180 ); + modelSettings.m_Rotation.z = NormalizeAngleDegrees( + elem.modelRotation.z + module->GetOrientationDegrees() + 180, -180, 180 ); + + module->Models().push_back( modelSettings ); + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( "ComponentsBodies6 stream is not fully parsed" ); + } +} + + void ALTIUM_PCB::HelperParseDimensions6Linear( const ADIMENSION6& aElem ) { if( aElem.referencePoint.size() != 2 ) @@ -1035,6 +1113,50 @@ void ALTIUM_PCB::ParseDimensions6Data( } } + +void ALTIUM_PCB::ParseModelsData( const CFB::CompoundFileReader& aReader, + const CFB::COMPOUND_FILE_ENTRY* aEntry, const wxString aRootDir ) +{ + ALTIUM_PARSER reader( aReader, aEntry ); + + wxString altiumModelsPath = wxPathOnly( m_board->GetFileName() ); + wxSetEnv( PROJECT_VAR_NAME, + altiumModelsPath ); // TODO: set KIPRJMOD always after import (not only when loading project)? + + int idx = 0; + while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) + { + AMODEL elem( reader ); + + wxString stepPath = aRootDir + std::to_string( idx++ ); + + const CFB::COMPOUND_FILE_ENTRY* stepEntry = FindStream( aReader, stepPath.c_str() ); + + size_t stepSize = static_cast( stepEntry->size ); + std::unique_ptr stepContent( new char[stepSize] ); + + // read file into buffer + aReader.ReadFile( stepEntry, 0, stepContent.get(), stepSize ); + + wxFileName storagePath( altiumModelsPath, elem.name ); + + wxMemoryInputStream stepStream( stepContent.get(), stepSize ); + wxZlibInputStream zlibInputStream( stepStream ); + + wxFileOutputStream outputStream( storagePath.GetFullPath() ); + outputStream.Write( zlibInputStream ); + outputStream.Close(); + + m_models.insert( { elem.id, "${KIPRJMOD}/" + elem.name } ); // KIPRJMOD + } + + if( reader.GetRemainingBytes() != 0 ) + { + THROW_IO_ERROR( "Models stream is not fully parsed" ); + } +} + + void ALTIUM_PCB::ParseNets6Data( const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ) { diff --git a/pcbnew/altium2kicadpcb_plugin/altium_pcb.h b/pcbnew/altium2kicadpcb_plugin/altium_pcb.h index 0c1ba9cfe6..b13b3fffcb 100644 --- a/pcbnew/altium2kicadpcb_plugin/altium_pcb.h +++ b/pcbnew/altium2kicadpcb_plugin/altium_pcb.h @@ -138,6 +138,8 @@ private: const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); void ParseDimensions6Data( const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseModelsData( const CFB::CompoundFileReader& aReader, + const CFB::COMPOUND_FILE_ENTRY* aEntry, const wxString aRootDir ); void ParseNets6Data( const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); void ParsePolygons6Data( @@ -148,6 +150,8 @@ private: // Binary Format void ParseArcs6Data( const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); + void ParseComponentsBodies6Data( + const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); void ParsePads6Data( const CFB::CompoundFileReader& aReader, const CFB::COMPOUND_FILE_ENTRY* aEntry ); void ParseVias6Data( @@ -181,6 +185,7 @@ private: BOARD* m_board; std::vector m_components; std::vector m_polygons; + std::map m_models; size_t m_num_nets; std::map m_layermap; // used to correctly map copper layers std::map> m_rules;