Browse Source
Solder mask integrity testing.
Solder mask integrity testing.
ADDED DRC test for solder mask aperture bridging copper from different nets. ADDED visualization of minimum web width processing for solder masks. ADDED allow_soldermask_bridges property for footprints. Fixes https://gitlab.com/kicad/code/kicad/issues/2183 Fixes https://gitlab.com/kicad/code/kicad/issues/17927.0
50 changed files with 3192 additions and 590 deletions
-
1common/pcb.keywords
-
8common/view/view.cpp
-
3include/pcb_base_frame.h
-
5include/view/view.h
-
2pcbnew/CMakeLists.txt
-
14pcbnew/board.cpp
-
2pcbnew/board.h
-
10pcbnew/board_commit.cpp
-
2pcbnew/board_design_settings.cpp
-
4pcbnew/dialogs/dialog_footprint_properties.cpp
-
60pcbnew/dialogs/dialog_footprint_properties_base.cpp
-
229pcbnew/dialogs/dialog_footprint_properties_base.fbp
-
6pcbnew/dialogs/dialog_footprint_properties_base.h
-
4pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp
-
48pcbnew/dialogs/dialog_footprint_properties_fp_editor_base.cpp
-
214pcbnew/dialogs/dialog_footprint_properties_fp_editor_base.fbp
-
2pcbnew/dialogs/dialog_footprint_properties_fp_editor_base.h
-
34pcbnew/dialogs/dialog_pad_properties_base.cpp
-
42pcbnew/dialogs/dialog_pad_properties_base.fbp
-
49pcbnew/dialogs/panel_setup_mask_and_paste_base.cpp
-
222pcbnew/dialogs/panel_setup_mask_and_paste_base.fbp
-
6pcbnew/dialogs/panel_setup_mask_and_paste_base.h
-
16pcbnew/drc/drc_item.cpp
-
7pcbnew/drc/drc_item.h
-
20pcbnew/drc/drc_rtree.h
-
6pcbnew/drc/drc_test_provider_edge_clearance.cpp
-
209pcbnew/drc/drc_test_provider_silk_to_mask.cpp
-
572pcbnew/drc/drc_test_provider_solder_mask.cpp
-
13pcbnew/footprint.h
-
23pcbnew/pcb_base_frame.cpp
-
8pcbnew/plugins/kicad/pcb_parser.cpp
-
3pcbnew/plugins/kicad/pcb_plugin.cpp
-
3pcbnew/plugins/kicad/pcb_plugin.h
-
5pcbnew/tools/drc_tool.cpp
-
2pcbnew/tools/edit_tool.cpp
-
6pcbnew/zone.h
-
4qa/data/issue1358.kicad_pcb
-
16qa/data/issue3812.kicad_pro
-
58qa/data/issue5320.kicad_pro
-
55qa/data/issue5567.kicad_pro
-
3qa/data/issue5990.kicad_pro
-
27qa/data/issue6260.kicad_pro
-
3qa/data/issue7325.kicad_pro
-
15qa/data/issue7975.kicad_pro
-
1156qa/data/solder_mask_bridge_test.kicad_pcb
-
497qa/data/solder_mask_bridge_test.kicad_pro
-
2qa/drc_proto/CMakeLists.txt
-
1qa/pcbnew/CMakeLists.txt
-
83qa/pcbnew/drc/test_solder_mask_bridging.cpp
-
2qa/pns/CMakeLists.txt
@ -1,209 +0,0 @@ |
|||
/*
|
|||
* This program source code file is part of KiCad, a free EDA CAD application. |
|||
* |
|||
* Copyright (C) 2004-2020 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.h>
|
|||
|
|||
#include <geometry/seg.h>
|
|||
#include <geometry/shape_segment.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>
|
|||
|
|||
/*
|
|||
Silk to pads clearance test. Check all pads against silkscreen (mask opening in the pad vs silkscreen) |
|||
Errors generated: |
|||
- DRCE_SILK_MASK_CLEARANCE |
|||
*/ |
|||
|
|||
class DRC_TEST_PROVIDER_SILK_TO_MASK : public ::DRC_TEST_PROVIDER |
|||
{ |
|||
public: |
|||
DRC_TEST_PROVIDER_SILK_TO_MASK (): |
|||
m_board( nullptr ), |
|||
m_largestClearance( 0 ) |
|||
{ |
|||
} |
|||
|
|||
virtual ~DRC_TEST_PROVIDER_SILK_TO_MASK() |
|||
{ |
|||
} |
|||
|
|||
virtual bool Run() override; |
|||
|
|||
virtual const wxString GetName() const override |
|||
{ |
|||
return "silk_to_mask"; |
|||
}; |
|||
|
|||
virtual const wxString GetDescription() const override |
|||
{ |
|||
return "Tests for silkscreen being clipped by solder mask"; |
|||
} |
|||
|
|||
virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override; |
|||
|
|||
private: |
|||
|
|||
BOARD* m_board; |
|||
int m_largestClearance; |
|||
}; |
|||
|
|||
|
|||
bool DRC_TEST_PROVIDER_SILK_TO_MASK::Run() |
|||
{ |
|||
m_board = m_drcEngine->GetBoard(); |
|||
|
|||
if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_MASK_CLEARANCE ) ) |
|||
{ |
|||
reportAux( "Silkscreen clipping violations ignored. Tests not run." ); |
|||
return true; // continue with other tests
|
|||
} |
|||
|
|||
DRC_CONSTRAINT worstClearanceConstraint; |
|||
m_largestClearance = 0; |
|||
|
|||
if( m_drcEngine->QueryWorstConstraint( SILK_CLEARANCE_CONSTRAINT, worstClearanceConstraint ) ) |
|||
m_largestClearance = worstClearanceConstraint.m_Value.Min(); |
|||
|
|||
reportAux( "Worst clearance : %d nm", m_largestClearance ); |
|||
|
|||
if( !reportPhase( _( "Checking silkscreen for potential soldermask clipping..." ) ) ) |
|||
return false; // DRC cancelled
|
|||
|
|||
DRC_RTREE maskTree, silkTree; |
|||
|
|||
auto addMaskToTree = |
|||
[&maskTree]( BOARD_ITEM *item ) -> bool |
|||
{ |
|||
for( PCB_LAYER_ID layer : { F_Mask, B_Mask } ) |
|||
{ |
|||
if( item->IsOnLayer( layer ) ) |
|||
maskTree.Insert( item, layer ); |
|||
} |
|||
|
|||
return true; |
|||
}; |
|||
|
|||
auto addSilkToTree = |
|||
[&silkTree]( BOARD_ITEM *item ) -> bool |
|||
{ |
|||
for( PCB_LAYER_ID layer : { F_SilkS, B_SilkS } ) |
|||
{ |
|||
if( item->IsOnLayer( layer ) ) |
|||
silkTree.Insert( item, layer ); |
|||
} |
|||
|
|||
return true; |
|||
}; |
|||
|
|||
auto checkClearance = |
|||
[&]( const DRC_RTREE::LAYER_PAIR& aLayers, DRC_RTREE::ITEM_WITH_SHAPE* aRefItem, |
|||
DRC_RTREE::ITEM_WITH_SHAPE* aTestItem, bool* aCollisionDetected ) -> bool |
|||
{ |
|||
if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_MASK_CLEARANCE ) ) |
|||
return false; |
|||
|
|||
if( isInvisibleText( aRefItem->parent ) ) |
|||
return true; |
|||
|
|||
if( isInvisibleText( aTestItem->parent ) ) |
|||
return true; |
|||
|
|||
auto constraint = m_drcEngine->EvalRules( SILK_CLEARANCE_CONSTRAINT, |
|||
aRefItem->parent, aTestItem->parent, |
|||
aLayers.first ); |
|||
|
|||
int minClearance = constraint.GetValue().Min(); |
|||
|
|||
if( minClearance < 0 ) |
|||
return true; |
|||
|
|||
int actual; |
|||
VECTOR2I pos; |
|||
|
|||
if( aRefItem->shape->Collide( aTestItem->shape, minClearance, &actual, &pos ) ) |
|||
{ |
|||
std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SILK_MASK_CLEARANCE ); |
|||
|
|||
if( minClearance > 0 ) |
|||
{ |
|||
m_msg.Printf( _( "(%s clearance %s; actual %s)" ), |
|||
constraint.GetName(), |
|||
MessageTextFromValue( userUnits(), minClearance ), |
|||
MessageTextFromValue( userUnits(), actual ) ); |
|||
|
|||
drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); |
|||
} |
|||
|
|||
drce->SetItems( aRefItem->parent, aTestItem->parent ); |
|||
drce->SetViolatingRule( constraint.GetParentRule() ); |
|||
|
|||
reportViolation( drce, (wxPoint) pos ); |
|||
|
|||
*aCollisionDetected = true; |
|||
} |
|||
|
|||
return true; |
|||
}; |
|||
|
|||
int numMask = forEachGeometryItem( s_allBasicItems, LSET( 2, F_Mask, B_Mask ), addMaskToTree ); |
|||
int numSilk = forEachGeometryItem( s_allBasicItems, LSET( 2, F_SilkS, B_SilkS ), addSilkToTree ); |
|||
|
|||
reportAux( _("Testing %d mask apertures against %d silkscreen features."), numMask, numSilk ); |
|||
|
|||
const std::vector<DRC_RTREE::LAYER_PAIR> layerPairs = |
|||
{ |
|||
DRC_RTREE::LAYER_PAIR( F_SilkS, F_Mask ), |
|||
DRC_RTREE::LAYER_PAIR( B_SilkS, B_Mask ) |
|||
}; |
|||
|
|||
// This is the number of tests between 2 calls to the progress bar
|
|||
const int delta = 250; |
|||
|
|||
maskTree.QueryCollidingPairs( &silkTree, layerPairs, checkClearance, m_largestClearance, |
|||
[&]( int aCount, int aSize ) -> bool |
|||
{ |
|||
return reportProgress( aCount, aSize, delta ); |
|||
} ); |
|||
|
|||
reportRuleStatistics(); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
|
|||
std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_SILK_TO_MASK::GetConstraintTypes() const |
|||
{ |
|||
return { SILK_CLEARANCE_CONSTRAINT }; |
|||
} |
|||
|
|||
|
|||
namespace detail |
|||
{ |
|||
static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_SILK_TO_MASK> dummy; |
|||
} |
|||
@ -0,0 +1,572 @@ |
|||
/*
|
|||
* This program source code file is part of KiCad, a free EDA CAD application. |
|||
* |
|||
* Copyright (C) 2004-2020 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 <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_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 "solder_mask_issues"; |
|||
}; |
|||
|
|||
virtual const wxString GetDescription() const override |
|||
{ |
|||
return "Tests for silkscreen being clipped by solder mask and copper being exposed by " |
|||
"mask apertures of other nets"; |
|||
} |
|||
|
|||
virtual std::set<DRC_CONSTRAINT_T> GetConstraintTypes() const override; |
|||
|
|||
private: |
|||
void addItemToRTrees( BOARD_ITEM* item ); |
|||
void buildRTrees(); |
|||
|
|||
void testSilkToMaskClearance(); |
|||
void testMaskBridges(); |
|||
|
|||
void testItemAgainstItems( BOARD_ITEM* aItem, const EDA_RECT& aItemBBox, |
|||
PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer ); |
|||
void testMaskItemAgainstZones( BOARD_ITEM* item, const EDA_RECT& itemBBox, |
|||
PCB_LAYER_ID refLayer, PCB_LAYER_ID targetLayer ); |
|||
|
|||
private: |
|||
DRC_RULE m_bridgeRule; |
|||
|
|||
BOARD* m_board; |
|||
int m_webWidth; |
|||
int m_maxError; |
|||
int m_largestClearance; |
|||
|
|||
std::unique_ptr<DRC_RTREE> m_tesselatedTree; |
|||
std::unique_ptr<DRC_RTREE> m_itemTree; |
|||
std::vector<ZONE*> m_copperZones; |
|||
|
|||
std::map< std::tuple<BOARD_ITEM*, BOARD_ITEM*, PCB_LAYER_ID>, int> m_checkedPairs; |
|||
}; |
|||
|
|||
|
|||
void DRC_TEST_PROVIDER_SOLDER_MASK::addItemToRTrees( BOARD_ITEM* item ) |
|||
{ |
|||
ZONE* solderMask = m_board->m_SolderMask; |
|||
|
|||
if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T ) |
|||
{ |
|||
ZONE* zone = static_cast<ZONE*>( item ); |
|||
|
|||
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 ); |
|||
} |
|||
} |
|||
|
|||
if( ( zone->GetLayerSet() & LSET::AllCuMask() ).any() && !zone->GetIsRuleArea() ) |
|||
m_copperZones.push_back( zone ); |
|||
} |
|||
else if( item->Type() == PCB_PAD_T ) |
|||
{ |
|||
for( PCB_LAYER_ID layer : { F_Mask, B_Mask } ) |
|||
{ |
|||
if( item->IsOnLayer( layer ) ) |
|||
{ |
|||
PAD* pad = static_cast<PAD*>( item ); |
|||
int clearance = ( m_webWidth / 2 ) + pad->GetSolderMaskMargin(); |
|||
|
|||
item->TransformShapeWithClearanceToPolygon( *solderMask->GetFill( layer ), F_Cu, |
|||
clearance, m_maxError, ERROR_OUTSIDE ); |
|||
|
|||
m_itemTree->Insert( item, layer, m_largestClearance ); |
|||
} |
|||
} |
|||
} |
|||
else if( item->Type() == PCB_VIA_T ) |
|||
{ |
|||
// JEY TODO: if( !aPlotOpt.GetPlotViaOnMaskLayer()
|
|||
// continue;
|
|||
|
|||
// Use the global mask clearance for vias
|
|||
int clearance = ( m_webWidth / 2 ) + m_board->GetDesignSettings().m_SolderMaskMargin; |
|||
|
|||
if( item->IsOnLayer( F_Cu ) ) |
|||
{ |
|||
item->TransformShapeWithClearanceToPolygon( *solderMask->GetFill( F_Mask ), F_Cu, |
|||
clearance, m_maxError, ERROR_OUTSIDE ); |
|||
|
|||
m_itemTree->Insert( item, F_Mask, F_Cu, m_largestClearance ); |
|||
} |
|||
|
|||
if( item->IsOnLayer( B_Cu ) ) |
|||
{ |
|||
item->TransformShapeWithClearanceToPolygon( *solderMask->GetFill( B_Mask ), B_Cu, |
|||
clearance, m_maxError, ERROR_OUTSIDE ); |
|||
|
|||
m_itemTree->Insert( item, B_Mask, B_Cu, m_largestClearance ); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// JEY TODO: plotter doesn't currently expand graphics by web thickness...
|
|||
|
|||
for( PCB_LAYER_ID layer : { F_Mask, B_Mask } ) |
|||
{ |
|||
if( item->IsOnLayer( layer ) ) |
|||
{ |
|||
item->TransformShapeWithClearanceToPolygon( *solderMask->GetFill( layer ), |
|||
layer, m_webWidth / 2, m_maxError, |
|||
ERROR_OUTSIDE ); |
|||
|
|||
m_itemTree->Insert( item, 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 }; |
|||
|
|||
size_t delta = 50; // Number of tests between 2 calls to the progress bar
|
|||
int itemCount = 0; |
|||
int itemIdx = 0; |
|||
|
|||
solderMask->GetFill( F_Mask )->RemoveAllContours(); |
|||
solderMask->GetFill( B_Mask )->RemoveAllContours(); |
|||
|
|||
m_tesselatedTree = std::make_unique<DRC_RTREE>(); |
|||
m_itemTree = std::make_unique<DRC_RTREE>(); |
|||
m_copperZones.clear(); |
|||
|
|||
forEachGeometryItem( s_allBasicItems, layers, |
|||
[&]( BOARD_ITEM* item ) -> bool |
|||
{ |
|||
++itemCount; |
|||
return true; |
|||
} ); |
|||
|
|||
forEachGeometryItem( s_allBasicItems, layers, |
|||
[&]( BOARD_ITEM* item ) -> bool |
|||
{ |
|||
if( !reportProgress( itemIdx++, itemCount, delta ) ) |
|||
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, 360.0 ); |
|||
|
|||
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_tesselatedTree->Insert( solderMask, F_Mask ); |
|||
m_tesselatedTree->Insert( solderMask, B_Mask ); |
|||
|
|||
m_checkedPairs.clear(); |
|||
} |
|||
|
|||
|
|||
void DRC_TEST_PROVIDER_SOLDER_MASK::testSilkToMaskClearance() |
|||
{ |
|||
LSET silkLayers = { 2, F_SilkS, B_SilkS }; |
|||
|
|||
size_t delta = 100; // Number of tests between 2 calls to the progress bar
|
|||
int itemCount = 0; |
|||
int itemIdx = 0; |
|||
|
|||
forEachGeometryItem( s_allBasicItems, silkLayers, |
|||
[&]( BOARD_ITEM* item ) -> bool |
|||
{ |
|||
++itemCount; |
|||
return true; |
|||
} ); |
|||
|
|||
forEachGeometryItem( s_allBasicItems, silkLayers, |
|||
[&]( BOARD_ITEM* item ) -> bool |
|||
{ |
|||
if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_CLEARANCE ) ) |
|||
return false; |
|||
|
|||
if( !reportProgress( itemIdx++, itemCount, delta ) ) |
|||
return false; |
|||
|
|||
if( isInvisibleText( item ) ) |
|||
return true; |
|||
|
|||
for( PCB_LAYER_ID layer : silkLayers.Seq() ) |
|||
{ |
|||
if( !item->IsOnLayer( layer ) ) |
|||
continue; |
|||
|
|||
EDA_RECT 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( clearance <= 0 ) |
|||
return true; |
|||
|
|||
std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape( layer ); |
|||
|
|||
if( m_tesselatedTree->QueryColliding( itemBBox, itemShape.get(), layer, |
|||
clearance, &actual, &pos ) ) |
|||
{ |
|||
auto drce = DRC_ITEM::Create( DRCE_SILK_CLEARANCE ); |
|||
|
|||
m_msg.Printf( _( "(%s clearance %s; actual %s)" ), |
|||
constraint.GetName(), |
|||
MessageTextFromValue( userUnits(), clearance ), |
|||
MessageTextFromValue( userUnits(), actual ) ); |
|||
|
|||
drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg ); |
|||
drce->SetItems( item ); |
|||
drce->SetViolatingRule( constraint.GetParentRule() ); |
|||
|
|||
reportViolation( drce, (wxPoint) pos ); |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} ); |
|||
} |
|||
|
|||
|
|||
void DRC_TEST_PROVIDER_SOLDER_MASK::testItemAgainstItems( BOARD_ITEM* aItem, |
|||
const EDA_RECT& aItemBBox, |
|||
PCB_LAYER_ID aRefLayer, |
|||
PCB_LAYER_ID aTargetLayer ) |
|||
{ |
|||
int itemNet = -1; |
|||
|
|||
if( aItem->IsConnected() ) |
|||
itemNet = static_cast<BOARD_CONNECTED_ITEM*>( aItem )->GetNetCode(); |
|||
|
|||
PAD* pad = dynamic_cast<PAD*>( aItem ); |
|||
std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aRefLayer ); |
|||
|
|||
m_itemTree->QueryColliding( aItem, aRefLayer, aTargetLayer, |
|||
// Filter:
|
|||
[&]( BOARD_ITEM* other ) -> bool |
|||
{ |
|||
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( pad && otherPad && pad->GetParent() == otherPad->GetParent() ) |
|||
{ |
|||
if( pad->GetParent()->GetAttributes() & FP_ALLOW_SOLDERMASK_BRIDGES ) |
|||
return false; |
|||
else if( pad->SameLogicalPadAs( 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 ); |
|||
|
|||
if( m_checkedPairs.count( { a, b, aTargetLayer } ) ) |
|||
{ |
|||
return false; |
|||
} |
|||
else |
|||
{ |
|||
m_checkedPairs[ { a, b, aTargetLayer } ] = 1; |
|||
return true; |
|||
// return aItemBBox.Intersects( other->GetBoundingBox() );
|
|||
} |
|||
}, |
|||
// Visitor:
|
|||
[&]( BOARD_ITEM* other ) -> bool |
|||
{ |
|||
PAD* otherPad = dynamic_cast<PAD*>( other ); |
|||
auto otherShape = other->GetEffectiveShape( aTargetLayer ); |
|||
int actual; |
|||
VECTOR2I pos; |
|||
int clearance = 0; // JEY TODO: probably need a board setting for mask registration?
|
|||
|
|||
if( pad ) |
|||
clearance += m_webWidth / 2 + pad->GetSolderMaskMargin(); |
|||
|
|||
if( otherPad ) |
|||
clearance += m_webWidth / 2 + otherPad->GetSolderMaskMargin(); |
|||
|
|||
if( itemShape->Collide( otherShape.get(), clearance, &actual, &pos ) ) |
|||
{ |
|||
auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE ); |
|||
|
|||
if( aTargetLayer == F_Mask ) |
|||
{ |
|||
drce->SetErrorMessage( _( "Front solder mask aperture bridges items with " |
|||
"different nets" ) ); |
|||
} |
|||
else |
|||
{ |
|||
drce->SetErrorMessage( _( "Rear solder mask aperture bridges items with " |
|||
"different nets" ) ); |
|||
} |
|||
|
|||
drce->SetItems( aItem, other ); |
|||
drce->SetViolatingRule( &m_bridgeRule ); |
|||
reportViolation( drce, (wxPoint) pos ); |
|||
} |
|||
|
|||
return true; |
|||
}, |
|||
m_largestClearance ); |
|||
} |
|||
|
|||
|
|||
void DRC_TEST_PROVIDER_SOLDER_MASK::testMaskItemAgainstZones( BOARD_ITEM* aItem, |
|||
const EDA_RECT& aItemBBox, |
|||
PCB_LAYER_ID aMaskLayer, |
|||
PCB_LAYER_ID aTargetLayer ) |
|||
{ |
|||
for( ZONE* zone : m_copperZones ) |
|||
{ |
|||
if( !zone->GetLayerSet().test( aTargetLayer ) ) |
|||
continue; |
|||
|
|||
if( zone->GetNetCode() && aItem->IsConnected() ) |
|||
{ |
|||
if( zone->GetNetCode() == static_cast<BOARD_CONNECTED_ITEM*>( aItem )->GetNetCode() ) |
|||
continue; |
|||
} |
|||
|
|||
if( aItem->GetBoundingBox().Intersects( zone->GetCachedBoundingBox() ) ) |
|||
{ |
|||
DRC_RTREE* zoneTree = m_board->m_CopperZoneRTrees[ zone ].get(); |
|||
int clearance = 0; // JEY TODO: probably need a board setting for mask registration?
|
|||
int actual; |
|||
VECTOR2I pos; |
|||
|
|||
std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aMaskLayer ); |
|||
|
|||
if( aItem->Type() == PCB_PAD_T ) |
|||
{ |
|||
PAD* pad = static_cast<PAD*>( aItem ); |
|||
|
|||
clearance += pad->GetSolderMaskMargin(); |
|||
} |
|||
|
|||
if( zoneTree && zoneTree->QueryColliding( aItemBBox, itemShape.get(), aTargetLayer, |
|||
clearance, &actual, &pos ) ) |
|||
{ |
|||
auto drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE ); |
|||
|
|||
if( aMaskLayer == F_Mask ) |
|||
{ |
|||
drce->SetErrorMessage( _( "Front solder mask aperture bridges items with " |
|||
"different nets" ) ); |
|||
} |
|||
else |
|||
{ |
|||
drce->SetErrorMessage( _( "Rear solder mask aperture bridges items with " |
|||
"different nets" ) ); |
|||
} |
|||
|
|||
drce->SetItems( aItem, zone ); |
|||
drce->SetViolatingRule( &m_bridgeRule ); |
|||
reportViolation( drce, (wxPoint) pos ); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
void DRC_TEST_PROVIDER_SOLDER_MASK::testMaskBridges() |
|||
{ |
|||
LSET copperAndMaskLayers = { 4, F_Mask, B_Mask, F_Cu, B_Cu }; |
|||
|
|||
size_t delta = 50; // Number of tests between 2 calls to the progress bar
|
|||
int itemCount = 0; |
|||
int itemIdx = 0; |
|||
|
|||
forEachGeometryItem( s_allBasicItemsButZones, copperAndMaskLayers, |
|||
[&]( BOARD_ITEM* item ) -> bool |
|||
{ |
|||
++itemCount; |
|||
return true; |
|||
} ); |
|||
|
|||
forEachGeometryItem( s_allBasicItemsButZones, copperAndMaskLayers, |
|||
[&]( BOARD_ITEM* item ) -> bool |
|||
{ |
|||
if( m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) ) |
|||
return false; |
|||
|
|||
if( !reportProgress( itemIdx++, itemCount, delta ) ) |
|||
return false; |
|||
|
|||
EDA_RECT itemBBox = item->GetBoundingBox(); |
|||
|
|||
if( item->IsOnLayer( F_Mask ) ) |
|||
{ |
|||
// 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 ) ) |
|||
{ |
|||
// 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( "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->GetSolderMaskMargin() ); |
|||
} |
|||
|
|||
// Order is important here: m_webWidth must be added in before m_largestClearance 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( "Worst clearance : %d nm", m_largestClearance ); |
|||
|
|||
if( !reportPhase( _( "Building solder mask..." ) ) ) |
|||
return false; // DRC cancelled
|
|||
|
|||
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 true; |
|||
} |
|||
|
|||
|
|||
std::set<DRC_CONSTRAINT_T> DRC_TEST_PROVIDER_SOLDER_MASK::GetConstraintTypes() const |
|||
{ |
|||
return { SILK_CLEARANCE_CONSTRAINT }; |
|||
} |
|||
|
|||
|
|||
namespace detail |
|||
{ |
|||
static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_SOLDER_MASK> dummy; |
|||
} |
|||
1156
qa/data/solder_mask_bridge_test.kicad_pcb
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,497 @@ |
|||
{ |
|||
"board": { |
|||
"design_settings": { |
|||
"defaults": { |
|||
"board_outline_line_width": 0.049999999999999996, |
|||
"copper_line_width": 0.19999999999999998, |
|||
"copper_text_italic": false, |
|||
"copper_text_size_h": 1.5, |
|||
"copper_text_size_v": 1.5, |
|||
"copper_text_thickness": 0.3, |
|||
"copper_text_upright": true, |
|||
"courtyard_line_width": 0.049999999999999996, |
|||
"dimension_precision": 1, |
|||
"dimension_units": 2, |
|||
"dimensions": { |
|||
"arrow_length": 1270000, |
|||
"extension_offset": 500000, |
|||
"keep_text_aligned": true, |
|||
"suppress_zeroes": false, |
|||
"text_position": 0, |
|||
"units_format": 1 |
|||
}, |
|||
"fab_line_width": 0.09999999999999999, |
|||
"fab_text_italic": false, |
|||
"fab_text_size_h": 1.0, |
|||
"fab_text_size_v": 1.0, |
|||
"fab_text_thickness": 0.15, |
|||
"fab_text_upright": true, |
|||
"other_line_width": 0.09999999999999999, |
|||
"other_text_italic": false, |
|||
"other_text_size_h": 1.0, |
|||
"other_text_size_v": 1.0, |
|||
"other_text_thickness": 0.15, |
|||
"other_text_upright": true, |
|||
"pads": { |
|||
"drill": 0.8, |
|||
"height": 1.6, |
|||
"width": 2.3 |
|||
}, |
|||
"silk_line_width": 0.12, |
|||
"silk_text_italic": false, |
|||
"silk_text_size_h": 1.0, |
|||
"silk_text_size_v": 1.0, |
|||
"silk_text_thickness": 0.15, |
|||
"silk_text_upright": true, |
|||
"zones": { |
|||
"45_degree_only": false, |
|||
"min_clearance": 0.44999999999999996 |
|||
} |
|||
}, |
|||
"diff_pair_dimensions": [ |
|||
{ |
|||
"gap": 0.0, |
|||
"via_gap": 0.0, |
|||
"width": 0.0 |
|||
} |
|||
], |
|||
"drc_exclusions": [ |
|||
"lib_footprint_issues|154305000|81417500|3bd3a1e0-b35d-4974-99fa-33d3eee5d601|00000000-0000-0000-0000-000000000000" |
|||
], |
|||
"meta": { |
|||
"version": 2 |
|||
}, |
|||
"rule_severities": { |
|||
"annular_width": "error", |
|||
"clearance": "error", |
|||
"copper_edge_clearance": "error", |
|||
"copper_sliver": "warning", |
|||
"courtyards_overlap": "error", |
|||
"diff_pair_gap_out_of_range": "error", |
|||
"diff_pair_uncoupled_length_too_long": "error", |
|||
"drill_out_of_range": "error", |
|||
"duplicate_footprints": "warning", |
|||
"extra_footprint": "warning", |
|||
"hole_clearance": "error", |
|||
"hole_near_hole": "error", |
|||
"invalid_outline": "error", |
|||
"item_on_disabled_layer": "error", |
|||
"items_not_allowed": "error", |
|||
"length_out_of_range": "error", |
|||
"lib_footprint_issues": "error", |
|||
"malformed_courtyard": "error", |
|||
"microvia_drill_out_of_range": "error", |
|||
"missing_courtyard": "ignore", |
|||
"missing_footprint": "warning", |
|||
"net_conflict": "warning", |
|||
"npth_inside_courtyard": "ignore", |
|||
"padstack": "error", |
|||
"pth_inside_courtyard": "ignore", |
|||
"shorting_items": "error", |
|||
"silk_over_copper": "error", |
|||
"silk_overlap": "error", |
|||
"skew_out_of_range": "error", |
|||
"solder_mask_bridge": "error", |
|||
"starved_thermal": "error", |
|||
"text_height": "warning", |
|||
"text_thickness": "warning", |
|||
"too_many_vias": "error", |
|||
"track_dangling": "warning", |
|||
"track_width": "error", |
|||
"tracks_crossing": "error", |
|||
"unconnected_items": "error", |
|||
"unresolved_variable": "error", |
|||
"via_dangling": "warning", |
|||
"zone_has_empty_net": "error", |
|||
"zones_intersect": "error" |
|||
}, |
|||
"rules": { |
|||
"allow_blind_buried_vias": false, |
|||
"allow_microvias": false, |
|||
"max_error": 0.005, |
|||
"min_clearance": 0.0, |
|||
"min_copper_edge_clearance": 0.01, |
|||
"min_hole_clearance": 0.0, |
|||
"min_hole_to_hole": 0.25, |
|||
"min_microvia_diameter": 0.19999999999999998, |
|||
"min_microvia_drill": 0.09999999999999999, |
|||
"min_resolved_spokes": 2, |
|||
"min_silk_clearance": 0.09999999999999999, |
|||
"min_text_height": 0.6, |
|||
"min_text_thickness": 0.09999999999999999, |
|||
"min_through_hole_diameter": 0.3, |
|||
"min_track_width": 0.19999999999999998, |
|||
"min_via_annular_width": 0.049999999999999996, |
|||
"min_via_annulus": 0.049999999999999996, |
|||
"min_via_diameter": 0.39999999999999997, |
|||
"use_height_for_length_calcs": true |
|||
}, |
|||
"track_widths": [ |
|||
0.0, |
|||
0.2, |
|||
0.4, |
|||
1.0, |
|||
2.0 |
|||
], |
|||
"via_dimensions": [ |
|||
{ |
|||
"diameter": 0.0, |
|||
"drill": 0.0 |
|||
} |
|||
], |
|||
"zones_allow_external_fillets": false, |
|||
"zones_use_no_outline": true |
|||
}, |
|||
"layer_presets": [ |
|||
{ |
|||
"activeLayer": -2, |
|||
"layers": [ |
|||
3, |
|||
4, |
|||
5, |
|||
6, |
|||
7, |
|||
8, |
|||
9, |
|||
10, |
|||
11, |
|||
12, |
|||
13, |
|||
14, |
|||
15, |
|||
16, |
|||
17, |
|||
18, |
|||
19, |
|||
20, |
|||
21, |
|||
22, |
|||
23, |
|||
24, |
|||
25, |
|||
26, |
|||
27, |
|||
28, |
|||
29, |
|||
30, |
|||
41, |
|||
44, |
|||
49, |
|||
50 |
|||
], |
|||
"name": "Fabrication", |
|||
"renderLayers": [ |
|||
125, |
|||
126, |
|||
127, |
|||
128, |
|||
129, |
|||
130, |
|||
131, |
|||
133, |
|||
134, |
|||
135, |
|||
136, |
|||
137, |
|||
138, |
|||
139, |
|||
143, |
|||
144 |
|||
] |
|||
} |
|||
] |
|||
}, |
|||
"boards": [], |
|||
"cvpcb": { |
|||
"equivalence_files": [] |
|||
}, |
|||
"erc": { |
|||
"meta": { |
|||
"version": 0 |
|||
}, |
|||
"pin_map": [ |
|||
[ |
|||
0, |
|||
0, |
|||
0, |
|||
0, |
|||
0, |
|||
1, |
|||
0, |
|||
0, |
|||
0, |
|||
0, |
|||
2 |
|||
], |
|||
[ |
|||
0, |
|||
2, |
|||
0, |
|||
1, |
|||
0, |
|||
1, |
|||
0, |
|||
2, |
|||
2, |
|||
2, |
|||
2 |
|||
], |
|||
[ |
|||
0, |
|||
0, |
|||
0, |
|||
0, |
|||
0, |
|||
1, |
|||
0, |
|||
1, |
|||
0, |
|||
1, |
|||
2 |
|||
], |
|||
[ |
|||
0, |
|||
1, |
|||
0, |
|||
0, |
|||
0, |
|||
1, |
|||
1, |
|||
2, |
|||
1, |
|||
1, |
|||
2 |
|||
], |
|||
[ |
|||
0, |
|||
0, |
|||
0, |
|||
0, |
|||
0, |
|||
1, |
|||
0, |
|||
0, |
|||
0, |
|||
0, |
|||
2 |
|||
], |
|||
[ |
|||
1, |
|||
1, |
|||
1, |
|||
1, |
|||
1, |
|||
1, |
|||
1, |
|||
1, |
|||
1, |
|||
1, |
|||
2 |
|||
], |
|||
[ |
|||
0, |
|||
0, |
|||
0, |
|||
1, |
|||
0, |
|||
1, |
|||
0, |
|||
0, |
|||
0, |
|||
0, |
|||
2 |
|||
], |
|||
[ |
|||
0, |
|||
2, |
|||
1, |
|||
2, |
|||
0, |
|||
1, |
|||
0, |
|||
2, |
|||
2, |
|||
2, |
|||
2 |
|||
], |
|||
[ |
|||
0, |
|||
2, |
|||
0, |
|||
1, |
|||
0, |
|||
1, |
|||
0, |
|||
2, |
|||
0, |
|||
0, |
|||
2 |
|||
], |
|||
[ |
|||
0, |
|||
2, |
|||
1, |
|||
1, |
|||
0, |
|||
1, |
|||
0, |
|||
2, |
|||
0, |
|||
0, |
|||
2 |
|||
], |
|||
[ |
|||
2, |
|||
2, |
|||
2, |
|||
2, |
|||
2, |
|||
2, |
|||
2, |
|||
2, |
|||
2, |
|||
2, |
|||
2 |
|||
] |
|||
], |
|||
"rule_severities": { |
|||
"bus_definition_conflict": "error", |
|||
"bus_label_syntax": "error", |
|||
"bus_to_bus_conflict": "error", |
|||
"bus_to_net_conflict": "error", |
|||
"different_unit_footprint": "error", |
|||
"different_unit_net": "error", |
|||
"duplicate_sheet_names": "error", |
|||
"global_label_dangling": "error", |
|||
"hier_label_mismatch": "error", |
|||
"label_dangling": "error", |
|||
"lib_symbol_issues": "warning", |
|||
"multiple_net_names": "error", |
|||
"net_not_bus_member": "error", |
|||
"no_connect_connected": "error", |
|||
"no_connect_dangling": "error", |
|||
"pin_not_connected": "error", |
|||
"pin_not_driven": "error", |
|||
"pin_to_pin": "warning", |
|||
"similar_labels": "error", |
|||
"unresolved_variable": "error", |
|||
"wire_dangling": "error" |
|||
} |
|||
}, |
|||
"libraries": { |
|||
"pinned_footprint_libs": [], |
|||
"pinned_symbol_libs": [] |
|||
}, |
|||
"meta": { |
|||
"filename": "LoRaNode.kicad_pro", |
|||
"version": 1 |
|||
}, |
|||
"net_settings": { |
|||
"classes": [ |
|||
{ |
|||
"bus_width": 12.0, |
|||
"clearance": 0.2, |
|||
"diff_pair_gap": 0.25, |
|||
"diff_pair_via_gap": 0.25, |
|||
"diff_pair_width": 0.2, |
|||
"line_style": 0, |
|||
"microvia_diameter": 0.3, |
|||
"microvia_drill": 0.1, |
|||
"name": "Default", |
|||
"pcb_color": "rgba(0, 0, 0, 0.000)", |
|||
"schematic_color": "rgba(0, 0, 0, 0.000)", |
|||
"track_width": 0.25, |
|||
"via_diameter": 0.85, |
|||
"via_drill": 0.4, |
|||
"wire_width": 6.0 |
|||
}, |
|||
{ |
|||
"bus_width": 12.0, |
|||
"clearance": 0.25, |
|||
"diff_pair_gap": 0.25, |
|||
"diff_pair_via_gap": 0.25, |
|||
"diff_pair_width": 0.2, |
|||
"line_style": 0, |
|||
"microvia_diameter": 0.3, |
|||
"microvia_drill": 0.1, |
|||
"name": "antenna", |
|||
"nets": [ |
|||
"/ANTENNA" |
|||
], |
|||
"pcb_color": "rgba(0, 0, 0, 0.000)", |
|||
"schematic_color": "rgba(0, 0, 0, 0.000)", |
|||
"track_width": 1.27, |
|||
"via_diameter": 0.85, |
|||
"via_drill": 0.4, |
|||
"wire_width": 6.0 |
|||
} |
|||
], |
|||
"meta": { |
|||
"version": 1 |
|||
}, |
|||
"net_colors": null |
|||
}, |
|||
"pcbnew": { |
|||
"last_paths": { |
|||
"gencad": "", |
|||
"idf": "", |
|||
"netlist": "", |
|||
"specctra_dsn": "", |
|||
"step": "", |
|||
"vmrl": "", |
|||
"vrml": "" |
|||
}, |
|||
"page_layout_descr_file": "" |
|||
}, |
|||
"schematic": { |
|||
"drawing": { |
|||
"default_bus_thickness": 12.0, |
|||
"default_junction_size": 40.0, |
|||
"default_line_thickness": 6.0, |
|||
"default_text_size": 50.0, |
|||
"default_wire_thickness": 6.0, |
|||
"field_names": [], |
|||
"intersheets_ref_prefix": "[", |
|||
"intersheets_ref_short": false, |
|||
"intersheets_ref_show": false, |
|||
"intersheets_ref_suffix": "]", |
|||
"pin_symbol_size": 25.0, |
|||
"text_offset_ratio": 0.3 |
|||
}, |
|||
"legacy_lib_dir": "", |
|||
"legacy_lib_list": [], |
|||
"meta": { |
|||
"version": 0 |
|||
}, |
|||
"net_format_name": "", |
|||
"page_layout_descr_file": "", |
|||
"plot_directory": "./", |
|||
"spice_adjust_passive_values": false, |
|||
"spice_external_command": "spice \"%I\"", |
|||
"subpart_first_id": 65, |
|||
"subpart_id_separator": 0 |
|||
}, |
|||
"sheets": [ |
|||
[ |
|||
"285a99a7-a1a2-4aa6-812b-d3c3533a57d7", |
|||
"" |
|||
], |
|||
[ |
|||
"00000000-0000-0000-0000-00005d8236a8", |
|||
"battery-protection" |
|||
], |
|||
[ |
|||
"00000000-0000-0000-0000-00005d8260b7", |
|||
"voltage-regulation" |
|||
], |
|||
[ |
|||
"00000000-0000-0000-0000-00005d825820", |
|||
"RS485-interface" |
|||
], |
|||
[ |
|||
"00000000-0000-0000-0000-00005d84b44c", |
|||
"I2C-interface" |
|||
] |
|||
], |
|||
"text_variables": {} |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
/*
|
|||
* This program source code file is part of KiCad, a free EDA CAD application. |
|||
* |
|||
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors. |
|||
* |
|||
* 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 <qa_utils/wx_utils/unit_test_utils.h>
|
|||
#include <qa/pcbnew/board_test_utils.h>
|
|||
#include <board.h>
|
|||
#include <board_design_settings.h>
|
|||
#include <pcb_marker.h>
|
|||
#include <drc/drc_item.h>
|
|||
#include <settings/settings_manager.h>
|
|||
|
|||
|
|||
struct DRC_REGRESSION_TEST_FIXTURE |
|||
{ |
|||
DRC_REGRESSION_TEST_FIXTURE() : |
|||
m_settingsManager( true /* headless */ ) |
|||
{ } |
|||
|
|||
SETTINGS_MANAGER m_settingsManager; |
|||
std::unique_ptr<BOARD> m_board; |
|||
}; |
|||
|
|||
|
|||
BOOST_FIXTURE_TEST_CASE( DRCSolderMaskBridgingTest, DRC_REGRESSION_TEST_FIXTURE ) |
|||
{ |
|||
KI_TEST::LoadBoard( m_settingsManager, "solder_mask_bridge_test", m_board ); |
|||
KI_TEST::FillZones( m_board.get(), 6 ); |
|||
|
|||
std::vector<DRC_ITEM> violations; |
|||
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); |
|||
|
|||
bds.m_DRCEngine->SetViolationHandler( |
|||
[&]( const std::shared_ptr<DRC_ITEM>& aItem, wxPoint aPos ) |
|||
{ |
|||
PCB_MARKER temp( aItem, aPos ); |
|||
|
|||
if( bds.m_DrcExclusions.find( temp.Serialize() ) == bds.m_DrcExclusions.end() ) |
|||
violations.push_back( *aItem ); |
|||
} ); |
|||
|
|||
bds.m_DRCEngine->RunTests( EDA_UNITS::MILLIMETRES, true, false ); |
|||
|
|||
if( violations.size() == 4 ) |
|||
{ |
|||
BOOST_CHECK_EQUAL( 1, 1 ); // quiet "did not check any assertions" warning
|
|||
BOOST_TEST_MESSAGE( "DRC solder mask bridge test passed" ); |
|||
} |
|||
else |
|||
{ |
|||
BOOST_CHECK_EQUAL( violations.size(), 4 ); |
|||
|
|||
std::map<KIID, EDA_ITEM*> itemMap; |
|||
m_board->FillItemMap( itemMap ); |
|||
|
|||
for( const DRC_ITEM& item : violations ) |
|||
{ |
|||
BOOST_TEST_MESSAGE( item.ShowReport( EDA_UNITS::INCHES, RPT_SEVERITY_ERROR, |
|||
itemMap ) ); |
|||
} |
|||
|
|||
BOOST_ERROR( "DRC solder mask bridge test failed" ); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue