Altium PCB: support solder/paste mask expansion rules.

- Writes expansion values to board design settings
- Imports footprint regions on Cu layers as pads
- Adds support for holes in non-Cu polygons in footprints
This commit is contained in:
Alex Shvartzkop 2023-07-21 20:02:59 +05:00
parent 8e1466a35a
commit acc03e91f3
4 changed files with 120 additions and 19 deletions

View File

@ -2,6 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Copyright (C) 2020 Thomas Pointhuber <thomas.pointhuber@gmx.at> * Copyright (C) 2020 Thomas Pointhuber <thomas.pointhuber@gmx.at>
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -533,6 +534,13 @@ ARULE6::ARULE6( ALTIUM_PARSER& aReader )
else if( rulekind == wxT( "PasteMaskExpansion" ) ) else if( rulekind == wxT( "PasteMaskExpansion" ) )
{ {
kind = ALTIUM_RULE_KIND::PASTE_MASK_EXPANSION; kind = ALTIUM_RULE_KIND::PASTE_MASK_EXPANSION;
pastemaskExpansion = ALTIUM_PARSER::ReadKicadUnit( props, wxT( "EXPANSION" ), wxT( "0" ) );
}
else if( rulekind == wxT( "SolderMaskExpansion" ) )
{
kind = ALTIUM_RULE_KIND::SOLDER_MASK_EXPANSION;
soldermaskExpansion =
ALTIUM_PARSER::ReadKicadUnit( props, wxT( "EXPANSION" ), wxT( "4mil" ) );
} }
else if( rulekind == wxT( "PlaneClearance" ) ) else if( rulekind == wxT( "PlaneClearance" ) )
{ {

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Copyright (C) 2020 Thomas Pointhuber <thomas.pointhuber@gmx.at> * Copyright (C) 2020 Thomas Pointhuber <thomas.pointhuber@gmx.at>
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors. * Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -105,8 +105,9 @@ enum class ALTIUM_RULE_KIND
HOLE_TO_HOLE_CLEARANCE = 5, HOLE_TO_HOLE_CLEARANCE = 5,
WIDTH = 6, WIDTH = 6,
PASTE_MASK_EXPANSION = 7, PASTE_MASK_EXPANSION = 7,
PLANE_CLEARANCE = 8, SOLDER_MASK_EXPANSION = 8,
POLYGON_CONNECT = 9, PLANE_CLEARANCE = 9,
POLYGON_CONNECT = 10,
}; };
enum class ALTIUM_CONNECT_STYLE enum class ALTIUM_CONNECT_STYLE
@ -502,6 +503,12 @@ struct ARULE6
// ALTIUM_RULE_KIND::PLANE_CLEARANCE // ALTIUM_RULE_KIND::PLANE_CLEARANCE
int planeclearanceClearance; int planeclearanceClearance;
// ALTIUM_RULE_KIND::SOLDER_MASK_EXPANSION
int soldermaskExpansion;
// ALTIUM_RULE_KIND::PASTE_MASK_EXPANSION
int pastemaskExpansion;
// ALTIUM_RULE_KIND::POLYGON_CONNECT // ALTIUM_RULE_KIND::POLYGON_CONNECT
int32_t polygonconnectAirgapwidth; int32_t polygonconnectAirgapwidth;
int32_t polygonconnectReliefconductorwidth; int32_t polygonconnectReliefconductorwidth;

View File

@ -771,7 +771,7 @@ FOOTPRINT* ALTIUM_PCB::ParseFootprint( const ALTIUM_COMPOUND_FILE& altiumLibFile
case ALTIUM_RECORD::REGION: case ALTIUM_RECORD::REGION:
{ {
AREGION6 region( parser, false ); AREGION6 region( parser, false );
ConvertShapeBasedRegions6ToFootprintItem( footprint.get(), region ); ConvertShapeBasedRegions6ToFootprintItem( footprint.get(), region, primitiveIndex );
break; break;
} }
case ALTIUM_RECORD::MODEL: case ALTIUM_RECORD::MODEL:
@ -1866,6 +1866,15 @@ void ALTIUM_PCB::ParseRules6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile
} ); } );
} }
const ARULE6* soldermaskRule = GetRuleDefault( ALTIUM_RULE_KIND::SOLDER_MASK_EXPANSION );
const ARULE6* pastemaskRule = GetRuleDefault( ALTIUM_RULE_KIND::PASTE_MASK_EXPANSION );
if( soldermaskRule )
m_board->GetDesignSettings().m_SolderMaskExpansion = soldermaskRule->soldermaskExpansion;
if( pastemaskRule )
m_board->GetDesignSettings().m_SolderPasteMargin = pastemaskRule->pastemaskExpansion;
if( reader.GetRemainingBytes() != 0 ) if( reader.GetRemainingBytes() != 0 )
THROW_IO_ERROR( wxT( "Rules6 stream is not fully parsed" ) ); THROW_IO_ERROR( wxT( "Rules6 stream is not fully parsed" ) );
} }
@ -1894,11 +1903,12 @@ void ALTIUM_PCB::ParseShapeBasedRegions6Data( const ALTIUM_COMPOUND_FILE& aA
const CFB::COMPOUND_FILE_ENTRY* aEntry ) const CFB::COMPOUND_FILE_ENTRY* aEntry )
{ {
if( m_progressReporter ) if( m_progressReporter )
m_progressReporter->Report( _( "Loading zones..." ) ); m_progressReporter->Report( _( "Loading polygons..." ) );
ALTIUM_PARSER reader( aAltiumPcbFile, aEntry ); ALTIUM_PARSER reader( aAltiumPcbFile, aEntry );
while( reader.GetRemainingBytes() >= 4 /* TODO: use Header section of file */ ) /* TODO: use Header section of file */
for( int primitiveIndex = 0; reader.GetRemainingBytes() >= 4; primitiveIndex++ )
{ {
checkpoint(); checkpoint();
AREGION6 elem( reader, true ); AREGION6 elem( reader, true );
@ -1912,7 +1922,7 @@ void ALTIUM_PCB::ParseShapeBasedRegions6Data( const ALTIUM_COMPOUND_FILE& aA
else else
{ {
FOOTPRINT* footprint = HelperGetFootprint( elem.component ); FOOTPRINT* footprint = HelperGetFootprint( elem.component );
ConvertShapeBasedRegions6ToFootprintItem( footprint, elem ); ConvertShapeBasedRegions6ToFootprintItem( footprint, elem, primitiveIndex );
} }
} }
@ -1974,7 +1984,8 @@ void ALTIUM_PCB::ConvertShapeBasedRegions6ToBoardItem( const AREGION6& aElem )
void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItem( FOOTPRINT* aFootprint, void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItem( FOOTPRINT* aFootprint,
const AREGION6& aElem ) const AREGION6& aElem,
const int aPrimitiveIndex )
{ {
if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout ) if( aElem.kind == ALTIUM_REGION_KIND::POLYGON_CUTOUT || aElem.is_keepout )
{ {
@ -2010,7 +2021,8 @@ void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItem( FOOTPRINT* aFoot
if( aElem.subpolyindex == ALTIUM_POLYGON_NONE ) if( aElem.subpolyindex == ALTIUM_POLYGON_NONE )
{ {
for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) ) for( PCB_LAYER_ID klayer : GetKicadLayersToIterate( aElem.layer ) )
ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer ); ConvertShapeBasedRegions6ToFootprintItemOnLayer( aFootprint, aElem, klayer,
aPrimitiveIndex );
} }
} }
else else
@ -2062,7 +2074,8 @@ void ALTIUM_PCB::ConvertShapeBasedRegions6ToBoardItemOnLayer( const AREGION6& aE
void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItemOnLayer( FOOTPRINT* aFootprint, void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItemOnLayer( FOOTPRINT* aFootprint,
const AREGION6& aElem, const AREGION6& aElem,
PCB_LAYER_ID aLayer ) PCB_LAYER_ID aLayer,
const int aPrimitiveIndex )
{ {
SHAPE_LINE_CHAIN linechain; SHAPE_LINE_CHAIN linechain;
HelperShapeLineChainFromAltiumVertices( linechain, aElem.outline ); HelperShapeLineChainFromAltiumVertices( linechain, aElem.outline );
@ -2076,15 +2089,85 @@ void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItemOnLayer( FOOTPRINT*
return; return;
} }
FP_SHAPE* shape = new FP_SHAPE( aFootprint, SHAPE_T::POLY ); SHAPE_POLY_SET polySet;
polySet.AddOutline( linechain );
shape->SetPolyShape( linechain ); for( const std::vector<ALTIUM_VERTICE>& hole : aElem.holes )
shape->SetFilled( true ); {
shape->SetLayer( aLayer ); SHAPE_LINE_CHAIN hole_linechain;
shape->SetStroke( STROKE_PARAMS( 0 ) ); HelperShapeLineChainFromAltiumVertices( hole_linechain, hole );
HelperShapeSetLocalCoord( shape ); if( hole_linechain.PointCount() < 3 )
aFootprint->Add( shape, ADD_MODE::APPEND ); continue;
polySet.AddHole( hole_linechain );
}
if( aLayer == F_Cu || aLayer == B_Cu )
{
PAD* pad = new PAD( aFootprint );
LSET padLayers;
padLayers.set( aLayer );
pad->SetKeepTopBottom( false ); // TODO: correct? This seems to be KiCad default on import
pad->SetAttribute( PAD_ATTRIB::SMD );
pad->SetShape( PAD_SHAPE::CUSTOM );
int anchorSize = 1;
VECTOR2I anchorPos = linechain.CPoint( 0 );
pad->SetShape( PAD_SHAPE::CUSTOM );
pad->SetAnchorPadShape( PAD_SHAPE::CIRCLE );
pad->SetSize( { anchorSize, anchorSize } );
pad->SetPosition( anchorPos );
SHAPE_POLY_SET shapePolys = polySet;
shapePolys.Move( -anchorPos );
pad->AddPrimitivePoly( shapePolys, 0, true );
auto& map = m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::REGION];
auto it = map.find( aPrimitiveIndex );
if( it != map.end() )
{
const AEXTENDED_PRIMITIVE_INFORMATION& info = it->second;
if( info.pastemaskexpansionmode == ALTIUM_MODE::MANUAL )
{
pad->SetLocalSolderPasteMargin(
info.pastemaskexpansionmanual ? info.pastemaskexpansionmanual : 1 );
}
if( info.soldermaskexpansionmode == ALTIUM_MODE::MANUAL )
{
pad->SetLocalSolderMaskMargin(
info.soldermaskexpansionmanual ? info.soldermaskexpansionmanual : 1 );
}
if( info.pastemaskexpansionmode != ALTIUM_MODE::NONE )
padLayers.set( aLayer == F_Cu ? F_Paste : B_Paste );
if( info.soldermaskexpansionmode != ALTIUM_MODE::NONE )
padLayers.set( aLayer == F_Cu ? F_Mask : B_Mask );
}
pad->SetLayerSet( padLayers );
aFootprint->Add( pad, ADD_MODE::APPEND );
}
else
{
PCB_SHAPE* shape = new PCB_SHAPE( aFootprint, SHAPE_T::POLY );
shape->SetPolyShape( polySet );
shape->SetFilled( true );
shape->SetLayer( aLayer );
shape->SetStroke( STROKE_PARAMS( 0 ) );
HelperShapeSetLocalCoord(shape);
aFootprint->Add( shape, ADD_MODE::APPEND );
}
} }

View File

@ -2,6 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Copyright (C) 2019-2020 Thomas Pointhuber <thomas.pointhuber@gmx.at> * Copyright (C) 2019-2020 Thomas Pointhuber <thomas.pointhuber@gmx.at>
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -196,11 +197,13 @@ private:
void ParseShapeBasedRegions6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile, void ParseShapeBasedRegions6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
const CFB::COMPOUND_FILE_ENTRY* aEntry ); const CFB::COMPOUND_FILE_ENTRY* aEntry );
void ConvertShapeBasedRegions6ToBoardItem( const AREGION6& aElem ); void ConvertShapeBasedRegions6ToBoardItem( const AREGION6& aElem );
void ConvertShapeBasedRegions6ToFootprintItem( FOOTPRINT* aFootprint, const AREGION6& aElem ); void ConvertShapeBasedRegions6ToFootprintItem( FOOTPRINT* aFootprint, const AREGION6& aElem,
const int aPrimitiveIndex );
void ConvertShapeBasedRegions6ToBoardItemOnLayer( const AREGION6& aElem, PCB_LAYER_ID aLayer ); void ConvertShapeBasedRegions6ToBoardItemOnLayer( const AREGION6& aElem, PCB_LAYER_ID aLayer );
void ConvertShapeBasedRegions6ToFootprintItemOnLayer( FOOTPRINT* aFootprint, void ConvertShapeBasedRegions6ToFootprintItemOnLayer( FOOTPRINT* aFootprint,
const AREGION6& aElem, const AREGION6& aElem,
PCB_LAYER_ID aLayer ); PCB_LAYER_ID aLayer,
const int aPrimitiveIndex );
void ParseExtendedPrimitiveInformationData( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile, void ParseExtendedPrimitiveInformationData( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,
const CFB::COMPOUND_FILE_ENTRY* aEntry ); const CFB::COMPOUND_FILE_ENTRY* aEntry );
void ParseRegions6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile, void ParseRegions6Data( const ALTIUM_COMPOUND_FILE& aAltiumPcbFile,