Altium import: handle polygons on copper

In Altium, copper polys will be connected automatically to their
associated pads.  In KiCad, we need to do the equivalent when parsing,
which is to combine the joining copper into the underlying pad.  We also
don't want to treat copper polys as proxy pads without the original
anymore.
This commit is contained in:
Seth Hillbrand 2024-06-26 17:27:08 -07:00
parent f97ac5ce73
commit 7102d9f72a
4 changed files with 159 additions and 192 deletions

View File

@ -1921,6 +1921,142 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer,
} }
std::vector<PCB_SHAPE*> PAD::Recombine( bool aIsDryRun, int maxError )
{
FOOTPRINT* footprint = GetParentFootprint();
for( BOARD_ITEM* item : footprint->GraphicalItems() )
item->ClearFlags( SKIP_STRUCT );
auto findNext =
[&]( PCB_LAYER_ID aLayer ) -> PCB_SHAPE*
{
SHAPE_POLY_SET padPoly;
TransformShapeToPolygon( padPoly, aLayer, 0, maxError, ERROR_INSIDE );
for( BOARD_ITEM* item : footprint->GraphicalItems() )
{
PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
if( !shape || ( shape->GetFlags() & SKIP_STRUCT ) )
continue;
if( shape->GetLayer() != aLayer )
continue;
if( shape->IsProxyItem() ) // Pad number (and net name) box
return shape;
SHAPE_POLY_SET drawPoly;
shape->TransformShapeToPolygon( drawPoly, aLayer, 0, maxError, ERROR_INSIDE );
drawPoly.BooleanIntersection( padPoly, SHAPE_POLY_SET::PM_FAST );
if( !drawPoly.IsEmpty() )
return shape;
}
return nullptr;
};
auto findMatching =
[&]( PCB_SHAPE* aShape ) -> std::vector<PCB_SHAPE*>
{
std::vector<PCB_SHAPE*> matching;
for( BOARD_ITEM* item : footprint->GraphicalItems() )
{
PCB_SHAPE* other = dynamic_cast<PCB_SHAPE*>( item );
if( !other || ( other->GetFlags() & SKIP_STRUCT ) )
continue;
if( GetLayerSet().test( other->GetLayer() )
&& aShape->Compare( other ) == 0 )
{
matching.push_back( other );
}
}
return matching;
};
PCB_LAYER_ID layer;
std::vector<PCB_SHAPE*> mergedShapes;
if( IsOnLayer( F_Cu ) )
layer = F_Cu;
else if( IsOnLayer( B_Cu ) )
layer = B_Cu;
else
layer = *GetLayerSet().UIOrder();
// If there are intersecting items to combine, we need to first make sure the pad is a
// custom-shape pad.
if( !aIsDryRun && findNext( layer ) && GetShape() != PAD_SHAPE::CUSTOM )
{
if( GetShape() == PAD_SHAPE::CIRCLE || GetShape() == PAD_SHAPE::RECTANGLE )
{
// Use the existing pad as an anchor
SetAnchorPadShape( GetShape() );
SetShape( PAD_SHAPE::CUSTOM );
}
else
{
// Create a new circular anchor and convert existing pad to a polygon primitive
SHAPE_POLY_SET existingOutline;
TransformShapeToPolygon( existingOutline, layer, 0, maxError, ERROR_INSIDE );
int minExtent = std::min( GetSize().x, GetSize().y );
SetAnchorPadShape( PAD_SHAPE::CIRCLE );
SetSize( VECTOR2I( minExtent, minExtent ) );
SetShape( PAD_SHAPE::CUSTOM );
PCB_SHAPE* shape = new PCB_SHAPE( nullptr, SHAPE_T::POLY );
shape->SetFilled( true );
shape->SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
shape->SetPolyShape( existingOutline );
shape->Rotate( VECTOR2I( 0, 0 ), - GetOrientation() );
shape->Move( - ShapePos() );
AddPrimitive( shape );
}
}
while( PCB_SHAPE* fpShape = findNext( layer ) )
{
fpShape->SetFlags( SKIP_STRUCT );
mergedShapes.push_back( fpShape );
if( !aIsDryRun )
{
PCB_SHAPE* primitive = static_cast<PCB_SHAPE*>( fpShape->Duplicate() );
primitive->SetParent( nullptr );
primitive->Move( - ShapePos() );
primitive->Rotate( VECTOR2I( 0, 0 ), - GetOrientation() );
AddPrimitive( primitive );
}
// See if there are other shapes that match and mark them for delete. (KiCad won't
// produce these, but old footprints from other vendors have them.)
for( PCB_SHAPE* other : findMatching( fpShape ) )
{
other->SetFlags( SKIP_STRUCT );
mergedShapes.push_back( other );
}
}
for( BOARD_ITEM* item : footprint->GraphicalItems() )
item->ClearFlags( SKIP_STRUCT );
if( !aIsDryRun )
ClearFlags( ENTERED );
return mergedShapes;
}
void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider, void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider,
const std::function<void( int aErrorCode, const std::function<void( int aErrorCode,
const wxString& aMsg )>& aErrorHandler ) const const wxString& aMsg )>& aErrorHandler ) const

View File

@ -733,6 +733,16 @@ public:
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override; bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override;
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override; bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
/**
* Recombines the pad with other graphical shapes in the footprint
*
* @param aIsDryRun if true, the pad will not be recombined but the operation will still be logged
* @param aMaxError the maximum error to allow for converting arcs to polygons
* @return a list of shapes that were recombined
*/
std::vector<PCB_SHAPE*> Recombine( bool aIsDryRun, int aMaxError );
wxString GetClass() const override wxString GetClass() const override
{ {
return wxT( "PAD" ); return wxT( "PAD" );

View File

@ -37,6 +37,7 @@
#include <pcb_track.h> #include <pcb_track.h>
#include <core/profile.h> #include <core/profile.h>
#include <string_utils.h> #include <string_utils.h>
#include <tools/pad_tool.h>
#include <zone.h> #include <zone.h>
#include <board_stackup_manager/stackup_predefined_prms.h> #include <board_stackup_manager/stackup_predefined_prms.h>
@ -813,6 +814,11 @@ FOOTPRINT* ALTIUM_PCB::ParseFootprint( ALTIUM_COMPOUND_FILE& altiumLibFile,
// Auto-position reference and value // Auto-position reference and value
footprint->AutoPositionFields(); footprint->AutoPositionFields();
for( PAD* pad : footprint->Pads() )
{
pad->Recombine( false, ARC_HIGH_DEF );
}
if( parser.HasParsingError() ) if( parser.HasParsingError() )
{ {
THROW_IO_ERROR( wxString::Format( wxT( "%s stream was not parsed correctly" ), THROW_IO_ERROR( wxString::Format( wxT( "%s stream was not parsed correctly" ),
@ -2582,70 +2588,14 @@ void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItemOnLayer( FOOTPRINT*
polySet.AddHole( hole_linechain ); polySet.AddHole( hole_linechain );
} }
if( aLayer == F_Cu || aLayer == B_Cu ) std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
{
std::unique_ptr<PAD> pad = std::make_unique<PAD>( aFootprint );
LSET padLayers; shape->SetPolyShape( polySet );
padLayers.set( aLayer ); shape->SetFilled( true );
shape->SetLayer( aLayer );
shape->SetStroke( STROKE_PARAMS( 0 ) );
pad->SetAttribute( PAD_ATTRIB::SMD ); aFootprint->Add( shape.release(), ADD_MODE::APPEND );
pad->SetShape( PAD_SHAPE::CUSTOM );
pad->SetThermalSpokeAngle( ANGLE_90 );
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.release(), ADD_MODE::APPEND );
}
else
{
std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( aFootprint, SHAPE_T::POLY );
shape->SetPolyShape( polySet );
shape->SetFilled( true );
shape->SetLayer( aLayer );
shape->SetStroke( STROKE_PARAMS( 0 ) );
aFootprint->Add( shape.release(), ADD_MODE::APPEND );
}
} }

View File

@ -848,140 +848,11 @@ void PAD_TOOL::explodePad( PAD* aPad, PCB_LAYER_ID* aLayer, BOARD_COMMIT& aCommi
std::vector<PCB_SHAPE*> PAD_TOOL::RecombinePad( PAD* aPad, bool aIsDryRun ) std::vector<PCB_SHAPE*> PAD_TOOL::RecombinePad( PAD* aPad, bool aIsDryRun )
{ {
int maxError = board()->GetDesignSettings().m_MaxError; int maxError = board()->GetDesignSettings().m_MaxError;
FOOTPRINT* footprint = aPad->GetParentFootprint();
// Don't leave an object in the point editor that might no longer exist after recombining. // Don't leave an object in the point editor that might no longer exist after recombining.
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear ); m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
for( BOARD_ITEM* item : footprint->GraphicalItems() ) return aPad->Recombine( aIsDryRun, maxError );
item->ClearFlags( SKIP_STRUCT );
auto findNext =
[&]( PCB_LAYER_ID aLayer ) -> PCB_SHAPE*
{
SHAPE_POLY_SET padPoly;
aPad->TransformShapeToPolygon( padPoly, aLayer, 0, maxError, ERROR_INSIDE );
for( BOARD_ITEM* item : footprint->GraphicalItems() )
{
PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
if( !shape || ( shape->GetFlags() & SKIP_STRUCT ) )
continue;
if( shape->GetLayer() != aLayer )
continue;
if( shape->IsProxyItem() ) // Pad number (and net name) box
return shape;
SHAPE_POLY_SET drawPoly;
shape->TransformShapeToPolygon( drawPoly, aLayer, 0, maxError, ERROR_INSIDE );
drawPoly.BooleanIntersection( padPoly, SHAPE_POLY_SET::PM_FAST );
if( !drawPoly.IsEmpty() )
return shape;
}
return nullptr;
};
auto findMatching =
[&]( PCB_SHAPE* aShape ) -> std::vector<PCB_SHAPE*>
{
std::vector<PCB_SHAPE*> matching;
for( BOARD_ITEM* item : footprint->GraphicalItems() )
{
PCB_SHAPE* other = dynamic_cast<PCB_SHAPE*>( item );
if( !other || ( other->GetFlags() & SKIP_STRUCT ) )
continue;
if( aPad->GetLayerSet().test( other->GetLayer() )
&& aShape->Compare( other ) == 0 )
{
matching.push_back( other );
}
}
return matching;
};
PCB_LAYER_ID layer;
std::vector<PCB_SHAPE*> mergedShapes;
if( aPad->IsOnLayer( F_Cu ) )
layer = F_Cu;
else if( aPad->IsOnLayer( B_Cu ) )
layer = B_Cu;
else
layer = *aPad->GetLayerSet().UIOrder();
// If there are intersecting items to combine, we need to first make sure the pad is a
// custom-shape pad.
if( !aIsDryRun && findNext( layer ) && aPad->GetShape() != PAD_SHAPE::CUSTOM )
{
if( aPad->GetShape() == PAD_SHAPE::CIRCLE || aPad->GetShape() == PAD_SHAPE::RECTANGLE )
{
// Use the existing pad as an anchor
aPad->SetAnchorPadShape( aPad->GetShape() );
aPad->SetShape( PAD_SHAPE::CUSTOM );
}
else
{
// Create a new circular anchor and convert existing pad to a polygon primitive
SHAPE_POLY_SET existingOutline;
aPad->TransformShapeToPolygon( existingOutline, layer, 0, maxError, ERROR_INSIDE );
int minExtent = std::min( aPad->GetSize().x, aPad->GetSize().y );
aPad->SetAnchorPadShape( PAD_SHAPE::CIRCLE );
aPad->SetSize( VECTOR2I( minExtent, minExtent ) );
aPad->SetShape( PAD_SHAPE::CUSTOM );
PCB_SHAPE* shape = new PCB_SHAPE( nullptr, SHAPE_T::POLY );
shape->SetFilled( true );
shape->SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
shape->SetPolyShape( existingOutline );
shape->Rotate( VECTOR2I( 0, 0 ), - aPad->GetOrientation() );
shape->Move( - aPad->ShapePos() );
aPad->AddPrimitive( shape );
}
}
while( PCB_SHAPE* fpShape = findNext( layer ) )
{
fpShape->SetFlags( SKIP_STRUCT );
mergedShapes.push_back( fpShape );
if( !aIsDryRun )
{
PCB_SHAPE* primitive = static_cast<PCB_SHAPE*>( fpShape->Duplicate() );
primitive->SetParent( nullptr );
primitive->Move( - aPad->ShapePos() );
primitive->Rotate( VECTOR2I( 0, 0 ), - aPad->GetOrientation() );
aPad->AddPrimitive( primitive );
}
// See if there are other shapes that match and mark them for delete. (KiCad won't
// produce these, but old footprints from other vendors have them.)
for( PCB_SHAPE* other : findMatching( fpShape ) )
{
other->SetFlags( SKIP_STRUCT );
mergedShapes.push_back( other );
}
}
for( BOARD_ITEM* item : footprint->GraphicalItems() )
item->ClearFlags( SKIP_STRUCT );
if( !aIsDryRun )
aPad->ClearFlags( ENTERED );
return mergedShapes;
} }