kicad-source/pcbnew/drc/drc_test_provider_solder_mask.cpp
Jeff Young 5427100539 Handle plotting of text on solder mask layer.
Admittedly this is an odd thing to do, but we should still be internally
consistent, and it has come up in customers' files.

Fixes https://gitlab.com/kicad/code/kicad/issues/14226
2023-03-12 21:21:15 +00:00

789 lines
28 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2004-2022 KiCad Developers.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <common.h>
#include <board_design_settings.h>
#include <board_connected_item.h>
#include <footprint.h>
#include <pad.h>
#include <pcb_track.h>
#include <pcb_text.h>
#include <zone.h>
#include <geometry/seg.h>
#include <drc/drc_engine.h>
#include <drc/drc_item.h>
#include <drc/drc_rule.h>
#include <drc/drc_test_provider_clearance_base.h>
#include <drc/drc_rtree.h>
/*
Solder mask tests. Checks for silkscreen which is clipped by mask openings and for bridges
between mask apertures with different nets.
Errors generated:
- DRCE_SILK_CLEARANCE
- DRCE_SOLDERMASK_BRIDGE
*/
class DRC_TEST_PROVIDER_SOLDER_MASK : public ::DRC_TEST_PROVIDER
{
public:
DRC_TEST_PROVIDER_SOLDER_MASK ():
m_board( nullptr ),
m_webWidth( 0 ),
m_maxError( 0 ),
m_largestClearance( 0 )
{
m_bridgeRule.m_Name = _( "board setup solder mask min width" );
}
virtual ~DRC_TEST_PROVIDER_SOLDER_MASK()
{
}
virtual bool Run() override;
virtual const wxString GetName() const override
{
return wxT( "solder_mask_issues" );
};
virtual const wxString GetDescription() const override
{
return wxT( "Tests for silkscreen being clipped by solder mask and copper being exposed "
"by mask apertures of other nets" );
}
private:
void addItemToRTrees( BOARD_ITEM* aItem );
void buildRTrees();
void testSilkToMaskClearance();
void testMaskBridges();
void testItemAgainstItems( BOARD_ITEM* aItem, const BOX2I& aItemBBox,
PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer );
void testMaskItemAgainstZones( BOARD_ITEM* item, const BOX2I& itemBBox,
PCB_LAYER_ID refLayer, PCB_LAYER_ID targetLayer );
bool checkMaskAperture( BOARD_ITEM* aMaskItem, BOARD_ITEM* aTestItem, PCB_LAYER_ID aTestLayer,
int aTestNet, BOARD_ITEM** aCollidingItem );
bool checkItemMask( BOARD_ITEM* aMaskItem, int aTestNet );
private:
DRC_RULE m_bridgeRule;
BOARD* m_board;
int m_webWidth;
int m_maxError;
int m_largestClearance;
std::unique_ptr<DRC_RTREE> m_fullSolderMaskRTree;
std::unique_ptr<DRC_RTREE> m_itemTree;
std::unordered_map<PTR_PTR_CACHE_KEY, LSET> m_checkedPairs;
// Shapes used to define solder mask apertures don't have nets, so we assign them the
// first object+net that bridges their aperture (after which any other nets will generate
// violations).
std::unordered_map<PTR_LAYER_CACHE_KEY, std::pair<BOARD_ITEM*, int>> m_maskApertureNetMap;
};
void DRC_TEST_PROVIDER_SOLDER_MASK::addItemToRTrees( BOARD_ITEM* aItem )
{
ZONE* solderMask = m_board->m_SolderMask;
if( aItem->Type() == PCB_ZONE_T || aItem->Type() == PCB_FP_ZONE_T )
{
ZONE* zone = static_cast<ZONE*>( aItem );
for( PCB_LAYER_ID layer : { F_Mask, B_Mask } )
{
if( zone->IsOnLayer( layer ) )
{
solderMask->GetFill( layer )->BooleanAdd( *zone->GetFilledPolysList( layer ),
SHAPE_POLY_SET::PM_FAST );
}
}
}
else if( aItem->Type() == PCB_PAD_T )
{
for( PCB_LAYER_ID layer : { F_Mask, B_Mask } )
{
if( aItem->IsOnLayer( layer ) )
{
PAD* pad = static_cast<PAD*>( aItem );
int clearance = ( m_webWidth / 2 ) + pad->GetSolderMaskExpansion();
aItem->TransformShapeToPolygon( *solderMask->GetFill( layer ), layer, clearance,
m_maxError, ERROR_OUTSIDE );
m_itemTree->Insert( aItem, layer, m_largestClearance );
}
}
}
else if( aItem->Type() == PCB_VIA_T )
{
for( PCB_LAYER_ID layer : { F_Mask, B_Mask } )
{
if( aItem->IsOnLayer( layer ) )
{
PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
int clearance = ( m_webWidth / 2 ) + via->GetSolderMaskExpansion();
via->TransformShapeToPolygon( *solderMask->GetFill( layer ), layer, clearance,
m_maxError, ERROR_OUTSIDE );
m_itemTree->Insert( aItem, layer, m_largestClearance );
}
}
}
else if( aItem->Type() == PCB_TEXT_T )
{
for( PCB_LAYER_ID layer : { F_Mask, B_Mask } )
{
if( aItem->IsOnLayer( layer ) )
{
const PCB_TEXT* text = static_cast<const PCB_TEXT*>( aItem );
text->TransformTextToPolySet( *solderMask->GetFill( layer ), layer,
m_webWidth / 2, m_maxError, ERROR_OUTSIDE );
m_itemTree->Insert( aItem, layer, m_largestClearance );
}
}
}
else if( aItem->Type() == PCB_FP_TEXT_T )
{
for( PCB_LAYER_ID layer : { F_Mask, B_Mask } )
{
if( aItem->IsOnLayer( layer ) )
{
const FP_TEXT* text = static_cast<const FP_TEXT*>( aItem );
text->TransformTextToPolySet( *solderMask->GetFill( layer ), layer,
m_webWidth / 2, m_maxError, ERROR_OUTSIDE );
m_itemTree->Insert( aItem, layer, m_largestClearance );
}
}
}
else
{
for( PCB_LAYER_ID layer : { F_Mask, B_Mask } )
{
if( aItem->IsOnLayer( layer ) )
{
aItem->TransformShapeToPolygon( *solderMask->GetFill( layer ), layer,
m_webWidth / 2, m_maxError, ERROR_OUTSIDE );
m_itemTree->Insert( aItem, layer, m_largestClearance );
}
}
}
}
void DRC_TEST_PROVIDER_SOLDER_MASK::buildRTrees()
{
ZONE* solderMask = m_board->m_SolderMask;
LSET layers = { 4, F_Mask, B_Mask, F_Cu, B_Cu };
const size_t progressDelta = 500;
int count = 0;
int ii = 0;
solderMask->GetFill( F_Mask )->RemoveAllContours();
solderMask->GetFill( B_Mask )->RemoveAllContours();
m_fullSolderMaskRTree = std::make_unique<DRC_RTREE>();
m_itemTree = std::make_unique<DRC_RTREE>();
forEachGeometryItem( s_allBasicItems, layers,
[&]( BOARD_ITEM* item ) -> bool
{
++count;
return true;
} );
forEachGeometryItem( s_allBasicItems, layers,
[&]( BOARD_ITEM* item ) -> bool
{
if( !reportProgress( ii++, count, progressDelta ) )
return false;
addItemToRTrees( item );
return true;
} );
solderMask->GetFill( F_Mask )->Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
solderMask->GetFill( B_Mask )->Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
int numSegs = GetArcToSegmentCount( m_webWidth / 2, m_maxError, FULL_CIRCLE );
solderMask->GetFill( F_Mask )->Deflate( m_webWidth / 2, numSegs );
solderMask->GetFill( B_Mask )->Deflate( m_webWidth / 2, numSegs );
solderMask->SetFillFlag( F_Mask, true );
solderMask->SetFillFlag( B_Mask, true );
solderMask->SetIsFilled( true );
solderMask->CacheTriangulation();
m_fullSolderMaskRTree->Insert( solderMask, F_Mask );
m_fullSolderMaskRTree->Insert( solderMask, B_Mask );
m_checkedPairs.clear();
}
void DRC_TEST_PROVIDER_SOLDER_MASK::testSilkToMaskClearance()
{
LSET silkLayers = { 2, F_SilkS, B_SilkS };
const size_t progressDelta = 250;
int count = 0;
int ii = 0;
forEachGeometryItem( s_allBasicItems, silkLayers,
[&]( BOARD_ITEM* item ) -> bool
{
++count;
return true;
} );
forEachGeometryItem( s_allBasicItems, silkLayers,
[&]( BOARD_ITEM* item ) -> bool
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_CLEARANCE ) )
return false;
if( !reportProgress( ii++, count, progressDelta ) )
return false;
if( isInvisibleText( item ) )
return true;
for( PCB_LAYER_ID layer : silkLayers.Seq() )
{
if( !item->IsOnLayer( layer ) )
continue;
BOX2I itemBBox = item->GetBoundingBox();
DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( SILK_CLEARANCE_CONSTRAINT,
item, nullptr, layer );
int clearance = constraint.GetValue().Min();
int actual;
VECTOR2I pos;
if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE || clearance <= 0 )
return true;
std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape( layer );
if( m_fullSolderMaskRTree->QueryColliding( itemBBox, itemShape.get(), layer,
clearance, &actual, &pos ) )
{
auto drce = DRC_ITEM::Create( DRCE_SILK_CLEARANCE );
wxString msg = formatMsg( _( "(%s clearance %s; actual %s)" ),
constraint.GetName(),
clearance,
actual );
drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg );
drce->SetItems( item );
drce->SetViolatingRule( constraint.GetParentRule() );
reportViolation( drce, pos, layer );
}
}
return true;
} );
}
bool isNullAperture( BOARD_ITEM* aItem )
{
if( aItem->Type() == PCB_PAD_T )
{
PAD* pad = static_cast<PAD*>( aItem );
if( pad->GetAttribute() == PAD_ATTRIB::NPTH
&& ( pad->GetShape() == PAD_SHAPE::CIRCLE || pad->GetShape() == PAD_SHAPE::OVAL )
&& pad->GetSize().x <= pad->GetDrillSize().x
&& pad->GetSize().y <= pad->GetDrillSize().y )
{
return true;
}
}
return false;
}
// Simple mask apertures aren't associated with copper items, so they only constitute a bridge
// when they expose other copper items having at least two distinct nets. We use a map to record
// the first net exposed by each mask aperture (on each copper layer).
//
// Note that this algorithm is also used for free pads.
bool isMaskAperture( BOARD_ITEM* aItem )
{
if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->IsFreePad() )
return true;
static const LSET saved( 2, F_Mask, B_Mask );
LSET maskLayers = aItem->GetLayerSet() & saved;
LSET otherLayers = aItem->GetLayerSet() & ~saved;
return maskLayers.count() > 0 && otherLayers.count() == 0;
}
bool DRC_TEST_PROVIDER_SOLDER_MASK::checkMaskAperture( BOARD_ITEM* aMaskItem, BOARD_ITEM* aTestItem,
PCB_LAYER_ID aTestLayer, int aTestNet,
BOARD_ITEM** aCollidingItem )
{
if( aTestLayer == F_Mask && !aTestItem->IsOnLayer( F_Cu ) )
return false;
if( aTestLayer == B_Mask && !aTestItem->IsOnLayer( B_Cu ) )
return false;
FOOTPRINT* fp = static_cast<FOOTPRINT*>( aMaskItem->GetParentFootprint() );
if( fp && ( fp->GetAttributes() & FP_ALLOW_SOLDERMASK_BRIDGES ) > 0 )
{
// Mask apertures in footprints which allow soldermask bridges are ignored entirely.
return false;
}
PTR_LAYER_CACHE_KEY key = { aMaskItem, aTestLayer };
auto ii = m_maskApertureNetMap.find( key );
if( ii == m_maskApertureNetMap.end() )
{
m_maskApertureNetMap[ key ] = { aTestItem, aTestNet };
// First net; no bridge yet....
return false;
}
if( ii->second.second == aTestNet && aTestNet > 0 )
{
// Same net; still no bridge...
return false;
}
*aCollidingItem = ii->second.first;
return true;
}
bool DRC_TEST_PROVIDER_SOLDER_MASK::checkItemMask( BOARD_ITEM* aMaskItem, int aTestNet )
{
FOOTPRINT* fp = static_cast<FOOTPRINT*>( aMaskItem->GetParentFootprint() );
wxCHECK( fp, false );
if( ( fp->GetAttributes() & FP_ALLOW_SOLDERMASK_BRIDGES ) > 0 )
{
// If we're allowing bridges then we're allowing bridges. Nothing to check.
return false;
}
// Graphic items are used to implement net-ties between pads of a group within a net-tie
// footprint. They must be allowed to intrude into their pad's mask aperture.
if( aTestNet < 0 && aMaskItem->Type() == PCB_PAD_T && fp->IsNetTie() )
{
std::map<wxString, int> padNumberToGroupIdxMap = fp->MapPadNumbersToNetTieGroups();
if( padNumberToGroupIdxMap[ static_cast<PAD*>( aMaskItem )->GetNumber() ] >= 0 )
return false;
}
return true;
}
void DRC_TEST_PROVIDER_SOLDER_MASK::testItemAgainstItems( BOARD_ITEM* aItem, const BOX2I& aItemBBox,
PCB_LAYER_ID aRefLayer,
PCB_LAYER_ID aTargetLayer )
{
int itemNet = -1;
if( aItem->IsConnected() )
itemNet = static_cast<BOARD_CONNECTED_ITEM*>( aItem )->GetNetCode();
BOARD_DESIGN_SETTINGS& bds = aItem->GetBoard()->GetDesignSettings();
PAD* pad = dynamic_cast<PAD*>( aItem );
PCB_VIA* via = dynamic_cast<PCB_VIA*>( aItem );
std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aRefLayer );
m_itemTree->QueryColliding( aItem, aRefLayer, aTargetLayer,
// Filter:
[&]( BOARD_ITEM* other ) -> bool
{
FOOTPRINT* itemFP = static_cast<FOOTPRINT*>( aItem->GetParentFootprint() );
PAD* otherPad = dynamic_cast<PAD*>( other );
int otherNet = -1;
if( other->IsConnected() )
otherNet = static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode();
if( otherNet > 0 && otherNet == itemNet )
return false;
if( isNullAperture( other ) )
return false;
if( itemFP && itemFP == other->GetParentFootprint() )
{
// Board-wide exclusion
if( bds.m_AllowSoldermaskBridgesInFPs )
return false;
// Footprint-specific exclusion
if( ( itemFP->GetAttributes() & FP_ALLOW_SOLDERMASK_BRIDGES ) > 0 )
return false;
}
if( pad && otherPad && ( pad->SameLogicalPadAs( otherPad )
|| pad->SharesNetTieGroup( otherPad ) ) )
{
return false;
}
BOARD_ITEM* a = aItem;
BOARD_ITEM* b = other;
// store canonical order so we don't collide in both directions (a:b and b:a)
if( static_cast<void*>( a ) > static_cast<void*>( b ) )
std::swap( a, b );
auto it = m_checkedPairs.find( { a, b } );
if( it != m_checkedPairs.end() && it->second.test( aTargetLayer ) )
{
return false;
}
else
{
m_checkedPairs[ { a, b } ].set( aTargetLayer );
return true;
}
},
// Visitor:
[&]( BOARD_ITEM* other ) -> bool
{
PAD* otherPad = dynamic_cast<PAD*>( other );
PCB_VIA* otherVia = dynamic_cast<PCB_VIA*>( other );
auto otherShape = other->GetEffectiveShape( aTargetLayer );
int otherNet = -1;
if( other->IsConnected() )
otherNet = static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode();
int actual;
VECTOR2I pos;
int clearance = 0;
if( aRefLayer == F_Mask || aRefLayer == B_Mask )
{
// Aperture-to-aperture must enforce web-min-width
clearance = m_webWidth;
}
else // ( aRefLayer == F_Cu || aRefLayer == B_Cu )
{
// Copper-to-aperture uses the solder-mask-to-copper-clearance
clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance;
}
if( pad )
clearance += pad->GetSolderMaskExpansion();
else if( via && !via->IsTented() )
clearance += via->GetSolderMaskExpansion();
if( otherPad )
clearance += otherPad->GetSolderMaskExpansion();
else if( otherVia && !otherVia->IsTented() )
clearance += otherVia->GetSolderMaskExpansion();
if( itemShape->Collide( otherShape.get(), clearance, &actual, &pos ) )
{
wxString msg;
BOARD_ITEM* colliding = nullptr;
if( aTargetLayer == F_Mask )
msg = _( "Front solder mask aperture bridges items with different nets" );
else
msg = _( "Rear solder mask aperture bridges items with different nets" );
// Simple mask apertures aren't associated with copper items, so they only
// constitute a bridge when they expose other copper items having at least
// two distinct nets.
if( isMaskAperture( aItem ) )
{
if( checkMaskAperture( aItem, other, aRefLayer, otherNet, &colliding ) )
{
auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
drce->SetErrorMessage( msg );
drce->SetItems( aItem, colliding, other );
drce->SetViolatingRule( &m_bridgeRule );
reportViolation( drce, pos, aTargetLayer );
}
}
else if( isMaskAperture( other ) )
{
if( checkMaskAperture( other, aItem, aRefLayer, itemNet, &colliding ) )
{
auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
drce->SetErrorMessage( msg );
drce->SetItems( other, colliding, aItem );
drce->SetViolatingRule( &m_bridgeRule );
reportViolation( drce, pos, aTargetLayer );
}
}
else if( checkItemMask( other, itemNet ) )
{
auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
drce->SetErrorMessage( msg );
drce->SetItems( aItem, other );
drce->SetViolatingRule( &m_bridgeRule );
reportViolation( drce, pos, aTargetLayer );
}
}
return !m_drcEngine->IsCancelled();
},
m_largestClearance );
}
void DRC_TEST_PROVIDER_SOLDER_MASK::testMaskItemAgainstZones( BOARD_ITEM* aItem,
const BOX2I& aItemBBox,
PCB_LAYER_ID aMaskLayer,
PCB_LAYER_ID aTargetLayer )
{
for( ZONE* zone : m_board->m_DRCCopperZones )
{
if( !zone->GetLayerSet().test( aTargetLayer ) )
continue;
int zoneNet = zone->GetNetCode();
if( aItem->IsConnected() )
{
BOARD_CONNECTED_ITEM* connectedItem = static_cast<BOARD_CONNECTED_ITEM*>( aItem );
if( zoneNet == connectedItem->GetNetCode() && zoneNet > 0 )
continue;
}
BOX2I inflatedBBox( aItemBBox );
int clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance;
if( aItem->Type() == PCB_PAD_T )
clearance += static_cast<PAD*>( aItem )->GetSolderMaskExpansion();
else if( aItem->Type() == PCB_VIA_T )
clearance += static_cast<PCB_VIA*>( aItem )->GetSolderMaskExpansion();
inflatedBBox.Inflate( clearance );
if( !inflatedBBox.Intersects( zone->GetBoundingBox() ) )
continue;
DRC_RTREE* zoneTree = m_board->m_CopperZoneRTreeCache[ zone ].get();
int actual;
VECTOR2I pos;
std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aMaskLayer );
if( zoneTree && zoneTree->QueryColliding( aItemBBox, itemShape.get(), aTargetLayer,
clearance, &actual, &pos ) )
{
wxString msg;
BOARD_ITEM* colliding = nullptr;
if( aMaskLayer == F_Mask )
msg = _( "Front solder mask aperture bridges items with different nets" );
else
msg = _( "Rear solder mask aperture bridges items with different nets" );
// Simple mask apertures aren't associated with copper items, so they only constitute
// a bridge when they expose other copper items having at least two distinct nets.
if( isMaskAperture( aItem ) && zoneNet >= 0 )
{
if( checkMaskAperture( aItem, zone, aTargetLayer, zoneNet, &colliding ) )
{
auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
drce->SetErrorMessage( msg );
drce->SetItems( aItem, colliding, zone );
drce->SetViolatingRule( &m_bridgeRule );
reportViolation( drce, pos, aTargetLayer );
}
}
else
{
auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
drce->SetErrorMessage( msg );
drce->SetItems( aItem, zone );
drce->SetViolatingRule( &m_bridgeRule );
reportViolation( drce, pos, aTargetLayer );
}
}
if( m_drcEngine->IsCancelled() )
return;
}
}
void DRC_TEST_PROVIDER_SOLDER_MASK::testMaskBridges()
{
LSET copperAndMaskLayers = { 4, F_Mask, B_Mask, F_Cu, B_Cu };
const size_t progressDelta = 250;
int count = 0;
int ii = 0;
forEachGeometryItem( s_allBasicItemsButZones, copperAndMaskLayers,
[&]( BOARD_ITEM* item ) -> bool
{
++count;
return true;
} );
forEachGeometryItem( s_allBasicItemsButZones, copperAndMaskLayers,
[&]( BOARD_ITEM* item ) -> bool
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
return false;
if( !reportProgress( ii++, count, progressDelta ) )
return false;
BOX2I itemBBox = item->GetBoundingBox();
if( item->IsOnLayer( F_Mask ) && !isNullAperture( item ) )
{
// Test for aperture-to-aperture collisions
testItemAgainstItems( item, itemBBox, F_Mask, F_Mask );
// Test for aperture-to-zone collisions
testMaskItemAgainstZones( item, itemBBox, F_Mask, F_Cu );
}
else if( item->IsOnLayer( F_Cu ) )
{
// Test for copper-item-to-aperture collisions
testItemAgainstItems( item, itemBBox, F_Cu, F_Mask );
}
if( item->IsOnLayer( B_Mask ) && !isNullAperture( item ) )
{
// Test for aperture-to-aperture collisions
testItemAgainstItems( item, itemBBox, B_Mask, B_Mask );
// Test for aperture-to-zone collisions
testMaskItemAgainstZones( item, itemBBox, B_Mask, B_Cu );
}
else if( item->IsOnLayer( B_Cu ) )
{
// Test for copper-item-to-aperture collisions
testItemAgainstItems( item, itemBBox, B_Cu, B_Mask );
}
return true;
} );
}
bool DRC_TEST_PROVIDER_SOLDER_MASK::Run()
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_CLEARANCE )
&& m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
{
reportAux( wxT( "Solder mask violations ignored. Tests not run." ) );
return true; // continue with other tests
}
m_board = m_drcEngine->GetBoard();
m_webWidth = m_board->GetDesignSettings().m_SolderMaskMinWidth;
m_maxError = m_board->GetDesignSettings().m_MaxError;
m_largestClearance = 0;
for( FOOTPRINT* footprint : m_board->Footprints() )
{
for( PAD* pad : footprint->Pads() )
m_largestClearance = std::max( m_largestClearance, pad->GetSolderMaskExpansion() );
}
// Order is important here: m_webWidth must be added in before m_largestCourtyardClearance is
// maxed with the various SILK_CLEARANCE_CONSTRAINTS.
m_largestClearance += m_largestClearance + m_webWidth;
DRC_CONSTRAINT worstClearanceConstraint;
if( m_drcEngine->QueryWorstConstraint( SILK_CLEARANCE_CONSTRAINT, worstClearanceConstraint ) )
m_largestClearance = std::max( m_largestClearance, worstClearanceConstraint.m_Value.Min() );
reportAux( wxT( "Worst clearance : %d nm" ), m_largestClearance );
if( !reportPhase( _( "Building solder mask..." ) ) )
return false; // DRC cancelled
m_checkedPairs.clear();
m_maskApertureNetMap.clear();
buildRTrees();
if( !reportPhase( _( "Checking solder mask to silk clearance..." ) ) )
return false; // DRC cancelled
testSilkToMaskClearance();
if( !reportPhase( _( "Checking solder mask web integrity..." ) ) )
return false; // DRC cancelled
testMaskBridges();
reportRuleStatistics();
return !m_drcEngine->IsCancelled();
}
namespace detail
{
static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_SOLDER_MASK> dummy;
}