diff --git a/3d-viewer/3d_canvas/board_adapter.h b/3d-viewer/3d_canvas/board_adapter.h index c0f4675478..cedf6ffd38 100644 --- a/3d-viewer/3d_canvas/board_adapter.h +++ b/3d-viewer/3d_canvas/board_adapter.h @@ -408,7 +408,8 @@ private: const EDA_ANGLE& aArcAngle, int aCircleToSegmentsCount, int aWidth, CONTAINER_2D_BASE* aContainer, const BOARD_ITEM& aOwner ); - void buildPadOutlineAsSegments( const PAD* aPad, CONTAINER_2D_BASE* aDstContainer, int aWidth ); + void buildPadOutlineAsSegments( const PAD* aPad, PCB_LAYER_ID aLayer, + CONTAINER_2D_BASE* aDstContainer, int aWidth ); public: static CUSTOM_COLORS_LIST g_SilkColors; diff --git a/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp b/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp index aa8783b147..0780622b9f 100644 --- a/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp +++ b/3d-viewer/3d_canvas/create_3Dgraphic_brd_items.cpp @@ -366,22 +366,22 @@ void BOARD_ADAPTER::createPadWithMargin( const PAD* aPad, CONTAINER_2D_BASE* aCo // is only the size of the anchor), so for those we punt and just use aMargin.x. if( ( clearance.x < 0 || clearance.x != clearance.y ) - && aPad->GetShape() != PAD_SHAPE::CUSTOM ) + && aPad->GetShape( aLayer ) != PAD_SHAPE::CUSTOM ) { - VECTOR2I dummySize = VECTOR2I( aPad->GetSize() ) + clearance + clearance; + VECTOR2I dummySize = VECTOR2I( aPad->GetSize( aLayer ) ) + clearance + clearance; if( dummySize.x <= 0 || dummySize.y <= 0 ) return; PAD dummy( *aPad ); - dummy.SetSize( VECTOR2I( dummySize.x, dummySize.y ) ); + dummy.SetSize( aLayer, VECTOR2I( dummySize.x, dummySize.y ) ); dummy.TransformShapeToPolygon( poly, aLayer, 0, maxError, ERROR_INSIDE ); clearance = { 0, 0 }; // Remove group membership from dummy item before deleting dummy.SetParentGroup( nullptr ); } - else if( aPad->GetShape() == PAD_SHAPE::CUSTOM ) + else if( aPad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM ) { // A custom pad can have many complex subshape items. To avoid issues, use its // final polygon shape, not its basic shape set. One cannot apply the clearance @@ -390,7 +390,7 @@ void BOARD_ADAPTER::createPadWithMargin( const PAD* aPad, CONTAINER_2D_BASE* aCo } else { - auto padShapes = std::static_pointer_cast( aPad->GetEffectiveShape() ); + auto padShapes = std::static_pointer_cast( aPad->GetEffectiveShape( aLayer ) ); for( const SHAPE* shape : padShapes->Shapes() ) { @@ -540,13 +540,13 @@ void BOARD_ADAPTER::addPads( const FOOTPRINT* aFootprint, CONTAINER_2D_BASE* aCo case F_Mask: case B_Mask: - margin.x += pad->GetSolderMaskExpansion(); - margin.y += pad->GetSolderMaskExpansion(); + margin.x += pad->GetSolderMaskExpansion( aLayerId ); + margin.y += pad->GetSolderMaskExpansion( aLayerId ); break; case F_Paste: case B_Paste: - margin += pad->GetSolderPasteMargin(); + margin += pad->GetSolderPasteMargin( aLayerId ); break; default: @@ -747,8 +747,8 @@ void BOARD_ADAPTER::addShape( const PCB_TEXTBOX* aTextBox, CONTAINER_2D_BASE* aC { SHAPE_POLY_SET polyList; - aTextBox->PCB_SHAPE::TransformShapeToPolygon( polyList, UNDEFINED_LAYER, 0, - ARC_HIGH_DEF, ERROR_INSIDE ); + aTextBox->PCB_SHAPE::TransformShapeToPolygon( polyList, UNDEFINED_LAYER, 0, ARC_HIGH_DEF, + ERROR_INSIDE ); ConvertPolygonToTriangles( polyList, *aContainer, m_biuTo3Dunits, *aOwner ); } @@ -775,13 +775,13 @@ void BOARD_ADAPTER::addSolidAreasShapes( const ZONE* aZone, CONTAINER_2D_BASE* a } -void BOARD_ADAPTER::buildPadOutlineAsSegments( const PAD* aPad, CONTAINER_2D_BASE* aContainer, - int aWidth ) +void BOARD_ADAPTER::buildPadOutlineAsSegments( const PAD* aPad, PCB_LAYER_ID aLayer, + CONTAINER_2D_BASE* aContainer, int aWidth ) { - if( aPad->GetShape() == PAD_SHAPE::CIRCLE ) // Draw a ring + if( aPad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE ) // Draw a ring { - const SFVEC2F center3DU = TO_SFVEC2F( aPad->ShapePos() ); - const int radius = aPad->GetSize().x / 2; + const SFVEC2F center3DU = TO_SFVEC2F( aPad->ShapePos( aLayer ) ); + const int radius = aPad->GetSize( aLayer ).x / 2; const float inner_radius3DU = TO_3DU( radius - aWidth / 2.0 ); const float outer_radius3DU = TO_3DU( radius + aWidth / 2.0 ); @@ -790,7 +790,8 @@ void BOARD_ADAPTER::buildPadOutlineAsSegments( const PAD* aPad, CONTAINER_2D_BAS else { // For other shapes, add outlines as thick segments in polygon buffer - const std::shared_ptr& corners = aPad->GetEffectivePolygon( ERROR_INSIDE ); + const std::shared_ptr& corners = aPad->GetEffectivePolygon( aLayer, + ERROR_INSIDE ); const SHAPE_LINE_CHAIN& path = corners->COutline( 0 ); for( int j = 0; j < path.PointCount(); j++ ) diff --git a/3d-viewer/3d_canvas/create_layer_items.cpp b/3d-viewer/3d_canvas/create_layer_items.cpp index 18bec6c2f3..ddafc41411 100644 --- a/3d-viewer/3d_canvas/create_layer_items.cpp +++ b/3d-viewer/3d_canvas/create_layer_items.cpp @@ -65,18 +65,18 @@ /* * This is used to draw pad outlines on silk layers. */ -void buildPadOutlineAsPolygon( const PAD* aPad, SHAPE_POLY_SET& aBuffer, int aWidth, int aMaxError, - ERROR_LOC aErrorLoc ) +void buildPadOutlineAsPolygon( const PAD* aPad, PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aBuffer, + int aWidth, int aMaxError, ERROR_LOC aErrorLoc ) { - if( aPad->GetShape() == PAD_SHAPE::CIRCLE ) // Draw a ring + if( aPad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE ) // Draw a ring { - TransformRingToPolygon( aBuffer, aPad->ShapePos(), aPad->GetSize().x / 2, aWidth, - aMaxError, aErrorLoc ); + TransformRingToPolygon( aBuffer, aPad->ShapePos( aLayer ), aPad->GetSize( aLayer ).x / 2, + aWidth, aMaxError, aErrorLoc ); } else { // For other shapes, add outlines as thick segments in polygon buffer - const SHAPE_LINE_CHAIN& path = aPad->GetEffectivePolygon( ERROR_INSIDE )->COutline( 0 ); + const SHAPE_LINE_CHAIN& path = aPad->GetEffectivePolygon( aLayer, ERROR_INSIDE )->COutline( 0 ); for( int ii = 0; ii < path.PointCount(); ++ii ) { @@ -903,7 +903,7 @@ void BOARD_ADAPTER::createLayers( REPORTER* aStatusReporter ) if( !pad->IsOnLayer( layer ) ) continue; - buildPadOutlineAsSegments( pad, layerContainer, linewidth ); + buildPadOutlineAsSegments( pad, layer, layerContainer, linewidth ); } } else @@ -995,7 +995,7 @@ void BOARD_ADAPTER::createLayers( REPORTER* aStatusReporter ) { if( pad->IsOnLayer( layer ) ) { - buildPadOutlineAsPolygon( pad, *layerPoly, linewidth, maxError, + buildPadOutlineAsPolygon( pad, layer, *layerPoly, linewidth, maxError, ERROR_INSIDE ); } } diff --git a/api/proto/board/board_types.proto b/api/proto/board/board_types.proto index a605419b00..5d8f7d4c66 100644 --- a/api/proto/board/board_types.proto +++ b/api/proto/board/board_types.proto @@ -141,7 +141,7 @@ enum PadStackType PST_NORMAL = 1; // The padstack uses up to three different shapes (F_Cu, inner layers, B_Cu) - PST_TOP_INNER_BOTTOM = 2; + PST_FRONT_INNER_BACK = 2; // The padstack's shape may differ on arbitrary copper layers PST_CUSTOM = 3; diff --git a/common/hash_eda.cpp b/common/hash_eda.cpp index 72fb280829..e46931d9a6 100644 --- a/common/hash_eda.cpp +++ b/common/hash_eda.cpp @@ -35,6 +35,8 @@ #include #include +#include + using namespace std; // Common calculation part for all BOARD_ITEMs @@ -96,9 +98,52 @@ size_t hash_fp_item( const EDA_ITEM* aItem, int aFlags ) { const PAD* pad = static_cast( aItem ); - ret = hash{}( static_cast( pad->GetShape() ) ); + // TODO(JE) Implement this + if( pad->Padstack().Mode() != PADSTACK::MODE::NORMAL ) + wxLogDebug( "Hashing pad with non-simple padstack; hash code needs to be expanded" ); - hash_combine( ret, pad->GetAttribute() ); + ret = hash{}( static_cast( pad->GetAttribute() ) ); + + auto hashPadLayer = + [&]( PCB_LAYER_ID aLayer ) + { + hash_combine( ret, pad->GetShape( aLayer ) ); + hash_combine( ret, pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y ); + hash_combine( ret, pad->GetOffset( aLayer ).x, pad->GetOffset( aLayer ).y ); + + switch( pad->GetShape( PADSTACK::ALL_LAYERS ) ) + { + case PAD_SHAPE::CHAMFERED_RECT: + hash_combine( ret, pad->GetChamferPositions( aLayer ) ); + hash_combine( ret, pad->GetChamferRectRatio( aLayer ) ); + break; + + case PAD_SHAPE::ROUNDRECT: + hash_combine( ret, pad->GetRoundRectCornerRadius( aLayer ) ); + break; + + case PAD_SHAPE::TRAPEZOID: + hash_combine( ret, pad->GetDelta( aLayer ).x, pad->GetDelta( aLayer ).y ); + break; + + case PAD_SHAPE::CUSTOM: + { + auto poly = pad->GetEffectivePolygon( aLayer, ERROR_INSIDE ); + + for( int ii = 0; ii < poly->VertexCount(); ++ii ) + { + VECTOR2I point = poly->CVertex( ii ) - pad->GetPosition(); + hash_combine( ret, point.x, point.y ); + } + + break; + } + default: + break; + } + }; + + pad->Padstack().ForEachUniqueLayer( hashPadLayer ); if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH ) { @@ -112,36 +157,6 @@ size_t hash_fp_item( const EDA_ITEM* aItem, int aFlags ) } ); } - hash_combine( ret, pad->GetSize().x, pad->GetSize().y ); - hash_combine( ret, pad->GetOffset().x, pad->GetOffset().y ); - - switch( pad->GetShape() ) - { - case PAD_SHAPE::CHAMFERED_RECT: - hash_combine( ret, pad->GetChamferPositions() ); - hash_combine( ret, pad->GetChamferRectRatio() ); - break; - case PAD_SHAPE::ROUNDRECT: - hash_combine( ret, pad->GetRoundRectCornerRadius() ); - break; - case PAD_SHAPE::TRAPEZOID: - hash_combine( ret, pad->GetDelta().x, pad->GetDelta().y ); - break; - case PAD_SHAPE::CUSTOM: - { - auto poly = pad->GetEffectivePolygon( ERROR_INSIDE ); - - for( int ii = 0; ii < poly->VertexCount(); ++ii ) - { - VECTOR2I point = poly->CVertex( ii ) - pad->GetPosition(); - hash_combine( ret, point.x, point.y ); - } - break; - } - default: - break; - } - hash_combine( ret, hash_board_item( pad, aFlags ) ); if( aFlags & HASH_POS ) diff --git a/common/pcb.keywords b/common/pcb.keywords index 3e4024d7bc..798128be51 100644 --- a/common/pcb.keywords +++ b/common/pcb.keywords @@ -145,6 +145,7 @@ fp_text fp_text_box free front +front_inner_back full general generator @@ -251,6 +252,7 @@ pad_prop_castellated pad_prop_testpoint pad_prop_heatsink pad_prop_mechanical +padstack padvia prefer_zone_connections private_layers @@ -292,6 +294,7 @@ segment segment_width separators setup +shape sheetfile sheetname silk_line_width diff --git a/include/layer_ids.h b/include/layer_ids.h index b28baea6ae..f572ac1bb3 100644 --- a/include/layer_ids.h +++ b/include/layer_ids.h @@ -740,6 +740,19 @@ inline bool IsNetCopperLayer( int aLayer ) return IsCopperLayer( aLayer ) || netCopperLayers.count( aLayer ); } +///! Converts KiCad copper layer enum to an ordinal between the front and back layers +inline size_t CopperLayerToOrdinal( PCB_LAYER_ID aLayer ) +{ + wxCHECK( IsCopperLayer( aLayer ), 0 ); + + switch( aLayer ) + { + case F_Cu: return 0; + case B_Cu: return MAX_CU_LAYERS - 1; + default: return ( aLayer - B_Cu ) / 2; + } +} + KICOMMON_API PCB_LAYER_ID ToLAYER_ID( int aLayer ); diff --git a/include/lset.h b/include/lset.h index 5ded02a3bb..1b01395b89 100644 --- a/include/lset.h +++ b/include/lset.h @@ -57,7 +57,7 @@ public: * @param aLayer is the layer to check * @return true if the layer is included */ - bool Contains( PCB_LAYER_ID aLayer ) + bool Contains( PCB_LAYER_ID aLayer ) const { // At the moment, LSET cannot store negative layers, but PCB_LAYER_ID can contain them if( aLayer < 0 ) diff --git a/pcbnew/api/api_pcb_enums.cpp b/pcbnew/api/api_pcb_enums.cpp index 382953e8d1..0f9530d481 100644 --- a/pcbnew/api/api_pcb_enums.cpp +++ b/pcbnew/api/api_pcb_enums.cpp @@ -108,7 +108,7 @@ types::PadStackType ToProtoEnum( PADSTACK::MODE aValue ) switch( aValue ) { case PADSTACK::MODE::NORMAL: return types::PadStackType::PST_NORMAL; - case PADSTACK::MODE::TOP_INNER_BOTTOM: return types::PadStackType::PST_TOP_INNER_BOTTOM; + case PADSTACK::MODE::FRONT_INNER_BACK: return types::PadStackType::PST_FRONT_INNER_BACK; case PADSTACK::MODE::CUSTOM: return types::PadStackType::PST_CUSTOM; default: @@ -124,7 +124,7 @@ PADSTACK::MODE FromProtoEnum( types::PadStackType aValue ) switch( aValue ) { case types::PadStackType::PST_NORMAL: return PADSTACK::MODE::NORMAL; - case types::PadStackType::PST_TOP_INNER_BOTTOM: return PADSTACK::MODE::TOP_INNER_BOTTOM; + case types::PadStackType::PST_FRONT_INNER_BACK: return PADSTACK::MODE::FRONT_INNER_BACK; case types::PadStackType::PST_CUSTOM: return PADSTACK::MODE::CUSTOM; default: diff --git a/pcbnew/autorouter/ar_matrix.cpp b/pcbnew/autorouter/ar_matrix.cpp index 4c22cd5f50..1eac76659b 100644 --- a/pcbnew/autorouter/ar_matrix.cpp +++ b/pcbnew/autorouter/ar_matrix.cpp @@ -49,7 +49,7 @@ AR_MATRIX::AR_MATRIX() m_GridRouting = 0; m_RouteCount = 0; m_routeLayerBottom = B_Cu; - m_routeLayerTop = F_Cu; + m_routeLayerTop = PADSTACK::ALL_LAYERS; } @@ -901,24 +901,25 @@ void AR_MATRIX::CreateKeepOutRectangle( void AR_MATRIX::PlacePad( PAD* aPad, int color, int marge, AR_MATRIX::CELL_OP op_logic ) { int dx, dy; - VECTOR2I shape_pos = aPad->ShapePos(); + VECTOR2I shape_pos = aPad->ShapePos( PADSTACK::ALL_LAYERS ); - dx = aPad->GetSize().x / 2; + // TODO(JE) padstacks + dx = aPad->GetSize( PADSTACK::ALL_LAYERS ).x / 2; dx += marge; - if( aPad->GetShape() == PAD_SHAPE::CIRCLE ) + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE ) { traceFilledCircle( shape_pos.x, shape_pos.y, dx, aPad->GetLayerSet(), color, op_logic ); return; } - dy = aPad->GetSize().y / 2; + dy = aPad->GetSize( PADSTACK::ALL_LAYERS ).y / 2; dy += marge; - if( aPad->GetShape() == PAD_SHAPE::TRAPEZOID ) + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::TRAPEZOID ) { - dx += abs( aPad->GetDelta().y ) / 2; - dy += abs( aPad->GetDelta().x ) / 2; + dx += abs( aPad->GetDelta( PADSTACK::ALL_LAYERS ).y ) / 2; + dy += abs( aPad->GetDelta( PADSTACK::ALL_LAYERS ).x ) / 2; } // The pad is a rectangle ( horizontal or vertical ) diff --git a/pcbnew/board.cpp b/pcbnew/board.cpp index aaabce3b24..8c2534e897 100644 --- a/pcbnew/board.cpp +++ b/pcbnew/board.cpp @@ -2337,7 +2337,7 @@ std::tuple BOARD::GetTrackLength( const PCB_TRACK& aTrack ) VECTOR2I loc; // We may not collide even if we passed the bounding-box hit test - if( pad->GetEffectivePolygon( ERROR_INSIDE )->Collide( trackSeg, 0, nullptr, &loc ) ) + if( pad->GetEffectivePolygon( track->GetLayer(), ERROR_INSIDE )->Collide( trackSeg, 0, nullptr, &loc ) ) { // Part 1: length of the seg to the intersection with the pad poly if( hitStart ) diff --git a/pcbnew/board_design_settings.cpp b/pcbnew/board_design_settings.cpp index f6df75113d..1cb988f20b 100644 --- a/pcbnew/board_design_settings.cpp +++ b/pcbnew/board_design_settings.cpp @@ -825,8 +825,8 @@ BOARD_DESIGN_SETTINGS::BOARD_DESIGN_SETTINGS( JSON_SETTINGS* aParent, const std: { nlohmann::json ret = { - { "width", pcbIUScale.IUTomm( m_Pad_Master->GetSize().x ) }, - { "height", pcbIUScale.IUTomm( m_Pad_Master->GetSize().y ) }, + { "width", pcbIUScale.IUTomm( m_Pad_Master->GetSize( PADSTACK::ALL_LAYERS ).x ) }, + { "height", pcbIUScale.IUTomm( m_Pad_Master->GetSize( PADSTACK::ALL_LAYERS ).y ) }, { "drill", pcbIUScale.IUTomm( m_Pad_Master->GetDrillSize().x ) } }; @@ -841,7 +841,7 @@ BOARD_DESIGN_SETTINGS::BOARD_DESIGN_SETTINGS( JSON_SETTINGS* aParent, const std: sz.x = pcbIUScale.mmToIU( aJson["width"].get() ); sz.y = pcbIUScale.mmToIU( aJson["height"].get() ); - m_Pad_Master->SetSize( sz ); + m_Pad_Master->SetSize( PADSTACK::ALL_LAYERS, sz ); int drill = pcbIUScale.mmToIU( aJson["drill"].get() ); @@ -1498,7 +1498,8 @@ void BOARD_DESIGN_SETTINGS::SetDefaultMasterPad() m_Pad_Master.get()->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE ); m_Pad_Master.get()->SetDrillSize( VECTOR2I( pcbIUScale.mmToIU( DEFAULT_PAD_DRILL_DIAMETER_MM ), 0 ) ); - m_Pad_Master.get()->SetShape( PAD_SHAPE::ROUNDRECT ); + m_Pad_Master.get()->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::ROUNDRECT ); m_Pad_Master.get()->SetRoundRectCornerRadius( + PADSTACK::ALL_LAYERS, pcbIUScale.mmToIU( DEFAULT_PAD_HEIGTH_MM / 100.0 * DEFAULT_PAD_REACT_RADIUS ) ); } diff --git a/pcbnew/connectivity/connectivity_data.cpp b/pcbnew/connectivity/connectivity_data.cpp index 7b194b3aa7..0c936fed34 100644 --- a/pcbnew/connectivity/connectivity_data.cpp +++ b/pcbnew/connectivity/connectivity_data.cpp @@ -459,8 +459,10 @@ bool CONNECTIVITY_DATA::IsConnectedOnLayer( const BOARD_CONNECTED_ITEM *aItem, i if( zone->IsFilled() ) { - const SHAPE_POLY_SET* zoneFill = zone->GetFill( ToLAYER_ID( aLayer ) ); - const SHAPE_LINE_CHAIN& padHull = pad->GetEffectivePolygon( ERROR_INSIDE )->Outline( 0 ); + PCB_LAYER_ID pcbLayer = ToLAYER_ID( aLayer ); + const SHAPE_POLY_SET* zoneFill = zone->GetFill( pcbLayer ); + const SHAPE_LINE_CHAIN& padHull = + pad->GetEffectivePolygon( pcbLayer, ERROR_INSIDE )->Outline( 0 ); for( const VECTOR2I& pt : zoneFill->COutline( islandIdx ).CPoints() ) { diff --git a/pcbnew/connectivity/connectivity_items.cpp b/pcbnew/connectivity/connectivity_items.cpp index 2d5884ed82..0b228382b1 100644 --- a/pcbnew/connectivity/connectivity_items.cpp +++ b/pcbnew/connectivity/connectivity_items.cpp @@ -150,8 +150,18 @@ CN_ITEM* CN_LIST::Add( PAD* pad ) if( !pad->IsOnCopperLayer() ) return nullptr; - auto item = new CN_ITEM( pad, false, 1 ); - item->AddAnchor( pad->ShapePos() ); + auto item = new CN_ITEM( pad, false, 1 ); + + std::set uniqueAnchors; + pad->Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + uniqueAnchors.insert( pad->ShapePos( aLayer ) ); + } ); + + for( const VECTOR2I& anchor : uniqueAnchors ) + item->AddAnchor( anchor ); + item->SetLayers( F_Cu, B_Cu ); switch( pad->GetAttribute() ) diff --git a/pcbnew/convert_shape_list_to_polygon.cpp b/pcbnew/convert_shape_list_to_polygon.cpp index 3c27565f32..c85901b9ee 100644 --- a/pcbnew/convert_shape_list_to_polygon.cpp +++ b/pcbnew/convert_shape_list_to_polygon.cpp @@ -157,21 +157,27 @@ static bool isCopperOutside( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aShape for( PAD* pad : aFootprint->Pads() ) { - SHAPE_POLY_SET poly = aShape.CloneDropTriangulation(); + pad->Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + SHAPE_POLY_SET poly = aShape.CloneDropTriangulation(); - poly.ClearArcs(); + poly.ClearArcs(); - poly.BooleanIntersection( *pad->GetEffectivePolygon( ERROR_INSIDE ), - SHAPE_POLY_SET::PM_FAST ); + poly.BooleanIntersection( *pad->GetEffectivePolygon( aLayer, ERROR_INSIDE ), + SHAPE_POLY_SET::PM_FAST ); - if( poly.OutlineCount() == 0 ) - { - VECTOR2I padPos = pad->GetPosition(); - wxLogTrace( traceBoardOutline, wxT( "Tested pad (%d, %d): outside" ), - padPos.x, padPos.y ); - padOutside = true; + if( poly.OutlineCount() == 0 ) + { + VECTOR2I padPos = pad->GetPosition(); + wxLogTrace( traceBoardOutline, wxT( "Tested pad (%d, %d): outside" ), + padPos.x, padPos.y ); + padOutside = true; + } + } ); + + if( padOutside ) break; - } VECTOR2I padPos = pad->GetPosition(); wxLogTrace( traceBoardOutline, wxT( "Tested pad (%d, %d): not outside" ), diff --git a/pcbnew/dialogs/dialog_pad_properties.cpp b/pcbnew/dialogs/dialog_pad_properties.cpp index bba160c793..95cddd52e0 100644 --- a/pcbnew/dialogs/dialog_pad_properties.cpp +++ b/pcbnew/dialogs/dialog_pad_properties.cpp @@ -112,7 +112,7 @@ static PAD_ATTRIB code_type[] = */ static bool PadHasMeaningfulRoundingRadius( const PAD& aPad ) { - const PAD_SHAPE shape = aPad.GetShape(); + const PAD_SHAPE shape = aPad.GetShape( PADSTACK::ALL_LAYERS ); return shape == PAD_SHAPE::ROUNDRECT || shape == PAD_SHAPE::CHAMFERED_RECT; } @@ -223,7 +223,7 @@ DIALOG_PAD_PROPERTIES::DIALOG_PAD_PROPERTIES( PCB_BASE_FRAME* aParent, PAD* aPad // Zero it out here to mark that we should recompute a better ratio if the user // selects a pad shape which would need a default rounding ratio computed for it if( !PadHasMeaningfulRoundingRadius( *m_previewPad ) ) - m_previewPad->SetRoundRectRadiusRatio( 0.0 ); + m_previewPad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, 0.0 ); if( m_isFpEditor ) { @@ -409,20 +409,20 @@ void DIALOG_PAD_PROPERTIES::prepareCanvas() void DIALOG_PAD_PROPERTIES::updateRoundRectCornerValues() { // Note: use ChangeValue() to avoid generating a wxEVT_TEXT event - m_cornerRadius.ChangeValue( m_previewPad->GetRoundRectCornerRadius() ); + m_cornerRadius.ChangeValue( m_previewPad->GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS ) ); - m_cornerRatio.ChangeDoubleValue( m_previewPad->GetRoundRectRadiusRatio() * 100.0 ); - m_mixedCornerRatio.ChangeDoubleValue( m_previewPad->GetRoundRectRadiusRatio() * 100.0 ); + m_cornerRatio.ChangeDoubleValue( m_previewPad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ) * 100.0 ); + m_mixedCornerRatio.ChangeDoubleValue( m_previewPad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ) * 100.0 ); - m_chamferRatio.ChangeDoubleValue( m_previewPad->GetChamferRectRatio() * 100.0 ); - m_mixedChamferRatio.ChangeDoubleValue( m_previewPad->GetChamferRectRatio() * 100.0 ); + m_chamferRatio.ChangeDoubleValue( m_previewPad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) * 100.0 ); + m_mixedChamferRatio.ChangeDoubleValue( m_previewPad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) * 100.0 ); } void DIALOG_PAD_PROPERTIES::onCornerRadiusChange( wxCommandEvent& event ) { - if( m_previewPad->GetShape() != PAD_SHAPE::ROUNDRECT - && m_previewPad->GetShape() != PAD_SHAPE::CHAMFERED_RECT ) + if( m_previewPad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::ROUNDRECT + && m_previewPad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::CHAMFERED_RECT ) { return; } @@ -432,10 +432,10 @@ void DIALOG_PAD_PROPERTIES::onCornerRadiusChange( wxCommandEvent& event ) if( transferDataToPad( m_previewPad ) ) { - m_previewPad->SetRoundRectCornerRadius( m_cornerRadius.GetValue() ); + m_previewPad->SetRoundRectCornerRadius( PADSTACK::ALL_LAYERS, m_cornerRadius.GetValue() ); - m_cornerRatio.ChangeDoubleValue( m_previewPad->GetRoundRectRadiusRatio() * 100.0 ); - m_mixedCornerRatio.ChangeDoubleValue( m_previewPad->GetRoundRectRadiusRatio() * 100.0 ); + m_cornerRatio.ChangeDoubleValue( m_previewPad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ) * 100.0 ); + m_mixedCornerRatio.ChangeDoubleValue( m_previewPad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ) * 100.0 ); redraw(); } @@ -447,8 +447,8 @@ void DIALOG_PAD_PROPERTIES::onCornerRadiusChange( wxCommandEvent& event ) void DIALOG_PAD_PROPERTIES::onCornerSizePercentChange( wxCommandEvent& event ) { - if( m_previewPad->GetShape() != PAD_SHAPE::ROUNDRECT - && m_previewPad->GetShape() != PAD_SHAPE::CHAMFERED_RECT ) + if( m_previewPad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::ROUNDRECT + && m_previewPad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::CHAMFERED_RECT ) { return; } @@ -511,7 +511,7 @@ void DIALOG_PAD_PROPERTIES::onCornerSizePercentChange( wxCommandEvent& event ) } if( changed && transferDataToPad( m_previewPad ) ) - m_cornerRadius.ChangeValue( m_previewPad->GetRoundRectCornerRadius() ); + m_cornerRadius.ChangeValue( m_previewPad->GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS ) ); redraw(); @@ -598,7 +598,7 @@ void DIALOG_PAD_PROPERTIES::initValues() } } - m_primitives = m_previewPad->GetPrimitives(); + m_primitives = m_previewPad->GetPrimitives( PADSTACK::ALL_LAYERS ); m_padNetSelector->SetSelectedNetcode( m_previewPad->GetNetCode() ); @@ -609,21 +609,21 @@ void DIALOG_PAD_PROPERTIES::initValues() m_holeX.ChangeValue( m_previewPad->GetDrillSize().x ); m_holeY.ChangeValue( m_previewPad->GetDrillSize().y ); - m_sizeX.ChangeValue( m_previewPad->GetSize().x ); - m_sizeY.ChangeValue( m_previewPad->GetSize().y ); + m_sizeX.ChangeValue( m_previewPad->GetSize( PADSTACK::ALL_LAYERS ).x ); + m_sizeY.ChangeValue( m_previewPad->GetSize( PADSTACK::ALL_LAYERS ).y ); - m_offsetShapeOpt->SetValue( m_previewPad->GetOffset() != VECTOR2I() ); - m_offsetX.ChangeValue( m_previewPad->GetOffset().x ); - m_offsetY.ChangeValue( m_previewPad->GetOffset().y ); + m_offsetShapeOpt->SetValue( m_previewPad->GetOffset( PADSTACK::ALL_LAYERS ) != VECTOR2I() ); + m_offsetX.ChangeValue( m_previewPad->GetOffset( PADSTACK::ALL_LAYERS ).x ); + m_offsetY.ChangeValue( m_previewPad->GetOffset( PADSTACK::ALL_LAYERS ).y ); - if( m_previewPad->GetDelta().x ) + if( m_previewPad->GetDelta( PADSTACK::ALL_LAYERS ).x ) { - m_trapDelta.ChangeValue( m_previewPad->GetDelta().x ); + m_trapDelta.ChangeValue( m_previewPad->GetDelta( PADSTACK::ALL_LAYERS ).x ); m_trapAxisCtrl->SetSelection( 0 ); } else { - m_trapDelta.ChangeValue( m_previewPad->GetDelta().y ); + m_trapDelta.ChangeValue( m_previewPad->GetDelta( PADSTACK::ALL_LAYERS ).y ); m_trapAxisCtrl->SetSelection( 1 ); } @@ -684,7 +684,7 @@ void DIALOG_PAD_PROPERTIES::initValues() else m_ZoneCustomPadShape->SetSelection( 0 ); - switch( m_previewPad->GetShape() ) + switch( m_previewPad->GetShape( PADSTACK::ALL_LAYERS ) ) { default: case PAD_SHAPE::CIRCLE: m_PadShapeSelector->SetSelection( CHOICE_SHAPE_CIRCLE ); break; @@ -694,28 +694,30 @@ void DIALOG_PAD_PROPERTIES::initValues() case PAD_SHAPE::ROUNDRECT: m_PadShapeSelector->SetSelection( CHOICE_SHAPE_ROUNDRECT ); break; case PAD_SHAPE::CHAMFERED_RECT: - if( m_previewPad->GetRoundRectRadiusRatio() > 0.0 ) + if( m_previewPad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ) > 0.0 ) m_PadShapeSelector->SetSelection( CHOICE_SHAPE_CHAMFERED_ROUNDED_RECT ); else m_PadShapeSelector->SetSelection( CHOICE_SHAPE_CHAMFERED_RECT ); break; case PAD_SHAPE::CUSTOM: - if( m_previewPad->GetAnchorPadShape() == PAD_SHAPE::RECTANGLE ) + if( m_previewPad->GetAnchorPadShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::RECTANGLE ) m_PadShapeSelector->SetSelection( CHOICE_SHAPE_CUSTOM_RECT_ANCHOR ); else m_PadShapeSelector->SetSelection( CHOICE_SHAPE_CUSTOM_CIRC_ANCHOR ); break; } - m_cbTopLeft->SetValue( m_previewPad->GetChamferPositions() & RECT_CHAMFER_TOP_LEFT ); - m_cbTopLeft1->SetValue( m_previewPad->GetChamferPositions() & RECT_CHAMFER_TOP_LEFT ); - m_cbTopRight->SetValue( m_previewPad->GetChamferPositions() & RECT_CHAMFER_TOP_RIGHT ); - m_cbTopRight1->SetValue( m_previewPad->GetChamferPositions() & RECT_CHAMFER_TOP_RIGHT ); - m_cbBottomLeft->SetValue( m_previewPad->GetChamferPositions() & RECT_CHAMFER_BOTTOM_LEFT ); - m_cbBottomLeft1->SetValue( m_previewPad->GetChamferPositions() & RECT_CHAMFER_BOTTOM_LEFT ); - m_cbBottomRight->SetValue( m_previewPad->GetChamferPositions() & RECT_CHAMFER_BOTTOM_RIGHT ); - m_cbBottomRight1->SetValue( m_previewPad->GetChamferPositions() & RECT_CHAMFER_BOTTOM_RIGHT ); + int chamferPositions = m_previewPad->GetChamferPositions( PADSTACK::ALL_LAYERS ); + + m_cbTopLeft->SetValue( chamferPositions & RECT_CHAMFER_TOP_LEFT ); + m_cbTopLeft1->SetValue( chamferPositions & RECT_CHAMFER_TOP_LEFT ); + m_cbTopRight->SetValue( chamferPositions & RECT_CHAMFER_TOP_RIGHT ); + m_cbTopRight1->SetValue( chamferPositions & RECT_CHAMFER_TOP_RIGHT ); + m_cbBottomLeft->SetValue( chamferPositions & RECT_CHAMFER_BOTTOM_LEFT ); + m_cbBottomLeft1->SetValue( chamferPositions & RECT_CHAMFER_BOTTOM_LEFT ); + m_cbBottomRight->SetValue( chamferPositions & RECT_CHAMFER_BOTTOM_RIGHT ); + m_cbBottomRight1->SetValue( chamferPositions & RECT_CHAMFER_BOTTOM_RIGHT ); updateRoundRectCornerValues(); @@ -820,7 +822,7 @@ void DIALOG_PAD_PROPERTIES::OnPadShapeSelection( wxCommandEvent& event ) m_shapePropsBook->SetSelection( 2 ); // Reasonable defaults - if( m_previewPad->GetRoundRectRadiusRatio() == 0.0 ) + if( m_previewPad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ) == 0.0 ) m_cornerRatio.ChangeDoubleValue( GetDefaultIpcRoundingRatio( *m_previewPad ) * 100 ); break; @@ -830,11 +832,11 @@ void DIALOG_PAD_PROPERTIES::OnPadShapeSelection( wxCommandEvent& event ) m_shapePropsBook->SetSelection( 3 ); // Reasonable default - if( m_previewPad->GetChamferRectRatio() == 0.0 ) - m_previewPad->SetChamferRectRatio( 0.2 ); + if( m_previewPad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) == 0.0 ) + m_previewPad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, 0.2 ); // Ensure the displayed value is up to date: - m_chamferRatio.ChangeDoubleValue( m_previewPad->GetChamferRectRatio() * 100.0 ); + m_chamferRatio.ChangeDoubleValue( m_previewPad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) * 100.0 ); // A reasonable default is one corner chamfered (usual for some SMD pads). if( !m_cbTopLeft->GetValue() && !m_cbTopRight->GetValue() @@ -852,17 +854,17 @@ void DIALOG_PAD_PROPERTIES::OnPadShapeSelection( wxCommandEvent& event ) m_shapePropsBook->SetSelection( 4 ); // Reasonable defaults - if( m_previewPad->GetRoundRectRadiusRatio() == 0.0 - && m_previewPad->GetChamferRectRatio() == 0.0 ) + if( m_previewPad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ) == 0.0 + && m_previewPad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) == 0.0 ) { - m_previewPad->SetRoundRectRadiusRatio( - GetDefaultIpcRoundingRatio( *m_previewPad ) ); - m_previewPad->SetChamferRectRatio( 0.2 ); + m_previewPad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, + GetDefaultIpcRoundingRatio( *m_previewPad ) ); + m_previewPad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, 0.2 ); } // Ensure the displayed values are up to date: - m_mixedChamferRatio.ChangeDoubleValue( m_previewPad->GetChamferRectRatio() * 100.0 ); - m_mixedCornerRatio.ChangeDoubleValue( m_previewPad->GetRoundRectRadiusRatio() * 100.0 ); + m_mixedChamferRatio.ChangeDoubleValue( m_previewPad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) * 100.0 ); + m_mixedCornerRatio.ChangeDoubleValue( m_previewPad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ) * 100.0 ); break; case CHOICE_SHAPE_CUSTOM_CIRC_ANCHOR: // PAD_SHAPE::CUSTOM, circular anchor @@ -949,7 +951,7 @@ void DIALOG_PAD_PROPERTIES::UpdateLayersDropdown() case PTH_DLG_TYPE: m_rbCopperLayersSel->Append( _( "All copper layers" ) ); m_rbCopperLayersSel->Append( wxString::Format( _( "%s, %s and connected layers" ), - m_board->GetLayerName( F_Cu ), + m_board->GetLayerName( PADSTACK::ALL_LAYERS ), m_board->GetLayerName( B_Cu ) ) ); m_rbCopperLayersSel->Append( _( "Connected layers only" ) ); m_rbCopperLayersSel->Append( _( "None" ) ); @@ -957,16 +959,16 @@ void DIALOG_PAD_PROPERTIES::UpdateLayersDropdown() case NPTH_DLG_TYPE: m_rbCopperLayersSel->Append( wxString::Format( _( "%s and %s" ), - m_board->GetLayerName( F_Cu ), + m_board->GetLayerName( PADSTACK::ALL_LAYERS ), m_board->GetLayerName( B_Cu ) ) ); - m_rbCopperLayersSel->Append( m_board->GetLayerName( F_Cu ) ); + m_rbCopperLayersSel->Append( m_board->GetLayerName( PADSTACK::ALL_LAYERS ) ); m_rbCopperLayersSel->Append( m_board->GetLayerName( B_Cu ) ); m_rbCopperLayersSel->Append( _( "None" ) ); break; case SMD_DLG_TYPE: case CONN_DLG_TYPE: - m_rbCopperLayersSel->Append( m_board->GetLayerName( F_Cu ) ); + m_rbCopperLayersSel->Append( m_board->GetLayerName( PADSTACK::ALL_LAYERS ) ); m_rbCopperLayersSel->Append( m_board->GetLayerName( B_Cu ) ); break; @@ -1106,9 +1108,9 @@ void DIALOG_PAD_PROPERTIES::OnUpdateUI( wxUpdateUIEvent& event ) break; case NPTH_DLG_TYPE: - if( cu_set.test( F_Cu ) && cu_set.test( B_Cu ) ) + if( cu_set.test( PADSTACK::ALL_LAYERS ) && cu_set.test( B_Cu ) ) m_stackupImagesBook->SetSelection( 4 ); - else if( cu_set.test( F_Cu ) ) + else if( cu_set.test( PADSTACK::ALL_LAYERS ) ) m_stackupImagesBook->SetSelection( 5 ); else if( cu_set.test( B_Cu ) ) m_stackupImagesBook->SetSelection( 6 ); @@ -1173,7 +1175,7 @@ void DIALOG_PAD_PROPERTIES::updatePadLayersList( LSET layer_mask, bool remove_un if( !layer_mask.any() ) layer_mask = PAD::SMDMask(); - if( layer_mask.test( F_Cu ) ) + if( layer_mask.test( PADSTACK::ALL_LAYERS ) ) m_rbCopperLayersSel->SetSelection( 0 ); else m_rbCopperLayersSel->SetSelection( 1 ); @@ -1184,7 +1186,7 @@ void DIALOG_PAD_PROPERTIES::updatePadLayersList( LSET layer_mask, bool remove_un if( !layer_mask.any() ) layer_mask = PAD::ConnSMDMask(); - if( layer_mask.test( F_Cu ) ) + if( layer_mask.test( PADSTACK::ALL_LAYERS ) ) m_rbCopperLayersSel->SetSelection( 0 ); else m_rbCopperLayersSel->SetSelection( 1 ); @@ -1195,9 +1197,9 @@ void DIALOG_PAD_PROPERTIES::updatePadLayersList( LSET layer_mask, bool remove_un if( !layer_mask.any() ) layer_mask = PAD::UnplatedHoleMask(); - if( layer_mask.test( F_Cu ) && layer_mask.test( B_Cu ) ) + if( layer_mask.test( PADSTACK::ALL_LAYERS ) && layer_mask.test( B_Cu ) ) m_rbCopperLayersSel->SetSelection( 0 ); - else if( layer_mask.test( F_Cu ) ) + else if( layer_mask.test( PADSTACK::ALL_LAYERS ) ) m_rbCopperLayersSel->SetSelection( 1 ); else if( layer_mask.test( B_Cu ) ) m_rbCopperLayersSel->SetSelection( 2 ); @@ -1423,28 +1425,31 @@ bool DIALOG_PAD_PROPERTIES::TransferDataFromWindow() commit.Modify( m_currentPad ); // Update values - m_currentPad->SetShape( m_masterPad->GetShape() ); + // TODO(JE) padstacks - a lot of the below can be removed; SetPadstack should handle it + m_currentPad->SetShape( PADSTACK::ALL_LAYERS, m_masterPad->GetShape( PADSTACK::ALL_LAYERS ) ); m_currentPad->SetAttribute( m_masterPad->GetAttribute() ); m_currentPad->SetFPRelativeOrientation( m_masterPad->GetOrientation() ); - m_currentPad->SetSize( m_masterPad->GetSize() ); + m_currentPad->SetSize( PADSTACK::ALL_LAYERS, m_masterPad->GetSize( PADSTACK::ALL_LAYERS ) ); - VECTOR2I size = m_masterPad->GetDelta(); - m_currentPad->SetDelta( size ); + VECTOR2I size = m_masterPad->GetDelta( PADSTACK::ALL_LAYERS ); + m_currentPad->SetDelta( PADSTACK::ALL_LAYERS, size ); m_currentPad->SetDrillSize( m_masterPad->GetDrillSize() ); m_currentPad->SetDrillShape( m_masterPad->GetDrillShape() ); - VECTOR2I offset = m_masterPad->GetOffset(); - m_currentPad->SetOffset( offset ); + VECTOR2I offset = m_masterPad->GetOffset( PADSTACK::ALL_LAYERS ); + m_currentPad->SetOffset( PADSTACK::ALL_LAYERS, offset ); m_currentPad->SetPadToDieLength( m_masterPad->GetPadToDieLength() ); - if( m_masterPad->GetShape() != PAD_SHAPE::CUSTOM ) + if( m_masterPad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::CUSTOM ) m_masterPad->DeletePrimitivesList(); - m_currentPad->SetAnchorPadShape( m_masterPad->GetAnchorPadShape() ); - m_currentPad->ReplacePrimitives( m_masterPad->GetPrimitives() ); + m_currentPad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, + m_masterPad->GetAnchorPadShape( PADSTACK::ALL_LAYERS ) ); + m_currentPad->ReplacePrimitives( PADSTACK::ALL_LAYERS, + m_masterPad->GetPrimitives( PADSTACK::ALL_LAYERS ) ); m_currentPad->SetPadstack( m_masterPad->Padstack() ); m_currentPad->SetLayerSet( m_masterPad->GetLayerSet() ); @@ -1465,19 +1470,22 @@ bool DIALOG_PAD_PROPERTIES::TransferDataFromWindow() m_currentPad->SetThermalSpokeWidth( m_masterPad->GetThermalSpokeWidth() ); m_currentPad->SetThermalSpokeAngle( m_masterPad->GetThermalSpokeAngle() ); m_currentPad->SetThermalGap( m_masterPad->GetThermalGap() ); - m_currentPad->SetRoundRectRadiusRatio( m_masterPad->GetRoundRectRadiusRatio() ); - m_currentPad->SetChamferRectRatio( m_masterPad->GetChamferRectRatio() ); - m_currentPad->SetChamferPositions( m_masterPad->GetChamferPositions() ); + m_currentPad->SetRoundRectRadiusRatio( + PADSTACK::ALL_LAYERS, m_masterPad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ) ); + m_currentPad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, + m_masterPad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) ); + m_currentPad->SetChamferPositions( PADSTACK::ALL_LAYERS, + m_masterPad->GetChamferPositions( PADSTACK::ALL_LAYERS ) ); m_currentPad->SetLocalZoneConnection( m_masterPad->GetLocalZoneConnection() ); m_currentPad->GetTeardropParams() = m_masterPad->GetTeardropParams(); // rounded rect pads with radius ratio = 0 are in fact rect pads. // So set the right shape (and perhaps issues with a radius = 0) - if( m_currentPad->GetShape() == PAD_SHAPE::ROUNDRECT && - m_currentPad->GetRoundRectRadiusRatio() == 0.0 ) + if( m_currentPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::ROUNDRECT && + m_currentPad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ) == 0.0 ) { - m_currentPad->SetShape( PAD_SHAPE::RECTANGLE ); + m_currentPad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); } // Set the fabrication property: @@ -1586,15 +1594,15 @@ bool DIALOG_PAD_PROPERTIES::transferDataToPad( PAD* aPad ) return false; aPad->SetAttribute( code_type[m_padType->GetSelection()] ); - aPad->SetShape( code_shape[m_PadShapeSelector->GetSelection()] ); + aPad->SetShape( PADSTACK::ALL_LAYERS, code_shape[m_PadShapeSelector->GetSelection()] ); if( m_PadShapeSelector->GetSelection() == CHOICE_SHAPE_CUSTOM_RECT_ANCHOR ) - aPad->SetAnchorPadShape( PAD_SHAPE::RECTANGLE ); + aPad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); else - aPad->SetAnchorPadShape( PAD_SHAPE::CIRCLE ); + aPad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); - if( aPad->GetShape() == PAD_SHAPE::CUSTOM ) - aPad->ReplacePrimitives( m_primitives ); + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM ) + aPad->ReplacePrimitives( PADSTACK::ALL_LAYERS, m_primitives ); aPad->GetTeardropParams().m_Enabled = m_cbTeardrops->GetValue(); aPad->GetTeardropParams().m_AllowUseTwoTracks = m_cbTeardropsUseNextTrack->GetValue(); @@ -1669,17 +1677,19 @@ bool DIALOG_PAD_PROPERTIES::transferDataToPad( PAD* aPad ) aPad->SetDrillSize( VECTOR2I( m_holeX.GetIntValue(), m_holeY.GetIntValue() ) ); } - if( aPad->GetShape() == PAD_SHAPE::CIRCLE ) - aPad->SetSize( VECTOR2I( m_sizeX.GetIntValue(), m_sizeX.GetIntValue() ) ); + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE ) + aPad->SetSize( PADSTACK::ALL_LAYERS, + VECTOR2I( m_sizeX.GetIntValue(), m_sizeX.GetIntValue() ) ); else - aPad->SetSize( VECTOR2I( m_sizeX.GetIntValue(), m_sizeY.GetIntValue() ) ); + aPad->SetSize( PADSTACK::ALL_LAYERS, + VECTOR2I( m_sizeX.GetIntValue(), m_sizeY.GetIntValue() ) ); // For a trapezoid, test delta value (be sure delta is not too large for pad size) // remember DeltaSize.x is the Y size variation bool error = false; VECTOR2I delta( 0, 0 ); - if( aPad->GetShape() == PAD_SHAPE::TRAPEZOID ) + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::TRAPEZOID ) { // For a trapezoid, only one of delta.x or delta.y is not 0, depending on axis. if( m_trapAxisCtrl->GetSelection() == 0 ) @@ -1687,37 +1697,38 @@ bool DIALOG_PAD_PROPERTIES::transferDataToPad( PAD* aPad ) else delta.y = m_trapDelta.GetIntValue(); - if( delta.x < 0 && delta.x < -aPad->GetSize().y ) + if( delta.x < 0 && delta.x < -aPad->GetSize( PADSTACK::ALL_LAYERS ).y ) { - delta.x = -aPad->GetSize().y + 2; + delta.x = -aPad->GetSize( PADSTACK::ALL_LAYERS ).y + 2; error = true; } - if( delta.x > 0 && delta.x > aPad->GetSize().y ) + if( delta.x > 0 && delta.x > aPad->GetSize( PADSTACK::ALL_LAYERS ).y ) { - delta.x = aPad->GetSize().y - 2; + delta.x = aPad->GetSize( PADSTACK::ALL_LAYERS ).y - 2; error = true; } - if( delta.y < 0 && delta.y < -aPad->GetSize().x ) + if( delta.y < 0 && delta.y < -aPad->GetSize( PADSTACK::ALL_LAYERS ).x ) { - delta.y = -aPad->GetSize().x + 2; + delta.y = -aPad->GetSize( PADSTACK::ALL_LAYERS ).x + 2; error = true; } - if( delta.y > 0 && delta.y > aPad->GetSize().x ) + if( delta.y > 0 && delta.y > aPad->GetSize( PADSTACK::ALL_LAYERS ).x ) { - delta.y = aPad->GetSize().x - 2; + delta.y = aPad->GetSize( PADSTACK::ALL_LAYERS ).x - 2; error = true; } } - aPad->SetDelta( delta ); + aPad->SetDelta( PADSTACK::ALL_LAYERS, delta ); if( m_offsetShapeOpt->GetValue() ) - aPad->SetOffset( VECTOR2I( m_offsetX.GetIntValue(), m_offsetY.GetIntValue() ) ); + aPad->SetOffset( PADSTACK::ALL_LAYERS, + VECTOR2I( m_offsetX.GetIntValue(), m_offsetY.GetIntValue() ) ); else - aPad->SetOffset( VECTOR2I() ); + aPad->SetOffset( PADSTACK::ALL_LAYERS, VECTOR2I() ); // Read pad length die if( m_padToDieOpt->GetValue() ) @@ -1758,16 +1769,17 @@ bool DIALOG_PAD_PROPERTIES::transferDataToPad( PAD* aPad ) if( m_cbBottomRight1->GetValue() ) chamfers |= RECT_CHAMFER_BOTTOM_RIGHT; } - aPad->SetChamferPositions( chamfers ); + aPad->SetChamferPositions( PADSTACK::ALL_LAYERS, chamfers ); - if( aPad->GetShape() == PAD_SHAPE::CUSTOM ) + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM ) { // The pad custom has a "anchor pad" (a basic shape: round or rect pad) // that is the minimal area of this pad, and is useful to ensure a hole // diameter is acceptable, and is used in Gerber files as flashed area // reference - if( aPad->GetAnchorPadShape() == PAD_SHAPE::CIRCLE ) - aPad->SetSize( VECTOR2I( m_sizeX.GetIntValue(), m_sizeX.GetIntValue() ) ); + if( aPad->GetAnchorPadShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE ) + aPad->SetSize( PADSTACK::ALL_LAYERS, + VECTOR2I( m_sizeX.GetIntValue(), m_sizeX.GetIntValue() ) ); } // Define the way the clearance area is defined in zones. Since all non-custom pad @@ -1805,21 +1817,25 @@ bool DIALOG_PAD_PROPERTIES::transferDataToPad( PAD* aPad ) break; } - if( aPad->GetShape() == PAD_SHAPE::ROUNDRECT ) + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::ROUNDRECT ) { - aPad->SetRoundRectRadiusRatio( m_cornerRatio.GetDoubleValue() / 100.0 ); + aPad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, + m_cornerRatio.GetDoubleValue() / 100.0 ); } - else if( aPad->GetShape() == PAD_SHAPE::CHAMFERED_RECT ) + else if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CHAMFERED_RECT ) { if( m_PadShapeSelector->GetSelection() == CHOICE_SHAPE_CHAMFERED_ROUNDED_RECT ) { - aPad->SetChamferRectRatio( m_mixedChamferRatio.GetDoubleValue() / 100.0 ); - aPad->SetRoundRectRadiusRatio( m_mixedCornerRatio.GetDoubleValue() / 100.0 ); + aPad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, + m_mixedChamferRatio.GetDoubleValue() / 100.0 ); + aPad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, + m_mixedCornerRatio.GetDoubleValue() / 100.0 ); } else // Choice is CHOICE_SHAPE_CHAMFERED_RECT, no rounded corner { - aPad->SetChamferRectRatio( m_chamferRatio.GetDoubleValue() / 100.0 ); - aPad->SetRoundRectRadiusRatio( 0 ); + aPad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, + m_chamferRatio.GetDoubleValue() / 100.0 ); + aPad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, 0 ); } } @@ -1863,8 +1879,8 @@ bool DIALOG_PAD_PROPERTIES::transferDataToPad( PAD* aPad ) case NPTH_DLG_TYPE: switch( copperLayersChoice ) { - case 0: padLayerMask.set( F_Cu ).set( B_Cu ); break; - case 1: padLayerMask.set( F_Cu ); break; + case 0: padLayerMask.set( PADSTACK::ALL_LAYERS ).set( B_Cu ); break; + case 1: padLayerMask.set( PADSTACK::ALL_LAYERS ); break; case 2: padLayerMask.set( B_Cu ); break; default: break; } @@ -1875,7 +1891,7 @@ bool DIALOG_PAD_PROPERTIES::transferDataToPad( PAD* aPad ) case CONN_DLG_TYPE: switch( copperLayersChoice ) { - case 0: padLayerMask.set( F_Cu ); break; + case 0: padLayerMask.set( PADSTACK::ALL_LAYERS ); break; case 1: padLayerMask.set( B_Cu ); break; } @@ -1929,8 +1945,8 @@ void DIALOG_PAD_PROPERTIES::OnOffsetCheckbox( wxCommandEvent& event ) { if( m_offsetShapeOpt->GetValue() ) { - m_offsetX.SetValue( m_previewPad->GetOffset().x ); - m_offsetY.SetValue( m_previewPad->GetOffset().y ); + m_offsetX.SetValue( m_previewPad->GetOffset( PADSTACK::ALL_LAYERS ).x ); + m_offsetY.SetValue( m_previewPad->GetOffset( PADSTACK::ALL_LAYERS ).y ); } // Show/hide controls depending on m_offsetShapeOpt being enabled diff --git a/pcbnew/drc/drc_test_provider_annular_width.cpp b/pcbnew/drc/drc_test_provider_annular_width.cpp index 59226dfe61..d4a2cd9909 100644 --- a/pcbnew/drc/drc_test_provider_annular_width.cpp +++ b/pcbnew/drc/drc_test_provider_annular_width.cpp @@ -202,17 +202,18 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() case PCB_PAD_T: { + // TODO(JE) padstacks PAD* pad = static_cast( item ); if( !pad->HasHole() || pad->GetAttribute() != PAD_ATTRIB::PTH ) return 0; - if( pad->GetOffset() == VECTOR2I( 0, 0 ) ) + if( pad->GetOffset( PADSTACK::ALL_LAYERS ) == VECTOR2I( 0, 0 ) ) { - switch( pad->GetShape() ) + switch( pad->GetShape( PADSTACK::ALL_LAYERS ) ) { case PAD_SHAPE::CHAMFERED_RECT: - if( pad->GetChamferRectRatio() > 0.30 ) + if( pad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) > 0.30 ) break; KI_FALLTHROUGH; @@ -277,9 +278,9 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() if( fp ) sameNumPads = fp->GetPads( pad->GetNumber(), pad ); - if( pad->GetOffset() == VECTOR2I( 0, 0 ) ) + if( pad->GetOffset( PADSTACK::ALL_LAYERS ) == VECTOR2I( 0, 0 ) ) { - switch( pad->GetShape() ) + switch( pad->GetShape( PADSTACK::ALL_LAYERS ) ) { case PAD_SHAPE::CIRCLE: annularWidth = ( pad->GetSizeX() - pad->GetDrillSizeX() ) / 2; @@ -292,7 +293,7 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() break; case PAD_SHAPE::CHAMFERED_RECT: - if( pad->GetChamferRectRatio() > 0.30 ) + if( pad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) > 0.30 ) break; KI_FALLTHROUGH; @@ -322,7 +323,8 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() SHAPE_POLY_SET padOutline; std::shared_ptr slot = pad->GetEffectiveHoleShape(); - pad->TransformShapeToPolygon( padOutline, UNDEFINED_LAYER, 0, maxError, + // TODO(JE) padstacks + pad->TransformShapeToPolygon( padOutline, PADSTACK::ALL_LAYERS, 0, maxError, ERROR_INSIDE ); if( sameNumPads.empty() ) @@ -353,7 +355,7 @@ bool DRC_TEST_PROVIDER_ANNULAR_WIDTH::Run() { // Construct the full pad with outline and hole. sameNumPad->TransformShapeToPolygon( otherPadOutline, - UNDEFINED_LAYER, 0, maxError, + PADSTACK::ALL_LAYERS, 0, maxError, ERROR_OUTSIDE ); sameNumPad->TransformHoleToPolygon( otherPadOutline, 0, maxError, diff --git a/pcbnew/drc/drc_test_provider_copper_clearance.cpp b/pcbnew/drc/drc_test_provider_copper_clearance.cpp index e98b62c66d..519cd68ed9 100644 --- a/pcbnew/drc/drc_test_provider_copper_clearance.cpp +++ b/pcbnew/drc/drc_test_provider_copper_clearance.cpp @@ -406,7 +406,7 @@ void DRC_TEST_PROVIDER_COPPER_CLEARANCE::testItemAgainstZone( BOARD_ITEM* aItem, for( PAD* pad : allowedNetTiePads ) { if( pad->GetBoundingBox().Intersects( itemBBox ) - && pad->GetEffectiveShape()->Collide( itemShape.get() ) ) + && pad->GetEffectiveShape( aLayer )->Collide( itemShape.get() ) ) { return; } diff --git a/pcbnew/drc/drc_test_provider_library_parity.cpp b/pcbnew/drc/drc_test_provider_library_parity.cpp index 2da97a97ca..1567c47fd6 100644 --- a/pcbnew/drc/drc_test_provider_library_parity.cpp +++ b/pcbnew/drc/drc_test_provider_library_parity.cpp @@ -298,7 +298,8 @@ bool padNeedsUpdate( const PAD* a, const PAD* b, REPORTER* aReporter ) return true; } - TEST( a->GetShape(), b->GetShape(), + // TOOD(JE) padstacks + TEST( a->GetShape( PADSTACK::ALL_LAYERS ), b->GetShape( PADSTACK::ALL_LAYERS ), wxString::Format( _( "%s pad shape type differs." ), PAD_DESC( a ) ) ); TEST( a->GetAttribute(), b->GetAttribute(), @@ -311,21 +312,24 @@ bool padNeedsUpdate( const PAD* a, const PAD* b, REPORTER* aReporter ) b->GetFPRelativeOrientation().Normalize().AsDegrees(), wxString::Format( _( "%s orientation differs." ), PAD_DESC( a ) ) ); - TEST( a->GetSize(), b->GetSize(), + TEST( a->GetSize( PADSTACK::ALL_LAYERS ), b->GetSize( PADSTACK::ALL_LAYERS ), wxString::Format( _( "%s size differs." ), PAD_DESC( a ) ) ); - TEST( a->GetDelta(), b->GetDelta(), + TEST( a->GetDelta( PADSTACK::ALL_LAYERS ), b->GetDelta( PADSTACK::ALL_LAYERS ), wxString::Format( _( "%s trapezoid delta differs." ), PAD_DESC( a ) ) ); - TEST_D( a->GetRoundRectRadiusRatio(), b->GetRoundRectRadiusRatio(), + TEST_D( a->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ), + b->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ), wxString::Format( _( "%s rounded corners differ." ), PAD_DESC( a ) ) ); - TEST_D( a->GetChamferRectRatio(), b->GetChamferRectRatio(), + TEST_D( a->GetChamferRectRatio( PADSTACK::ALL_LAYERS ), + b->GetChamferRectRatio( PADSTACK::ALL_LAYERS ), wxString::Format( _( "%s chamfered corner sizes differ." ), PAD_DESC( a ) ) ); - TEST( a->GetChamferPositions(), b->GetChamferPositions(), + TEST( a->GetChamferPositions( PADSTACK::ALL_LAYERS ), + b->GetChamferPositions( PADSTACK::ALL_LAYERS ), wxString::Format( _( "%s chamfered corners differ." ), PAD_DESC( a ) ) ); - TEST_PT( a->GetOffset(), b->GetOffset(), + TEST_PT( a->GetOffset( PADSTACK::ALL_LAYERS ), b->GetOffset( PADSTACK::ALL_LAYERS ), wxString::Format( _( "%s shape offset from hole differs." ), PAD_DESC( a ) ) ); TEST( a->GetDrillShape(), b->GetDrillShape(), @@ -348,15 +352,17 @@ bool padNeedsUpdate( const PAD* a, const PAD* b, REPORTER* aReporter ) bool primitivesDiffer = false; - if( a->GetPrimitives().size() != b->GetPrimitives().size() ) + if( a->GetPrimitives( PADSTACK::ALL_LAYERS ).size() != + b->GetPrimitives( PADSTACK::ALL_LAYERS ).size() ) { primitivesDiffer = true; } else { - for( size_t ii = 0; ii < a->GetPrimitives().size(); ++ii ) + for( size_t ii = 0; ii < a->GetPrimitives( PADSTACK::ALL_LAYERS ).size(); ++ii ) { - if( primitiveNeedsUpdate( a->GetPrimitives()[ii], b->GetPrimitives()[ii] ) ) + if( primitiveNeedsUpdate( a->GetPrimitives( PADSTACK::ALL_LAYERS )[ii], + b->GetPrimitives( PADSTACK::ALL_LAYERS )[ii] ) ) { primitivesDiffer = true; break; diff --git a/pcbnew/drc/drc_test_provider_solder_mask.cpp b/pcbnew/drc/drc_test_provider_solder_mask.cpp index 19f72fbe5f..0d9618458b 100644 --- a/pcbnew/drc/drc_test_provider_solder_mask.cpp +++ b/pcbnew/drc/drc_test_provider_solder_mask.cpp @@ -134,7 +134,7 @@ void DRC_TEST_PROVIDER_SOLDER_MASK::addItemToRTrees( BOARD_ITEM* aItem ) if( aItem->IsOnLayer( layer ) ) { PAD* pad = static_cast( aItem ); - int clearance = ( m_webWidth / 2 ) + pad->GetSolderMaskExpansion(); + int clearance = ( m_webWidth / 2 ) + pad->GetSolderMaskExpansion( layer ); aItem->TransformShapeToPolygon( *solderMask->GetFill( layer ), layer, clearance, m_maxError, ERROR_OUTSIDE ); @@ -321,10 +321,12 @@ bool isNullAperture( BOARD_ITEM* aItem ) { PAD* pad = static_cast( aItem ); + // TODO(JE) padstacks if( pad->GetAttribute() == PAD_ATTRIB::NPTH - && ( pad->GetShape() == PAD_SHAPE::CIRCLE || pad->GetShape() == PAD_SHAPE::OVAL ) - && pad->GetSize().x <= pad->GetDrillSize().x - && pad->GetSize().y <= pad->GetDrillSize().y ) + && ( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE + || pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::OVAL ) + && pad->GetSize( PADSTACK::ALL_LAYERS ).x <= pad->GetDrillSize().x + && pad->GetSize( PADSTACK::ALL_LAYERS ).y <= pad->GetDrillSize().y ) { return true; } @@ -358,7 +360,7 @@ bool DRC_TEST_PROVIDER_SOLDER_MASK::checkMaskAperture( BOARD_ITEM* aMaskItem, BO PCB_LAYER_ID aTestLayer, int aTestNet, BOARD_ITEM** aCollidingItem ) { - if( aTestLayer == F_Mask && !aTestItem->IsOnLayer( F_Cu ) ) + if( aTestLayer == F_Mask && !aTestItem->IsOnLayer( PADSTACK::ALL_LAYERS ) ) return false; if( aTestLayer == B_Mask && !aTestItem->IsOnLayer( B_Cu ) ) @@ -557,12 +559,12 @@ void DRC_TEST_PROVIDER_SOLDER_MASK::testItemAgainstItems( BOARD_ITEM* aItem, con } if( pad ) - clearance += pad->GetSolderMaskExpansion(); + clearance += pad->GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ); else if( via && !via->IsTented( aRefLayer ) ) clearance += via->GetSolderMaskExpansion(); if( otherPad ) - clearance += otherPad->GetSolderMaskExpansion(); + clearance += otherPad->GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ); else if( otherVia && !otherVia->IsTented( aRefLayer ) ) clearance += otherVia->GetSolderMaskExpansion(); @@ -644,7 +646,7 @@ void DRC_TEST_PROVIDER_SOLDER_MASK::testMaskItemAgainstZones( BOARD_ITEM* aItem, int clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance; if( aItem->Type() == PCB_PAD_T ) - clearance += static_cast( aItem )->GetSolderMaskExpansion(); + clearance += static_cast( aItem )->GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ); else if( aItem->Type() == PCB_VIA_T ) clearance += static_cast( aItem )->GetSolderMaskExpansion(); @@ -735,7 +737,7 @@ void DRC_TEST_PROVIDER_SOLDER_MASK::testMaskBridges() // Test for aperture-to-zone collisions testMaskItemAgainstZones( item, itemBBox, F_Mask, F_Cu ); } - else if( item->IsOnLayer( F_Cu ) ) + else if( item->IsOnLayer( PADSTACK::ALL_LAYERS ) ) { // Test for copper-item-to-aperture collisions testItemAgainstItems( item, itemBBox, F_Cu, F_Mask ); @@ -777,7 +779,7 @@ bool DRC_TEST_PROVIDER_SOLDER_MASK::Run() for( FOOTPRINT* footprint : m_board->Footprints() ) { for( PAD* pad : footprint->Pads() ) - m_largestClearance = std::max( m_largestClearance, pad->GetSolderMaskExpansion() ); + m_largestClearance = std::max( m_largestClearance, pad->GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ) ); } // Order is important here: m_webWidth must be added in before m_largestCourtyardClearance is diff --git a/pcbnew/exporters/export_d356.cpp b/pcbnew/exporters/export_d356.cpp index da0e916f07..0beb9bcc29 100644 --- a/pcbnew/exporters/export_d356.cpp +++ b/pcbnew/exporters/export_d356.cpp @@ -119,13 +119,14 @@ static void build_pad_testpoints( BOARD *aPcb, std::vector & aRecor rk.mechanical = ( pad->GetAttribute() == PAD_ATTRIB::NPTH ); rk.x_location = pad->GetPosition().x - origin.x; rk.y_location = origin.y - pad->GetPosition().y; - rk.x_size = pad->GetSize().x; + // TODO(JE) padstacks + rk.x_size = pad->GetSize( PADSTACK::ALL_LAYERS ).x; // Rule: round pads have y = 0 - if( pad->GetShape() == PAD_SHAPE::CIRCLE ) + if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE ) rk.y_size = 0; else - rk.y_size = pad->GetSize().y; + rk.y_size = pad->GetSize( PADSTACK::ALL_LAYERS ).y; rk.rotation = - pad->GetOrientation().AsDegrees(); diff --git a/pcbnew/exporters/export_gencad_writer.cpp b/pcbnew/exporters/export_gencad_writer.cpp index 08abdedd6f..ca4c6a5b40 100644 --- a/pcbnew/exporters/export_gencad_writer.cpp +++ b/pcbnew/exporters/export_gencad_writer.cpp @@ -314,7 +314,7 @@ void GENCAD_EXPORTER::CreatePadsShapesSection() for( unsigned i = 0; iGetOffset(); + const VECTOR2I& off = pad->GetOffset( PADSTACK::ALL_LAYERS ); pad->SetSubRatsnest( pad_name_number ); @@ -331,13 +331,13 @@ void GENCAD_EXPORTER::CreatePadsShapesSection() fprintf( m_file, "PAD P%d", pad->GetSubRatsnest() ); padstacks.push_back( pad ); // Will have its own padstack later - int dx = pad->GetSize().x / 2; - int dy = pad->GetSize().y / 2; + int dx = pad->GetSize( PADSTACK::ALL_LAYERS ).x / 2; + int dy = pad->GetSize( PADSTACK::ALL_LAYERS ).y / 2; - switch( pad->GetShape() ) + switch( pad->GetShape( PADSTACK::ALL_LAYERS ) ) { default: - UNIMPLEMENTED_FOR( pad->ShowPadShape() ); + UNIMPLEMENTED_FOR( pad->ShowPadShape( PADSTACK::ALL_LAYERS ) ); KI_FALLTHROUGH; case PAD_SHAPE::CIRCLE: @@ -348,7 +348,7 @@ void GENCAD_EXPORTER::CreatePadsShapesSection() fprintf( m_file, "CIRCLE %g %g %g\n", off.x / SCALE_FACTOR, -off.y / SCALE_FACTOR, - pad->GetSize().x / (SCALE_FACTOR * 2) ); + pad->GetSize( PADSTACK::ALL_LAYERS ).x / (SCALE_FACTOR * 2) ); break; case PAD_SHAPE::RECTANGLE: @@ -365,12 +365,12 @@ void GENCAD_EXPORTER::CreatePadsShapesSection() case PAD_SHAPE::ROUNDRECT: case PAD_SHAPE::OVAL: { - const VECTOR2I& size = pad->GetSize(); + const VECTOR2I& size = pad->GetSize( PADSTACK::ALL_LAYERS ); int radius = std::min( size.x, size.y ) / 2; - if( pad->GetShape() == PAD_SHAPE::ROUNDRECT ) + if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::ROUNDRECT ) { - radius = pad->GetRoundRectCornerRadius(); + radius = pad->GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS ); } int lineX = size.x / 2 - radius; @@ -455,8 +455,8 @@ void GENCAD_EXPORTER::CreatePadsShapesSection() { fprintf( m_file, " POLYGON %g\n", pad->GetDrillSize().x / SCALE_FACTOR ); - int ddx = pad->GetDelta().x / 2; - int ddy = pad->GetDelta().y / 2; + int ddx = pad->GetDelta( PADSTACK::ALL_LAYERS ).x / 2; + int ddy = pad->GetDelta( PADSTACK::ALL_LAYERS ).y / 2; VECTOR2I poly[4]; poly[0] = VECTOR2I( -dx + ddy, dy + ddx ); @@ -485,12 +485,13 @@ void GENCAD_EXPORTER::CreatePadsShapesSection() int maxError = m_board->GetDesignSettings().m_MaxError; VECTOR2I padOffset( 0, 0 ); - TransformRoundChamferedRectToPolygon( outline, padOffset, pad->GetSize(), + TransformRoundChamferedRectToPolygon( outline, padOffset, + pad->GetSize( PADSTACK::ALL_LAYERS ), pad->GetOrientation(), - pad->GetRoundRectCornerRadius(), - pad->GetChamferRectRatio(), - pad->GetChamferPositions(), 0, maxError, - ERROR_INSIDE ); + pad->GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS ), + pad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ), + pad->GetChamferPositions( PADSTACK::ALL_LAYERS ), + 0, maxError, ERROR_INSIDE ); for( int jj = 0; jj < outline.OutlineCount(); ++jj ) { @@ -516,7 +517,7 @@ void GENCAD_EXPORTER::CreatePadsShapesSection() fprintf( m_file, " POLYGON %g\n", pad->GetDrillSize().x / SCALE_FACTOR ); SHAPE_POLY_SET outline; - pad->MergePrimitivesAsPolygon( &outline ); + pad->MergePrimitivesAsPolygon( F_Cu, &outline ); for( int jj = 0; jj < outline.OutlineCount(); ++jj ) { diff --git a/pcbnew/exporters/export_hyperlynx.cpp b/pcbnew/exporters/export_hyperlynx.cpp index c7fa3a8597..f75524dbba 100644 --- a/pcbnew/exporters/export_hyperlynx.cpp +++ b/pcbnew/exporters/export_hyperlynx.cpp @@ -212,9 +212,10 @@ private: HYPERLYNX_PAD_STACK::HYPERLYNX_PAD_STACK( BOARD* aBoard, const PAD* aPad ) { + // TODO(JE) padstacks m_board = aBoard; - m_sx = aPad->GetSize().x; - m_sy = aPad->GetSize().y; + m_sx = aPad->GetSize( PADSTACK::ALL_LAYERS ).x; + m_sy = aPad->GetSize( PADSTACK::ALL_LAYERS ).y; m_angle = 180.0 - aPad->GetOrientation().AsDegrees(); if( m_angle < 0.0 ) @@ -222,7 +223,7 @@ HYPERLYNX_PAD_STACK::HYPERLYNX_PAD_STACK( BOARD* aBoard, const PAD* aPad ) m_layers = aPad->GetLayerSet(); m_drill = aPad->GetDrillSize().x; - m_shape = aPad->GetShape(); + m_shape = aPad->GetShape( PADSTACK::ALL_LAYERS ); m_type = PAD_ATTRIB::PTH; m_id = 0; } diff --git a/pcbnew/exporters/place_file_exporter.cpp b/pcbnew/exporters/place_file_exporter.cpp index 318810ce1e..3c0e8c391c 100644 --- a/pcbnew/exporters/place_file_exporter.cpp +++ b/pcbnew/exporters/place_file_exporter.cpp @@ -400,9 +400,10 @@ std::string PLACE_FILE_EXPORTER::GenReportData() if( pad->GetLayerSet()[F_Cu] ) layer |= 2; + // TODO(JE) padstacks static const char* layer_name[4] = { "nocopper", "back", "front", "both" }; snprintf( line, sizeof(line), "Shape %s Layer %s\n", - TO_UTF8( pad->ShowPadShape() ), + TO_UTF8( pad->ShowPadShape( PADSTACK::ALL_LAYERS ) ), layer_name[layer] ); buffer += line; @@ -411,8 +412,8 @@ std::string PLACE_FILE_EXPORTER::GenReportData() snprintf( line, sizeof(line), "position %9.6f %9.6f size %9.6f %9.6f orientation %.2f\n", padPos.x * conv_unit, padPos.y * conv_unit, - pad->GetSize().x * conv_unit, - pad->GetSize().y * conv_unit, + pad->GetSize( PADSTACK::ALL_LAYERS ).x * conv_unit, + pad->GetSize( PADSTACK::ALL_LAYERS ).y * conv_unit, ( pad->GetOrientation() - footprint->GetOrientation() ).AsDegrees() ); buffer += line; @@ -420,8 +421,8 @@ std::string PLACE_FILE_EXPORTER::GenReportData() buffer += line; snprintf( line, sizeof(line), "shape_offset %9.6f %9.6f\n", - pad->GetOffset().x * conv_unit, - pad->GetOffset().y * conv_unit ); + pad->GetOffset( PADSTACK::ALL_LAYERS ).x * conv_unit, + pad->GetOffset( PADSTACK::ALL_LAYERS ).y * conv_unit ); buffer += line; buffer += "$EndPAD\n"; diff --git a/pcbnew/exporters/step/exporter_step.cpp b/pcbnew/exporters/step/exporter_step.cpp index 4443ba22ce..c11c6ec794 100644 --- a/pcbnew/exporters/step/exporter_step.cpp +++ b/pcbnew/exporters/step/exporter_step.cpp @@ -206,9 +206,10 @@ bool EXPORTER_STEP::buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOri continue; SHAPE_POLY_SET poly; - - pad->TransformShapeToPolygon( poly, pcblayer, pad->GetSolderMaskExpansion(), - maxError, ERROR_INSIDE ); + PCB_LAYER_ID cuLayer = ( pcblayer == F_Mask ) ? F_Cu : B_Cu; + pad->TransformShapeToPolygon( poly, cuLayer, + pad->GetSolderMaskExpansion( cuLayer ), maxError, + ERROR_INSIDE ); m_poly_shapes[pcblayer].Append( poly ); } @@ -245,7 +246,7 @@ bool EXPORTER_STEP::buildFootprint3DShapes( FOOTPRINT* aFootprint, VECTOR2D aOri if( !pad->IsOnLayer( pcblayer ) ) continue; - std::shared_ptr padPoly = pad->GetEffectivePolygon(); + std::shared_ptr padPoly = pad->GetEffectivePolygon( pcblayer ); SHAPE_POLY_SET gfxPoly( poly ); if( padPoly->Collide( &gfxPoly ) ) diff --git a/pcbnew/footprint.cpp b/pcbnew/footprint.cpp index b24fd88965..ea52ebe349 100644 --- a/pcbnew/footprint.cpp +++ b/pcbnew/footprint.cpp @@ -1463,7 +1463,12 @@ SHAPE_POLY_SET FOOTPRINT::GetBoundingHull() const for( PAD* pad : m_pads ) { - pad->TransformShapeToPolygon( rawPolys, UNDEFINED_LAYER, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); + pad->Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + pad->TransformShapeToPolygon( rawPolys, aLayer, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); + } ); + // In case hole is larger than pad pad->TransformHoleToPolygon( rawPolys, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); } @@ -2713,6 +2718,16 @@ double FOOTPRINT::GetCoverageArea( const BOARD_ITEM* aItem, const GENERAL_COLLEC double width = static_cast( aItem )->GetWidth(); return width * width; } + else if( aItem->Type() == PCB_PAD_T ) + { + static_cast( aItem )->Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + SHAPE_POLY_SET padPoly; + aItem->TransformShapeToPolygon( padPoly, aLayer, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); + poly.BooleanAdd( padPoly, SHAPE_POLY_SET::PM_FAST ); + } ); + } else { aItem->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); @@ -3120,8 +3135,9 @@ void FOOTPRINT::CheckShortingPads( const std::functionGetBoundingBox().Intersects( other->GetBoundingBox() ) ) { VECTOR2I pos; - SHAPE* padShape = pad->GetEffectiveShape().get(); - SHAPE* otherShape = other->GetEffectiveShape().get(); + // TODO(JE) padstacks - need to check complex against complex + SHAPE* padShape = pad->GetEffectiveShape( PADSTACK::ALL_LAYERS ).get(); + SHAPE* otherShape = other->GetEffectiveShape( PADSTACK::ALL_LAYERS ).get(); if( padShape->Collide( otherShape, 0, nullptr, &pos ) ) aErrorHandler( pad, other, DRCE_SHORTING_ITEMS, pos ); @@ -3465,13 +3481,31 @@ bool FOOTPRINT::cmp_pads::operator()( const PAD* aFirst, const PAD* aSecond ) co if( aFirst->GetFPRelativePosition().y != aSecond->GetFPRelativePosition().y ) return aFirst->GetFPRelativePosition().y < aSecond->GetFPRelativePosition().y; - if( aFirst->GetSize().x != aSecond->GetSize().x ) - return aFirst->GetSize().x < aSecond->GetSize().x; - if( aFirst->GetSize().y != aSecond->GetSize().y ) - return aFirst->GetSize().y < aSecond->GetSize().y; + std::optional padCopperMatches; - if( aFirst->GetShape() != aSecond->GetShape() ) - return aFirst->GetShape() < aSecond->GetShape(); + // Pick the "most complex" padstack to iterate + const PAD* checkPad = aFirst; + + if( aSecond->Padstack().Mode() == PADSTACK::MODE::CUSTOM + || ( aSecond->Padstack().Mode() == PADSTACK::MODE::FRONT_INNER_BACK && + aFirst->Padstack().Mode() == PADSTACK::MODE::NORMAL ) ) + { + checkPad = aSecond; + } + + checkPad->Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + if( aFirst->GetSize( aLayer ).x != aSecond->GetSize( aLayer ).x ) + padCopperMatches = aFirst->GetSize( aLayer ).x < aSecond->GetSize( aLayer ).x; + else if( aFirst->GetSize( aLayer ).y != aSecond->GetSize( aLayer ).y ) + padCopperMatches = aFirst->GetSize( aLayer ).y < aSecond->GetSize( aLayer ).y; + else if( aFirst->GetShape( aLayer ) != aSecond->GetShape( aLayer ) ) + padCopperMatches = aFirst->GetShape( aLayer ) < aSecond->GetShape( aLayer ); + } ); + + if( padCopperMatches.has_value() ) + return *padCopperMatches; if( aFirst->GetLayerSet().Seq() != aSecond->GetLayerSet().Seq() ) return aFirst->GetLayerSet().Seq() < aSecond->GetLayerSet().Seq(); @@ -3483,6 +3517,7 @@ bool FOOTPRINT::cmp_pads::operator()( const PAD* aFirst, const PAD* aSecond ) co } +#if 0 bool FOOTPRINT::cmp_padstack::operator()( const PAD* aFirst, const PAD* aSecond ) const { if( aFirst->GetSize().x != aSecond->GetSize().x ) @@ -3536,6 +3571,7 @@ bool FOOTPRINT::cmp_padstack::operator()( const PAD* aFirst, const PAD* aSecond return false; } +#endif bool FOOTPRINT::cmp_zones::operator()( const ZONE* aFirst, const ZONE* aSecond ) const @@ -3569,69 +3605,87 @@ void FOOTPRINT::TransformPadsToPolySet( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aL bool aSkipNPTHPadsWihNoCopper, bool aSkipPlatedPads, bool aSkipNonPlatedPads ) const { + auto processPad = + [&]( const PAD* pad, PCB_LAYER_ID padLayer ) + { + VECTOR2I clearance( aClearance, aClearance ); + + switch( aLayer ) + { + case F_Cu: + if( aSkipPlatedPads && pad->FlashLayer( F_Mask ) ) + return; + + if( aSkipNonPlatedPads && !pad->FlashLayer( F_Mask ) ) + return; + + break; + + case B_Cu: + if( aSkipPlatedPads && pad->FlashLayer( B_Mask ) ) + return; + + if( aSkipNonPlatedPads && !pad->FlashLayer( B_Mask ) ) + return; + + break; + + case F_Mask: + case B_Mask: + clearance.x += pad->GetSolderMaskExpansion( padLayer ); + clearance.y += pad->GetSolderMaskExpansion( padLayer ); + break; + + case F_Paste: + case B_Paste: + clearance += pad->GetSolderPasteMargin( padLayer ); + break; + + default: + break; + } + + // Our standard TransformShapeToPolygon() routines can't handle differing x:y clearance + // values (which get generated when a relative paste margin is used with an oblong pad). + // So we apply this huge hack and fake a larger pad to run the transform on. + // Of course being a hack it falls down when dealing with custom shape pads (where the + // size is only the size of the anchor), so for those we punt and just use clearance.x. + + if( ( clearance.x < 0 || clearance.x != clearance.y ) + && pad->GetShape( padLayer ) != PAD_SHAPE::CUSTOM ) + { + VECTOR2I dummySize = pad->GetSize( padLayer ) + clearance + clearance; + + if( dummySize.x <= 0 || dummySize.y <= 0 ) + return; + + PAD dummy( *pad ); + dummy.SetSize( padLayer, dummySize ); + dummy.TransformShapeToPolygon( aBuffer, padLayer, 0, aMaxError, aErrorLoc ); + } + else + { + pad->TransformShapeToPolygon( aBuffer, padLayer, clearance.x, aMaxError, + aErrorLoc ); + } + }; + for( const PAD* pad : m_pads ) { if( !pad->FlashLayer( aLayer ) ) continue; - VECTOR2I clearance( aClearance, aClearance ); - - switch( aLayer ) + if( aLayer == UNDEFINED_LAYER ) { - case F_Cu: - if( aSkipPlatedPads && pad->FlashLayer( F_Mask ) ) - continue; - - if( aSkipNonPlatedPads && !pad->FlashLayer( F_Mask ) ) - continue; - - break; - - case B_Cu: - if( aSkipPlatedPads && pad->FlashLayer( B_Mask ) ) - continue; - - if( aSkipNonPlatedPads && !pad->FlashLayer( B_Mask ) ) - continue; - - break; - - case F_Mask: - case B_Mask: - clearance.x += pad->GetSolderMaskExpansion(); - clearance.y += pad->GetSolderMaskExpansion(); - break; - - case F_Paste: - case B_Paste: - clearance += pad->GetSolderPasteMargin(); - break; - - default: - break; - } - - // Our standard TransformShapeToPolygon() routines can't handle differing x:y clearance - // values (which get generated when a relative paste margin is used with an oblong pad). - // So we apply this huge hack and fake a larger pad to run the transform on. - // Of course being a hack it falls down when dealing with custom shape pads (where the - // size is only the size of the anchor), so for those we punt and just use clearance.x. - - if( ( clearance.x < 0 || clearance.x != clearance.y ) - && pad->GetShape() != PAD_SHAPE::CUSTOM ) - { - VECTOR2I dummySize = pad->GetSize() + clearance + clearance; - - if( dummySize.x <= 0 || dummySize.y <= 0 ) - continue; - - PAD dummy( *pad ); - dummy.SetSize( dummySize ); - dummy.TransformShapeToPolygon( aBuffer, aLayer, 0, aMaxError, aErrorLoc ); + pad->Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID l ) + { + processPad( pad, l ); + } ); } else { - pad->TransformShapeToPolygon( aBuffer, aLayer, clearance.x, aMaxError, aErrorLoc ); + processPad( pad, aLayer ); } } } diff --git a/pcbnew/footprint.h b/pcbnew/footprint.h index ebcd5fd083..747dcb6038 100644 --- a/pcbnew/footprint.h +++ b/pcbnew/footprint.h @@ -1009,12 +1009,13 @@ public: { bool operator()( const PAD* aFirst, const PAD* aSecond ) const; }; - + // TODO(JE) padstacks -- this code isn't used anywhere though +#if 0 struct cmp_padstack { bool operator()( const PAD* aFirst, const PAD* aSecond ) const; }; - +#endif struct cmp_zones { bool operator()( const ZONE* aFirst, const ZONE* aSecond ) const; diff --git a/pcbnew/microwave/microwave_footprint.cpp b/pcbnew/microwave/microwave_footprint.cpp index dfa448810d..7914108ea7 100644 --- a/pcbnew/microwave/microwave_footprint.cpp +++ b/pcbnew/microwave/microwave_footprint.cpp @@ -128,28 +128,29 @@ FOOTPRINT* MICROWAVE_TOOL::createFootprint( MICROWAVE_FOOTPRINT_SHAPE aFootprint switch( aFootprintShape ) { case MICROWAVE_FOOTPRINT_SHAPE::GAP: //Gap : - offsetX = -( gap_size + pad->GetSize().x ) / 2; + offsetX = -( gap_size + pad->GetSize( PADSTACK::ALL_LAYERS ).x ) / 2; pad->SetX( pad->GetPosition().x + offsetX ); pad = *( it + 1 ); - pad->SetX( pad->GetPosition().x + offsetX + gap_size + pad->GetSize().x ); + pad->SetX( pad->GetPosition().x + offsetX + gap_size + pad->GetSize( PADSTACK::ALL_LAYERS ).x ); break; case MICROWAVE_FOOTPRINT_SHAPE::STUB: //Stub : pad->SetNumber( wxT( "1" ) ); - offsetY = -( gap_size + pad->GetSize().y ) / 2; + offsetY = -( gap_size + pad->GetSize( PADSTACK::ALL_LAYERS ).y ) / 2; pad = *( it + 1 ); - pad->SetSize( VECTOR2I( pad->GetSize().x, gap_size ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, + VECTOR2I( pad->GetSize( PADSTACK::ALL_LAYERS ).x, gap_size ) ); pad->SetY( pad->GetPosition().y + offsetY ); break; case MICROWAVE_FOOTPRINT_SHAPE::STUB_ARC: // Arc Stub created by a polygonal approach: { - pad->SetShape( PAD_SHAPE::CUSTOM ); - pad->SetAnchorPadShape( PAD_SHAPE::RECTANGLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM ); + pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); int numPoints = ( angle.AsDegrees() / 5.0 ) + 3; std::vector polyPoints; @@ -174,7 +175,7 @@ FOOTPRINT* MICROWAVE_TOOL::createFootprint( MICROWAVE_FOOTPRINT_SHAPE aFootprint // Close the polygon: polyPoints.push_back( polyPoints[0] ); - pad->AddPrimitivePoly( polyPoints, 0, true ); // add a polygonal basic shape + pad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, polyPoints, 0, true ); // add a polygonal basic shape break; } @@ -217,10 +218,10 @@ FOOTPRINT* MICROWAVE_TOOL::createBaseFootprint( const wxString& aValue, footprint->Add( pad, ADD_MODE::INSERT ); int tw = editFrame.GetDesignSettings().GetCurrentTrackWidth(); - pad->SetSize( VECTOR2I( tw, tw ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( tw, tw ) ); pad->SetPosition( footprint->GetPosition() ); - pad->SetShape( PAD_SHAPE::RECTANGLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); pad->SetAttribute( PAD_ATTRIB::SMD ); pad->SetLayerSet( { F_Cu } ); diff --git a/pcbnew/microwave/microwave_inductor.cpp b/pcbnew/microwave/microwave_inductor.cpp index 71b59c5454..fb1a76051e 100644 --- a/pcbnew/microwave/microwave_inductor.cpp +++ b/pcbnew/microwave/microwave_inductor.cpp @@ -435,11 +435,12 @@ FOOTPRINT* MICROWAVE_TOOL::createMicrowaveInductor( MICROWAVE_INDUCTOR_PATTERN& pad->SetNumber( wxT( "1" ) ); pad->SetPosition( aInductorPattern.m_End ); - pad->SetSize( VECTOR2I( aInductorPattern.m_Width, aInductorPattern.m_Width ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, + VECTOR2I( aInductorPattern.m_Width, aInductorPattern.m_Width ) ); pad->SetLayerSet( LSET( { footprint->GetLayer() } ) ); pad->SetAttribute( PAD_ATTRIB::SMD ); - pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); PAD* newpad = new PAD( *pad ); const_cast( newpad->m_Uuid ) = KIID(); @@ -462,4 +463,4 @@ FOOTPRINT* MICROWAVE_TOOL::createMicrowaveInductor( MICROWAVE_INDUCTOR_PATTERN& footprint->Value().SetPosition( valPos ); return footprint; -} \ No newline at end of file +} diff --git a/pcbnew/pad.cpp b/pcbnew/pad.cpp index 250894b3b6..0b452c5170 100644 --- a/pcbnew/pad.cpp +++ b/pcbnew/pad.cpp @@ -72,7 +72,7 @@ PAD::PAD( FOOTPRINT* parent ) : m_padStack( this ) { VECTOR2I& drill = m_padStack.Drill().size; - VECTOR2I& size = m_padStack.Size(); + VECTOR2I& size = m_padStack.Size( PADSTACK::ALL_LAYERS ); size.x = size.y = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 60 ); // Default pad size 60 mils. drill.x = drill.y = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 30 ); // Default drill size 30 mils. m_lengthPadToDie = 0; @@ -80,19 +80,19 @@ PAD::PAD( FOOTPRINT* parent ) : if( m_parent && m_parent->Type() == PCB_FOOTPRINT_T ) m_pos = GetParent()->GetPosition(); - SetShape( PAD_SHAPE::CIRCLE ); // Default pad shape is PAD_CIRCLE. - SetAnchorPadShape( PAD_SHAPE::CIRCLE ); // Default shape for custom shaped pads - // is PAD_CIRCLE. + SetShape( F_Cu, PAD_SHAPE::CIRCLE ); // Default pad shape is PAD_CIRCLE. + SetAnchorPadShape( F_Cu, PAD_SHAPE::CIRCLE ); // Default shape for custom shaped pads + // is PAD_CIRCLE. SetDrillShape( PAD_DRILL_SHAPE::CIRCLE ); // Default pad drill shape is a circle. - m_attribute = PAD_ATTRIB::PTH; // Default pad type is plated through hole - SetProperty( PAD_PROP::NONE ); // no special fabrication property + m_attribute = PAD_ATTRIB::PTH; // Default pad type is plated through hole + SetProperty( PAD_PROP::NONE ); // no special fabrication property // Parameters for round rect only: - m_padStack.SetRoundRectRadiusRatio( 0.25 ); // from IPC-7351C standard + m_padStack.SetRoundRectRadiusRatio( 0.25, F_Cu ); // from IPC-7351C standard // Parameters for chamfered rect only: - m_padStack.SetChamferRatio( 0.2 ); - m_padStack.SetChamferPositions( RECT_NO_CHAMFER ); + m_padStack.SetChamferRatio( 0.2, F_Cu ); + m_padStack.SetChamferPositions( RECT_NO_CHAMFER, F_Cu ); // Set layers mask to default for a standard thru hole pad. m_padStack.SetLayerSet( PTHMask() ); @@ -339,20 +339,28 @@ bool PAD::FlashLayer( int aLayer, bool aOnlyCheckIfPermitted ) const if( aLayer == UNDEFINED_LAYER ) return true; - if( !IsOnLayer( static_cast( aLayer ) ) ) + // Sometimes this is called with GAL layers and should just return true + if( aLayer > PCB_LAYER_ID_COUNT ) + return true; + + const PCB_LAYER_ID& layer = static_cast( aLayer ); + + if( !IsOnLayer( layer ) ) return false; if( GetAttribute() == PAD_ATTRIB::NPTH && IsCopperLayer( aLayer ) ) { - if( GetShape() == PAD_SHAPE::CIRCLE && GetDrillShape() == PAD_DRILL_SHAPE::CIRCLE ) + if( GetShape( layer ) == PAD_SHAPE::CIRCLE && GetDrillShape() == PAD_DRILL_SHAPE::CIRCLE ) { - if( GetOffset() == VECTOR2I( 0, 0 ) && GetDrillSize().x >= GetSize().x ) + if( GetOffset( layer ) == VECTOR2I( 0, 0 ) && GetDrillSize().x >= GetSize( layer ).x ) return false; } - else if( GetShape() == PAD_SHAPE::OVAL && GetDrillShape() == PAD_DRILL_SHAPE::OBLONG ) + else if( GetShape( layer ) == PAD_SHAPE::OVAL + && GetDrillShape() == PAD_DRILL_SHAPE::OBLONG ) { - if( GetOffset() == VECTOR2I( 0, 0 ) - && GetDrillSize().x >= GetSize().x && GetDrillSize().y >= GetSize().y ) + if( GetOffset( layer ) == VECTOR2I( 0, 0 ) + && GetDrillSize().x >= GetSize( layer ).x + && GetDrillSize().y >= GetSize( layer ).y ) { return false; } @@ -427,40 +435,50 @@ void PAD::SetDrillShape( PAD_DRILL_SHAPE aShape ) } -int PAD::GetRoundRectCornerRadius() const +int PAD::GetRoundRectCornerRadius( PCB_LAYER_ID aLayer ) const { - return m_padStack.RoundRectRadius(); + return m_padStack.RoundRectRadius( aLayer ); } -void PAD::SetRoundRectCornerRadius( double aRadius ) +void PAD::SetRoundRectCornerRadius( PCB_LAYER_ID aLayer, double aRadius ) { - m_padStack.SetRoundRectRadius( aRadius ); + m_padStack.SetRoundRectRadius( aRadius, aLayer ); } -void PAD::SetRoundRectRadiusRatio( double aRadiusScale ) +void PAD::SetRoundRectRadiusRatio( PCB_LAYER_ID aLayer, double aRadiusScale ) { - m_padStack.SetRoundRectRadiusRatio( alg::clamp( 0.0, aRadiusScale, 0.5 ) ); + m_padStack.SetRoundRectRadiusRatio( alg::clamp( 0.0, aRadiusScale, 0.5 ), aLayer ); SetDirty(); } -void PAD::SetChamferRectRatio( double aChamferScale ) +void PAD::SetFrontRoundRectRadiusRatio( double aRadiusScale ) { - m_padStack.SetChamferRatio( alg::clamp( 0.0, aChamferScale, 0.5 ) ); + m_padStack.SetRoundRectRadiusRatio( alg::clamp( 0.0, aRadiusScale, 0.5 ), F_Cu ); + SetDirty(); +} + + +void PAD::SetChamferRectRatio( PCB_LAYER_ID aLayer, double aChamferScale ) +{ + m_padStack.SetChamferRatio( alg::clamp( 0.0, aChamferScale, 0.5 ), aLayer ); SetDirty(); } -const std::shared_ptr& PAD::GetEffectivePolygon( ERROR_LOC aErrorLoc ) const +const std::shared_ptr& PAD::GetEffectivePolygon( PCB_LAYER_ID aLayer, + ERROR_LOC aErrorLoc ) const { if( m_polyDirty[ aErrorLoc ] ) BuildEffectivePolygon( aErrorLoc ); - return m_effectivePolygon[ aErrorLoc ]; + aLayer = Padstack().EffectiveLayerFor( aLayer ); + + return m_effectivePolygons[ aLayer ][ aErrorLoc ]; } @@ -495,16 +513,18 @@ std::shared_ptr PAD::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING fla } if( m_shapesDirty ) - BuildEffectiveShapes( aLayer ); + BuildEffectiveShapes(); - return m_effectiveShape; + aLayer = Padstack().EffectiveLayerFor( aLayer ); + + return m_effectiveShapes[aLayer]; } std::shared_ptr PAD::GetEffectiveHoleShape() const { if( m_shapesDirty ) - BuildEffectiveShapes( UNDEFINED_LAYER ); + BuildEffectiveShapes(); return m_effectiveHoleShape; } @@ -519,7 +539,7 @@ int PAD::GetBoundingRadius() const } -void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const +void PAD::BuildEffectiveShapes() const { std::lock_guard RAII_lock( m_shapesBuildingLock ); @@ -528,23 +548,51 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const if( !m_shapesDirty ) return; + m_effectiveBoundingBox = BOX2I(); + + Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + const SHAPE_COMPOUND& layerShape = buildEffectiveShape( aLayer ); + m_effectiveBoundingBox.Merge( layerShape.BBox() ); + } ); + + // Hole shape + m_effectiveHoleShape = nullptr; + + VECTOR2I half_size = m_padStack.Drill().size / 2; + int half_width = std::min( half_size.x, half_size.y ); + VECTOR2I half_len( half_size.x - half_width, half_size.y - half_width ); + + RotatePoint( half_len, GetOrientation() ); + + m_effectiveHoleShape = std::make_shared( m_pos - half_len, m_pos + half_len, + half_width * 2 ); + m_effectiveBoundingBox.Merge( m_effectiveHoleShape->BBox() ); + + // All done + m_shapesDirty = false; +} + + +const SHAPE_COMPOUND& PAD::buildEffectiveShape( PCB_LAYER_ID aLayer ) const +{ const BOARD* board = GetBoard(); int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; - m_effectiveShape = std::make_shared(); - m_effectiveHoleShape = nullptr; + m_effectiveShapes[aLayer] = std::make_shared(); - auto add = [this]( SHAPE* aShape ) + auto add = [this, aLayer]( SHAPE* aShape ) { - m_effectiveShape->AddShape( aShape ); + m_effectiveShapes[aLayer]->AddShape( aShape ); }; - VECTOR2I shapePos = ShapePos(); // Fetch only once; rotation involves trig - PAD_SHAPE effectiveShape = GetShape(); + VECTOR2I shapePos = ShapePos( aLayer ); // Fetch only once; rotation involves trig + PAD_SHAPE effectiveShape = GetShape( aLayer ); const VECTOR2I& size = m_padStack.Size( aLayer ); - if( GetShape() == PAD_SHAPE::CUSTOM ) - effectiveShape = GetAnchorPadShape(); + if( effectiveShape == PAD_SHAPE::CUSTOM ) + effectiveShape = GetAnchorPadShape( aLayer ); switch( effectiveShape ) { @@ -572,7 +620,7 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const case PAD_SHAPE::TRAPEZOID: case PAD_SHAPE::ROUNDRECT: { - int r = ( effectiveShape == PAD_SHAPE::ROUNDRECT ) ? GetRoundRectCornerRadius() : 0; + int r = ( effectiveShape == PAD_SHAPE::ROUNDRECT ) ? GetRoundRectCornerRadius( aLayer ) : 0; VECTOR2I half_size( size.x / 2, size.y / 2 ); VECTOR2I trap_delta( 0, 0 ); @@ -582,7 +630,7 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const // Avoid degenerated shapes (0 length segments) that always create issues // For roundrect pad very near a circle, use only a circle - const int min_len = pcbIUScale.mmToIU( 0.0001); + const int min_len = pcbIUScale.mmToIU( 0.0001 ); if( half_size.x < min_len && half_size.y < min_len ) { @@ -647,9 +695,11 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const { SHAPE_POLY_SET outline; - TransformRoundChamferedRectToPolygon( outline, shapePos, GetSize(), GetOrientation(), - GetRoundRectCornerRadius(), GetChamferRectRatio(), - GetChamferPositions(), 0, maxError, ERROR_INSIDE ); + TransformRoundChamferedRectToPolygon( outline, shapePos, GetSize( aLayer ), + GetOrientation(), GetRoundRectCornerRadius( aLayer ), + GetChamferRectRatio( aLayer ), + GetChamferPositions( aLayer ), 0, maxError, + ERROR_INSIDE ); add( new SHAPE_SIMPLE( outline.COutline( 0 ) ) ); } @@ -661,9 +711,9 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const break; } - if( GetShape() == PAD_SHAPE::CUSTOM ) + if( GetShape( aLayer ) == PAD_SHAPE::CUSTOM ) { - for( const std::shared_ptr& primitive : m_padStack.Primitives() ) + for( const std::shared_ptr& primitive : m_padStack.Primitives( aLayer ) ) { if( !primitive->IsProxyItem() ) { @@ -677,21 +727,7 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const } } - m_effectiveBoundingBox = m_effectiveShape->BBox(); - - // Hole shape - VECTOR2I half_size = m_padStack.Drill().size / 2; - int half_width = std::min( half_size.x, half_size.y ); - VECTOR2I half_len( half_size.x - half_width, half_size.y - half_width ); - - RotatePoint( half_len, GetOrientation() ); - - m_effectiveHoleShape = std::make_shared( m_pos - half_len, m_pos + half_len, - half_width * 2 ); - m_effectiveBoundingBox.Merge( m_effectiveHoleShape->BBox() ); - - // All done - m_shapesDirty = false; + return *m_effectiveShapes[aLayer]; } @@ -707,31 +743,35 @@ void PAD::BuildEffectivePolygon( ERROR_LOC aErrorLoc ) const const BOARD* board = GetBoard(); int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; - // Polygon - std::shared_ptr& effectivePolygon = m_effectivePolygon[ aErrorLoc ]; - - effectivePolygon = std::make_shared(); - TransformShapeToPolygon( *effectivePolygon, UNDEFINED_LAYER, 0, maxError, aErrorLoc ); - - // Bounding radius - // - // PADSTACKS TODO: these will both need to cycle through all layers to get the largest - // values.... - if( aErrorLoc == ERROR_OUTSIDE ) - { - m_effectiveBoundingRadius = 0; - - for( int cnt = 0; cnt < effectivePolygon->OutlineCount(); ++cnt ) + Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) { - const SHAPE_LINE_CHAIN& poly = effectivePolygon->COutline( cnt ); + // Polygon + std::shared_ptr& effectivePolygon = + m_effectivePolygons[ aLayer ][ aErrorLoc ]; - for( int ii = 0; ii < poly.PointCount(); ++ii ) + effectivePolygon = std::make_shared(); + TransformShapeToPolygon( *effectivePolygon, aLayer, 0, maxError, aErrorLoc ); + + // Bounding radius + + if( aErrorLoc == ERROR_OUTSIDE ) { - int dist = KiROUND( ( poly.CPoint( ii ) - m_pos ).EuclideanNorm() ); - m_effectiveBoundingRadius = std::max( m_effectiveBoundingRadius, dist ); + m_effectiveBoundingRadius = 0; + + for( int cnt = 0; cnt < effectivePolygon->OutlineCount(); ++cnt ) + { + const SHAPE_LINE_CHAIN& poly = effectivePolygon->COutline( cnt ); + + for( int ii = 0; ii < poly.PointCount(); ++ii ) + { + int dist = KiROUND( ( poly.CPoint( ii ) - m_pos ).EuclideanNorm() ); + m_effectiveBoundingRadius = std::max( m_effectiveBoundingRadius, dist ); + } + } } - } - } + + } ); // All done m_polyDirty[ aErrorLoc ] = false; @@ -741,7 +781,7 @@ void PAD::BuildEffectivePolygon( ERROR_LOC aErrorLoc ) const const BOX2I PAD::GetBoundingBox() const { if( m_shapesDirty ) - BuildEffectiveShapes( UNDEFINED_LAYER ); + BuildEffectiveShapes(); return m_effectiveBoundingBox; } @@ -831,8 +871,13 @@ EDA_ANGLE PAD::GetFPRelativeOrientation() const void PAD::Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection ) { MIRROR( m_pos, aCentre, aFlipDirection ); - MIRROR( m_padStack.Offset(), VECTOR2I{ 0, 0 }, aFlipDirection ); - MIRROR( m_padStack.TrapezoidDeltaSize(), VECTOR2I{ 0, 0 }, aFlipDirection ); + + m_padStack.ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + MIRROR( m_padStack.Offset( aLayer ), VECTOR2I{ 0, 0 }, aFlipDirection ); + MIRROR( m_padStack.TrapezoidDeltaSize( aLayer ), VECTOR2I{ 0, 0 }, aFlipDirection ); + } ); SetFPRelativeOrientation( -GetFPRelativeOrientation() ); @@ -851,20 +896,24 @@ void PAD::Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection ) aBitfield &= ~b; }; - if( aFlipDirection == FLIP_DIRECTION::LEFT_RIGHT ) - { - mirrorBitFlags( m_padStack.ChamferPositions(), RECT_CHAMFER_TOP_LEFT, - RECT_CHAMFER_TOP_RIGHT ); - mirrorBitFlags( m_padStack.ChamferPositions(), RECT_CHAMFER_BOTTOM_LEFT, - RECT_CHAMFER_BOTTOM_RIGHT ); - } - else - { - mirrorBitFlags( m_padStack.ChamferPositions(), RECT_CHAMFER_TOP_LEFT, - RECT_CHAMFER_BOTTOM_LEFT ); - mirrorBitFlags( m_padStack.ChamferPositions(), RECT_CHAMFER_TOP_RIGHT, - RECT_CHAMFER_BOTTOM_RIGHT ); - } + Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + if( aFlipDirection == FLIP_DIRECTION::LEFT_RIGHT ) + { + mirrorBitFlags( m_padStack.ChamferPositions( aLayer ), RECT_CHAMFER_TOP_LEFT, + RECT_CHAMFER_TOP_RIGHT ); + mirrorBitFlags( m_padStack.ChamferPositions( aLayer ), RECT_CHAMFER_BOTTOM_LEFT, + RECT_CHAMFER_BOTTOM_RIGHT ); + } + else + { + mirrorBitFlags( m_padStack.ChamferPositions( aLayer ), RECT_CHAMFER_TOP_LEFT, + RECT_CHAMFER_BOTTOM_LEFT ); + mirrorBitFlags( m_padStack.ChamferPositions( aLayer ), RECT_CHAMFER_TOP_RIGHT, + RECT_CHAMFER_BOTTOM_RIGHT ); + } + } ); // flip pads layers // PADS items are currently on all copper layers, or @@ -882,19 +931,23 @@ void PAD::Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection ) void PAD::FlipPrimitives( FLIP_DIRECTION aFlipDirection ) { - for( std::shared_ptr& primitive : m_padStack.Primitives() ) - primitive->Flip( VECTOR2I( 0, 0 ), aFlipDirection ); + Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + for( std::shared_ptr& primitive : m_padStack.Primitives( aLayer ) ) + primitive->Flip( VECTOR2I( 0, 0 ), aFlipDirection ); + } ); SetDirty(); } -VECTOR2I PAD::ShapePos() const +VECTOR2I PAD::ShapePos( PCB_LAYER_ID aLayer ) const { - if( m_padStack.Offset().x == 0 && m_padStack.Offset().y == 0 ) - return m_pos; + VECTOR2I loc_offset = m_padStack.Offset( aLayer ); - VECTOR2I loc_offset = m_padStack.Offset(); + if( loc_offset.x == 0 && loc_offset.y == 0 ) + return m_pos; RotatePoint( loc_offset, GetOrientation() ); @@ -910,33 +963,41 @@ bool PAD::IsOnCopperLayer() const { // NPTH pads have no plated hole cylinder. If their annular ring size is 0 or // negative, then they have no annular ring either. + bool hasAnnularRing = true; - switch( GetShape() ) - { - case PAD_SHAPE::CIRCLE: - if( m_padStack.Offset() == VECTOR2I( 0, 0 ) - && m_padStack.Size().x <= m_padStack.Drill().size.x ) + Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) { + switch( GetShape( aLayer ) ) + { + case PAD_SHAPE::CIRCLE: + if( m_padStack.Offset( aLayer ) == VECTOR2I( 0, 0 ) + && m_padStack.Size( aLayer ).x <= m_padStack.Drill().size.x ) + { + hasAnnularRing = false; + } + + break; + + case PAD_SHAPE::OVAL: + if( m_padStack.Offset( aLayer ) == VECTOR2I( 0, 0 ) + && m_padStack.Size( aLayer ).x <= m_padStack.Drill().size.x + && m_padStack.Size( aLayer ).y <= m_padStack.Drill().size.y ) + { + hasAnnularRing = false; + } + + break; + + default: + // We could subtract the hole polygon from the shape polygon for these, but it + // would be expensive and we're probably well out of the common use cases.... + break; + } + } ); + + if( !hasAnnularRing ) return false; - } - - break; - - case PAD_SHAPE::OVAL: - if( m_padStack.Offset() == VECTOR2I( 0, 0 ) - && m_padStack.Size().x <= m_padStack.Drill().size.x - && m_padStack.Size().y <= m_padStack.Drill().size.y ) - { - return false; - } - - break; - - default: - // We could subtract the hole polygon from the shape polygon for these, but it - // would be expensive and we're probably well out of the common use cases.... - break; - } } return ( GetLayerSet() & LSET::AllCuMask() ).any(); @@ -990,7 +1051,7 @@ int PAD::GetOwnClearance( PCB_LAYER_ID aLayer, wxString* aSource ) const } -int PAD::GetSolderMaskExpansion() const +int PAD::GetSolderMaskExpansion( PCB_LAYER_ID aLayer ) const { // Pads defined only on mask layers (and perhaps on other tech layers) use the shape // defined by the pad settings only. ALL other pads, even those that don't actually have @@ -998,7 +1059,14 @@ int PAD::GetSolderMaskExpansion() const if( ( m_padStack.LayerSet() & LSET::AllCuMask() ).none() ) return 0; - std::optional margin = m_padStack.SolderMaskMargin(); + if( IsFrontLayer( aLayer ) ) + aLayer = F_Mask; + else if( IsBackLayer( aLayer ) ) + aLayer = B_Mask; + else + return 0; + + std::optional margin = m_padStack.SolderMaskMargin( aLayer ); if( !margin.has_value() ) { @@ -1014,10 +1082,12 @@ int PAD::GetSolderMaskExpansion() const int marginValue = margin.value_or( 0 ); + PCB_LAYER_ID cuLayer = ( aLayer == B_Mask ) ? B_Cu : F_Cu; + // ensure mask have a size always >= 0 if( marginValue < 0 ) { - int minsize = -std::min( m_padStack.Size().x, m_padStack.Size().y ) / 2; + int minsize = -std::min( m_padStack.Size( cuLayer ).x, m_padStack.Size( cuLayer ).y ) / 2; if( marginValue < minsize ) marginValue = minsize; @@ -1027,7 +1097,7 @@ int PAD::GetSolderMaskExpansion() const } -VECTOR2I PAD::GetSolderPasteMargin() const +VECTOR2I PAD::GetSolderPasteMargin( PCB_LAYER_ID aLayer ) const { // Pads defined only on mask layers (and perhaps on other tech layers) use the shape // defined by the pad settings only. ALL other pads, even those that don't actually have @@ -1035,8 +1105,15 @@ VECTOR2I PAD::GetSolderPasteMargin() const if( ( m_padStack.LayerSet() & LSET::AllCuMask() ).none() ) return VECTOR2I( 0, 0 ); - std::optional margin = m_padStack.SolderPasteMargin(); - std::optional mratio = m_padStack.SolderPasteMarginRatio(); + if( IsFrontLayer( aLayer ) ) + aLayer = F_Mask; + else if( IsBackLayer( aLayer ) ) + aLayer = B_Paste; + else + return VECTOR2I( 0, 0 ); + + std::optional margin = m_padStack.SolderPasteMargin( aLayer ); + std::optional mratio = m_padStack.SolderPasteMarginRatio( aLayer ); if( !margin.has_value() ) { @@ -1062,18 +1139,21 @@ VECTOR2I PAD::GetSolderPasteMargin() const mratio = board->GetDesignSettings().m_SolderPasteMarginRatio; } + PCB_LAYER_ID cuLayer = ( aLayer == B_Mask ) ? B_Cu : F_Cu; + VECTOR2I padSize = m_padStack.Size( cuLayer ); + VECTOR2I pad_margin; - pad_margin.x = margin.value_or( 0 ) + KiROUND( m_padStack.Size().x * mratio.value_or( 0 ) ); - pad_margin.y = margin.value_or( 0 ) + KiROUND( m_padStack.Size().y * mratio.value_or( 0 ) ); + pad_margin.x = margin.value_or( 0 ) + KiROUND( padSize.x * mratio.value_or( 0 ) ); + pad_margin.y = margin.value_or( 0 ) + KiROUND( padSize.y * mratio.value_or( 0 ) ); // ensure mask have a size always >= 0 - if( m_padStack.Shape() != PAD_SHAPE::CUSTOM ) + if( m_padStack.Shape( aLayer ) != PAD_SHAPE::CUSTOM ) { - if( pad_margin.x < -m_padStack.Size().x / 2 ) - pad_margin.x = -m_padStack.Size().x / 2; + if( pad_margin.x < -padSize.x / 2 ) + pad_margin.x = -padSize.x / 2; - if( pad_margin.y < -m_padStack.Size().y / 2 ) - pad_margin.y = -m_padStack.Size().y / 2; + if( pad_margin.y < -padSize.y / 2 ) + pad_margin.y = -padSize.y / 2; } return pad_margin; @@ -1155,7 +1235,8 @@ void PAD::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& { if( GetAttribute() == PAD_ATTRIB::SMD ) { - const std::shared_ptr& poly = PAD::GetEffectivePolygon(); + // TOOD(JE) padstacks + const std::shared_ptr& poly = GetEffectivePolygon( PADSTACK::ALL_LAYERS ); double area = poly->Area(); aList.emplace_back( _( "Area" ), aFrame->MessageTextFromValue( area, true, EDA_DATA_TYPE::AREA ) ); @@ -1180,17 +1261,21 @@ void PAD::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& case PAD_PROP::MECHANICAL: props += _( "Mechanical" ); break; } - aList.emplace_back( ShowPadShape(), props ); + // TODO(JE) How to show complex padstack info in the message panel + aList.emplace_back( ShowPadShape( PADSTACK::ALL_LAYERS ), props ); - if( ( GetShape() == PAD_SHAPE::CIRCLE || GetShape() == PAD_SHAPE::OVAL ) - && m_padStack.Size().x == m_padStack.Size().y ) + PAD_SHAPE padShape = GetShape( PADSTACK::ALL_LAYERS ); + VECTOR2I padSize = m_padStack.Size( PADSTACK::ALL_LAYERS ); + + if( ( padShape == PAD_SHAPE::CIRCLE || padShape == PAD_SHAPE::OVAL ) + && padSize.x == padSize.y ) { - aList.emplace_back( _( "Diameter" ), aFrame->MessageTextFromValue( m_padStack.Size().x ) ); + aList.emplace_back( _( "Diameter" ), aFrame->MessageTextFromValue( padSize.x ) ); } else { - aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( m_padStack.Size().x ) ); - aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( m_padStack.Size().y ) ); + aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( padSize.x ) ); + aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( padSize.y ) ); } EDA_ANGLE fp_orient = parentFootprint ? parentFootprint->GetOrientation() : ANGLE_0; @@ -1254,7 +1339,19 @@ bool PAD::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const if( delta.SquaredEuclideanNorm() > SEG::Square( boundingRadius ) ) return false; - return GetEffectivePolygon( ERROR_INSIDE )->Contains( aPosition, -1, aAccuracy ); + bool contains = false; + + Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID l ) + { + if( GetEffectivePolygon( l, ERROR_INSIDE )->Contains( aPosition, -1, aAccuracy ) ) + { + contains = true; + return; + } + } ); + + return contains; } @@ -1277,25 +1374,38 @@ bool PAD::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const if( !arect.Intersects( bbox ) ) return false; - const std::shared_ptr& poly = GetEffectivePolygon( ERROR_INSIDE ); + bool hit = false; - int count = poly->TotalVertices(); + Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + const std::shared_ptr& poly = + GetEffectivePolygon( aLayer, ERROR_INSIDE ); - for( int ii = 0; ii < count; ii++ ) - { - VECTOR2I vertex = poly->CVertex( ii ); - VECTOR2I vertexNext = poly->CVertex( ( ii + 1 ) % count ); + int count = poly->TotalVertices(); - // Test if the point is within aRect - if( arect.Contains( vertex ) ) - return true; + for( int ii = 0; ii < count; ii++ ) + { + VECTOR2I vertex = poly->CVertex( ii ); + VECTOR2I vertexNext = poly->CVertex( ( ii + 1 ) % count ); - // Test if this edge intersects aRect - if( arect.Intersects( vertex, vertexNext ) ) - return true; - } + // Test if the point is within aRect + if( arect.Contains( vertex ) ) + { + hit = true; + break; + } - return false; + // Test if this edge intersects aRect + if( arect.Intersects( vertex, vertexNext ) ) + { + hit = true; + break; + } + } + } ); + + return hit; } } @@ -1304,76 +1414,14 @@ int PAD::Compare( const PAD* aPadRef, const PAD* aPadCmp ) { int diff; - // TODO(JE) move padstack comparision into PADSTACK - - if( ( diff = static_cast( aPadRef->GetShape() ) - - static_cast( aPadCmp->GetShape() ) ) != 0 ) - return diff; - if( ( diff = static_cast( aPadRef->m_attribute ) - static_cast( aPadCmp->m_attribute ) ) != 0 ) return diff; - if( ( diff = static_cast( aPadRef->GetDrillShape() ) - - static_cast( aPadCmp->GetDrillShape() ) ) != 0 ) - return diff; - - if( ( diff = aPadRef->Padstack().Drill().size.x - aPadCmp->Padstack().Drill().size.x ) != 0 ) - return diff; - - if( ( diff = aPadRef->Padstack().Drill().size.y - aPadCmp->Padstack().Drill().size.y ) != 0 ) - return diff; - - if( ( diff = aPadRef->m_padStack.Size().x - aPadCmp->m_padStack.Size().x ) != 0 ) - return diff; - - if( ( diff = aPadRef->m_padStack.Size().y - aPadCmp->m_padStack.Size().y ) != 0 ) - return diff; - - if( ( diff = aPadRef->m_padStack.Offset().x - aPadCmp->m_padStack.Offset().x ) != 0 ) - return diff; - - if( ( diff = aPadRef->m_padStack.Offset().y - aPadCmp->m_padStack.Offset().y ) != 0 ) - return diff; - - if( ( diff = aPadRef->m_padStack.TrapezoidDeltaSize().x - - aPadCmp->m_padStack.TrapezoidDeltaSize().x ) - != 0 ) - { - return diff; - } - - if( ( diff = aPadRef->m_padStack.TrapezoidDeltaSize().y - - aPadCmp->m_padStack.TrapezoidDeltaSize().y ) - != 0 ) - { - return diff; - } - - if( ( diff = aPadRef->m_padStack.RoundRectRadiusRatio() - - aPadCmp->m_padStack.RoundRectRadiusRatio() ) - != 0 ) - { - return diff; - } - - if( ( diff = aPadRef->m_padStack.ChamferPositions() - aPadCmp->m_padStack.ChamferPositions() ) != 0 ) - return diff; - - if( ( diff = aPadRef->m_padStack.ChamferRatio() - aPadCmp->m_padStack.ChamferRatio() ) != 0 ) - return diff; - - if( ( diff = static_cast( aPadRef->m_padStack.Primitives().size() ) - - static_cast( aPadCmp->m_padStack.Primitives().size() ) ) != 0 ) - return diff; - - // @todo: Compare custom pad primitives for pads that have the same number of primitives - // here. Currently there is no compare function for PCB_SHAPE objects. - // Dick: specctra_export needs this // Lorenzo: gencad also needs it to implement padstacks! - return aPadRef->GetLayerSet().compare( aPadCmp->GetLayerSet() ); + return PADSTACK::Compare( &aPadRef->Padstack(), &aPadCmp->Padstack() ); } @@ -1386,9 +1434,9 @@ void PAD::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle ) } -wxString PAD::ShowPadShape() const +wxString PAD::ShowPadShape( PCB_LAYER_ID aLayer ) const { - switch( GetShape() ) + switch( GetShape( aLayer ) ) { case PAD_SHAPE::CIRCLE: return _( "Circle" ); case PAD_SHAPE::OVAL: return _( "Oval" ); @@ -1532,15 +1580,19 @@ void PAD::ViewGetLayers( int aLayers[], int& aCount ) const if( m_attribute == PAD_ATTRIB::NPTH ) aLayers[aCount++] = LAYER_NON_PLATEDHOLES; - if( IsOnLayer( F_Cu ) && IsOnLayer( B_Cu ) ) + LSET cuLayers = ( m_padStack.LayerSet() & LSET::AllCuMask() ); + + if( cuLayers.count() > 1 ) { // Multi layer pad - aLayers[aCount++] = LAYER_PADS_TH; + for( PCB_LAYER_ID layer : cuLayers.Seq() ) + aLayers[aCount++] = layer; + aLayers[aCount++] = LAYER_PAD_NETNAMES; } else if( IsOnLayer( F_Cu ) ) { - aLayers[aCount++] = LAYER_PADS_SMD_FR; + aLayers[aCount++] = F_Cu; // Is this a PTH pad that has only front copper? If so, we need to also display the // net name on the PTH netname layer so that it isn't blocked by the drill hole. @@ -1551,7 +1603,7 @@ void PAD::ViewGetLayers( int aLayers[], int& aCount ) const } else if( IsOnLayer( B_Cu ) ) { - aLayers[aCount++] = LAYER_PADS_SMD_BK; + aLayers[aCount++] = B_Cu; // Is this a PTH pad that has only back copper? If so, we need to also display the // net name on the PTH netname layer so that it isn't blocked by the drill hole. @@ -1560,16 +1612,6 @@ void PAD::ViewGetLayers( int aLayers[], int& aCount ) const else aLayers[aCount++] = LAYER_PAD_BK_NETNAMES; } - else - { - // Internal layers only. (Not yet supported in GUI, but is being used by Python - // footprint generators and will be needed anyway once pad stacks are supported.) - for ( int internal = In1_Cu; internal < In30_Cu; ++internal ) - { - if( IsOnLayer( (PCB_LAYER_ID) internal ) ) - aLayers[aCount++] = internal; - } - } // Check non-copper layers. This list should include all the layers that the // footprint editor allows a pad to be placed on. @@ -1603,16 +1645,18 @@ double PAD::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const return HIDE; } + const PCB_LAYER_ID& pcbLayer = static_cast( aLayer ); + if( !IsFlipped() && !aView->IsLayerVisible( LAYER_FOOTPRINTS_FR ) ) return HIDE; if( IsFlipped() && !aView->IsLayerVisible( LAYER_FOOTPRINTS_BK ) ) return HIDE; - if( IsFrontLayer( (PCB_LAYER_ID) aLayer ) && !aView->IsLayerVisible( LAYER_PADS_SMD_FR ) ) + if( IsFrontLayer( pcbLayer ) && !aView->IsLayerVisible( LAYER_PADS_SMD_FR ) ) return HIDE; - if( IsBackLayer( (PCB_LAYER_ID) aLayer ) && !aView->IsLayerVisible( LAYER_PADS_SMD_BK ) ) + if( IsBackLayer( pcbLayer ) && !aView->IsLayerVisible( LAYER_PADS_SMD_BK ) ) return HIDE; LSET visible = board->GetVisibleLayers() & board->GetEnabledLayers(); @@ -1648,8 +1692,8 @@ double PAD::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const return ( double ) pcbIUScale.mmToIU( 5 ) / divisor; } - VECTOR2L padSize = - GetShape() != PAD_SHAPE::CUSTOM ? VECTOR2L( GetSize() ) : GetBoundingBox().GetSize(); + VECTOR2L padSize = GetShape( pcbLayer ) != PAD_SHAPE::CUSTOM + ? VECTOR2L( GetSize( pcbLayer ) ) : GetBoundingBox().GetSize(); int64_t minSide = std::min( padSize.x, padSize.y ); @@ -1663,8 +1707,19 @@ double PAD::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const const BOX2I PAD::ViewBBox() const { // Bounding box includes soldermask too. Remember mask and/or paste margins can be < 0 - int solderMaskMargin = std::max( GetSolderMaskExpansion(), 0 ); - VECTOR2I solderPasteMargin = VECTOR2D( GetSolderPasteMargin() ); + int solderMaskMargin = 0; + VECTOR2I solderPasteMargin; + + Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + solderMaskMargin = std::max( solderMaskMargin, + std::max( GetSolderMaskExpansion( aLayer ), 0 ) ); + VECTOR2I layerMargin = GetSolderPasteMargin( aLayer ); + solderPasteMargin.x = std::max( solderPasteMargin.x, layerMargin.x ); + solderPasteMargin.y = std::max( solderPasteMargin.y, layerMargin.y ); + } ); + BOX2I bbox = GetBoundingBox(); int clearance = 0; @@ -1687,7 +1742,6 @@ const BOX2I PAD::ViewBBox() const void PAD::ImportSettingsFrom( const PAD& aMasterPad ) { SetPadstack( aMasterPad.Padstack() ); - SetShape( aMasterPad.GetShape() ); // Layer Set should be updated before calling SetAttribute() SetLayerSet( aMasterPad.GetLayerSet() ); SetAttribute( aMasterPad.GetAttribute() ); @@ -1717,29 +1771,13 @@ void PAD::ImportSettingsFrom( const PAD& aMasterPad ) SetOrientation( pad_rot ); - SetSize( aMasterPad.GetSize() ); - SetDelta( VECTOR2I( 0, 0 ) ); - SetOffset( aMasterPad.GetOffset() ); - SetDrillSize( aMasterPad.GetDrillSize() ); - SetDrillShape( aMasterPad.GetDrillShape() ); - SetRoundRectRadiusRatio( aMasterPad.GetRoundRectRadiusRatio() ); - SetChamferRectRatio( aMasterPad.GetChamferRectRatio() ); - SetChamferPositions( aMasterPad.GetChamferPositions() ); - - switch( aMasterPad.GetShape() ) - { - case PAD_SHAPE::TRAPEZOID: - SetDelta( aMasterPad.GetDelta() ); - break; - - case PAD_SHAPE::CIRCLE: - // ensure size.y == size.x - SetSize( VECTOR2I( GetSize().x, GetSize().x ) ); - break; - - default: - ; - } + Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + if( aMasterPad.GetShape( aLayer ) == PAD_SHAPE::CIRCLE ) + SetSize( F_Cu, VECTOR2I( GetSize( PADSTACK::ALL_LAYERS ).x, + GetSize( PADSTACK::ALL_LAYERS ).x ) ); + } ); switch( aMasterPad.GetAttribute() ) { @@ -1768,10 +1806,6 @@ void PAD::ImportSettingsFrom( const PAD& aMasterPad ) m_teardropParams = aMasterPad.m_teardropParams; - // Add or remove custom pad shapes: - ReplacePrimitives( aMasterPad.GetPrimitives() ); - SetAnchorPadShape( aMasterPad.GetAnchorPadShape() ); - SetDirty(); } @@ -1805,23 +1839,25 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aMaxError, ERROR_LOC aErrorLoc, bool ignoreLineWidth ) const { wxASSERT_MSG( !ignoreLineWidth, wxT( "IgnoreLineWidth has no meaning for pads." ) ); + wxASSERT_MSG( aLayer != UNDEFINED_LAYER, + wxT( "UNDEFINED_LAYER is no longer allowed for PAD::TransformShapeToPolygon" ) ); // minimal segment count to approximate a circle to create the polygonal pad shape // This minimal value is mainly for very small pads, like SM0402. // Most of time pads are using the segment count given by aError value. const int pad_min_seg_per_circle_count = 16; - int dx = m_padStack.Size().x / 2; - int dy = m_padStack.Size().y / 2; + int dx = m_padStack.Size( aLayer ).x / 2; + int dy = m_padStack.Size( aLayer ).y / 2; - VECTOR2I padShapePos = ShapePos(); // Note: for pad having a shape offset, the pad - // position is NOT the shape position + VECTOR2I padShapePos = ShapePos( aLayer ); // Note: for pad having a shape offset, the pad + // position is NOT the shape position - switch( GetShape() ) + switch( PAD_SHAPE shape = GetShape( aLayer ) ) { case PAD_SHAPE::CIRCLE: case PAD_SHAPE::OVAL: // Note: dx == dy is not guaranteed for circle pads in legacy boards - if( dx == dy || ( GetShape() == PAD_SHAPE::CIRCLE ) ) + if( dx == dy || ( shape == PAD_SHAPE::CIRCLE ) ) { TransformCircleToPolygon( aBuffer, padShapePos, dx + aClearance, aMaxError, aErrorLoc, pad_min_seg_per_circle_count ); @@ -1843,12 +1879,13 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, case PAD_SHAPE::TRAPEZOID: case PAD_SHAPE::RECTANGLE: { - int ddx = GetShape() == PAD_SHAPE::TRAPEZOID ? m_padStack.TrapezoidDeltaSize().x / 2 : 0; - int ddy = GetShape() == PAD_SHAPE::TRAPEZOID ? m_padStack.TrapezoidDeltaSize().y / 2 : 0; + const VECTOR2I& trapDelta = m_padStack.TrapezoidDeltaSize( aLayer ); + int ddx = shape == PAD_SHAPE::TRAPEZOID ? trapDelta.x / 2 : 0; + int ddy = shape == PAD_SHAPE::TRAPEZOID ? trapDelta.y / 2 : 0; SHAPE_POLY_SET outline; - TransformTrapezoidToPolygon( outline, padShapePos, m_padStack.Size(), GetOrientation(), ddx, - ddy, aClearance, aMaxError, aErrorLoc ); + TransformTrapezoidToPolygon( outline, padShapePos, m_padStack.Size( aLayer ), + GetOrientation(), ddx, ddy, aClearance, aMaxError, aErrorLoc ); aBuffer.Append( outline ); break; } @@ -1856,13 +1893,13 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, case PAD_SHAPE::CHAMFERED_RECT: case PAD_SHAPE::ROUNDRECT: { - bool doChamfer = GetShape() == PAD_SHAPE::CHAMFERED_RECT; + bool doChamfer = shape == PAD_SHAPE::CHAMFERED_RECT; SHAPE_POLY_SET outline; - TransformRoundChamferedRectToPolygon( outline, padShapePos, m_padStack.Size(), - GetOrientation(), GetRoundRectCornerRadius(), - doChamfer ? GetChamferRectRatio() : 0, - doChamfer ? GetChamferPositions() : 0, + TransformRoundChamferedRectToPolygon( outline, padShapePos, m_padStack.Size( aLayer ), + GetOrientation(), GetRoundRectCornerRadius( aLayer ), + doChamfer ? GetChamferRectRatio( aLayer ) : 0, + doChamfer ? GetChamferPositions( aLayer ) : 0, aClearance, aMaxError, aErrorLoc ); aBuffer.Append( outline ); break; @@ -1871,7 +1908,7 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, case PAD_SHAPE::CUSTOM: { SHAPE_POLY_SET outline; - MergePrimitivesAsPolygon( &outline, aErrorLoc ); + MergePrimitivesAsPolygon( aLayer, &outline, aErrorLoc ); outline.Rotate( GetOrientation() ); outline.Move( VECTOR2I( padShapePos ) ); @@ -1899,7 +1936,7 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, default: wxFAIL_MSG( wxT( "PAD::TransformShapeToPolygon no implementation for " ) - + wxString( std::string( magic_enum::enum_name( GetShape() ) ) ) ); + + wxString( std::string( magic_enum::enum_name( shape ) ) ) ); break; } } @@ -1974,15 +2011,17 @@ std::vector PAD::Recombine( bool aIsDryRun, int maxError ) else layer = GetLayerSet().UIOrder().front(); + PAD_SHAPE origShape = GetShape( layer ); + // 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( !aIsDryRun && findNext( layer ) && origShape != PAD_SHAPE::CUSTOM ) { - if( GetShape() == PAD_SHAPE::CIRCLE || GetShape() == PAD_SHAPE::RECTANGLE ) + if( origShape == PAD_SHAPE::CIRCLE || origShape == PAD_SHAPE::RECTANGLE ) { // Use the existing pad as an anchor - SetAnchorPadShape( GetShape() ); - SetShape( PAD_SHAPE::CUSTOM ); + SetAnchorPadShape( layer, origShape ); + SetShape( layer, PAD_SHAPE::CUSTOM ); } else { @@ -1990,18 +2029,18 @@ std::vector PAD::Recombine( bool aIsDryRun, int maxError ) 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 ); + int minExtent = std::min( GetSize( layer ).x, GetSize( layer ).y ); + SetAnchorPadShape( layer, PAD_SHAPE::CIRCLE ); + SetSize( layer, VECTOR2I( minExtent, minExtent ) ); + SetShape( layer, 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->Move( - ShapePos() ); + shape->Move( - ShapePos( layer ) ); shape->Rotate( VECTOR2I( 0, 0 ), - GetOrientation() ); - AddPrimitive( shape ); + AddPrimitive( layer, shape ); } } @@ -2016,10 +2055,10 @@ std::vector PAD::Recombine( bool aIsDryRun, int maxError ) PCB_SHAPE* primitive = static_cast( fpShape->Duplicate() ); primitive->SetParent( nullptr ); - primitive->Move( - ShapePos() ); + primitive->Move( - ShapePos( layer ) ); primitive->Rotate( VECTOR2I( 0, 0 ), - GetOrientation() ); - AddPrimitive( primitive ); + AddPrimitive( layer, primitive ); } // See if there are other shapes that match and mark them for delete. (KiCad won't @@ -2045,121 +2084,13 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider, const std::function& aErrorHandler ) const { - VECTOR2I pad_size = GetSize(); - VECTOR2I drill_size = GetDrillSize(); - wxString msg; - - if( GetShape() == PAD_SHAPE::CUSTOM ) - pad_size = GetBoundingBox().GetSize(); - else if( pad_size.x <= 0 || ( pad_size.y <= 0 && GetShape() != PAD_SHAPE::CIRCLE ) ) - aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Pad must have a positive size)" ) ); - - // Test hole against pad shape - if( IsOnCopperLayer() && GetDrillSize().x > 0 ) - { - // Ensure the drill size can be handled in next calculations. - // Use min size = 4 IU to be able to build a polygon from a hole shape - const int min_drill_size = 4; - - if( GetDrillSizeX() <= min_drill_size || GetDrillSizeY() <= min_drill_size ) - { - msg.Printf( _( "(PTH pad hole size must be larger than %s)" ), - aUnitsProvider->StringFromValue( min_drill_size, true ) ); - aErrorHandler( DRCE_PADSTACK_INVALID, msg ); - } - - int maxError = GetBoard()->GetDesignSettings().m_MaxError; - SHAPE_POLY_SET padOutline; - - TransformShapeToPolygon( padOutline, UNDEFINED_LAYER, 0, maxError, ERROR_INSIDE ); - - if( GetAttribute() == PAD_ATTRIB::PTH ) - { - // Test if there is copper area outside hole - std::shared_ptr hole = GetEffectiveHoleShape(); - SHAPE_POLY_SET holeOutline; - - TransformOvalToPolygon( holeOutline, hole->GetSeg().A, hole->GetSeg().B, - hole->GetWidth(), ARC_HIGH_DEF, ERROR_OUTSIDE ); - - SHAPE_POLY_SET copper = padOutline; - copper.BooleanSubtract( holeOutline, SHAPE_POLY_SET::POLYGON_MODE::PM_FAST ); - - if( copper.IsEmpty() ) - { - aErrorHandler( DRCE_PADSTACK, _( "(PTH pad hole leaves no copper)" ) ); - } - else - { - // Test if the pad hole is fully inside the copper area - holeOutline.BooleanSubtract( padOutline, SHAPE_POLY_SET::POLYGON_MODE::PM_FAST ); - - if( !holeOutline.IsEmpty() ) - aErrorHandler( DRCE_PADSTACK, _( "(PTH pad hole non fully inside copper)" ) ); - } - } - else - { - // Test only if the pad hole's centre is inside the copper area - if( !padOutline.Collide( GetPosition() ) ) - aErrorHandler( DRCE_PADSTACK, _( "(pad hole not inside pad shape)" ) ); - } - } - - if( GetLocalClearance().value_or( 0 ) < 0 ) - aErrorHandler( DRCE_PADSTACK, _( "(negative local clearance values have no effect)" ) ); - - // Some pads need a negative solder mask clearance (mainly for BGA with small pads) - // However the negative solder mask clearance must not create negative mask size - // Therefore test for minimal acceptable negative value - std::optional solderMaskMargin = GetLocalSolderMaskMargin(); - - if( solderMaskMargin.has_value() && solderMaskMargin.value() < 0 ) - { - int absMargin = abs( solderMaskMargin.value() ); - - if( GetShape() == PAD_SHAPE::CUSTOM ) - { - for( const std::shared_ptr& shape : GetPrimitives() ) - { - BOX2I shapeBBox = shape->GetBoundingBox(); - - if( absMargin > shapeBBox.GetWidth() || absMargin > shapeBBox.GetHeight() ) - { - aErrorHandler( DRCE_PADSTACK, _( "(negative solder mask clearance is larger " - "than some shape primitives; results may be " - "surprising)" ) ); - - break; - } - } - } - else if( absMargin > pad_size.x || absMargin > pad_size.y ) - { - aErrorHandler( DRCE_PADSTACK, _( "(negative solder mask clearance is larger than pad; " - "no solder mask will be generated)" ) ); - } - } - - // Some pads need a positive solder paste clearance (mainly for BGA with small pads) - // However, a positive value can create issues if the resulting shape is too big. - // (like a solder paste creating a solder paste area on a neighbor pad or on the solder mask) - // So we could ask for user to confirm the choice - // For now we just check for disappearing paste - wxSize paste_size; - int paste_margin = GetLocalSolderPasteMargin().value_or( 0 ); - double paste_ratio = GetLocalSolderPasteMarginRatio().value_or( 0 ); - - paste_size.x = pad_size.x + paste_margin + KiROUND( pad_size.x * paste_ratio ); - paste_size.y = pad_size.y + paste_margin + KiROUND( pad_size.y * paste_ratio ); - - if( paste_size.x <= 0 || paste_size.y <= 0 ) - { - aErrorHandler( DRCE_PADSTACK, _( "(negative solder paste margin is larger than pad; " - "no solder paste mask will be generated)" ) ); - } + Padstack().ForEachUniqueLayer( [&]( PCB_LAYER_ID aLayer ) + { + doCheckPad( aLayer, aUnitsProvider, aErrorHandler ); + } ); LSET padlayers_mask = GetLayerSet(); + VECTOR2I drill_size = GetDrillSize(); if( padlayers_mask.none() ) aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Pad has no layer)" ) ); @@ -2176,6 +2107,27 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider, } } + if( ( GetProperty() == PAD_PROP::FIDUCIAL_GLBL || GetProperty() == PAD_PROP::FIDUCIAL_LOCAL ) + && GetAttribute() == PAD_ATTRIB::NPTH ) + { + aErrorHandler( DRCE_PADSTACK, _( "('fiducial' property makes no sense on NPTH pads)" ) ); + } + + if( GetProperty() == PAD_PROP::TESTPOINT && GetAttribute() == PAD_ATTRIB::NPTH ) + aErrorHandler( DRCE_PADSTACK, _( "('testpoint' property makes no sense on NPTH pads)" ) ); + + if( GetProperty() == PAD_PROP::HEATSINK && GetAttribute() == PAD_ATTRIB::NPTH ) + aErrorHandler( DRCE_PADSTACK, _( "('heatsink' property makes no sense of NPTH pads)" ) ); + + if( GetProperty() == PAD_PROP::CASTELLATED && GetAttribute() != PAD_ATTRIB::PTH ) + aErrorHandler( DRCE_PADSTACK, _( "('castellated' property is for PTH pads)" ) ); + + if( GetProperty() == PAD_PROP::BGA && GetAttribute() != PAD_ATTRIB::SMD ) + aErrorHandler( DRCE_PADSTACK, _( "('BGA' property is for SMD pads)" ) ); + + if( GetProperty() == PAD_PROP::MECHANICAL && GetAttribute() != PAD_ATTRIB::PTH ) + aErrorHandler( DRCE_PADSTACK, _( "('mechanical' property is for PTH pads)" ) ); + switch( GetAttribute() ) { case PAD_ATTRIB::NPTH: // Not plated, but through hole, a hole is expected @@ -2240,58 +2192,157 @@ void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider, break; } } +} - if( ( GetProperty() == PAD_PROP::FIDUCIAL_GLBL || GetProperty() == PAD_PROP::FIDUCIAL_LOCAL ) - && GetAttribute() == PAD_ATTRIB::NPTH ) + +void PAD::doCheckPad( PCB_LAYER_ID aLayer, UNITS_PROVIDER* aUnitsProvider, + const std::function& aErrorHandler ) const +{ + VECTOR2I drill_size = GetDrillSize(); + wxString msg; + + VECTOR2I pad_size = GetSize( aLayer ); + + if( GetShape( aLayer ) == PAD_SHAPE::CUSTOM ) + pad_size = GetBoundingBox().GetSize(); + else if( pad_size.x <= 0 || ( pad_size.y <= 0 && GetShape( aLayer ) != PAD_SHAPE::CIRCLE ) ) + aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Pad must have a positive size)" ) ); + + // Test hole against pad shape + if( IsOnCopperLayer() && GetDrillSize().x > 0 ) { - aErrorHandler( DRCE_PADSTACK, _( "('fiducial' property makes no sense on NPTH pads)" ) ); + // Ensure the drill size can be handled in next calculations. + // Use min size = 4 IU to be able to build a polygon from a hole shape + const int min_drill_size = 4; + + if( GetDrillSizeX() <= min_drill_size || GetDrillSizeY() <= min_drill_size ) + { + msg.Printf( _( "(PTH pad hole size must be larger than %s)" ), + aUnitsProvider->StringFromValue( min_drill_size, true ) ); + aErrorHandler( DRCE_PADSTACK_INVALID, msg ); + } + + int maxError = GetBoard()->GetDesignSettings().m_MaxError; + SHAPE_POLY_SET padOutline; + + TransformShapeToPolygon( padOutline, aLayer, 0, maxError, ERROR_INSIDE ); + + if( GetAttribute() == PAD_ATTRIB::PTH ) + { + // Test if there is copper area outside hole + std::shared_ptr hole = GetEffectiveHoleShape(); + SHAPE_POLY_SET holeOutline; + + TransformOvalToPolygon( holeOutline, hole->GetSeg().A, hole->GetSeg().B, + hole->GetWidth(), ARC_HIGH_DEF, ERROR_OUTSIDE ); + + SHAPE_POLY_SET copper = padOutline; + copper.BooleanSubtract( holeOutline, SHAPE_POLY_SET::POLYGON_MODE::PM_FAST ); + + if( copper.IsEmpty() ) + { + aErrorHandler( DRCE_PADSTACK, _( "(PTH pad hole leaves no copper)" ) ); + } + else + { + // Test if the pad hole is fully inside the copper area + holeOutline.BooleanSubtract( padOutline, SHAPE_POLY_SET::POLYGON_MODE::PM_FAST ); + + if( !holeOutline.IsEmpty() ) + aErrorHandler( DRCE_PADSTACK, _( "(PTH pad hole non fully inside copper)" ) ); + } + } + else + { + // Test only if the pad hole's centre is inside the copper area + if( !padOutline.Collide( GetPosition() ) ) + aErrorHandler( DRCE_PADSTACK, _( "(pad hole not inside pad shape)" ) ); + } } - if( GetProperty() == PAD_PROP::TESTPOINT && GetAttribute() == PAD_ATTRIB::NPTH ) - aErrorHandler( DRCE_PADSTACK, _( "('testpoint' property makes no sense on NPTH pads)" ) ); + if( GetLocalClearance().value_or( 0 ) < 0 ) + aErrorHandler( DRCE_PADSTACK, _( "(negative local clearance values have no effect)" ) ); - if( GetProperty() == PAD_PROP::HEATSINK && GetAttribute() == PAD_ATTRIB::NPTH ) - aErrorHandler( DRCE_PADSTACK, _( "('heatsink' property makes no sense of NPTH pads)" ) ); + // Some pads need a negative solder mask clearance (mainly for BGA with small pads) + // However the negative solder mask clearance must not create negative mask size + // Therefore test for minimal acceptable negative value + std::optional solderMaskMargin = GetLocalSolderMaskMargin(); - if( GetProperty() == PAD_PROP::CASTELLATED && GetAttribute() != PAD_ATTRIB::PTH ) - aErrorHandler( DRCE_PADSTACK, _( "('castellated' property is for PTH pads)" ) ); - - if( GetProperty() == PAD_PROP::BGA && GetAttribute() != PAD_ATTRIB::SMD ) - aErrorHandler( DRCE_PADSTACK, _( "('BGA' property is for SMD pads)" ) ); - - if( GetProperty() == PAD_PROP::MECHANICAL && GetAttribute() != PAD_ATTRIB::PTH ) - aErrorHandler( DRCE_PADSTACK, _( "('mechanical' property is for PTH pads)" ) ); - - if( GetShape() == PAD_SHAPE::ROUNDRECT ) + if( solderMaskMargin.has_value() && solderMaskMargin.value() < 0 ) { - if( GetRoundRectRadiusRatio() < 0.0 ) + int absMargin = abs( solderMaskMargin.value() ); + + if( GetShape( aLayer ) == PAD_SHAPE::CUSTOM ) + { + for( const std::shared_ptr& shape : GetPrimitives( aLayer ) ) + { + BOX2I shapeBBox = shape->GetBoundingBox(); + + if( absMargin > shapeBBox.GetWidth() || absMargin > shapeBBox.GetHeight() ) + { + aErrorHandler( DRCE_PADSTACK, _( "(negative solder mask clearance is larger " + "than some shape primitives; results may be " + "surprising)" ) ); + + break; + } + } + } + else if( absMargin > pad_size.x || absMargin > pad_size.y ) + { + aErrorHandler( DRCE_PADSTACK, _( "(negative solder mask clearance is larger than pad; " + "no solder mask will be generated)" ) ); + } + } + + // Some pads need a positive solder paste clearance (mainly for BGA with small pads) + // However, a positive value can create issues if the resulting shape is too big. + // (like a solder paste creating a solder paste area on a neighbor pad or on the solder mask) + // So we could ask for user to confirm the choice + // For now we just check for disappearing paste + wxSize paste_size; + int paste_margin = GetLocalSolderPasteMargin().value_or( 0 ); + double paste_ratio = GetLocalSolderPasteMarginRatio().value_or( 0 ); + + paste_size.x = pad_size.x + paste_margin + KiROUND( pad_size.x * paste_ratio ); + paste_size.y = pad_size.y + paste_margin + KiROUND( pad_size.y * paste_ratio ); + + if( paste_size.x <= 0 || paste_size.y <= 0 ) + { + aErrorHandler( DRCE_PADSTACK, _( "(negative solder paste margin is larger than pad; " + "no solder paste mask will be generated)" ) ); + } + + if( GetShape( aLayer ) == PAD_SHAPE::ROUNDRECT ) + { + if( GetRoundRectRadiusRatio( aLayer ) < 0.0 ) aErrorHandler( DRCE_PADSTACK_INVALID, _( "(negative corner radius is not allowed)" ) ); - else if( GetRoundRectRadiusRatio() > 50.0 ) + else if( GetRoundRectRadiusRatio( aLayer ) > 50.0 ) aErrorHandler( DRCE_PADSTACK, _( "(corner size will make pad circular)" ) ); } - else if( GetShape() == PAD_SHAPE::CHAMFERED_RECT ) + else if( GetShape( aLayer ) == PAD_SHAPE::CHAMFERED_RECT ) { - if( GetChamferRectRatio() < 0.0 ) + if( GetChamferRectRatio( aLayer ) < 0.0 ) aErrorHandler( DRCE_PADSTACK_INVALID, _( "(negative corner chamfer is not allowed)" ) ); - else if( GetChamferRectRatio() > 50.0 ) + else if( GetChamferRectRatio( aLayer ) > 50.0 ) aErrorHandler( DRCE_PADSTACK_INVALID, _( "(corner chamfer is too large)" ) ); } - else if( GetShape() == PAD_SHAPE::TRAPEZOID ) + else if( GetShape( aLayer ) == PAD_SHAPE::TRAPEZOID ) { - if( ( GetDelta().x < 0 && GetDelta().x < -GetSize().y ) - || ( GetDelta().x > 0 && GetDelta().x > GetSize().y ) - || ( GetDelta().y < 0 && GetDelta().y < -GetSize().x ) - || ( GetDelta().y > 0 && GetDelta().y > GetSize().x ) ) + if( ( GetDelta( aLayer ).x < 0 && GetDelta( aLayer ).x < -GetSize( aLayer ).y ) + || ( GetDelta( aLayer ).x > 0 && GetDelta( aLayer ).x > GetSize( aLayer ).y ) + || ( GetDelta( aLayer ).y < 0 && GetDelta( aLayer ).y < -GetSize( aLayer ).x ) + || ( GetDelta( aLayer ).y > 0 && GetDelta( aLayer ).y > GetSize( aLayer ).x ) ) { aErrorHandler( DRCE_PADSTACK_INVALID, _( "(trapezoid delta is too large)" ) ); } } - // PADSTACKS TODO: this will need to check each layer in the pad... - if( GetShape() == PAD_SHAPE::CUSTOM ) + if( GetShape( aLayer ) == PAD_SHAPE::CUSTOM ) { SHAPE_POLY_SET mergedPolygon; - MergePrimitivesAsPolygon( &mergedPolygon ); + MergePrimitivesAsPolygon( aLayer, &mergedPolygon ); if( mergedPolygon.OutlineCount() > 1 ) aErrorHandler( DRCE_PADSTACK_INVALID, _( "(custom pad shape must resolve to a single polygon)" ) ); @@ -2315,7 +2366,7 @@ bool PAD::operator==( const BOARD_ITEM& aBoardItem ) const bool PAD::operator==( const PAD& aOther ) const { - if( GetShape() != aOther.GetShape() ) + if( Padstack() != aOther.Padstack() ) return false; if( GetPosition() != aOther.GetPosition() ) @@ -2324,75 +2375,6 @@ bool PAD::operator==( const PAD& aOther ) const if( GetAttribute() != aOther.GetAttribute() ) return false; - if( GetSize() != aOther.GetSize() ) - return false; - - if( GetOffset() != aOther.GetOffset() ) - return false; - - if( GetDrillSize() != aOther.GetDrillSize() ) - return false; - - if( GetDrillShape() != aOther.GetDrillShape() ) - return false; - - if( GetRoundRectRadiusRatio() != aOther.GetRoundRectRadiusRatio() ) - return false; - - if( GetChamferRectRatio() != aOther.GetChamferRectRatio() ) - return false; - - if( GetChamferPositions() != aOther.GetChamferPositions() ) - return false; - - if( GetOrientation() != aOther.GetOrientation() ) - return false; - - if( GetLocalZoneConnection() != aOther.GetLocalZoneConnection() ) - return false; - - if( GetThermalSpokeWidth() != aOther.GetThermalSpokeWidth() ) - return false; - - if( GetThermalSpokeAngle() != aOther.GetThermalSpokeAngle() ) - return false; - - if( GetThermalGap() != aOther.GetThermalGap() ) - return false; - - if( GetCustomShapeInZoneOpt() != aOther.GetCustomShapeInZoneOpt() ) - return false; - - if( GetPrimitives().size() != aOther.GetPrimitives().size() ) - return false; - - for( size_t ii = 0; ii < GetPrimitives().size(); ii++ ) - { - if( *GetPrimitives()[ii] != *aOther.GetPrimitives()[ii] ) - return false; - } - - if( GetAnchorPadShape() != aOther.GetAnchorPadShape() ) - return false; - - if( GetLocalClearance() != aOther.GetLocalClearance() ) - return false; - - if( GetLocalSolderMaskMargin() != aOther.GetLocalSolderMaskMargin() ) - return false; - - if( GetLocalSolderPasteMargin() != aOther.GetLocalSolderPasteMargin() ) - return false; - - if( GetLocalSolderPasteMarginRatio() != aOther.GetLocalSolderPasteMarginRatio() ) - return false; - - if( GetLocalSpokeWidthOverride() != aOther.GetLocalSpokeWidthOverride() ) - return false; - - if( GetLayerSet() != aOther.GetLayerSet() ) - return false; - return true; } @@ -2409,77 +2391,13 @@ double PAD::Similarity( const BOARD_ITEM& aOther ) const double similarity = 1.0; - if( GetShape() != other.GetShape() ) - similarity *= 0.9; - if( GetPosition() != other.GetPosition() ) similarity *= 0.9; if( GetAttribute() != other.GetAttribute() ) similarity *= 0.9; - if( GetSize() != other.GetSize() ) - similarity *= 0.9; - - if( GetOffset() != other.GetOffset() ) - similarity *= 0.9; - - if( GetDrillSize() != other.GetDrillSize() ) - similarity *= 0.9; - - if( GetDrillShape() != other.GetDrillShape() ) - similarity *= 0.9; - - if( GetRoundRectRadiusRatio() != other.GetRoundRectRadiusRatio() ) - similarity *= 0.9; - - if( GetChamferRectRatio() != other.GetChamferRectRatio() ) - similarity *= 0.9; - - if( GetChamferPositions() != other.GetChamferPositions() ) - similarity *= 0.9; - - if( GetOrientation() != other.GetOrientation() ) - similarity *= 0.9; - - if( GetLocalZoneConnection() != other.GetLocalZoneConnection() ) - similarity *= 0.9; - - if( GetThermalSpokeWidth() != other.GetThermalSpokeWidth() ) - similarity *= 0.9; - - if( GetThermalSpokeAngle() != other.GetThermalSpokeAngle() ) - similarity *= 0.9; - - if( GetThermalGap() != other.GetThermalGap() ) - similarity *= 0.9; - - if( GetCustomShapeInZoneOpt() != other.GetCustomShapeInZoneOpt() ) - similarity *= 0.9; - - if( GetPrimitives().size() != other.GetPrimitives().size() ) - similarity *= 0.9; - - if( GetAnchorPadShape() != other.GetAnchorPadShape() ) - similarity *= 0.9; - - if( GetLocalClearance() != other.GetLocalClearance() ) - similarity *= 0.9; - - if( GetLocalSolderMaskMargin() != other.GetLocalSolderMaskMargin() ) - similarity *= 0.9; - - if( GetLocalSolderPasteMargin() != other.GetLocalSolderPasteMargin() ) - similarity *= 0.9; - - if( GetLocalSolderPasteMarginRatio() != other.GetLocalSolderPasteMarginRatio() ) - similarity *= 0.9; - - if( GetLocalSpokeWidthOverride() != other.GetLocalSpokeWidthOverride() ) - similarity *= 0.9; - - if( GetLayerSet() != other.GetLayerSet() ) - similarity *= 0.9; + similarity *= Padstack().Similarity( other.Padstack() ); return similarity; } @@ -2567,6 +2485,15 @@ static struct PAD_DESC return false; }; + auto hasNormalPadstack = + [=]( INSPECTABLE* aItem ) -> bool + { + if( PAD* pad = dynamic_cast( aItem ) ) + return pad->Padstack().Mode() == PADSTACK::MODE::NORMAL; + + return true; + }; + propMgr.OverrideAvailability( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Net" ), isCopperPad ); propMgr.OverrideAvailability( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ), @@ -2579,8 +2506,9 @@ static struct PAD_DESC propMgr.AddProperty( padType, groupPad ); auto shape = new PROPERTY_ENUM( _HKI( "Pad Shape" ), - &PAD::SetShape, &PAD::GetShape ); - propMgr.AddProperty( shape, groupPad ); + &PAD::SetFrontShape, &PAD::GetFrontShape ); + propMgr.AddProperty( shape, groupPad ) + .SetAvailableFunc( hasNormalPadstack ); auto padNumber = new PROPERTY( _HKI( "Pad Number" ), &PAD::SetNumber, &PAD::GetNumber ); @@ -2596,27 +2524,40 @@ static struct PAD_DESC propMgr.AddProperty( new PROPERTY( _HKI( "Size X" ), &PAD::SetSizeX, &PAD::GetSizeX, - PROPERTY_DISPLAY::PT_SIZE ), groupPad ); + PROPERTY_DISPLAY::PT_SIZE ), groupPad ) + .SetAvailableFunc( hasNormalPadstack ); propMgr.AddProperty( new PROPERTY( _HKI( "Size Y" ), &PAD::SetSizeY, &PAD::GetSizeY, PROPERTY_DISPLAY::PT_SIZE ), groupPad ) .SetAvailableFunc( [=]( INSPECTABLE* aItem ) -> bool { - // Circle pads have no usable y-size if( PAD* pad = dynamic_cast( aItem ) ) - return pad->GetShape() != PAD_SHAPE::CIRCLE; + { + // Custom padstacks can't have size modified through panel + if( pad->Padstack().Mode() != PADSTACK::MODE::NORMAL ) + return false; + + // Circle pads have no usable y-size + return pad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::CIRCLE; + } return true; } ); auto roundRadiusRatio = new PROPERTY( _HKI( "Corner Radius Ratio" ), - &PAD::SetRoundRectRadiusRatio, &PAD::GetRoundRectRadiusRatio ); + &PAD::SetFrontRoundRectRadiusRatio, &PAD::GetFrontRoundRectRadiusRatio ); roundRadiusRatio->SetAvailableFunc( [=]( INSPECTABLE* aItem ) -> bool { if( PAD* pad = dynamic_cast( aItem ) ) - return pad->GetShape() == PAD_SHAPE::ROUNDRECT; + { + // Custom padstacks can't have this property modified through panel + if( pad->Padstack().Mode() != PADSTACK::MODE::NORMAL ) + return false; + + return pad->GetShape( F_Cu ) == PAD_SHAPE::ROUNDRECT; + } return false; } ); diff --git a/pcbnew/pad.h b/pcbnew/pad.h index c71fea1ad4..4d4ab33f9a 100644 --- a/pcbnew/pad.h +++ b/pcbnew/pad.h @@ -181,16 +181,25 @@ public: /** * Set the new shape of this pad. */ - void SetShape( PAD_SHAPE aShape ) + void SetShape( PCB_LAYER_ID aLayer, PAD_SHAPE aShape ) { - m_padStack.SetShape( aShape ); + m_padStack.SetShape( aShape, aLayer ); SetDirty(); } /** * @return the shape of this pad. */ - PAD_SHAPE GetShape() const { return m_padStack.Shape(); } + PAD_SHAPE GetShape( PCB_LAYER_ID aLayer ) const { return m_padStack.Shape( aLayer ); } + + // Used for the properties panel, which does not support padstacks at the moment + void SetFrontShape( PAD_SHAPE aShape ) + { + m_padStack.SetShape( aShape, F_Cu ); + SetDirty(); + } + + PAD_SHAPE GetFrontShape() const { return m_padStack.Shape( F_Cu ); } void SetPosition( const VECTOR2I& aPos ) override { @@ -203,9 +212,9 @@ public: /** * @return the shape of the anchor pad shape, for custom shaped pads. */ - PAD_SHAPE GetAnchorPadShape() const + PAD_SHAPE GetAnchorPadShape( PCB_LAYER_ID aLayer ) const { - return m_padStack.AnchorShape(); + return m_padStack.AnchorShape( aLayer ); } /** @@ -232,11 +241,12 @@ public: * @param aShape is the shape of the anchor pad shape( currently, only #PAD_SHAPE::RECTANGLE or * #PAD_SHAPE::CIRCLE. */ - void SetAnchorPadShape( PAD_SHAPE aShape ) + void SetAnchorPadShape( PCB_LAYER_ID aLayer, PAD_SHAPE aShape ) { m_padStack.SetAnchorShape( aShape == PAD_SHAPE::RECTANGLE ? PAD_SHAPE::RECTANGLE - : PAD_SHAPE::CIRCLE ); + : PAD_SHAPE::CIRCLE, + aLayer ); SetDirty(); } @@ -248,20 +258,33 @@ public: void SetY( int y ) { m_pos.y = y; SetDirty(); } void SetX( int x ) { m_pos.x = x; SetDirty(); } - void SetSize( const VECTOR2I& aSize ) + void SetSize( PCB_LAYER_ID aLayer, const VECTOR2I& aSize ) { - m_padStack.Size() = aSize; + m_padStack.Size( aLayer ) = aSize; SetDirty(); } - const VECTOR2I& GetSize() const { return m_padStack.Size(); } + const VECTOR2I& GetSize( PCB_LAYER_ID aLayer ) const { return m_padStack.Size( aLayer ); } - void SetSizeX( const int aX ) { if( aX > 0 ) { m_padStack.Size().x = aX; SetDirty(); } } - int GetSizeX() const { return m_padStack.Size().x; } - void SetSizeY( const int aY ) { if( aY > 0 ) { m_padStack.Size().y = aY; SetDirty(); } } - int GetSizeY() const { return m_padStack.Size().y; } + // These accessors are for the properties panel, which does not have the ability to deal with + // custom padstacks where the properties can vary by layer. The properties should be disabled + // in the GUI when the padstack mode is set to anything other than NORMAL, but so that the code + // compiles, these are set up to work with the front layer (in other words, assume the mode is + // NORMAL, where F_Cu stores the whole padstack data) + void SetSizeX( const int aX ) { if( aX > 0 ) { m_padStack.Size( PADSTACK::ALL_LAYERS ).x = aX; SetDirty(); } } + int GetSizeX() const { return m_padStack.Size( PADSTACK::ALL_LAYERS ).x; } + void SetSizeY( const int aY ) { if( aY > 0 ) { m_padStack.Size( PADSTACK::ALL_LAYERS ).y = aY; SetDirty(); } } + int GetSizeY() const { return m_padStack.Size( PADSTACK::ALL_LAYERS ).y; } - void SetDelta( const VECTOR2I& aSize ) { m_padStack.TrapezoidDeltaSize() = aSize; SetDirty(); } - const VECTOR2I& GetDelta() const { return m_padStack.TrapezoidDeltaSize(); } + void SetDelta( PCB_LAYER_ID aLayer, const VECTOR2I& aSize ) + { + m_padStack.TrapezoidDeltaSize( aLayer ) = aSize; + SetDirty(); + } + + const VECTOR2I& GetDelta( PCB_LAYER_ID aLayer ) const + { + return m_padStack.TrapezoidDeltaSize( aLayer ); + } void SetDrillSize( const VECTOR2I& aSize ) { m_padStack.Drill().size = aSize; SetDirty(); } const VECTOR2I& GetDrillSize() const { return m_padStack.Drill().size; } @@ -270,8 +293,13 @@ public: void SetDrillSizeY( const int aY ) { m_padStack.Drill().size.y = aY; SetDirty(); } int GetDrillSizeY() const { return m_padStack.Drill().size.y; } - void SetOffset( const VECTOR2I& aOffset ) { m_padStack.Offset() = aOffset; SetDirty(); } - const VECTOR2I& GetOffset() const { return m_padStack.Offset(); } + void SetOffset( PCB_LAYER_ID aLayer, const VECTOR2I& aOffset ) + { + m_padStack.Offset( aLayer ) = aOffset; + SetDirty(); + } + + const VECTOR2I& GetOffset( PCB_LAYER_ID aLayer ) const { return m_padStack.Offset( aLayer ); } VECTOR2I GetCenter() const override { return GetPosition(); } @@ -290,34 +318,38 @@ public: * - a arc * - a bezier curve */ - void AddPrimitivePoly( const SHAPE_POLY_SET& aPoly, int aThickness, bool aFilled ); - void AddPrimitivePoly( const std::vector& aPoly, int aThickness, bool aFilled ); + void AddPrimitivePoly( PCB_LAYER_ID aLayer, const SHAPE_POLY_SET& aPoly, int aThickness, + bool aFilled ); + void AddPrimitivePoly( PCB_LAYER_ID aLayer, const std::vector& aPoly, int aThickness, + bool aFilled ); /** * Merge all basic shapes to a #SHAPE_POLY_SET. * * @note The results are relative to the pad position, orientation 0. * + * @param aLayer is the copper layer to merge shapes for * @param aMergedPolygon will store the final polygon * @param aErrorLoc is used when a circle (or arc) is approximated by segments * = ERROR_INSIDE to build a polygon inside the arc/circle (usual shape to raw/plot) * = ERROR_OUIDE to build a polygon outside the arc/circle * (for instance when building a clearance area) */ - void MergePrimitivesAsPolygon( SHAPE_POLY_SET* aMergedPolygon, + void MergePrimitivesAsPolygon( PCB_LAYER_ID aLayer, SHAPE_POLY_SET* aMergedPolygon, ERROR_LOC aErrorLoc = ERROR_INSIDE ) const; /** * Clear the basic shapes list. + * @param aLayer is the layer to clear, or UNDEFINED_LAYER to clear all layers */ - void DeletePrimitivesList(); + void DeletePrimitivesList( PCB_LAYER_ID aLayer = UNDEFINED_LAYER ); /** * Accessor to the basic shape list for custom-shaped pads. */ - const std::vector>& GetPrimitives() const + const std::vector>& GetPrimitives( PCB_LAYER_ID aLayer ) const { - return m_padStack.Primitives(); + return m_padStack.Primitives( aLayer ); } void Flip( const VECTOR2I& VECTOR2I, FLIP_DIRECTION aFlipDirection ) override; @@ -332,18 +364,20 @@ public: * Clear the current custom shape primitives list and import a new list. Copies the input, * which is not altered. */ - void ReplacePrimitives( const std::vector>& aPrimitivesList ); + void ReplacePrimitives( PCB_LAYER_ID aLayer, + const std::vector>& aPrimitivesList ); /** * Import a custom shape primitive list (composed of basic shapes) and add items to the * current list. Copies the input, which is not altered. */ - void AppendPrimitives( const std::vector>& aPrimitivesList ); + void AppendPrimitives( PCB_LAYER_ID aLayer, + const std::vector>& aPrimitivesList ); /** * Add item to the custom shape primitives list */ - void AddPrimitive( PCB_SHAPE* aPrimitive ); + void AddPrimitive( PCB_LAYER_ID aLayer, PCB_SHAPE* aPrimitive ); /** * Set the rotation angle of the pad. @@ -476,17 +510,17 @@ public: * polygon), but should never contain a SHAPE_POLY_SET (a complex polygon consisting of * multiple outlines and/or holes). * - * @param aLayer optional parameter allowing a caller to specify a particular layer (default - * is to return the pad's "natural" shape). + * @param aLayer determines which layer to query for shape * @param aFlash optional parameter allowing a caller to force the pad to be flashed (or not * flashed) on the current layer (default is to honour the pad's setting and * the current connections for the given layer). */ virtual std::shared_ptr - GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER, + GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING flashPTHPads = FLASHING::DEFAULT ) const override; - const std::shared_ptr& GetEffectivePolygon( ERROR_LOC aErrorLoc = ERROR_INSIDE ) const; + const std::shared_ptr& GetEffectivePolygon( PCB_LAYER_ID aLayer, + ERROR_LOC aErrorLoc = ERROR_INSIDE ) const; /** * Return a SHAPE_SEGMENT object representing the pad's hole. @@ -526,7 +560,7 @@ public: * 2 if 0, the parent footprint value * 3 if 0, the global value */ - int GetSolderMaskExpansion() const; + int GetSolderMaskExpansion( PCB_LAYER_ID aLayer ) const; /** * Usually < 0 (mask shape smaller than pad)because the margin can be dependent on the pad @@ -539,7 +573,7 @@ public: * * @return the margin for the solder mask layer. */ - VECTOR2I GetSolderPasteMargin() const; + VECTOR2I GetSolderPasteMargin( PCB_LAYER_ID aLayer ) const; ZONE_CONNECTION GetZoneConnectionOverrides( wxString* aSource = nullptr ) const; @@ -586,10 +620,10 @@ public: * * @return The radius of the rounded corners for this pad. */ - void SetRoundRectCornerRadius( double aRadius ); - int GetRoundRectCornerRadius() const; + void SetRoundRectCornerRadius( PCB_LAYER_ID aLayer, double aRadius ); + int GetRoundRectCornerRadius( PCB_LAYER_ID aLayer ) const; - VECTOR2I ShapePos() const; + VECTOR2I ShapePos( PCB_LAYER_ID aLayer ) const; /** * Has meaning only for rounded rectangle pads. @@ -597,8 +631,18 @@ public: * Set the ratio between the smaller X or Y size and the rounded corner radius. * Cannot be > 0.5; the normalized IPC-7351C value is 0.25 */ - void SetRoundRectRadiusRatio( double aRadiusScale ); - double GetRoundRectRadiusRatio() const { return m_padStack.RoundRectRadiusRatio(); } + void SetRoundRectRadiusRatio( PCB_LAYER_ID aLayer, double aRadiusScale ); + double GetRoundRectRadiusRatio( PCB_LAYER_ID aLayer ) const + { + return m_padStack.RoundRectRadiusRatio( aLayer ); + } + + // For properties panel, which only supports normal padstacks + void SetFrontRoundRectRadiusRatio( double aRadiusScale ); + double GetFrontRoundRectRadiusRatio() const + { + return m_padStack.RoundRectRadiusRatio( F_Cu ); + } /** * Has meaning only for chamfered rectangular pads. @@ -606,8 +650,11 @@ public: * Set the ratio between the smaller X or Y size and chamfered corner size. * Cannot be < 0.5. */ - void SetChamferRectRatio( double aChamferScale ); - double GetChamferRectRatio() const { return m_padStack.ChamferRatio(); } + void SetChamferRectRatio( PCB_LAYER_ID aLayer, double aChamferScale ); + double GetChamferRectRatio( PCB_LAYER_ID aLayer ) const + { + return m_padStack.ChamferRatio( aLayer ); + } /** * Has meaning only for chamfered rectangular pads. @@ -616,8 +663,15 @@ public: * * @param aPositions a bit-set of #RECT_CHAMFER_POSITIONS. */ - void SetChamferPositions( int aPositions ) { m_padStack.SetChamferPositions( aPositions ); } - int GetChamferPositions() const { return m_padStack.ChamferPositions(); } + void SetChamferPositions( PCB_LAYER_ID aLayer, int aPositions ) + { + m_padStack.SetChamferPositions( aPositions, aLayer ); + } + + int GetChamferPositions( PCB_LAYER_ID aLayer ) const + { + return m_padStack.ChamferPositions( aLayer ); + } /** * @return the netcode. @@ -773,7 +827,7 @@ public: /** * @return the GUI-appropriate name of the shape. */ - wxString ShowPadShape() const; + wxString ShowPadShape( PCB_LAYER_ID aLayer ) const; /** * @return the GUI-appropriate description of the pad type (attribute) : Std, SMD ... @@ -796,7 +850,7 @@ public: * Rebuild the effective shape cache (and bounding box and radius) for the pad and clears * the dirty bit. */ - void BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const; + void BuildEffectiveShapes() const; void BuildEffectivePolygon( ERROR_LOC aErrorLoc = ERROR_INSIDE ) const; virtual void ViewGetLayers( int aLayers[], int& aCount ) const override; @@ -828,9 +882,15 @@ protected: virtual void swapData( BOARD_ITEM* aImage ) override; private: - void addPadPrimitivesToPolygon( SHAPE_POLY_SET* aMergedPolygon, int aError, + const SHAPE_COMPOUND& buildEffectiveShape( PCB_LAYER_ID aLayer ) const; + + void addPadPrimitivesToPolygon( PCB_LAYER_ID aLayer, SHAPE_POLY_SET* aMergedPolygon, int aError, ERROR_LOC aErrorLoc ) const; + void doCheckPad( PCB_LAYER_ID aLayer, UNITS_PROVIDER* aUnitsProvider, + const std::function& aErrorHandler ) const; + private: wxString m_number; // Pad name (pin number in schematic) wxString m_pinFunction; // Pin name in schematic @@ -841,15 +901,17 @@ private: PADSTACK m_padStack; // Must be set to true to force rebuild shapes to draw (after geometry change for instance) + typedef std::map> LAYER_SHAPE_MAP; mutable bool m_shapesDirty; mutable std::mutex m_shapesBuildingLock; mutable BOX2I m_effectiveBoundingBox; - mutable std::shared_ptr m_effectiveShape; + mutable LAYER_SHAPE_MAP m_effectiveShapes; mutable std::shared_ptr m_effectiveHoleShape; + typedef std::map, 2>> LAYER_POLYGON_MAP; mutable bool m_polyDirty[2]; mutable std::mutex m_polyBuildingLock; - mutable std::shared_ptr m_effectivePolygon[2]; + mutable LAYER_POLYGON_MAP m_effectivePolygons; mutable int m_effectiveBoundingRadius; int m_subRatsnest; // Variable used to handle subnet (block) number in diff --git a/pcbnew/pad_custom_shape_functions.cpp b/pcbnew/pad_custom_shape_functions.cpp index 7d256f80b5..7b770fe008 100644 --- a/pcbnew/pad_custom_shape_functions.cpp +++ b/pcbnew/pad_custom_shape_functions.cpp @@ -37,7 +37,8 @@ * the shape is a polygon (can be with thick outline), segment, circle or arc */ -void PAD::AddPrimitivePoly( const SHAPE_POLY_SET& aPoly, int aThickness, bool aFilled ) +void PAD::AddPrimitivePoly( PCB_LAYER_ID aLayer, const SHAPE_POLY_SET& aPoly, int aThickness, + bool aFilled ) { // If aPoly has holes, convert it to a polygon with no holes. SHAPE_POLY_SET poly_no_hole; @@ -57,71 +58,85 @@ void PAD::AddPrimitivePoly( const SHAPE_POLY_SET& aPoly, int aThickness, bool aF item->SetPolyShape( poly_outline ); item->SetStroke( STROKE_PARAMS( aThickness, LINE_STYLE::SOLID ) ); item->SetParent( this ); - m_padStack.AddPrimitive( item ); + m_padStack.AddPrimitive( item, aLayer ); } SetDirty(); } -void PAD::AddPrimitivePoly( const std::vector& aPoly, int aThickness, bool aFilled ) +void PAD::AddPrimitivePoly( PCB_LAYER_ID aLayer, const std::vector& aPoly, int aThickness, + bool aFilled ) { PCB_SHAPE* item = new PCB_SHAPE( nullptr, SHAPE_T::POLY ); item->SetFilled( aFilled ); item->SetPolyPoints( aPoly ); item->SetStroke( STROKE_PARAMS( aThickness, LINE_STYLE::SOLID ) ); item->SetParent( this ); - m_padStack.AddPrimitive( item ); + m_padStack.AddPrimitive( item, aLayer ); SetDirty(); } -void PAD::ReplacePrimitives( const std::vector>& aPrimitivesList ) +void PAD::ReplacePrimitives( PCB_LAYER_ID aLayer, + const std::vector>& aPrimitivesList ) { // clear old list - DeletePrimitivesList(); + DeletePrimitivesList( aLayer ); // Import to the given shape list if( aPrimitivesList.size() ) - AppendPrimitives( aPrimitivesList ); + AppendPrimitives( aLayer, aPrimitivesList ); SetDirty(); } -void PAD::AppendPrimitives( const std::vector>& aPrimitivesList ) +void PAD::AppendPrimitives( PCB_LAYER_ID aLayer, + const std::vector>& aPrimitivesList ) { // Add duplicates of aPrimitivesList to the pad primitives list: for( const std::shared_ptr& prim : aPrimitivesList ) - AddPrimitive( new PCB_SHAPE( *prim ) ); + AddPrimitive( aLayer, new PCB_SHAPE( *prim ) ); SetDirty(); } -void PAD::AddPrimitive( PCB_SHAPE* aPrimitive ) +void PAD::AddPrimitive( PCB_LAYER_ID aLayer, PCB_SHAPE* aPrimitive ) { aPrimitive->SetParent( this ); - m_padStack.AddPrimitive( aPrimitive ); + m_padStack.AddPrimitive( aPrimitive, aLayer ); SetDirty(); } // clear the basic shapes list and associated data -void PAD::DeletePrimitivesList() +void PAD::DeletePrimitivesList( PCB_LAYER_ID aLayer ) { - m_padStack.ClearPrimitives(); + if( aLayer == UNDEFINED_LAYER ) + { + m_padStack.ForEachUniqueLayer( + [&]( PCB_LAYER_ID l ) + { + m_padStack.ClearPrimitives( l ); + } ); + } + else + { + m_padStack.ClearPrimitives( aLayer); + } SetDirty(); } -void PAD::addPadPrimitivesToPolygon( SHAPE_POLY_SET* aMergedPolygon, int aError, - ERROR_LOC aErrorLoc ) const +void PAD::addPadPrimitivesToPolygon( PCB_LAYER_ID aLayer, SHAPE_POLY_SET* aMergedPolygon, + int aError, ERROR_LOC aErrorLoc ) const { SHAPE_POLY_SET polyset; - for( const std::shared_ptr& primitive : m_padStack.Primitives() ) + for( const std::shared_ptr& primitive : m_padStack.Primitives( aLayer ) ) { if( !primitive->IsProxyItem() ) primitive->TransformShapeToPolygon( polyset, UNDEFINED_LAYER, 0, aError, aErrorLoc ); @@ -137,7 +152,8 @@ void PAD::addPadPrimitivesToPolygon( SHAPE_POLY_SET* aMergedPolygon, int aError, } } -void PAD::MergePrimitivesAsPolygon( SHAPE_POLY_SET* aMergedPolygon, ERROR_LOC aErrorLoc ) const +void PAD::MergePrimitivesAsPolygon( PCB_LAYER_ID aLayer, SHAPE_POLY_SET* aMergedPolygon, + ERROR_LOC aErrorLoc ) const { const BOARD* board = GetBoard(); int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF; @@ -146,21 +162,23 @@ void PAD::MergePrimitivesAsPolygon( SHAPE_POLY_SET* aMergedPolygon, ERROR_LOC aE // Add the anchor pad shape in aMergedPolygon, others in aux_polyset: // The anchor pad is always at 0,0 - switch( GetAnchorPadShape() ) + VECTOR2I padSize = GetSize( aLayer ); + + switch( GetAnchorPadShape( aLayer ) ) { case PAD_SHAPE::RECTANGLE: { - SHAPE_RECT rect( -GetSize().x / 2, -GetSize().y / 2, GetSize().x, GetSize().y ); + SHAPE_RECT rect( -padSize.x / 2, -padSize.y / 2, padSize.x, padSize.y ); aMergedPolygon->AddOutline( rect.Outline() ); } break; default: case PAD_SHAPE::CIRCLE: - TransformCircleToPolygon( *aMergedPolygon, VECTOR2I( 0, 0 ), GetSize().x / 2, maxError, + TransformCircleToPolygon( *aMergedPolygon, VECTOR2I( 0, 0 ), padSize.x / 2, maxError, aErrorLoc ); break; } - addPadPrimitivesToPolygon( aMergedPolygon, maxError, aErrorLoc ); + addPadPrimitivesToPolygon( aLayer, aMergedPolygon, maxError, aErrorLoc ); } diff --git a/pcbnew/padstack.cpp b/pcbnew/padstack.cpp index 37dabde3fa..a2a99c16f4 100644 --- a/pcbnew/padstack.cpp +++ b/pcbnew/padstack.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include @@ -34,16 +36,17 @@ PADSTACK::PADSTACK( BOARD_ITEM* aParent ) : m_unconnectedLayerMode( UNCONNECTED_LAYER_MODE::KEEP_ALL ), m_customShapeInZoneMode( CUSTOM_SHAPE_ZONE_MODE::OUTLINE ) { - m_defaultCopperProps.shape = SHAPE_PROPS(); - m_defaultCopperProps.zone_connection = ZONE_CONNECTION::INHERITED; - m_defaultCopperProps.thermal_spoke_width = 0; - m_defaultCopperProps.thermal_spoke_angle = ANGLE_45; - m_defaultCopperProps.thermal_gap = 0; + m_copperProps[PADSTACK::ALL_LAYERS].shape = SHAPE_PROPS(); + m_copperProps[PADSTACK::ALL_LAYERS].zone_connection = ZONE_CONNECTION::INHERITED; + m_copperProps[PADSTACK::ALL_LAYERS].thermal_spoke_width = 0; + m_copperProps[PADSTACK::ALL_LAYERS].thermal_spoke_angle = ANGLE_45; + m_copperProps[PADSTACK::ALL_LAYERS].thermal_gap = 0; m_drill.shape = PAD_DRILL_SHAPE::CIRCLE; m_drill.start = F_Cu; m_drill.end = B_Cu; + m_secondaryDrill.shape = PAD_DRILL_SHAPE::UNDEFINED; m_secondaryDrill.start = UNDEFINED_LAYER; m_secondaryDrill.end = UNDEFINED_LAYER; } @@ -57,32 +60,63 @@ PADSTACK::PADSTACK( const PADSTACK& aOther ) PADSTACK& PADSTACK::operator=( const PADSTACK &aOther ) { - m_mode = aOther.m_mode; - m_layerSet = aOther.m_layerSet; - m_customName = aOther.m_customName; - m_defaultCopperProps = aOther.m_defaultCopperProps; - m_frontMaskProps = aOther.m_frontMaskProps; - m_backMaskProps = aOther.m_backMaskProps; - m_unconnectedLayerMode = aOther.m_unconnectedLayerMode; - m_copperOverrides = aOther.m_copperOverrides; - m_drill = aOther.m_drill; - m_secondaryDrill = aOther.m_secondaryDrill; + m_mode = aOther.m_mode; + m_layerSet = aOther.m_layerSet; + m_customName = aOther.m_customName; + m_orientation = aOther.m_orientation; + m_copperProps = aOther.m_copperProps; + m_frontMaskProps = aOther.m_frontMaskProps; + m_backMaskProps = aOther.m_backMaskProps; + m_unconnectedLayerMode = aOther.m_unconnectedLayerMode; + m_customShapeInZoneMode = aOther.m_customShapeInZoneMode; + m_drill = aOther.m_drill; + m_secondaryDrill = aOther.m_secondaryDrill; return *this; } bool PADSTACK::operator==( const PADSTACK& aOther ) const { - return m_mode == aOther.m_mode - && m_layerSet == aOther.m_layerSet - && m_customName == aOther.m_customName - && m_defaultCopperProps == aOther.m_defaultCopperProps - && m_frontMaskProps == aOther.m_frontMaskProps - && m_backMaskProps == aOther.m_backMaskProps - && m_unconnectedLayerMode == aOther.m_unconnectedLayerMode - && m_copperOverrides == aOther.m_copperOverrides - && m_drill == aOther.m_drill - && m_secondaryDrill == aOther.m_secondaryDrill; + if( m_mode != aOther.m_mode ) + return false; + + if( m_layerSet != aOther.m_layerSet ) + return false; + + if( m_customName != aOther.m_customName ) + return false; + + if( m_orientation != aOther.m_orientation ) + return false; + + if( m_frontMaskProps != aOther.m_frontMaskProps ) + return false; + + if( m_backMaskProps != aOther.m_backMaskProps ) + return false; + + if( m_unconnectedLayerMode != aOther.m_unconnectedLayerMode ) + return false; + + if( m_customShapeInZoneMode != aOther.m_customShapeInZoneMode ) + return false; + + if( m_drill != aOther.m_drill ) + return false; + + if( m_secondaryDrill != aOther.m_secondaryDrill ) + return false; + + bool copperMatches = true; + + ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + if( CopperLayer( aLayer ) != aOther.CopperLayer( aLayer ) ) + copperMatches = false; + } ); + + return copperMatches; } @@ -106,15 +140,16 @@ bool PADSTACK::Deserialize( const google::protobuf::Any& aContainer ) Drill().end = FromProtoEnum( padstack.end_layer() ); // We don't yet support complex padstacks + // TODO(JE) Rework for full padstacks if( padstack.layers_size() == 1 ) { const PadStackLayer& layer = padstack.layers( 0 ); - Size() = kiapi::common::UnpackVector2( layer.size() ); + Size( ALL_LAYERS ) = kiapi::common::UnpackVector2( layer.size() ); SetLayerSet( kiapi::board::UnpackLayerSet( layer.layers() ) ); - SetShape( FromProtoEnum( layer.shape() ) ); - SetAnchorShape( FromProtoEnum( layer.custom_anchor_shape() ) ); + SetShape( FromProtoEnum( layer.shape() ), F_Cu ); + SetAnchorShape( FromProtoEnum( layer.custom_anchor_shape() ), F_Cu ); - SHAPE_PROPS& props = CopperLayerDefaults().shape; + SHAPE_PROPS& props = CopperLayer( ALL_LAYERS ).shape; props.chamfered_rect_ratio = layer.chamfer_ratio(); props.round_rect_radius_ratio = layer.corner_rounding_ratio(); @@ -130,7 +165,7 @@ bool PADSTACK::Deserialize( const google::protobuf::Any& aContainer ) if( layer.chamfered_corners().bottom_right() ) props.chamfered_rect_positions |= RECT_CHAMFER_BOTTOM_RIGHT; - ClearPrimitives(); + ClearPrimitives( F_Cu ); google::protobuf::Any a; for( const GraphicShape& shapeProto : layer.custom_shapes() ) @@ -139,29 +174,29 @@ bool PADSTACK::Deserialize( const google::protobuf::Any& aContainer ) std::unique_ptr shape = std::make_unique( m_parent ); if( shape->Deserialize( a ) ) - AddPrimitive( shape.release() ); + AddPrimitive( shape.release(), F_Cu ); } if( layer.has_zone_settings() ) { - CopperLayerDefaults().zone_connection = + CopperLayer( ALL_LAYERS ).zone_connection = FromProtoEnum( layer.zone_settings().zone_connection() ); if( layer.zone_settings().has_thermal_spokes() ) { const ThermalSpokeSettings& thermals = layer.zone_settings().thermal_spokes(); - CopperLayerDefaults().thermal_gap = thermals.gap(); - CopperLayerDefaults().thermal_spoke_width = thermals.width(); - SetThermalSpokeAngle( thermals.angle().value_degrees() ); + CopperLayer( ALL_LAYERS ).thermal_gap = thermals.gap(); + CopperLayer( ALL_LAYERS ).thermal_spoke_width = thermals.width(); + SetThermalSpokeAngle( thermals.angle().value_degrees(), F_Cu ); } } else { - CopperLayerDefaults().zone_connection = ZONE_CONNECTION::INHERITED; - CopperLayerDefaults().thermal_gap = 0; - CopperLayerDefaults().thermal_spoke_width = 0; - CopperLayerDefaults().thermal_spoke_angle = DefaultThermalSpokeAngleForShape(); + CopperLayer( ALL_LAYERS ).zone_connection = ZONE_CONNECTION::INHERITED; + CopperLayer( ALL_LAYERS ).thermal_gap = 0; + CopperLayer( ALL_LAYERS ).thermal_spoke_width = 0; + CopperLayer( ALL_LAYERS ).thermal_spoke_angle = DefaultThermalSpokeAngleForShape( F_Cu ); } } @@ -302,24 +337,26 @@ void PADSTACK::Serialize( google::protobuf::Any& aContainer ) const kiapi::common::PackVector2( *padstack.mutable_drill_diameter(), Drill().size ); padstack.mutable_angle()->set_value_degrees( m_orientation.AsDegrees() ); + // TODO(JE) Rework for full padstacks PadStackLayer* stackLayer = padstack.add_layers(); kiapi::board::PackLayerSet( *stackLayer->mutable_layers(), LayerSet() ); - kiapi::common::PackVector2( *stackLayer->mutable_size(), Size() ); - stackLayer->set_shape( ToProtoEnum( Shape() ) ); - stackLayer->set_custom_anchor_shape( ToProtoEnum( AnchorShape() ) ); - stackLayer->set_chamfer_ratio( CopperLayerDefaults().shape.chamfered_rect_ratio ); - stackLayer->set_corner_rounding_ratio( CopperLayerDefaults().shape.round_rect_radius_ratio ); + kiapi::common::PackVector2( *stackLayer->mutable_size(), Size( PADSTACK::ALL_LAYERS ) ); + stackLayer->set_shape( ToProtoEnum( Shape( ALL_LAYERS ) ) ); + stackLayer->set_custom_anchor_shape( ToProtoEnum( AnchorShape( ALL_LAYERS ) ) ); + stackLayer->set_chamfer_ratio( CopperLayer( ALL_LAYERS ).shape.chamfered_rect_ratio ); + stackLayer->set_corner_rounding_ratio( CopperLayer( ALL_LAYERS ).shape.round_rect_radius_ratio ); google::protobuf::Any a; - for( const std::shared_ptr& shape : Primitives() ) + // TODO(JE) Rework for full padstacks + for( const std::shared_ptr& shape : Primitives( ALL_LAYERS ) ) { shape->Serialize( a ); GraphicShape* s = stackLayer->add_custom_shapes(); a.UnpackTo( s ); } - const int& corners = CopperLayerDefaults().shape.chamfered_rect_positions; + const int& corners = CopperLayer( ALL_LAYERS ).shape.chamfered_rect_positions; stackLayer->mutable_chamfered_corners()->set_top_left( corners & RECT_CHAMFER_TOP_LEFT ); stackLayer->mutable_chamfered_corners()->set_top_right( corners & RECT_CHAMFER_TOP_RIGHT ); stackLayer->mutable_chamfered_corners()->set_bottom_left( corners & RECT_CHAMFER_BOTTOM_LEFT ); @@ -328,15 +365,15 @@ void PADSTACK::Serialize( google::protobuf::Any& aContainer ) const ZoneConnectionSettings* zoneSettings = stackLayer->mutable_zone_settings(); ThermalSpokeSettings* thermalSettings = zoneSettings->mutable_thermal_spokes(); - if( CopperLayerDefaults().zone_connection.has_value() ) + if( CopperLayer( ALL_LAYERS ).zone_connection.has_value() ) { zoneSettings->set_zone_connection( ToProtoEnum( - *CopperLayerDefaults().zone_connection ) ); + *CopperLayer( ALL_LAYERS ).zone_connection ) ); } - thermalSettings->set_width( CopperLayerDefaults().thermal_spoke_width.value_or( 0 ) ); - thermalSettings->set_gap( CopperLayerDefaults().thermal_gap.value_or( 0 ) ); - thermalSettings->mutable_angle()->set_value_degrees( ThermalSpokeAngle().AsDegrees() ); + thermalSettings->set_width( CopperLayer( ALL_LAYERS ).thermal_spoke_width.value_or( 0 ) ); + thermalSettings->set_gap( CopperLayer( ALL_LAYERS ).thermal_gap.value_or( 0 ) ); + thermalSettings->mutable_angle()->set_value_degrees( ThermalSpokeAngle( F_Cu ).AsDegrees() ); padstack.set_unconnected_layer_removal( ToProtoEnum( m_unconnectedLayerMode ) ); @@ -410,6 +447,169 @@ void PADSTACK::Serialize( google::protobuf::Any& aContainer ) const } +int PADSTACK::Compare( const PADSTACK* aPadstackRef, const PADSTACK* aPadstackCmp ) +{ + int diff; + + auto compareCopperProps = + [&]( PCB_LAYER_ID aLayer ) + { + if( ( diff = static_cast( aPadstackRef->Shape( aLayer ) ) - + static_cast( aPadstackCmp->Shape( aLayer ) ) ) != 0 ) + return diff; + + if( ( diff = aPadstackRef->Size( aLayer ).x - aPadstackCmp->Size( aLayer ).x ) != 0 ) + return diff; + + if( ( diff = aPadstackRef->Size( aLayer ).y - aPadstackCmp->Size( aLayer ).y ) != 0 ) + return diff; + + if( ( diff = aPadstackRef->Offset( aLayer ).x + - aPadstackCmp->Offset( aLayer ).x ) != 0 ) + return diff; + + if( ( diff = aPadstackRef->Offset( aLayer ).y + - aPadstackCmp->Offset( aLayer ).y ) != 0 ) + return diff; + + if( ( diff = aPadstackRef->TrapezoidDeltaSize( aLayer ).x + - aPadstackCmp->TrapezoidDeltaSize( aLayer ).x ) + != 0 ) + { + return diff; + } + + if( ( diff = aPadstackRef->TrapezoidDeltaSize( aLayer ).y + - aPadstackCmp->TrapezoidDeltaSize( aLayer ).y ) + != 0 ) + { + return diff; + } + + if( ( diff = aPadstackRef->RoundRectRadiusRatio( aLayer ) + - aPadstackCmp->RoundRectRadiusRatio( aLayer ) ) + != 0 ) + { + return diff; + } + + if( ( diff = aPadstackRef->ChamferPositions( aLayer ) + - aPadstackCmp->ChamferPositions( aLayer ) ) != 0 ) + return diff; + + if( ( diff = aPadstackRef->ChamferRatio( aLayer ) + - aPadstackCmp->ChamferRatio( aLayer ) ) != 0 ) + return diff; + + if( ( diff = static_cast( aPadstackRef->Primitives( aLayer ).size() ) - + static_cast( aPadstackCmp->Primitives( aLayer ).size() ) ) != 0 ) + return diff; + + // @todo: Compare custom pad primitives for pads that have the same number of primitives + // here. Currently there is no compare function for PCB_SHAPE objects. + return 0; + }; + + aPadstackRef->ForEachUniqueLayer( compareCopperProps ); + + if( ( diff = static_cast( aPadstackRef->DrillShape() ) - + static_cast( aPadstackCmp->DrillShape() ) ) != 0 ) + return diff; + + if( ( diff = aPadstackRef->Drill().size.x - aPadstackCmp->Drill().size.x ) != 0 ) + return diff; + + if( ( diff = aPadstackRef->Drill().size.y - aPadstackCmp->Drill().size.y ) != 0 ) + return diff; + + return aPadstackRef->LayerSet().compare( aPadstackCmp->LayerSet() ); +} + + +double PADSTACK::Similarity( const PADSTACK& aOther ) const +{ + double similarity = 1.0; + + ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + if( Shape( aLayer ) != aOther.Shape( aLayer ) ) + similarity *= 0.9; + + if( Size( aLayer ) != aOther.Size( aLayer ) ) + similarity *= 0.9; + + if( Offset( aLayer ) != aOther.Offset( aLayer ) ) + similarity *= 0.9; + + if( RoundRectRadiusRatio( aLayer ) != aOther.RoundRectRadiusRatio( aLayer ) ) + similarity *= 0.9; + + if( ChamferRatio( aLayer ) != aOther.ChamferRatio( aLayer ) ) + similarity *= 0.9; + + if( ChamferPositions( aLayer ) != aOther.ChamferPositions( aLayer ) ) + similarity *= 0.9; + + if( Primitives( aLayer ).size() != aOther.Primitives( aLayer ).size() ) + similarity *= 0.9; + + if( AnchorShape( aLayer ) != aOther.AnchorShape( aLayer ) ) + similarity *= 0.9; + } ); + + if( Drill() != aOther.Drill() ) + similarity *= 0.9; + + if( DrillShape() != aOther.DrillShape() ) + similarity *= 0.9; + + if( GetOrientation() != aOther.GetOrientation() ) + similarity *= 0.9; + + if( ZoneConnection() != aOther.ZoneConnection() ) + similarity *= 0.9; + + if( ThermalSpokeWidth() != aOther.ThermalSpokeWidth() ) + similarity *= 0.9; + + if( ThermalSpokeAngle() != aOther.ThermalSpokeAngle() ) + similarity *= 0.9; + + if( ThermalGap() != aOther.ThermalGap() ) + similarity *= 0.9; + + if( CustomShapeInZoneMode() != aOther.CustomShapeInZoneMode() ) + similarity *= 0.9; + + if( Clearance() != aOther.Clearance() ) + similarity *= 0.9; + + if( SolderMaskMargin() != aOther.SolderMaskMargin() ) + similarity *= 0.9; + + if( SolderPasteMargin() != aOther.SolderPasteMargin() ) + similarity *= 0.9; + + if( SolderPasteMarginRatio() != aOther.SolderPasteMarginRatio() ) + similarity *= 0.9; + + if( ThermalGap() != aOther.ThermalGap() ) + similarity *= 0.9; + + if( ThermalSpokeWidth() != aOther.ThermalSpokeWidth() ) + similarity *= 0.9; + + if( ThermalSpokeAngle() != aOther.ThermalSpokeAngle() ) + similarity *= 0.9; + + if( LayerSet() != aOther.LayerSet() ) + similarity *= 0.9; + + return similarity; +} + + wxString PADSTACK::Name() const { // TODO @@ -452,11 +652,36 @@ bool PADSTACK::SHAPE_PROPS::operator==( const SHAPE_PROPS& aOther ) const bool PADSTACK::COPPER_LAYER_PROPS::operator==( const COPPER_LAYER_PROPS& aOther ) const { - return shape == aOther.shape && zone_connection == aOther.zone_connection - && thermal_spoke_width == aOther.thermal_spoke_width - && thermal_spoke_angle == aOther.thermal_spoke_angle - && thermal_gap == aOther.thermal_gap - && custom_shapes == aOther.custom_shapes; + if( shape != aOther.shape ) + return false; + + if( zone_connection != aOther.zone_connection ) + return false; + + if( thermal_spoke_width != aOther.thermal_spoke_width ) + return false; + + if( thermal_spoke_angle != aOther.thermal_spoke_angle ) + return false; + + if( thermal_gap != aOther.thermal_gap ) + return false; + + if( custom_shapes.size() != aOther.custom_shapes.size() ) + return false; + + if( !std::equal( custom_shapes.begin(), custom_shapes.end(), + aOther.custom_shapes.begin(), aOther.custom_shapes.end(), + []( const std::shared_ptr& aFirst, + const std::shared_ptr& aSecond ) + { + return *aFirst == *aSecond; + } ) ) + { + return false; + } + + return true; } @@ -477,37 +702,159 @@ bool PADSTACK::DRILL_PROPS::operator==( const DRILL_PROPS& aOther ) const } +void PADSTACK::SetMode( MODE aMode ) +{ + m_mode = aMode; + + switch( m_mode ) + { + case MODE::NORMAL: + std::erase_if( m_copperProps, + []( const auto& aEntry ) + { + const auto& [key, value] = aEntry; + return key != ALL_LAYERS; + } ); + break; + + case MODE::FRONT_INNER_BACK: + m_copperProps[INNER_LAYERS] = m_copperProps[ALL_LAYERS]; + m_copperProps[B_Cu] = m_copperProps[ALL_LAYERS]; + break; + + case MODE::CUSTOM: + for( PCB_LAYER_ID layer : LAYER_RANGE( In1_Cu, B_Cu, MAX_CU_LAYERS ) ) + m_copperProps[layer] = m_copperProps[ALL_LAYERS]; + + break; + } +} + + +void PADSTACK::ForEachUniqueLayer( const std::function& aMethod ) const +{ + switch( Mode() ) + { + case MODE::NORMAL: + aMethod( F_Cu ); + break; + + case MODE::FRONT_INNER_BACK: + aMethod( F_Cu ); + aMethod( INNER_LAYERS ); + aMethod( B_Cu ); + break; + + case MODE::CUSTOM: + for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, MAX_CU_LAYERS ) ) + aMethod( layer ); + + break; + } +} + + +PCB_LAYER_ID PADSTACK::EffectiveLayerFor( PCB_LAYER_ID aLayer ) const +{ + switch( static_cast( aLayer ) ) + { + case LAYER_PAD_FR_NETNAMES: + return F_Cu; + + case LAYER_PAD_BK_NETNAMES: + return Mode() == MODE::NORMAL ? F_Cu : B_Cu; + + // For these, just give the front copper geometry, it doesn't matter. + case LAYER_PAD_NETNAMES: + case LAYER_VIA_NETNAMES: + case LAYER_PADS_TH: + case LAYER_PAD_PLATEDHOLES: + case LAYER_VIA_HOLES: + case LAYER_PAD_HOLEWALLS: + case LAYER_VIA_HOLEWALLS: + return ALL_LAYERS; + + default: + break; + } + + switch( Mode() ) + { + case MODE::CUSTOM: + case MODE::FRONT_INNER_BACK: + { + if( IsFrontLayer( aLayer ) ) + return F_Cu; + + if( IsBackLayer( aLayer ) ) + return B_Cu; + + wxASSERT_MSG( IsCopperLayer( aLayer ), + wxString::Format( wxT( "Unhandled layer %d in PADSTACK::EffectiveLayerFor" ), + aLayer ) ); + + return Mode() == MODE::CUSTOM ? aLayer : INNER_LAYERS; + } + + case MODE::NORMAL: + break; + } + + return F_Cu; +} + + +PADSTACK::COPPER_LAYER_PROPS& PADSTACK::CopperLayer( PCB_LAYER_ID aLayer ) +{ + PCB_LAYER_ID layer = EffectiveLayerFor( aLayer ); + // Create on-demand + return m_copperProps[layer]; +} + + +const PADSTACK::COPPER_LAYER_PROPS& PADSTACK::CopperLayer( PCB_LAYER_ID aLayer ) const +{ + PCB_LAYER_ID layer = EffectiveLayerFor( aLayer ); + + wxCHECK_MSG( m_copperProps.contains( layer ), m_copperProps.at( ALL_LAYERS ), + "Attempt to retrieve layer " + std::string( magic_enum::enum_name( layer ) ) + " from a " + "padstack that does not contain it" ); + + return m_copperProps.at( layer ); +} + + PAD_SHAPE PADSTACK::Shape( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().shape.shape; + return CopperLayer( aLayer ).shape.shape; } void PADSTACK::SetShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer ) { - CopperLayerDefaults().shape.shape = aShape; + CopperLayer( aLayer ).shape.shape = aShape; } VECTOR2I& PADSTACK::Size( PCB_LAYER_ID aLayer ) { - return CopperLayerDefaults().shape.size; + return CopperLayer( aLayer ).shape.size; } const VECTOR2I& PADSTACK::Size( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().shape.size; + return CopperLayer( aLayer ).shape.size; } -PAD_DRILL_SHAPE PADSTACK::DrillShape( PCB_LAYER_ID aLayer ) const +PAD_DRILL_SHAPE PADSTACK::DrillShape() const { return m_drill.shape; } -void PADSTACK::SetDrillShape( PAD_DRILL_SHAPE aShape, PCB_LAYER_ID aLayer ) +void PADSTACK::SetDrillShape( PAD_DRILL_SHAPE aShape ) { m_drill.shape = aShape; } @@ -515,49 +862,49 @@ void PADSTACK::SetDrillShape( PAD_DRILL_SHAPE aShape, PCB_LAYER_ID aLayer ) VECTOR2I& PADSTACK::Offset( PCB_LAYER_ID aLayer ) { - return CopperLayerDefaults().shape.offset; + return CopperLayer( aLayer ).shape.offset; } const VECTOR2I& PADSTACK::Offset( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().shape.offset; + return CopperLayer( aLayer ).shape.offset; } PAD_SHAPE PADSTACK::AnchorShape( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().shape.anchor_shape; + return CopperLayer( aLayer ).shape.anchor_shape; } void PADSTACK::SetAnchorShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer ) { - CopperLayerDefaults().shape.anchor_shape = aShape; + CopperLayer( aLayer ).shape.anchor_shape = aShape; } VECTOR2I& PADSTACK::TrapezoidDeltaSize( PCB_LAYER_ID aLayer ) { - return CopperLayerDefaults().shape.trapezoid_delta_size; + return CopperLayer( aLayer ).shape.trapezoid_delta_size; } const VECTOR2I& PADSTACK::TrapezoidDeltaSize( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().shape.trapezoid_delta_size; + return CopperLayer( aLayer ).shape.trapezoid_delta_size; } double PADSTACK::RoundRectRadiusRatio( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().shape.round_rect_radius_ratio; + return CopperLayer( aLayer ).shape.round_rect_radius_ratio; } void PADSTACK::SetRoundRectRadiusRatio( double aRatio, PCB_LAYER_ID aLayer ) { - CopperLayerDefaults().shape.round_rect_radius_ratio = aRatio; + CopperLayer( aLayer ).shape.round_rect_radius_ratio = aRatio; } @@ -580,43 +927,43 @@ void PADSTACK::SetRoundRectRadius( double aRadius, PCB_LAYER_ID aLayer ) double PADSTACK::ChamferRatio( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().shape.chamfered_rect_ratio; + return CopperLayer( aLayer ).shape.chamfered_rect_ratio; } void PADSTACK::SetChamferRatio( double aRatio, PCB_LAYER_ID aLayer ) { - CopperLayerDefaults().shape.chamfered_rect_ratio = aRatio; + CopperLayer( aLayer ).shape.chamfered_rect_ratio = aRatio; } int& PADSTACK::ChamferPositions( PCB_LAYER_ID aLayer ) { - return CopperLayerDefaults().shape.chamfered_rect_positions; + return CopperLayer( aLayer ).shape.chamfered_rect_positions; } const int& PADSTACK::ChamferPositions( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().shape.chamfered_rect_positions; + return CopperLayer( aLayer ).shape.chamfered_rect_positions; } void PADSTACK::SetChamferPositions( int aPositions, PCB_LAYER_ID aLayer ) { - CopperLayerDefaults().shape.chamfered_rect_positions = aPositions; + CopperLayer( aLayer ).shape.chamfered_rect_positions = aPositions; } std::optional& PADSTACK::Clearance( PCB_LAYER_ID aLayer ) { - return CopperLayerDefaults().clearance; + return CopperLayer( aLayer ).clearance; } const std::optional& PADSTACK::Clearance( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().clearance; + return CopperLayer( aLayer ).clearance; } @@ -657,48 +1004,49 @@ std::optional& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer ) const std::optional& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer ) const { return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_paste_margin_ratio - : m_backMaskProps.solder_paste_margin_ratio;} + : m_backMaskProps.solder_paste_margin_ratio; +} std::optional& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer ) { - return CopperLayerDefaults().zone_connection; + return CopperLayer( aLayer ).zone_connection; } const std::optional& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().zone_connection; + return CopperLayer( aLayer ).zone_connection; } std::optional& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer ) { - return CopperLayerDefaults().thermal_spoke_width; + return CopperLayer( aLayer ).thermal_spoke_width; } const std::optional& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().thermal_spoke_width; + return CopperLayer( aLayer ).thermal_spoke_width; } std::optional& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer ) { - return CopperLayerDefaults().thermal_gap; + return CopperLayer( aLayer ).thermal_gap; } const std::optional& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().thermal_gap; + return CopperLayer( aLayer ).thermal_gap; } EDA_ANGLE PADSTACK::DefaultThermalSpokeAngleForShape( PCB_LAYER_ID aLayer ) const { - const COPPER_LAYER_PROPS& defaults = CopperLayerDefaults(); + const COPPER_LAYER_PROPS& defaults = CopperLayer( aLayer ); return ( defaults.shape.shape == PAD_SHAPE::CIRCLE || ( defaults.shape.shape == PAD_SHAPE::CUSTOM @@ -709,7 +1057,7 @@ EDA_ANGLE PADSTACK::DefaultThermalSpokeAngleForShape( PCB_LAYER_ID aLayer ) cons EDA_ANGLE PADSTACK::ThermalSpokeAngle( PCB_LAYER_ID aLayer ) const { - const COPPER_LAYER_PROPS& defaults = CopperLayerDefaults(); + const COPPER_LAYER_PROPS& defaults = CopperLayer( aLayer ); return defaults.thermal_spoke_angle.value_or( DefaultThermalSpokeAngleForShape( aLayer ) ); } @@ -717,25 +1065,25 @@ EDA_ANGLE PADSTACK::ThermalSpokeAngle( PCB_LAYER_ID aLayer ) const void PADSTACK::SetThermalSpokeAngle( EDA_ANGLE aAngle, PCB_LAYER_ID aLayer ) { - CopperLayerDefaults().thermal_spoke_angle = aAngle; + CopperLayer( aLayer ).thermal_spoke_angle = aAngle; } std::vector>& PADSTACK::Primitives( PCB_LAYER_ID aLayer ) { - return CopperLayerDefaults().custom_shapes; + return CopperLayer( aLayer ).custom_shapes; } const std::vector>& PADSTACK::Primitives( PCB_LAYER_ID aLayer ) const { - return CopperLayerDefaults().custom_shapes; + return CopperLayer( aLayer ).custom_shapes; } void PADSTACK::AddPrimitive( PCB_SHAPE* aShape, PCB_LAYER_ID aLayer ) { - CopperLayerDefaults().custom_shapes.emplace_back( aShape ); + CopperLayer( aLayer ).custom_shapes.emplace_back( aShape ); } @@ -743,7 +1091,7 @@ void PADSTACK::AppendPrimitives( const std::vector>& PCB_LAYER_ID aLayer ) { for( const std::shared_ptr& prim : aPrimitivesList ) - AddPrimitive( new PCB_SHAPE( *prim ) ); + AddPrimitive( new PCB_SHAPE( *prim ), aLayer ); } @@ -759,7 +1107,7 @@ void PADSTACK::ReplacePrimitives( const std::vector>& void PADSTACK::ClearPrimitives( PCB_LAYER_ID aLayer ) { - CopperLayerDefaults().custom_shapes.clear(); + CopperLayer( aLayer ).custom_shapes.clear(); } diff --git a/pcbnew/padstack.h b/pcbnew/padstack.h index f8e04771f2..02201e1ce2 100644 --- a/pcbnew/padstack.h +++ b/pcbnew/padstack.h @@ -61,6 +61,7 @@ enum class PAD_SHAPE : int */ enum class PAD_DRILL_SHAPE { + UNDEFINED, CIRCLE, OBLONG, }; @@ -125,13 +126,20 @@ public: MOUNTING ///< A mounting hole (plated or unplated, not associated with a footprint) }; + ///! Copper geometry mode: controls how many unique copper layer shapes this padstack has enum class MODE { NORMAL, ///< Shape is the same on all layers - TOP_INNER_BOTTOM, ///< Up to three shapes can be defined (top, inner, bottom) + FRONT_INNER_BACK, ///< Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu) CUSTOM ///< Shapes can be defined on arbitrary layers }; + ///! Temporary layer identifier to identify code that is not padstack-aware + static constexpr PCB_LAYER_ID ALL_LAYERS = F_Cu; + + ///! The layer identifier to use for "inner layers" on top/inner/bottom padstacks + static constexpr PCB_LAYER_ID INNER_LAYERS = In1_Cu; + ///! Whether or not to remove the copper shape for unconnected layers enum class UNCONNECTED_LAYER_MODE { @@ -243,6 +251,20 @@ public: void Serialize( google::protobuf::Any &aContainer ) const override; bool Deserialize( const google::protobuf::Any &aContainer ) override; + /** + * Compare two padstacks and return 0 if they are equal. + * + * @return less than 0 if left less than right, 0 if equal, or greater than 0 if left + * greater than right. + */ + static int Compare( const PADSTACK* aPadstackRef, const PADSTACK* aPadstackCmp ); + + /** + * Return a measure of how likely the other object is to represent the same + * object. The scale runs from 0.0 (definitely different objects) to 1.0 (same) + */ + double Similarity( const PADSTACK& aOther ) const; + const LSET& LayerSet() const { return m_layerSet; } LSET& LayerSet() { return m_layerSet; } void SetLayerSet( const LSET& aSet ) { m_layerSet = aSet; } @@ -251,7 +273,7 @@ public: PCB_LAYER_ID EndLayer() const; MODE Mode() const { return m_mode; } - void SetMode( MODE aMode ) { m_mode = aMode; } + void SetMode( MODE aMode ); ///! Returns the name of this padstack in IPC-7351 format wxString Name() const; @@ -272,8 +294,8 @@ public: UNCONNECTED_LAYER_MODE UnconnectedLayerMode() const { return m_unconnectedLayerMode; } void SetUnconnectedLayerMode( UNCONNECTED_LAYER_MODE aMode ) { m_unconnectedLayerMode = aMode; } - COPPER_LAYER_PROPS& CopperLayerDefaults() { return m_defaultCopperProps; } - const COPPER_LAYER_PROPS& CopperLayerDefaults() const { return m_defaultCopperProps; } + COPPER_LAYER_PROPS& CopperLayer( PCB_LAYER_ID aLayer ); + const COPPER_LAYER_PROPS& CopperLayer( PCB_LAYER_ID aLayer ) const; MASK_LAYER_PROPS& FrontOuterLayers() { return m_frontMaskProps; } const MASK_LAYER_PROPS& FrontOuterLayers() const { return m_frontMaskProps; } @@ -292,38 +314,57 @@ public: CUSTOM_SHAPE_ZONE_MODE CustomShapeInZoneMode() const { return m_customShapeInZoneMode; } void SetCustomShapeInZoneMode( CUSTOM_SHAPE_ZONE_MODE aM ) { m_customShapeInZoneMode = aM; } + /** + * Runs the given callable for each active unique copper layer in this padstack, meaning + * F_Cu for MODE::NORMAL; F_Cu, PADSTACK::INNER_LAYERS, and B_Cu for top/inner/bottom, + * and an arbitrary set of layers for full-custom padstacks. + * @param aMethod will be called once for each independent copper layer in the padstack + */ + void ForEachUniqueLayer( const std::function& aMethod ) const; + + /** + * Determines which geometry layer should be used for the given input layer. + * For example, for MODE::NORMAL, this will always be F_Cu, and for MODE::FRONT_INNER_BACK, + * this will be one of F_Cu, PADSTACK::INNER_LAYERS, and B_Cu depending on the input + * layer. + * + * @param aLayer is a valid board layer + * @return the layer that the padstack's geometry is stored on for the given input layer + */ + PCB_LAYER_ID EffectiveLayerFor( PCB_LAYER_ID aLayer ) const; + // The following section has convenience getters for the padstack properties on a given layer. - PAD_SHAPE Shape( PCB_LAYER_ID aLayer = F_Cu ) const; - void SetShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer = F_Cu ); + PAD_SHAPE Shape( PCB_LAYER_ID aLayer ) const; + void SetShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer ); - VECTOR2I& Size( PCB_LAYER_ID aLayer = F_Cu ); - const VECTOR2I& Size( PCB_LAYER_ID aLayer = F_Cu ) const; + VECTOR2I& Size( PCB_LAYER_ID aLayer ); + const VECTOR2I& Size( PCB_LAYER_ID aLayer ) const; - PAD_DRILL_SHAPE DrillShape( PCB_LAYER_ID aLayer = F_Cu ) const; - void SetDrillShape( PAD_DRILL_SHAPE aShape, PCB_LAYER_ID aLayer = F_Cu ); + PAD_DRILL_SHAPE DrillShape() const; + void SetDrillShape( PAD_DRILL_SHAPE aShape ); - VECTOR2I& Offset( PCB_LAYER_ID aLayer = F_Cu ); - const VECTOR2I& Offset( PCB_LAYER_ID aLayer = F_Cu ) const; + VECTOR2I& Offset( PCB_LAYER_ID aLayer ); + const VECTOR2I& Offset( PCB_LAYER_ID aLayer ) const; - PAD_SHAPE AnchorShape( PCB_LAYER_ID aLayer = F_Cu ) const; - void SetAnchorShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer = F_Cu ); + PAD_SHAPE AnchorShape( PCB_LAYER_ID aLayer ) const; + void SetAnchorShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer ); - VECTOR2I& TrapezoidDeltaSize( PCB_LAYER_ID aLayer = F_Cu ); - const VECTOR2I& TrapezoidDeltaSize( PCB_LAYER_ID aLayer = F_Cu ) const; + VECTOR2I& TrapezoidDeltaSize( PCB_LAYER_ID aLayer ); + const VECTOR2I& TrapezoidDeltaSize( PCB_LAYER_ID aLayer ) const; - double RoundRectRadiusRatio( PCB_LAYER_ID aLayer = F_Cu ) const; - void SetRoundRectRadiusRatio( double aRatio, PCB_LAYER_ID aLayer = F_Cu ); + double RoundRectRadiusRatio( PCB_LAYER_ID aLayer ) const; + void SetRoundRectRadiusRatio( double aRatio, PCB_LAYER_ID aLayer ); - int RoundRectRadius( PCB_LAYER_ID aLayer = F_Cu ) const; - void SetRoundRectRadius( double aRadius, PCB_LAYER_ID aLayer = F_Cu ); + int RoundRectRadius( PCB_LAYER_ID aLayer ) const; + void SetRoundRectRadius( double aRadius, PCB_LAYER_ID aLayer ); - double ChamferRatio( PCB_LAYER_ID aLayer = F_Cu ) const; - void SetChamferRatio( double aRatio, PCB_LAYER_ID aLayer = F_Cu ); + double ChamferRatio( PCB_LAYER_ID aLayer ) const; + void SetChamferRatio( double aRatio, PCB_LAYER_ID aLayer ); - int& ChamferPositions( PCB_LAYER_ID aLayer = F_Cu ); - const int& ChamferPositions( PCB_LAYER_ID aLayer = F_Cu ) const; - void SetChamferPositions( int aPositions, PCB_LAYER_ID aLayer = F_Cu ); + int& ChamferPositions( PCB_LAYER_ID aLayer ); + const int& ChamferPositions( PCB_LAYER_ID aLayer ) const; + void SetChamferPositions( int aPositions, PCB_LAYER_ID aLayer ); std::optional& Clearance( PCB_LAYER_ID aLayer = F_Cu ); const std::optional& Clearance( PCB_LAYER_ID aLayer = F_Cu ) const; @@ -350,15 +391,15 @@ public: EDA_ANGLE ThermalSpokeAngle( PCB_LAYER_ID aLayer = F_Cu ) const; void SetThermalSpokeAngle( EDA_ANGLE aAngle, PCB_LAYER_ID aLayer = F_Cu ); - std::vector>& Primitives( PCB_LAYER_ID aLayer = F_Cu ); - const std::vector>& Primitives( PCB_LAYER_ID aLayer = F_Cu ) const; + std::vector>& Primitives( PCB_LAYER_ID aLayer ); + const std::vector>& Primitives( PCB_LAYER_ID aLayer ) const; /** * Adds a custom shape primitive to the padstack. * @param aShape is a shape to add as a custom primitive. Ownership is passed to this PADSTACK. * @param aLayer is the padstack layer to add to. */ - void AddPrimitive( PCB_SHAPE* aShape, PCB_LAYER_ID aLayer = F_Cu ); + void AddPrimitive( PCB_SHAPE* aShape, PCB_LAYER_ID aLayer ); /** * Appends a copy of each shape in the given list to this padstack's custom shape list @@ -366,7 +407,7 @@ public: * @param aLayer is the padstack layer to add to. */ void AppendPrimitives( const std::vector>& aPrimitivesList, - PCB_LAYER_ID aLayer = F_Cu ); + PCB_LAYER_ID aLayer ); /** * Clears the existing primitive list (freeing the owned shapes) and adds copies of the given @@ -375,9 +416,9 @@ public: * @param aLayer is the padstack layer to add to. */ void ReplacePrimitives( const std::vector>& aPrimitivesList, - PCB_LAYER_ID aLayer = F_Cu ); + PCB_LAYER_ID aLayer ); - void ClearPrimitives( PCB_LAYER_ID aLayer = F_Cu ); + void ClearPrimitives( PCB_LAYER_ID aLayer ); private: ///! The BOARD_ITEM this PADSTACK belongs to; will be used as the parent for owned shapes @@ -396,7 +437,8 @@ private: EDA_ANGLE m_orientation; ///! The properties applied to copper layers if they aren't overridden - COPPER_LAYER_PROPS m_defaultCopperProps; + //COPPER_LAYER_PROPS m_defaultCopperProps; + std::unordered_map m_copperProps; ///! The overrides applied to front outer technical layers MASK_LAYER_PROPS m_frontMaskProps; @@ -413,12 +455,6 @@ private: */ CUSTOM_SHAPE_ZONE_MODE m_customShapeInZoneMode; - ///! Any entries here override the copper layer settings on the given copper layer. - ///! If m_mode == MODE::TOP_INNER_BOTTOM, the inner layer setting is always In1_Cu and the only - ///! keys in this map that are used are F_Cu, In1_Cu, and B_Cu. - ///! If m_mode == MODE::NORMAL, this map is ignored. - std::unordered_map m_copperOverrides; - ///! The primary drill parameters, which also define the start and end layers for through-hole ///! vias and pads (F_Cu to B_Cu for normal holes; a subset of layers for blind/buried vias) DRILL_PROPS m_drill; diff --git a/pcbnew/pcb_io/altium/altium_pcb.cpp b/pcbnew/pcb_io/altium/altium_pcb.cpp index 1727af3cc3..74458dea36 100644 --- a/pcbnew/pcb_io/altium/altium_pcb.cpp +++ b/pcbnew/pcb_io/altium/altium_pcb.cpp @@ -2670,26 +2670,26 @@ void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItemOnLayer( FOOTPRINT* if( aLayer == F_Cu || aLayer == B_Cu ) { + // TODO(JE) padstacks -- not sure what should happen here yet std::unique_ptr pad = std::make_unique( aFootprint ); LSET padLayers; padLayers.set( aLayer ); pad->SetAttribute( PAD_ATTRIB::SMD ); - pad->SetShape( PAD_SHAPE::CUSTOM ); + pad->SetShape( PADSTACK::ALL_LAYERS, 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->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } ); pad->SetPosition( anchorPos ); SHAPE_POLY_SET shapePolys = polySet; shapePolys.Move( -anchorPos ); - pad->AddPrimitivePoly( shapePolys, 0, true ); + pad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, shapePolys, 0, true ); auto& map = m_extendedPrimitiveInformationMaps[ALTIUM_RECORD::REGION]; auto it = map.find( aPrimitiveIndex ); @@ -3120,21 +3120,24 @@ void ALTIUM_PCB::ConvertVias6ToFootprintItem( FOOTPRINT* aFootprint, const AVIA6 pad->SetNetCode( GetNetCode( aElem.net ) ); pad->SetPosition( aElem.position ); - pad->SetSize( VECTOR2I( aElem.diameter, aElem.diameter ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( aElem.diameter, aElem.diameter ) ); pad->SetDrillSize( VECTOR2I( aElem.holesize, aElem.holesize ) ); pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE ); - pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); pad->SetAttribute( PAD_ATTRIB::PTH ); // Pads are always through holes in KiCad pad->SetLayerSet( LSET().AllCuMask() ); if( aElem.viamode == ALTIUM_PAD_MODE::SIMPLE ) + { pad->Padstack().SetMode( PADSTACK::MODE::NORMAL ); + } else if( aElem.viamode == ALTIUM_PAD_MODE::TOP_MIDDLE_BOTTOM ) { - pad->Padstack().SetMode( PADSTACK::MODE::TOP_INNER_BOTTOM ); - pad->Padstack().Size( In2_Cu ) = VECTOR2I( aElem.diameter_by_layer[1], aElem.diameter_by_layer[1] ); + pad->Padstack().SetMode( PADSTACK::MODE::FRONT_INNER_BACK ); + pad->Padstack().Size( PADSTACK::INNER_LAYERS ) = + VECTOR2I( aElem.diameter_by_layer[1], aElem.diameter_by_layer[1] ); } else { @@ -3375,7 +3378,7 @@ void ALTIUM_PCB::ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, con } if( aElem.sizeAndShape ) - pad->SetOffset( aElem.sizeAndShape->holeoffset[0] ); + pad->SetOffset( PADSTACK::ALL_LAYERS, aElem.sizeAndShape->holeoffset[0] ); } PADSTACK& ps = pad->Padstack(); @@ -3383,6 +3386,8 @@ void ALTIUM_PCB::ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, con auto setCopperGeometry = [&]( PCB_LAYER_ID aLayer, ALTIUM_PAD_SHAPE aShape, const VECTOR2I& aSize ) { + int altLayer = CopperLayerToOrdinal( aLayer ); + ps.Size( aLayer ) = aSize; switch( aShape ) @@ -3393,10 +3398,10 @@ void ALTIUM_PCB::ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, con case ALTIUM_PAD_SHAPE::CIRCLE: if( aElem.sizeAndShape - && aElem.sizeAndShape->alt_shape[0] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT ) + && aElem.sizeAndShape->alt_shape[altLayer] == ALTIUM_PAD_SHAPE_ALT::ROUNDRECT ) { ps.SetShape( PAD_SHAPE::ROUNDRECT, aLayer ); // 100 = round, 0 = rectangular - double ratio = aElem.sizeAndShape->cornerradius[0] / 200.; + double ratio = aElem.sizeAndShape->cornerradius[altLayer] / 200.; ps.SetRoundRectRadiusRatio( ratio, aLayer ); } else if( aElem.topsize.x == aElem.topsize.y ) @@ -3450,42 +3455,33 @@ void ALTIUM_PCB::ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, con { case ALTIUM_PAD_MODE::SIMPLE: ps.SetMode( PADSTACK::MODE::NORMAL ); - setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize ); + setCopperGeometry( PADSTACK::ALL_LAYERS, aElem.topshape, aElem.topsize ); break; case ALTIUM_PAD_MODE::TOP_MIDDLE_BOTTOM: - ps.SetMode( PADSTACK::MODE::TOP_INNER_BOTTOM ); + ps.SetMode( PADSTACK::MODE::FRONT_INNER_BACK ); setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize ); - setCopperGeometry( In1_Cu, aElem.midshape, aElem.midsize ); + setCopperGeometry( PADSTACK::INNER_LAYERS, aElem.midshape, aElem.midsize ); setCopperGeometry( B_Cu, aElem.botshape, aElem.botsize ); break; case ALTIUM_PAD_MODE::FULL_STACK: ps.SetMode( PADSTACK::MODE::CUSTOM ); - setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize ); - if( !m_footprintName.IsEmpty() ) + setCopperGeometry( F_Cu, aElem.topshape, aElem.topsize ); + setCopperGeometry( B_Cu, aElem.botshape, aElem.botsize ); + setCopperGeometry( In1_Cu, aElem.midshape, aElem.midsize ); + + if( aElem.sizeAndShape ) { - if( m_reporter ) + size_t i = 0; + + for( PCB_LAYER_ID layer : LAYER_RANGE( In2_Cu, In30_Cu, MAX_CU_LAYERS ) ) { - wxString msg; - msg.Printf( _( "Error loading library '%s':\n" - "Footprint %s pad %s uses a complex pad stack (not yet supported)." ), - m_library, - m_footprintName, - aElem.name ); - m_reporter->Report( msg, RPT_SEVERITY_DEBUG ); - } - } - else - { - if( m_reporter ) - { - wxString msg; - msg.Printf( _( "Footprint %s pad %s uses a complex pad stack (not yet supported)." ), - aFootprint->GetReference(), - aElem.name ); - m_reporter->Report( msg, RPT_SEVERITY_DEBUG ); + setCopperGeometry( layer, aElem.sizeAndShape->inner_shape[i], + VECTOR2I( aElem.sizeAndShape->inner_size[i].x, + aElem.sizeAndShape->inner_size[i].y ) ); + i++; } } @@ -3495,9 +3491,9 @@ void ALTIUM_PCB::ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, con if( pad->GetAttribute() == PAD_ATTRIB::NPTH && pad->HasHole() ) { // KiCad likes NPTH pads to be the same size & shape as their holes - pad->SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE::CIRCLE ? PAD_SHAPE::CIRCLE - : PAD_SHAPE::OVAL ); - pad->SetSize( pad->GetDrillSize() ); + pad->SetShape( PADSTACK::ALL_LAYERS, pad->GetDrillShape() == PAD_DRILL_SHAPE::CIRCLE ? PAD_SHAPE::CIRCLE + : PAD_SHAPE::OVAL ); + pad->SetSize( PADSTACK::ALL_LAYERS, pad->GetDrillSize() ); } switch( aElem.layer ) @@ -4666,7 +4662,7 @@ void ALTIUM_PCB::ConvertFills6ToFootprintItemOnLayer( FOOTPRINT* aFootprint, con // Handle rotation multiples of 90 degrees if( rotation.IsCardinal() ) { - pad->SetShape( PAD_SHAPE::RECTANGLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); int width = std::abs( aElem.pos2.x - aElem.pos1.x ); int height = std::abs( aElem.pos2.y - aElem.pos1.y ); @@ -4675,19 +4671,19 @@ void ALTIUM_PCB::ConvertFills6ToFootprintItemOnLayer( FOOTPRINT* aFootprint, con if( rotation.IsCardinal90() ) std::swap( width, height ); - pad->SetSize( { width, height } ); + pad->SetSize( PADSTACK::ALL_LAYERS, { width, height } ); pad->SetPosition( aElem.pos1 / 2 + aElem.pos2 / 2 ); } else { - pad->SetShape( PAD_SHAPE::CUSTOM ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM ); int anchorSize = std::min( std::abs( aElem.pos2.x - aElem.pos1.x ), std::abs( aElem.pos2.y - aElem.pos1.y ) ); VECTOR2I anchorPos = aElem.pos1; - pad->SetAnchorPadShape( PAD_SHAPE::CIRCLE ); - pad->SetSize( { anchorSize, anchorSize } ); + pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } ); pad->SetPosition( anchorPos ); SHAPE_POLY_SET shapePolys; @@ -4701,7 +4697,7 @@ void ALTIUM_PCB::ConvertFills6ToFootprintItemOnLayer( FOOTPRINT* aFootprint, con VECTOR2I center( aElem.pos1.x / 2 + aElem.pos2.x / 2 - anchorPos.x, aElem.pos1.y / 2 + aElem.pos2.y / 2 - anchorPos.y ); shapePolys.Rotate( EDA_ANGLE( aElem.rotation, DEGREES_T ), center ); - pad->AddPrimitivePoly( shapePolys, 0, true ); + pad->AddPrimitivePoly( F_Cu, shapePolys, 0, true ); } pad->SetThermalSpokeAngle( ANGLE_90 ); diff --git a/pcbnew/pcb_io/cadstar/cadstar_pcb_archive_loader.cpp b/pcbnew/pcb_io/cadstar/cadstar_pcb_archive_loader.cpp index 8fbd3d8fca..daa9154abb 100644 --- a/pcbnew/pcb_io/cadstar/cadstar_pcb_archive_loader.cpp +++ b/pcbnew/pcb_io/cadstar/cadstar_pcb_archive_loader.cpp @@ -913,15 +913,15 @@ void CADSTAR_PCB_ARCHIVE_LOADER::loadLibraryCoppers( const SYMDEF_PCB& aComponen if( anchorSize <= 0 ) anchorSize = 1; - pad->SetShape( PAD_SHAPE::CUSTOM ); - pad->SetAnchorPadShape( PAD_SHAPE::CIRCLE ); - pad->SetSize( { anchorSize, anchorSize } ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM ); + pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, { anchorSize, anchorSize } ); pad->SetPosition( anchorPos ); SHAPE_POLY_SET shapePolys = getPolySetFromCadstarShape( compCopper.Shape, lineThickness, aFootprint ); shapePolys.Move( -anchorPos ); - pad->AddPrimitivePoly( shapePolys, 0, true ); + pad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, shapePolys, 0, true ); // Now renumber all the associated pads COMPONENT_PAD associatedPad; @@ -1133,30 +1133,32 @@ PAD* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadPad( const COMPONENT_PAD& aCadstarPad, { case PAD_SHAPE_TYPE::ANNULUS: //todo fix: use custom shape instead (Donught shape, i.e. a circle with a hole) - pad->SetShape( PAD_SHAPE::CIRCLE ); - pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), - getKiCadLength( csPadcode.Shape.Size ) } ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, { getKiCadLength( csPadcode.Shape.Size ), + getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::BULLET: - pad->SetShape( PAD_SHAPE::CHAMFERED_RECT ); - pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CHAMFERED_RECT ); + pad->SetSize( PADSTACK::ALL_LAYERS, + { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), - getKiCadLength( csPadcode.Shape.Size ) } ); - pad->SetChamferPositions( RECT_CHAMFER_POSITIONS::RECT_CHAMFER_BOTTOM_LEFT - | RECT_CHAMFER_POSITIONS::RECT_CHAMFER_TOP_LEFT ); - pad->SetRoundRectRadiusRatio( 0.5 ); - pad->SetChamferRectRatio( 0.0 ); + getKiCadLength( csPadcode.Shape.Size ) } ); + pad->SetChamferPositions( PADSTACK::ALL_LAYERS, + RECT_CHAMFER_POSITIONS::RECT_CHAMFER_BOTTOM_LEFT + | RECT_CHAMFER_POSITIONS::RECT_CHAMFER_TOP_LEFT ); + pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, 0.5 ); + pad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, 0.0 ); padOffset.x = getKiCadLength( ( (long long) csPadcode.Shape.LeftLength / 2 ) - ( (long long) csPadcode.Shape.RightLength / 2 ) ); break; case PAD_SHAPE_TYPE::CIRCLE: - pad->SetShape( PAD_SHAPE::CIRCLE ); - pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), - getKiCadLength( csPadcode.Shape.Size ) } ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, { getKiCadLength( csPadcode.Shape.Size ), + getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::DIAMOND: @@ -1164,9 +1166,9 @@ PAD* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadPad( const COMPONENT_PAD& aCadstarPad, // Cadstar diamond shape is a square rotated 45 degrees // We convert it in KiCad to a square with chamfered edges int sizeOfSquare = (double) getKiCadLength( csPadcode.Shape.Size ) * sqrt(2.0); - pad->SetShape( PAD_SHAPE::RECTANGLE ); - pad->SetChamferRectRatio( 0.5 ); - pad->SetSize( { sizeOfSquare, sizeOfSquare } ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); + pad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, 0.5 ); + pad->SetSize( PADSTACK::ALL_LAYERS, { sizeOfSquare, sizeOfSquare } ); padOffset.x = getKiCadLength( ( (long long) csPadcode.Shape.LeftLength / 2 ) - ( (long long) csPadcode.Shape.RightLength / 2 ) ); @@ -1174,42 +1176,46 @@ PAD* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadPad( const COMPONENT_PAD& aCadstarPad, break; case PAD_SHAPE_TYPE::FINGER: - pad->SetShape( PAD_SHAPE::OVAL ); - pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); + pad->SetSize( PADSTACK::ALL_LAYERS, + { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), - getKiCadLength( csPadcode.Shape.Size ) } ); + getKiCadLength( csPadcode.Shape.Size ) } ); padOffset.x = getKiCadLength( ( (long long) csPadcode.Shape.LeftLength / 2 ) - ( (long long) csPadcode.Shape.RightLength / 2 ) ); break; case PAD_SHAPE_TYPE::OCTAGON: - pad->SetShape( PAD_SHAPE::CHAMFERED_RECT ); - pad->SetChamferPositions( RECT_CHAMFER_POSITIONS::RECT_CHAMFER_ALL ); - pad->SetChamferRectRatio( 0.25 ); - pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), - getKiCadLength( csPadcode.Shape.Size ) } ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CHAMFERED_RECT ); + pad->SetChamferPositions( PADSTACK::ALL_LAYERS, RECT_CHAMFER_POSITIONS::RECT_CHAMFER_ALL ); + pad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, 0.25 ); + pad->SetSize( PADSTACK::ALL_LAYERS, { getKiCadLength( csPadcode.Shape.Size ), + getKiCadLength( csPadcode.Shape.Size ) } ); break; case PAD_SHAPE_TYPE::RECTANGLE: - pad->SetShape( PAD_SHAPE::RECTANGLE ); - pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, + { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), - getKiCadLength( csPadcode.Shape.Size ) } ); + getKiCadLength( csPadcode.Shape.Size ) } ); padOffset.x = getKiCadLength( ( (long long) csPadcode.Shape.LeftLength / 2 ) - ( (long long) csPadcode.Shape.RightLength / 2 ) ); break; case PAD_SHAPE_TYPE::ROUNDED_RECT: - pad->SetShape( PAD_SHAPE::ROUNDRECT ); - pad->SetRoundRectCornerRadius( getKiCadLength( csPadcode.Shape.InternalFeature ) ); - pad->SetSize( { getKiCadLength( (long long) csPadcode.Shape.Size + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::ROUNDRECT ); + pad->SetRoundRectCornerRadius( PADSTACK::ALL_LAYERS, + getKiCadLength( csPadcode.Shape.InternalFeature ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, + { getKiCadLength( (long long) csPadcode.Shape.Size + (long long) csPadcode.Shape.LeftLength + (long long) csPadcode.Shape.RightLength ), - getKiCadLength( csPadcode.Shape.Size ) } ); + getKiCadLength( csPadcode.Shape.Size ) } ); padOffset.x = getKiCadLength( ( (long long) csPadcode.Shape.LeftLength / 2 ) - ( (long long) csPadcode.Shape.RightLength / 2 ) ); @@ -1217,9 +1223,9 @@ PAD* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadPad( const COMPONENT_PAD& aCadstarPad, case PAD_SHAPE_TYPE::SQUARE: - pad->SetShape( PAD_SHAPE::RECTANGLE ); - pad->SetSize( { getKiCadLength( csPadcode.Shape.Size ), - getKiCadLength( csPadcode.Shape.Size ) } ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, { getKiCadLength( csPadcode.Shape.Size ), + getKiCadLength( csPadcode.Shape.Size ) } ); break; default: @@ -1287,10 +1293,10 @@ PAD* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadPad( const COMPONENT_PAD& aCadstarPad, if( editedPadOutline.Contains( { 0, 0 } ) ) { - pad->SetAnchorPadShape( PAD_SHAPE::RECTANGLE ); - pad->SetSize( VECTOR2I( { 4, 4 } ) ); - pad->SetShape( PAD_SHAPE::CUSTOM ); - pad->AddPrimitive( padShape ); + pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( { 4, 4 } ) ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM ); + pad->AddPrimitive( PADSTACK::ALL_LAYERS, padShape ); padOffset = { 0, 0 }; } else @@ -1312,12 +1318,12 @@ PAD* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadPad( const COMPONENT_PAD& aCadstarPad, { wxFAIL_MSG( wxT( "No copper layers defined in the pad?" ) ); csPadcode.SlotOrientation = 0; - pad->SetOffset( drillOffset ); + pad->SetOffset( PADSTACK::ALL_LAYERS, drillOffset ); } } else { - pad->SetOffset( drillOffset ); + pad->SetOffset( PADSTACK::ALL_LAYERS, drillOffset ); } EDA_ANGLE padOrientation = getAngle( aCadstarPad.OrientAngle ) diff --git a/pcbnew/pcb_io/eagle/pcb_io_eagle.cpp b/pcbnew/pcb_io/eagle/pcb_io_eagle.cpp index 36784c6cfd..6afbc17864 100644 --- a/pcbnew/pcb_io/eagle/pcb_io_eagle.cpp +++ b/pcbnew/pcb_io/eagle/pcb_io_eagle.cpp @@ -1988,25 +1988,25 @@ void PCB_IO_EAGLE::packagePad( FOOTPRINT* aFootprint, wxXmlNode* aTree ) switch( *e.shape ) { case EPAD::ROUND: - pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); break; case EPAD::OCTAGON: - pad->SetShape( PAD_SHAPE::CHAMFERED_RECT ); - pad->SetChamferPositions( RECT_CHAMFER_ALL ); - pad->SetChamferRectRatio( 1 - M_SQRT1_2 ); // Regular polygon + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CHAMFERED_RECT ); + pad->SetChamferPositions( PADSTACK::ALL_LAYERS, RECT_CHAMFER_ALL ); + pad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, 1 - M_SQRT1_2 ); // Regular polygon break; case EPAD::LONG: - pad->SetShape( PAD_SHAPE::OVAL ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); break; case EPAD::SQUARE: - pad->SetShape( PAD_SHAPE::RECTANGLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); break; case EPAD::OFFSET: - pad->SetShape( PAD_SHAPE::OVAL ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); break; } } @@ -2018,7 +2018,7 @@ void PCB_IO_EAGLE::packagePad( FOOTPRINT* aFootprint, wxXmlNode* aTree ) if( e.diameter && e.diameter->value > 0 ) { int diameter = e.diameter->ToPcbUnits(); - pad->SetSize( VECTOR2I( diameter, diameter ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( diameter, diameter ) ); } else { @@ -2026,20 +2026,20 @@ void PCB_IO_EAGLE::packagePad( FOOTPRINT* aFootprint, wxXmlNode* aTree ) double annulus = drillz * m_rules->rvPadTop; // copper annulus, eagle "restring" annulus = eagleClamp( m_rules->rlMinPadTop, annulus, m_rules->rlMaxPadTop ); int diameter = KiROUND( drillz + 2 * annulus ); - pad->SetSize( VECTOR2I( KiROUND( diameter ), KiROUND( diameter ) ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( KiROUND( diameter ), KiROUND( diameter ) ) ); } - if( pad->GetShape() == PAD_SHAPE::OVAL ) + if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::OVAL ) { // The Eagle "long" pad is wider than it is tall; m_elongation is percent elongation - VECTOR2I sz = pad->GetSize(); + VECTOR2I sz = pad->GetSize( PADSTACK::ALL_LAYERS ); sz.x = ( sz.x * ( 100 + m_rules->psElongationLong ) ) / 100; - pad->SetSize( sz ); + pad->SetSize( PADSTACK::ALL_LAYERS, sz ); if( e.shape && *e.shape == EPAD::OFFSET ) { int offset = KiROUND( ( sz.x - sz.y ) / 2.0 ); - pad->SetOffset( VECTOR2I( offset, 0 ) ); + pad->SetOffset( PADSTACK::ALL_LAYERS, VECTOR2I( offset, 0 ) ); } } @@ -2464,7 +2464,7 @@ void PCB_IO_EAGLE::packageHole( FOOTPRINT* aFootprint, wxXmlNode* aTree, bool aC PAD* pad = new PAD( aFootprint ); aFootprint->Add( pad ); - pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); pad->SetAttribute( PAD_ATTRIB::NPTH ); // Mechanical purpose only: @@ -2487,7 +2487,7 @@ void PCB_IO_EAGLE::packageHole( FOOTPRINT* aFootprint, wxXmlNode* aTree, bool aC VECTOR2I sz( e.drill.ToPcbUnits(), e.drill.ToPcbUnits() ); pad->SetDrillSize( sz ); - pad->SetSize( sz ); + pad->SetSize( PADSTACK::ALL_LAYERS, sz ); pad->SetLayerSet( LSET::AllCuMask().set( B_Mask ).set( F_Mask ) ); } @@ -2505,11 +2505,11 @@ void PCB_IO_EAGLE::packageSMD( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const aFootprint->Add( pad ); transferPad( e, pad ); - pad->SetShape( PAD_SHAPE::RECTANGLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); pad->SetAttribute( PAD_ATTRIB::SMD ); VECTOR2I padSize( e.dx.ToPcbUnits(), e.dy.ToPcbUnits() ); - pad->SetSize( padSize ); + pad->SetSize( PADSTACK::ALL_LAYERS, padSize ); pad->SetLayer( layer ); const LSET front( { F_Cu, F_Paste, F_Mask } ); @@ -2535,8 +2535,8 @@ void PCB_IO_EAGLE::packageSMD( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const if( e.roundness ) roundRatio = std::fmax( *e.roundness / 200.0, roundRatio ); - pad->SetShape( PAD_SHAPE::ROUNDRECT ); - pad->SetRoundRectRadiusRatio( roundRatio ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::ROUNDRECT ); + pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, roundRatio ); } if( e.rot ) @@ -2576,7 +2576,7 @@ void PCB_IO_EAGLE::transferPad( const EPAD_COMMON& aEaglePad, PAD* aPad ) const VECTOR2I padPos( kicad_x( aEaglePad.x ), kicad_y( aEaglePad.y ) ); // Solder mask - const VECTOR2I& padSize( aPad->GetSize() ); + const VECTOR2I& padSize( aPad->GetSize( PADSTACK::ALL_LAYERS ) ); aPad->SetLocalSolderMaskMargin( eagleClamp( m_rules->mlMinStopFrame, diff --git a/pcbnew/pcb_io/easyeda/pcb_io_easyeda_parser.cpp b/pcbnew/pcb_io/easyeda/pcb_io_easyeda_parser.cpp index ba3c6a6ce5..0d9c319025 100644 --- a/pcbnew/pcb_io/easyeda/pcb_io_easyeda_parser.cpp +++ b/pcbnew/pcb_io/easyeda/pcb_io_easyeda_parser.cpp @@ -850,8 +850,8 @@ void PCB_IO_EASYEDA_PARSER::ParseToBoardItemContainer( pad->SetPosition( center ); pad->SetLayerSet( PAD::PTHMask() ); pad->SetAttribute( PAD_ATTRIB::PTH ); - pad->SetShape( PAD_SHAPE::CIRCLE ); - pad->SetSize( VECTOR2I( kdia, kdia ) ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( kdia, kdia ) ); pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE ); pad->SetDrillSize( VECTOR2I( kdrill, kdrill ) ); @@ -909,8 +909,8 @@ void PCB_IO_EASYEDA_PARSER::ParseToBoardItemContainer( pad->SetPosition( center ); pad->SetLayerSet( PAD::UnplatedHoleMask() ); pad->SetAttribute( PAD_ATTRIB::NPTH ); - pad->SetShape( PAD_SHAPE::CIRCLE ); - pad->SetSize( VECTOR2I( kdia, kdia ) ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( kdia, kdia ) ); pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE ); pad->SetDrillSize( VECTOR2I( kdia, kdia ) ); @@ -955,7 +955,7 @@ void PCB_IO_EASYEDA_PARSER::ParseToBoardItemContainer( pad->SetNet( getOrAddNetItem( arr[7] ) ); pad->SetNumber( arr[8] ); pad->SetPosition( center ); - pad->SetSize( size ); + pad->SetSize( PADSTACK::ALL_LAYERS, size ); pad->SetOrientationDegrees( Convert( arr[11] ) ); pad->SetThermalSpokeAngle( ANGLE_0 ); @@ -991,24 +991,24 @@ void PCB_IO_EASYEDA_PARSER::ParseToBoardItemContainer( wxString padType = arr[1]; if( padType == wxS( "ELLIPSE" ) ) { - pad->SetShape( PAD_SHAPE::OVAL ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); } else if( padType == wxS( "RECT" ) ) { - pad->SetShape( PAD_SHAPE::RECTANGLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); } else if( padType == wxS( "OVAL" ) ) { if( pad->GetSizeX() == pad->GetSizeY() ) - pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); else - pad->SetShape( PAD_SHAPE::OVAL ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); } else if( padType == wxS( "POLYGON" ) ) { - pad->SetShape( PAD_SHAPE::CUSTOM ); - pad->SetAnchorPadShape( PAD_SHAPE::CIRCLE ); - pad->SetSize( { 1, 1 } ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM ); + pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, { 1, 1 } ); wxArrayString data = wxSplit( arr[10], ' ', '\0' ); @@ -1024,7 +1024,7 @@ void PCB_IO_EASYEDA_PARSER::ParseToBoardItemContainer( chain.Move( -center ); chain.Rotate( -pad->GetOrientation() ); - pad->AddPrimitivePoly( chain, 0, true ); + pad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, chain, 0, true ); } wxString holeDia = arr[9]; diff --git a/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp b/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp index 8c53bafbf1..a91b7cc14a 100644 --- a/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp +++ b/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp @@ -668,16 +668,16 @@ std::unique_ptr PCB_IO_EASYEDAPRO_PARSER::createPAD( FOOTPRINT* size.y = padShape.at( 2 ); double cr_p = padShape.size() > 3 ? padShape.at( 3 ).get() : 0; - pad->SetSize( ScaleSize( size ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, ScaleSize( size ) ); if( cr_p == 0 ) { - pad->SetShape( PAD_SHAPE::RECTANGLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); } else { - pad->SetShape( PAD_SHAPE::ROUNDRECT ); - pad->SetRoundRectRadiusRatio( cr_p / 100 ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::ROUNDRECT ); + pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, cr_p / 100 ); } } else if( padSh == wxS( "ELLIPSE" ) ) @@ -686,8 +686,8 @@ std::unique_ptr PCB_IO_EASYEDAPRO_PARSER::createPAD( FOOTPRINT* size.x = padShape.at( 1 ); size.y = padShape.at( 2 ); - pad->SetSize( ScaleSize( size ) ); - pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, ScaleSize( size ) ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); } else if( padSh == wxS( "OVAL" ) ) { @@ -695,14 +695,14 @@ std::unique_ptr PCB_IO_EASYEDAPRO_PARSER::createPAD( FOOTPRINT* size.x = padShape.at( 1 ); size.y = padShape.at( 2 ); - pad->SetSize( ScaleSize( size ) ); - pad->SetShape( PAD_SHAPE::OVAL ); + pad->SetSize( PADSTACK::ALL_LAYERS, ScaleSize( size ) ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); } else if( padSh == wxS( "POLY" ) ) { - pad->SetShape( PAD_SHAPE::CUSTOM ); - pad->SetAnchorPadShape( PAD_SHAPE::CIRCLE ); - pad->SetSize( { 1, 1 } ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM ); + pad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, { 1, 1 } ); nlohmann::json polyData = padShape.at( 1 ); @@ -716,7 +716,7 @@ std::unique_ptr PCB_IO_EASYEDAPRO_PARSER::createPAD( FOOTPRINT* shape->Move( -pad->GetPosition() ); - pad->AddPrimitive( shape.release() ); + pad->AddPrimitive( PADSTACK::ALL_LAYERS, shape.release() ); } } diff --git a/pcbnew/pcb_io/fabmaster/import_fabmaster.cpp b/pcbnew/pcb_io/fabmaster/import_fabmaster.cpp index 2be302f487..a3c0a9f06a 100644 --- a/pcbnew/pcb_io/fabmaster/import_fabmaster.cpp +++ b/pcbnew/pcb_io/fabmaster/import_fabmaster.cpp @@ -2289,7 +2289,7 @@ bool FABMASTER::loadFootprints( BOARD* aBoard ) { auto& pad = padstack->second; - newpad->SetShape( pad.shape ); + newpad->SetShape( PADSTACK::ALL_LAYERS, pad.shape ); if( pad.shape == PAD_SHAPE::CUSTOM ) { @@ -2297,7 +2297,8 @@ bool FABMASTER::loadFootprints( BOARD* aBoard ) // is fully hidden by the custom pad int pad_size = std::min( pad.width, pad.height ); - newpad->SetSize( VECTOR2I( pad_size / 2, pad_size / 2 ) ); + newpad->SetSize( PADSTACK::ALL_LAYERS, + VECTOR2I( pad_size / 2, pad_size / 2 ) ); std::string custom_name = pad.custom_name + "_" + pin->refdes + "_" + pin->pin_number; @@ -2359,7 +2360,7 @@ bool FABMASTER::loadFootprints( BOARD* aBoard ) wxLogError( _( "Invalid custom pad '%s'. Replacing with " "circular pad." ), custom_name.c_str() ); - newpad->SetShape( PAD_SHAPE::CIRCLE ); + newpad->SetShape( F_Cu, PAD_SHAPE::CIRCLE ); } else { @@ -2380,18 +2381,18 @@ bool FABMASTER::loadFootprints( BOARD* aBoard ) DEGREES_T ) ); } - newpad->AddPrimitivePoly( poly_outline, 0, true ); + newpad->AddPrimitivePoly( PADSTACK::ALL_LAYERS, poly_outline, 0, true ); } SHAPE_POLY_SET mergedPolygon; - newpad->MergePrimitivesAsPolygon( &mergedPolygon ); + newpad->MergePrimitivesAsPolygon( PADSTACK::ALL_LAYERS, &mergedPolygon ); if( mergedPolygon.OutlineCount() > 1 ) { wxLogError( _( "Invalid custom pad '%s'. Replacing with " "circular pad." ), custom_name.c_str() ); - newpad->SetShape( PAD_SHAPE::CIRCLE ); + newpad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); } } else @@ -2402,7 +2403,8 @@ bool FABMASTER::loadFootprints( BOARD* aBoard ) } else { - newpad->SetSize( VECTOR2I( pad.width, pad.height ) ); + newpad->SetSize( PADSTACK::ALL_LAYERS, + VECTOR2I( pad.width, pad.height ) ); } if( pad.drill ) diff --git a/pcbnew/pcb_io/geda/pcb_io_geda.cpp b/pcbnew/pcb_io/geda/pcb_io_geda.cpp index d4475f1b48..4a10adf344 100644 --- a/pcbnew/pcb_io/geda/pcb_io_geda.cpp +++ b/pcbnew/pcb_io/geda/pcb_io_geda.cpp @@ -510,7 +510,7 @@ FOOTPRINT* GPCB_FPL_CACHE::parseFOOTPRINT( LINE_READER* aLineReader ) static const LSET pad_front( { F_Cu, F_Mask, F_Paste } ); static const LSET pad_back( { B_Cu, B_Mask, B_Paste } ); - pad->SetShape( PAD_SHAPE::RECTANGLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); pad->SetAttribute( PAD_ATTRIB::SMD ); pad->SetLayerSet( pad_front ); @@ -558,17 +558,17 @@ FOOTPRINT* GPCB_FPL_CACHE::parseFOOTPRINT( LINE_READER* aLineReader ) VECTOR2I padPos( ( x1 + x2 ) / 2, ( y1 + y2 ) / 2 ); - pad->SetSize( VECTOR2I( delta.EuclideanNorm() + width, width ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( delta.EuclideanNorm() + width, width ) ); padPos += footprint->GetPosition(); pad->SetPosition( padPos ); if( !testFlags( parameters[paramCnt-2], 0x0100, wxT( "square" ) ) ) { - if( pad->GetSize().x == pad->GetSize().y ) - pad->SetShape( PAD_SHAPE::CIRCLE ); + if( pad->GetSize( PADSTACK::ALL_LAYERS ).x == pad->GetSize( PADSTACK::ALL_LAYERS ).y ) + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); else - pad->SetShape( PAD_SHAPE::OVAL ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); } if( pad->GetSizeX() > 0 && pad->GetSizeY() > 0 ) @@ -601,14 +601,14 @@ FOOTPRINT* GPCB_FPL_CACHE::parseFOOTPRINT( LINE_READER* aLineReader ) PAD* pad = new PAD( footprint.get() ); - pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); static const LSET pad_set = LSET::AllCuMask() | LSET( { F_SilkS, F_Mask, B_Mask } ); pad->SetLayerSet( pad_set ); if( testFlags( parameters[paramCnt-2], 0x0100, wxT( "square" ) ) ) - pad->SetShape( PAD_SHAPE::RECTANGLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); // Set the pad name: // Pcbnew pad name is used for electrical connection calculations. @@ -621,7 +621,7 @@ FOOTPRINT* GPCB_FPL_CACHE::parseFOOTPRINT( LINE_READER* aLineReader ) int padSize = parseInt( parameters[4], conv_unit ); - pad->SetSize( VECTOR2I( padSize, padSize ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( padSize, padSize ) ); int drillSize = 0; @@ -653,8 +653,11 @@ FOOTPRINT* GPCB_FPL_CACHE::parseFOOTPRINT( LINE_READER* aLineReader ) padPos += footprint->GetPosition(); pad->SetPosition( padPos ); - if( pad->GetShape() == PAD_SHAPE::CIRCLE && pad->GetSize().x != pad->GetSize().y ) - pad->SetShape( PAD_SHAPE::OVAL ); + if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE + && pad->GetSize( PADSTACK::ALL_LAYERS ).x != pad->GetSize( PADSTACK::ALL_LAYERS ).y ) + { + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); + } footprint->Add( pad ); continue; diff --git a/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp b/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp index 9a8534eb3c..b0ef404f47 100644 --- a/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp +++ b/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp @@ -392,8 +392,8 @@ void PCB_IO_IPC2581::addLocationNode( wxXmlNode* aNode, const PAD& aPad, bool aR else pos = aPad.GetPosition(); - if( aPad.GetOffset().x != 0 || aPad.GetOffset().y != 0 ) - pos += aPad.GetOffset(); + if( aPad.GetOffset( PADSTACK::ALL_LAYERS ).x != 0 || aPad.GetOffset( PADSTACK::ALL_LAYERS ).y != 0 ) + pos += aPad.GetOffset( PADSTACK::ALL_LAYERS ); addLocationNode( aNode, pos.x, pos.y ); } @@ -755,12 +755,12 @@ void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAY VECTOR2I expansion{ 0, 0 }; if( LSET( { F_Mask, B_Mask } ).Contains( aLayer ) ) - expansion.x = expansion.y = 2 * aPad.GetSolderMaskExpansion(); + expansion.x = expansion.y = 2 * aPad.GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ); if( LSET( { F_Paste, B_Paste } ).Contains( aLayer ) ) - expansion = 2 * aPad.GetSolderPasteMargin(); + expansion = 2 * aPad.GetSolderPasteMargin( PADSTACK::ALL_LAYERS ); - switch( aPad.GetShape() ) + switch( aPad.GetShape( PADSTACK::ALL_LAYERS ) ) { case PAD_SHAPE::CIRCLE: { @@ -785,7 +785,7 @@ void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAY addAttribute( entry_node, "id", name ); wxXmlNode* rect_node = appendNode( entry_node, "RectCenter" ); - VECTOR2D pad_size = aPad.GetSize() + expansion; + VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion; addAttribute( rect_node, "width", floatVal( m_scale * std::abs( pad_size.x ) ) ); addAttribute( rect_node, "height", floatVal( m_scale * std::abs( pad_size.y ) ) ); @@ -801,7 +801,7 @@ void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAY addAttribute( entry_node, "id", name ); wxXmlNode* oval_node = appendNode( entry_node, "Oval" ); - VECTOR2D pad_size = aPad.GetSize() + expansion; + VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion; addAttribute( oval_node, "width", floatVal( m_scale * pad_size.x ) ); addAttribute( oval_node, "height", floatVal( m_scale * pad_size.y ) ); @@ -817,11 +817,11 @@ void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAY addAttribute( entry_node, "id", name ); wxXmlNode* roundrect_node = appendNode( entry_node, "RectRound" ); - VECTOR2D pad_size = aPad.GetSize() + expansion; + VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion; addAttribute( roundrect_node, "width", floatVal( m_scale * pad_size.x ) ); addAttribute( roundrect_node, "height", floatVal( m_scale * pad_size.y ) ); roundrect_node->AddAttribute( "radius", - floatVal( m_scale * aPad.GetRoundRectCornerRadius() ) ); + floatVal( m_scale * aPad.GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS ) ) ); addAttribute( roundrect_node, "upperRight", "true" ); addAttribute( roundrect_node, "upperLeft", "true" ); addAttribute( roundrect_node, "lowerRight", "true" ); @@ -839,16 +839,16 @@ void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAY addAttribute( entry_node, "id", name ); wxXmlNode* chamfered_node = appendNode( entry_node, "RectCham" ); - VECTOR2D pad_size = aPad.GetSize() + expansion; + VECTOR2D pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ) + expansion; addAttribute( chamfered_node, "width", floatVal( m_scale * pad_size.x ) ); addAttribute( chamfered_node, "height", floatVal( m_scale * pad_size.y ) ); int shorterSide = std::min( pad_size.x, pad_size.y ); - int chamfer = std::max( 0, KiROUND( aPad.GetChamferRectRatio() * shorterSide ) ); + int chamfer = std::max( 0, KiROUND( aPad.GetChamferRectRatio( PADSTACK::ALL_LAYERS ) * shorterSide ) ); addAttribute( chamfered_node, "chamfer", floatVal( m_scale * chamfer ) ); - int positions = aPad.GetChamferPositions(); + int positions = aPad.GetChamferPositions( PADSTACK::ALL_LAYERS ); if( positions & RECT_CHAMFER_TOP_LEFT ) addAttribute( chamfered_node, "upperLeft", "true" ); @@ -870,8 +870,8 @@ void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAY wxXmlNode* entry_node = appendNode( m_shape_std_node, "EntryStandard" ); addAttribute( entry_node, "id", name ); - VECTOR2I pad_size = aPad.GetSize(); - VECTOR2I trap_delta = aPad.GetDelta(); + VECTOR2I pad_size = aPad.GetSize( PADSTACK::ALL_LAYERS ); + VECTOR2I trap_delta = aPad.GetDelta( PADSTACK::ALL_LAYERS ); SHAPE_POLY_SET outline; outline.NewOutline(); int dx = pad_size.x / 2; @@ -905,7 +905,7 @@ void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PAD& aPad, PCB_LAY addAttribute( entry_node, "id", name ); SHAPE_POLY_SET shape; - aPad.MergePrimitivesAsPolygon( &shape ); + aPad.MergePrimitivesAsPolygon( PADSTACK::ALL_LAYERS, &shape ); if( expansion != VECTOR2I( 0, 0 ) ) { @@ -1138,7 +1138,7 @@ void PCB_IO_IPC2581::addSlotCavity( wxXmlNode* aNode, const PAD& aPad, const wxS addLocationNode( slotNode, 0.0, 0.0 ); SHAPE_POLY_SET poly_set; - aPad.GetEffectiveShape()->TransformToPolygon( poly_set, 0, ERROR_INSIDE ); + aPad.GetEffectiveShape( PADSTACK::ALL_LAYERS )->TransformToPolygon( poly_set, 0, ERROR_INSIDE ); addOutlineNode( slotNode, poly_set ); } @@ -1671,7 +1671,7 @@ void PCB_IO_IPC2581::addVia( wxXmlNode* aContentNode, const PCB_VIA* aVia, PCB_L int hole = aVia->GetDrillValue(); dummy.SetDrillSize( VECTOR2I( hole, hole ) ); dummy.SetPosition( aVia->GetStart() ); - dummy.SetSize( VECTOR2I( aVia->GetWidth(), aVia->GetWidth() ) ); + dummy.SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( aVia->GetWidth(), aVia->GetWidth() ) ); addShape( padNode, dummy, aLayer ); } @@ -1709,7 +1709,7 @@ void PCB_IO_IPC2581::addPadStack( wxXmlNode* aPadNode, const PAD* aPad ) aPad->GetAttribute() == PAD_ATTRIB::PTH ? "PLATED" : "NONPLATED" ); addAttribute( padStackHoleNode, "plusTol", "0.0" ); addAttribute( padStackHoleNode, "minusTol", "0.0" ); - addXY( padStackHoleNode, aPad->GetOffset() ); + addXY( padStackHoleNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ) ); } LSEQ layer_seq = aPad->GetLayerSet().Seq(); @@ -1724,12 +1724,12 @@ void PCB_IO_IPC2581::addPadStack( wxXmlNode* aPadNode, const PAD* aPad ) wxXmlNode* padStackPadDefNode = appendNode( padStackDefNode, "PadstackPadDef" ); addAttribute( padStackPadDefNode, "layerRef", m_layer_name_map[layer] ); addAttribute( padStackPadDefNode, "padUse", "REGULAR" ); - addLocationNode( padStackPadDefNode, aPad->GetOffset().x, aPad->GetOffset().y ); + addLocationNode( padStackPadDefNode, aPad->GetOffset( PADSTACK::ALL_LAYERS ).x, aPad->GetOffset( PADSTACK::ALL_LAYERS ).y ); if( aPad->HasHole() || !aPad->FlashLayer( layer ) ) { PCB_SHAPE shape( nullptr, SHAPE_T::CIRCLE ); - shape.SetStart( aPad->GetOffset() ); + shape.SetStart( aPad->GetOffset( PADSTACK::ALL_LAYERS ) ); shape.SetEnd( shape.GetStart() + aPad->GetDrillSize() / 2 ); addShape( padStackPadDefNode, shape ); } diff --git a/pcbnew/pcb_io/kicad_legacy/pcb_io_kicad_legacy.cpp b/pcbnew/pcb_io/kicad_legacy/pcb_io_kicad_legacy.cpp index d2119f5395..fa13e9fbde 100644 --- a/pcbnew/pcb_io/kicad_legacy/pcb_io_kicad_legacy.cpp +++ b/pcbnew/pcb_io/kicad_legacy/pcb_io_kicad_legacy.cpp @@ -1099,7 +1099,7 @@ void PCB_IO_KICAD_LEGACY::loadSETUP() BIU x = biuParse( line + SZ( "PadSize" ), &data ); BIU y = biuParse( data ); - bds.m_Pad_Master->SetSize( VECTOR2I( x, y ) ); + bds.m_Pad_Master->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( x, y ) ); } else if( TESTLINE( "PadDrill" ) ) { @@ -1444,9 +1444,9 @@ void PCB_IO_KICAD_LEGACY::loadPAD( FOOTPRINT* aFootprint ) // chances are both were ASCII, but why take chances? pad->SetNumber( padNumber ); - pad->SetShape( static_cast( padshape ) ); - pad->SetSize( VECTOR2I( size_x, size_y ) ); - pad->SetDelta( VECTOR2I( delta_x, delta_y ) ); + pad->SetShape( PADSTACK::ALL_LAYERS, static_cast( padshape ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( size_x, size_y ) ); + pad->SetDelta( PADSTACK::ALL_LAYERS, VECTOR2I( delta_x, delta_y ) ); pad->SetOrientation( orient ); } else if( TESTLINE( "Dr" ) ) // (Dr)ill @@ -1476,7 +1476,7 @@ void PCB_IO_KICAD_LEGACY::loadPAD( FOOTPRINT* aFootprint ) } pad->SetDrillShape( drShape ); - pad->SetOffset( VECTOR2I( offs_x, offs_y ) ); + pad->SetOffset( PADSTACK::ALL_LAYERS, VECTOR2I( offs_x, offs_y ) ); pad->SetDrillSize( VECTOR2I( drill_x, drill_y ) ); } else if( TESTLINE( "At" ) ) // (At)tribute diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp index b6dc4e0d39..674f4d6b94 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -1508,21 +1509,25 @@ void PCB_IO_KICAD_SEXPR::formatLayers( LSET aLayerMask, int aNestLevel ) const void PCB_IO_KICAD_SEXPR::format( const PAD* aPad, int aNestLevel ) const { const BOARD* board = aPad->GetBoard(); - const char* shape; - switch( aPad->GetShape() ) - { - case PAD_SHAPE::CIRCLE: shape = "circle"; break; - case PAD_SHAPE::RECTANGLE: shape = "rect"; break; - case PAD_SHAPE::OVAL: shape = "oval"; break; - case PAD_SHAPE::TRAPEZOID: shape = "trapezoid"; break; - case PAD_SHAPE::CHAMFERED_RECT: - case PAD_SHAPE::ROUNDRECT: shape = "roundrect"; break; - case PAD_SHAPE::CUSTOM: shape = "custom"; break; - - default: - THROW_IO_ERROR( wxString::Format( _( "unknown pad type: %d"), aPad->GetShape() ) ); - } + auto shapeName = + [&]( PCB_LAYER_ID aLayer ) + { + switch( aPad->GetShape( aLayer ) ) + { + case PAD_SHAPE::CIRCLE: return "circle"; + case PAD_SHAPE::RECTANGLE: return "rect"; + case PAD_SHAPE::OVAL: return "oval"; + case PAD_SHAPE::TRAPEZOID: return "trapezoid"; + case PAD_SHAPE::CHAMFERED_RECT: + case PAD_SHAPE::ROUNDRECT: return "roundrect"; + case PAD_SHAPE::CUSTOM: return "custom"; + + default: + THROW_IO_ERROR( wxString::Format( _( "unknown pad type: %d"), + aPad->GetShape( aLayer ) ) ); + } + }; const char* type; @@ -1559,7 +1564,7 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad, int aNestLevel ) const m_out->Print( aNestLevel, "(pad %s %s %s", m_out->Quotew( aPad->GetNumber() ).c_str(), type, - shape ); + shapeName( PADSTACK::ALL_LAYERS ) ); m_out->Print( 0, " (at %s", formatInternalUnits( aPad->GetFPRelativePosition() ).c_str() ); @@ -1568,13 +1573,17 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad, int aNestLevel ) const m_out->Print( 0, ")" ); - m_out->Print( 0, " (size %s)", formatInternalUnits( aPad->GetSize() ).c_str() ); + m_out->Print( 0, " (size %s)", formatInternalUnits( aPad->GetSize( PADSTACK::ALL_LAYERS ) ).c_str() ); - if( (aPad->GetDelta().x) != 0 || (aPad->GetDelta().y != 0 ) ) - m_out->Print( 0, " (rect_delta %s)", formatInternalUnits( aPad->GetDelta() ).c_str() ); + if( aPad->GetDelta( PADSTACK::ALL_LAYERS ).x != 0 + || aPad->GetDelta( PADSTACK::ALL_LAYERS ).y != 0 ) + { + m_out->Print( 0, " (rect_delta %s)", + formatInternalUnits( aPad->GetDelta( PADSTACK::ALL_LAYERS ) ).c_str() ); + } VECTOR2I sz = aPad->GetDrillSize(); - VECTOR2I shapeoffset = aPad->GetOffset(); + VECTOR2I shapeoffset = aPad->GetOffset( PADSTACK::ALL_LAYERS ); if( (sz.x > 0) || (sz.y > 0) || (shapeoffset.x != 0) || (shapeoffset.y != 0) ) @@ -1590,8 +1599,15 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad, int aNestLevel ) const if( sz.y > 0 && sz.x != sz.y ) m_out->Print( 0, " %s", formatInternalUnits( sz.y ).c_str() ); - if( (shapeoffset.x != 0) || (shapeoffset.y != 0) ) - m_out->Print( 0, " (offset %s)", formatInternalUnits( aPad->GetOffset() ).c_str() ); + // NOTE: Shape offest is a property of the copper shape, not of the drill, but this was put + // in the file format under the drill section. So, it is left here to minimize file format + // changes, but note that the other padstack layers (if present) will have an offset stored + // separately. + if( shapeoffset.x != 0 || shapeoffset.y != 0 ) + { + m_out->Print( 0, " (offset %s)", + formatInternalUnits( aPad->GetOffset( PADSTACK::ALL_LAYERS ) ).c_str() ); + } m_out->Print( 0, ")" ); } @@ -1625,37 +1641,47 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad, int aNestLevel ) const } } - // Output the radius ratio for rounded and chamfered rect pads - if( aPad->GetShape() == PAD_SHAPE::ROUNDRECT || aPad->GetShape() == PAD_SHAPE::CHAMFERED_RECT) - { - m_out->Print( 0, " (roundrect_rratio %s)", - FormatDouble2Str( aPad->GetRoundRectRadiusRatio() ).c_str() ); - } + auto formatCornerProperties = + [&]( PCB_LAYER_ID aLayer ) + { + // Output the radius ratio for rounded and chamfered rect pads + if( aPad->GetShape( aLayer ) == PAD_SHAPE::ROUNDRECT + || aPad->GetShape( aLayer ) == PAD_SHAPE::CHAMFERED_RECT) + { + m_out->Print( 0, " (roundrect_rratio %s)", + FormatDouble2Str( aPad->GetRoundRectRadiusRatio( aLayer ) ).c_str() ); + } - // Output the chamfer corners for chamfered rect pads - if( aPad->GetShape() == PAD_SHAPE::CHAMFERED_RECT) - { - m_out->Print( 0, "\n" ); + // Output the chamfer corners for chamfered rect pads + if( aPad->GetShape( aLayer ) == PAD_SHAPE::CHAMFERED_RECT) + { + m_out->Print( 0, "\n" ); - m_out->Print( aNestLevel+1, "(chamfer_ratio %s)", - FormatDouble2Str( aPad->GetChamferRectRatio() ).c_str() ); + m_out->Print( aNestLevel+1, "(chamfer_ratio %s)", + FormatDouble2Str( aPad->GetChamferRectRatio( aLayer ) ).c_str() ); - m_out->Print( 0, " (chamfer" ); + m_out->Print( 0, " (chamfer" ); - if( ( aPad->GetChamferPositions() & RECT_CHAMFER_TOP_LEFT ) ) - m_out->Print( 0, " top_left" ); + if( ( aPad->GetChamferPositions( aLayer ) & RECT_CHAMFER_TOP_LEFT ) ) + m_out->Print( 0, " top_left" ); - if( ( aPad->GetChamferPositions() & RECT_CHAMFER_TOP_RIGHT ) ) - m_out->Print( 0, " top_right" ); + if( ( aPad->GetChamferPositions( aLayer ) & RECT_CHAMFER_TOP_RIGHT ) ) + m_out->Print( 0, " top_right" ); - if( ( aPad->GetChamferPositions() & RECT_CHAMFER_BOTTOM_LEFT ) ) - m_out->Print( 0, " bottom_left" ); + if( ( aPad->GetChamferPositions( aLayer ) & RECT_CHAMFER_BOTTOM_LEFT ) ) + m_out->Print( 0, " bottom_left" ); - if( ( aPad->GetChamferPositions() & RECT_CHAMFER_BOTTOM_RIGHT ) ) - m_out->Print( 0, " bottom_right" ); + if( ( aPad->GetChamferPositions( aLayer ) & RECT_CHAMFER_BOTTOM_RIGHT ) ) + m_out->Print( 0, " bottom_right" ); - m_out->Print( 0, ")" ); - } + m_out->Print( 0, ")" ); + } + + }; + + // For normal padstacks, this is the one and only set of properties. For complex ones, this + // will represent the front layer properties, and other layers will be formatted below + formatCornerProperties( PADSTACK::ALL_LAYERS ); std::string output; @@ -1727,8 +1753,9 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad, int aNestLevel ) const EDA_ANGLE defaultThermalSpokeAngle = ANGLE_90; - if( aPad->GetShape() == PAD_SHAPE::CIRCLE || - ( aPad->GetShape() == PAD_SHAPE::CUSTOM && aPad->GetAnchorPadShape() == PAD_SHAPE::CIRCLE ) ) + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE || + ( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM + && aPad->GetAnchorPadShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE ) ) { defaultThermalSpokeAngle = ANGLE_45; } @@ -1751,7 +1778,120 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad, int aNestLevel ) const m_out->Print( aNestLevel+1, "%s", output.c_str()+1 ); // +1 skips 1st space on 1st element } - if( aPad->GetShape() == PAD_SHAPE::CUSTOM ) + auto anchorShape = + [&]( PCB_LAYER_ID aLayer ) + { + switch( aPad->GetAnchorPadShape( aLayer ) ) + { + case PAD_SHAPE::RECTANGLE: return "rect"; + default: + case PAD_SHAPE::CIRCLE: return "circle"; + } + }; + + auto formatPrimitives = + [&]( PCB_LAYER_ID aLayer ) + { + m_out->Print( aNestLevel+1, "(primitives" ); + + int nested_level = aNestLevel+2; + + // Output all basic shapes + for( const std::shared_ptr& primitive : aPad->GetPrimitives( aLayer ) ) + { + m_out->Print( 0, "\n"); + + switch( primitive->GetShape() ) + { + case SHAPE_T::SEGMENT: + if( primitive->IsProxyItem() ) + { + m_out->Print( nested_level, "(gr_vector (start %s) (end %s)", + formatInternalUnits( primitive->GetStart() ).c_str(), + formatInternalUnits( primitive->GetEnd() ).c_str() ); + } + else + { + m_out->Print( nested_level, "(gr_line (start %s) (end %s)", + formatInternalUnits( primitive->GetStart() ).c_str(), + formatInternalUnits( primitive->GetEnd() ).c_str() ); + } + break; + + case SHAPE_T::RECTANGLE: + if( primitive->IsProxyItem() ) + { + m_out->Print( nested_level, "(gr_bbox (start %s) (end %s)", + formatInternalUnits( primitive->GetStart() ).c_str(), + formatInternalUnits( primitive->GetEnd() ).c_str() ); + } + else + { + m_out->Print( nested_level, "(gr_rect (start %s) (end %s)", + formatInternalUnits( primitive->GetStart() ).c_str(), + formatInternalUnits( primitive->GetEnd() ).c_str() ); + } + break; + + case SHAPE_T::ARC: + m_out->Print( nested_level, "(gr_arc (start %s) (mid %s) (end %s)", + formatInternalUnits( primitive->GetStart() ).c_str(), + formatInternalUnits( primitive->GetArcMid() ).c_str(), + formatInternalUnits( primitive->GetEnd() ).c_str() ); + break; + + case SHAPE_T::CIRCLE: + m_out->Print( nested_level, "(gr_circle (center %s) (end %s)", + formatInternalUnits( primitive->GetStart() ).c_str(), + formatInternalUnits( primitive->GetEnd() ).c_str() ); + break; + + case SHAPE_T::BEZIER: + m_out->Print( nested_level, "(gr_curve (pts (xy %s) (xy %s) (xy %s) (xy %s))", + formatInternalUnits( primitive->GetStart() ).c_str(), + formatInternalUnits( primitive->GetBezierC1() ).c_str(), + formatInternalUnits( primitive->GetBezierC2() ).c_str(), + formatInternalUnits( primitive->GetEnd() ).c_str() ); + break; + + case SHAPE_T::POLY: + if( primitive->IsPolyShapeValid() ) + { + const SHAPE_POLY_SET& poly = primitive->GetPolyShape(); + const SHAPE_LINE_CHAIN& outline = poly.Outline( 0 ); + + m_out->Print( nested_level, "(gr_poly\n" ); + formatPolyPts( outline, nested_level, ADVANCED_CFG::GetCfg().m_CompactSave ); + + // Align the next info at the right place. + m_out->Print( nested_level, " " ); + } + break; + + default: + break; + } + + if( !primitive->IsProxyItem() ) + m_out->Print( 0, " (width %s)", formatInternalUnits( primitive->GetWidth() ).c_str() ); + + // The filled flag represents if a solid fill is present on circles, + // rectangles and polygons + if( ( primitive->GetShape() == SHAPE_T::POLY ) + || ( primitive->GetShape() == SHAPE_T::RECTANGLE ) + || ( primitive->GetShape() == SHAPE_T::CIRCLE ) ) + { + KICAD_FORMAT::FormatBool( m_out, 0, "fill", primitive->IsFilled() ); + } + + m_out->Print( 0, ")" ); + } + + m_out->Print( 0, "\n" ); + m_out->Print( aNestLevel+1, ")" ); // end of (primitives + }; + + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM ) { m_out->Print( 0, "\n"); m_out->Print( aNestLevel+1, "(options" ); @@ -1764,114 +1904,13 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad, int aNestLevel ) const #endif // Output the anchor pad shape (circle/rect) - if( aPad->GetAnchorPadShape() == PAD_SHAPE::RECTANGLE ) - shape = "rect"; - else - shape = "circle"; - - m_out->Print( 0, " (anchor %s)", shape ); + m_out->Print( 0, " (anchor %s)", anchorShape( PADSTACK::ALL_LAYERS ) ); m_out->Print( 0, ")"); // end of (options ... // Output graphic primitive of the pad shape m_out->Print( 0, "\n"); - m_out->Print( aNestLevel+1, "(primitives" ); - - int nested_level = aNestLevel+2; - - // Output all basic shapes - for( const std::shared_ptr& primitive : aPad->GetPrimitives() ) - { - m_out->Print( 0, "\n"); - - switch( primitive->GetShape() ) - { - case SHAPE_T::SEGMENT: - if( primitive->IsProxyItem() ) - { - m_out->Print( nested_level, "(gr_vector (start %s) (end %s)", - formatInternalUnits( primitive->GetStart() ).c_str(), - formatInternalUnits( primitive->GetEnd() ).c_str() ); - } - else - { - m_out->Print( nested_level, "(gr_line (start %s) (end %s)", - formatInternalUnits( primitive->GetStart() ).c_str(), - formatInternalUnits( primitive->GetEnd() ).c_str() ); - } - break; - - case SHAPE_T::RECTANGLE: - if( primitive->IsProxyItem() ) - { - m_out->Print( nested_level, "(gr_bbox (start %s) (end %s)", - formatInternalUnits( primitive->GetStart() ).c_str(), - formatInternalUnits( primitive->GetEnd() ).c_str() ); - } - else - { - m_out->Print( nested_level, "(gr_rect (start %s) (end %s)", - formatInternalUnits( primitive->GetStart() ).c_str(), - formatInternalUnits( primitive->GetEnd() ).c_str() ); - } - break; - - case SHAPE_T::ARC: - m_out->Print( nested_level, "(gr_arc (start %s) (mid %s) (end %s)", - formatInternalUnits( primitive->GetStart() ).c_str(), - formatInternalUnits( primitive->GetArcMid() ).c_str(), - formatInternalUnits( primitive->GetEnd() ).c_str() ); - break; - - case SHAPE_T::CIRCLE: - m_out->Print( nested_level, "(gr_circle (center %s) (end %s)", - formatInternalUnits( primitive->GetStart() ).c_str(), - formatInternalUnits( primitive->GetEnd() ).c_str() ); - break; - - case SHAPE_T::BEZIER: - m_out->Print( nested_level, "(gr_curve (pts (xy %s) (xy %s) (xy %s) (xy %s))", - formatInternalUnits( primitive->GetStart() ).c_str(), - formatInternalUnits( primitive->GetBezierC1() ).c_str(), - formatInternalUnits( primitive->GetBezierC2() ).c_str(), - formatInternalUnits( primitive->GetEnd() ).c_str() ); - break; - - case SHAPE_T::POLY: - if( primitive->IsPolyShapeValid() ) - { - const SHAPE_POLY_SET& poly = primitive->GetPolyShape(); - const SHAPE_LINE_CHAIN& outline = poly.Outline( 0 ); - - m_out->Print( nested_level, "(gr_poly\n" ); - formatPolyPts( outline, nested_level, ADVANCED_CFG::GetCfg().m_CompactSave ); - - // Align the next info at the right place. - m_out->Print( nested_level, " " ); - } - break; - - default: - break; - } - - if( !primitive->IsProxyItem() ) - m_out->Print( 0, " (width %s)", formatInternalUnits( primitive->GetWidth() ).c_str() ); - - // The filled flag represents if a solid fill is present on circles, - // rectangles and polygons - if( ( primitive->GetShape() == SHAPE_T::POLY ) - || ( primitive->GetShape() == SHAPE_T::RECTANGLE ) - || ( primitive->GetShape() == SHAPE_T::CIRCLE ) ) - { - KICAD_FORMAT::FormatBool( m_out, 0, "fill", primitive->IsFilled() ); - } - - m_out->Print( 0, ")" ); - } - - m_out->Print( 0, "\n" ); - m_out->Print( aNestLevel+1, ")" ); // end of (primitives + formatPrimitives( PADSTACK::ALL_LAYERS ); } if( !isDefaultTeardropParameters( aPad->GetTeardropParams() ) ) @@ -1880,12 +1919,147 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad, int aNestLevel ) const formatTeardropParameters( aPad->GetTeardropParams(), aNestLevel+1 ); } + formatTenting( aPad->Padstack() ); + m_out->Print( 0, "\n" ); + + // TODO: Refactor so that we call formatPadLayer( ALL_LAYERS ) above instead of redundant code + auto formatPadLayer = + [&]( PCB_LAYER_ID aLayer ) + { + const PADSTACK& padstack = aPad->Padstack(); + + m_out->Print( 0, " (shape %s)", shapeName( aLayer ) ); + + m_out->Print( 0, " (size %s)", + formatInternalUnits( aPad->GetSize( aLayer ) ).c_str() ); + + const VECTOR2I& delta = aPad->GetDelta( aLayer ); + + if( delta.x != 0 || delta.y != 0 ) + m_out->Print( 0, " (rect_delta %s)", formatInternalUnits( delta ).c_str() ); + + shapeoffset = aPad->GetOffset( aLayer ); + + if( shapeoffset.x != 0 || shapeoffset.y != 0 ) + m_out->Print( 0, " (offset %s)", formatInternalUnits( shapeoffset ).c_str() ); + + formatCornerProperties( aLayer ); + + if( aPad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM ) + { + m_out->Print( aNestLevel + 1, "(options" ); + + // Output the anchor pad shape (circle/rect) + m_out->Print( 0, " (anchor %s)", anchorShape( aLayer ) ); + + m_out->Print( 0, ")" ); // end of (options ... + + // Output graphic primitive of the pad shape + formatPrimitives( aLayer ); + } + + EDA_ANGLE defaultLayerAngle = ANGLE_90; + + if( aPad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE || + ( aPad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM + && aPad->GetAnchorPadShape( aLayer ) == PAD_SHAPE::CIRCLE ) ) + { + defaultLayerAngle = ANGLE_45; + } + + EDA_ANGLE layerSpokeAngle = padstack.ThermalSpokeAngle( aLayer ); + + if( layerSpokeAngle != defaultLayerAngle ) + { + StrPrintf( &output, " (thermal_bridge_angle %s)", + EDA_UNIT_UTILS::FormatAngle( layerSpokeAngle ).c_str() ); + } + + if( padstack.ThermalGap( aLayer ).has_value() ) + { + StrPrintf( &output, " (thermal_gap %s)", + formatInternalUnits( *padstack.ThermalGap( aLayer ) ).c_str() ); + } + + if( padstack.ThermalSpokeWidth( aLayer ).has_value() ) + { + StrPrintf( &output, " (thermal_bridge_width %s)", + formatInternalUnits( *padstack.ThermalSpokeWidth( aLayer ) ).c_str() ); + } + + if( padstack.Clearance( aLayer ).has_value() ) + { + StrPrintf( &output, " (clearance %s)", + formatInternalUnits( *padstack.Clearance( aLayer ) ).c_str() ); + } + + if( padstack.ZoneConnection( aLayer ).has_value() ) + { + StrPrintf( &output, " (zone_connect %d)", + static_cast( *padstack.ZoneConnection( aLayer ) ) ); + } + }; + + + if( aPad->Padstack().Mode() != PADSTACK::MODE::NORMAL ) + { + std::string mode = + aPad->Padstack().Mode() == PADSTACK::MODE::CUSTOM ? "custom" : "front_inner_back"; + m_out->Print( 0, "(padstack (mode %s)", mode.c_str() ); + + if( aPad->Padstack().Mode() == PADSTACK::MODE::FRONT_INNER_BACK ) + { + m_out->Print( 0, "(layer \"Inner\"" ); + formatPadLayer( PADSTACK::INNER_LAYERS ); + m_out->Print( 0, ")(layer \"B.Cu\"" ); + formatPadLayer( B_Cu ); + m_out->Print( 0, ")" ); + } + else + { + for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, board->GetCopperLayerCount() ) ) + { + m_out->Print( 0, "(layer %s", m_out->Quotew( LSET::Name( layer ) ).c_str() ); + formatPadLayer( layer ); + m_out->Print( 0, ")" ); + } + } + + m_out->Print( 0, ")" ); + } + KICAD_FORMAT::FormatUuid( m_out, aPad->m_Uuid ); m_out->Print( aNestLevel, ")\n" ); } +void PCB_IO_KICAD_SEXPR::formatTenting( const PADSTACK& aPadstack ) const +{ + std::optional front = aPadstack.FrontOuterLayers().has_solder_mask; + std::optional back = aPadstack.BackOuterLayers().has_solder_mask; + + if( front.has_value() || back.has_value() ) + { + if( front.value_or( false ) || back.value_or( false ) ) + { + m_out->Print( 0, " (tenting " ); + + if( front.value_or( false ) ) + m_out->Print( 0, " front" ); + if( back.value_or( false ) ) + m_out->Print( 0, " back" ); + + m_out->Print( 0, ")" ); + } + else + { + m_out->Print( 0, " (tenting none)" ); + } + } +} + + void PCB_IO_KICAD_SEXPR::format( const PCB_TEXT* aText, int aNestLevel ) const { FOOTPRINT* parentFP = aText->GetParentFootprint(); @@ -2312,27 +2486,7 @@ void PCB_IO_KICAD_SEXPR::format( const PCB_TRACK* aTrack, int aNestLevel ) const m_out->Print( 0, ")" ); } - std::optional front = via->Padstack().FrontOuterLayers().has_solder_mask; - std::optional back = via->Padstack().BackOuterLayers().has_solder_mask; - - if( front.has_value() || back.has_value() ) - { - if( front.value_or( false ) || back.value_or( false ) ) - { - m_out->Print( 0, " (tenting " ); - - if( front.value_or( false ) ) - m_out->Print( 0, " front" ); - if( back.value_or( false ) ) - m_out->Print( 0, " back" ); - - m_out->Print( 0, ")" ); - } - else - { - m_out->Print( 0, " (tenting none)" ); - } - } + formatTenting( via->Padstack() ); if( !isDefaultTeardropParameters( via->GetTeardropParams() ) ) { diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h index c6a6dd3874..a5df29a4e6 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h @@ -49,6 +49,7 @@ class PCB_SHAPE; class PCB_REFERENCE_IMAGE; class PCB_TARGET; class PAD; +class PADSTACK; class PCB_GROUP; class PCB_GENERATOR; class PCB_TRACK; @@ -162,7 +163,8 @@ class PCB_IO_KICAD_SEXPR; // forward decl //#define SEXPR_BOARD_FILE_VERSION 20240703 // User layer types //#define SEXPR_BOARD_FILE_VERSION 20240706 // Embedded Files //#define SEXPR_BOARD_FILE_VERSION 20240819 // Embedded Files - Update hash algorithm to Murmur3 -#define SEXPR_BOARD_FILE_VERSION 20240928 // Component classes +//#define SEXPR_BOARD_FILE_VERSION 20240928 // Component classes +#define SEXPR_BOARD_FILE_VERSION 20240929 // Complex padstacks #define BOARD_FILE_HOST_VERSION 20200825 ///< Earlier files than this include the host tag #define LEGACY_ARC_FORMATTING 20210925 ///< These were the last to use old arc formatting @@ -443,6 +445,8 @@ private: void formatLayers( LSET aLayerMask, int aNestLevel = 0 ) const; + void formatTenting( const PADSTACK& aPadstack ) const; + friend class FP_CACHE; protected: diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp index d6452922fb..a78c8b9023 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -2350,7 +2351,7 @@ void PCB_IO_KICAD_SEXPR_PARSER::parseSetup() VECTOR2I sz; sz.x = parseBoardUnits( "master pad width" ); sz.y = parseBoardUnits( "master pad height" ); - bds.m_Pad_Master->SetSize( sz ); + bds.m_Pad_Master->SetSize( PADSTACK::ALL_LAYERS, sz ); m_board->m_LegacyDesignSettingsLoaded = true; NeedRIGHT(); break; @@ -4907,29 +4908,29 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) switch( token ) { case T_circle: - pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); break; case T_rect: - pad->SetShape( PAD_SHAPE::RECTANGLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); break; case T_oval: - pad->SetShape( PAD_SHAPE::OVAL ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); break; case T_trapezoid: - pad->SetShape( PAD_SHAPE::TRAPEZOID ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::TRAPEZOID ); break; case T_roundrect: // Note: the shape can be PAD_SHAPE::ROUNDRECT or PAD_SHAPE::CHAMFERED_RECT // (if chamfer parameters are found later in pad descr.) - pad->SetShape( PAD_SHAPE::ROUNDRECT ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::ROUNDRECT ); break; case T_custom: - pad->SetShape( PAD_SHAPE::CUSTOM ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM ); break; default: @@ -4956,7 +4957,7 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) case T_size: sz.x = parseBoardUnits( "width value" ); sz.y = parseBoardUnits( "height value" ); - pad->SetSize( sz ); + pad->SetSize( PADSTACK::ALL_LAYERS, sz ); NeedRIGHT(); break; @@ -4983,7 +4984,7 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) VECTOR2I delta; delta.x = parseBoardUnits( "rectangle delta width" ); delta.y = parseBoardUnits( "rectangle delta height" ); - pad->SetDelta( delta ); + pad->SetDelta( PADSTACK::ALL_LAYERS, delta ); NeedRIGHT(); break; } @@ -5023,7 +5024,7 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) case T_offset: pt.x = parseBoardUnits( "drill offset x" ); pt.y = parseBoardUnits( "drill offset y" ); - pad->SetOffset( pt ); + pad->SetOffset( PADSTACK::ALL_LAYERS, pt ); NeedRIGHT(); break; @@ -5167,15 +5168,16 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) break; case T_roundrect_rratio: - pad->SetRoundRectRadiusRatio( parseDouble( "roundrect radius ratio" ) ); + pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, + parseDouble( "roundrect radius ratio" ) ); NeedRIGHT(); break; case T_chamfer_ratio: - pad->SetChamferRectRatio( parseDouble( "chamfer ratio" ) ); + pad->SetChamferRectRatio( PADSTACK::ALL_LAYERS, parseDouble( "chamfer ratio" ) ); - if( pad->GetChamferRectRatio() > 0 ) - pad->SetShape( PAD_SHAPE::CHAMFERED_RECT ); + if( pad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) > 0 ) + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CHAMFERED_RECT ); NeedRIGHT(); break; @@ -5208,7 +5210,7 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) break; case T_RIGHT: - pad->SetChamferPositions( chamfers ); + pad->SetChamferPositions( PADSTACK::ALL_LAYERS, chamfers ); end_list = true; break; @@ -5218,8 +5220,8 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) } } - if( pad->GetChamferPositions() != RECT_NO_CHAMFER ) - pad->SetShape( PAD_SHAPE::CHAMFERED_RECT ); + if( pad->GetChamferPositions( PADSTACK::ALL_LAYERS ) != RECT_NO_CHAMFER ) + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CHAMFERED_RECT ); break; } @@ -5256,6 +5258,10 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) parsePAD_option( pad.get() ); break; + case T_padstack: + parsePadstack( pad.get() ); + break; + case T_primitives: for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { @@ -5270,14 +5276,14 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) case T_gr_rect: case T_gr_poly: case T_gr_curve: - pad->AddPrimitive( parsePCB_SHAPE( nullptr ) ); + pad->AddPrimitive( PADSTACK::ALL_LAYERS, parsePCB_SHAPE( nullptr ) ); break; case T_gr_bbox: { PCB_SHAPE* numberBox = parsePCB_SHAPE( nullptr ); numberBox->SetIsProxyItem(); - pad->AddPrimitive( numberBox ); + pad->AddPrimitive( PADSTACK::ALL_LAYERS, numberBox ); break; } @@ -5285,7 +5291,7 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) { PCB_SHAPE* spokeTemplate = parsePCB_SHAPE( nullptr ); spokeTemplate->SetIsProxyItem(); - pad->AddPrimitive( spokeTemplate ); + pad->AddPrimitive( PADSTACK::ALL_LAYERS, spokeTemplate ); break; } @@ -5348,7 +5354,7 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) Expecting( "at, locked, drill, layers, net, die_length, roundrect_rratio, " "solder_mask_margin, solder_paste_margin, solder_paste_margin_ratio, uuid, " "clearance, tstamp, primitives, remove_unused_layers, keep_end_layers, " - "pinfunction, pintype, zone_connect, thermal_width, thermal_gap or " + "pinfunction, pintype, zone_connect, thermal_width, thermal_gap, padstack or " "teardrops" ); } } @@ -5366,12 +5372,12 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) else { // This is here because custom pad anchor shape isn't known before reading (options - if( pad->GetShape() == PAD_SHAPE::CIRCLE ) + if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE ) { pad->SetThermalSpokeAngle( ANGLE_45 ); } - else if( pad->GetShape() == PAD_SHAPE::CUSTOM - && pad->GetAnchorPadShape() == PAD_SHAPE::CIRCLE ) + else if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM + && pad->GetAnchorPadShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE ) { if( m_requiredVersion <= 20211014 ) // 6.0 pad->SetThermalSpokeAngle( ANGLE_90 ); @@ -5394,7 +5400,8 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) // Zero-sized pads are likely algorithmically unsafe. if( pad->GetSizeX() <= 0 || pad->GetSizeY() <= 0 ) { - pad->SetSize( VECTOR2I( pcbIUScale.mmToIU( 0.001 ), pcbIUScale.mmToIU( 0.001 ) ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, + VECTOR2I( pcbIUScale.mmToIU( 0.001 ), pcbIUScale.mmToIU( 0.001 ) ) ); wxLogWarning( _( "Invalid zero-sized pad pinned to %s in\nfile: %s\nline: %d\noffset: %d" ), wxT( "1µm" ), CurSource(), CurLineNumber(), CurOffset() ); @@ -5428,7 +5435,7 @@ bool PCB_IO_KICAD_SEXPR_PARSER::parsePAD_option( PAD* aPad ) break; case T_rect: - aPad->SetAnchorPadShape( PAD_SHAPE::RECTANGLE ); + aPad->SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); break; default: @@ -5476,6 +5483,353 @@ bool PCB_IO_KICAD_SEXPR_PARSER::parsePAD_option( PAD* aPad ) } +void PCB_IO_KICAD_SEXPR_PARSER::parsePadstack( PAD* aPad ) +{ + PADSTACK& padstack = aPad->Padstack(); + + for( T token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + switch( token ) + { + case T_mode: + token = NextTok(); + + switch( token ) + { + case T_front_inner_back: + padstack.SetMode( PADSTACK::MODE::FRONT_INNER_BACK ); + break; + + case T_custom: + padstack.SetMode( PADSTACK::MODE::CUSTOM ); + break; + + default: + Expecting( "front_inner_back or custom" ); + } + + NeedRIGHT(); + break; + + case T_layer: + { + NextTok(); + PCB_LAYER_ID curLayer = UNDEFINED_LAYER; + + if( curText == "Inner" ) + { + if( padstack.Mode() != PADSTACK::MODE::FRONT_INNER_BACK ) + { + THROW_IO_ERROR( wxString::Format( _( "Invalid padstack layer in\nfile: %s\n" + "line: %d\noffset: %d." ), + CurSource(), CurLineNumber(), CurOffset() ) ); + } + + curLayer = PADSTACK::INNER_LAYERS; + } + else + { + curLayer = lookUpLayer( m_layerIndices ); + } + + if( !IsCopperLayer( curLayer ) ) + { + wxString error; + error.Printf( _( "Invalid padstack layer '%s' in file '%s' at line %d, offset %d." ), + curText, CurSource().GetData(), CurLineNumber(), CurOffset() ); + THROW_IO_ERROR( error ); + } + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + switch( token ) + { + case T_shape: + token = NextTok(); + + switch( token ) + { + case T_circle: + aPad->SetShape( curLayer, PAD_SHAPE::CIRCLE ); + break; + + case T_rect: + aPad->SetShape( curLayer, PAD_SHAPE::RECTANGLE ); + break; + + case T_oval: + aPad->SetShape( curLayer, PAD_SHAPE::OVAL ); + break; + + case T_trapezoid: + aPad->SetShape( curLayer, PAD_SHAPE::TRAPEZOID ); + break; + + case T_roundrect: + // Note: the shape can be PAD_SHAPE::ROUNDRECT or PAD_SHAPE::CHAMFERED_RECT + // (if chamfer parameters are found later in pad descr.) + aPad->SetShape( curLayer, PAD_SHAPE::ROUNDRECT ); + break; + + case T_custom: + aPad->SetShape( curLayer, PAD_SHAPE::CUSTOM ); + break; + + default: + Expecting( "circle, rectangle, roundrect, oval, trapezoid or custom" ); + } + + NeedRIGHT(); + break; + + case T_size: + { + VECTOR2I sz; + sz.x = parseBoardUnits( "width value" ); + sz.y = parseBoardUnits( "height value" ); + aPad->SetSize( curLayer, sz ); + NeedRIGHT(); + break; + } + + case T_offset: + { + VECTOR2I pt; + pt.x = parseBoardUnits( "drill offset x" ); + pt.y = parseBoardUnits( "drill offset y" ); + aPad->SetOffset( curLayer, pt ); + NeedRIGHT(); + break; + } + + case T_rect_delta: + { + VECTOR2I delta; + delta.x = parseBoardUnits( "rectangle delta width" ); + delta.y = parseBoardUnits( "rectangle delta height" ); + aPad->SetDelta( curLayer, delta ); + NeedRIGHT(); + break; + } + + case T_roundrect_rratio: + aPad->SetRoundRectRadiusRatio( curLayer, + parseDouble( "roundrect radius ratio" ) ); + NeedRIGHT(); + break; + + case T_chamfer_ratio: + { + double ratio = parseDouble( "chamfer ratio" ); + aPad->SetChamferRectRatio( curLayer, ratio ); + + if( ratio > 0 ) + aPad->SetShape( curLayer, PAD_SHAPE::CHAMFERED_RECT ); + + NeedRIGHT(); + break; + } + + case T_chamfer: + { + int chamfers = 0; + bool end_list = false; + + while( !end_list ) + { + token = NextTok(); + + switch( token ) + { + case T_top_left: + chamfers |= RECT_CHAMFER_TOP_LEFT; + break; + + case T_top_right: + chamfers |= RECT_CHAMFER_TOP_RIGHT; + break; + + case T_bottom_left: + chamfers |= RECT_CHAMFER_BOTTOM_LEFT; + break; + + case T_bottom_right: + chamfers |= RECT_CHAMFER_BOTTOM_RIGHT; + break; + + case T_RIGHT: + aPad->SetChamferPositions( curLayer, chamfers ); + end_list = true; + break; + + default: + Expecting( "chamfer_top_left chamfer_top_right chamfer_bottom_left or " + "chamfer_bottom_right" ); + } + } + + if( end_list && chamfers != RECT_NO_CHAMFER ) + aPad->SetShape( curLayer, PAD_SHAPE::CHAMFERED_RECT ); + + break; + } + + case T_thermal_bridge_width: + padstack.ThermalSpokeWidth( curLayer ) = + parseBoardUnits( "thermal relief spoke width" ); + NeedRIGHT(); + break; + + case T_thermal_gap: + padstack.ThermalGap( curLayer ) = parseBoardUnits( "thermal relief gap value" ); + NeedRIGHT(); + break; + + case T_thermal_bridge_angle: + padstack.SetThermalSpokeAngle( + EDA_ANGLE( parseDouble( "thermal spoke angle" ), DEGREES_T ) ); + NeedRIGHT(); + break; + + case T_zone_connect: + padstack.ZoneConnection( curLayer ) = magic_enum::enum_cast( + parseInt( "zone connection value" ) ); + NeedRIGHT(); + break; + + case T_clearance: + padstack.Clearance( curLayer ) = parseBoardUnits( "local clearance value" ); + NeedRIGHT(); + break; + + case T_tenting: + parseTenting( padstack ); + break; + + // TODO: refactor parsePAD_options to work on padstacks too + case T_options: + { + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + switch( token ) + { + case T_anchor: + token = NextTok(); + // Custom shaped pads have a "anchor pad", which is the reference + // for connection calculations. + // Because this is an anchor, only the 2 very basic shapes are managed: + // circle and rect. The default is circle + switch( token ) + { + case T_circle: // default + break; + + case T_rect: + padstack.SetAnchorShape( PAD_SHAPE::RECTANGLE, curLayer ); + break; + + default: + // Currently, because pad options is a moving target + // just skip unknown keywords + break; + } + NeedRIGHT(); + break; + + case T_clearance: + token = NextTok(); + // TODO: m_customShapeInZoneMode is not per-layer at the moment + NeedRIGHT(); + break; + + default: + // Currently, because pad options is a moving target + // just skip unknown keywords + while( ( token = NextTok() ) != T_RIGHT ) + { + } + + break; + } + } + + break; + } + + // TODO: deduplicate with non-padstack parser + case T_primitives: + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token == T_LEFT ) + token = NextTok(); + + switch( token ) + { + case T_gr_arc: + case T_gr_line: + case T_gr_circle: + case T_gr_rect: + case T_gr_poly: + case T_gr_curve: + padstack.AddPrimitive( parsePCB_SHAPE( nullptr ), curLayer ); + break; + + case T_gr_bbox: + { + PCB_SHAPE* numberBox = parsePCB_SHAPE( nullptr ); + numberBox->SetIsProxyItem(); + padstack.AddPrimitive( numberBox, curLayer ); + break; + } + + case T_gr_vector: + { + PCB_SHAPE* spokeTemplate = parsePCB_SHAPE( nullptr ); + spokeTemplate->SetIsProxyItem(); + padstack.AddPrimitive( spokeTemplate, curLayer ); + break; + } + + default: + Expecting( "gr_line, gr_arc, gr_circle, gr_curve, gr_rect, gr_bbox or gr_poly" ); + break; + } + } + + break; + + default: + // Not strict-parsing padstack layers yet + continue; + } + } + + break; + } + + default: + Expecting( "mode or layer" ); + break; + } + } +} + + void PCB_IO_KICAD_SEXPR_PARSER::parseGROUP_members( GROUP_INFO& aGroupInfo ) { T token; @@ -5983,26 +6337,8 @@ PCB_VIA* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_VIA() break; case T_tenting: - { - bool front = false; - bool back = false; - - // If the via has a tenting token, it means this individual via has a tenting override - for( token = NextTok(); token != T_RIGHT; token = NextTok() ) - { - if( token == T_front ) - front = true; - else if( token == T_back ) - back = true; - else if( token != T_none ) - Expecting( "front, back, or none" ); - } - - via->Padstack().FrontOuterLayers().has_solder_mask = front; - via->Padstack().BackOuterLayers().has_solder_mask = back; - + parseTenting( via->Padstack() ); break; - } case T_tstamp: case T_uuid: @@ -6035,6 +6371,27 @@ PCB_VIA* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_VIA() } +void PCB_IO_KICAD_SEXPR_PARSER::parseTenting( PADSTACK& aPadstack ) +{ + bool front = false; + bool back = false; + + // If there is a tenting token, it means this individual pad/via has a tenting override + for( T token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token == T_front ) + front = true; + else if( token == T_back ) + back = true; + else if( token != T_none ) + Expecting( "front, back, or none" ); + } + + aPadstack.FrontOuterLayers().has_solder_mask = front; + aPadstack.BackOuterLayers().has_solder_mask = back; +} + + ZONE* PCB_IO_KICAD_SEXPR_PARSER::parseZONE( BOARD_ITEM_CONTAINER* aParent ) { wxCHECK_MSG( CurTok() == T_zone, nullptr, diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.h b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.h index dfec3f7529..3c176929a4 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.h +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.h @@ -232,6 +232,7 @@ private: // Parse only the (option ...) inside a pad description bool parsePAD_option( PAD* aPad ); + void parsePadstack( PAD* aPad ); PCB_ARC* parseARC(); PCB_TRACK* parsePCB_TRACK(); @@ -318,6 +319,8 @@ private: */ void parseRenderCache( EDA_TEXT* text ); + void parseTenting( PADSTACK& aPadstack ); + FP_3DMODEL* parse3DModel(); /** diff --git a/pcbnew/pcb_io/odbpp/odb_eda_data.cpp b/pcbnew/pcb_io/odbpp/odb_eda_data.cpp index a269c0a850..9b28ee8f3a 100644 --- a/pcbnew/pcb_io/odbpp/odb_eda_data.cpp +++ b/pcbnew/pcb_io/odbpp/odb_eda_data.cpp @@ -292,9 +292,10 @@ void EDA_DATA::PACKAGE::AddPin( const PAD* aPad, size_t aPinNum ) VECTOR2D relpos = aPad->GetFPRelativePosition(); // TODO: is odb pkg pin center means center of pad hole or center of pad shape? + // TODO(JE) padstacks - if( aPad->GetOffset().x != 0 || aPad->GetOffset().y != 0 ) - relpos += aPad->GetOffset(); + if( aPad->GetOffset( PADSTACK::ALL_LAYERS ).x != 0 || aPad->GetOffset( PADSTACK::ALL_LAYERS ).y != 0 ) + relpos += aPad->GetOffset( PADSTACK::ALL_LAYERS ); pin->m_center = ODB::AddXY( relpos ); @@ -332,7 +333,8 @@ void EDA_DATA::PACKAGE::AddPin( const PAD* aPad, size_t aPinNum ) pin->mtype = PIN::MOUNT_TYPE::UNDEFINED; } - const std::shared_ptr& polygons = aPad->GetEffectivePolygon( ERROR_INSIDE ); + const std::shared_ptr& polygons = + aPad->GetEffectivePolygon( PADSTACK::ALL_LAYERS, ERROR_INSIDE ); // TODO: Here we put all pad shapes as polygonl, we should switch by pad shape // Note:pad only use polygons->Polygon(0), diff --git a/pcbnew/pcb_io/odbpp/odb_entity.cpp b/pcbnew/pcb_io/odbpp/odb_entity.cpp index 5db7f082eb..e0a15169e0 100644 --- a/pcbnew/pcb_io/odbpp/odb_entity.cpp +++ b/pcbnew/pcb_io/odbpp/odb_entity.cpp @@ -943,12 +943,12 @@ void ODB_STEP_ENTITY::MakeLayerEntity() ( LSET( { F_Paste, B_Paste } ) & LSET( { layer } ) ).any(); if( onSolderMaskLayer ) - margin.x = margin.y = pad->GetSolderMaskExpansion(); + margin.x = margin.y = pad->GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ); if( onSolderPasteLayer ) - margin = pad->GetSolderPasteMargin(); + margin = pad->GetSolderPasteMargin( PADSTACK::ALL_LAYERS ); - VECTOR2I padPlotsSize = pad->GetSize() + margin * 2; + VECTOR2I padPlotsSize = pad->GetSize( PADSTACK::ALL_LAYERS ) + margin * 2; if( onCopperLayer && !pad->IsOnCopperLayer() ) continue; @@ -956,7 +956,7 @@ void ODB_STEP_ENTITY::MakeLayerEntity() if( onCopperLayer && !pad->FlashLayer( layer ) ) continue; - if( pad->GetShape() != PAD_SHAPE::CUSTOM + if( pad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::CUSTOM && ( padPlotsSize.x <= 0 || padPlotsSize.y <= 0 ) ) continue; diff --git a/pcbnew/pcb_io/odbpp/odb_feature.cpp b/pcbnew/pcb_io/odbpp/odb_feature.cpp index 976b3203fe..db14bfad32 100644 --- a/pcbnew/pcb_io/odbpp/odb_feature.cpp +++ b/pcbnew/pcb_io/odbpp/odb_feature.cpp @@ -231,22 +231,24 @@ void FEATURES_MANAGER::AddPadShape( const PAD& aPad, PCB_LAYER_ID aLayer ) VECTOR2I expansion{ 0, 0 }; + // TODO(JE) padstacks + if( aLayer != UNDEFINED_LAYER && LSET( { F_Mask, B_Mask } ).Contains( aLayer ) ) - expansion.x = expansion.y = aPad.GetSolderMaskExpansion(); + expansion.x = expansion.y = aPad.GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ); if( aLayer != UNDEFINED_LAYER && LSET( { F_Paste, B_Paste } ).Contains( aLayer ) ) - expansion = aPad.GetSolderPasteMargin(); + expansion = aPad.GetSolderPasteMargin( PADSTACK::ALL_LAYERS ); int mask_clearance = expansion.x; - VECTOR2I plotSize = aPad.GetSize() + 2 * expansion; + VECTOR2I plotSize = aPad.GetSize( PADSTACK::ALL_LAYERS ) + 2 * expansion; - VECTOR2I center = aPad.ShapePos(); + VECTOR2I center = aPad.ShapePos( PADSTACK::ALL_LAYERS ); wxString width = ODB::SymDouble2String( std::abs( plotSize.x ) ); wxString height = ODB::SymDouble2String( std::abs( plotSize.y ) ); - switch( aPad.GetShape() ) + switch( aPad.GetShape( PADSTACK::ALL_LAYERS ) ) { case PAD_SHAPE::CIRCLE: { @@ -282,7 +284,7 @@ void FEATURES_MANAGER::AddPadShape( const PAD& aPad, PCB_LAYER_ID aLayer ) } case PAD_SHAPE::ROUNDRECT: { - wxString rad = ODB::SymDouble2String( aPad.GetRoundRectCornerRadius() ); + wxString rad = ODB::SymDouble2String( aPad.GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS ) ); AddFeature( ODB::AddXY( center ), AddRoundRectSymbol( width, height, rad ), aPad.GetOrientation(), mirror ); @@ -291,10 +293,11 @@ void FEATURES_MANAGER::AddPadShape( const PAD& aPad, PCB_LAYER_ID aLayer ) } case PAD_SHAPE::CHAMFERED_RECT: { - int shorterSide = std::min( plotSize.x, plotSize.y ); - int chamfer = std::max( 0, KiROUND( aPad.GetChamferRectRatio() * shorterSide ) ); + int shorterSide = std::min( plotSize.x, plotSize.y ); + int chamfer = std::max( + 0, KiROUND( aPad.GetChamferRectRatio( PADSTACK::ALL_LAYERS ) * shorterSide ) ); wxString rad = ODB::SymDouble2String( chamfer ); - int positions = aPad.GetChamferPositions(); + int positions = aPad.GetChamferPositions( PADSTACK::ALL_LAYERS ); AddFeature( ODB::AddXY( center ), AddChamferRectSymbol( width, height, rad, positions ), @@ -325,7 +328,7 @@ void FEATURES_MANAGER::AddPadShape( const PAD& aPad, PCB_LAYER_ID aLayer ) case PAD_SHAPE::CUSTOM: { SHAPE_POLY_SET shape; - aPad.MergePrimitivesAsPolygon( &shape ); + aPad.MergePrimitivesAsPolygon( PADSTACK::ALL_LAYERS, &shape ); // as for custome shape, odb++ don't rotate the polygon, // so we rotate the polygon in kicad anticlockwise @@ -692,12 +695,13 @@ void FEATURES_MANAGER::InitFeatureList( PCB_LAYER_ID aLayer, std::vectorGetDrillSizeX() == pad->GetDrillSizeY() ) - dummy.SetShape( PAD_SHAPE::CIRCLE ); // round hole shape + dummy.SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); // round hole shape else - dummy.SetShape( PAD_SHAPE::OVAL ); // slot hole shape + dummy.SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); // slot hole shape - dummy.SetOffset( VECTOR2I( 0, 0 ) ); // use hole position not pad position - dummy.SetSize( pad->GetDrillSize() ); + dummy.SetOffset( PADSTACK::ALL_LAYERS, + VECTOR2I( 0, 0 ) ); // use hole position not pad position + dummy.SetSize( PADSTACK::ALL_LAYERS, pad->GetDrillSize() ); AddPadShape( dummy, aLayer ); @@ -772,7 +776,7 @@ void FEATURES_MANAGER::AddVia( const PCB_VIA* aVia, PCB_LAYER_ID aLayer ) int hole = aVia->GetDrillValue(); dummy.SetDrillSize( VECTOR2I( hole, hole ) ); dummy.SetPosition( aVia->GetStart() ); - dummy.SetSize( VECTOR2I( aVia->GetWidth(), aVia->GetWidth() ) ); + dummy.SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( aVia->GetWidth(), aVia->GetWidth() ) ); AddPadShape( dummy, aLayer ); } @@ -783,7 +787,7 @@ void FEATURES_MANAGER::AddViaDrillHole( const PCB_VIA* aVia, PCB_LAYER_ID aLayer PAD dummy( nullptr ); // default pad shape is circle int hole = aVia->GetDrillValue(); dummy.SetPosition( aVia->GetStart() ); - dummy.SetSize( VECTOR2I( hole, hole ) ); + dummy.SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( hole, hole ) ); AddPadShape( dummy, aLayer ); } diff --git a/pcbnew/pcb_io/odbpp/odb_netlist.cpp b/pcbnew/pcb_io/odbpp/odb_netlist.cpp index de5acfc7c7..8ac3435d5f 100644 --- a/pcbnew/pcb_io/odbpp/odb_netlist.cpp +++ b/pcbnew/pcb_io/odbpp/odb_netlist.cpp @@ -109,13 +109,13 @@ void ODB_NET_LIST::InitPadNetPoints( BOARD* net_point.mechanical = ( pad->GetAttribute() == PAD_ATTRIB::NPTH ); net_point.x_location = pad->GetPosition().x - origin.x; net_point.y_location = origin.y - pad->GetPosition().y; - net_point.x_size = pad->GetSize().x; + net_point.x_size = pad->GetSize( PADSTACK::ALL_LAYERS ).x; // Rule: round pads have y = 0 - if( pad->GetShape() == PAD_SHAPE::CIRCLE ) + if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE ) net_point.y_size = net_point.x_size; else - net_point.y_size = pad->GetSize().y; + net_point.y_size = pad->GetSize( PADSTACK::ALL_LAYERS ).y; // net_point.rotation = ( ANGLE_360 - pad->GetOrientation() ).Normalize().AsDegrees(); diff --git a/pcbnew/pcb_io/pcad/pcad_pad.cpp b/pcbnew/pcb_io/pcad/pcad_pad.cpp index 4fab67d569..5f0e4b5178 100644 --- a/pcbnew/pcb_io/pcad/pcad_pad.cpp +++ b/pcbnew/pcb_io/pcad/pcad_pad.cpp @@ -206,12 +206,12 @@ void PCAD_PAD::AddToFootprint( FOOTPRINT* aFootprint, const EDA_ANGLE& aRotation if( !m_IsHolePlated && m_Hole ) { // mechanical hole - pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); pad->SetAttribute( PAD_ATTRIB::NPTH ); pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE ); pad->SetDrillSize( VECTOR2I( m_Hole, m_Hole ) ); - pad->SetSize( VECTOR2I( m_Hole, m_Hole ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( m_Hole, m_Hole ) ); // Mounting Hole: Solder Mask Margin from Top Layer Width size. // Used the default zone clearance (simplify) @@ -270,29 +270,29 @@ void PCAD_PAD::AddToFootprint( FOOTPRINT* aFootprint, const EDA_ANGLE& aRotation || padShapeName.IsSameAs( wxT( "MtHole" ), false ) ) { if( width != height ) - pad->SetShape( PAD_SHAPE::OVAL ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::OVAL ); else - pad->SetShape( PAD_SHAPE::CIRCLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); } else if( padShapeName.IsSameAs( wxT( "Rect" ), false ) ) { - pad->SetShape( PAD_SHAPE::RECTANGLE ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); } else if( padShapeName.IsSameAs( wxT( "RndRect" ), false ) ) { - pad->SetShape( PAD_SHAPE::ROUNDRECT ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::ROUNDRECT ); } else if( padShapeName.IsSameAs( wxT( "Polygon" ), false ) ) { - pad->SetShape( PAD_SHAPE::RECTANGLE ); // approximation + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::RECTANGLE ); // approximation } - pad->SetSize( VECTOR2I( width, height ) ); - pad->SetDelta( VECTOR2I( 0, 0 ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( width, height ) ); + pad->SetDelta( PADSTACK::ALL_LAYERS, VECTOR2I( 0, 0 ) ); pad->SetOrientation( m_Rotation + aRotation ); pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE ); - pad->SetOffset( VECTOR2I( 0, 0 ) ); + pad->SetOffset( PADSTACK::ALL_LAYERS, VECTOR2I( 0, 0 ) ); pad->SetDrillSize( VECTOR2I( m_Hole, m_Hole ) ); pad->SetAttribute( padType ); diff --git a/pcbnew/pcb_painter.cpp b/pcbnew/pcb_painter.cpp index e294ca76e9..58e68ad871 100644 --- a/pcbnew/pcb_painter.cpp +++ b/pcbnew/pcb_painter.cpp @@ -1194,6 +1194,7 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer ) { const BOARD* board = aPad->GetBoard(); COLOR4D color = m_pcbSettings.GetColor( aPad, aLayer ); + const PCB_LAYER_ID& pcbLayer = static_cast( aLayer ); if( IsNetnameLayer( aLayer ) ) { @@ -1252,16 +1253,16 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer ) } } } - else if( aPad->GetShape() == PAD_SHAPE::CUSTOM ) + else if( aPad->GetShape( pcbLayer ) == PAD_SHAPE::CUSTOM ) { // See if we have a number box - for( const std::shared_ptr& primitive : aPad->GetPrimitives() ) + for( const std::shared_ptr& primitive : aPad->GetPrimitives( pcbLayer ) ) { if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::RECTANGLE ) { position = primitive->GetCenter(); RotatePoint( position, aPad->GetOrientation() ); - position += aPad->ShapePos(); + position += aPad->ShapePos( pcbLayer ); padsize.x = abs( primitive->GetBotRight().x - primitive->GetTopLeft().x ); padsize.y = abs( primitive->GetBotRight().y - primitive->GetTopLeft().y ); @@ -1275,10 +1276,11 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer ) } } - if( aPad->GetShape() != PAD_SHAPE::CUSTOM ) + if( aPad->GetShape( pcbLayer ) != PAD_SHAPE::CUSTOM ) { // Don't allow a 45° rotation to bloat a pad's bounding box unnecessarily - double limit = std::min( aPad->GetSize().x, aPad->GetSize().y ) * 1.1; + double limit = std::min( aPad->GetSize( pcbLayer ).x, + aPad->GetSize( pcbLayer ).y ) * 1.1; if( padsize.x > limit && padsize.y > limit ) { @@ -1353,8 +1355,11 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer ) // Round and oval pads have less room to display the net name than other // (i.e RECT) shapes, so reduce the text size for these shapes - if( aPad->GetShape() == PAD_SHAPE::CIRCLE || aPad->GetShape() == PAD_SHAPE::OVAL ) + if( aPad->GetShape( pcbLayer ) == PAD_SHAPE::CIRCLE + || aPad->GetShape( pcbLayer ) == PAD_SHAPE::OVAL ) + { tsize *= 0.9; + } VECTOR2D namesize( tsize*Xscale_for_stroked_font, tsize ); textpos.y = std::min( tsize * 1.4, double( Y_offset_netname ) ); @@ -1463,19 +1468,19 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer ) if( drawShape ) { - VECTOR2I pad_size = aPad->GetSize(); + VECTOR2I pad_size = aPad->GetSize( pcbLayer ); VECTOR2I margin; switch( aLayer ) { case F_Mask: case B_Mask: - margin.x = margin.y = aPad->GetSolderMaskExpansion(); + margin.x = margin.y = aPad->GetSolderMaskExpansion( pcbLayer ); break; case F_Paste: case B_Paste: - margin = aPad->GetSolderPasteMargin(); + margin = aPad->GetSolderPasteMargin( pcbLayer ); break; default: @@ -1491,8 +1496,9 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer ) if( simpleShapes ) { - if( ( margin.x != margin.y && aPad->GetShape() != PAD_SHAPE::CUSTOM ) - || ( aPad->GetShape() == PAD_SHAPE::ROUNDRECT && ( margin.x < 0 || margin.y < 0 ) ) ) + if( ( margin.x != margin.y && aPad->GetShape( pcbLayer ) != PAD_SHAPE::CUSTOM ) + || ( aPad->GetShape( pcbLayer ) == PAD_SHAPE::ROUNDRECT + && ( margin.x < 0 || margin.y < 0 ) ) ) { // Our algorithms below (polygon inflation in particular) can't handle differential // inflation along separate axes. So for those cases we build a dummy pad instead, @@ -1508,27 +1514,30 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer ) if( dummyPad->GetParentGroup() ) dummyPad->GetParentGroup()->RemoveItem( dummyPad.get() ); - int initial_radius = dummyPad->GetRoundRectCornerRadius(); + int initial_radius = dummyPad->GetRoundRectCornerRadius( pcbLayer ); - dummyPad->SetSize( pad_size + margin + margin ); + dummyPad->SetSize( pcbLayer, pad_size + margin + margin ); - if( dummyPad->GetShape() == PAD_SHAPE::ROUNDRECT ) + if( dummyPad->GetShape( pcbLayer ) == PAD_SHAPE::ROUNDRECT ) { // To keep the right margin around the corners, we need to modify the corner radius. // We must have only one radius correction, so use the smallest absolute margin. int radius_margin = std::max( margin.x, margin.y ); // radius_margin is < 0 - dummyPad->SetRoundRectCornerRadius( std::max( initial_radius + radius_margin, 0 ) ); + dummyPad->SetRoundRectCornerRadius( + pcbLayer, std::max( initial_radius + radius_margin, 0 ) ); } - shapes = std::dynamic_pointer_cast( dummyPad->GetEffectiveShape() ); + shapes = std::dynamic_pointer_cast( + dummyPad->GetEffectiveShape( pcbLayer ) ); margin.x = margin.y = 0; } else { - shapes = std::dynamic_pointer_cast( aPad->GetEffectiveShape() ); + shapes = std::dynamic_pointer_cast( + aPad->GetEffectiveShape( pcbLayer ) ); } - if( aPad->GetShape() == PAD_SHAPE::CUSTOM && ( margin.x || margin.y ) ) + if( aPad->GetShape( pcbLayer ) == PAD_SHAPE::CUSTOM && ( margin.x || margin.y ) ) { // We can't draw as shapes because we don't know which edges are internal and which // are external (so we don't know when to apply the margin and when not to). @@ -1706,7 +1715,7 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer ) if( aPad->FlashLayer( activeLayer ) && clearance > 0 ) { - auto shape = std::dynamic_pointer_cast( aPad->GetEffectiveShape() ); + auto shape = std::dynamic_pointer_cast( aPad->GetEffectiveShape( pcbLayer ) ); if( shape && shape->Size() == 1 && shape->Shapes()[0]->Type() == SH_SEGMENT ) { diff --git a/pcbnew/pcb_track.cpp b/pcbnew/pcb_track.cpp index 3745528e7b..54f8d9db7b 100644 --- a/pcbnew/pcb_track.cpp +++ b/pcbnew/pcb_track.cpp @@ -100,7 +100,7 @@ PCB_VIA::PCB_VIA( BOARD_ITEM* aParent ) : m_padStack.LayerSet().reset(); // For now, vias are always circles - m_padStack.SetShape( PAD_SHAPE::CIRCLE ); + m_padStack.SetShape( PAD_SHAPE::CIRCLE, PADSTACK::ALL_LAYERS ); for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, BoardCopperLayerCount() ) ) m_zoneLayerOverrides[layer] = ZLO_NONE; @@ -311,13 +311,13 @@ double PCB_VIA::Similarity( const BOARD_ITEM& aOther ) const void PCB_VIA::SetWidth( int aWidth ) { - m_padStack.Size() = { aWidth, aWidth }; + m_padStack.Size( PADSTACK::ALL_LAYERS ) = { aWidth, aWidth }; } int PCB_VIA::GetWidth() const { - return m_padStack.Size().x; + return m_padStack.Size( PADSTACK::ALL_LAYERS ).x; } @@ -447,7 +447,7 @@ bool PCB_VIA::Deserialize( const google::protobuf::Any &aContainer ) return false; // We don't yet support complex padstacks for vias - SetWidth( m_padStack.Size().x ); + SetWidth( m_padStack.Size( PADSTACK::ALL_LAYERS ).x ); SetViaType( FromProtoEnum( via.type() ) ); SetNetCode( via.net().code().value() ); SetLocked( via.locked() == kiapi::common::types::LockedState::LS_LOCKED ); diff --git a/pcbnew/plot_board_layers.cpp b/pcbnew/plot_board_layers.cpp index 92fbcb8175..71515ce278 100644 --- a/pcbnew/plot_board_layers.cpp +++ b/pcbnew/plot_board_layers.cpp @@ -336,6 +336,8 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, if( onCopperLayer && !pad->FlashLayer( aLayerMask ) ) continue; + // TODO(JE) padstacks - different behavior for single layer or multilayer + COLOR4D color = COLOR4D::BLACK; // If we're plotting a single layer, the color for that layer can be used directly. @@ -372,10 +374,10 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, width_adj = itemplotter.getFineWidthAdj(); if( onSolderMaskLayer ) - margin.x = margin.y = pad->GetSolderMaskExpansion(); + margin.x = margin.y = pad->GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ); if( onSolderPasteLayer ) - margin = pad->GetSolderPasteMargin(); + margin = pad->GetSolderPasteMargin( PADSTACK::ALL_LAYERS ); // not all shapes can have a different margin for x and y axis // in fact only oval and rect shapes can have different values. @@ -384,33 +386,33 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, int mask_clearance = margin.x; // Now offset the pad size by margin + width_adj - VECTOR2I padPlotsSize = pad->GetSize() + margin * 2 + VECTOR2I( width_adj, width_adj ); + VECTOR2I padPlotsSize = pad->GetSize( PADSTACK::ALL_LAYERS ) + margin * 2 + VECTOR2I( width_adj, width_adj ); // Store these parameters that can be modified to plot inflated/deflated pads shape - PAD_SHAPE padShape = pad->GetShape(); - VECTOR2I padSize = pad->GetSize(); - VECTOR2I padDelta = pad->GetDelta(); // has meaning only for trapezoidal pads + PAD_SHAPE padShape = pad->GetShape( PADSTACK::ALL_LAYERS ); + VECTOR2I padSize = pad->GetSize( PADSTACK::ALL_LAYERS ); + VECTOR2I padDelta = pad->GetDelta( PADSTACK::ALL_LAYERS ); // has meaning only for trapezoidal pads // CornerRadius and CornerRadiusRatio can be modified // the radius is built from the ratio, so saving/restoring the ratio is enough - double padCornerRadiusRatio = pad->GetRoundRectRadiusRatio(); + double padCornerRadiusRatio = pad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ); // Don't draw a 0 sized pad. // Note: a custom pad can have its pad anchor with size = 0 - if( pad->GetShape() != PAD_SHAPE::CUSTOM + if( pad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::CUSTOM && ( padPlotsSize.x <= 0 || padPlotsSize.y <= 0 ) ) { continue; } - switch( pad->GetShape() ) + switch( pad->GetShape( PADSTACK::ALL_LAYERS ) ) { case PAD_SHAPE::CIRCLE: case PAD_SHAPE::OVAL: - pad->SetSize( padPlotsSize ); + pad->SetSize( PADSTACK::ALL_LAYERS, padPlotsSize ); if( aPlotOpt.GetSkipPlotNPTH_Pads() && ( aPlotOpt.GetDrillMarksType() == DRILL_MARKS::NO_DRILL_SHAPE ) && - ( pad->GetSize() == pad->GetDrillSize() ) && + ( pad->GetSize( PADSTACK::ALL_LAYERS ) == pad->GetDrillSize() ) && ( pad->GetAttribute() == PAD_ATTRIB::NPTH ) ) { break; @@ -420,12 +422,12 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, break; case PAD_SHAPE::RECTANGLE: - pad->SetSize( padPlotsSize ); + pad->SetSize( PADSTACK::ALL_LAYERS, padPlotsSize ); if( mask_clearance > 0 ) { - pad->SetShape( PAD_SHAPE::ROUNDRECT ); - pad->SetRoundRectCornerRadius( mask_clearance ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::ROUNDRECT ); + pad->SetRoundRectCornerRadius( PADSTACK::ALL_LAYERS, mask_clearance ); } itemplotter.PlotPad( pad, color, padPlotMode ); @@ -444,8 +446,8 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, else { PAD dummy( *pad ); - dummy.SetAnchorPadShape( PAD_SHAPE::CIRCLE ); - dummy.SetShape( PAD_SHAPE::CUSTOM ); + dummy.SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + dummy.SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM ); SHAPE_POLY_SET outline; outline.NewOutline(); int dx = padSize.x / 2; @@ -464,12 +466,12 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, CORNER_STRATEGY::ROUND_ALL_CORNERS, maxError, SHAPE_POLY_SET::PM_FAST ); dummy.DeletePrimitivesList(); - dummy.AddPrimitivePoly( outline, 0, true ); + dummy.AddPrimitivePoly( PADSTACK::ALL_LAYERS, outline, 0, true ); // Be sure the anchor pad is not bigger than the deflated shape because this // anchor will be added to the pad shape when plotting the pad. So now the // polygonal shape is built, we can clamp the anchor size - dummy.SetSize( VECTOR2I( 0, 0 ) ); + dummy.SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( 0, 0 ) ); itemplotter.PlotPad( &dummy, color, padPlotMode ); } @@ -481,9 +483,9 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, // rounding is stored as a percent, but we have to update this ratio // to force recalculation of other values after size changing (we do not // really change the rounding percent value) - double radius_ratio = pad->GetRoundRectRadiusRatio(); - pad->SetSize( padPlotsSize ); - pad->SetRoundRectRadiusRatio( radius_ratio ); + double radius_ratio = pad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ); + pad->SetSize( PADSTACK::ALL_LAYERS, padPlotsSize ); + pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, radius_ratio ); itemplotter.PlotPad( pad, color, padPlotMode ); break; @@ -493,7 +495,7 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, if( mask_clearance == 0 ) { // the size can be slightly inflated by width_adj (PS/PDF only) - pad->SetSize( padPlotsSize ); + pad->SetSize( PADSTACK::ALL_LAYERS, padPlotsSize ); itemplotter.PlotPad( pad, color, padPlotMode ); } else @@ -506,7 +508,7 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, // pad offset and orientation 0. The actual pos, offset and rotation will be // taken in account later by the plot function dummy.SetPosition( VECTOR2I( 0, 0 ) ); - dummy.SetOffset( VECTOR2I( 0, 0 ) ); + dummy.SetOffset( PADSTACK::ALL_LAYERS, VECTOR2I( 0, 0 ) ); dummy.SetOrientation( ANGLE_0 ); SHAPE_POLY_SET outline; dummy.TransformShapeToPolygon( outline, UNDEFINED_LAYER, 0, maxError, @@ -516,18 +518,18 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, SHAPE_POLY_SET::PM_FAST ); // Initialize the dummy pad shape: - dummy.SetAnchorPadShape( PAD_SHAPE::CIRCLE ); - dummy.SetShape( PAD_SHAPE::CUSTOM ); + dummy.SetAnchorPadShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + dummy.SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CUSTOM ); dummy.DeletePrimitivesList(); - dummy.AddPrimitivePoly( outline, 0, true ); + dummy.AddPrimitivePoly( PADSTACK::ALL_LAYERS, outline, 0, true ); // Be sure the anchor pad is not bigger than the deflated shape because this // anchor will be added to the pad shape when plotting the pad. // So we set the anchor size to 0 - dummy.SetSize( VECTOR2I( 0, 0 ) ); + dummy.SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( 0, 0 ) ); // Restore pad position and offset dummy.SetPosition( pad->GetPosition() ); - dummy.SetOffset( pad->GetOffset() ); + dummy.SetOffset( PADSTACK::ALL_LAYERS, pad->GetOffset( PADSTACK::ALL_LAYERS ) ); dummy.SetOrientation( pad->GetOrientation() ); itemplotter.PlotPad( &dummy, color, padPlotMode ); @@ -543,7 +545,7 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, dummy.SetParentGroup( nullptr ); SHAPE_POLY_SET shape; - pad->MergePrimitivesAsPolygon( &shape ); + pad->MergePrimitivesAsPolygon( PADSTACK::ALL_LAYERS, &shape ); // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate() // which can create bad shapes if margin.x is < 0 @@ -551,13 +553,13 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, CORNER_STRATEGY::ROUND_ALL_CORNERS, maxError, SHAPE_POLY_SET::PM_FAST ); dummy.DeletePrimitivesList(); - dummy.AddPrimitivePoly( shape, 0, true ); + dummy.AddPrimitivePoly( PADSTACK::ALL_LAYERS, shape, 0, true ); // Be sure the anchor pad is not bigger than the deflated shape because this // anchor will be added to the pad shape when plotting the pad. So now the // polygonal shape is built, we can clamp the anchor size if( mask_clearance < 0 ) // we expect margin.x = margin.y for custom pads - dummy.SetSize( padPlotsSize ); + dummy.SetSize( PADSTACK::ALL_LAYERS, padPlotsSize ); itemplotter.PlotPad( &dummy, color, padPlotMode ); break; @@ -565,10 +567,10 @@ void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask, } // Restore the pad parameters modified by the plot code - pad->SetSize( padSize ); - pad->SetDelta( padDelta ); - pad->SetShape( padShape ); - pad->SetRoundRectRadiusRatio( padCornerRadiusRatio ); + pad->SetSize( PADSTACK::ALL_LAYERS, padSize ); + pad->SetDelta( PADSTACK::ALL_LAYERS, padDelta ); + pad->SetShape( PADSTACK::ALL_LAYERS, padShape ); + pad->SetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS, padCornerRadiusRatio ); } if( footprint->IsDNP() @@ -989,8 +991,7 @@ void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask, ERROR_OUTSIDE ); // add shapes inflated by aMinThickness/2 in areas - item->TransformShapeToPolygon( areas, layer, inflate, maxError, - ERROR_OUTSIDE ); + item->TransformShapeToPolygon( areas, layer, inflate, maxError, ERROR_OUTSIDE ); } } } diff --git a/pcbnew/plot_brditems_plotter.cpp b/pcbnew/plot_brditems_plotter.cpp index b187bf0fd0..8ca1a24b12 100644 --- a/pcbnew/plot_brditems_plotter.cpp +++ b/pcbnew/plot_brditems_plotter.cpp @@ -84,16 +84,17 @@ void BRDITEMS_PLOTTER::PlotPadNumber( const PAD* aPad, const COLOR4D& aColor ) VECTOR2I position = padBBox.Centre(); VECTOR2I padsize = padBBox.GetSize(); - if( aPad->GetShape() == PAD_SHAPE::CUSTOM ) + // TODO(JE) padstacks + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM ) { // See if we have a number box - for( const std::shared_ptr& primitive : aPad->GetPrimitives() ) + for( const std::shared_ptr& primitive : aPad->GetPrimitives( PADSTACK::ALL_LAYERS ) ) { if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::RECTANGLE ) { position = primitive->GetCenter(); RotatePoint( position, aPad->GetOrientation() ); - position += aPad->ShapePos(); + position += aPad->ShapePos( PADSTACK::ALL_LAYERS ); padsize.x = abs( primitive->GetBotRight().x - primitive->GetTopLeft().x ); padsize.y = abs( primitive->GetBotRight().y - primitive->GetTopLeft().y ); @@ -103,10 +104,11 @@ void BRDITEMS_PLOTTER::PlotPadNumber( const PAD* aPad, const COLOR4D& aColor ) } } - if( aPad->GetShape() != PAD_SHAPE::CUSTOM ) + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::CUSTOM ) { // Don't allow a 45° rotation to bloat a pad's bounding box unnecessarily - int limit = KiROUND( std::min( aPad->GetSize().x, aPad->GetSize().y ) * 1.1 ); + int limit = KiROUND( std::min( aPad->GetSize( PADSTACK::ALL_LAYERS ).x, + aPad->GetSize( PADSTACK::ALL_LAYERS ).y ) * 1.1 ); if( padsize.x > limit && padsize.y > limit ) { @@ -142,7 +144,7 @@ void BRDITEMS_PLOTTER::PlotPadNumber( const PAD* aPad, const COLOR4D& aColor ) void BRDITEMS_PLOTTER::PlotPad( const PAD* aPad, const COLOR4D& aColor, OUTLINE_MODE aPlotMode ) { - VECTOR2I shape_pos = aPad->ShapePos(); + VECTOR2I shape_pos = aPad->ShapePos( PADSTACK::ALL_LAYERS ); GBR_METADATA metadata; bool plotOnCopperLayer = ( m_layerMask & LSET::AllCuMask() ).any(); @@ -272,24 +274,26 @@ void BRDITEMS_PLOTTER::PlotPad( const PAD* aPad, const COLOR4D& aColor, OUTLINE_ if( aPlotMode == SKETCH ) m_plotter->SetCurrentLineWidth( GetSketchPadLineWidth(), &metadata ); - switch( aPad->GetShape() ) + switch( aPad->GetShape( PADSTACK::ALL_LAYERS ) ) { case PAD_SHAPE::CIRCLE: - m_plotter->FlashPadCircle( shape_pos, aPad->GetSize().x, aPlotMode, &metadata ); + m_plotter->FlashPadCircle( shape_pos, aPad->GetSize( PADSTACK::ALL_LAYERS ).x, + aPlotMode, &metadata ); break; case PAD_SHAPE::OVAL: - m_plotter->FlashPadOval( shape_pos, aPad->GetSize(), aPad->GetOrientation(), aPlotMode, - &metadata ); + m_plotter->FlashPadOval( shape_pos, aPad->GetSize( PADSTACK::ALL_LAYERS ), + aPad->GetOrientation(), aPlotMode, &metadata ); break; case PAD_SHAPE::RECTANGLE: - m_plotter->FlashPadRect( shape_pos, aPad->GetSize(), aPad->GetOrientation(), aPlotMode, - &metadata ); + m_plotter->FlashPadRect( shape_pos, aPad->GetSize( PADSTACK::ALL_LAYERS ), + aPad->GetOrientation(), aPlotMode, &metadata ); break; case PAD_SHAPE::ROUNDRECT: - m_plotter->FlashPadRoundRect( shape_pos, aPad->GetSize(), aPad->GetRoundRectCornerRadius(), + m_plotter->FlashPadRoundRect( shape_pos, aPad->GetSize( PADSTACK::ALL_LAYERS ), + aPad->GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS ), aPad->GetOrientation(), aPlotMode, &metadata ); break; @@ -301,8 +305,8 @@ void BRDITEMS_PLOTTER::PlotPad( const PAD* aPad, const COLOR4D& aColor, OUTLINE_ VECTOR2I coord[4]; // Order is lower left, lower right, upper right, upper left. - VECTOR2I half_size = aPad->GetSize() / 2; - VECTOR2I trap_delta = aPad->GetDelta() / 2; + VECTOR2I half_size = aPad->GetSize( PADSTACK::ALL_LAYERS ) / 2; + VECTOR2I trap_delta = aPad->GetDelta( PADSTACK::ALL_LAYERS ) / 2; coord[0] = VECTOR2I( -half_size.x - trap_delta.y, half_size.y + trap_delta.x ); coord[1] = VECTOR2I( half_size.x + trap_delta.y, half_size.y - trap_delta.x ); @@ -318,11 +322,12 @@ void BRDITEMS_PLOTTER::PlotPad( const PAD* aPad, const COLOR4D& aColor, OUTLINE_ { GERBER_PLOTTER* gerberPlotter = static_cast( m_plotter ); - gerberPlotter->FlashPadChamferRoundRect( shape_pos, aPad->GetSize(), - aPad->GetRoundRectCornerRadius(), - aPad->GetChamferRectRatio(), - aPad->GetChamferPositions(), - aPad->GetOrientation(), aPlotMode, &metadata ); + gerberPlotter->FlashPadChamferRoundRect( shape_pos, + aPad->GetSize( PADSTACK::ALL_LAYERS ), + aPad->GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS ), + aPad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ), + aPad->GetChamferPositions( PADSTACK::ALL_LAYERS ), aPad->GetOrientation(), + aPlotMode, &metadata ); break; } @@ -331,11 +336,12 @@ void BRDITEMS_PLOTTER::PlotPad( const PAD* aPad, const COLOR4D& aColor, OUTLINE_ default: case PAD_SHAPE::CUSTOM: { - const std::shared_ptr& polygons = aPad->GetEffectivePolygon( ERROR_INSIDE ); + const std::shared_ptr& polygons = + aPad->GetEffectivePolygon( PADSTACK::ALL_LAYERS, ERROR_INSIDE ); if( polygons->OutlineCount() ) { - m_plotter->FlashPadCustom( shape_pos, aPad->GetSize(), aPad->GetOrientation(), + m_plotter->FlashPadCustom( shape_pos, aPad->GetSize( PADSTACK::ALL_LAYERS ), aPad->GetOrientation(), polygons.get(), aPlotMode, &metadata ); } } @@ -1207,7 +1213,7 @@ void BRDITEMS_PLOTTER::PlotDrillMarks() continue; plotOneDrillMark( pad->GetDrillShape(), pad->GetPosition(), pad->GetDrillSize(), - pad->GetSize(), pad->GetOrientation(), smallDrill ); + pad->GetSize( PADSTACK::ALL_LAYERS ), pad->GetOrientation(), smallDrill ); } } diff --git a/pcbnew/python/swig/pad.i b/pcbnew/python/swig/pad.i index 3b3a09292b..79de6b4e89 100644 --- a/pcbnew/python/swig/pad.i +++ b/pcbnew/python/swig/pad.i @@ -1,12 +1,14 @@ %rename(AddPrimitiveShape) PAD::AddPrimitive; +%{ +#include +#include +%} + %include padstack.h %include pad.h %rename(Get) operator PAD*; -%{ -#include -%} /* Only for compatibility with old python scripts: */ const int PAD_SHAPE_RECT = (const int)PAD_SHAPE::RECTANGLE; @@ -19,6 +21,19 @@ const int PAD_DRILL_SHAPE_OBLONG = (const int)PAD_DRILL_SHAPE::OBLONG; %extend PAD { + // Overrides to make non-padstack-aware scripts continue to work + PAD_SHAPE GetShape() { return $self->GetShape( F_Cu ); } + void SetShape( PAD_SHAPE aShape ) { $self->SetShape( F_Cu, aShape ); } + + VECTOR2I GetSize() { return $self->GetSize( F_Cu ); } + void SetSize( VECTOR2I aSize ) { $self->SetSize( F_Cu, aSize ); } + + VECTOR2I GetDelta() { return $self->GetDelta( F_Cu ); } + void SetDelta( VECTOR2I aSize ) { $self->SetDelta( F_Cu, aSize ); } + + VECTOR2I GetOffset() { return $self->GetOffset( F_Cu ); } + void SetOffset( VECTOR2I aOffset ) { $self->SetOffset( F_Cu, aOffset ); } + %pythoncode %{ @@ -44,7 +59,7 @@ const int PAD_DRILL_SHAPE_OBLONG = (const int)PAD_DRILL_SHAPE::OBLONG; # have gotten used to this API, so keep compatibility with it def AddPrimitive(self, *args): if len(args) == 2: - return self.AddPrimitivePoly(*args, True) + return self.AddPrimitivePoly(F_Cu, *args, True) elif len(args) == 3: if type(args[1] in [wxPoint,wxSize,VECTOR2I]): s = PCB_SHAPE(None, SHAPE_T_SEGMENT) @@ -77,7 +92,7 @@ const int PAD_DRILL_SHAPE_OBLONG = (const int)PAD_DRILL_SHAPE::OBLONG; # GetCustomShapeAsPolygon() is the old accessor to get custom shapes def GetCustomShapeAsPolygon(self, layer=UNDEFINED_LAYER): polygon_set = SHAPE_POLY_SET() - self.MergePrimitivesAsPolygon(polygon_set) + self.MergePrimitivesAsPolygon(F_Cu, polygon_set) return polygon_set %} } diff --git a/pcbnew/router/pns_kicad_iface.cpp b/pcbnew/router/pns_kicad_iface.cpp index 2a7c284e7d..df715c31e4 100644 --- a/pcbnew/router/pns_kicad_iface.cpp +++ b/pcbnew/router/pns_kicad_iface.cpp @@ -1120,14 +1120,15 @@ PNS_KICAD_IFACE::~PNS_KICAD_IFACE() } -std::unique_ptr PNS_KICAD_IFACE_BASE::syncPad( PAD* aPad ) +std::vector> PNS_KICAD_IFACE_BASE::syncPad( PAD* aPad ) { + std::vector> solids; PNS_LAYER_RANGE layers( 0, aPad->BoardCopperLayerCount() ); LSEQ lmsk = aPad->GetLayerSet().CuStack(); // ignore non-copper pads except for those with holes if( lmsk.empty() && aPad->GetDrillSize().x == 0 ) - return nullptr; + return solids; switch( aPad->GetAttribute() ) { @@ -1147,66 +1148,91 @@ std::unique_ptr PNS_KICAD_IFACE_BASE::syncPad( PAD* aPad ) } if( !is_copper ) - return nullptr; + return solids; break; } default: wxLogTrace( wxT( "PNS" ), wxT( "unsupported pad type 0x%x" ), aPad->GetAttribute() ); - return nullptr; + return solids; } - std::unique_ptr solid = std::make_unique(); + // TODO(JE) padstacks - need to make multiple solids for custom padstack situations + auto makeSolidFromPadLayer = + [&]( PCB_LAYER_ID aLayer ) + { + std::unique_ptr solid = std::make_unique(); - if( aPad->GetAttribute() == PAD_ATTRIB::NPTH ) - solid->SetRoutable( false ); + if( aPad->GetAttribute() == PAD_ATTRIB::NPTH ) + solid->SetRoutable( false ); - solid->SetLayers( layers ); - solid->SetNet( aPad->GetNet() ); - solid->SetParent( aPad ); - solid->SetPadToDie( aPad->GetPadToDieLength() ); - solid->SetOrientation( aPad->GetOrientation() ); + if( aPad->Padstack().Mode() == PADSTACK::MODE::CUSTOM ) + { + solid->SetLayers( GetPNSLayerFromBoardLayer( aLayer ) ); + } + else if( aPad->Padstack().Mode() == PADSTACK::MODE::FRONT_INNER_BACK ) + { + if( aLayer == F_Cu || aLayer == B_Cu ) + solid->SetLayers( GetPNSLayerFromBoardLayer( aLayer ) ); + else + solid->SetLayers( PNS_LAYER_RANGE( 1, aPad->BoardCopperLayerCount() - 1 ) ); + } + else + { + solid->SetLayers( layers ); + } - if( aPad->IsFreePad() ) - solid->SetIsFreePad(); + solid->SetNet( aPad->GetNet() ); + solid->SetParent( aPad ); + solid->SetPadToDie( aPad->GetPadToDieLength() ); + solid->SetOrientation( aPad->GetOrientation() ); - VECTOR2I wx_c = aPad->ShapePos(); - VECTOR2I offset = aPad->GetOffset(); + if( aPad->IsFreePad() ) + solid->SetIsFreePad(); - VECTOR2I c( wx_c.x, wx_c.y ); + VECTOR2I wx_c = aPad->ShapePos( aLayer ); + VECTOR2I offset = aPad->GetOffset( aLayer ); - RotatePoint( offset, aPad->GetOrientation() ); + VECTOR2I c( wx_c.x, wx_c.y ); - solid->SetPos( VECTOR2I( c.x - offset.x, c.y - offset.y ) ); - solid->SetOffset( VECTOR2I( offset.x, offset.y ) ); + RotatePoint( offset, aPad->GetOrientation() ); - if( aPad->GetDrillSize().x > 0 ) - solid->SetHole( new PNS::HOLE( aPad->GetEffectiveHoleShape()->Clone() ) ); + solid->SetPos( VECTOR2I( c.x - offset.x, c.y - offset.y ) ); + solid->SetOffset( VECTOR2I( offset.x, offset.y ) ); - // We generate a single SOLID for a pad, so we have to treat it as ALWAYS_FLASHED and then - // perform layer-specific flashing tests internally. - const std::shared_ptr& shape = aPad->GetEffectiveShape( UNDEFINED_LAYER, - FLASHING::ALWAYS_FLASHED ); + if( aPad->GetDrillSize().x > 0 ) + solid->SetHole( new PNS::HOLE( aPad->GetEffectiveHoleShape()->Clone() ) ); - if( shape->HasIndexableSubshapes() && shape->GetIndexableSubshapeCount() == 1 ) - { - std::vector subshapes; - shape->GetIndexableSubshapes( subshapes ); + // We generate a single SOLID for a pad, so we have to treat it as ALWAYS_FLASHED and + // then perform layer-specific flashing tests internally. + const std::shared_ptr& shape = + aPad->GetEffectiveShape( aLayer, FLASHING::ALWAYS_FLASHED ); - solid->SetShape( subshapes[0]->Clone() ); - } - // For anything that's not a single shape we use a polygon. Multiple shapes have a tendency - // to confuse the hull generator. https://gitlab.com/kicad/code/kicad/-/issues/15553 - else - { - const std::shared_ptr& poly = aPad->GetEffectivePolygon( ERROR_OUTSIDE ); + if( shape->HasIndexableSubshapes() && shape->GetIndexableSubshapeCount() == 1 ) + { + std::vector subshapes; + shape->GetIndexableSubshapes( subshapes ); - if( poly->OutlineCount() ) - solid->SetShape( new SHAPE_SIMPLE( poly->Outline( 0 ) ) ); - } + solid->SetShape( subshapes[0]->Clone() ); + } + // For anything that's not a single shape we use a polygon. Multiple shapes have a tendency + // to confuse the hull generator. https://gitlab.com/kicad/code/kicad/-/issues/15553 + else + { + const std::shared_ptr& poly = + aPad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE ); - return solid; + if( poly->OutlineCount() ) + solid->SetShape( new SHAPE_SIMPLE( poly->Outline( 0 ) ) ); + } + + solids.emplace_back( std::move( solid ) ); + }; + + aPad->Padstack().ForEachUniqueLayer( makeSolidFromPadLayer ); + + return solids; } @@ -1612,7 +1638,9 @@ void PNS_KICAD_IFACE_BASE::SyncWorld( PNS::NODE *aWorld ) { for( PAD* pad : footprint->Pads() ) { - if( std::unique_ptr solid = syncPad( pad ) ) + std::vector> solids = syncPad( pad ); + + for( std::unique_ptr& solid : solids ) aWorld->Add( std::move( solid ) ); std::optional clearanceOverride = pad->GetClearanceOverrides( nullptr ); @@ -2194,4 +2222,4 @@ PNS_LAYER_RANGE PNS_KICAD_IFACE_BASE::SetLayersFromPCBNew( PCB_LAYER_ID aStartLa { return PNS_LAYER_RANGE( GetPNSLayerFromBoardLayer( aStartLayer ), GetPNSLayerFromBoardLayer( aEndLayer ) ); -} \ No newline at end of file +} diff --git a/pcbnew/router/pns_kicad_iface.h b/pcbnew/router/pns_kicad_iface.h index b11d1d6ad1..2e05c1d8cc 100644 --- a/pcbnew/router/pns_kicad_iface.h +++ b/pcbnew/router/pns_kicad_iface.h @@ -104,7 +104,7 @@ protected: PNS_PCBNEW_RULE_RESOLVER* m_ruleResolver; PNS::DEBUG_DECORATOR* m_debugDecorator; - std::unique_ptr syncPad( PAD* aPad ); + std::vector> syncPad( PAD* aPad ); std::unique_ptr syncTrack( PCB_TRACK* aTrack ); std::unique_ptr syncArc( PCB_ARC* aArc ); std::unique_ptr syncVia( PCB_VIA* aVia ); diff --git a/pcbnew/router/pns_topology.cpp b/pcbnew/router/pns_topology.cpp index 4b2effecb0..70c15eb327 100644 --- a/pcbnew/router/pns_topology.cpp +++ b/pcbnew/router/pns_topology.cpp @@ -369,9 +369,9 @@ const ITEM_SET TOPOLOGY::AssembleTuningPath( ITEM* aStart, SOLID** aStartPad, SO return initialPath; auto clipLineToPad = - []( SHAPE_LINE_CHAIN& aLine, PAD* aPad, bool aForward = true ) + []( SHAPE_LINE_CHAIN& aLine, PAD* aPad, PCB_LAYER_ID aLayer, bool aForward = true ) { - const auto& shape = aPad->GetEffectivePolygon( ERROR_INSIDE ); + const auto& shape = aPad->GetEffectivePolygon( aLayer, ERROR_INSIDE ); int start = aForward ? 0 : aLine.PointCount() - 1; int delta = aForward ? 1 : -1; @@ -415,9 +415,9 @@ const ITEM_SET TOPOLOGY::AssembleTuningPath( ITEM* aStart, SOLID** aStartPad, SO }; auto processPad = - [&]( const JOINT* aJoint, PAD* aPad ) + [&]( const JOINT* aJoint, PAD* aPad, PCB_LAYER_ID aLayer ) { - const auto& shape = aPad->GetEffectivePolygon( ERROR_INSIDE ); + const auto& shape = aPad->GetEffectivePolygon( aLayer, ERROR_INSIDE ); for( int idx = 0; idx < initialPath.Size(); idx++ ) { @@ -435,19 +435,20 @@ const ITEM_SET TOPOLOGY::AssembleTuningPath( ITEM* aStart, SOLID** aStartPad, SO continue; SHAPE_LINE_CHAIN& slc = line->Line(); + const PCB_LAYER_ID& layer = static_cast( line->Layer() ); if( shape->Contains( slc.CPoint( 0 ) ) ) - clipLineToPad( slc, aPad, true ); + clipLineToPad( slc, aPad, layer, true ); else if( shape->Contains( slc.CPoint( -1 ) ) ) - clipLineToPad( slc, aPad, false ); + clipLineToPad( slc, aPad, layer, false ); } }; if( padA ) - processPad( joints.first, padA ); + processPad( joints.first, padA, static_cast( joints.first->Layer() ) ); if( padB ) - processPad( joints.second, padB ); + processPad( joints.second, padB, static_cast( joints.second->Layer() ) ); return initialPath; } diff --git a/pcbnew/specctra_import_export/specctra_export.cpp b/pcbnew/specctra_import_export/specctra_export.cpp index a588d8098b..5bf4fc57d3 100644 --- a/pcbnew/specctra_import_export/specctra_export.cpp +++ b/pcbnew/specctra_import_export/specctra_export.cpp @@ -203,9 +203,10 @@ static POINT mapPt( const VECTOR2I& pt, FOOTPRINT* aFootprint ) */ static bool isRoundKeepout( PAD* aPad ) { - if( aPad->GetShape() == PAD_SHAPE::CIRCLE ) + // TODO(JE) padstacks + if( aPad->GetShape( ::PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE ) { - if( aPad->GetDrillSize().x >= aPad->GetSize().x ) + if( aPad->GetDrillSize().x >= aPad->GetSize( ::PADSTACK::ALL_LAYERS ).x ) return true; if( !( aPad->GetLayerSet() & LSET::AllCuMask() ).any() ) @@ -281,10 +282,12 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) POINT dsnOffset; - if( aPad->GetOffset().x || aPad->GetOffset().y ) - { - VECTOR2I offset( aPad->GetOffset().x, aPad->GetOffset().y ); + // TODO(JE) padstacks + const VECTOR2I& padSize = aPad->GetSize( ::PADSTACK::ALL_LAYERS ); + const VECTOR2I& offset = aPad->GetOffset( ::PADSTACK::ALL_LAYERS ); + if( offset.x || offset.y ) + { dsnOffset = mapPt( offset ); // Using () would cause padstack name to be quoted, and {} locks freerouter, so use []. std::ostringstream oss; @@ -294,11 +297,11 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) uniqifier += oss.str(); } - switch( aPad->GetShape() ) + switch( aPad->GetShape( ::PADSTACK::ALL_LAYERS ) ) { case PAD_SHAPE::CIRCLE: { - double diameter = scale( aPad->GetSize().x ); + double diameter = scale( padSize.x ); for( int ndx = 0; ndx < reportedLayers; ++ndx ) { @@ -317,7 +320,7 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) std::ostringstream oss; oss << "Round" << uniqifier << "Pad_" << std::fixed << std::setprecision(6) - << IU2um(aPad->GetSize().x) << "_um"; + << IU2um( padSize.x ) << "_um"; padstack->SetPadstackId( oss.str().c_str() ); break; @@ -325,8 +328,8 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) case PAD_SHAPE::RECTANGLE: { - double dx = scale( aPad->GetSize().x ) / 2.0; - double dy = scale( aPad->GetSize().y ) / 2.0; + double dx = scale( padSize.x ) / 2.0; + double dy = scale( padSize.y ) / 2.0; POINT lowerLeft( -dx, -dy ); POINT upperRight( dx, dy ); @@ -350,7 +353,7 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) std::ostringstream oss; oss << "Rect" << uniqifier << "Pad_" << std::fixed << std::setprecision(6) - << IU2um(aPad->GetSize().x) << "x" << IU2um(aPad->GetSize().y) << "_um"; + << IU2um( padSize.x ) << "x" << IU2um( padSize.y ) << "_um"; padstack->SetPadstackId( oss.str().c_str() ); break; @@ -358,8 +361,8 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) case PAD_SHAPE::OVAL: { - double dx = scale( aPad->GetSize().x ) / 2.0; - double dy = scale( aPad->GetSize().y ) / 2.0; + double dx = scale( padSize.x ) / 2.0; + double dy = scale( padSize.y ) / 2.0; double dr = dx - dy; double radius; POINT pstart; @@ -400,7 +403,7 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) std::ostringstream oss; oss << "Oval" << uniqifier << "Pad_" << std::fixed << std::setprecision(6) - << IU2um(aPad->GetSize().x) << "x" << IU2um(aPad->GetSize().y) << "_um"; + << IU2um( padSize.x ) << "x" << IU2um( padSize.y ) << "_um"; padstack->SetPadstackId( oss.str().c_str() ); break; @@ -408,11 +411,13 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) case PAD_SHAPE::TRAPEZOID: { - double dx = scale( aPad->GetSize().x ) / 2.0; - double dy = scale( aPad->GetSize().y ) / 2.0; + double dx = scale( padSize.x ) / 2.0; + double dy = scale( padSize.y ) / 2.0; - double ddx = scale( aPad->GetDelta().x ) / 2.0; - double ddy = scale( aPad->GetDelta().y ) / 2.0; + const VECTOR2I& delta = aPad->GetDelta( ::PADSTACK::ALL_LAYERS ); + + double ddx = scale( delta.x ) / 2.0; + double ddy = scale( delta.y ) / 2.0; // see class_pad_draw_functions.cpp which draws the trapezoid pad POINT lowerLeft( -dx - ddy, -dy - ddx ); @@ -447,9 +452,9 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) // this string _must_ be unique for a given physical shape std::ostringstream oss; oss << "Trapz" << uniqifier << "Pad_" << std::fixed << std::setprecision(6) - << IU2um(aPad->GetSize().x) << "x" << IU2um(aPad->GetSize().y) << "_" - << (aPad->GetDelta().x < 0 ? "n" : "p") << std::abs(IU2um(aPad->GetDelta().x)) << "x" - << (aPad->GetDelta().y < 0 ? "n" : "p") << std::abs(IU2um(aPad->GetDelta().y)) << "_um"; + << IU2um( padSize.x ) << "x" << IU2um( padSize.y ) << "_" + << ( delta.x < 0 ? "n" : "p") << std::abs( IU2um( delta.x ) ) << "x" + << ( delta.y < 0 ? "n" : "p") << std::abs( IU2um( delta.y ) ) << "_um"; padstack->SetPadstackId( oss.str().c_str() ); break; @@ -460,7 +465,7 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) { // Export the shape as as polygon, round rect does not exist as primitive const int circleToSegmentsCount = 36; - int rradius = aPad->GetRoundRectCornerRadius(); + int rradius = aPad->GetRoundRectCornerRadius( ::PADSTACK::ALL_LAYERS ); SHAPE_POLY_SET cornerBuffer; // Use a slightly bigger shape because the round corners are approximated by @@ -473,17 +478,16 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) */ double correctionFactor = cos( M_PI / (double) circleToSegmentsCount ); int extra_clearance = KiROUND( rradius * ( 1.0 - correctionFactor ) ); - VECTOR2I psize = aPad->GetSize(); + VECTOR2I psize = padSize; psize.x += extra_clearance * 2; psize.y += extra_clearance * 2; rradius += extra_clearance; - bool doChamfer = aPad->GetShape() == PAD_SHAPE::CHAMFERED_RECT; + bool doChamfer = aPad->GetShape( ::PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CHAMFERED_RECT; TransformRoundChamferedRectToPolygon( cornerBuffer, VECTOR2I( 0, 0 ), psize, ANGLE_0, - rradius, aPad->GetChamferRectRatio(), - doChamfer ? aPad->GetChamferPositions() : 0, - 0, aBoard->GetDesignSettings().m_MaxError, - ERROR_INSIDE ); + rradius, aPad->GetChamferRectRatio( ::PADSTACK::ALL_LAYERS ), + doChamfer ? aPad->GetChamferPositions( ::PADSTACK::ALL_LAYERS ) : 0, + 0, aBoard->GetDesignSettings().m_MaxError, ERROR_INSIDE ); SHAPE_LINE_CHAIN& polygonal_shape = cornerBuffer.Outline( 0 ); @@ -521,12 +525,12 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) std::ostringstream oss; oss << "RoundRect" << uniqifier << "Pad_" << std::fixed << std::setprecision(6) - << IU2um( aPad->GetSize().x ) << 'x' - << IU2um( aPad->GetSize().y ) << '_' + << IU2um( padSize.x ) << 'x' + << IU2um( padSize.y ) << '_' << IU2um( rradius ) << "_um_" - << ( doChamfer ? aPad->GetChamferRectRatio() : 0.0 ) << '_' + << ( doChamfer ? aPad->GetChamferRectRatio( ::PADSTACK::ALL_LAYERS ) : 0.0 ) << '_' << std::hex << std::uppercase - << ( doChamfer ? aPad->GetChamferPositions() : 0 ); + << ( doChamfer ? aPad->GetChamferPositions( ::PADSTACK::ALL_LAYERS ) : 0 ); padstack->SetPadstackId( oss.str().c_str() ); break; @@ -536,7 +540,7 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) { std::vector polygonal_shape; SHAPE_POLY_SET pad_shape; - aPad->MergePrimitivesAsPolygon( &pad_shape ); + aPad->MergePrimitivesAsPolygon( ::PADSTACK::ALL_LAYERS, &pad_shape ); #ifdef EXPORT_CUSTOM_PADS_CONVEX_HULL BuildConvexHull( polygonal_shape, pad_shape ); @@ -579,7 +583,7 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad ) std::ostringstream oss; oss << "Cust" << uniqifier << "Pad_" << std::fixed << std::setprecision(6) - << IU2um( aPad->GetSize().x ) << 'x' << IU2um( aPad->GetSize().y ) << '_' + << IU2um( padSize.x ) << 'x' << IU2um( padSize.y ) << '_' << IU2um( rect.GetWidth() ) << 'x' << IU2um( rect.GetHeight() ) << '_' << polygonal_shape.size() << "_um_" << hash.ToString(); diff --git a/pcbnew/teardrop/teardrop.cpp b/pcbnew/teardrop/teardrop.cpp index 63aa8a60ed..1156f84b89 100644 --- a/pcbnew/teardrop/teardrop.cpp +++ b/pcbnew/teardrop/teardrop.cpp @@ -199,7 +199,8 @@ void TEARDROP_MANAGER::UpdateTeardrops( BOARD_COMMIT& aCommit, continue; TEARDROP_PARAMETERS& tdParams = pad->GetTeardropParams(); - int annularWidth = std::min( pad->GetSize().x, pad->GetSize().y ); + VECTOR2I padSize = pad->GetSize( track->GetLayer() ); + int annularWidth = std::min( padSize.x, padSize.y ); if( !tdParams.m_Enabled ) continue; diff --git a/pcbnew/teardrop/teardrop_utils.cpp b/pcbnew/teardrop/teardrop_utils.cpp index c3b22e77a2..a5838d0321 100644 --- a/pcbnew/teardrop/teardrop_utils.cpp +++ b/pcbnew/teardrop/teardrop_utils.cpp @@ -71,8 +71,9 @@ int TEARDROP_MANAGER::GetWidth( BOARD_ITEM* aItem ) } else if( aItem->Type() == PCB_PAD_T ) { + // TODO(JE) padstacks PAD* pad = static_cast( aItem ); - return std::min( pad->GetSize().x, pad->GetSize().y ); + return std::min( pad->GetSize( PADSTACK::ALL_LAYERS ).x, pad->GetSize( PADSTACK::ALL_LAYERS ).y ); } else if( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_ARC_T ) { @@ -90,8 +91,11 @@ bool TEARDROP_MANAGER::IsRound( BOARD_ITEM* aItem ) { PAD* pad = static_cast( aItem ); - return pad->GetShape() == PAD_SHAPE::CIRCLE - || ( pad->GetShape() == PAD_SHAPE::OVAL && pad->GetSize().x == pad->GetSize().y ); + // TODO(JE) padstacks + return pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE + || ( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::OVAL + && pad->GetSize( PADSTACK::ALL_LAYERS ).x + == pad->GetSize( PADSTACK::ALL_LAYERS ).y ); } return true; diff --git a/pcbnew/tools/convert_tool.cpp b/pcbnew/tools/convert_tool.cpp index 881cbf97b0..7636c10162 100644 --- a/pcbnew/tools/convert_tool.cpp +++ b/pcbnew/tools/convert_tool.cpp @@ -879,7 +879,8 @@ SHAPE_POLY_SET CONVERT_TOOL::makePolysFromClosedGraphics( const std::dequeSetFilled( true ); shape->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, bds.m_MaxError, ERROR_INSIDE, - aStrategy == COPY_LINEWIDTH || aStrategy == CENTERLINE ); + aStrategy == COPY_LINEWIDTH + || aStrategy == CENTERLINE ); if( aStrategy != BOUNDING_HULL ) shape->SetFillMode( wasFilled ); diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp index b1ee47e143..2ce29b33d6 100644 --- a/pcbnew/tools/edit_tool.cpp +++ b/pcbnew/tools/edit_tool.cpp @@ -2011,20 +2011,21 @@ int EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent ) */ static void mirrorPad( PAD& aPad, const VECTOR2I& aMirrorPoint, FLIP_DIRECTION aFlipDirection ) { - if( aPad.GetShape() == PAD_SHAPE::CUSTOM ) + // TODO(JE) padstacks + if( aPad.GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM ) aPad.FlipPrimitives( aFlipDirection ); VECTOR2I tmpPt = aPad.GetPosition(); MIRROR( tmpPt, aMirrorPoint, aFlipDirection ); aPad.SetPosition( tmpPt ); - tmpPt = aPad.GetOffset(); + tmpPt = aPad.GetOffset( PADSTACK::ALL_LAYERS ); MIRROR( tmpPt, VECTOR2I{ 0, 0 }, aFlipDirection ); - aPad.SetOffset( tmpPt ); + aPad.SetOffset( PADSTACK::ALL_LAYERS, tmpPt ); - VECTOR2I tmpz = aPad.GetDelta(); + VECTOR2I tmpz = aPad.GetDelta( PADSTACK::ALL_LAYERS ); MIRROR( tmpz, VECTOR2I{ 0, 0 }, aFlipDirection ); - aPad.SetDelta( tmpz ); + aPad.SetDelta( PADSTACK::ALL_LAYERS, tmpz ); aPad.SetOrientation( -aPad.GetOrientation() ); } @@ -3257,4 +3258,4 @@ void EDIT_TOOL::setTransitions() Go( &EDIT_TOOL::copyToClipboard, PCB_ACTIONS::copyWithReference.MakeEvent() ); Go( &EDIT_TOOL::cutToClipboard, ACTIONS::cut.MakeEvent() ); } -// clang-format on \ No newline at end of file +// clang-format on diff --git a/pcbnew/tools/item_modification_routine.cpp b/pcbnew/tools/item_modification_routine.cpp index 2d46773a03..82851b81d1 100644 --- a/pcbnew/tools/item_modification_routine.cpp +++ b/pcbnew/tools/item_modification_routine.cpp @@ -843,7 +843,8 @@ void OUTSET_ROUTINE::ProcessItem( BOARD_ITEM& aItem ) { const PAD& pad = static_cast( aItem ); - const PAD_SHAPE pad_shape = pad.GetShape(); + // TODO(JE) padstacks + const PAD_SHAPE pad_shape = pad.GetShape( PADSTACK::ALL_LAYERS ); switch( pad_shape ) { @@ -851,7 +852,7 @@ void OUTSET_ROUTINE::ProcessItem( BOARD_ITEM& aItem ) case PAD_SHAPE::ROUNDRECT: case PAD_SHAPE::OVAL: { - const VECTOR2I pad_size = pad.GetSize(); + const VECTOR2I pad_size = pad.GetSize( PADSTACK::ALL_LAYERS ); BOX2I box{ pad.GetPosition() - pad_size / 2, pad_size }; box.Inflate( m_params.outsetDistance ); @@ -859,7 +860,7 @@ void OUTSET_ROUTINE::ProcessItem( BOARD_ITEM& aItem ) int radius = m_params.outsetDistance; if( pad_shape == PAD_SHAPE::ROUNDRECT ) { - radius += pad.GetRoundRectCornerRadius(); + radius += pad.GetRoundRectCornerRadius( PADSTACK::ALL_LAYERS ); } else if( pad_shape == PAD_SHAPE::OVAL ) { @@ -880,7 +881,7 @@ void OUTSET_ROUTINE::ProcessItem( BOARD_ITEM& aItem ) } case PAD_SHAPE::CIRCLE: { - const int radius = pad.GetSize().x / 2 + m_params.outsetDistance; + const int radius = pad.GetSize( PADSTACK::ALL_LAYERS ).x / 2 + m_params.outsetDistance; const CIRCLE circle( pad.GetPosition(), radius ); addCircleOrRect( circle ); AddSuccess(); @@ -1012,4 +1013,4 @@ void OUTSET_ROUTINE::ProcessItem( BOARD_ITEM& aItem ) { handler.DeleteItem( aItem ); } -} \ No newline at end of file +} diff --git a/pcbnew/tools/pad_tool.cpp b/pcbnew/tools/pad_tool.cpp index 8f99f76594..32d988fc69 100644 --- a/pcbnew/tools/pad_tool.cpp +++ b/pcbnew/tools/pad_tool.cpp @@ -209,7 +209,8 @@ static void doPushPadProperties( BOARD& board, const PAD& aSrcPad, BOARD_COMMIT& for( PAD* pad : footprint->Pads() ) { - if( aPadShapeFilter && ( pad->GetShape() != aSrcPad.GetShape() ) ) + // TODO(JE) padstacks + if( aPadShapeFilter && ( pad->GetShape( PADSTACK::ALL_LAYERS ) != aSrcPad.GetShape( PADSTACK::ALL_LAYERS ) ) ) continue; EDA_ANGLE padAngle = pad->GetOrientation() - footprint->GetOrientation(); @@ -594,6 +595,7 @@ int PAD_TOOL::PlacePad( const TOOL_EVENT& aEvent ) std::unique_ptr CreateItem() override { + // TODO(JE) padstacks PAD* pad = new PAD( m_board->GetFirstFootprint() ); PAD* master = m_frame->GetDesignSettings().m_Pad_Master.get(); @@ -608,7 +610,7 @@ int PAD_TOOL::PlacePad( const TOOL_EVENT& aEvent ) if( pad->GetProperty() != PAD_PROP::HEATSINK ) { pad->SetAttribute( PAD_ATTRIB::SMD ); - pad->SetShape( PAD_SHAPE::ROUNDRECT ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::ROUNDRECT ); pad->SetSizeX( 1.5 * pad->GetSizeY() ); pad->SetLayerSet( PAD::SMDMask() ); } @@ -617,8 +619,8 @@ int PAD_TOOL::PlacePad( const TOOL_EVENT& aEvent ) && master->GetAttribute() == PAD_ATTRIB::SMD ) { pad->SetAttribute( PAD_ATTRIB::PTH ); - pad->SetShape( PAD_SHAPE::CIRCLE ); - pad->SetSize( VECTOR2I( pad->GetSizeX(), pad->GetSizeX() ) ); + pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( pad->GetSizeX(), pad->GetSizeX() ) ); // Gives an acceptable drill size: it cannot be 0, but from pad master // it is currently 0, therefore change it: @@ -843,15 +845,16 @@ void PAD_TOOL::explodePad( PAD* aPad, PCB_LAYER_ID* aLayer, BOARD_COMMIT& aCommi else *aLayer = aPad->GetLayerSet().UIOrder().front(); - if( aPad->GetShape() == PAD_SHAPE::CUSTOM ) + // TODO(JE) padstacks + if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM ) { - for( const std::shared_ptr& primitive : aPad->GetPrimitives() ) + for( const std::shared_ptr& primitive : aPad->GetPrimitives( PADSTACK::ALL_LAYERS ) ) { PCB_SHAPE* shape = static_cast( primitive->Duplicate() ); shape->SetParent( board()->GetFirstFootprint() ); shape->Rotate( VECTOR2I( 0, 0 ), aPad->GetOrientation() ); - shape->Move( aPad->ShapePos() ); + shape->Move( aPad->ShapePos( PADSTACK::ALL_LAYERS ) ); shape->SetLayer( *aLayer ); if( shape->IsProxyItem() && shape->GetShape() == SHAPE_T::SEGMENT ) @@ -865,7 +868,8 @@ void PAD_TOOL::explodePad( PAD* aPad, PCB_LAYER_ID* aLayer, BOARD_COMMIT& aCommi aCommit.Add( shape ); } - aPad->SetShape( aPad->GetAnchorPadShape() ); + // TODO(JE) padstacks + aPad->SetShape( PADSTACK::ALL_LAYERS, aPad->GetAnchorPadShape( PADSTACK::ALL_LAYERS ) ); aPad->DeletePrimitivesList(); } diff --git a/pcbnew/tools/pcb_grid_helper.cpp b/pcbnew/tools/pcb_grid_helper.cpp index 25fe8d7e25..3d6f944c4a 100644 --- a/pcbnew/tools/pcb_grid_helper.cpp +++ b/pcbnew/tools/pcb_grid_helper.cpp @@ -1097,7 +1097,7 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos KIGEOM::OVAL_CENTER | KIGEOM::OVAL_CAP_TIPS | KIGEOM::OVAL_SIDE_MIDPOINTS | KIGEOM::OVAL_CARDINAL_EXTREMES; - auto handlePadShape = [&]( PAD* aPad ) + auto handlePadShape = [&]( PAD* aPad, PCB_LAYER_ID aLayer ) { addAnchor( aPad->GetPosition(), ORIGIN | SNAPPABLE, aPad, POINT_TYPE::PT_CENTER ); @@ -1105,11 +1105,12 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos if( aFrom ) return; - switch( aPad->GetShape() ) + switch( aPad->GetShape( aLayer ) ) { case PAD_SHAPE::CIRCLE: { - const CIRCLE circle( aPad->ShapePos(), aPad->GetSizeX() / 2 ); + const CIRCLE circle( aPad->ShapePos( aLayer ), aPad->GetSizeX() / 2 ); + for( const TYPED_POINT2I& pt : KIGEOM::GetCircleKeyPoints( circle, false ) ) { addAnchor( pt.m_point, OUTLINE | SNAPPABLE, aPad, pt.m_types ); @@ -1119,7 +1120,8 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos } case PAD_SHAPE::OVAL: { - const OVAL oval( aPad->GetSize(), aPad->GetPosition(), aPad->GetOrientation() ); + const OVAL oval( aPad->GetSize( aLayer ), aPad->GetPosition(), aPad->GetOrientation() ); + for( const TYPED_POINT2I& pt : KIGEOM::GetOvalKeyPoints( oval, ovalKeyPointFlags ) ) { addAnchor( pt.m_point, OUTLINE | SNAPPABLE, aPad, pt.m_types ); @@ -1132,11 +1134,11 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos case PAD_SHAPE::ROUNDRECT: case PAD_SHAPE::CHAMFERED_RECT: { - VECTOR2I half_size( aPad->GetSize() / 2 ); + VECTOR2I half_size( aPad->GetSize( aLayer ) / 2 ); VECTOR2I trap_delta( 0, 0 ); - if( aPad->GetShape() == PAD_SHAPE::TRAPEZOID ) - trap_delta = aPad->GetDelta() / 2; + if( aPad->GetShape( aLayer ) == PAD_SHAPE::TRAPEZOID ) + trap_delta = aPad->GetDelta( aLayer ) / 2; SHAPE_LINE_CHAIN corners; @@ -1147,7 +1149,7 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos corners.SetClosed( true ); corners.Rotate( aPad->GetOrientation() ); - corners.Move( aPad->ShapePos() ); + corners.Move( aPad->ShapePos( aLayer ) ); for( std::size_t ii = 0; ii < corners.GetSegmentCount(); ++ii ) { @@ -1164,7 +1166,7 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos default: { - const auto& outline = aPad->GetEffectivePolygon( ERROR_INSIDE ); + const auto& outline = aPad->GetEffectivePolygon( aLayer, ERROR_INSIDE ); if( !outline->IsEmpty() ) { @@ -1326,7 +1328,12 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos if( !pad->GetBoundingBox().Contains( aRefPos ) ) continue; - handlePadShape( pad ); + pad->Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + if( activeLayers.count( aLayer ) ) + handlePadShape( pad, aLayer ); + } ); } if( aFrom && aSelectionFilter && !aSelectionFilter->footprints ) @@ -1360,7 +1367,16 @@ void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos } if( checkVisibility( aItem ) ) - handlePadShape( static_cast( aItem ) ); + { + PAD* pad = static_cast( aItem ); + + pad->Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + if( activeLayers.count( aLayer ) ) + handlePadShape( pad, aLayer ); + } ); + } break; @@ -1629,4 +1645,4 @@ PCB_GRID_HELPER::ANCHOR* PCB_GRID_HELPER::nearestAnchor( const VECTOR2I& aPos, i } return best; -} \ No newline at end of file +} diff --git a/pcbnew/tools/pcb_point_editor.cpp b/pcbnew/tools/pcb_point_editor.cpp index d8cbfca4fd..f13cc0f216 100644 --- a/pcbnew/tools/pcb_point_editor.cpp +++ b/pcbnew/tools/pcb_point_editor.cpp @@ -322,13 +322,15 @@ std::shared_ptr PCB_POINT_EDITOR::makePoints( EDA_ITEM* aItem ) case PCB_PAD_T: { const PAD* pad = static_cast( aItem ); - VECTOR2I shapePos = pad->ShapePos(); - VECTOR2I halfSize( pad->GetSize().x / 2, pad->GetSize().y / 2 ); + // TODO(JE) padstacks + VECTOR2I shapePos = pad->ShapePos( PADSTACK::ALL_LAYERS ); + VECTOR2I halfSize( pad->GetSize( PADSTACK::ALL_LAYERS ).x / 2, + pad->GetSize( PADSTACK::ALL_LAYERS ).y / 2 ); if( !m_isFootprintEditor || pad->IsLocked() ) break; - switch( pad->GetShape() ) + switch( pad->GetShape( PADSTACK::ALL_LAYERS ) ) { case PAD_SHAPE::CIRCLE: points->AddPoint( VECTOR2I( shapePos.x + halfSize.x, shapePos.y ) ); @@ -1671,14 +1673,15 @@ void PCB_POINT_EDITOR::updateItem( BOARD_COMMIT* aCommit ) { PAD* pad = static_cast( item ); - switch( pad->GetShape() ) + // TODO(JE) padstacks + switch( pad->GetShape( PADSTACK::ALL_LAYERS ) ) { case PAD_SHAPE::CIRCLE: { VECTOR2I end = m_editPoints->Point( 0 ).GetPosition(); int diameter = 2 * ( end - pad->GetPosition() ).EuclideanNorm(); - pad->SetSize( VECTOR2I( diameter, diameter ) ); + pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( diameter, diameter ) ); break; } @@ -1697,7 +1700,7 @@ void PCB_POINT_EDITOR::updateItem( BOARD_COMMIT* aCommit ) pinEditedCorner( topLeft, topRight, botLeft, botRight, holeCenter, holeSize ); - if( ( pad->GetOffset().x || pad->GetOffset().y ) + if( ( pad->GetOffset( PADSTACK::ALL_LAYERS ).x || pad->GetOffset( PADSTACK::ALL_LAYERS ).y ) || ( pad->GetDrillSize().x && pad->GetDrillSize().y ) ) { // Keep hole pinned at the current location; adjust the pad around the hole @@ -1729,8 +1732,8 @@ void PCB_POINT_EDITOR::updateItem( BOARD_COMMIT* aCommit ) RotatePoint( deltaOffset, -pad->GetOrientation() ); - pad->SetSize( padSize ); - pad->SetOffset( -deltaOffset ); + pad->SetSize( PADSTACK::ALL_LAYERS, padSize ); + pad->SetOffset( PADSTACK::ALL_LAYERS, -deltaOffset ); } else { @@ -1759,7 +1762,7 @@ void PCB_POINT_EDITOR::updateItem( BOARD_COMMIT* aCommit ) if( pad->GetOrientation() == ANGLE_90 || pad->GetOrientation() == ANGLE_270 ) std::swap( padSize.x, padSize.y ); - pad->SetSize( padSize ); + pad->SetSize( PADSTACK::ALL_LAYERS, padSize ); pad->SetPosition( VECTOR2I( ( left + right ) / 2, ( top + bottom ) / 2 ) ); } break; @@ -2215,12 +2218,13 @@ void PCB_POINT_EDITOR::updatePoints() case PCB_PAD_T: { + // TODO(JE) padstacks const PAD* pad = static_cast( item ); bool locked = pad->GetParent() && pad->IsLocked(); - VECTOR2I shapePos = pad->ShapePos(); - VECTOR2I halfSize( pad->GetSize().x / 2, pad->GetSize().y / 2 ); + VECTOR2I shapePos = pad->ShapePos( PADSTACK::ALL_LAYERS ); + VECTOR2I halfSize( pad->GetSize( PADSTACK::ALL_LAYERS ).x / 2, pad->GetSize( PADSTACK::ALL_LAYERS ).y / 2 ); - switch( pad->GetShape() ) + switch( pad->GetShape( PADSTACK::ALL_LAYERS ) ) { case PAD_SHAPE::CIRCLE: { diff --git a/pcbnew/tools/pcb_selection_tool.cpp b/pcbnew/tools/pcb_selection_tool.cpp index 642b1f7aba..996f3ce88f 100644 --- a/pcbnew/tools/pcb_selection_tool.cpp +++ b/pcbnew/tools/pcb_selection_tool.cpp @@ -3298,6 +3298,19 @@ int PCB_SELECTION_TOOL::hitTestDistance( const VECTOR2I& aWhere, BOARD_ITEM* aIt break; } + case PCB_PAD_T: + { + static_cast( aItem )->Padstack().ForEachUniqueLayer( + [&]( PCB_LAYER_ID aLayer ) + { + int layerDistance = INT_MAX; + aItem->GetEffectiveShape( aLayer )->Collide( loc, aMaxDistance, &layerDistance ); + distance = std::min( distance, layerDistance ); + } ); + + break; + } + default: aItem->GetEffectiveShape()->Collide( loc, aMaxDistance, &distance ); break; diff --git a/pcbnew/tracks_cleaner.cpp b/pcbnew/tracks_cleaner.cpp index 5631f700f1..dcea7699f3 100644 --- a/pcbnew/tracks_cleaner.cpp +++ b/pcbnew/tracks_cleaner.cpp @@ -340,7 +340,7 @@ void TRACKS_CLEANER::deleteTracksInPads() track->TransformShapeToPolygon( poly, track->GetLayer(), 0, ARC_HIGH_DEF, ERROR_INSIDE ); - poly.BooleanSubtract( *pad->GetEffectivePolygon( ERROR_INSIDE ), + poly.BooleanSubtract( *pad->GetEffectivePolygon( track->GetLayer(), ERROR_INSIDE ), SHAPE_POLY_SET::PM_FAST ); if( poly.IsEmpty() ) diff --git a/pcbnew/zone_filler.cpp b/pcbnew/zone_filler.cpp index 6681d3f9e6..9059c61d7a 100644 --- a/pcbnew/zone_filler.cpp +++ b/pcbnew/zone_filler.cpp @@ -267,7 +267,7 @@ bool ZONE_FILLER::Fill( const std::vector& aZones, bool aCheck, wxWindow* { if( pad->IsDirty() ) { - pad->BuildEffectiveShapes( UNDEFINED_LAYER ); + pad->BuildEffectiveShapes(); pad->BuildEffectivePolygon( ERROR_OUTSIDE ); } } @@ -910,7 +910,7 @@ bool ZONE_FILLER::Fill( const std::vector& aZones, bool aCheck, wxWindow* */ void ZONE_FILLER::addKnockout( PAD* aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles ) { - if( aPad->GetShape() == PAD_SHAPE::CUSTOM ) + if( aPad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM ) { SHAPE_POLY_SET poly; aPad->TransformShapeToPolygon( poly, aLayer, aGap, m_maxError, ERROR_OUTSIDE ); @@ -1407,7 +1407,7 @@ void ZONE_FILLER::buildCopperItemClearances( const ZONE* aZone, PCB_LAYER_ID aLa for( PAD* pad : allowedNetTiePads ) { if( pad->GetBoundingBox().Intersects( itemBBox ) - && pad->GetEffectiveShape()->Collide( itemShape.get() ) ) + && pad->GetEffectiveShape( aLayer )->Collide( itemShape.get() ) ) { skipItem = true; break; @@ -2025,7 +2025,7 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer, // Spoke width should ideally be smaller than the pad minor axis. // Otherwise the thermal shape is not really a thermal relief, // and the algo to count the actual number of spokes can fail - int spoke_max_allowed_w = std::min( pad->GetSize().x, pad->GetSize().y ); + int spoke_max_allowed_w = std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y ); spoke_w = alg::clamp( constraint.Value().Min(), spoke_w, constraint.Value().Max() ); @@ -2047,9 +2047,9 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer, bool customSpokes = false; - if( pad->GetShape() == PAD_SHAPE::CUSTOM ) + if( pad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM ) { - for( const std::shared_ptr& primitive : pad->GetPrimitives() ) + for( const std::shared_ptr& primitive : pad->GetPrimitives( aLayer ) ) { if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT ) { @@ -2132,7 +2132,7 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer, if( thermalPoly.OutlineCount() ) thermalOutline = thermalPoly.Outline( 0 ); - for( const std::shared_ptr& primitive : pad->GetPrimitives() ) + for( const std::shared_ptr& primitive : pad->GetPrimitives( aLayer ) ) { if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT ) { @@ -2141,11 +2141,11 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer, RotatePoint( seg.A, pad->GetOrientation() ); RotatePoint( seg.B, pad->GetOrientation() ); - seg.A += pad->ShapePos(); - seg.B += pad->ShapePos(); + seg.A += pad->ShapePos( aLayer ); + seg.B += pad->ShapePos( aLayer ); // Make sure seg.A is the origin - if( !pad->GetEffectivePolygon( ERROR_OUTSIDE )->Contains( seg.A ) ) + if( !pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Contains( seg.A ) ) seg.Reverse(); // Trim seg.B to the thermal outline @@ -2178,7 +2178,7 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer, // Spokes are from center of pad shape, not from hole. So the dummy pad has no shape // offset and is at position 0,0 dummy_pad.SetPosition( VECTOR2I( 0, 0 ) ); - dummy_pad.SetOffset( VECTOR2I( 0, 0 ) ); + dummy_pad.SetOffset( aLayer, VECTOR2I( 0, 0 ) ); BOX2I spokesBox = dummy_pad.GetBoundingBox(); @@ -2189,8 +2189,9 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer, // This is a touchy case because the bounding box for circles overshoots the mark // when rotated at 45 degrees. So we just build spokes at 0 degrees and rotate // them later. - if( pad->GetShape() == PAD_SHAPE::CIRCLE - || ( pad->GetShape() == PAD_SHAPE::OVAL && pad->GetSizeX() == pad->GetSizeY() ) ) + if( pad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE + || ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL + && pad->GetSizeX() == pad->GetSizeY() ) ) { buildSpokesFromOrigin( spokesBox, ANGLE_0 ); @@ -2211,7 +2212,7 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer, for( int ii = 0; ii < 4; ++ii, ++spokeIter ) { spokeIter->Rotate( pad->GetOrientation() ); - spokeIter->Move( pad->ShapePos() ); + spokeIter->Move( pad->ShapePos( aLayer ) ); } // Remove group membership from dummy item before deleting @@ -2417,7 +2418,7 @@ bool ZONE_FILLER::addHatchFillTypeOnZone( const ZONE* aZone, PCB_LAYER_ID aLayer // wide (and that the apron itself meets min_apron_radius. But that would // take a lot of code and math, and the following approximation is close // enough. - int pad_width = std::min( pad->GetSize().x, pad->GetSize().y ); + int pad_width = std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y ); int slot_width = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y ); int min_annular_ring_width = ( pad_width - slot_width ) / 2; int clearance = std::max( min_apron_radius - pad_width / 2, diff --git a/qa/data/pcbnew/padstacks.kicad_pro b/qa/data/pcbnew/padstacks.kicad_pro index 36aa69edf6..1786a9832e 100644 --- a/qa/data/pcbnew/padstacks.kicad_pro +++ b/qa/data/pcbnew/padstacks.kicad_pro @@ -69,6 +69,7 @@ "duplicate_footprints": "warning", "extra_footprint": "warning", "footprint": "error", + "footprint_filters_mismatch": "ignore", "footprint_symbol_mismatch": "warning", "footprint_type_mismatch": "ignore", "hole_clearance": "error", @@ -209,6 +210,7 @@ "mfg": "", "mpn": "" }, + "layer_pairs": [], "layer_presets": [], "viewports": [] }, @@ -237,6 +239,7 @@ "microvia_drill": 0.1, "name": "Default", "pcb_color": "rgba(0, 0, 0, 0.000)", + "priority": 2147483647, "schematic_color": "rgba(0, 0, 0, 0.000)", "track_width": 0.2, "via_diameter": 0.6, @@ -245,7 +248,7 @@ } ], "meta": { - "version": 3 + "version": 4 }, "net_colors": null, "netclass_assignments": null, diff --git a/qa/data/pcbnew/padstacks_complex.kicad_pcb b/qa/data/pcbnew/padstacks_complex.kicad_pcb new file mode 100644 index 0000000000..c1cd902b4a --- /dev/null +++ b/qa/data/pcbnew/padstacks_complex.kicad_pcb @@ -0,0 +1,514 @@ +(kicad_pcb + (version 20240922) + (generator "pcbnew") + (generator_version "8.99") + (general + (thickness 1.6) + (legacy_teardrops no) + ) + (paper "A4") + (layers + (0 "F.Cu" signal "Top Layer") + (4 "In1.Cu" signal "Inner 1") + (6 "In2.Cu" signal "Inner 2") + (2 "B.Cu" signal "Bottom Layer") + (13 "F.Paste" user "Top Paste") + (15 "B.Paste" user "Bottom Paste") + (5 "F.SilkS" user "Top Overlay") + (7 "B.SilkS" user "Bottom Overlay") + (1 "F.Mask" user "Top Solder") + (3 "B.Mask" user "Bottom Solder") + (17 "Dwgs.User" user "Mechanical 10") + (23 "Eco2.User" user "Mechanical 11") + (35 "F.Fab" user "Mechanical 12") + (33 "B.Fab" user "Mechanical 13") + (39 "User.1" auxiliary "Mechanical 1") + (41 "User.2" auxiliary "Mechanical 2") + (43 "User.3" auxiliary "Mechanical 3") + (45 "User.4" auxiliary "Mechanical 4") + (47 "User.5" auxiliary "Mechanical 5") + (49 "User.6" auxiliary "Mechanical 6") + (51 "User.7" auxiliary "Mechanical 7") + (53 "User.8" auxiliary "Mechanical 8") + (55 "User.9" auxiliary "Mechanical 9") + ) + (setup + (pad_to_mask_clearance 0.1016) + (allow_soldermask_bridges_in_footprints no) + (tenting front back) + (aux_axis_origin 139.0396 112.0521) + (grid_origin 139.0396 112.0521) + (pcbplotparams + (layerselection 0x55555555_5755f5ff) + (plot_on_all_layers_selection 0x00000000_00000000) + (disableapertmacros no) + (usegerberextensions no) + (usegerberattributes yes) + (usegerberadvancedattributes yes) + (creategerberjobfile yes) + (dashed_line_dash_ratio 12.000000) + (dashed_line_gap_ratio 3.000000) + (svgprecision 4) + (plotframeref no) + (mode 1) + (useauxorigin no) + (hpglpennumber 1) + (hpglpenspeed 20) + (hpglpendiameter 15.000000) + (pdf_front_fp_property_popups yes) + (pdf_back_fp_property_popups yes) + (pdf_metadata yes) + (dxfpolygonmode yes) + (dxfimperialunits yes) + (dxfusepcbnewfont yes) + (psnegative no) + (psa4output no) + (plotinvisibletext no) + (sketchpadsonfab no) + (plotpadnumbers no) + (hidednponfab no) + (sketchdnponfab yes) + (crossoutdnponfab yes) + (subtractmaskfromsilk no) + (outputformat 1) + (mirror no) + (drillshape 1) + (scaleselection 1) + (outputdirectory "") + ) + ) + (net 0 "") + (footprint "" + (layer "F.Cu") + (uuid "3a4eafab-d139-4800-b69e-143e1ef40b0a") + (at 141.9606 108.3691) + (property "Reference" "" + (at 0 0 0) + (layer "F.SilkS") + (uuid "049a62a3-9fc8-43fc-a6d0-e009ee98322c") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Value" "" + (at 0 0 0) + (layer "F.Fab") + (uuid "a3d33654-928f-49c7-aa57-2b03ba669843") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "2f286585-c62e-432a-b464-ee376e564620") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "cddfeda3-b3de-471e-8df7-17dc0bc1dc69") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "a3f8d3b5-ec53-41bc-b274-15f5a25e5114") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (pad "3" thru_hole rect + (at 0 0) + (size 0.762 0.762) + (drill 0.762) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (padstack + (mode custom) + (layer "F.Cu" + (shape rect) + (size 0.762 0.762) + ) + (layer "In1.Cu" + (shape rect) + (size 1.016 1.016) + ) + (layer "In2.Cu" + (shape rect) + (size 1.27 -1.27) + ) + (layer "B.Cu" + (shape rect) + (size 1.524 1.524) + ) + ) + (uuid "05e66fff-73b7-4418-9e88-371b772d1746") + ) + (embedded_fonts no) + ) + (footprint "" + (layer "F.Cu") + (uuid "4c101875-30c8-4fee-977d-d046f55e6cad") + (at 142.5956 101.6381) + (property "Reference" "" + (at 0 0 0) + (layer "F.SilkS") + (uuid "0d04b81f-058b-42ef-a522-47669cb798e6") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Value" "" + (at 0 0 0) + (layer "F.Fab") + (uuid "5b5c5819-a434-47d4-9736-2f8e808a301c") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "966a9396-01ef-4a5a-8c3a-8baccd1ffafe") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "7ee55c0a-19a6-4561-b497-7eca6881eee8") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "04b3d8a7-9949-4516-a9da-a2bd55990869") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (pad "1" thru_hole oval + (at 0 0) + (size 3.048 1.524) + (drill 0.762) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (padstack + (mode front_inner_back) + (layer "Inner" + (shape oval) + (size 1.27 1.27) + ) + (layer "B.Cu" + (shape oval) + (size 2.54 1.016) + ) + ) + (uuid "43d32944-eb49-4135-9a55-229ae4e9215e") + ) + (embedded_fonts no) + ) + (footprint "" + (layer "F.Cu") + (uuid "5d995ced-0862-4a26-9800-6a1e1be8ed5d") + (at 152.6286 101.8921) + (property "Reference" "" + (at 0 0 0) + (layer "F.SilkS") + (uuid "af2d434a-687b-485f-ac17-5f6c919d3fd9") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Value" "" + (at 0 0 0) + (layer "F.Fab") + (uuid "fa30a83b-8f4d-4c9f-a7c7-ca661b3837d3") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "849d5059-e8ed-44c6-8f77-2452f4a0bcd5") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "dd4b3753-45d0-452c-b5c5-d2af9b47f84f") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "52196d14-76ad-407f-9436-d010116d6793") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (pad "2" thru_hole rect + (at 0 0) + (size 0.762 1.524) + (drill 0.762) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (padstack + (mode front_inner_back) + (layer "Inner" + (shape roundrect) + (size 1.016 1.016) + (roundrect_rratio 0.25) + (chamfer_ratio 0.25) + (chamfer top_left top_right bottom_left bottom_right) + ) + (layer "B.Cu" + (shape roundrect) + (size 1.27 0.635) + (roundrect_rratio 0.25) + ) + ) + (uuid "6761b056-4502-46e7-9140-90350fc3a716") + ) + (embedded_fonts no) + ) + (footprint "" + (layer "F.Cu") + (uuid "de370b91-1036-4c23-b560-328b74f60526") + (at 151.1046 108.6231) + (property "Reference" "" + (at 0 0 0) + (layer "F.SilkS") + (uuid "00616395-462d-4abc-8a24-89627050c545") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Value" "" + (at 0 0 0) + (layer "F.Fab") + (uuid "e10b91a0-6954-40da-8392-e004088beea9") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "9ac2eaf9-a884-4e95-a69d-dd7a0234911a") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "ea366c2b-d3ad-4989-a762-e7add1a349f8") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (layer "F.Fab") + (hide yes) + (uuid "9778ea3d-95b6-4a66-9e3a-bc5b67a6675c") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (pad "4" thru_hole rect + (at 0 0) + (size 0.762 0.762) + (drill 0.762) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (padstack + (mode custom) + (layer "F.Cu" + (shape rect) + (size 0.762 0.762) + ) + (layer "In1.Cu" + (shape rect) + (size 1.016 1.016) + ) + (layer "In2.Cu" + (shape rect) + (size 1.27 -1.27) + ) + (layer "B.Cu" + (shape rect) + (size 1.524 1.524) + ) + ) + (uuid "073fbd1f-fe6f-43a2-b6dd-bb6db17d1841") + ) + (embedded_fonts no) + ) + (gr_line + (start 139.0396 97.9551) + (end 157.9626 97.9551) + (stroke + (width 0.05) + (type solid) + ) + (layer "Edge.Cuts") + (uuid "7f359d64-fc9d-4484-affe-7274dc9c796a") + ) + (gr_line + (start 157.9626 97.9551) + (end 157.9626 112.0521) + (stroke + (width 0.05) + (type solid) + ) + (layer "Edge.Cuts") + (uuid "9397ce7a-881e-4b3c-8637-a18786cf258d") + ) + (gr_line + (start 139.0396 112.0521) + (end 139.0396 97.9551) + (stroke + (width 0.05) + (type solid) + ) + (layer "Edge.Cuts") + (uuid "94f1cdaa-ec15-4a20-81c7-47a914486f88") + ) + (gr_line + (start 157.9626 112.0521) + (end 139.0396 112.0521) + (stroke + (width 0.05) + (type solid) + ) + (layer "Edge.Cuts") + (uuid "a24d3f7f-d3ee-4409-bc2f-902308d58680") + ) + (gr_line + (start 157.9626 112.0521) + (end 157.9626 97.9551) + (stroke + (width 0.05) + (type solid) + ) + (layer "User.1") + (uuid "27ae6dc5-e739-4845-b01a-2a7ab8e68ae6") + ) + (gr_line + (start 139.0396 112.0521) + (end 139.0396 97.9551) + (stroke + (width 0.05) + (type solid) + ) + (layer "User.1") + (uuid "7327172a-6e40-4311-b4ec-5c56e217b331") + ) + (gr_line + (start 139.0396 97.9551) + (end 157.9626 97.9551) + (stroke + (width 0.05) + (type solid) + ) + (layer "User.1") + (uuid "a7cbbedd-4d3f-47f7-8a32-77b448493a5f") + ) + (gr_line + (start 139.0396 112.0521) + (end 157.9626 112.0521) + (stroke + (width 0.05) + (type solid) + ) + (layer "User.1") + (uuid "b421b53f-7d4f-4aa0-9ae4-d1e8186eb79c") + ) + (embedded_fonts no) +) diff --git a/qa/data/pcbnew/padstacks_complex.kicad_pro b/qa/data/pcbnew/padstacks_complex.kicad_pro new file mode 100644 index 0000000000..7847c9944d --- /dev/null +++ b/qa/data/pcbnew/padstacks_complex.kicad_pro @@ -0,0 +1,294 @@ +{ + "board": { + "3dviewports": [], + "design_settings": { + "defaults": { + "apply_defaults_to_fp_fields": false, + "apply_defaults_to_fp_shapes": false, + "apply_defaults_to_fp_text": false, + "board_outline_line_width": 0.05, + "copper_line_width": 0.2, + "copper_text_italic": false, + "copper_text_size_h": 1.5, + "copper_text_size_v": 1.5, + "copper_text_thickness": 0.3, + "copper_text_upright": false, + "courtyard_line_width": 0.05, + "dimension_precision": 4, + "dimension_units": 3, + "dimensions": { + "arrow_length": 1270000, + "extension_offset": 500000, + "keep_text_aligned": true, + "suppress_zeroes": false, + "text_position": 0, + "units_format": 1 + }, + "fab_line_width": 0.1, + "fab_text_italic": false, + "fab_text_size_h": 1.0, + "fab_text_size_v": 1.0, + "fab_text_thickness": 0.15, + "fab_text_upright": false, + "other_line_width": 0.1, + "other_text_italic": false, + "other_text_size_h": 1.0, + "other_text_size_v": 1.0, + "other_text_thickness": 0.15, + "other_text_upright": false, + "pads": { + "drill": 0.8, + "height": 1.27, + "width": 2.54 + }, + "silk_line_width": 0.1, + "silk_text_italic": false, + "silk_text_size_h": 1.0, + "silk_text_size_v": 1.0, + "silk_text_thickness": 0.1, + "silk_text_upright": false, + "zones": { + "min_clearance": 0.5 + } + }, + "diff_pair_dimensions": [], + "drc_exclusions": [], + "meta": { + "version": 2 + }, + "rule_severities": { + "annular_width": "error", + "clearance": "error", + "connection_width": "warning", + "copper_edge_clearance": "error", + "copper_sliver": "warning", + "courtyards_overlap": "error", + "diff_pair_gap_out_of_range": "error", + "diff_pair_uncoupled_length_too_long": "error", + "drill_out_of_range": "error", + "duplicate_footprints": "warning", + "extra_footprint": "warning", + "footprint": "error", + "footprint_filters_mismatch": "ignore", + "footprint_symbol_mismatch": "warning", + "footprint_type_mismatch": "ignore", + "hole_clearance": "error", + "hole_to_hole": "warning", + "holes_co_located": "warning", + "invalid_outline": "error", + "isolated_copper": "warning", + "item_on_disabled_layer": "error", + "items_not_allowed": "error", + "length_out_of_range": "error", + "lib_footprint_issues": "warning", + "lib_footprint_mismatch": "warning", + "malformed_courtyard": "error", + "microvia_drill_out_of_range": "error", + "missing_courtyard": "ignore", + "missing_footprint": "warning", + "net_conflict": "warning", + "npth_inside_courtyard": "ignore", + "padstack": "warning", + "pth_inside_courtyard": "ignore", + "shorting_items": "error", + "silk_edge_clearance": "warning", + "silk_over_copper": "warning", + "silk_overlap": "warning", + "skew_out_of_range": "error", + "solder_mask_bridge": "error", + "starved_thermal": "error", + "text_height": "warning", + "text_thickness": "warning", + "through_hole_pad_without_hole": "error", + "too_many_vias": "error", + "track_dangling": "warning", + "track_width": "error", + "tracks_crossing": "error", + "unconnected_items": "error", + "unresolved_variable": "error", + "via_dangling": "warning", + "zones_intersect": "error" + }, + "rules": { + "max_error": 0.005, + "min_clearance": 0.254, + "min_connection": 0.0, + "min_copper_edge_clearance": 0.5, + "min_hole_clearance": 0.25, + "min_hole_to_hole": 0.254, + "min_microvia_diameter": 0.2, + "min_microvia_drill": 0.1, + "min_resolved_spokes": 2, + "min_silk_clearance": 0.0, + "min_text_height": 0.8, + "min_text_thickness": 0.08, + "min_through_hole_diameter": 0.7112, + "min_track_width": 0.254, + "min_via_annular_width": 0.1, + "min_via_diameter": 1.27, + "solder_mask_to_copper_clearance": 0.0, + "use_height_for_length_calcs": true + }, + "teardrop_options": [ + { + "td_onpadsmd": true, + "td_onroundshapesonly": false, + "td_ontrackend": false, + "td_onviapad": true + } + ], + "teardrop_parameters": [ + { + "td_allow_use_two_tracks": true, + "td_curve_segcount": 0, + "td_height_ratio": 1.0, + "td_length_ratio": 0.5, + "td_maxheight": 2.0, + "td_maxlen": 1.0, + "td_on_pad_in_zone": false, + "td_target_name": "td_round_shape", + "td_width_to_size_filter_ratio": 0.9 + }, + { + "td_allow_use_two_tracks": true, + "td_curve_segcount": 0, + "td_height_ratio": 1.0, + "td_length_ratio": 0.5, + "td_maxheight": 2.0, + "td_maxlen": 1.0, + "td_on_pad_in_zone": false, + "td_target_name": "td_rect_shape", + "td_width_to_size_filter_ratio": 0.9 + }, + { + "td_allow_use_two_tracks": true, + "td_curve_segcount": 0, + "td_height_ratio": 1.0, + "td_length_ratio": 0.5, + "td_maxheight": 2.0, + "td_maxlen": 1.0, + "td_on_pad_in_zone": false, + "td_target_name": "td_track_end", + "td_width_to_size_filter_ratio": 0.9 + } + ], + "track_widths": [], + "tuning_pattern_settings": { + "diff_pair_defaults": { + "corner_radius_percentage": 80, + "corner_style": 1, + "max_amplitude": 1.0, + "min_amplitude": 0.2, + "single_sided": false, + "spacing": 1.0 + }, + "diff_pair_skew_defaults": { + "corner_radius_percentage": 80, + "corner_style": 1, + "max_amplitude": 1.0, + "min_amplitude": 0.2, + "single_sided": false, + "spacing": 0.6 + }, + "single_track_defaults": { + "corner_radius_percentage": 80, + "corner_style": 1, + "max_amplitude": 1.0, + "min_amplitude": 0.2, + "single_sided": false, + "spacing": 0.6 + } + }, + "via_dimensions": [], + "zones_allow_external_fillets": false + }, + "ipc2581": { + "dist": "", + "distpn": "", + "internal_id": "", + "mfg": "", + "mpn": "" + }, + "layer_pairs": [], + "layer_presets": [], + "viewports": [] + }, + "boards": [], + "cvpcb": { + "equivalence_files": [] + }, + "libraries": { + "pinned_footprint_libs": [], + "pinned_symbol_libs": [] + }, + "meta": { + "filename": "padstacks_complex.kicad_pro", + "version": 1 + }, + "net_settings": { + "classes": [ + { + "bus_width": 12, + "clearance": 0.2, + "diff_pair_gap": 0.25, + "diff_pair_via_gap": 0.25, + "diff_pair_width": 0.2, + "line_style": 0, + "microvia_diameter": 0.3, + "microvia_drill": 0.1, + "name": "Default", + "pcb_color": "rgba(0, 0, 0, 0.000)", + "priority": -1, + "schematic_color": "rgba(0, 0, 0, 0.000)", + "track_width": 0.2, + "via_diameter": 0.6, + "via_drill": 0.3, + "wire_width": 6 + }, + { + "bus_width": 12, + "clearance": 0.2, + "diff_pair_gap": 0.25, + "diff_pair_via_gap": 0.25, + "diff_pair_width": 0.2, + "line_style": 0, + "microvia_diameter": 0.3, + "microvia_drill": 0.1, + "name": "All Nets", + "pcb_color": "rgba(0, 0, 0, 0.000)", + "priority": -1, + "schematic_color": "rgba(0, 0, 0, 0.000)", + "track_width": 0.2, + "via_diameter": 0.6, + "via_drill": 0.3, + "wire_width": 6 + } + ], + "meta": { + "version": 4 + }, + "net_colors": null, + "netclass_assignments": null, + "netclass_patterns": [] + }, + "pcbnew": { + "last_paths": { + "gencad": "", + "idf": "", + "netlist": "", + "plot": "", + "pos_files": "", + "specctra_dsn": "", + "step": "", + "svg": "", + "vrml": "" + }, + "page_layout_descr_file": "" + }, + "schematic": { + "legacy_lib_dir": "", + "legacy_lib_list": [] + }, + "sheets": [], + "text_variables": {} +} diff --git a/qa/data/pcbnew/padstacks_normal.kicad_pcb b/qa/data/pcbnew/padstacks_normal.kicad_pcb new file mode 100644 index 0000000000..9f98486648 --- /dev/null +++ b/qa/data/pcbnew/padstacks_normal.kicad_pcb @@ -0,0 +1,1349 @@ +(kicad_pcb + (version 20240108) + (generator "pcbnew") + (generator_version "8.0") + (general + (thickness 1.6) + (legacy_teardrops no) + ) + (paper "A4") + (layers + (0 "F.Cu" signal) + (31 "B.Cu" signal) + (32 "B.Adhes" user "B.Adhesive") + (33 "F.Adhes" user "F.Adhesive") + (34 "B.Paste" user) + (35 "F.Paste" user) + (36 "B.SilkS" user "B.Silkscreen") + (37 "F.SilkS" user "F.Silkscreen") + (38 "B.Mask" user) + (39 "F.Mask" user) + (40 "Dwgs.User" user "User.Drawings") + (41 "Cmts.User" user "User.Comments") + (42 "Eco1.User" user "User.Eco1") + (43 "Eco2.User" user "User.Eco2") + (44 "Edge.Cuts" user) + (45 "Margin" user) + (46 "B.CrtYd" user "B.Courtyard") + (47 "F.CrtYd" user "F.Courtyard") + (48 "B.Fab" user) + (49 "F.Fab" user) + (50 "User.1" user) + (51 "User.2" user) + (52 "User.3" user) + (53 "User.4" user) + (54 "User.5" user) + (55 "User.6" user) + (56 "User.7" user) + (57 "User.8" user) + (58 "User.9" user) + ) + (setup + (pad_to_mask_clearance 0) + (allow_soldermask_bridges_in_footprints no) + (pcbplotparams + (layerselection 0x00010fc_ffffffff) + (plot_on_all_layers_selection 0x0000000_00000000) + (disableapertmacros no) + (usegerberextensions no) + (usegerberattributes yes) + (usegerberadvancedattributes yes) + (creategerberjobfile yes) + (dashed_line_dash_ratio 12.000000) + (dashed_line_gap_ratio 3.000000) + (svgprecision 4) + (plotframeref no) + (viasonmask no) + (mode 1) + (useauxorigin no) + (hpglpennumber 1) + (hpglpenspeed 20) + (hpglpendiameter 15.000000) + (pdf_front_fp_property_popups yes) + (pdf_back_fp_property_popups yes) + (dxfpolygonmode yes) + (dxfimperialunits yes) + (dxfusepcbnewfont yes) + (psnegative no) + (psa4output no) + (plotreference yes) + (plotvalue yes) + (plotfptext yes) + (plotinvisibletext no) + (sketchpadsonfab no) + (subtractmaskfromsilk no) + (outputformat 1) + (mirror no) + (drillshape 1) + (scaleselection 1) + (outputdirectory "") + ) + ) + (net 0 "") + (footprint "Connector_JAE:JAE_LY20-18P-DLT1_2x09_P2.00mm_Horizontal" + (layer "F.Cu") + (uuid "316b7e5b-da3f-4b6c-be32-9ae354514af7") + (at 152 83) + (descr "Molex LY 20 series connector, LY20-18P-DLT1, 9 Circuits (http://www.jae.com/z-en/pdf_download_exec.cfm?param=SJ038187.pdf), generated with kicad-footprint-generator") + (tags "connector JAE top entry") + (property "Reference" "J2" + (at 3.2 -3 0) + (layer "F.SilkS") + (uuid "cd0c023a-c510-480a-889b-4b600ea5d231") + (effects + (font + (size 1 1) + (thickness 0.15) + ) + ) + ) + (property "Value" "JAE_LY20-18P-DLT1_2x09_P2.00mm_Horizontal" + (at 3.2 19 0) + (layer "F.Fab") + (uuid "539ec362-4c7e-41ed-89ed-9b30d9818178") + (effects + (font + (size 1 1) + (thickness 0.15) + ) + ) + ) + (property "Footprint" "Connector_JAE:JAE_LY20-18P-DLT1_2x09_P2.00mm_Horizontal" + (at 0 0 0) + (unlocked yes) + (layer "F.Fab") + (hide yes) + (uuid "0e3af1cc-4d94-4e02-847d-faf9f59f5d0a") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (unlocked yes) + (layer "F.Fab") + (hide yes) + (uuid "66483486-b0a0-4a11-a002-2c0ef7b03fe9") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (unlocked yes) + (layer "F.Fab") + (hide yes) + (uuid "b09d34d4-5135-4131-abe5-229f948eece9") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (attr through_hole) + (fp_line + (start -1.01 -2.21) + (end -1.01 0.2) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "83d2d342-dadc-44ae-bf70-258c9b5134ab") + ) + (fp_line + (start -0.71 -1.91) + (end -0.71 -0.86) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "d1a71da1-4349-4b6a-b437-a92aec44f743") + ) + (fp_line + (start -0.71 -1.91) + (end 7.11 -1.91) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "5650d852-afc5-4922-aca4-01dfeef5a82f") + ) + (fp_line + (start -0.71 0.86) + (end -0.71 1.514717) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "0f782441-b271-429a-90b4-b2d5eb072913") + ) + (fp_line + (start -0.71 2.485283) + (end -0.71 3.514717) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "ef1f4c5b-4541-4711-9d1c-9274a4551cd6") + ) + (fp_line + (start -0.71 4.485283) + (end -0.71 5.514717) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "07c8a3aa-8622-446a-b3e0-1a945e6923b3") + ) + (fp_line + (start -0.71 6.485283) + (end -0.71 7.514717) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "6c98ba2e-3c3a-464f-ad12-878f26ada9c7") + ) + (fp_line + (start -0.71 8.485283) + (end -0.71 9.514717) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "fa761da3-ce86-45d1-93cc-df978248a956") + ) + (fp_line + (start -0.71 10.485283) + (end -0.71 11.514717) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "55f14c6d-3192-4960-a7a8-c85908ccdea6") + ) + (fp_line + (start -0.71 12.485283) + (end -0.71 13.514717) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "ad8da40c-6024-421a-8082-d99240279317") + ) + (fp_line + (start -0.71 14.485283) + (end -0.71 15.514717) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "77109afb-94bc-4ec5-904f-3cae5c19e551") + ) + (fp_line + (start -0.71 16.485283) + (end -0.71 17.91) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "237701e1-c818-45cc-9551-9cac17ed29a4") + ) + (fp_line + (start -0.71 17.91) + (end 7.11 17.91) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "dd99635c-cefa-4ad9-97be-b908f76177c8") + ) + (fp_line + (start 1.4 -2.21) + (end -1.01 -2.21) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "2c95da81-1423-4650-a3df-724599ca6144") + ) + (fp_line + (start 4.2 2.3) + (end 4.2 8) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "2a15ac65-a8bb-4cc0-b132-473f4ca3b2e3") + ) + (fp_line + (start 4.2 3.75) + (end 6.733333 3.75) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "89808dd8-20d4-45be-aa10-d3e2e115744e") + ) + (fp_line + (start 4.2 5.75) + (end 6.733333 5.75) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "43055b29-f2ff-4969-83f9-a51089523f58") + ) + (fp_line + (start 4.2 7.75) + (end 6.733333 7.75) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "6b9fa94f-e074-4f96-b7eb-8ed9a4c39f0c") + ) + (fp_line + (start 4.2 9.75) + (end 6.733333 9.75) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "39ec60c7-61e1-49a2-8ab6-d6ef509fd9c3") + ) + (fp_line + (start 4.2 11.75) + (end 6.733333 11.75) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "2ddc9044-5a4e-445d-901c-5b414d6cab4b") + ) + (fp_line + (start 4.2 13.7) + (end 4.2 8) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "dca4a20e-aef4-4ebf-905b-bee21bc15653") + ) + (fp_line + (start 6.2 2.3) + (end 6.2 3.75) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "a0865ece-dbc3-4964-9293-e764ed5d0952") + ) + (fp_line + (start 6.2 13.7) + (end 6.2 12.25) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "40034a74-bb3f-4046-8791-3a97055fcf72") + ) + (fp_line + (start 6.733333 3.75) + (end 6.9 4) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "7144817f-adf1-418b-8df8-2563209dde41") + ) + (fp_line + (start 6.733333 4.25) + (end 4.2 4.25) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "2247e65f-ac72-4607-8fbd-d1479f0218bb") + ) + (fp_line + (start 6.733333 5.75) + (end 6.9 6) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "467d07c3-debc-4e65-81cd-557e4fd2e268") + ) + (fp_line + (start 6.733333 6.25) + (end 4.2 6.25) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "71d8b394-88d7-4365-af5f-ae1346ae4e7a") + ) + (fp_line + (start 6.733333 7.75) + (end 6.9 8) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "fa0eea7a-3bf2-4bde-8869-c03066fd5413") + ) + (fp_line + (start 6.733333 8.25) + (end 4.2 8.25) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "ac7afc04-1b40-4d78-adde-c6cd49793692") + ) + (fp_line + (start 6.733333 9.75) + (end 6.9 10) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "69b2333d-6824-490b-80c0-605f63007801") + ) + (fp_line + (start 6.733333 10.25) + (end 4.2 10.25) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "bcc654a5-219a-4d5d-8b97-ed283a6558c1") + ) + (fp_line + (start 6.733333 11.75) + (end 6.9 12) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "52dcde57-4411-491f-9284-01a96d05c817") + ) + (fp_line + (start 6.733333 12.25) + (end 4.2 12.25) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "b961ffed-6b71-448d-b820-0d755f34d000") + ) + (fp_line + (start 6.9 4) + (end 6.733333 4.25) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "3df82f36-ce8c-4f42-89c3-087be3470bdd") + ) + (fp_line + (start 6.9 6) + (end 6.733333 6.25) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "8ed0ecf6-2abe-470f-9b83-0283b954240c") + ) + (fp_line + (start 6.9 8) + (end 6.733333 8.25) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "6281e159-fa65-40e1-a72f-bcdeb6a0de75") + ) + (fp_line + (start 6.9 10) + (end 6.733333 10.25) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "96fb652e-6f5a-4cf7-b1a6-7d47dacf53a4") + ) + (fp_line + (start 6.9 12) + (end 6.733333 12.25) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "29e4c222-1a8c-4200-829d-32c47400c70d") + ) + (fp_line + (start 7.11 -1.91) + (end 7.11 2.3) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "f629cea0-b43c-4fa0-8c6d-ce13b2e6ee6d") + ) + (fp_line + (start 7.11 2.3) + (end 4.2 2.3) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "0958ecd9-7ba7-4038-9057-013e38092359") + ) + (fp_line + (start 7.11 13.7) + (end 4.2 13.7) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "4c466209-3270-4b7c-af1e-8be3aa8704e4") + ) + (fp_line + (start 7.11 17.91) + (end 7.11 13.7) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "9a45fc91-5d67-47dc-8f0e-c9c9e8772027") + ) + (fp_line + (start -1.1 -2.3) + (end -1.1 18.3) + (stroke + (width 0.05) + (type solid) + ) + (layer "F.CrtYd") + (uuid "7ae43fc7-f838-448f-9e04-de8d6ba86d62") + ) + (fp_line + (start -1.1 18.3) + (end 7.5 18.3) + (stroke + (width 0.05) + (type solid) + ) + (layer "F.CrtYd") + (uuid "9894d0ae-13f3-48d5-9852-5a9154c7280c") + ) + (fp_line + (start 7.5 -2.3) + (end -1.1 -2.3) + (stroke + (width 0.05) + (type solid) + ) + (layer "F.CrtYd") + (uuid "52f077a8-628c-4c93-8839-063c4f6743b7") + ) + (fp_line + (start 7.5 18.3) + (end 7.5 -2.3) + (stroke + (width 0.05) + (type solid) + ) + (layer "F.CrtYd") + (uuid "7c4f0de7-d3fc-4022-9ddb-4a15bd6a2cb8") + ) + (fp_line + (start -0.6 -1.8) + (end -0.6 17.8) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "275ef989-b52c-419c-ad53-9b60ca8ca6d3") + ) + (fp_line + (start -0.6 0.5) + (end 0.107107 0) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "0617c757-75ea-4eef-8fab-6ee7b4c3c7ab") + ) + (fp_line + (start -0.6 17.8) + (end 7 17.8) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "3a322705-0a40-43e1-b5c6-e416d6219149") + ) + (fp_line + (start 0.107107 0) + (end -0.6 -0.5) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "775f34a9-0262-4fe9-88aa-434c1bb481e8") + ) + (fp_line + (start 7 -1.8) + (end -0.6 -1.8) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "ca9ae2b0-da67-40c1-8f53-a81d62a8de00") + ) + (fp_line + (start 7 17.8) + (end 7 -1.8) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "03111640-65db-4876-ba00-953c66959af5") + ) + (fp_text user "${REFERENCE}" + (at 3.2 17.1 0) + (layer "F.Fab") + (uuid "b68f3323-b411-41cd-872a-150c7f26598b") + (effects + (font + (size 1 1) + (thickness 0.15) + ) + ) + ) + (pad "a1" thru_hole roundrect + (at 0 0) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (roundrect_rratio 0.208333) + (uuid "ea91b74a-4136-4257-853a-39ade95b1d31") + ) + (pad "a2" thru_hole trapezoid + (at 0 2) + (size 1.3 1.1) + (rect_delta 0.2 0) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "c84ce1a1-01bd-4a4d-997e-ddb22cef6e29") + ) + (pad "a3" thru_hole roundrect + (at 0 4) + (size 1 1.2) + (drill oval 0.4 0.6) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (roundrect_rratio 0.16) + (uuid "64e50337-771f-46a3-a076-61e16a78362d") + ) + (pad "a4" thru_hole roundrect + (at 0 6) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (roundrect_rratio 0) + (chamfer_ratio 0.3) + (chamfer bottom_left bottom_right) + (uuid "db269caf-357e-4dfb-aad7-d8d0b2584b2b") + ) + (pad "a5" thru_hole oval + (at 0 8) + (size 2 1.2) + (drill 0.8 + (offset -0.3 0) + ) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "65454bd0-a765-4b31-9de0-9572d5dc6ab9") + ) + (pad "a6" thru_hole circle + (at 0 10) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "9a7c3813-b665-45a2-8e99-859642f39bef") + ) + (pad "a7" thru_hole circle + (at 0 12) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "af07291c-8870-4cad-835b-b08f8579cb54") + ) + (pad "a8" thru_hole circle + (at 0 14) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "6aa53636-7c9d-488b-b46b-7b37b510c413") + ) + (pad "a9" thru_hole circle + (at 0 16) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "0be86ccd-0bdb-4f9c-b060-472725218d5a") + ) + (pad "b1" thru_hole oval + (at 2 0) + (size 2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "eedeff9a-1437-4504-bfc0-2d80d6ed697e") + ) + (pad "b2" thru_hole trapezoid + (at 2 2) + (size 1.2 1.2) + (rect_delta 0 0.5) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "fa9e14d0-c174-4e23-81f7-f8bf1ddfa62f") + ) + (pad "b3" thru_hole roundrect + (at 2 4) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (roundrect_rratio 0) + (chamfer_ratio 0.2) + (chamfer top_left) + (uuid "af52e452-de39-4499-b7d2-914d52c25987") + ) + (pad "b4" thru_hole roundrect + (at 2 6) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (roundrect_rratio 0.22) + (chamfer_ratio 0.21) + (chamfer top_left bottom_left) + (uuid "9d172811-4b43-4090-87a0-44527e36efea") + ) + (pad "b5" thru_hole roundrect + (at 2 8) + (size 1.2 1.2) + (drill 0.8 + (offset 0.1 -0.15) + ) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (roundrect_rratio 0) + (chamfer_ratio 0.2) + (chamfer top_left) + (uuid "e2d39975-c95f-4512-8375-223d4718decd") + ) + (pad "b6" thru_hole circle + (at 2 10) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "c5c4c7ad-3eb7-4155-93b0-ed579b1b5314") + ) + (pad "b7" thru_hole circle + (at 2 12) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "1731788f-ce7a-4ec8-ace3-a7a99fdf00ee") + ) + (pad "b8" thru_hole circle + (at 2 14) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "220fbcd5-e702-4a55-a40b-0a4a14e26f1f") + ) + (pad "b9" thru_hole circle + (at 2 16) + (size 1.2 1.2) + (drill 0.8) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "799c959e-472f-4787-9250-05cfac45f574") + ) + (model "${KICAD8_3DMODEL_DIR}/Connector_JAE.3dshapes/JAE_LY20-18P-DLT1_2x09_P2.00mm_Horizontal.wrl" + (offset + (xyz 0 0 0) + ) + (scale + (xyz 1 1 1) + ) + (rotate + (xyz 0 0 0) + ) + ) + ) + (footprint "Connector_Samtec:Samtec_LSHM-105-xx.x-x-DV-S_2x05-1SH_P0.50mm_Vertical" + (layer "F.Cu") + (uuid "766e01eb-6552-46e3-8465-091bc54046bf") + (at 139 91 90) + (descr "Molex LSHM 0.50 mm Razor Beam High-Speed Hermaphroditic Terminal/Socket Strip, LSHM-105-xx.x-x-DV-S, 5 Pins per row (http://suddendocs.samtec.com/prints/lshm-1xx-xx.x-x-dv-a-x-x-tr-footprint.pdf), generated with kicad-footprint-generator") + (tags "connector Samtec side entry") + (property "Reference" "J1" + (at 0 -3.8 90) + (layer "F.SilkS") + (uuid "859f2e23-2130-4e02-9f06-3bcbfd7aef8e") + (effects + (font + (size 1 1) + (thickness 0.15) + ) + ) + ) + (property "Value" "Samtec_LSHM-105-xx.x-x-DV-S_2x05-1SH_P0.50mm_Vertical" + (at 0 3.8 90) + (layer "F.Fab") + (uuid "0f1fa088-6505-41a3-b1f9-3fdec3a56ce3") + (effects + (font + (size 1 1) + (thickness 0.15) + ) + ) + ) + (property "Footprint" "Connector_Samtec:Samtec_LSHM-105-xx.x-x-DV-S_2x05-1SH_P0.50mm_Vertical" + (at 0 0 90) + (unlocked yes) + (layer "F.Fab") + (hide yes) + (uuid "c9709b6a-92c5-4955-bdbd-06228e68cf53") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 90) + (unlocked yes) + (layer "F.Fab") + (hide yes) + (uuid "cc1da934-81b1-4d4e-93f4-368ed065cf0a") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (property "Description" "" + (at 0 0 90) + (unlocked yes) + (layer "F.Fab") + (hide yes) + (uuid "12fb42aa-9de7-4240-a0cc-3709b5b4b09a") + (effects + (font + (size 1.27 1.27) + (thickness 0.15) + ) + ) + ) + (attr smd) + (fp_line + (start -0.8 -3.142843) + (end -1 -2.86) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "8caf45ff-05a0-4f7f-b252-38b864d1b475") + ) + (fp_line + (start -1.2 -3.142843) + (end -0.8 -3.142843) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "f11ef24c-627f-4187-9d5f-f101bf5bfcb1") + ) + (fp_line + (start -1 -2.86) + (end -1.2 -3.142843) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "0643fa2d-7286-4ea0-bce5-441410a97ab3") + ) + (fp_line + (start 4.185 -2.6) + (end 4.485 -2.3) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "62fc4097-afc7-4815-9d8a-2a8ed0e1648f") + ) + (fp_line + (start 1.41 -2.6) + (end 4.185 -2.6) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "0098078e-78b2-4c7d-a3bd-c2b6a30ff58f") + ) + (fp_line + (start -1.41 -2.6) + (end -4.185 -2.6) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "946917f8-a30c-40cf-8736-c7eeae9cf742") + ) + (fp_line + (start -4.185 -2.6) + (end -4.485 -2.3) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "5c4c4e37-9444-4a6c-8401-1bef8149b353") + ) + (fp_line + (start 4.485 -2.3) + (end 4.485 0.376954) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "41e3e54a-2ae1-432c-b506-8fa7637e32d3") + ) + (fp_line + (start -4.485 -2.3) + (end -4.485 0.376954) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "c9601b49-e76c-45a3-8310-7ae83b3d5dd7") + ) + (fp_line + (start 4.485 1.923046) + (end 4.485 2.3) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "0d718e36-cf8c-445f-96ad-89aa8082fe77") + ) + (fp_line + (start -4.485 1.923046) + (end -4.485 2.3) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "c2c613d6-deb4-49b2-a851-72c988ff0060") + ) + (fp_line + (start 4.485 2.3) + (end 4.185 2.6) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "b2905be3-7157-4872-a267-2c63049db938") + ) + (fp_line + (start -4.485 2.3) + (end -4.185 2.6) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "150d7c0d-f821-4db2-8339-256d32a2fed4") + ) + (fp_line + (start 4.185 2.6) + (end 1.41 2.6) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "5e87550a-506c-45c6-afc8-dae39516a3a9") + ) + (fp_line + (start -4.185 2.6) + (end -1.41 2.6) + (stroke + (width 0.12) + (type solid) + ) + (layer "F.SilkS") + (uuid "577d4b23-d0ed-4519-8092-46625f98e6d4") + ) + (fp_line + (start 4.97 -3.1) + (end -4.97 -3.1) + (stroke + (width 0.05) + (type solid) + ) + (layer "F.CrtYd") + (uuid "6d2759c9-3689-4aaa-b234-d04e89e18572") + ) + (fp_line + (start -4.97 -3.1) + (end -4.97 3.1) + (stroke + (width 0.05) + (type solid) + ) + (layer "F.CrtYd") + (uuid "0fb486db-49bb-4424-8989-e3b967937ff2") + ) + (fp_line + (start 4.97 3.1) + (end 4.97 -3.1) + (stroke + (width 0.05) + (type solid) + ) + (layer "F.CrtYd") + (uuid "9d8f8b65-aba5-47b9-bc79-8b26f8647882") + ) + (fp_line + (start -4.97 3.1) + (end 4.97 3.1) + (stroke + (width 0.05) + (type solid) + ) + (layer "F.CrtYd") + (uuid "57257ca5-4ff6-4cae-a9ce-5060178fabf7") + ) + (fp_line + (start 4.075 -2.49) + (end 4.375 -2.19) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "686aba36-0f30-4e95-b430-76d5dd6b438b") + ) + (fp_line + (start 0 -2.49) + (end 4.075 -2.49) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "8c66e9f8-a313-4c52-8238-386511842c01") + ) + (fp_line + (start 0 -2.49) + (end -4.075 -2.49) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "6367c6e9-e771-40a3-a059-fa8095ed0d52") + ) + (fp_line + (start -1.5 -2.49) + (end -1 -1.782893) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "de52bd11-3e33-415d-8f7b-4ee3b74b94cd") + ) + (fp_line + (start -4.075 -2.49) + (end -4.375 -2.19) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "b6a75b6e-4429-4c42-b86c-355c4b947b57") + ) + (fp_line + (start 4.375 -2.19) + (end 4.375 2.19) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "03135d33-a6db-41fb-aca8-3fd82ad1df86") + ) + (fp_line + (start -4.375 -2.19) + (end -4.375 2.19) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "c5d3586e-908b-4cfc-b4fd-9e0c7494705f") + ) + (fp_line + (start -1 -1.782893) + (end -0.5 -2.49) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "0ce255f7-6585-45c9-8585-f360dd47b092") + ) + (fp_line + (start 4.375 2.19) + (end 4.075 2.49) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "e5bf573e-9a92-4561-adb3-dd6d143920c4") + ) + (fp_line + (start -4.375 2.19) + (end -4.075 2.49) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "a2077244-5ef8-41b7-a8af-d4b30839fee7") + ) + (fp_line + (start 4.075 2.49) + (end 0 2.49) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "11494559-c7ae-4450-ab76-734a8b842ee8") + ) + (fp_line + (start -4.075 2.49) + (end 0 2.49) + (stroke + (width 0.1) + (type solid) + ) + (layer "F.Fab") + (uuid "5e5e348f-6fda-45ab-95b9-7f14fae9706d") + ) + (fp_text user "${REFERENCE}" + (at 0 0 90) + (layer "F.Fab") + (uuid "862a69a0-dc87-4eb4-91ec-26d88876e0eb") + (effects + (font + (size 1 1) + (thickness 0.15) + ) + ) + ) + (pad "" np_thru_hole circle + (at -2.25 -0.85 90) + (size 1.45 1.45) + (drill 1.45) + (layers "*.Cu" "*.Mask") + (uuid "a40648f5-a360-4552-a93b-ed5f867c5d6e") + ) + (pad "" np_thru_hole circle + (at 2.25 -0.85 90) + (size 1.45 1.45) + (drill 1.45) + (layers "*.Cu" "*.Mask") + (uuid "d7cc7819-ee1c-4762-802a-869bf2ba42a5") + ) + (pad "1" smd rect + (at -1 -1.85 90) + (size 0.3 1.5) + (layers "F.Cu" "F.Paste" "F.Mask") + (uuid "9a535bef-cfc2-4b16-9280-e59edfe99515") + ) + (pad "2" smd rect + (at -1 1.85 90) + (size 0.3 1.5) + (layers "F.Cu" "F.Paste" "F.Mask") + (uuid "2efb2f35-1de1-49c0-91b1-48391319c92c") + ) + (pad "3" smd rect + (at -0.5 -1.85 90) + (size 0.3 1.5) + (layers "F.Cu" "F.Paste" "F.Mask") + (uuid "0751ed5d-d702-4fd8-8dc9-22c05e96d9d1") + ) + (pad "4" smd rect + (at -0.5 1.85 90) + (size 0.3 1.5) + (layers "F.Cu" "F.Paste" "F.Mask") + (uuid "3d8ac901-ea85-46d9-a214-a6702d857436") + ) + (pad "5" smd rect + (at 0 -1.85 90) + (size 0.3 1.5) + (layers "F.Cu" "F.Paste" "F.Mask") + (uuid "655fd543-3b38-42ba-92fc-306558d9d200") + ) + (pad "6" smd rect + (at 0 1.85 90) + (size 0.3 1.5) + (layers "F.Cu" "F.Paste" "F.Mask") + (uuid "a55ae8dd-c646-4264-a6e3-a2c148dc5d96") + ) + (pad "7" smd rect + (at 0.5 -1.85 90) + (size 0.3 1.5) + (layers "F.Cu" "F.Paste" "F.Mask") + (uuid "7758c0e0-ee38-4b6c-afa1-042bfd670a7b") + ) + (pad "8" smd rect + (at 0.5 1.85 90) + (size 0.3 1.5) + (layers "F.Cu" "F.Paste" "F.Mask") + (uuid "22c49b36-0446-4ab9-9623-66129983bd3d") + ) + (pad "9" smd rect + (at 1 -1.85 90) + (size 0.3 1.5) + (layers "F.Cu" "F.Paste" "F.Mask") + (uuid "d6f52609-d02e-49c8-9fd2-d39910d6a18d") + ) + (pad "10" smd rect + (at 1 1.85 90) + (size 0.3 1.5) + (layers "F.Cu" "F.Paste" "F.Mask") + (uuid "eac38dff-cc6e-4f8c-ae6c-7b1de9f7a687") + ) + (pad "SH" thru_hole circle + (at -3.725 1.15 90) + (size 1.5 1.5) + (drill 1) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "7e0ff2fe-28e2-48b4-bc00-0fe8e855e3a5") + ) + (pad "SH" thru_hole circle + (at 3.725 1.15 90) + (size 1.5 1.5) + (drill 1) + (layers "*.Cu" "*.Mask") + (remove_unused_layers no) + (uuid "422e4b0d-0363-48bd-9d20-99616fd4ee1f") + ) + (model "${KICAD8_3DMODEL_DIR}/Connector_Samtec.3dshapes/Samtec_LSHM-105-xx.x-x-DV-S_2x05-1SH_P0.50mm_Vertical.wrl" + (offset + (xyz 0 0 0) + ) + (scale + (xyz 1 1 1) + ) + (rotate + (xyz 0 0 0) + ) + ) + ) +) diff --git a/qa/data/pcbnew/padstacks_normal.kicad_pro b/qa/data/pcbnew/padstacks_normal.kicad_pro new file mode 100644 index 0000000000..e0fe914f6b --- /dev/null +++ b/qa/data/pcbnew/padstacks_normal.kicad_pro @@ -0,0 +1,273 @@ +{ + "board": { + "3dviewports": [], + "design_settings": { + "defaults": { + "apply_defaults_to_fp_fields": false, + "apply_defaults_to_fp_shapes": false, + "apply_defaults_to_fp_text": false, + "board_outline_line_width": 0.05, + "copper_line_width": 0.2, + "copper_text_italic": false, + "copper_text_size_h": 1.5, + "copper_text_size_v": 1.5, + "copper_text_thickness": 0.3, + "copper_text_upright": false, + "courtyard_line_width": 0.05, + "dimension_precision": 4, + "dimension_units": 3, + "dimensions": { + "arrow_length": 1270000, + "extension_offset": 500000, + "keep_text_aligned": true, + "suppress_zeroes": false, + "text_position": 0, + "units_format": 1 + }, + "fab_line_width": 0.1, + "fab_text_italic": false, + "fab_text_size_h": 1.0, + "fab_text_size_v": 1.0, + "fab_text_thickness": 0.15, + "fab_text_upright": false, + "other_line_width": 0.1, + "other_text_italic": false, + "other_text_size_h": 1.0, + "other_text_size_v": 1.0, + "other_text_thickness": 0.15, + "other_text_upright": false, + "pads": { + "drill": 0.8, + "height": 1.2, + "width": 1.2 + }, + "silk_line_width": 0.1, + "silk_text_italic": false, + "silk_text_size_h": 1.0, + "silk_text_size_v": 1.0, + "silk_text_thickness": 0.1, + "silk_text_upright": false, + "zones": { + "min_clearance": 0.5 + } + }, + "diff_pair_dimensions": [], + "drc_exclusions": [], + "meta": { + "version": 2 + }, + "rule_severities": { + "annular_width": "error", + "clearance": "error", + "connection_width": "warning", + "copper_edge_clearance": "error", + "copper_sliver": "warning", + "courtyards_overlap": "error", + "diff_pair_gap_out_of_range": "error", + "diff_pair_uncoupled_length_too_long": "error", + "drill_out_of_range": "error", + "duplicate_footprints": "warning", + "extra_footprint": "warning", + "footprint": "error", + "footprint_symbol_mismatch": "warning", + "footprint_type_mismatch": "ignore", + "hole_clearance": "error", + "hole_near_hole": "error", + "holes_co_located": "warning", + "invalid_outline": "error", + "isolated_copper": "warning", + "item_on_disabled_layer": "error", + "items_not_allowed": "error", + "length_out_of_range": "error", + "lib_footprint_issues": "warning", + "lib_footprint_mismatch": "warning", + "malformed_courtyard": "error", + "microvia_drill_out_of_range": "error", + "missing_courtyard": "ignore", + "missing_footprint": "warning", + "net_conflict": "warning", + "npth_inside_courtyard": "ignore", + "padstack": "warning", + "pth_inside_courtyard": "ignore", + "shorting_items": "error", + "silk_edge_clearance": "warning", + "silk_over_copper": "warning", + "silk_overlap": "warning", + "skew_out_of_range": "error", + "solder_mask_bridge": "error", + "starved_thermal": "error", + "text_height": "warning", + "text_thickness": "warning", + "through_hole_pad_without_hole": "error", + "too_many_vias": "error", + "track_dangling": "warning", + "track_width": "error", + "tracks_crossing": "error", + "unconnected_items": "error", + "unresolved_variable": "error", + "via_dangling": "warning", + "zones_intersect": "error" + }, + "rules": { + "max_error": 0.005, + "min_clearance": 0.0, + "min_connection": 0.0, + "min_copper_edge_clearance": 0.5, + "min_hole_clearance": 0.25, + "min_hole_to_hole": 0.25, + "min_microvia_diameter": 0.2, + "min_microvia_drill": 0.1, + "min_resolved_spokes": 2, + "min_silk_clearance": 0.0, + "min_text_height": 0.8, + "min_text_thickness": 0.08, + "min_through_hole_diameter": 0.3, + "min_track_width": 0.0, + "min_via_annular_width": 0.1, + "min_via_diameter": 0.5, + "solder_mask_to_copper_clearance": 0.0, + "use_height_for_length_calcs": true + }, + "teardrop_options": [ + { + "td_onpadsmd": true, + "td_onroundshapesonly": false, + "td_ontrackend": false, + "td_onviapad": true + } + ], + "teardrop_parameters": [ + { + "td_allow_use_two_tracks": true, + "td_curve_segcount": 0, + "td_height_ratio": 1.0, + "td_length_ratio": 0.5, + "td_maxheight": 2.0, + "td_maxlen": 1.0, + "td_on_pad_in_zone": false, + "td_target_name": "td_round_shape", + "td_width_to_size_filter_ratio": 0.9 + }, + { + "td_allow_use_two_tracks": true, + "td_curve_segcount": 0, + "td_height_ratio": 1.0, + "td_length_ratio": 0.5, + "td_maxheight": 2.0, + "td_maxlen": 1.0, + "td_on_pad_in_zone": false, + "td_target_name": "td_rect_shape", + "td_width_to_size_filter_ratio": 0.9 + }, + { + "td_allow_use_two_tracks": true, + "td_curve_segcount": 0, + "td_height_ratio": 1.0, + "td_length_ratio": 0.5, + "td_maxheight": 2.0, + "td_maxlen": 1.0, + "td_on_pad_in_zone": false, + "td_target_name": "td_track_end", + "td_width_to_size_filter_ratio": 0.9 + } + ], + "track_widths": [], + "tuning_pattern_settings": { + "diff_pair_defaults": { + "corner_radius_percentage": 80, + "corner_style": 1, + "max_amplitude": 1.0, + "min_amplitude": 0.2, + "single_sided": false, + "spacing": 1.0 + }, + "diff_pair_skew_defaults": { + "corner_radius_percentage": 80, + "corner_style": 1, + "max_amplitude": 1.0, + "min_amplitude": 0.2, + "single_sided": false, + "spacing": 0.6 + }, + "single_track_defaults": { + "corner_radius_percentage": 80, + "corner_style": 1, + "max_amplitude": 1.0, + "min_amplitude": 0.2, + "single_sided": false, + "spacing": 0.6 + } + }, + "via_dimensions": [], + "zones_allow_external_fillets": false + }, + "ipc2581": { + "dist": "", + "distpn": "", + "internal_id": "", + "mfg": "", + "mpn": "" + }, + "layer_presets": [], + "viewports": [] + }, + "boards": [], + "cvpcb": { + "equivalence_files": [] + }, + "libraries": { + "pinned_footprint_libs": [], + "pinned_symbol_libs": [] + }, + "meta": { + "filename": "padstacks_normal.kicad_pro", + "version": 1 + }, + "net_settings": { + "classes": [ + { + "bus_width": 12, + "clearance": 0.2, + "diff_pair_gap": 0.25, + "diff_pair_via_gap": 0.25, + "diff_pair_width": 0.2, + "line_style": 0, + "microvia_diameter": 0.3, + "microvia_drill": 0.1, + "name": "Default", + "pcb_color": "rgba(0, 0, 0, 0.000)", + "schematic_color": "rgba(0, 0, 0, 0.000)", + "track_width": 0.2, + "via_diameter": 0.6, + "via_drill": 0.3, + "wire_width": 6 + } + ], + "meta": { + "version": 3 + }, + "net_colors": null, + "netclass_assignments": null, + "netclass_patterns": [] + }, + "pcbnew": { + "last_paths": { + "gencad": "", + "idf": "", + "netlist": "", + "plot": "", + "pos_files": "", + "specctra_dsn": "", + "step": "", + "svg": "", + "vrml": "" + }, + "page_layout_descr_file": "" + }, + "schematic": { + "legacy_lib_dir": "", + "legacy_lib_list": [] + }, + "sheets": [], + "text_variables": {} +} diff --git a/qa/pcbnew_utils/board_test_utils.cpp b/qa/pcbnew_utils/board_test_utils.cpp index 5b5b821f01..51c7cb3b76 100644 --- a/qa/pcbnew_utils/board_test_utils.cpp +++ b/qa/pcbnew_utils/board_test_utils.cpp @@ -328,6 +328,7 @@ void CheckFootprint( const FOOTPRINT* expected, const FOOTPRINT* fp ) void CheckFpPad( const PAD* expected, const PAD* pad ) { + // TODO(JE) padstacks BOOST_TEST_CONTEXT( "Assert PAD with KIID=" << expected->m_Uuid.AsString() ) { CHECK_ENUM_CLASS_EQUAL( expected->Type(), pad->Type() ); @@ -335,15 +336,19 @@ void CheckFpPad( const PAD* expected, const PAD* pad ) BOOST_CHECK_EQUAL( expected->GetNumber(), pad->GetNumber() ); CHECK_ENUM_CLASS_EQUAL( expected->GetAttribute(), pad->GetAttribute() ); CHECK_ENUM_CLASS_EQUAL( expected->GetProperty(), pad->GetProperty() ); - CHECK_ENUM_CLASS_EQUAL( expected->GetShape(), pad->GetShape() ); + CHECK_ENUM_CLASS_EQUAL( expected->GetShape( PADSTACK::ALL_LAYERS ), + pad->GetShape( PADSTACK::ALL_LAYERS ) ); BOOST_CHECK_EQUAL( expected->IsLocked(), pad->IsLocked() ); BOOST_CHECK_EQUAL( expected->GetPosition(), pad->GetPosition() ); - BOOST_CHECK_EQUAL( expected->GetSize(), pad->GetSize() ); + BOOST_CHECK_EQUAL( expected->GetSize( PADSTACK::ALL_LAYERS ), + pad->GetSize( PADSTACK::ALL_LAYERS ) ); BOOST_CHECK_EQUAL( expected->GetOrientation(), pad->GetOrientation() ); - BOOST_CHECK_EQUAL( expected->GetDelta(), pad->GetDelta() ); - BOOST_CHECK_EQUAL( expected->GetOffset(), pad->GetOffset() ); + BOOST_CHECK_EQUAL( expected->GetDelta( PADSTACK::ALL_LAYERS ), + pad->GetDelta( PADSTACK::ALL_LAYERS ) ); + BOOST_CHECK_EQUAL( expected->GetOffset( PADSTACK::ALL_LAYERS ), + pad->GetOffset( PADSTACK::ALL_LAYERS ) ); BOOST_CHECK_EQUAL( expected->GetDrillSize(), pad->GetDrillSize() ); CHECK_ENUM_CLASS_EQUAL( expected->GetDrillShape(), pad->GetDrillShape() ); @@ -365,25 +370,31 @@ void CheckFpPad( const PAD* expected, const PAD* pad ) BOOST_CHECK_EQUAL( expected->GetThermalSpokeWidth(), pad->GetThermalSpokeWidth() ); BOOST_CHECK_EQUAL( expected->GetThermalSpokeAngle(), pad->GetThermalSpokeAngle() ); BOOST_CHECK_EQUAL( expected->GetThermalGap(), pad->GetThermalGap() ); - BOOST_CHECK_EQUAL( expected->GetRoundRectRadiusRatio(), pad->GetRoundRectRadiusRatio() ); - BOOST_CHECK_EQUAL( expected->GetChamferRectRatio(), pad->GetChamferRectRatio() ); - BOOST_CHECK_EQUAL( expected->GetChamferPositions(), pad->GetChamferPositions() ); + BOOST_CHECK_EQUAL( expected->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ), + pad->GetRoundRectRadiusRatio( PADSTACK::ALL_LAYERS ) ); + BOOST_CHECK_EQUAL( expected->GetChamferRectRatio( PADSTACK::ALL_LAYERS ), + pad->GetChamferRectRatio( PADSTACK::ALL_LAYERS ) ); + BOOST_CHECK_EQUAL( expected->GetChamferPositions( PADSTACK::ALL_LAYERS ), + pad->GetChamferPositions( PADSTACK::ALL_LAYERS ) ); BOOST_CHECK_EQUAL( expected->GetRemoveUnconnected(), pad->GetRemoveUnconnected() ); BOOST_CHECK_EQUAL( expected->GetKeepTopBottom(), pad->GetKeepTopBottom() ); // TODO: did we check everything for complex pad shapes? - CHECK_ENUM_CLASS_EQUAL( expected->GetAnchorPadShape(), pad->GetAnchorPadShape() ); + CHECK_ENUM_CLASS_EQUAL( expected->GetAnchorPadShape( PADSTACK::ALL_LAYERS ), + pad->GetAnchorPadShape( PADSTACK::ALL_LAYERS ) ); CHECK_ENUM_CLASS_EQUAL( expected->GetCustomShapeInZoneOpt(), pad->GetCustomShapeInZoneOpt() ); - BOOST_CHECK_EQUAL( expected->GetPrimitives().size(), pad->GetPrimitives().size() ); + BOOST_CHECK_EQUAL( expected->GetPrimitives( PADSTACK::ALL_LAYERS ).size(), + pad->GetPrimitives( PADSTACK::ALL_LAYERS ).size() ); - if( expected->GetPrimitives().size() == pad->GetPrimitives().size() ) + if( expected->GetPrimitives( PADSTACK::ALL_LAYERS ).size() + == pad->GetPrimitives( PADSTACK::ALL_LAYERS ).size() ) { - for( size_t i = 0; i < expected->GetPrimitives().size(); ++i ) + for( size_t i = 0; i < expected->GetPrimitives( PADSTACK::ALL_LAYERS ).size(); ++i ) { - CheckFpShape( expected->GetPrimitives().at( i ).get(), - pad->GetPrimitives().at( i ).get() ); + CheckFpShape( expected->GetPrimitives( PADSTACK::ALL_LAYERS ).at( i ).get(), + pad->GetPrimitives( PADSTACK::ALL_LAYERS ).at( i ).get() ); } } diff --git a/qa/tests/pcbnew/test_pns_basics.cpp b/qa/tests/pcbnew/test_pns_basics.cpp index 75518a737a..6dd7539c29 100644 --- a/qa/tests/pcbnew/test_pns_basics.cpp +++ b/qa/tests/pcbnew/test_pns_basics.cpp @@ -53,9 +53,10 @@ static bool isCopper( const PNS::ITEM* aItem ) // round NPTH with a hole size >= pad size are not on a copper layer // All other NPTH are seen on copper layers // This is a basic criteria, but probably enough for a NPTH - if( pad->GetShape() == PAD_SHAPE::CIRCLE ) + // TODO(JE) padstacks + if( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE ) { - if( pad->GetSize().x <= pad->GetDrillSize().x ) + if( pad->GetSize( PADSTACK::ALL_LAYERS ).x <= pad->GetDrillSize().x ) return false; } diff --git a/qa/tests/pcbnew/test_save_load.cpp b/qa/tests/pcbnew/test_save_load.cpp index 1b456328d2..c4f8c5d3e2 100644 --- a/qa/tests/pcbnew/test_save_load.cpp +++ b/qa/tests/pcbnew/test_save_load.cpp @@ -51,12 +51,17 @@ BOOST_FIXTURE_TEST_CASE( RegressionSaveLoadTests, SAVE_LOAD_TEST_FIXTURE ) "issue6260", "issue6945", "issue7267", + "padstacks", + "padstacks_normal", + "padstacks_complex" /* "issue8003" */ }; // issue8003 is flaky on some platforms auto savePath = std::filesystem::temp_directory_path() / "group_saveload_tst.kicad_pcb"; for( const wxString& relPath : tests ) { + BOOST_TEST_CONTEXT( relPath ) + KI_TEST::LoadBoard( m_settingsManager, relPath, m_board ); KI_TEST::DumpBoardToFile( *m_board.get(), savePath.string() ); diff --git a/qa/tests/pcbnew/test_zone_filler.cpp b/qa/tests/pcbnew/test_zone_filler.cpp index 53e12b86cf..5db44a81a8 100644 --- a/qa/tests/pcbnew/test_zone_filler.cpp +++ b/qa/tests/pcbnew/test_zone_filler.cpp @@ -62,7 +62,10 @@ BOOST_FIXTURE_TEST_CASE( BasicZoneFills, ZONE_FILL_TEST_FIXTURE ) for( PAD* pad : m_board->Footprints()[0]->Pads() ) { if( pad->GetNumber() == "2" || pad->GetNumber() == "4" || pad->GetNumber() == "6" ) - pad->SetSize( pad->GetSize() + VECTOR2I( delta, delta ) ); + { + pad->SetSize( PADSTACK::ALL_LAYERS, + pad->GetSize( PADSTACK::ALL_LAYERS ) + VECTOR2I( delta, delta ) ); + } } int ii = 0;