From 7102d9f72af121ec01ea0d50a7c0291653765ed7 Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Wed, 26 Jun 2024 17:27:08 -0700 Subject: [PATCH] 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. --- pcbnew/pad.cpp | 136 ++++++++++++++++++++++++++++ pcbnew/pad.h | 10 ++ pcbnew/pcb_io/altium/altium_pcb.cpp | 74 +++------------ pcbnew/tools/pad_tool.cpp | 131 +-------------------------- 4 files changed, 159 insertions(+), 192 deletions(-) diff --git a/pcbnew/pad.cpp b/pcbnew/pad.cpp index 37feb92717..88175bd38c 100644 --- a/pcbnew/pad.cpp +++ b/pcbnew/pad.cpp @@ -1921,6 +1921,142 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, } +std::vector 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( 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 + { + std::vector matching; + + for( BOARD_ITEM* item : footprint->GraphicalItems() ) + { + PCB_SHAPE* other = dynamic_cast( 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 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( 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, const std::function& aErrorHandler ) const diff --git a/pcbnew/pad.h b/pcbnew/pad.h index e9db76940a..1a18962c33 100644 --- a/pcbnew/pad.h +++ b/pcbnew/pad.h @@ -733,6 +733,16 @@ public: bool HitTest( const VECTOR2I& aPosition, 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 Recombine( bool aIsDryRun, int aMaxError ); + wxString GetClass() const override { return wxT( "PAD" ); diff --git a/pcbnew/pcb_io/altium/altium_pcb.cpp b/pcbnew/pcb_io/altium/altium_pcb.cpp index a149543926..4d52b36d9a 100644 --- a/pcbnew/pcb_io/altium/altium_pcb.cpp +++ b/pcbnew/pcb_io/altium/altium_pcb.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -813,6 +814,11 @@ FOOTPRINT* ALTIUM_PCB::ParseFootprint( ALTIUM_COMPOUND_FILE& altiumLibFile, // Auto-position reference and value footprint->AutoPositionFields(); + for( PAD* pad : footprint->Pads() ) + { + pad->Recombine( false, ARC_HIGH_DEF ); + } + if( parser.HasParsingError() ) { 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 ); } - if( aLayer == F_Cu || aLayer == B_Cu ) - { - std::unique_ptr pad = std::make_unique( aFootprint ); + std::unique_ptr shape = std::make_unique( aFootprint, SHAPE_T::POLY ); - LSET padLayers; - padLayers.set( aLayer ); + shape->SetPolyShape( polySet ); + shape->SetFilled( true ); + shape->SetLayer( aLayer ); + shape->SetStroke( STROKE_PARAMS( 0 ) ); - pad->SetAttribute( PAD_ATTRIB::SMD ); - 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 shape = std::make_unique( 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 ); - } + aFootprint->Add( shape.release(), ADD_MODE::APPEND ); } diff --git a/pcbnew/tools/pad_tool.cpp b/pcbnew/tools/pad_tool.cpp index 7ff1c096b2..c89680ee2d 100644 --- a/pcbnew/tools/pad_tool.cpp +++ b/pcbnew/tools/pad_tool.cpp @@ -848,140 +848,11 @@ void PAD_TOOL::explodePad( PAD* aPad, PCB_LAYER_ID* aLayer, BOARD_COMMIT& aCommi std::vector PAD_TOOL::RecombinePad( PAD* aPad, bool aIsDryRun ) { 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. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear ); - for( BOARD_ITEM* item : footprint->GraphicalItems() ) - 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( 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 - { - std::vector matching; - - for( BOARD_ITEM* item : footprint->GraphicalItems() ) - { - PCB_SHAPE* other = dynamic_cast( 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 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( 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; + return aPad->Recombine( aIsDryRun, maxError ); }