From d6b75c64e1f2fd7fc787b2a8f265dd5130d1e435 Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Wed, 13 Sep 2023 11:27:04 +0100 Subject: [PATCH] ADDED: custom-shaped pad spoke templates. --- common/eda_shape.cpp | 67 +++++++++++------- include/eda_shape.h | 7 +- pcbnew/pad.cpp | 2 +- pcbnew/pad_custom_shape_functions.cpp | 2 +- pcbnew/pcb_painter.cpp | 15 ++-- pcbnew/pcb_shape.cpp | 99 +++++++++++++++++++++------ pcbnew/pcb_shape.h | 4 +- pcbnew/plugins/kicad/pcb_parser.cpp | 2 +- pcbnew/plugins/kicad/pcb_plugin.cpp | 2 +- pcbnew/tools/pad_tool.cpp | 87 ++++------------------- pcbnew/tools/pcb_point_editor.cpp | 2 +- pcbnew/zone_filler.cpp | 58 +++++++++++++++- 12 files changed, 215 insertions(+), 132 deletions(-) diff --git a/common/eda_shape.cpp b/common/eda_shape.cpp index 6e75df6a41..89decbe0a8 100644 --- a/common/eda_shape.cpp +++ b/common/eda_shape.cpp @@ -47,7 +47,7 @@ EDA_SHAPE::EDA_SHAPE( SHAPE_T aType, int aLineWidth, FILL_T aFill ) : m_rectangleWidth( 0 ), m_segmentLength( 0 ), m_editState( 0 ), - m_annotationProxy( false ) + m_proxyItem( false ) { } @@ -59,18 +59,27 @@ EDA_SHAPE::~EDA_SHAPE() wxString EDA_SHAPE::ShowShape() const { - if( IsAnnotationProxy() ) - return _( "Number Box" ); - - switch( m_shape ) + if( IsProxyItem() ) { - case SHAPE_T::SEGMENT: return _( "Line" ); - case SHAPE_T::RECTANGLE: return _( "Rect" ); - case SHAPE_T::ARC: return _( "Arc" ); - case SHAPE_T::CIRCLE: return _( "Circle" ); - case SHAPE_T::BEZIER: return _( "Bezier Curve" ); - case SHAPE_T::POLY: return _( "Polygon" ); - default: return wxT( "??" ); + switch( m_shape ) + { + case SHAPE_T::SEGMENT: return _( "Thermal Spoke" ); + case SHAPE_T::RECTANGLE: return _( "Number Box" ); + default: return wxT( "??" ); + } + } + else + { + switch( m_shape ) + { + case SHAPE_T::SEGMENT: return _( "Line" ); + case SHAPE_T::RECTANGLE: return _( "Rect" ); + case SHAPE_T::ARC: return _( "Arc" ); + case SHAPE_T::CIRCLE: return _( "Circle" ); + case SHAPE_T::BEZIER: return _( "Bezier Curve" ); + case SHAPE_T::POLY: return _( "Polygon" ); + default: return wxT( "??" ); + } } } @@ -674,15 +683,27 @@ void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeA wxString EDA_SHAPE::GetFriendlyName() const { - switch( m_shape ) + if( IsProxyItem() ) { - case SHAPE_T::CIRCLE: return _( "Circle" ); - case SHAPE_T::ARC: return _( "Arc" ); - case SHAPE_T::BEZIER: return _( "Curve" ); - case SHAPE_T::POLY: return _( "Polygon" ); - case SHAPE_T::RECTANGLE: return IsAnnotationProxy() ? _( "Pad Number Box" ) : _( "Rectangle" ); - case SHAPE_T::SEGMENT: return _( "Segment" ); - default: return _( "Unrecognized" ); + switch( m_shape ) + { + case SHAPE_T::RECTANGLE: return _( "Pad Number Box" ); + case SHAPE_T::SEGMENT: return _( "Thermal Spoke Template" ); + default: return _( "Unrecognized" ); + } + } + else + { + switch( m_shape ) + { + case SHAPE_T::CIRCLE: return _( "Circle" ); + case SHAPE_T::ARC: return _( "Arc" ); + case SHAPE_T::BEZIER: return _( "Curve" ); + case SHAPE_T::POLY: return _( "Polygon" ); + case SHAPE_T::RECTANGLE: return _( "Rectangle" ); + case SHAPE_T::SEGMENT: return _( "Segment" ); + default: return _( "Unrecognized" ); + } } } @@ -880,7 +901,7 @@ bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist ); case SHAPE_T::RECTANGLE: - if( IsAnnotationProxy() || IsFilled() ) // Filled rect hit-test + if( IsProxyItem() || IsFilled() ) // Filled rect hit-test { SHAPE_POLY_SET poly; poly.NewOutline(); @@ -1175,7 +1196,7 @@ std::vector EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineCh { std::vector pts = GetRectCorners(); - if( ( IsFilled() || IsAnnotationProxy() ) && !aEdgeOnly ) + if( ( IsFilled() || IsProxyItem() ) && !aEdgeOnly ) effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) ); if( width > 0 || !IsFilled() || aEdgeOnly ) @@ -1598,7 +1619,7 @@ void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance { std::vector pts = GetRectCorners(); - if( IsFilled() || IsAnnotationProxy() ) + if( IsFilled() || IsProxyItem() ) { aBuffer.NewOutline(); diff --git a/include/eda_shape.h b/include/eda_shape.h index 31038d75f0..a63a3172b7 100644 --- a/include/eda_shape.h +++ b/include/eda_shape.h @@ -85,8 +85,8 @@ public: wxString SHAPE_T_asString() const; - virtual bool IsAnnotationProxy() const { return m_annotationProxy; } - virtual void SetIsAnnotationProxy( bool aIsProxy = true ) { m_annotationProxy = aIsProxy; } + virtual bool IsProxyItem() const { return m_proxyItem; } + virtual void SetIsProxyItem( bool aIsProxy = true ) { m_proxyItem = aIsProxy; } bool IsFilled() const { @@ -399,7 +399,8 @@ protected: SHAPE_POLY_SET m_poly; // Stores the S_POLYGON shape int m_editState; - bool m_annotationProxy; // A shape storing the position of an annotation + bool m_proxyItem; // A shape storing proxy information (ie: a pad + // number box, thermal spoke template, etc.) }; #ifndef SWIG diff --git a/pcbnew/pad.cpp b/pcbnew/pad.cpp index fcfb9a4455..82e67d720f 100644 --- a/pcbnew/pad.cpp +++ b/pcbnew/pad.cpp @@ -566,7 +566,7 @@ void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const { for( const std::shared_ptr& primitive : m_editPrimitives ) { - if( !primitive->IsAnnotationProxy() ) + if( !primitive->IsProxyItem() ) { for( SHAPE* shape : primitive->MakeEffectiveShapes() ) { diff --git a/pcbnew/pad_custom_shape_functions.cpp b/pcbnew/pad_custom_shape_functions.cpp index 52fbad46e2..daffbe58a0 100644 --- a/pcbnew/pad_custom_shape_functions.cpp +++ b/pcbnew/pad_custom_shape_functions.cpp @@ -124,7 +124,7 @@ void PAD::addPadPrimitivesToPolygon( SHAPE_POLY_SET* aMergedPolygon, int aError, for( const std::shared_ptr& primitive : m_editPrimitives ) { - if( !primitive->IsAnnotationProxy() ) + if( !primitive->IsProxyItem() ) primitive->TransformShapeToPolygon( polyset, UNDEFINED_LAYER, 0, aError, aErrorLoc ); } diff --git a/pcbnew/pcb_painter.cpp b/pcbnew/pcb_painter.cpp index 94665ca50f..a3acc8893b 100644 --- a/pcbnew/pcb_painter.cpp +++ b/pcbnew/pcb_painter.cpp @@ -1171,7 +1171,7 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer ) { const PCB_SHAPE* shape = static_cast( aItem ); - if( shape->IsAnnotationProxy() ) + if( shape->IsProxyItem() && shape->GetShape() == SHAPE_T::RECTANGLE ) { position = shape->GetCenter(); padsize = shape->GetBotRight() - shape->GetTopLeft(); @@ -1190,7 +1190,7 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer ) // See if we have a number box for( const std::shared_ptr& primitive : aPad->GetPrimitives() ) { - if( primitive->IsAnnotationProxy() ) + if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::RECTANGLE ) { position = aPad->GetPosition() + primitive->GetCenter(); padsize.x = abs( primitive->GetBotRight().x - primitive->GetTopLeft().x ); @@ -1734,7 +1734,14 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) switch( aShape->GetShape() ) { case SHAPE_T::SEGMENT: - if( outline_mode ) + if( aShape->IsProxyItem() ) + { + m_gal->SetIsFill( false ); + m_gal->SetIsStroke( true ); + m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth ); + m_gal->DrawSegment( aShape->GetStart(), aShape->GetEnd(), thickness ); + } + else if( outline_mode ) { m_gal->DrawSegment( aShape->GetStart(), aShape->GetEnd(), thickness ); } @@ -1752,7 +1759,7 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) { std::vector pts = aShape->GetRectCorners(); - if( aShape->IsAnnotationProxy() ) + if( aShape->IsProxyItem() ) { m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth ); m_gal->DrawLine( pts[0], pts[1] ); diff --git a/pcbnew/pcb_shape.cpp b/pcbnew/pcb_shape.cpp index 43f60c9794..078fed4e96 100644 --- a/pcbnew/pcb_shape.cpp +++ b/pcbnew/pcb_shape.cpp @@ -354,14 +354,45 @@ void PCB_SHAPE::Mirror( const VECTOR2I& aCentre, bool aMirrorAroundXAxis ) } -void PCB_SHAPE::SetIsAnnotationProxy( bool aIsProxy ) +void PCB_SHAPE::SetIsProxyItem( bool aIsProxy ) { - if( aIsProxy && !m_annotationProxy ) - SetStroke( STROKE_PARAMS( 1 ) ); - else if( m_annotationProxy && !aIsProxy ) - SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ) ) ); + PAD* parentPad = nullptr; - m_annotationProxy = aIsProxy; + if( GetBoard() && GetBoard()->IsFootprintHolder() ) + { + for( FOOTPRINT* fp : GetBoard()->Footprints() ) + { + for( PAD* pad : fp->Pads() ) + { + if( pad->IsEntered() ) + { + parentPad = pad; + break; + } + } + } + } + + if( aIsProxy && !m_proxyItem ) + { + if( GetShape() == SHAPE_T::SEGMENT ) + { + if( parentPad && parentPad->GetThermalSpokeWidth() ) + SetWidth( parentPad->GetThermalSpokeWidth() ); + else + SetWidth( pcbIUScale.mmToIU( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MM ) ); + } + else + { + SetWidth( 1 ); + } + } + else if( m_proxyItem && !aIsProxy ) + { + SetWidth( pcbIUScale.mmToIU( DEFAULT_LINE_WIDTH ) ); + } + + m_proxyItem = aIsProxy; } @@ -591,21 +622,42 @@ static struct PCB_SHAPE_DESC _HKI( "Net" ), isCopper ); auto isPadEditMode = - []( INSPECTABLE* aItem ) -> bool + []( BOARD* aBoard ) -> bool + { + if( aBoard && aBoard->IsFootprintHolder() ) + { + for( FOOTPRINT* fp : aBoard->Footprints() ) + { + for( PAD* pad : fp->Pads() ) + { + if( pad->IsEntered() ) + return true; + } + } + } + + return false; + }; + + auto showNumberBoxProperty = + [&]( INSPECTABLE* aItem ) -> bool { if( PCB_SHAPE* shape = dynamic_cast( aItem ) ) { - if( shape->GetBoard() && shape->GetBoard()->IsFootprintHolder() ) - { - for( FOOTPRINT* fp : shape->GetBoard()->Footprints() ) - { - for( PAD* pad : fp->Pads() ) - { - if( pad->IsEntered() ) - return true; - } - } - } + if( shape->GetShape() == SHAPE_T::RECTANGLE ) + return isPadEditMode( shape->GetBoard() ); + } + + return false; + }; + + auto showSpokeTemplateProperty = + [&]( INSPECTABLE* aItem ) -> bool + { + if( PCB_SHAPE* shape = dynamic_cast( aItem ) ) + { + if( shape->GetShape() == SHAPE_T::SEGMENT ) + return isPadEditMode( shape->GetBoard() ); } return false; @@ -614,8 +666,15 @@ static struct PCB_SHAPE_DESC const wxString groupPadPrimitives = _HKI( "Pad Primitives" ); propMgr.AddProperty( new PROPERTY( _HKI( "Number Box" ), - &PCB_SHAPE::SetIsAnnotationProxy, &PCB_SHAPE::IsAnnotationProxy ), + &PCB_SHAPE::SetIsProxyItem, + &PCB_SHAPE::IsProxyItem ), groupPadPrimitives ) - .SetAvailableFunc( isPadEditMode ); + .SetAvailableFunc( showNumberBoxProperty ); + + propMgr.AddProperty( new PROPERTY( _HKI( "Thermal Spoke Template" ), + &PCB_SHAPE::SetIsProxyItem, + &PCB_SHAPE::IsProxyItem ), + groupPadPrimitives ) + .SetAvailableFunc( showSpokeTemplateProperty ); } } _PCB_SHAPE_DESC; diff --git a/pcbnew/pcb_shape.h b/pcbnew/pcb_shape.h index db6918dda7..851c23ccd2 100644 --- a/pcbnew/pcb_shape.h +++ b/pcbnew/pcb_shape.h @@ -102,8 +102,8 @@ public: std::shared_ptr GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER, FLASHING aFlash = FLASHING::DEFAULT ) const override; - bool IsAnnotationProxy() const override { return m_annotationProxy; } - void SetIsAnnotationProxy( bool aIsProxy = true ) override; + bool IsProxyItem() const override { return m_proxyItem; } + void SetIsProxyItem( bool aIsProxy = true ) override; void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aList ) override; diff --git a/pcbnew/plugins/kicad/pcb_parser.cpp b/pcbnew/plugins/kicad/pcb_parser.cpp index 5d3b25845e..1617ef4b05 100644 --- a/pcbnew/plugins/kicad/pcb_parser.cpp +++ b/pcbnew/plugins/kicad/pcb_parser.cpp @@ -4585,7 +4585,7 @@ PAD* PCB_PARSER::parsePAD( FOOTPRINT* aParent ) case T_gr_bbox: { PCB_SHAPE* numberBox = parsePCB_SHAPE( nullptr ); - numberBox->SetIsAnnotationProxy(); + numberBox->SetIsProxyItem(); pad->AddPrimitive( numberBox ); break; } diff --git a/pcbnew/plugins/kicad/pcb_plugin.cpp b/pcbnew/plugins/kicad/pcb_plugin.cpp index 1a710dc208..f74274d693 100644 --- a/pcbnew/plugins/kicad/pcb_plugin.cpp +++ b/pcbnew/plugins/kicad/pcb_plugin.cpp @@ -1704,7 +1704,7 @@ void PCB_PLUGIN::format( const PAD* aPad, int aNestLevel ) const break; case SHAPE_T::RECTANGLE: - if( primitive->IsAnnotationProxy() ) + if( primitive->IsProxyItem() ) { m_out->Print( nested_level, "(gr_bbox (start %s) (end %s)", formatInternalUnits( primitive->GetStart() ).c_str(), diff --git a/pcbnew/tools/pad_tool.cpp b/pcbnew/tools/pad_tool.cpp index 7e5ed56d7b..bfcaff1735 100644 --- a/pcbnew/tools/pad_tool.cpp +++ b/pcbnew/tools/pad_tool.cpp @@ -766,48 +766,21 @@ PCB_LAYER_ID PAD_TOOL::explodePad( PAD* aPad ) for( const std::shared_ptr& primitive : aPad->GetPrimitives() ) { - PCB_SHAPE* shape = new PCB_SHAPE( board()->GetFirstFootprint() ); - - shape->SetShape( primitive->GetShape() ); - shape->SetIsAnnotationProxy( primitive->IsAnnotationProxy()); - shape->SetFilled( primitive->IsFilled() ); - shape->SetStroke( primitive->GetStroke() ); - - switch( shape->GetShape() ) - { - case SHAPE_T::SEGMENT: - case SHAPE_T::RECTANGLE: - case SHAPE_T::CIRCLE: - shape->SetStart( primitive->GetStart() ); - shape->SetEnd( primitive->GetEnd() ); - break; - - case SHAPE_T::ARC: - shape->SetStart( primitive->GetStart() ); - shape->SetEnd( primitive->GetEnd() ); - shape->SetCenter( primitive->GetCenter() ); - break; - - case SHAPE_T::BEZIER: - shape->SetStart( primitive->GetStart() ); - shape->SetEnd( primitive->GetEnd() ); - shape->SetBezierC1( primitive->GetBezierC1() ); - shape->SetBezierC2( primitive->GetBezierC2() ); - break; - - case SHAPE_T::POLY: - shape->SetPolyShape( primitive->GetPolyShape() ); - break; - - default: - UNIMPLEMENTED_FOR( shape->SHAPE_T_asString() ); - } + PCB_SHAPE* shape = static_cast( primitive->Duplicate() ); + shape->SetParent( board()->GetFirstFootprint() ); shape->Rotate( VECTOR2I( 0, 0 ), aPad->GetOrientation() ); shape->Move( aPad->ShapePos() ); - shape->SetLayer( layer ); + if( shape->IsProxyItem() && shape->GetShape() == SHAPE_T::SEGMENT ) + { + if( aPad->GetThermalSpokeWidth() ) + shape->SetWidth( aPad->GetThermalSpokeWidth() ); + else + shape->SetWidth( pcbIUScale.mmToIU( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MM ) ); + } + commit.Add( shape ); } @@ -851,7 +824,7 @@ std::vector PAD_TOOL::RecombinePad( PAD* aPad, bool aIsDryRun, BOARD if( shape->GetLayer() != aLayer ) continue; - if( shape->IsAnnotationProxy() ) // Pad number (and net name) box + if( shape->IsProxyItem() ) // Pad number (and net name) box return shape; SHAPE_POLY_SET drawPoly; @@ -938,46 +911,12 @@ std::vector PAD_TOOL::RecombinePad( PAD* aPad, bool aIsDryRun, BOARD if( !aIsDryRun ) { - PCB_SHAPE* primitive = new PCB_SHAPE; - - primitive->SetShape( fpShape->GetShape() ); - primitive->SetFilled( fpShape->IsFilled() ); - primitive->SetStroke( fpShape->GetStroke() ); - - switch( primitive->GetShape() ) - { - case SHAPE_T::SEGMENT: - case SHAPE_T::RECTANGLE: - case SHAPE_T::CIRCLE: - primitive->SetStart( fpShape->GetStart() ); - primitive->SetEnd( fpShape->GetEnd() ); - break; - - case SHAPE_T::ARC: - primitive->SetStart( fpShape->GetStart() ); - primitive->SetEnd( fpShape->GetEnd() ); - primitive->SetCenter( fpShape->GetCenter() ); - break; - - case SHAPE_T::BEZIER: - primitive->SetStart( fpShape->GetStart() ); - primitive->SetEnd( fpShape->GetEnd() ); - primitive->SetBezierC1( fpShape->GetBezierC1() ); - primitive->SetBezierC2( fpShape->GetBezierC2() ); - break; - - case SHAPE_T::POLY: - primitive->SetPolyShape( fpShape->GetPolyShape() ); - break; - - default: - UNIMPLEMENTED_FOR( primitive->SHAPE_T_asString() ); - } + PCB_SHAPE* primitive = static_cast( fpShape->Duplicate() ); + primitive->SetParent( nullptr ); primitive->Move( - aPad->ShapePos() ); primitive->Rotate( VECTOR2I( 0, 0 ), - aPad->GetOrientation() ); - primitive->SetIsAnnotationProxy( fpShape->IsAnnotationProxy() ); aPad->AddPrimitive( primitive ); aCommit.Remove( fpShape ); diff --git a/pcbnew/tools/pcb_point_editor.cpp b/pcbnew/tools/pcb_point_editor.cpp index bb421d32c3..04d70c5e36 100644 --- a/pcbnew/tools/pcb_point_editor.cpp +++ b/pcbnew/tools/pcb_point_editor.cpp @@ -1303,7 +1303,7 @@ void PCB_POINT_EDITOR::updateItem() const break; } - if( shape->IsAnnotationProxy() ) + if( shape->IsProxyItem() ) { for( PAD* pad : shape->GetParentFootprint()->Pads() ) { diff --git a/pcbnew/zone_filler.cpp b/pcbnew/zone_filler.cpp index 7a23a3dcd3..16ba2c3b71 100644 --- a/pcbnew/zone_filler.cpp +++ b/pcbnew/zone_filler.cpp @@ -1774,6 +1774,20 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer, if( !( itemBB.Intersects( zoneBB ) ) ) continue; + bool customSpokes = false; + + if( pad->GetShape() == PAD_SHAPE::CUSTOM ) + { + for( const std::shared_ptr& primitive : pad->GetPrimitives() ) + { + if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT ) + { + customSpokes = true; + break; + } + } + } + // Thermal spokes consist of square-ended segments from the pad center to points just // outside the thermal relief. The outside end has an extra center point (which must be // at idx 3) which is used for testing whether or not the spoke connects to copper in the @@ -1826,9 +1840,51 @@ void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer, } }; + if( customSpokes ) + { + SHAPE_POLY_SET thermalPoly; + SHAPE_LINE_CHAIN thermalOutline; + + pad->TransformShapeToPolygon( thermalPoly, aLayer, thermalReliefGap + epsilon, + m_maxError, ERROR_OUTSIDE ); + + if( thermalPoly.OutlineCount() ) + thermalOutline = thermalPoly.Outline( 0 ); + + for( const std::shared_ptr& primitive : pad->GetPrimitives() ) + { + if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT ) + { + SEG seg( primitive->GetStart(), primitive->GetEnd() ); + SHAPE_LINE_CHAIN::INTERSECTIONS intersections; + + // Make sure seg.A is the origin + if( !pad->GetEffectivePolygon()->Contains( seg.A ) ) + seg.Reverse(); + + // Trim seg.B to the thermal outline + if( thermalOutline.Intersect( seg, intersections ) ) + { + seg.B = intersections.front().p; + + VECTOR2I offset = ( seg.B - seg.A ).Perpendicular().Resize( spoke_half_w ); + SHAPE_LINE_CHAIN spoke; + + spoke.Append( seg.A + offset ); + spoke.Append( seg.A - offset ); + spoke.Append( seg.B - offset ); + spoke.Append( seg.B ); // test pt + spoke.Append( seg.B + offset ); + + spoke.SetClosed( true ); + aSpokesList.push_back( std::move( spoke ) ); + } + } + } + } // If the spokes are at a cardinal angle then we can generate them from a bounding box // without trig. - if( ( pad->GetOrientation() + pad->GetThermalSpokeAngle() ).IsCardinal() ) + else if( ( pad->GetOrientation() + pad->GetThermalSpokeAngle() ).IsCardinal() ) { BOX2I spokesBox = pad->GetBoundingBox(); spokesBox.Inflate( thermalReliefGap + epsilon );