Merge branch 'altium_custom_pads' into 'master'

Draft: Fix Altium custom pads import

See merge request kicad/code/kicad!2254
This commit is contained in:
Roberto Fernandez Bautista 2025-09-11 21:01:21 +00:00
commit 8f5e8b90a3
6 changed files with 126 additions and 24 deletions

View File

@ -46,11 +46,19 @@ int32_t ALTIUM_PROPS_UTILS::ConvertToKicadUnit( const double aValue )
}
std::optional<int> ALTIUM_PROPS_UTILS::ReadOptInt( const std::map<wxString, wxString>& aProps,
const wxString& aKey )
{
const std::map<wxString, wxString>::const_iterator& value = aProps.find( aKey );
return value == aProps.end() ? std::optional<int>{} : wxAtoi( value->second );
}
int ALTIUM_PROPS_UTILS::ReadInt( const std::map<wxString, wxString>& aProps, const wxString& aKey,
int aDefault )
{
const std::map<wxString, wxString>::const_iterator& value = aProps.find( aKey );
return value == aProps.end() ? aDefault : wxAtoi( value->second );
std::optional<int> opt = ReadOptInt(aProps, aKey);
return opt.has_value() ? opt.value() : aDefault;
}

View File

@ -27,6 +27,7 @@
#include <stdint.h>
#include <map>
#include <optional>
#include <wx/string.h>
@ -36,6 +37,9 @@ class ALTIUM_PROPS_UTILS
public:
static int32_t ConvertToKicadUnit( const double aValue );
static std::optional<int> ReadOptInt( const std::map<wxString, wxString>& aProps,
const wxString& aKey );
static int ReadInt( const std::map<wxString, wxString>& aProps, const wxString& aKey,
int aDefault );

View File

@ -1192,6 +1192,8 @@ AREGION6::AREGION6( ALTIUM_BINARY_PARSER& aReader, bool aExtendedVertices )
int pkind = ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "KIND" ), 0 );
bool is_cutout = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "ISBOARDCUTOUT" ), false );
v7layer = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "V7_LAYER" ), wxEmptyString );
name = ALTIUM_PROPS_UTILS::ReadString( properties, wxT( "NAME" ), wxEmptyString );
is_shapebased = ALTIUM_PROPS_UTILS::ReadBool( properties, wxT( "ISSHAPEBASED" ), false );
keepoutrestrictions = static_cast<uint8_t>(
ALTIUM_PROPS_UTILS::ReadInt( properties, wxT( "KEEPOUTRESTRIC" ), 0x1F ) );
@ -1201,6 +1203,25 @@ AREGION6::AREGION6( ALTIUM_BINARY_PARSER& aReader, bool aExtendedVertices )
subpolyindex = static_cast<uint16_t>(
ALTIUM_PROPS_UTILS::ReadInt( properties, "SUBPOLYINDEX", ALTIUM_POLYGON_NONE ) );
unionindex = ALTIUM_PROPS_UTILS::ReadOptInt( properties, "UNIONINDEX" );
padindex = ALTIUM_PROPS_UTILS::ReadOptInt( properties, "PADINDEX" );
arc_resolution = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, "ARCRESOLUTION", "0.1mil");
cavity_height = ALTIUM_PROPS_UTILS::ReadKicadUnit( properties, "CAVITYHEIGHT", "0mil");
// Note RFB:
// Sometimes the format could have vertices in the properties region, e.g. like below. In these
// cases a property "MAINCONTOURVERTEXCOUNT" will also be present containing the number of
// vertices. For now, I decided it is not worth it to parse it since the same data is present in
// the binary stream in all samples I have seen.
// KIND0=0
// VX0=-27mil
// VY0=-15mil
// CX0=0mil
// CY0=0mil
// SA0= 0.00000000000000E+0000
// EA0= 0.00000000000000E+0000
// R0=0mil
switch( pkind )
{
case 0:

View File

@ -562,8 +562,16 @@ struct AREGION6
uint8_t keepoutrestrictions;
uint16_t holecount;
wxString name;
wxString v7layer; ///< duplicate data? seems identical to 'layer' in the binary stream
std::optional<int> unionindex; ///< Unclear what unionindex refers to
std::optional<int> padindex;
ALTIUM_REGION_KIND kind; // I assume this means if normal or keepout?
std::optional<int32_t> cavity_height; ///< Unclear what cavity height is
std::optional<int32_t> arc_resolution;
std::vector<ALTIUM_VERTICE> outline;
std::vector<std::vector<ALTIUM_VERTICE>> holes;

View File

@ -76,6 +76,29 @@ bool IsAltiumLayerAPlane( ALTIUM_LAYER aLayer )
return aLayer >= ALTIUM_LAYER::INTERNAL_PLANE_1 && aLayer <= ALTIUM_LAYER::INTERNAL_PLANE_16;
}
PAD* ALTIUM_PCB::HelperGetPad( int aPadIndex ) const
{
if( !m_padIndexMap.contains( aPadIndex ) )
{
THROW_IO_ERROR( wxString::Format( wxT( "Component creator tries to access pad id "
"%d, which does not exist" ),
aPadIndex ) );
}
BOARD_CONNECTED_ITEM* padBoardItem = m_padIndexMap.at( aPadIndex );
if( padBoardItem->Type() != PCB_PAD_T )
{
// Todo: graceful handling?
THROW_IO_ERROR( wxString::Format( wxT( "Pad %d is not on a copper layer, unexpected" ),
aPadIndex ) );
}
return static_cast<PAD*>( padBoardItem );
}
FOOTPRINT* ALTIUM_PCB::HelperGetFootprint( uint16_t aComponent ) const
{
if( aComponent == ALTIUM_COMPONENT_NONE || m_components.size() <= aComponent )
@ -793,7 +816,7 @@ FOOTPRINT* ALTIUM_PCB::ParseFootprint( ALTIUM_PCB_COMPOUND_FILE& altiumLibFile,
case ALTIUM_RECORD::PAD:
{
APAD6 pad( parser );
ConvertPads6ToFootprintItem( footprint.get(), pad );
ConvertPads6ToFootprintItem( footprint.get(), pad, primitiveIndex );
break;
}
case ALTIUM_RECORD::VIA:
@ -2685,6 +2708,11 @@ void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItem( FOOTPRINT* aFoot
void ALTIUM_PCB::ConvertShapeBasedRegions6ToBoardItemOnLayer( const AREGION6& aElem,
PCB_LAYER_ID aLayer )
{
wxASSERT_MSG( !aElem.padindex.has_value(),
wxT( "ConvertShapeBasedRegions6ToBoardItemOnLayer was called for a Region with a "
"pad index! ConvertShapeBasedRegions6ToFootprintItemOnLayer should be "
"called instead" ) );
SHAPE_LINE_CHAIN linechain;
HelperShapeLineChainFromAltiumVertices( linechain, aElem.outline );
@ -2758,9 +2786,24 @@ void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItemOnLayer( FOOTPRINT*
polySet.AddHole( hole_linechain );
}
if( aLayer == F_Cu || aLayer == B_Cu )
if( aElem.padindex.has_value() && LSET::AllCuMask().Contains( aLayer ) )
{
// Replace shape on layer of existing pad
int actualPadindex=aElem.padindex.value() - 1; // altium seems to index starting at 1?
PAD* pad = HelperGetPad( actualPadindex ); // throws if pad doesn't exist
pad->SetShape( aLayer, PAD_SHAPE::CUSTOM );
polySet.Move( -pad->GetPosition() );
polySet.Rotate( -pad->Padstack().GetOrientation() );
pad->AddPrimitivePoly( aLayer, polySet, 0, true );
// Ignore extended primitive information from the region - this appears to mean nothing
// for regions used in pads. It should have already been set correctly when the pad was
// loaded
}
else if( aLayer == F_Cu || aLayer == B_Cu )
{
// TODO(JE) padstacks -- not sure what should happen here yet
std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
LSET padLayers;
@ -3158,6 +3201,8 @@ void ALTIUM_PCB::ParsePads6Data( const ALTIUM_PCB_COMPOUND_FILE& aAltiumPcbF
ALTIUM_BINARY_PARSER reader( aAltiumPcbFile, aEntry );
int padindex = 0;
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ )
{
checkpoint();
@ -3165,12 +3210,12 @@ void ALTIUM_PCB::ParsePads6Data( const ALTIUM_PCB_COMPOUND_FILE& aAltiumPcbF
if( elem.component == ALTIUM_COMPONENT_NONE )
{
ConvertPads6ToBoardItem( elem );
ConvertPads6ToBoardItem( elem, padindex++ );
}
else
{
FOOTPRINT* footprint = HelperGetFootprint( elem.component );
ConvertPads6ToFootprintItem( footprint, elem );
ConvertPads6ToFootprintItem( footprint, elem, padindex++ );
}
}
@ -3179,13 +3224,13 @@ void ALTIUM_PCB::ParsePads6Data( const ALTIUM_PCB_COMPOUND_FILE& aAltiumPcbF
}
void ALTIUM_PCB::ConvertPads6ToBoardItem( const APAD6& aElem )
void ALTIUM_PCB::ConvertPads6ToBoardItem( const APAD6& aElem, const int aPadIndex )
{
// It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
&& aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
{
ConvertPads6ToBoardItemOnNonCopper( aElem );
ConvertPads6ToBoardItemOnNonCopper( aElem, aPadIndex );
}
else
{
@ -3193,7 +3238,7 @@ void ALTIUM_PCB::ConvertPads6ToBoardItem( const APAD6& aElem )
std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( m_board );
footprint->SetPosition( aElem.position );
ConvertPads6ToFootprintItemOnCopper( footprint.get(), aElem );
ConvertPads6ToFootprintItemOnCopper( footprint.get(), aElem, aPadIndex );
m_board->Add( footprint.release(), ADD_MODE::APPEND );
}
@ -3274,22 +3319,24 @@ void ALTIUM_PCB::ConvertVias6ToFootprintItem( FOOTPRINT* aFootprint, const AVIA6
}
void ALTIUM_PCB::ConvertPads6ToFootprintItem( FOOTPRINT* aFootprint, const APAD6& aElem )
void ALTIUM_PCB::ConvertPads6ToFootprintItem( FOOTPRINT* aFootprint, const APAD6& aElem,
const int aPadIndex )
{
// It is possible to place altium pads on non-copper layers -> we need to interpolate them using drawings!
if( !IsAltiumLayerCopper( aElem.layer ) && !IsAltiumLayerAPlane( aElem.layer )
&& aElem.layer != ALTIUM_LAYER::MULTI_LAYER )
{
ConvertPads6ToFootprintItemOnNonCopper( aFootprint, aElem );
ConvertPads6ToFootprintItemOnNonCopper( aFootprint, aElem, aPadIndex );
}
else
{
ConvertPads6ToFootprintItemOnCopper( aFootprint, aElem );
ConvertPads6ToFootprintItemOnCopper( aFootprint, aElem, aPadIndex );
}
}
void ALTIUM_PCB::ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, const APAD6& aElem )
void ALTIUM_PCB::ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, const APAD6& aElem,
const int aPadIndex )
{
std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
@ -3617,11 +3664,13 @@ void ALTIUM_PCB::ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, con
if( aElem.is_tent_bottom )
pad->SetLayerSet( pad->GetLayerSet().reset( B_Mask ) );
aFootprint->Add( pad.release(), ADD_MODE::APPEND );
PAD* pad_ptr = pad.release();
m_padIndexMap.emplace( aPadIndex, pad_ptr );
aFootprint->Add( pad_ptr, ADD_MODE::APPEND );
}
void ALTIUM_PCB::ConvertPads6ToBoardItemOnNonCopper( const APAD6& aElem )
void ALTIUM_PCB::ConvertPads6ToBoardItemOnNonCopper( const APAD6& aElem, const int aPadIndex )
{
PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
@ -3643,11 +3692,14 @@ void ALTIUM_PCB::ConvertPads6ToBoardItemOnNonCopper( const APAD6& aElem )
HelperParsePad6NonCopper( aElem, klayer, pad.get() );
m_board->Add( pad.release(), ADD_MODE::APPEND );
PCB_SHAPE* pad_ptr = pad.release();
m_padIndexMap.emplace( aPadIndex, pad_ptr );
m_board->Add( pad_ptr, ADD_MODE::APPEND );
}
void ALTIUM_PCB::ConvertPads6ToFootprintItemOnNonCopper( FOOTPRINT* aFootprint, const APAD6& aElem )
void ALTIUM_PCB::ConvertPads6ToFootprintItemOnNonCopper( FOOTPRINT* aFootprint, const APAD6& aElem,
const int aPadIndex )
{
PCB_LAYER_ID klayer = GetKicadLayer( aElem.layer );
@ -3689,7 +3741,9 @@ void ALTIUM_PCB::ConvertPads6ToFootprintItemOnNonCopper( FOOTPRINT* aFootprint,
HelperParsePad6NonCopper( aElem, klayer, pad.get() );
aFootprint->Add( pad.release(), ADD_MODE::APPEND );
PCB_SHAPE* pad_ptr = pad.release();
m_padIndexMap.emplace( aPadIndex, pad_ptr );
aFootprint->Add( pad_ptr, ADD_MODE::APPEND );
}

View File

@ -85,6 +85,8 @@ enum class ALTIUM_PCB_DIR
class BOARD;
class BOARD_CONNECTED_ITEM;
class PAD;
class FP_SHAPE;
class PCB_SHAPE;
class PCB_TEXTBOX;
@ -182,11 +184,14 @@ private:
const ACOMPONENTBODY6& aElem );
void ParsePads6Data( const ALTIUM_PCB_COMPOUND_FILE& aAltiumPcbFile,
const CFB::COMPOUND_FILE_ENTRY* aEntry );
void ConvertPads6ToBoardItem( const APAD6& aElem );
void ConvertPads6ToFootprintItem( FOOTPRINT* aFootprint, const APAD6& aElem );
void ConvertPads6ToBoardItemOnNonCopper( const APAD6& aElem );
void ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, const APAD6& aElem );
void ConvertPads6ToFootprintItemOnNonCopper( FOOTPRINT* aFootprint, const APAD6& aElem );
void ConvertPads6ToBoardItem( const APAD6& aElem, const int aPadIndex );
void ConvertPads6ToFootprintItem( FOOTPRINT* aFootprint, const APAD6& aElem,
const int aPadIndex );
void ConvertPads6ToBoardItemOnNonCopper( const APAD6& aElem, const int aPadIndex );
void ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, const APAD6& aElem,
const int aPadIndex );
void ConvertPads6ToFootprintItemOnNonCopper( FOOTPRINT* aFootprint, const APAD6& aElem,
const int aPadIndex );
void ParseVias6Data( const ALTIUM_PCB_COMPOUND_FILE& aAltiumPcbFile,
const CFB::COMPOUND_FILE_ENTRY* aEntry );
void ConvertVias6ToFootprintItem( FOOTPRINT* aFootprint, const AVIA6& aElem );
@ -261,6 +266,7 @@ private:
const ALTIUM_LAYER aAltiumLayer );
FOOTPRINT* HelperGetFootprint( uint16_t aComponent ) const;
PAD* HelperGetPad( int aPadIndex ) const;
void remapUnsureLayers( std::vector<ABOARD6_LAYER_STACKUP>& aStackup );
@ -277,6 +283,7 @@ private:
std::map<ALTIUM_RULE_KIND, std::vector<ARULE6>> m_rules;
std::map<ALTIUM_RECORD, std::multimap<int, const AEXTENDED_PRIMITIVE_INFORMATION>>
m_extendedPrimitiveInformationMaps;
std::map<int, BOARD_CONNECTED_ITEM*> m_padIndexMap;
std::map<ALTIUM_LAYER, ZONE*> m_outer_plane;