From e16130a02c1be59fe3b1ddd97d60655ff5f011be Mon Sep 17 00:00:00 2001 From: Jon Evans Date: Mon, 8 Apr 2024 09:09:20 -0400 Subject: [PATCH] Move pad and via properties into PADSTACK --- pcbnew/api/api_pcb_enums.cpp | 42 +++ pcbnew/dialogs/dialog_pad_properties.cpp | 18 +- .../dialogs/dialog_track_via_properties.cpp | 24 +- pcbnew/pad.cpp | 294 +++++++-------- pcbnew/pad.h | 281 +++++++------- pcbnew/padstack.cpp | 342 +++++++++++++++++- pcbnew/padstack.h | 235 +++++++++++- pcbnew/pcb_io/altium/altium_pcb.cpp | 3 - .../cadstar/cadstar_pcb_archive_loader.cpp | 3 - pcbnew/pcb_io/eagle/pcb_io_eagle.cpp | 4 - .../pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp | 18 +- .../kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp | 10 +- pcbnew/pcb_track.cpp | 159 ++++---- pcbnew/pcb_track.h | 71 +++- pcbnew/zone_filler.cpp | 2 +- qa/tests/api/test_api_enums.cpp | 5 + 16 files changed, 1076 insertions(+), 435 deletions(-) diff --git a/pcbnew/api/api_pcb_enums.cpp b/pcbnew/api/api_pcb_enums.cpp index af7f3ec829..ecd7258391 100644 --- a/pcbnew/api/api_pcb_enums.cpp +++ b/pcbnew/api/api_pcb_enums.cpp @@ -103,3 +103,45 @@ ZONE_CONNECTION FromProtoEnum( types::ZoneConnectionStyle aValue ) "Unhandled case in FromProtoEnum" ); } } + + +template<> +types::UnconnectedLayerRemoval ToProtoEnum( PADSTACK::UNCONNECTED_LAYER_MODE aValue ) +{ + switch( aValue ) + { + case PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL: + return types::UnconnectedLayerRemoval::ULR_KEEP; + + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL: + return types::UnconnectedLayerRemoval::ULR_REMOVE; + + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END: + return types::UnconnectedLayerRemoval::ULR_REMOVE_EXCEPT_START_AND_END; + + default: + wxCHECK_MSG( false, types::UnconnectedLayerRemoval::ULR_UNKNOWN, + "Unhandled case in ToProtoEnum"); + } +} + + +template<> +PADSTACK::UNCONNECTED_LAYER_MODE FromProtoEnum( types::UnconnectedLayerRemoval aValue ) +{ + switch( aValue ) + { + case types::UnconnectedLayerRemoval::ULR_KEEP: + return PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL; + + case types::UnconnectedLayerRemoval::ULR_REMOVE: + return PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL; + + case types::UnconnectedLayerRemoval::ULR_REMOVE_EXCEPT_START_AND_END: + return PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END; + + default: + wxCHECK_MSG( false, PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL, + "Unhandled case in FromProtoEnum"); + } +} diff --git a/pcbnew/dialogs/dialog_pad_properties.cpp b/pcbnew/dialogs/dialog_pad_properties.cpp index 7f6b59ec42..8ab6146fa2 100644 --- a/pcbnew/dialogs/dialog_pad_properties.cpp +++ b/pcbnew/dialogs/dialog_pad_properties.cpp @@ -676,7 +676,7 @@ void DIALOG_PAD_PROPERTIES::initValues() case ZONE_CONNECTION::NONE: m_ZoneConnectionChoice->SetSelection( 3 ); break; } - if( m_previewPad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL ) + if( m_previewPad->GetCustomShapeInZoneOpt() == PADSTACK::CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL ) m_ZoneCustomPadShape->SetSelection( 1 ); else m_ZoneCustomPadShape->SetSelection( 0 ); @@ -1620,9 +1620,8 @@ bool DIALOG_PAD_PROPERTIES::TransferDataFromWindow() m_currentPad->SetAnchorPadShape( m_masterPad->GetAnchorPadShape() ); m_currentPad->ReplacePrimitives( m_masterPad->GetPrimitives() ); + m_currentPad->SetPadstack( m_masterPad->Padstack() ); m_currentPad->SetLayerSet( m_masterPad->GetLayerSet() ); - m_currentPad->SetRemoveUnconnected( m_masterPad->GetRemoveUnconnected() ); - m_currentPad->SetKeepTopBottom( m_masterPad->GetKeepTopBottom() ); m_currentPad->SetNumber( m_masterPad->GetNumber() ); @@ -1947,8 +1946,8 @@ bool DIALOG_PAD_PROPERTIES::transferDataToPad( PAD* aPad ) // shapes are convex to begin with, this really only makes any difference for custom // pad shapes. aPad->SetCustomShapeInZoneOpt( m_ZoneCustomPadShape->GetSelection() == 0 ? - CUST_PAD_SHAPE_IN_ZONE_OUTLINE : - CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL ); + PADSTACK::CUSTOM_SHAPE_ZONE_MODE::OUTLINE : + PADSTACK::CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL ); switch( aPad->GetAttribute() ) { @@ -2001,8 +2000,7 @@ bool DIALOG_PAD_PROPERTIES::transferDataToPad( PAD* aPad ) LSET padLayerMask = LSET(); int copperLayersChoice = m_rbCopperLayersSel->GetSelection(); - aPad->SetRemoveUnconnected( false ); - aPad->SetKeepTopBottom( false ); + aPad->Padstack().SetUnconnectedLayerMode( PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL ); switch( m_padType->GetSelection() ) { @@ -2017,14 +2015,14 @@ bool DIALOG_PAD_PROPERTIES::transferDataToPad( PAD* aPad ) case 1: // Front, back and connected padLayerMask |= LSET::AllCuMask(); - aPad->SetRemoveUnconnected( true ); - aPad->SetKeepTopBottom( true ); + aPad->Padstack().SetUnconnectedLayerMode( + PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END ); break; case 2: // Connected only padLayerMask |= LSET::AllCuMask(); - aPad->SetRemoveUnconnected( true ); + aPad->Padstack().SetUnconnectedLayerMode( PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL ); break; case 3: diff --git a/pcbnew/dialogs/dialog_track_via_properties.cpp b/pcbnew/dialogs/dialog_track_via_properties.cpp index 703e2653af..eef4d602f2 100644 --- a/pcbnew/dialogs/dialog_track_via_properties.cpp +++ b/pcbnew/dialogs/dialog_track_via_properties.cpp @@ -115,12 +115,13 @@ DIALOG_TRACK_VIA_PROPERTIES::DIALOG_TRACK_VIA_PROPERTIES( PCB_BASE_FRAME* aParen auto getAnnularRingSelection = []( const PCB_VIA* via ) -> int { - if( !via->GetRemoveUnconnected() ) - return 0; - else if( via->GetKeepStartEnd() ) - return 1; - else - return 2; + switch( via->Padstack().UnconnectedLayerMode() ) + { + default: + case PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL: return 0; + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END: return 1; + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL: return 2; + } }; // Look for values that are common for every item that is selected @@ -649,15 +650,16 @@ bool DIALOG_TRACK_VIA_PROPERTIES::TransferDataFromWindow() switch( m_annularRingsCtrl->GetSelection() ) { case 0: - v->SetRemoveUnconnected( false ); + v->Padstack().SetUnconnectedLayerMode( + PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL ); break; case 1: - v->SetRemoveUnconnected( true ); - v->SetKeepStartEnd( true ); + v->Padstack().SetUnconnectedLayerMode( + PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END ); break; case 2: - v->SetRemoveUnconnected( true ); - v->SetKeepStartEnd( false ); + v->Padstack().SetUnconnectedLayerMode( + PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL ); break; default: break; diff --git a/pcbnew/pad.cpp b/pcbnew/pad.cpp index 1f4ba44611..d974a71b1b 100644 --- a/pcbnew/pad.cpp +++ b/pcbnew/pad.cpp @@ -68,8 +68,10 @@ using KIGFX::PCB_RENDER_SETTINGS; PAD::PAD( FOOTPRINT* parent ) : BOARD_CONNECTED_ITEM( parent, PCB_PAD_T ) { - m_size.x = m_size.y = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 60 ); // Default pad size 60 mils. - m_drill.x = m_drill.y = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 30 ); // Default drill size 30 mils. + VECTOR2I& drill = m_padStack.Drill().size; + VECTOR2I& size = m_padStack.Size(); + 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_orient = ANGLE_0; m_lengthPadToDie = 0; @@ -84,28 +86,19 @@ PAD::PAD( FOOTPRINT* parent ) : SetProperty( PAD_PROP::NONE ); // no special fabrication property // Parameters for round rect only: - m_roundedCornerScale = 0.25; // from IPC-7351C standard + m_padStack.SetRoundRectRadiusRatio( 0.25 ); // from IPC-7351C standard // Parameters for chamfered rect only: - m_chamferScale = 0.2; // Size of chamfer: ratio of smallest of X,Y size - m_chamferPositions = RECT_NO_CHAMFER; // No chamfered corner - - m_zoneConnection = ZONE_CONNECTION::INHERITED; // Use parent setting by default - m_thermalSpokeWidth = 0; // Use parent setting by default - m_thermalSpokeAngle = ANGLE_45; // Default for circular pads - m_thermalGap = 0; // Use parent setting by default - - m_customShapeClearanceArea = CUST_PAD_SHAPE_IN_ZONE_OUTLINE; + m_padStack.SetChamferRatio( 0.2 ); + m_padStack.SetChamferPositions( RECT_NO_CHAMFER ); // Set layers mask to default for a standard thru hole pad. - m_layerMask = PTHMask(); + m_padStack.SetLayerSet( PTHMask() ); SetSubRatsnest( 0 ); // used in ratsnest calculations SetDirty(); m_effectiveBoundingRadius = 0; - m_removeUnconnectedLayer = false; - m_keepTopBottomLayer = true; m_zoneLayerOverrides.fill( ZLO_NONE ); } @@ -132,8 +125,6 @@ PAD& PAD::operator=( const PAD &aOther ) SetPinFunction( aOther.GetPinFunction() ); SetSubRatsnest( aOther.GetSubRatsnest() ); m_effectiveBoundingRadius = aOther.m_effectiveBoundingRadius; - m_removeUnconnectedLayer = aOther.m_removeUnconnectedLayer; - m_keepTopBottomLayer = aOther.m_keepTopBottomLayer; return *this; } @@ -167,21 +158,9 @@ void PAD::Serialize( google::protobuf::Any &aContainer ) const stackLayer->set_shape( ToProtoEnum( GetShape() ) ); - kiapi::board::types::UnconnectedLayerRemoval ulr; - - if( m_removeUnconnectedLayer ) - { - if( m_keepTopBottomLayer ) - ulr = kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE_EXCEPT_START_AND_END; - else - ulr = kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE; - } - else - { - ulr = kiapi::board::types::UnconnectedLayerRemoval::ULR_KEEP; - } - - padstack->set_unconnected_layer_removal( ulr ); + padstack->set_unconnected_layer_removal( + ToProtoEnum( GetUnconnectedLayerMode() ) ); kiapi::board::types::DesignRuleOverrides* overrides = pad.mutable_overrides(); @@ -240,24 +219,8 @@ bool PAD::Deserialize( const google::protobuf::Any &aContainer ) SetShape( FromProtoEnum( layer.shape() ) ); } - switch( padstack.unconnected_layer_removal() ) - { - case kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE: - m_removeUnconnectedLayer = true; - m_keepTopBottomLayer = false; - break; - - case kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE_EXCEPT_START_AND_END: - m_removeUnconnectedLayer = true; - m_keepTopBottomLayer = true; - break; - - default: - case kiapi::board::types::UnconnectedLayerRemoval::ULR_KEEP: - m_removeUnconnectedLayer = false; - m_keepTopBottomLayer = false; - break; - } + SetUnconnectedLayerMode( + FromProtoEnum( padstack.unconnected_layer_removal() ) ); const kiapi::board::types::DesignRuleOverrides& overrides = pad.overrides(); @@ -453,13 +416,18 @@ bool PAD::FlashLayer( int aLayer, bool aOnlyCheckIfPermitted ) const if( GetProperty() == PAD_PROP::HEATSINK ) return true; - if( !m_removeUnconnectedLayer ) + PADSTACK::UNCONNECTED_LAYER_MODE mode = m_padStack.UnconnectedLayerMode(); + + if( mode == PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL ) return true; // Plated through hole pads need copper on the top/bottom layers for proper soldering // Unless the user has removed them in the pad dialog - if( m_keepTopBottomLayer && ( aLayer == F_Cu || aLayer == B_Cu ) ) + if( mode == PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END + && ( aLayer == F_Cu || aLayer == B_Cu ) ) + { return true; + } if( const BOARD* board = GetBoard() ) { @@ -482,22 +450,19 @@ bool PAD::FlashLayer( int aLayer, bool aOnlyCheckIfPermitted ) const int PAD::GetRoundRectCornerRadius() const { - return KiROUND( std::min( m_size.x, m_size.y ) * m_roundedCornerScale ); + return m_padStack.RoundRectRadius(); } void PAD::SetRoundRectCornerRadius( double aRadius ) { - int min_r = std::min( m_size.x, m_size.y ); - - if( min_r > 0 ) - SetRoundRectRadiusRatio( aRadius / min_r ); + m_padStack.SetRoundRectRadius( aRadius ); } void PAD::SetRoundRectRadiusRatio( double aRadiusScale ) { - m_roundedCornerScale = alg::clamp( 0.0, aRadiusScale, 0.5 ); + m_padStack.SetRoundRectRadiusRatio( alg::clamp( 0.0, aRadiusScale, 0.5 ) ); SetDirty(); } @@ -505,7 +470,7 @@ void PAD::SetRoundRectRadiusRatio( double aRadiusScale ) void PAD::SetChamferRectRatio( double aChamferScale ) { - m_chamferScale = alg::clamp( 0.0, aChamferScale, 0.5 ); + m_padStack.SetChamferRatio( alg::clamp( 0.0, aChamferScale, 0.5 ) ); SetDirty(); } @@ -597,6 +562,7 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const VECTOR2I shapePos = ShapePos(); // Fetch only once; rotation involves trig PAD_SHAPE effectiveShape = GetShape(); + const VECTOR2I& size = m_padStack.Size( aLayer ); if( GetShape() == PAD_SHAPE::CUSTOM ) effectiveShape = GetAnchorPadShape(); @@ -604,17 +570,17 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const switch( effectiveShape ) { case PAD_SHAPE::CIRCLE: - add( new SHAPE_CIRCLE( shapePos, m_size.x / 2 ) ); + add( new SHAPE_CIRCLE( shapePos, size.x / 2 ) ); break; case PAD_SHAPE::OVAL: - if( m_size.x == m_size.y ) // the oval pad is in fact a circle + if( size.x == size.y ) // the oval pad is in fact a circle { - add( new SHAPE_CIRCLE( shapePos, m_size.x / 2 ) ); + add( new SHAPE_CIRCLE( shapePos, size.x / 2 ) ); } else { - VECTOR2I half_size = m_size / 2; + VECTOR2I half_size = 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, m_orient ); @@ -628,7 +594,7 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const case PAD_SHAPE::ROUNDRECT: { int r = ( effectiveShape == PAD_SHAPE::ROUNDRECT ) ? GetRoundRectCornerRadius() : 0; - VECTOR2I half_size( m_size.x / 2, m_size.y / 2 ); + VECTOR2I half_size( size.x / 2, size.y / 2 ); VECTOR2I trap_delta( 0, 0 ); if( r ) @@ -647,7 +613,7 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const } else if( effectiveShape == PAD_SHAPE::TRAPEZOID ) { - trap_delta = m_deltaSize / 2; + trap_delta = m_padStack.TrapezoidDeltaSize( aLayer ) / 2; } SHAPE_LINE_CHAIN corners; @@ -735,7 +701,7 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const m_effectiveBoundingBox = m_effectiveShape->BBox(); // Hole shape - VECTOR2I half_size = m_drill / 2; + 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 ); @@ -808,31 +774,33 @@ void PAD::SetAttribute( PAD_ATTRIB aAttribute ) { m_attribute = aAttribute; + LSET& layerMask = m_padStack.LayerSet(); + switch( aAttribute ) { case PAD_ATTRIB::PTH: // Plump up to all copper layers - m_layerMask |= LSET::AllCuMask(); + layerMask |= LSET::AllCuMask(); break; case PAD_ATTRIB::SMD: case PAD_ATTRIB::CONN: { // Trim down to no more than one copper layer - LSET copperLayers = m_layerMask & LSET::AllCuMask(); + LSET copperLayers = layerMask & LSET::AllCuMask(); if( copperLayers.count() > 1 ) { - m_layerMask &= ~LSET::AllCuMask(); + layerMask &= ~LSET::AllCuMask(); if( copperLayers.test( B_Cu ) ) - m_layerMask.set( B_Cu ); + layerMask.set( B_Cu ); else - m_layerMask.set( copperLayers.Seq().front() ); + layerMask.set( copperLayers.Seq().front() ); } // No hole - m_drill = VECTOR2I( 0, 0 ); + m_padStack.Drill().size = VECTOR2I( 0, 0 ); break; } @@ -888,14 +856,14 @@ void PAD::Flip( const VECTOR2I& aCentre, bool aFlipLeftRight ) if( aFlipLeftRight ) { MIRROR( m_pos.x, aCentre.x ); - MIRROR( m_offset.x, 0 ); - MIRROR( m_deltaSize.x, 0 ); + MIRROR( m_padStack.Offset().x, 0 ); + MIRROR( m_padStack.TrapezoidDeltaSize().x, 0 ); } else { MIRROR( m_pos.y, aCentre.y ); - MIRROR( m_offset.y, 0 ); - MIRROR( m_deltaSize.y, 0 ); + MIRROR( m_padStack.Offset().y, 0 ); + MIRROR( m_padStack.TrapezoidDeltaSize().y, 0 ); } SetFPRelativeOrientation( -GetFPRelativeOrientation() ); @@ -917,20 +885,24 @@ void PAD::Flip( const VECTOR2I& aCentre, bool aFlipLeftRight ) if( aFlipLeftRight ) { - mirrorBitFlags( m_chamferPositions, RECT_CHAMFER_TOP_LEFT, RECT_CHAMFER_TOP_RIGHT ); - mirrorBitFlags( m_chamferPositions, RECT_CHAMFER_BOTTOM_LEFT, RECT_CHAMFER_BOTTOM_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_chamferPositions, RECT_CHAMFER_TOP_LEFT, RECT_CHAMFER_BOTTOM_LEFT ); - mirrorBitFlags( m_chamferPositions, RECT_CHAMFER_TOP_RIGHT, RECT_CHAMFER_BOTTOM_RIGHT ); + mirrorBitFlags( m_padStack.ChamferPositions(), RECT_CHAMFER_TOP_LEFT, + RECT_CHAMFER_BOTTOM_LEFT ); + mirrorBitFlags( m_padStack.ChamferPositions(), RECT_CHAMFER_TOP_RIGHT, + RECT_CHAMFER_BOTTOM_RIGHT ); } // flip pads layers // PADS items are currently on all copper layers, or // currently, only on Front or Back layers. // So the copper layers count is not taken in account - SetLayerSet( FlipLayerMask( m_layerMask ) ); + SetLayerSet( FlipLayerMask( m_padStack.LayerSet() ) ); // Flip the basic shapes, in custom pads FlipPrimitives( aFlipLeftRight ); @@ -950,10 +922,10 @@ void PAD::FlipPrimitives( bool aFlipLeftRight ) VECTOR2I PAD::ShapePos() const { - if( m_offset.x == 0 && m_offset.y == 0 ) + if( m_padStack.Offset().x == 0 && m_padStack.Offset().y == 0 ) return m_pos; - VECTOR2I loc_offset = m_offset; + VECTOR2I loc_offset = m_padStack.Offset(); RotatePoint( loc_offset, m_orient ); @@ -973,14 +945,21 @@ bool PAD::IsOnCopperLayer() const switch( GetShape() ) { case PAD_SHAPE::CIRCLE: - if( m_offset == VECTOR2I( 0, 0 ) && m_size.x <= m_drill.x ) + if( m_padStack.Offset() == VECTOR2I( 0, 0 ) + && m_padStack.Size().x <= m_padStack.Drill().size.x ) + { return false; + } break; case PAD_SHAPE::OVAL: - if( m_offset == VECTOR2I( 0, 0 ) && m_size.x <= m_drill.x && m_size.y <= m_drill.y ) + 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; @@ -997,16 +976,16 @@ bool PAD::IsOnCopperLayer() const std::optional PAD::GetLocalClearance( wxString* aSource ) const { - if( m_clearance.has_value() && aSource ) + if( m_padStack.Clearance().has_value() && aSource ) *aSource = _( "pad" ); - return m_clearance; + return m_padStack.Clearance(); } std::optional PAD::GetClearanceOverrides( wxString* aSource ) const { - if( m_clearance.has_value() ) + if( m_padStack.Clearance().has_value() ) return GetLocalClearance( aSource ); if( FOOTPRINT* parentFootprint = GetParentFootprint() ) @@ -1047,10 +1026,10 @@ int PAD::GetSolderMaskExpansion() 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 // any copper (such as NPTH pads with holes the same size as the pad) get mask expansion. - if( ( m_layerMask & LSET::AllCuMask() ).none() ) + if( ( m_padStack.LayerSet() & LSET::AllCuMask() ).none() ) return 0; - std::optional margin = m_solderMaskMargin; + std::optional margin = m_padStack.SolderMaskMargin(); if( !margin.has_value() ) { @@ -1069,7 +1048,7 @@ int PAD::GetSolderMaskExpansion() const // ensure mask have a size always >= 0 if( marginValue < 0 ) { - int minsize = -std::min( m_size.x, m_size.y ) / 2; + int minsize = -std::min( m_padStack.Size().x, m_padStack.Size().y ) / 2; if( marginValue < minsize ) marginValue = minsize; @@ -1084,11 +1063,11 @@ VECTOR2I PAD::GetSolderPasteMargin() 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 // any copper (such as NPTH pads with holes the same size as the pad) get paste expansion. - if( ( m_layerMask & LSET::AllCuMask() ).none() ) + if( ( m_padStack.LayerSet() & LSET::AllCuMask() ).none() ) return VECTOR2I( 0, 0 ); - std::optional margin = m_solderPasteMargin; - std::optional mratio = m_solderPasteMarginRatio; + std::optional margin = m_padStack.SolderPasteMargin(); + std::optional mratio = m_padStack.SolderPasteMarginRatio(); if( !margin.has_value() ) { @@ -1115,17 +1094,17 @@ VECTOR2I PAD::GetSolderPasteMargin() const } VECTOR2I pad_margin; - pad_margin.x = margin.value_or( 0 ) + KiROUND( m_size.x * mratio.value_or( 0 ) ); - pad_margin.y = margin.value_or( 0 ) + KiROUND( m_size.y * mratio.value_or( 0 ) ); + 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 ) ); // ensure mask have a size always >= 0 - if( m_padShape != PAD_SHAPE::CUSTOM ) + if( m_padStack.Shape() != PAD_SHAPE::CUSTOM ) { - if( pad_margin.x < -m_size.x / 2 ) - pad_margin.x = -m_size.x / 2; + if( pad_margin.x < -m_padStack.Size().x / 2 ) + pad_margin.x = -m_padStack.Size().x / 2; - if( pad_margin.y < -m_size.y / 2 ) - pad_margin.y = -m_size.y / 2; + if( pad_margin.y < -m_padStack.Size().y / 2 ) + pad_margin.y = -m_padStack.Size().y / 2; } return pad_margin; @@ -1134,7 +1113,7 @@ VECTOR2I PAD::GetSolderPasteMargin() const ZONE_CONNECTION PAD::GetZoneConnectionOverrides( wxString* aSource ) const { - ZONE_CONNECTION connection = m_zoneConnection; + ZONE_CONNECTION connection = m_padStack.ZoneConnection().value_or( ZONE_CONNECTION::INHERITED ); if( connection != ZONE_CONNECTION::INHERITED ) { @@ -1154,19 +1133,19 @@ ZONE_CONNECTION PAD::GetZoneConnectionOverrides( wxString* aSource ) const int PAD::GetLocalSpokeWidthOverride( wxString* aSource ) const { - if( m_thermalSpokeWidth > 0 && aSource ) + if( m_padStack.ThermalSpokeWidth().has_value() && aSource ) *aSource = _( "pad" ); - return m_thermalSpokeWidth; + return m_padStack.ThermalSpokeWidth().value_or( 0 ); } int PAD::GetLocalThermalGapOverride( wxString* aSource ) const { - if( m_thermalGap > 0 && aSource ) + if( m_padStack.ThermalGap().has_value() && aSource ) *aSource = _( "pad" ); - return m_thermalGap; + return m_padStack.ThermalGap().value_or( 0 ); } @@ -1235,14 +1214,14 @@ void PAD::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aList.emplace_back( ShowPadShape(), props ); if( ( GetShape() == PAD_SHAPE::CIRCLE || GetShape() == PAD_SHAPE::OVAL ) - && m_size.x == m_size.y ) + && m_padStack.Size().x == m_padStack.Size().y ) { - aList.emplace_back( _( "Diameter" ), aFrame->MessageTextFromValue( m_size.x ) ); + aList.emplace_back( _( "Diameter" ), aFrame->MessageTextFromValue( m_padStack.Size().x ) ); } else { - aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( m_size.x ) ); - aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( m_size.y ) ); + aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( m_padStack.Size().x ) ); + aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( m_padStack.Size().y ) ); } EDA_ANGLE fp_orient = parentFootprint ? parentFootprint->GetOrientation() : ANGLE_0; @@ -1262,20 +1241,22 @@ void PAD::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aFrame->MessageTextFromValue( GetPadToDieLength() ) ); } - if( m_drill.x > 0 || m_drill.y > 0 ) + const VECTOR2I& drill = m_padStack.Drill().size; + + if( drill.x > 0 || drill.y > 0 ) { if( GetDrillShape() == PAD_DRILL_SHAPE::CIRCLE ) { aList.emplace_back( _( "Hole" ), wxString::Format( wxT( "%s" ), - aFrame->MessageTextFromValue( m_drill.x ) ) ); + aFrame->MessageTextFromValue( drill.x ) ) ); } else { aList.emplace_back( _( "Hole X / Y" ), wxString::Format( wxT( "%s / %s" ), - aFrame->MessageTextFromValue( m_drill.x ), - aFrame->MessageTextFromValue( m_drill.y ) ) ); + aFrame->MessageTextFromValue( drill.x ), + aFrame->MessageTextFromValue( drill.y ) ) ); } } @@ -1354,6 +1335,8 @@ 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; @@ -1362,41 +1345,53 @@ int PAD::Compare( const PAD* aPadRef, const PAD* aPadCmp ) static_cast( aPadCmp->m_attribute ) ) != 0 ) return diff; - if( ( diff = static_cast( aPadRef->m_drillShape ) - - static_cast( aPadCmp->m_drillShape ) ) != 0 ) + if( ( diff = static_cast( aPadRef->GetDrillShape() ) - + static_cast( aPadCmp->GetDrillShape() ) ) != 0 ) return diff; - if( ( diff = aPadRef->m_drill.x - aPadCmp->m_drill.x ) != 0 ) + if( ( diff = aPadRef->Padstack().Drill().size.x - aPadCmp->Padstack().Drill().size.x ) != 0 ) return diff; - if( ( diff = aPadRef->m_drill.y - aPadCmp->m_drill.y ) != 0 ) + if( ( diff = aPadRef->Padstack().Drill().size.y - aPadCmp->Padstack().Drill().size.y ) != 0 ) return diff; - if( ( diff = aPadRef->m_size.x - aPadCmp->m_size.x ) != 0 ) + if( ( diff = aPadRef->m_padStack.Size().x - aPadCmp->m_padStack.Size().x ) != 0 ) return diff; - if( ( diff = aPadRef->m_size.y - aPadCmp->m_size.y ) != 0 ) + if( ( diff = aPadRef->m_padStack.Size().y - aPadCmp->m_padStack.Size().y ) != 0 ) return diff; - if( ( diff = aPadRef->m_offset.x - aPadCmp->m_offset.x ) != 0 ) + if( ( diff = aPadRef->m_padStack.Offset().x - aPadCmp->m_padStack.Offset().x ) != 0 ) return diff; - if( ( diff = aPadRef->m_offset.y - aPadCmp->m_offset.y ) != 0 ) + if( ( diff = aPadRef->m_padStack.Offset().y - aPadCmp->m_padStack.Offset().y ) != 0 ) return diff; - if( ( diff = aPadRef->m_deltaSize.x - aPadCmp->m_deltaSize.x ) != 0 ) + 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_deltaSize.y - aPadCmp->m_deltaSize.y ) != 0 ) - return diff; - - if( ( diff = aPadRef->m_roundedCornerScale - aPadCmp->m_roundedCornerScale ) != 0 ) - return diff; - - if( ( diff = aPadRef->m_chamferPositions - aPadCmp->m_chamferPositions ) != 0 ) - return diff; - - if( ( diff = aPadRef->m_chamferScale - aPadCmp->m_chamferScale ) != 0 ) + if( ( diff = aPadRef->m_padStack.ChamferRatio() - aPadCmp->m_padStack.ChamferRatio() ) != 0 ) return diff; if( ( diff = static_cast( aPadRef->m_editPrimitives.size() ) - @@ -1410,7 +1405,7 @@ int PAD::Compare( const PAD* aPadRef, const PAD* aPadCmp ) // Lorenzo: gencad also needs it to implement padstacks! #if __cplusplus >= 201103L - long long d = aPadRef->m_layerMask.to_ullong() - aPadCmp->m_layerMask.to_ullong(); + long long d = aPadRef->GetLayerSet().to_ullong() - aPadCmp->GetLayerSet().to_ullong(); if( d < 0 ) return -1; @@ -1420,8 +1415,8 @@ int PAD::Compare( const PAD* aPadRef, const PAD* aPadCmp ) return 0; #else // these strings are not typically constructed, since we don't get here often. - std::string s1 = aPadRef->m_layerMask.to_string(); - std::string s2 = aPadCmp->m_layerMask.to_string(); + std::string s1 = aPadRef->GetLayerSet().to_string(); + std::string s2 = aPadCmp->GetLayerSet().to_string(); return s1.compare( s2 ); #endif } @@ -1687,6 +1682,7 @@ 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() ); @@ -1717,9 +1713,6 @@ void PAD::ImportSettingsFrom( const PAD& aMasterPad ) SetOrientation( pad_rot ); - SetRemoveUnconnected( aMasterPad.GetRemoveUnconnected() ); - SetKeepTopBottom( aMasterPad.GetKeepTopBottom() ); - SetSize( aMasterPad.GetSize() ); SetDelta( VECTOR2I( 0, 0 ) ); SetOffset( aMasterPad.GetOffset() ); @@ -1813,8 +1806,8 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, // 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_size.x / 2; - int dy = m_size.y / 2; + int dx = m_padStack.Size().x / 2; + int dy = m_padStack.Size().y / 2; VECTOR2I padShapePos = ShapePos(); // Note: for pad having a shape offset, the pad // position is NOT the shape position @@ -1846,11 +1839,11 @@ 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_deltaSize.x / 2 : 0; - int ddy = GetShape() == PAD_SHAPE::TRAPEZOID ? m_deltaSize.y / 2 : 0; + int ddx = GetShape() == PAD_SHAPE::TRAPEZOID ? m_padStack.TrapezoidDeltaSize().x / 2 : 0; + int ddy = GetShape() == PAD_SHAPE::TRAPEZOID ? m_padStack.TrapezoidDeltaSize().y / 2 : 0; SHAPE_POLY_SET outline; - TransformTrapezoidToPolygon( outline, padShapePos, m_size, m_orient, ddx, ddy, aClearance, + TransformTrapezoidToPolygon( outline, padShapePos, m_padStack.Size(), m_orient, ddx, ddy, aClearance, aMaxError, aErrorLoc ); aBuffer.Append( outline ); break; @@ -1862,7 +1855,7 @@ void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, bool doChamfer = GetShape() == PAD_SHAPE::CHAMFERED_RECT; SHAPE_POLY_SET outline; - TransformRoundChamferedRectToPolygon( outline, padShapePos, m_size, m_orient, + TransformRoundChamferedRectToPolygon( outline, padShapePos, m_padStack.Size(), m_orient, GetRoundRectCornerRadius(), doChamfer ? GetChamferRectRatio() : 0, doChamfer ? GetChamferPositions() : 0, @@ -2129,6 +2122,12 @@ static struct PAD_DESC .Map( ZONE_CONNECTION::THT_THERMAL, _HKI( "Thermal reliefs for PTH" ) ); } + ENUM_MAP::Instance() + .Map( PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL, _HKI( "All copper layers" ) ) + .Map( PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL, _HKI( "Connected layers only" ) ) + .Map( PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END, + _HKI( "Front, back and connected layers" ) ); + PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); REGISTER_TYPE( PAD ); propMgr.InheritsAfter( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ) ); @@ -2230,6 +2229,11 @@ static struct PAD_DESC propMgr.AddProperty( new PROPERTY_ENUM( _HKI( "Fabrication Property" ), &PAD::SetProperty, &PAD::GetProperty ), groupPad ); + auto layerMode = new PROPERTY_ENUM( + _HKI( "Copper Layers" ), + &PAD::SetUnconnectedLayerMode, &PAD::GetUnconnectedLayerMode ); + propMgr.AddProperty( layerMode, groupPad ); + auto padToDie = new PROPERTY( _HKI( "Pad To Die Length" ), &PAD::SetPadToDieLength, &PAD::GetPadToDieLength, PROPERTY_DISPLAY::PT_SIZE ); diff --git a/pcbnew/pad.h b/pcbnew/pad.h index 83833212cc..674fd4937a 100644 --- a/pcbnew/pad.h +++ b/pcbnew/pad.h @@ -40,12 +40,6 @@ class PCB_SHAPE; class SHAPE; class SHAPE_SEGMENT; -enum CUST_PAD_SHAPE_IN_ZONE -{ - CUST_PAD_SHAPE_IN_ZONE_OUTLINE, - CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL -}; - class LINE_READER; class EDA_3D_CANVAS; class FOOTPRINT; @@ -183,14 +177,14 @@ public: */ void SetShape( PAD_SHAPE aShape ) { - m_padShape = aShape; + m_padStack.SetShape( aShape ); SetDirty(); } /** * @return the shape of this pad. */ - PAD_SHAPE GetShape() const { return m_padShape; } + PAD_SHAPE GetShape() const { return m_padStack.Shape(); } void SetPosition( const VECTOR2I& aPos ) override { @@ -203,14 +197,17 @@ public: /** * @return the shape of the anchor pad shape, for custom shaped pads. */ - PAD_SHAPE GetAnchorPadShape() const { return m_anchorPadShape; } + PAD_SHAPE GetAnchorPadShape() const + { + return m_padStack.AnchorShape(); + } /** * @return the option for the custom pad shape to use as clearance area in copper zones. */ - CUST_PAD_SHAPE_IN_ZONE GetCustomShapeInZoneOpt() const + PADSTACK::CUSTOM_SHAPE_ZONE_MODE GetCustomShapeInZoneOpt() const { - return m_customShapeClearanceArea; + return m_padStack.CustomShapeInZoneMode(); } /** @@ -218,9 +215,9 @@ public: * * @param aOption is the clearance area shape CUST_PAD_SHAPE_IN_ZONE option */ - void SetCustomShapeInZoneOpt( CUST_PAD_SHAPE_IN_ZONE aOption ) + void SetCustomShapeInZoneOpt( PADSTACK::CUSTOM_SHAPE_ZONE_MODE aOption ) { - m_customShapeClearanceArea = aOption; + m_padStack.SetCustomShapeInZoneMode( aOption ); } /** @@ -231,7 +228,9 @@ public: */ void SetAnchorPadShape( PAD_SHAPE aShape ) { - m_anchorPadShape = ( aShape == PAD_SHAPE::RECTANGLE ) ? PAD_SHAPE::RECTANGLE : PAD_SHAPE::CIRCLE; + m_padStack.SetAnchorShape( aShape == PAD_SHAPE::RECTANGLE + ? PAD_SHAPE::RECTANGLE + : PAD_SHAPE::CIRCLE ); SetDirty(); } @@ -243,28 +242,37 @@ public: void SetY( int y ) { m_pos.y = y; SetDirty(); } void SetX( int x ) { m_pos.x = x; SetDirty(); } - void SetSize( const VECTOR2I& aSize ) { m_size = aSize; SetDirty(); } - const VECTOR2I& GetSize() const { return m_size; } - void SetSizeX( const int aX ) { if( aX > 0 ) { m_size.x = aX; SetDirty(); } } - int GetSizeX() const { return m_size.x; } - void SetSizeY( const int aY ) { if( aY > 0 ) { m_size.y = aY; SetDirty(); } } - int GetSizeY() const { return m_size.y; } + void SetSize( const VECTOR2I& aSize ) + { + m_padStack.Size() = aSize; + SetDirty(); + } + const VECTOR2I& GetSize() const { return m_padStack.Size(); } - void SetDelta( const VECTOR2I& aSize ) { m_deltaSize = aSize; SetDirty(); } - const VECTOR2I& GetDelta() const { return m_deltaSize; } + 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; } - void SetDrillSize( const VECTOR2I& aSize ) { m_drill = aSize; SetDirty(); } - const VECTOR2I& GetDrillSize() const { return m_drill; } - void SetDrillSizeX( const int aX ) { m_drill.x = aX; SetDirty(); } - int GetDrillSizeX() const { return m_drill.x; } - void SetDrillSizeY( const int aY ) { m_drill.y = aY; SetDirty(); } - int GetDrillSizeY() const { return m_drill.y; } + void SetDelta( const VECTOR2I& aSize ) { m_padStack.TrapezoidDeltaSize() = aSize; SetDirty(); } + const VECTOR2I& GetDelta() const { return m_padStack.TrapezoidDeltaSize(); } - void SetOffset( const VECTOR2I& aOffset ) { m_offset = aOffset; SetDirty(); } - const VECTOR2I& GetOffset() const { return m_offset; } + void SetDrillSize( const VECTOR2I& aSize ) { m_padStack.Drill().size = aSize; SetDirty(); } + const VECTOR2I& GetDrillSize() const { return m_padStack.Drill().size; } + void SetDrillSizeX( const int aX ) { m_padStack.Drill().size.x = aX; SetDirty(); } + int GetDrillSizeX() const { return m_padStack.Drill().size.x; } + 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(); } VECTOR2I GetCenter() const override { return GetPosition(); } + const PADSTACK& Padstack() const { return m_padStack; } + PADSTACK& Padstack() { return m_padStack; } + void SetPadstack( const PADSTACK& aPadstack ) { m_padStack = aPadstack; } + /** * Has meaning only for custom shape pads. * add a free shape to the shape list. @@ -355,8 +363,12 @@ public: return m_orient.AsDegrees(); } - void SetDrillShape( PAD_DRILL_SHAPE aShape ) { m_drillShape = aShape; m_shapesDirty = true; } - PAD_DRILL_SHAPE GetDrillShape() const { return m_drillShape; } + void SetDrillShape( PAD_DRILL_SHAPE aShape ) + { + m_padStack.Drill().shape = aShape; + m_shapesDirty = true; + } + PAD_DRILL_SHAPE GetDrillShape() const { return m_padStack.Drill().shape; } bool IsDirty() const { @@ -370,8 +382,8 @@ public: m_polyDirty[ERROR_OUTSIDE] = true; } - void SetLayerSet( LSET aLayers ) override { m_layerMask = aLayers; } - LSET GetLayerSet() const override { return m_layerMask; } + void SetLayerSet( LSET aLayers ) override { m_padStack.SetLayerSet( aLayers ); } + LSET GetLayerSet() const override { return m_padStack.LayerSet(); } void SetAttribute( PAD_ATTRIB aAttribute ); PAD_ATTRIB GetAttribute() const { return m_attribute; } @@ -383,26 +395,41 @@ public: // format, so for now just infer a copper-less pad to be an APERTURE pad. bool IsAperturePad() const { - return ( m_layerMask & LSET::AllCuMask() ).none(); + return ( m_padStack.LayerSet() & LSET::AllCuMask() ).none(); } void SetPadToDieLength( int aLength ) { m_lengthPadToDie = aLength; } int GetPadToDieLength() const { return m_lengthPadToDie; } - std::optional GetLocalClearance() const override { return m_clearance; } - void SetLocalClearance( std::optional aClearance ) { m_clearance = aClearance; } + std::optional GetLocalClearance() const override { return m_padStack.Clearance(); } + void SetLocalClearance( std::optional aClearance ) { m_padStack.Clearance() = aClearance; } - std::optional GetLocalSolderMaskMargin() const { return m_solderMaskMargin; } - void SetLocalSolderMaskMargin( std::optional aMargin ) { m_solderMaskMargin = aMargin; } + std::optional GetLocalSolderMaskMargin() const { return m_padStack.SolderMaskMargin(); } + void SetLocalSolderMaskMargin( std::optional aMargin ) + { + m_padStack.SolderMaskMargin() = aMargin; + } - std::optional GetLocalSolderPasteMargin() const { return m_solderPasteMargin; } - void SetLocalSolderPasteMargin( std::optional aMargin ) { m_solderPasteMargin = aMargin; } + std::optional GetLocalSolderPasteMargin() const { return m_padStack.SolderPasteMargin(); } + void SetLocalSolderPasteMargin( std::optional aMargin ) + { + m_padStack.SolderPasteMargin() = aMargin; + } - std::optional GetLocalSolderPasteMarginRatio() const { return m_solderPasteMarginRatio; } - void SetLocalSolderPasteMarginRatio( std::optional aRatio ) { m_solderPasteMarginRatio = aRatio; } + std::optional GetLocalSolderPasteMarginRatio() const + { + return m_padStack.SolderPasteMarginRatio(); + } + void SetLocalSolderPasteMarginRatio( std::optional aRatio ) + { + m_padStack.SolderPasteMarginRatio() = aRatio; + } - void SetLocalZoneConnection( ZONE_CONNECTION aType ) { m_zoneConnection = aType; } - ZONE_CONNECTION GetLocalZoneConnection() const { return m_zoneConnection; } + void SetLocalZoneConnection( ZONE_CONNECTION aType ) { m_padStack.ZoneConnection() = aType; } + ZONE_CONNECTION GetLocalZoneConnection() const + { + return m_padStack.ZoneConnection().value_or( ZONE_CONNECTION::INHERITED ); + } /** * Return the pad's "own" clearance in internal units. @@ -518,8 +545,8 @@ public: * Set the width of the thermal spokes connecting the pad to a zone. If != 0 this will * override similar settings in the parent footprint and zone. */ - void SetThermalSpokeWidth( int aWidth ) { m_thermalSpokeWidth = aWidth; } - int GetThermalSpokeWidth() const { return m_thermalSpokeWidth; } + void SetThermalSpokeWidth( int aWidth ) { m_padStack.ThermalSpokeWidth() = aWidth; } + int GetThermalSpokeWidth() const { return m_padStack.ThermalSpokeWidth().value_or( 0 ); } int GetLocalSpokeWidthOverride( wxString* aSource = nullptr ) const; @@ -528,21 +555,27 @@ public: * pads and circular-anchored custom shaped pads), while 90° will produce a + (the default * for all other shapes). */ - void SetThermalSpokeAngle( const EDA_ANGLE& aAngle ) { m_thermalSpokeAngle = aAngle; } - EDA_ANGLE GetThermalSpokeAngle() const { return m_thermalSpokeAngle; } + void SetThermalSpokeAngle( const EDA_ANGLE& aAngle ) + { + m_padStack.SetThermalSpokeAngle( aAngle ); + } + EDA_ANGLE GetThermalSpokeAngle() const + { + return m_padStack.ThermalSpokeAngle(); + } // For property system void SetThermalSpokeAngleDegrees( double aAngle ) { - m_thermalSpokeAngle = EDA_ANGLE( aAngle, DEGREES_T ); + m_padStack.ThermalSpokeAngle() = EDA_ANGLE( aAngle, DEGREES_T ); } double GetThermalSpokeAngleDegrees() const { - return m_thermalSpokeAngle.AsDegrees(); + return m_padStack.ThermalSpokeAngle().AsDegrees(); } - void SetThermalGap( int aGap ) { m_thermalGap = aGap; } - int GetThermalGap() const { return m_thermalGap; } + void SetThermalGap( int aGap ) { m_padStack.ThermalGap() = aGap; } + int GetThermalGap() const { return m_padStack.ThermalGap().value_or( 0 ); } int GetLocalThermalGapOverride( wxString* aSource = nullptr ) const; @@ -563,7 +596,7 @@ public: * Cannot be > 0.5; the normalized IPC-7351C value is 0.25 */ void SetRoundRectRadiusRatio( double aRadiusScale ); - double GetRoundRectRadiusRatio() const { return m_roundedCornerScale; } + double GetRoundRectRadiusRatio() const { return m_padStack.RoundRectRadiusRatio(); } /** * Has meaning only for chamfered rectangular pads. @@ -572,7 +605,7 @@ public: * Cannot be < 0.5. */ void SetChamferRectRatio( double aChamferScale ); - double GetChamferRectRatio() const { return m_chamferScale; } + double GetChamferRectRatio() const { return m_padStack.ChamferRatio(); } /** * Has meaning only for chamfered rectangular pads. @@ -581,8 +614,8 @@ public: * * @param aPositions a bit-set of #RECT_CHAMFER_POSITIONS. */ - void SetChamferPositions( int aPositions ) { m_chamferPositions = aPositions; } - int GetChamferPositions() const { return m_chamferPositions; } + void SetChamferPositions( int aPositions ) { m_padStack.SetChamferPositions( aPositions ); } + int GetChamferPositions() const { return m_padStack.ChamferPositions(); } /** * @return the netcode. @@ -591,27 +624,65 @@ public: void SetSubRatsnest( int aSubRatsnest ) { m_subRatsnest = aSubRatsnest; } /** - * Set the unconnected removal property. - * - * If true, the copper is removed on zone fill or when specifically requested when the pad - * is not connected on a layer. This requires that there be a through hole. + * @deprecated - use Padstack().SetUnconnectedLayerMode() + * Sets the unconnected removal property. If true, the copper is removed on zone fill + * or when specifically requested when the via is not connected on a layer. */ - void SetRemoveUnconnected( bool aSet ) { m_removeUnconnectedLayer = aSet; } - bool GetRemoveUnconnected() const { return m_removeUnconnectedLayer; } + void SetRemoveUnconnected( bool aSet ) + { + m_padStack.SetUnconnectedLayerMode( aSet + ? PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL + : PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL ); + } + + bool GetRemoveUnconnected() const + { + return m_padStack.UnconnectedLayerMode() != PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL; + } /** - * Set whether we keep the top and bottom connections even if they are not connected. + * @deprecated - use Padstack().SetUnconnectedLayerMode() + * Sets whether we keep the start and end annular rings even if they are not connected */ - void SetKeepTopBottom( bool aSet ) { m_keepTopBottomLayer = aSet; } - bool GetKeepTopBottom() const { return m_keepTopBottomLayer; } + void SetKeepTopBottom( bool aSet ) + { + m_padStack.SetUnconnectedLayerMode( aSet + ? PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END + : PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL ); + } + + bool GetKeepTopBottom() const + { + return m_padStack.UnconnectedLayerMode() + == PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END; + } + + void SetUnconnectedLayerMode( PADSTACK::UNCONNECTED_LAYER_MODE aMode ) + { + m_padStack.SetUnconnectedLayerMode( aMode ); + } + + PADSTACK::UNCONNECTED_LAYER_MODE GetUnconnectedLayerMode() const + { + return m_padStack.UnconnectedLayerMode(); + } bool ConditionallyFlashed( PCB_LAYER_ID aLayer ) const { - if( !m_removeUnconnectedLayer ) + switch( m_padStack.UnconnectedLayerMode() ) + { + case PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL: return false; - if( m_keepTopBottomLayer && ( aLayer == F_Cu || aLayer == B_Cu ) ) - return false; + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL: + return true; + + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END: + { + if( aLayer == m_padStack.Drill().start || aLayer == m_padStack.Drill().end ) + return false; + } + } return true; } @@ -620,7 +691,7 @@ public: bool IsOnLayer( PCB_LAYER_ID aLayer ) const override { - return m_layerMask[aLayer]; + return m_padStack.LayerSet().test( aLayer ); } /** @@ -761,10 +832,7 @@ private: VECTOR2I m_pos; // Pad Position on board - PAD_SHAPE m_padShape; // Shape: PAD_SHAPE::CIRCLE, PAD_SHAPE::RECTANGLE, - // PAD_SHAPE::OVAL, PAD_SHAPE::TRAPEZOID, - // PAD_SHAPE::ROUNDRECT, PAD_SHAPE::CHAMFERED_RECT, - // PAD_SHAPE::CUSTOM + PADSTACK m_padStack; /* * Editing definitions of primitives for custom pad shapes. In local coordinates relative * to m_Pos (NOT shapePos) at orient 0. @@ -786,36 +854,6 @@ private: int m_subRatsnest; // Variable used to handle subnet (block) number in // ratsnest computations - VECTOR2I m_drill; // Drill diameter (x == y) or slot dimensions (x != y) - VECTOR2I m_size; // X and Y size (relative to orient 0) - - PAD_DRILL_SHAPE m_drillShape; // PAD_DRILL_SHAPE::CIRCLE, PAD_DRILL_SHAPE::OBLONG - - double m_roundedCornerScale; // Scaling factor of min(width, height) to corner - // radius, default 0.25 - double m_chamferScale; // Scaling factor of min(width, height) to chamfer - // size, default 0.25 - int m_chamferPositions; // The positions of the chamfers (at orient 0) - - PAD_SHAPE m_anchorPadShape; // For custom shaped pads: shape of pad anchor, - // PAD_SHAPE::RECTANGLE, PAD_SHAPE::CIRCLE - - /* - * Most of the time the hole is the center of the shape (m_Offset = 0). But some designers - * use oblong/rect pads with a hole moved to one of the oblong/rect pad shape ends. - * In all cases the hole is at the pad position. This offset is from the hole to the center - * of the pad shape (ie: the copper area around the hole). - * ShapePos() returns the board shape position according to the offset and the pad rotation. - */ - VECTOR2I m_offset; - - LSET m_layerMask; // Bitwise layer: 1 = copper layer, 15 = cmp, - // 2..14 = internal layers, 16..31 = technical layers - - VECTOR2I m_deltaSize; // Delta for PAD_SHAPE::TRAPEZOID; half the delta squeezes - // one end and half expands the other. It is only valid - // to have a single axis be non-0. - PAD_ATTRIB m_attribute = PAD_ATTRIB::PTH; PAD_PROP m_property; // Property in fab files (BGA, FIDUCIAL, TESTPOINT, etc.) @@ -824,41 +862,6 @@ private: int m_lengthPadToDie; // Length net from pad to die, inside the package - ///< If true, the pad copper is removed for layers that are not connected. - bool m_removeUnconnectedLayer; - - ///< When removing unconnected pads, keep the top and bottom pads. - bool m_keepTopBottomLayer; - - /* - * Pad clearances, margins, etc. exist in a hierarchy. If a given level is specified then - * the remaining levels are NOT consulted. - * - * LEVEL 1: (highest priority) local overrides (pad, footprint, etc.) - * LEVEL 2: Rules - * LEVEL 3: Accumulated local settings, netclass settings, & board design settings - * - * These are the LEVEL 1 settings (overrides) for a pad. - */ - std::optional m_clearance; - std::optional m_solderMaskMargin; // Solder mask margin - std::optional m_solderPasteMargin; // Solder paste margin absolute value - std::optional m_solderPasteMarginRatio; // Solder mask margin ratio of pad size - // The final margin is the sum of these 2 values - - /* - * How to build the custom shape in zone, to create the clearance area: - * CUST_PAD_SHAPE_IN_ZONE_OUTLINE = use pad shape - * CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL = use the convex hull of the pad shape - */ - CUST_PAD_SHAPE_IN_ZONE m_customShapeClearanceArea; - - ZONE_CONNECTION m_zoneConnection; // No connection, thermal relief, etc. - int m_thermalSpokeWidth; // Thermal spoke width. - EDA_ANGLE m_thermalSpokeAngle; // Rotation of the spokes. 45° will produce an X, - // while 90° will produce a +. - int m_thermalGap; - std::mutex m_zoneLayerOverridesMutex; std::array m_zoneLayerOverrides; }; diff --git a/pcbnew/padstack.cpp b/pcbnew/padstack.cpp index a7664e0793..37ed806086 100644 --- a/pcbnew/padstack.cpp +++ b/pcbnew/padstack.cpp @@ -18,11 +18,66 @@ * with this program. If not, see . */ +#include // RECT_CHAMFER_POSITIONS #include "padstack.h" -PADSTACK::PADSTACK() +PADSTACK::PADSTACK() : + m_mode( MODE::NORMAL ), + 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 = std::nullopt; + m_defaultCopperProps.thermal_spoke_angle = ANGLE_45; + m_defaultCopperProps.thermal_gap = std::nullopt; + + m_drill.shape = PAD_DRILL_SHAPE::CIRCLE; + m_drill.start = F_Cu; + m_drill.end = B_Cu; + + m_secondaryDrill.start = UNDEFINED_LAYER; + m_secondaryDrill.end = UNDEFINED_LAYER; +} + + +PADSTACK::PADSTACK( const PADSTACK& aOther ) +{ + *this = 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_defaultOuterProps = aOther.m_defaultOuterProps; + m_unconnectedLayerMode = aOther.m_unconnectedLayerMode; + m_copperOverrides = aOther.m_copperOverrides; + m_topOverrides = aOther.m_topOverrides; + m_bottomOverrides = aOther.m_bottomOverrides; + 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_defaultOuterProps == aOther.m_defaultOuterProps + && m_unconnectedLayerMode == aOther.m_unconnectedLayerMode + && m_copperOverrides == aOther.m_copperOverrides + && m_topOverrides == aOther.m_topOverrides + && m_bottomOverrides == aOther.m_bottomOverrides + && m_drill == aOther.m_drill + && m_secondaryDrill == aOther.m_secondaryDrill; } @@ -39,5 +94,290 @@ void PADSTACK::Serialize( google::protobuf::Any& aContainer ) const wxString PADSTACK::Name() const { + // TODO return wxEmptyString; } + + +PADSTACK::SHAPE_PROPS::SHAPE_PROPS() : + shape( PAD_SHAPE::CIRCLE ), + anchor_shape( PAD_SHAPE::CIRCLE ), + round_rect_corner_radius( 0 ), + round_rect_radius_ratio( 0.25 ), + chamfered_rect_ratio( 0.2 ), + chamfered_rect_positions( RECT_NO_CHAMFER ) +{ +} + + +bool PADSTACK::SHAPE_PROPS::operator==( const SHAPE_PROPS& aOther ) const +{ + return shape == aOther.shape && offset == aOther.offset + && round_rect_corner_radius == aOther.round_rect_corner_radius + && round_rect_radius_ratio == aOther.round_rect_radius_ratio + && chamfered_rect_ratio == aOther.chamfered_rect_ratio + && chamfered_rect_positions == aOther.chamfered_rect_positions; +} + + +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; +} + + +bool PADSTACK::OUTER_LAYER_PROPS::operator==( const OUTER_LAYER_PROPS& aOther ) const +{ + return solder_mask_margin == aOther.solder_mask_margin + && solder_paste_margin == aOther.solder_paste_margin + && solder_paste_margin_ratio == aOther.solder_paste_margin_ratio + && has_solder_mask == aOther.has_solder_mask + && has_solder_paste == aOther.has_solder_paste; +} + + +bool PADSTACK::DRILL_PROPS::operator==( const DRILL_PROPS& aOther ) const +{ + return size == aOther.size && shape == aOther.shape + && start == aOther.start && end == aOther.end; +} + + +PAD_SHAPE PADSTACK::Shape( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().shape.shape; +} + + +void PADSTACK::SetShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer ) +{ + CopperLayerDefaults().shape.shape = aShape; +} + + +VECTOR2I& PADSTACK::Size( PCB_LAYER_ID aLayer ) +{ + return CopperLayerDefaults().shape.size; +} + + +const VECTOR2I& PADSTACK::Size( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().shape.size; +} + + +PAD_DRILL_SHAPE PADSTACK::DrillShape( PCB_LAYER_ID aLayer ) const +{ + return m_drill.shape; +} + + +void PADSTACK::SetDrillShape( PAD_DRILL_SHAPE aShape, PCB_LAYER_ID aLayer ) +{ + m_drill.shape = aShape; +} + + +VECTOR2I& PADSTACK::Offset( PCB_LAYER_ID aLayer ) +{ + return CopperLayerDefaults().shape.offset; +} + + +const VECTOR2I& PADSTACK::Offset( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().shape.offset; +} + + +PAD_SHAPE PADSTACK::AnchorShape( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().shape.anchor_shape; +} + + +void PADSTACK::SetAnchorShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer ) +{ + CopperLayerDefaults().shape.anchor_shape = aShape; +} + + +VECTOR2I& PADSTACK::TrapezoidDeltaSize( PCB_LAYER_ID aLayer ) +{ + return CopperLayerDefaults().shape.trapezoid_delta_size; +} + + +const VECTOR2I& PADSTACK::TrapezoidDeltaSize( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().shape.trapezoid_delta_size; +} + + +double PADSTACK::RoundRectRadiusRatio( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().shape.round_rect_radius_ratio; +} + + +void PADSTACK::SetRoundRectRadiusRatio( double aRatio, PCB_LAYER_ID aLayer ) +{ + CopperLayerDefaults().shape.round_rect_radius_ratio = aRatio; +} + + +int PADSTACK::RoundRectRadius( PCB_LAYER_ID aLayer ) const +{ + const VECTOR2I& size = Size( aLayer ); + return KiROUND( std::min( size.x, size.y ) * RoundRectRadiusRatio( aLayer ) ); +} + + +void PADSTACK::SetRoundRectRadius( double aRadius, PCB_LAYER_ID aLayer ) +{ + const VECTOR2I& size = Size( aLayer ); + int min_r = std::min( size.x, size.y ); + + if( min_r > 0 ) + SetRoundRectRadiusRatio( aRadius / min_r, aLayer ); +} + + +double PADSTACK::ChamferRatio( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().shape.chamfered_rect_ratio; +} + + +void PADSTACK::SetChamferRatio( double aRatio, PCB_LAYER_ID aLayer ) +{ + CopperLayerDefaults().shape.chamfered_rect_ratio = aRatio; +} + + +int& PADSTACK::ChamferPositions( PCB_LAYER_ID aLayer ) +{ + return CopperLayerDefaults().shape.chamfered_rect_positions; +} + + +const int& PADSTACK::ChamferPositions( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().shape.chamfered_rect_positions; +} + + +void PADSTACK::SetChamferPositions( int aPositions, PCB_LAYER_ID aLayer ) +{ + CopperLayerDefaults().shape.chamfered_rect_positions = aPositions; +} + + +std::optional& PADSTACK::Clearance( PCB_LAYER_ID aLayer ) +{ + return CopperLayerDefaults().clearance; +} + + +const std::optional& PADSTACK::Clearance( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().clearance; +} + + +std::optional& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer ) +{ + return OuterLayerDefaults().solder_mask_margin; +} + + +const std::optional& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer ) const +{ + return OuterLayerDefaults().solder_mask_margin; +} + + +std::optional& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer ) +{ + return OuterLayerDefaults().solder_paste_margin; +} + + +const std::optional& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer ) const +{ + return OuterLayerDefaults().solder_paste_margin; +} + + +std::optional& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer ) +{ + return OuterLayerDefaults().solder_paste_margin_ratio; +} + + +const std::optional& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer ) const +{ + return OuterLayerDefaults().solder_paste_margin_ratio; +} + + +std::optional& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer ) +{ + return CopperLayerDefaults().zone_connection; +} + + +const std::optional& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().zone_connection; +} + + +std::optional& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer ) +{ + return CopperLayerDefaults().thermal_spoke_width; +} + + +const std::optional& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().thermal_spoke_width; +} + + +std::optional& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer ) +{ + return CopperLayerDefaults().thermal_gap; +} + + +const std::optional& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer ) const +{ + return CopperLayerDefaults().thermal_gap; +} + + +EDA_ANGLE PADSTACK::ThermalSpokeAngle( PCB_LAYER_ID aLayer ) const +{ + const COPPER_LAYER_PROPS& defaults = CopperLayerDefaults(); + + return defaults.thermal_spoke_angle.value_or( + ( defaults.shape.shape == PAD_SHAPE::CIRCLE + || ( defaults.shape.shape == PAD_SHAPE::CUSTOM + && defaults.shape.anchor_shape == PAD_SHAPE::CIRCLE ) ) + ? ANGLE_45 : ANGLE_90 ); +} + + +void PADSTACK::SetThermalSpokeAngle( EDA_ANGLE aAngle, PCB_LAYER_ID aLayer ) +{ + CopperLayerDefaults().thermal_spoke_angle = aAngle; +} + + +IMPLEMENT_ENUM_TO_WXANY( PADSTACK::UNCONNECTED_LAYER_MODE ) diff --git a/pcbnew/padstack.h b/pcbnew/padstack.h index 9d50bf87f4..8645af51a4 100644 --- a/pcbnew/padstack.h +++ b/pcbnew/padstack.h @@ -26,8 +26,14 @@ #include #include +#include +#include +#include +#include #include +class PCB_SHAPE; + /** * The set of pad shapes, used with PAD::{Set,Get}Shape() @@ -92,6 +98,19 @@ enum class PAD_PROP }; +/** + * A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of + * the word. This means that a PCB_PAD has a padstack, but also a PCB_VIA. The padstack for + * a pad defines its geometry on copper, soldermask, and paste layers, as well as any drilling + * or milling associated with the pad (round or slot hole, back-drilling, etc). Padstacks also + * define thermal relief settings for all copper layers, clearance overrides for all copper + * layers, and potentially other properties in the future. In other words, the padstack defines + * most of the geometric features of a pad on all layers. It does not define electrical properties + * or other metadata. + * + * For padstacks that do not vary between layers, F_Cu is used as the copper layer to store all + * padstack properties. + */ class PADSTACK : public SERIALIZABLE { public: @@ -111,35 +130,243 @@ public: CUSTOM ///< Shapes can be defined on arbitrary layers }; + ///! Whether or not to remove the copper shape for unconnected layers + enum class UNCONNECTED_LAYER_MODE + { + KEEP_ALL, + REMOVE_ALL, + REMOVE_EXCEPT_START_AND_END + }; + + enum class CUSTOM_SHAPE_ZONE_MODE + { + OUTLINE, + CONVEXHULL + }; + + ///! The set of properties that define a pad's shape on a given layer + struct SHAPE_PROPS + { + PAD_SHAPE shape; ///< Shape of the pad + PAD_SHAPE anchor_shape; ///< Shape of the anchor when shape == PAD_SHAPE::CUSTOM + VECTOR2I size; ///< Size of the shape, or of the anchor pad for custom shape pads + + /* + * Most of the time the hole is the center of the shape (m_Offset = 0). But some designers + * use oblong/rect pads with a hole moved to one of the oblong/rect pad shape ends. + * In all cases the hole is at the pad position. This offset is from the hole to the center + * of the pad shape (ie: the copper area around the hole). + * ShapePos() returns the board shape position according to the offset and the pad rotation. + */ + VECTOR2I offset; ///< Offset of the shape center from the pad center + + double round_rect_corner_radius; + double round_rect_radius_ratio; + double chamfered_rect_ratio; ///< Size of chamfer: ratio of smallest of X,Y size + int chamfered_rect_positions; ///< @see RECT_CHAMFER_POSITIONS + + /** + * Delta for PAD_SHAPE::TRAPEZOID; half the delta squeezes one end and half expands the + * other. It is only valid to have a single axis be non-zero. + */ + VECTOR2I trapezoid_delta_size; + + SHAPE_PROPS(); + bool operator==( const SHAPE_PROPS& aOther ) const; + }; + + /** + * The features of a padstack that can vary between copper layers + * All parameters are optional; leaving them un-set means "use parent/rule defaults" + * Pad clearances, margins, etc. exist in a hierarchy. If a given level is specified then + * the remaining levels are NOT consulted. + * + * LEVEL 1: (highest priority) local overrides (pad, footprint, etc.) + * LEVEL 2: Rules + * LEVEL 3: Accumulated local settings, netclass settings, & board design settings + * + * These are the LEVEL 1 settings (overrides) for a pad. + */ + struct COPPER_LAYER_PROPS + { + SHAPE_PROPS shape; + std::optional zone_connection; + std::optional thermal_spoke_width; + std::optional thermal_spoke_angle; + std::optional thermal_gap; + std::optional clearance; + + std::vector> custom_shapes; + + bool operator==( const COPPER_LAYER_PROPS& aOther ) const; + }; + + ///! The features of a padstack that can vary on outer layers. + ///! All parameters are optional; leaving them un-set means "use parent/rule defaults" struct OUTER_LAYER_PROPS { std::optional solder_mask_margin; std::optional solder_paste_margin; std::optional solder_paste_margin_ratio; - ZONE_CONNECTION zone_connection; + std::optional has_solder_mask; ///< True if this outer layer has mask (is not tented) + std::optional has_solder_paste; ///< True if this outer layer has solder paste + + bool operator==( const OUTER_LAYER_PROPS& aOther ) const; }; - struct INNER_LAYER_PROPS + ///! The properties of a padstack drill. Drill position is always the pad position (origin). + struct DRILL_PROPS { - ZONE_CONNECTION zone_connection; + VECTOR2I size; ///< Drill diameter (x == y) or slot dimensions (x != y) + PAD_DRILL_SHAPE shape; + PCB_LAYER_ID start; + PCB_LAYER_ID end; + + bool operator==( const DRILL_PROPS& aOther ) const; }; public: PADSTACK(); - virtual ~PADSTACK() = default; + PADSTACK( const PADSTACK& aOther ); + PADSTACK& operator=( const PADSTACK &aOther ); + + bool operator==( const PADSTACK& aOther ) const; + bool operator!=( const PADSTACK& aOther ) const { return !operator==( aOther ); } void Serialize( google::protobuf::Any &aContainer ) const override; bool Deserialize( const google::protobuf::Any &aContainer ) override; + const LSET& LayerSet() const { return m_layerSet; } + LSET& LayerSet() { return m_layerSet; } + void SetLayerSet( const LSET& aSet ) { m_layerSet = aSet; } + ///! Returns the name of this padstack in IPC-7351 format wxString Name() const; + DRILL_PROPS& Drill() { return m_drill; } + const DRILL_PROPS& Drill() const { return m_drill; } + + DRILL_PROPS& SecondaryDrill() { return m_secondaryDrill; } + const DRILL_PROPS& SecondaryDrill() const { return m_secondaryDrill; } + + 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; } + + OUTER_LAYER_PROPS& OuterLayerDefaults() { return m_defaultOuterProps; } + const OUTER_LAYER_PROPS& OuterLayerDefaults() const { return m_defaultOuterProps; } + + CUSTOM_SHAPE_ZONE_MODE CustomShapeInZoneMode() const { return m_customShapeInZoneMode; } + void SetCustomShapeInZoneMode( CUSTOM_SHAPE_ZONE_MODE aM ) { m_customShapeInZoneMode = aM; } + + // 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 ); + + VECTOR2I& Size( PCB_LAYER_ID aLayer = F_Cu ); + const VECTOR2I& Size( PCB_LAYER_ID aLayer = F_Cu ) const; + + PAD_DRILL_SHAPE DrillShape( PCB_LAYER_ID aLayer = F_Cu ) const; + void SetDrillShape( PAD_DRILL_SHAPE aShape, PCB_LAYER_ID aLayer = F_Cu ); + + VECTOR2I& Offset( PCB_LAYER_ID aLayer = F_Cu ); + const VECTOR2I& Offset( PCB_LAYER_ID aLayer = F_Cu ) const; + + PAD_SHAPE AnchorShape( PCB_LAYER_ID aLayer = F_Cu ) const; + void SetAnchorShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer = F_Cu ); + + VECTOR2I& TrapezoidDeltaSize( PCB_LAYER_ID aLayer = F_Cu ); + const VECTOR2I& TrapezoidDeltaSize( PCB_LAYER_ID aLayer = F_Cu ) const; + + double RoundRectRadiusRatio( PCB_LAYER_ID aLayer = F_Cu ) const; + void SetRoundRectRadiusRatio( double aRatio, PCB_LAYER_ID aLayer = F_Cu ); + + int RoundRectRadius( PCB_LAYER_ID aLayer = F_Cu ) const; + void SetRoundRectRadius( double aRadius, PCB_LAYER_ID aLayer = F_Cu ); + + double ChamferRatio( PCB_LAYER_ID aLayer = F_Cu ) const; + void SetChamferRatio( double aRatio, PCB_LAYER_ID aLayer = F_Cu ); + + 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 ); + + std::optional& Clearance( PCB_LAYER_ID aLayer = F_Cu ); + const std::optional& Clearance( PCB_LAYER_ID aLayer = F_Cu ) const; + + std::optional& SolderMaskMargin( PCB_LAYER_ID aLayer = F_Cu ); + const std::optional& SolderMaskMargin( PCB_LAYER_ID aLayer = F_Cu ) const; + + std::optional& SolderPasteMargin( PCB_LAYER_ID aLayer = F_Cu ); + const std::optional& SolderPasteMargin( PCB_LAYER_ID aLayer = F_Cu ) const; + + std::optional& SolderPasteMarginRatio( PCB_LAYER_ID aLayer = F_Cu ); + const std::optional& SolderPasteMarginRatio( PCB_LAYER_ID aLayer = F_Cu ) const; + + std::optional& ZoneConnection( PCB_LAYER_ID aLayer = F_Cu ); + const std::optional& ZoneConnection( PCB_LAYER_ID aLayer = F_Cu ) const; + + std::optional& ThermalSpokeWidth( PCB_LAYER_ID aLayer = F_Cu ); + const std::optional& ThermalSpokeWidth( PCB_LAYER_ID aLayer = F_Cu ) const; + + std::optional& ThermalGap( PCB_LAYER_ID aLayer = F_Cu ); + const std::optional& ThermalGap( PCB_LAYER_ID aLayer = F_Cu ) const; + + EDA_ANGLE ThermalSpokeAngle( PCB_LAYER_ID aLayer = F_Cu ) const; + void SetThermalSpokeAngle( EDA_ANGLE aAngle, PCB_LAYER_ID aLayer = F_Cu ); + private: + MODE m_mode; + + ///! The board layers that this padstack is active on + LSET m_layerSet; + ///! An override for the IPC-7351 padstack name wxString m_customName; + + ///! The properties applied to copper layers if they aren't overridden + COPPER_LAYER_PROPS m_defaultCopperProps; + + ///! The properties applied to outer technical layers if they aren't overridden + OUTER_LAYER_PROPS m_defaultOuterProps; + + UNCONNECTED_LAYER_MODE m_unconnectedLayerMode; + + /** + * How to build the custom shape in zone, to create the clearance area: + * CUSTOM_SHAPE_ZONE_MODE::OUTLINE = use pad shape + * CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL = use the convex hull of the pad shape + */ + 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; + + ///! Any non-null optional values here override the mask/paste settings for the top layers + OUTER_LAYER_PROPS m_topOverrides; + + ///! Any non-null optional values here override the mask/paste settings for bottom layers + OUTER_LAYER_PROPS m_bottomOverrides; + + ///! 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; + + ///! Secondary drill, used to define back-drilling + DRILL_PROPS m_secondaryDrill; }; +#ifndef SWIG +DECLARE_ENUM_TO_WXANY( PADSTACK::UNCONNECTED_LAYER_MODE ); +#endif + #endif //KICAD_PADSTACK_H diff --git a/pcbnew/pcb_io/altium/altium_pcb.cpp b/pcbnew/pcb_io/altium/altium_pcb.cpp index 1eda0ced2a..434499d400 100644 --- a/pcbnew/pcb_io/altium/altium_pcb.cpp +++ b/pcbnew/pcb_io/altium/altium_pcb.cpp @@ -2568,7 +2568,6 @@ void ALTIUM_PCB::ConvertShapeBasedRegions6ToFootprintItemOnLayer( FOOTPRINT* LSET padLayers; padLayers.set( aLayer ); - pad->SetKeepTopBottom( false ); // TODO: correct? This seems to be KiCad default on import pad->SetAttribute( PAD_ATTRIB::SMD ); pad->SetShape( PAD_SHAPE::CUSTOM ); pad->SetThermalSpokeAngle( ANGLE_90 ); @@ -3016,8 +3015,6 @@ void ALTIUM_PCB::ConvertPads6ToFootprintItemOnCopper( FOOTPRINT* aFootprint, con { std::unique_ptr pad = std::make_unique( aFootprint ); - pad->SetKeepTopBottom( false ); // TODO: correct? This seems to be KiCad default on import - pad->SetNumber( aElem.name ); pad->SetNetCode( GetNetCode( aElem.net ) ); diff --git a/pcbnew/pcb_io/cadstar/cadstar_pcb_archive_loader.cpp b/pcbnew/pcb_io/cadstar/cadstar_pcb_archive_loader.cpp index 4c042da704..9f405a9e70 100644 --- a/pcbnew/pcb_io/cadstar/cadstar_pcb_archive_loader.cpp +++ b/pcbnew/pcb_io/cadstar/cadstar_pcb_archive_loader.cpp @@ -898,7 +898,6 @@ void CADSTAR_PCB_ARCHIVE_LOADER::loadLibraryCoppers( const SYMDEF_PCB& aComponen anchorPad = aComponent.ComponentPads.at( compCopper.AssociatedPadIDs.front() ); std::unique_ptr pad = std::make_unique( aFootprint ); - pad->SetKeepTopBottom( false ); // TODO: correct? This seems to be KiCad default on import pad->SetAttribute( PAD_ATTRIB::SMD ); pad->SetLayerSet( copperLayers ); pad->SetNumber( anchorPad.Identifier.IsEmpty() @@ -1342,8 +1341,6 @@ PAD* CADSTAR_PCB_ARCHIVE_LOADER::getKiCadPad( const COMPONENT_PAD& aCadstarPad, m_padcodesTested.insert( csPadcode.ID ); } - pad->SetKeepTopBottom( false ); // TODO: correct? This seems to be KiCad default on import - return pad.release(); } diff --git a/pcbnew/pcb_io/eagle/pcb_io_eagle.cpp b/pcbnew/pcb_io/eagle/pcb_io_eagle.cpp index be033970cb..f0ad51d268 100644 --- a/pcbnew/pcb_io/eagle/pcb_io_eagle.cpp +++ b/pcbnew/pcb_io/eagle/pcb_io_eagle.cpp @@ -2450,8 +2450,6 @@ void PCB_IO_EAGLE::packageHole( FOOTPRINT* aFootprint, wxXmlNode* aTree, bool aC PAD* pad = new PAD( aFootprint ); aFootprint->Add( pad ); - pad->SetKeepTopBottom( false ); // TODO: correct? This seems to be KiCad default on import - pad->SetShape( PAD_SHAPE::CIRCLE ); pad->SetAttribute( PAD_ATTRIB::NPTH ); @@ -2493,8 +2491,6 @@ void PCB_IO_EAGLE::packageSMD( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const aFootprint->Add( pad ); transferPad( e, pad ); - pad->SetKeepTopBottom( false ); // TODO: correct? This seems to be KiCad default on import - pad->SetShape( PAD_SHAPE::RECTANGLE ); pad->SetAttribute( PAD_ATTRIB::SMD ); 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 7559960668..7f95a5f58f 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp @@ -1697,7 +1697,7 @@ void PCB_IO_KICAD_SEXPR::format( const PAD* aPad, int aNestLevel ) const m_out->Print( 0, "\n"); m_out->Print( aNestLevel+1, "(options" ); - if( aPad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL ) + if( aPad->GetCustomShapeInZoneOpt() == PADSTACK::CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL ) m_out->Print( 0, " (clearance convexhull)" ); #if 1 // Set to 1 to output the default option else @@ -2199,12 +2199,20 @@ void PCB_IO_KICAD_SEXPR::format( const PCB_TRACK* aTrack, int aNestLevel ) const m_out->Quotew( LSET::Name( layer1 ) ).c_str(), m_out->Quotew( LSET::Name( layer2 ) ).c_str() ); - if( via->GetRemoveUnconnected() ) + switch( via->Padstack().UnconnectedLayerMode() ) { - KICAD_FORMAT::FormatBool( m_out, 0, "remove_unused_layers", - via->GetRemoveUnconnected() ); + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL: + m_out->Print( 0, "(remove_unused_layers yes)" ); + m_out->Print( 0, "(keep_end_layers no)" ); + break; - KICAD_FORMAT::FormatBool( m_out, 0, "keep_end_layers", via->GetKeepStartEnd() ); + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END: + m_out->Print( 0, "(remove_unused_layers yes)" ); + m_out->Print( 0, "(keep_end_layers yes)" ); + break; + + case PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL: + break; } if( via->IsLocked() ) 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 f0b0924ab2..3426e22d7d 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 @@ -4699,9 +4699,6 @@ PAD* PCB_IO_KICAD_SEXPR_PARSER::parsePAD( FOOTPRINT* aParent ) std::unique_ptr pad = std::make_unique( aParent ); - // File only contains a token if KeepTopBottom is true - pad->SetKeepTopBottom( false ); - NeedSYMBOLorNUMBER(); pad->SetNumber( FromUTF8() ); @@ -5286,11 +5283,11 @@ bool PCB_IO_KICAD_SEXPR_PARSER::parsePAD_option( PAD* aPad ) switch( token ) { case T_outline: - aPad->SetCustomShapeInZoneOpt( CUST_PAD_SHAPE_IN_ZONE_OUTLINE ); + aPad->SetCustomShapeInZoneOpt( PADSTACK::CUSTOM_SHAPE_ZONE_MODE::OUTLINE ); break; case T_convexhull: - aPad->SetCustomShapeInZoneOpt( CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL ); + aPad->SetCustomShapeInZoneOpt( PADSTACK::CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL ); break; default: @@ -5715,8 +5712,7 @@ PCB_VIA* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_VIA() std::unique_ptr via = std::make_unique( m_board ); // File format default is no-token == no-feature. - via->SetRemoveUnconnected( false ); - via->SetKeepStartEnd( false ); + via->Padstack().SetUnconnectedLayerMode( PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL ); for( token = NextTok(); token != T_RIGHT; token = NextTok() ) { diff --git a/pcbnew/pcb_track.cpp b/pcbnew/pcb_track.cpp index 8da6b6ba29..9f84effdc0 100644 --- a/pcbnew/pcb_track.cpp +++ b/pcbnew/pcb_track.cpp @@ -86,11 +86,11 @@ PCB_VIA::PCB_VIA( BOARD_ITEM* aParent ) : PCB_TRACK( aParent, PCB_VIA_T ) { SetViaType( VIATYPE::THROUGH ); - m_bottomLayer = B_Cu; + Padstack().Drill().start = F_Cu; + Padstack().Drill().end = B_Cu; SetDrillDefault(); - m_removeUnconnectedLayer = false; - m_keepStartEndLayer = true; + Padstack().SetUnconnectedLayerMode( PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL ); m_zoneLayerOverrides.fill( ZLO_NONE ); @@ -118,11 +118,8 @@ PCB_VIA& PCB_VIA::operator=( const PCB_VIA &aOther ) m_CachedLOD = aOther.m_CachedLOD; m_CachedScale = aOther.m_CachedScale; - m_bottomLayer = aOther.m_bottomLayer; m_viaType = aOther.m_viaType; - m_drill = aOther.m_drill; - m_removeUnconnectedLayer = aOther.m_removeUnconnectedLayer; - m_keepStartEndLayer = aOther.m_keepStartEndLayer; + m_padStack = aOther.m_padStack; m_isFree = aOther.m_isFree; return *this; @@ -249,10 +246,8 @@ bool PCB_VIA::operator==( const BOARD_ITEM& aOther ) const const PCB_VIA& other = static_cast( aOther ); return m_Start == other.m_Start && m_End == other.m_End && m_layer == other.m_layer && - m_bottomLayer == other.m_bottomLayer && m_Width == other.m_Width && - m_viaType == other.m_viaType && m_drill == other.m_drill && - m_removeUnconnectedLayer == other.m_removeUnconnectedLayer && - m_keepStartEndLayer == other.m_keepStartEndLayer && + m_padStack == other.m_padStack && m_Width == other.m_Width && + m_viaType == other.m_viaType && m_zoneLayerOverrides == other.m_zoneLayerOverrides; } @@ -278,21 +273,12 @@ double PCB_VIA::Similarity( const BOARD_ITEM& aOther ) const if( m_End != other.m_End ) similarity *= 0.9; - if( m_bottomLayer != other.m_bottomLayer ) + if( m_padStack != other.m_padStack ) similarity *= 0.9; if( m_viaType != other.m_viaType ) similarity *= 0.9; - if( m_drill != other.m_drill ) - similarity *= 0.9; - - if( m_removeUnconnectedLayer != other.m_removeUnconnectedLayer ) - similarity *= 0.9; - - if( m_keepStartEndLayer != other.m_keepStartEndLayer ) - similarity *= 0.9; - if( m_zoneLayerOverrides != other.m_zoneLayerOverrides ) similarity *= 0.9; @@ -389,14 +375,16 @@ void PCB_VIA::Serialize( google::protobuf::Any &aContainer ) const via.mutable_position()->set_x_nm( GetPosition().x ); via.mutable_position()->set_y_nm( GetPosition().y ); + const PADSTACK& stack = Padstack(); + kiapi::board::types::PadStack* padstack = via.mutable_pad_stack(); padstack->set_type( GetViaType() == VIATYPE::BLIND_BURIED ? kiapi::board::types::PadStackType::PST_BLIND_BURIED : kiapi::board::types::PadStackType::PST_THROUGH ); padstack->set_start_layer( - ToProtoEnum( m_layer ) ); + ToProtoEnum( stack.Drill().start ) ); padstack->set_end_layer( - ToProtoEnum( m_bottomLayer ) ); + ToProtoEnum( stack.Drill().end ) ); kiapi::common::PackVector2( *padstack->mutable_drill_diameter(), { GetDrillValue(), GetDrillValue() } ); @@ -405,23 +393,12 @@ void PCB_VIA::Serialize( google::protobuf::Any &aContainer ) const kiapi::common::PackVector2( *stackLayer->mutable_size(), { GetWidth(), GetWidth() } ); - kiapi::board::types::UnconnectedLayerRemoval ulr; - - if( m_removeUnconnectedLayer ) - { - if( m_keepStartEndLayer ) - ulr = kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE_EXCEPT_START_AND_END; - else - ulr = kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE; - } - else - { - ulr = kiapi::board::types::UnconnectedLayerRemoval::ULR_KEEP; - } - // TODO: Microvia status is ignored here. Do we still need it? - padstack->set_unconnected_layer_removal( ulr ); + padstack->set_unconnected_layer_removal( + ToProtoEnum( + Padstack().UnconnectedLayerMode() ) ); via.set_locked( IsLocked() ? kiapi::common::types::LockedState::LS_LOCKED : kiapi::common::types::LockedState::LS_UNLOCKED ); @@ -466,36 +443,19 @@ bool PCB_VIA::Deserialize( const google::protobuf::Any &aContainer ) if( GetViaType() != VIATYPE::THROUGH ) { - m_layer = FromProtoEnum( + Padstack().Drill().start = FromProtoEnum( padstack.start_layer() ); - m_bottomLayer = FromProtoEnum( + Padstack().Drill().end = FromProtoEnum( padstack.end_layer() ); } else { - m_layer = F_Cu; - m_bottomLayer = B_Cu; - } - - switch( padstack.unconnected_layer_removal() ) - { - case kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE: - m_removeUnconnectedLayer = true; - m_keepStartEndLayer = false; - break; - - case kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE_EXCEPT_START_AND_END: - m_removeUnconnectedLayer = true; - m_keepStartEndLayer = true; - break; - - default: - case kiapi::board::types::UnconnectedLayerRemoval::ULR_KEEP: - m_removeUnconnectedLayer = false; - m_keepStartEndLayer = false; - break; + Padstack().Drill().start = F_Cu; + Padstack().Drill().end = B_Cu; } + Padstack().SetUnconnectedLayerMode( FromProtoEnum( + padstack.unconnected_layer_removal() ) ); SetNetCode( via.net().code().value() ); SetLocked( via.locked() == kiapi::common::types::LockedState::LS_LOCKED ); @@ -599,8 +559,8 @@ int PCB_VIA::GetMinAnnulus( PCB_LAYER_ID aLayer, wxString* aSource ) const int PCB_VIA::GetDrillValue() const { - if( m_drill > 0 ) // Use the specific value. - return m_drill; + if( m_padStack.Drill().size.x > 0 ) // Use the specific value. + return m_padStack.Drill().size.x; // Use the default value from the Netclass NETCLASS* netclass = GetEffectiveNetClass(); @@ -833,7 +793,7 @@ INSPECT_RESULT PCB_TRACK::Visit( INSPECTOR inspector, void* testData, std::shared_ptr PCB_VIA::GetEffectiveHoleShape() const { - return std::make_shared( SEG( m_Start, m_Start ), m_drill ); + return std::make_shared( SEG( m_Start, m_Start ), Padstack().Drill().size.x ); } @@ -862,7 +822,7 @@ bool PCB_VIA::IsOnLayer( PCB_LAYER_ID aLayer ) const return GetLayerSet().test( aLayer ); #endif - if( aLayer >= m_layer && aLayer <= m_bottomLayer ) + if( aLayer >= Padstack().Drill().start && aLayer <= Padstack().Drill().end ) return true; if( !IsTented() ) @@ -877,20 +837,32 @@ bool PCB_VIA::IsOnLayer( PCB_LAYER_ID aLayer ) const } +PCB_LAYER_ID PCB_VIA::GetLayer() const +{ + return Padstack().Drill().start; +} + + +void PCB_VIA::SetLayer( PCB_LAYER_ID aLayer ) +{ + Padstack().Drill().start = aLayer; +} + + LSET PCB_VIA::GetLayerSet() const { LSET layermask; - if( m_layer < PCBNEW_LAYER_ID_START ) + if( Padstack().Drill().start < PCBNEW_LAYER_ID_START ) return layermask; if( GetViaType() == VIATYPE::THROUGH ) layermask = LSET::AllCuMask(); else - wxASSERT( m_layer <= m_bottomLayer ); + wxASSERT( Padstack().Drill().start <= Padstack().Drill().end ); // PCB_LAYER_IDs are numbered from front to back, this is top to bottom. - for( int id = m_layer; id <= m_bottomLayer; ++id ) + for( int id = Padstack().Drill().start; id <= Padstack().Drill().end; ++id ) layermask.set( id ); if( !IsTented() ) @@ -918,11 +890,11 @@ void PCB_VIA::SetLayerSet( LSET aLayerSet ) if( first ) { - m_layer = layer; + Padstack().Drill().start = layer; first = false; } - m_bottomLayer = layer; + Padstack().Drill().end = layer; } } @@ -930,21 +902,21 @@ void PCB_VIA::SetLayerSet( LSET aLayerSet ) void PCB_VIA::SetLayerPair( PCB_LAYER_ID aTopLayer, PCB_LAYER_ID aBottomLayer ) { - m_layer = aTopLayer; - m_bottomLayer = aBottomLayer; + Padstack().Drill().start = aTopLayer; + Padstack().Drill().end = aBottomLayer; SanitizeLayers(); } void PCB_VIA::SetTopLayer( PCB_LAYER_ID aLayer ) { - m_layer = aLayer; + Padstack().Drill().start = aLayer; } void PCB_VIA::SetBottomLayer( PCB_LAYER_ID aLayer ) { - m_bottomLayer = aLayer; + Padstack().Drill().end = aLayer; } @@ -955,8 +927,8 @@ void PCB_VIA::LayerPair( PCB_LAYER_ID* top_layer, PCB_LAYER_ID* bottom_layer ) c if( GetViaType() != VIATYPE::THROUGH ) { - b_layer = m_bottomLayer; - t_layer = m_layer; + b_layer = Padstack().Drill().end; + t_layer = Padstack().Drill().start; if( b_layer < t_layer ) std::swap( b_layer, t_layer ); @@ -972,13 +944,13 @@ void PCB_VIA::LayerPair( PCB_LAYER_ID* top_layer, PCB_LAYER_ID* bottom_layer ) c PCB_LAYER_ID PCB_VIA::TopLayer() const { - return m_layer; + return Padstack().Drill().start; } PCB_LAYER_ID PCB_VIA::BottomLayer() const { - return m_bottomLayer; + return Padstack().Drill().end; } @@ -986,12 +958,12 @@ void PCB_VIA::SanitizeLayers() { if( GetViaType() == VIATYPE::THROUGH ) { - m_layer = F_Cu; - m_bottomLayer = B_Cu; + Padstack().Drill().start = F_Cu; + Padstack().Drill().end = B_Cu; } - if( m_bottomLayer < m_layer ) - std::swap( m_bottomLayer, m_layer ); + if( Padstack().Drill().end < Padstack().Drill().start ) + std::swap( Padstack().Drill().end, Padstack().Drill().start ); } @@ -1021,12 +993,28 @@ bool PCB_VIA::FlashLayer( int aLayer ) const if( !IsOnLayer( static_cast( aLayer ) ) ) return false; - if( !m_removeUnconnectedLayer || !IsCopperLayer( aLayer ) ) + if( !IsCopperLayer( aLayer ) ) return true; - if( m_keepStartEndLayer && ( aLayer == m_layer || aLayer == m_bottomLayer ) ) + switch( Padstack().UnconnectedLayerMode() ) + { + case PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL: return true; + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END: + { + if( aLayer == Padstack().Drill().start || aLayer == Padstack().Drill().end ) + return true; + + // Check for removal below + break; + } + + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL: + // Check for removal below + break; + } + // Must be static to keep from raising its ugly head in performance profiles static std::initializer_list connectedTypes = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, PCB_PAD_T }; @@ -1736,8 +1724,7 @@ static struct TRACK_VIA_DESC &PCB_VIA::SetWidth, &PCB_VIA::GetWidth, PROPERTY_DISPLAY::PT_SIZE ) ); propMgr.AddProperty( new PROPERTY( _HKI( "Hole" ), &PCB_VIA::SetDrill, &PCB_VIA::GetDrillValue, PROPERTY_DISPLAY::PT_SIZE ), groupVia ); - propMgr.ReplaceProperty( TYPE_HASH( BOARD_ITEM ), _HKI( "Layer" ), - new PROPERTY_ENUM( _HKI( "Layer Top" ), + propMgr.AddProperty( new PROPERTY_ENUM( _HKI( "Layer Top" ), &PCB_VIA::SetLayer, &PCB_VIA::GetLayer ), groupVia ); propMgr.AddProperty( new PROPERTY_ENUM( _HKI( "Layer Bottom" ), &PCB_VIA::SetBottomLayer, &PCB_VIA::BottomLayer ), groupVia ); diff --git a/pcbnew/pcb_track.h b/pcbnew/pcb_track.h index 6dc4a5afc4..cab54ab528 100644 --- a/pcbnew/pcb_track.h +++ b/pcbnew/pcb_track.h @@ -43,6 +43,7 @@ #include #include #include +#include class PCB_TRACK; class PCB_VIA; @@ -410,6 +411,10 @@ public: VIATYPE GetViaType() const { return m_viaType; } void SetViaType( VIATYPE aViaType ) { m_viaType = aViaType; } + const PADSTACK& Padstack() const { return m_padStack; } + PADSTACK& Padstack() { return m_padStack; } + void SetPadstack( const PADSTACK& aPadstack ) { m_padStack = aPadstack; } + bool HasHole() const override { return true; @@ -423,6 +428,9 @@ public: bool IsTented() const override; int GetSolderMaskExpansion() const; + PCB_LAYER_ID GetLayer() const override; + void SetLayer( PCB_LAYER_ID aLayer ) override; + bool IsOnLayer( PCB_LAYER_ID aLayer ) const override; virtual LSET GetLayerSet() const override; @@ -494,25 +502,55 @@ public: int GetMinAnnulus( PCB_LAYER_ID aLayer, wxString* aSource ) const; /** + * @deprecated - use Padstack().SetUnconnectedLayerMode() * Sets the unconnected removal property. If true, the copper is removed on zone fill * or when specifically requested when the via is not connected on a layer. */ - void SetRemoveUnconnected( bool aSet ) { m_removeUnconnectedLayer = aSet; } - bool GetRemoveUnconnected() const { return m_removeUnconnectedLayer; } + void SetRemoveUnconnected( bool aSet ) + { + m_padStack.SetUnconnectedLayerMode( aSet + ? PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL + : PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL ); + } + + bool GetRemoveUnconnected() const + { + return m_padStack.UnconnectedLayerMode() != PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL; + } /** + * @deprecated - use Padstack().SetUnconnectedLayerMode() * Sets whether we keep the start and end annular rings even if they are not connected */ - void SetKeepStartEnd( bool aSet ) { m_keepStartEndLayer = aSet; } - bool GetKeepStartEnd() const { return m_keepStartEndLayer; } + void SetKeepStartEnd( bool aSet ) + { + m_padStack.SetUnconnectedLayerMode( aSet + ? PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END + : PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL ); + } + + bool GetKeepStartEnd() const + { + return m_padStack.UnconnectedLayerMode() + == PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END; + } bool ConditionallyFlashed( PCB_LAYER_ID aLayer ) const { - if( !m_removeUnconnectedLayer ) + switch( m_padStack.UnconnectedLayerMode() ) + { + case PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL: return false; - if( m_keepStartEndLayer && ( aLayer == m_layer || aLayer == m_bottomLayer ) ) - return false; + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL: + return true; + + case PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END: + { + if( aLayer == m_padStack.Drill().start || aLayer == m_padStack.Drill().end ) + return false; + } + } return true; } @@ -547,14 +585,17 @@ public: * * @param aDrill is the new drill diameter */ - void SetDrill( int aDrill ) { m_drill = aDrill; } + void SetDrill( int aDrill ) + { + m_padStack.Drill().size = { aDrill, aDrill }; + } /** * Return the local drill setting for this PCB_VIA. * * @note Use GetDrillValue() to get the calculated value. */ - int GetDrill() const { return m_drill; } + int GetDrill() const { return m_padStack.Drill().size.x; } /** * Calculate the drill value for vias (m_drill if > 0, or default drill value for the board). @@ -566,7 +607,10 @@ public: /** * Set the drill value for vias to the default value #UNDEFINED_DRILL_DIAMETER. */ - void SetDrillDefault() { m_drill = UNDEFINED_DRILL_DIAMETER; } + void SetDrillDefault() + { + m_padStack.Drill().size = { UNDEFINED_DRILL_DIAMETER, UNDEFINED_DRILL_DIAMETER }; + } /** * Check if the via is a free via (as opposed to one created on a track by the router). @@ -611,15 +655,10 @@ protected: wxString layerMaskDescribe() const override; private: - /// The bottom layer of the via (the top layer is in m_layer) - PCB_LAYER_ID m_bottomLayer; - VIATYPE m_viaType; ///< through, blind/buried or micro - int m_drill; ///< for vias: via drill (- 1 for default value) + PADSTACK m_padStack; - bool m_removeUnconnectedLayer; ///< Remove annular rings on unconnected layers - bool m_keepStartEndLayer; ///< Keep the start and end annular rings bool m_isFree; ///< "Free" vias don't get their nets auto-updated std::mutex m_zoneLayerOverridesMutex; diff --git a/pcbnew/zone_filler.cpp b/pcbnew/zone_filler.cpp index 9d5b342bbc..044951fda6 100644 --- a/pcbnew/zone_filler.cpp +++ b/pcbnew/zone_filler.cpp @@ -773,7 +773,7 @@ void ZONE_FILLER::addKnockout( PAD* aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_P aPad->TransformShapeToPolygon( poly, aLayer, aGap, m_maxError, ERROR_OUTSIDE ); // the pad shape in zone can be its convex hull or the shape itself - if( aPad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL ) + if( aPad->GetCustomShapeInZoneOpt() == PADSTACK::CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL ) { std::vector convex_hull; BuildConvexHull( convex_hull, poly ); diff --git a/qa/tests/api/test_api_enums.cpp b/qa/tests/api/test_api_enums.cpp index 07864f3658..40574afe1e 100644 --- a/qa/tests/api/test_api_enums.cpp +++ b/qa/tests/api/test_api_enums.cpp @@ -160,4 +160,9 @@ BOOST_AUTO_TEST_CASE( ZoneConnectionStyle ) testEnums(); } +BOOST_AUTO_TEST_CASE( UnconnectedLayerRemoval ) +{ + testEnums(); +} + BOOST_AUTO_TEST_SUITE_END()