Move connection width testing to rule system.

Also copies connection width progress reporting architecture over to
the sliver checker.
This commit is contained in:
Jeff Young 2022-07-31 16:02:04 +01:00
parent 927bc8141b
commit 0304ad4494
17 changed files with 743 additions and 183 deletions

View File

@ -4,6 +4,7 @@ board_edge
buried_via
clearance
condition
connection_width
constraint
courtyard_clearance
diff_pair_gap

View File

@ -186,7 +186,8 @@ BOARD_DESIGN_SETTINGS::BOARD_DESIGN_SETTINGS( JSON_SETTINGS* aParent, const std:
m_DRCSeverities[ DRCE_LIB_FOOTPRINT_ISSUES ] = RPT_SEVERITY_WARNING;
m_DRCSeverities[ DRCE_LIB_FOOTPRINT_MISMATCH ] = RPT_SEVERITY_WARNING;
m_DRCSeverities[ DRCE_CONNECTION_WIDTH ] = RPT_SEVERITY_WARNING;
// TODO: Change to warning after testing
m_DRCSeverities[ DRCE_CONNECTION_WIDTH ] = RPT_SEVERITY_IGNORE;
m_MaxError = ARC_HIGH_DEF;
m_ZoneKeepExternalFillets = false;

View File

@ -577,7 +577,8 @@ void DIALOG_DRC::OnDRCItemRClick( wxDataViewEvent& aEvent )
|| rcItem->GetErrorCode() == DRCE_VIA_DIAMETER
|| rcItem->GetErrorCode() == DRCE_ANNULAR_WIDTH
|| rcItem->GetErrorCode() == DRCE_DRILL_OUT_OF_RANGE
|| rcItem->GetErrorCode() == DRCE_MICROVIA_DRILL_OUT_OF_RANGE )
|| rcItem->GetErrorCode() == DRCE_MICROVIA_DRILL_OUT_OF_RANGE
|| rcItem->GetErrorCode() == DRCE_CONNECTION_WIDTH )
{
menu.Append( 3, _( "Run constraints resolution tool..." ) );
}

View File

@ -368,6 +368,7 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
tokens = wxT( "annular_width|"
"assertion|"
"clearance|"
"connection_width|"
"courtyard_clearance|"
"diff_pair_gap|"
"diff_pair_uncoupled|"

View File

@ -24,6 +24,7 @@
* annular\_width
* clearance
* connection\_width
* courtyard_clearance
* diff\_pair\_gap
* diff\_pair\_uncoupled
@ -272,3 +273,10 @@ For the latter use a `(layer "layer_name")` clause in the rule.
(layer "F.Courtyard")
(constraint physical_clearance (min 3mm))
(condition "B.Layer == 'Edge.Cuts'"))
# Check current-carrying capacity
(rule high-current
(constraint track_width (min 1.0mm))
(constraint connection_width (min 0.8mm))
(condition "A.NetClass == 'Power'"))

View File

@ -25,6 +25,7 @@ _HKI( "### Top-level Clauses\n"
"\n"
" * annular\\_width\n"
" * clearance\n"
" * connection\\_width\n"
" * courtyard_clearance\n"
" * diff\\_pair\\_gap\n"
" * diff\\_pair\\_uncoupled\n"
@ -273,4 +274,10 @@ _HKI( "### Top-level Clauses\n"
" (layer \"F.Courtyard\")\n"
" (constraint physical_clearance (min 3mm))\n"
" (condition \"B.Layer == 'Edge.Cuts'\"))\n"
"" );
"\n"
"\n"
" # Check current-carrying capacity\n"
" (rule high-current\n"
" (constraint track_width (min 1.0mm))\n"
" (constraint connection_width (min 0.8mm))\n"
" (condition \"A.NetClass == 'Power'\"))" );

View File

@ -152,6 +152,10 @@ void DRC_ENGINE::loadImplicitRules()
widthConstraint.Value().SetMin( bds.m_TrackMinWidth );
rule->AddConstraint( widthConstraint );
DRC_CONSTRAINT connectionConstraint( CONNECTION_WIDTH_CONSTRAINT );
connectionConstraint.Value().SetMin( bds.m_MinConn );
rule->AddConstraint( connectionConstraint );
DRC_CONSTRAINT drillConstraint( HOLE_SIZE_CONSTRAINT );
drillConstraint.Value().SetMin( bds.m_MinThroughDrill );
rule->AddConstraint( drillConstraint );
@ -890,6 +894,7 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
case TEXT_THICKNESS_CONSTRAINT:
case DIFF_PAIR_GAP_CONSTRAINT:
case LENGTH_CONSTRAINT:
case CONNECTION_WIDTH_CONSTRAINT:
{
if( aReporter )
{
@ -958,12 +963,8 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO
break;
case TEXT_HEIGHT_CONSTRAINT:
REPORT( wxString::Format( _( "Checking %s: min %s." ),
EscapeHTML( c->constraint.GetName() ),
min ) )
break;
case TEXT_THICKNESS_CONSTRAINT:
case CONNECTION_WIDTH_CONSTRAINT:
REPORT( wxString::Format( _( "Checking %s: min %s." ),
EscapeHTML( c->constraint.GetName() ),
min ) )
@ -1487,10 +1488,15 @@ bool DRC_ENGINE::IsErrorLimitExceeded( int error_code )
void DRC_ENGINE::ReportViolation( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos,
PCB_LAYER_ID aMarkerLayer )
{
static std::mutex globalLock;
m_errorLimits[ aItem->GetErrorCode() ] -= 1;
if( m_violationHandler )
{
std::lock_guard<std::mutex> guard( globalLock );
m_violationHandler( aItem, aPos, aMarkerLayer );
}
if( m_reporter )
{
@ -1605,6 +1611,20 @@ bool DRC_ENGINE::QueryWorstConstraint( DRC_CONSTRAINT_T aConstraintId, DRC_CONST
}
std::set<int> DRC_ENGINE::QueryDistinctConstraints( DRC_CONSTRAINT_T aConstraintId )
{
std::set<int> distinctMinimums;
if( m_constraintMap.count( aConstraintId ) )
{
for( DRC_ENGINE_CONSTRAINT* c : *m_constraintMap[aConstraintId] )
distinctMinimums.emplace( c->constraint.GetValue().Min() );
}
return distinctMinimums;
}
// fixme: move two functions below to pcbcommon?
int DRC_ENGINE::MatchDpSuffix( const wxString& aNetName, wxString& aComplementNet,
wxString& aBaseDpName )

View File

@ -1,7 +1,7 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2019-2021 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2019-2022 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
@ -176,6 +176,7 @@ public:
bool IsCancelled() const;
bool QueryWorstConstraint( DRC_CONSTRAINT_T aRuleId, DRC_CONSTRAINT& aConstraint );
std::set<int> QueryDistinctConstraints( DRC_CONSTRAINT_T aConstraintId );
std::vector<DRC_TEST_PROVIDER*> GetTestProviders() const { return m_testProviders; };

View File

@ -68,7 +68,8 @@ enum DRC_CONSTRAINT_T
VIA_COUNT_CONSTRAINT,
PHYSICAL_CLEARANCE_CONSTRAINT,
PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
ASSERTION_CONSTRAINT
ASSERTION_CONSTRAINT,
CONNECTION_WIDTH_CONSTRAINT
};

View File

@ -313,6 +313,7 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
case T_text_height: c.m_Type = TEXT_HEIGHT_CONSTRAINT; break;
case T_text_thickness: c.m_Type = TEXT_THICKNESS_CONSTRAINT; break;
case T_track_width: c.m_Type = TRACK_WIDTH_CONSTRAINT; break;
case T_connection_width: c.m_Type = CONNECTION_WIDTH_CONSTRAINT; break;
case T_annular_width: c.m_Type = ANNULAR_WIDTH_CONSTRAINT; break;
case T_via_diameter: c.m_Type = VIA_DIAMETER_CONSTRAINT; break;
case T_zone_connection: c.m_Type = ZONE_CONNECTION_CONSTRAINT; break;

View File

@ -37,6 +37,7 @@
#include <drc/drc_item.h>
#include <drc/drc_test_provider.h>
#include <drc/drc_rtree.h>
#include <drc/drc_rule_condition.h>
#include <footprint.h>
#include <geometry/seg.h>
#include <geometry/shape_poly_set.h>
@ -569,19 +570,24 @@ bool DRC_TEST_PROVIDER_CONNECTION_WIDTH::Run()
if( !reportPhase( _( "Checking nets for minimum connection width..." ) ) )
return false; // DRC cancelled
LSET copperLayerSet = m_drcEngine->GetBoard()->GetEnabledLayers() & LSET::AllCuMask();
LSEQ copperLayers = copperLayerSet.Seq();
BOARD* board = m_drcEngine->GetBoard();
BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
LSET copperLayerSet = m_drcEngine->GetBoard()->GetEnabledLayers() & LSET::AllCuMask();
LSEQ copperLayers = copperLayerSet.Seq();
BOARD* board = m_drcEngine->GetBoard();
DRC_RTREE* tree = board->m_CopperItemRTreeCache.get();
std::set<int> distinctMinWidths
= m_drcEngine->QueryDistinctConstraints( CONNECTION_WIDTH_CONSTRAINT );
if( m_drcEngine->IsCancelled() )
return false; // DRC cancelled
std::map<PCB_LAYER_ID, std::map<int, std::set<BOARD_ITEM*>>> net_items;
std::atomic<size_t> done( 1 );
struct ITEMS_POLY
{
std::set<BOARD_ITEM*> items;
SHAPE_POLY_SET poly;
};
DRC_RTREE* tree = board->m_CopperItemRTreeCache.get();
std::map< std::pair<int, PCB_LAYER_ID>, ITEMS_POLY > dataset;
std::atomic<size_t> done( 1 );
auto calc_effort =
[&]( const std::set<BOARD_ITEM*>& items, PCB_LAYER_ID aLayer ) -> size_t
@ -604,28 +610,38 @@ bool DRC_TEST_PROVIDER_CONNECTION_WIDTH::Run()
return effort;
};
auto min_checker =
[&](const std::set<BOARD_ITEM*>& aItems, PCB_LAYER_ID aLayer ) -> size_t
auto build_netlayer_polys =
[&]( int aNetcode, const PCB_LAYER_ID aLayer ) -> size_t
{
if( m_drcEngine->IsCancelled() )
return 0;
SHAPE_POLY_SET poly;
ITEMS_POLY& data = dataset[ { aNetcode, aLayer } ];
for( BOARD_ITEM* item : aItems )
for( BOARD_ITEM* item : data.items )
{
item->TransformShapeWithClearanceToPolygon( poly, aLayer, 0, ARC_HIGH_DEF,
ERROR_OUTSIDE );
item->TransformShapeWithClearanceToPolygon( data.poly, aLayer, 0,
ARC_HIGH_DEF, ERROR_OUTSIDE );
}
poly.Fracture( SHAPE_POLY_SET::PM_FAST );
data.poly.Fracture( SHAPE_POLY_SET::PM_FAST );
int minimum_width = bds.m_MinConn;
POLYGON_TEST test( minimum_width );
done.fetch_add( calc_effort( data.items, aLayer ) );
for( int ii = 0; ii < poly.OutlineCount(); ++ii )
return 1;
};
auto min_checker =
[&]( const ITEMS_POLY& aDataset, const PCB_LAYER_ID aLayer, int aMinWidth ) -> size_t
{
if( m_drcEngine->IsCancelled() )
return 0;
POLYGON_TEST test( aMinWidth );
for( int ii = 0; ii < aDataset.poly.OutlineCount(); ++ii )
{
const SHAPE_LINE_CHAIN& chain = poly.COutline( ii );
const SHAPE_LINE_CHAIN& chain = aDataset.poly.COutline( ii );
test.FindPairs( chain );
auto& ret = test.GetVertices();
@ -635,42 +651,57 @@ bool DRC_TEST_PROVIDER_CONNECTION_WIDTH::Run()
SEG span( chain.CPoint( pt.first ), chain.CPoint( pt.second ) );
VECTOR2I location = ( span.A + span.B ) / 2;
int dist = ( span.A - span.B ).EuclideanNorm();
std::set<BOARD_ITEM*> items = tree->GetObjectsAt( location, aLayer,
minimum_width );
std::set<BOARD_ITEM*> nearbyItems = tree->GetObjectsAt( location, aLayer,
aMinWidth );
std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_CONNECTION_WIDTH );
wxString msg;
std::vector<BOARD_ITEM*> items;
msg.Printf( _( "Minimum connection width %s; actual %s" ),
MessageTextFromValue( userUnits(), minimum_width ),
MessageTextFromValue( userUnits(), dist ) );
drce->SetErrorMessage( msg + wxS( " " ) + layerDesc( aLayer ) );
for( BOARD_ITEM* item : items )
for( BOARD_ITEM* item : nearbyItems )
{
if( item->HitTest( location, minimum_width ) )
drce->AddItem( item );
if( item->HitTest( location, aMinWidth ) )
items.push_back( item );
}
if( !drce->GetIDs().empty() )
reportViolation( drce, location, aLayer );
if( !items.empty() )
{
DRC_CONSTRAINT c = m_drcEngine->EvalRules( CONNECTION_WIDTH_CONSTRAINT,
items[0],
items.size() > 1 ? items[1]
: nullptr,
aLayer );
if( c.Value().Min() == aMinWidth )
{
auto drce = DRC_ITEM::Create( DRCE_CONNECTION_WIDTH );
wxString msg;
msg.Printf( _( "Minimum connection width %s; actual %s" ),
MessageTextFromValue( userUnits(), aMinWidth ),
MessageTextFromValue( userUnits(), dist ) );
drce->SetErrorMessage( msg + wxS( " " ) + layerDesc( aLayer ) );
drce->SetViolatingRule( c.GetParentRule() );
for( BOARD_ITEM* item : items )
drce->AddItem( item );
reportViolation( drce, location, aLayer );
}
}
}
}
done.fetch_add( calc_effort( aItems, aLayer ) );
done.fetch_add( calc_effort( aDataset.items, aLayer ) );
return 1;
};
for( PCB_LAYER_ID layer : copperLayers )
{
auto& layer_items = net_items[layer];
for( ZONE* zone : board->m_DRCCopperZones )
{
if( !zone->GetIsRuleArea() && zone->IsOnLayer( layer ) )
layer_items[zone->GetNetCode()].emplace( zone );
dataset[ { zone->GetNetCode(), layer } ].items.emplace( zone );
}
for( PCB_TRACK* track : board->Tracks() )
@ -678,11 +709,11 @@ bool DRC_TEST_PROVIDER_CONNECTION_WIDTH::Run()
if( PCB_VIA* via = dynamic_cast<PCB_VIA*>( track ) )
{
if( via->FlashLayer( static_cast<int>( layer ) ) )
layer_items[via->GetNetCode()].emplace( via );
dataset[ { via->GetNetCode(), layer } ].items.emplace( via );
}
else if( track->IsOnLayer( layer ) )
{
layer_items[track->GetNetCode()].emplace( track );
dataset[ { track->GetNetCode(), layer } ].items.emplace( track );
}
}
@ -691,29 +722,50 @@ bool DRC_TEST_PROVIDER_CONNECTION_WIDTH::Run()
for( PAD* pad : fp->Pads() )
{
if( pad->FlashLayer( static_cast<int>( layer ) ) )
layer_items[pad->GetNetCode()].emplace( pad );
dataset[ { pad->GetNetCode(), layer } ].items.emplace( pad );
}
// Footprint zones are also in the m_DRCCopperZones cache
}
}
thread_pool& tp = GetKiCadThreadPool();
thread_pool& tp = GetKiCadThreadPool();
std::vector<std::future<size_t>> returns;
size_t return_count = 0;
size_t total_effort = 0;
size_t total_effort = 0;
for( auto& layer_items : net_items )
return_count += layer_items.second.size();
for( const std::pair<const std::pair<int, PCB_LAYER_ID>, ITEMS_POLY>& netLayer : dataset )
total_effort += calc_effort( netLayer.second.items, netLayer.first.second );
returns.reserve( return_count );
total_effort += total_effort * distinctMinWidths.size();
for( auto& layer_items : net_items )
returns.reserve( dataset.size() );
for( const std::pair<const std::pair<int, PCB_LAYER_ID>, ITEMS_POLY>& netLayer : dataset )
{
for( const auto& items : layer_items.second )
returns.emplace_back( tp.submit( build_netlayer_polys, netLayer.first.first,
netLayer.first.second ) );
}
for( std::future<size_t>& retval : returns )
{
std::future_status status;
do
{
returns.emplace_back( tp.submit( min_checker, items.second, layer_items.first ) );
total_effort += calc_effort( items.second, layer_items.first );
m_drcEngine->ReportProgress( static_cast<double>( done ) / total_effort );
status = retval.wait_for( std::chrono::milliseconds( 100 ) );
} while( status != std::future_status::ready );
}
returns.clear();
returns.reserve( dataset.size() * distinctMinWidths.size() );
for( const std::pair<const std::pair<int, PCB_LAYER_ID>, ITEMS_POLY>& netLayer : dataset )
{
for( int minWidth : distinctMinWidths )
{
returns.emplace_back( tp.submit( min_checker, netLayer.second, netLayer.first.second,
minWidth ) );
}
}

View File

@ -89,91 +89,186 @@ bool DRC_TEST_PROVIDER_SLIVER_CHECKER::Run()
int testLength = widthTolerance / ( 2 * sin( DEG2RAD( angleTolerance / 2 ) ) );
LSET copperLayerSet = m_drcEngine->GetBoard()->GetEnabledLayers() & LSET::AllCuMask();
LSEQ copperLayers = copperLayerSet.Seq();
int layerCount = copperLayers.size();
size_t layerCount = copperLayers.size();
// Report progress on board zones only. Everything else is in the noise.
int zoneLayerCount = 0;
std::atomic<size_t> done( 1 );
for( PCB_LAYER_ID layer : copperLayers )
{
for( ZONE* zone : m_drcEngine->GetBoard()->Zones() )
{
if( !zone->GetIsRuleArea() && zone->IsOnLayer( layer ) )
zoneLayerCount++;
}
}
PROGRESS_REPORTER* reporter = m_drcEngine->GetProgressReporter();
if( reporter && reporter->IsCancelled() )
if( m_drcEngine->IsCancelled() )
return false; // DRC cancelled
std::vector<SHAPE_POLY_SET> layerPolys( layerCount );
std::vector<size_t> layerEfforts( layerCount );
size_t total_effort = 0;
std::atomic<size_t> done( 1 );
auto calc_effort =
[&]( BOARD_ITEM* item, PCB_LAYER_ID aLayer ) -> size_t
{
if( item->Type() == PCB_ZONE_T )
{
ZONE* zone = static_cast<ZONE*>( item );
return zone->GetFilledPolysList( aLayer )->FullPointCount();
}
else
{
return 4;
}
};
auto poly_builder =
[&]( size_t ii ) -> size_t
{
PCB_LAYER_ID layer = copperLayers[ii];
SHAPE_POLY_SET& poly = layerPolys[ii];
if( m_drcEngine->IsCancelled() )
return 0;
SHAPE_POLY_SET fill;
forEachGeometryItem( s_allBasicItems, LSET().set( layer ),
[&]( BOARD_ITEM* item ) -> bool
{
if( dynamic_cast<ZONE*>( item) )
{
ZONE* zone = static_cast<ZONE*>( item );
if( !zone->GetIsRuleArea() )
{
fill = zone->GetFill( layer )->CloneDropTriangulation();
fill.Unfracture( SHAPE_POLY_SET::PM_FAST );
for( int jj = 0; jj < fill.OutlineCount(); ++jj )
poly.AddOutline( fill.Outline( jj ) );
}
}
else
{
item->TransformShapeWithClearanceToPolygon( poly, layer, 0,
ARC_LOW_DEF,
ERROR_OUTSIDE );
}
done.fetch_add( calc_effort( item, layer ) );
if( m_drcEngine->IsCancelled() )
return false;
return true;
} );
if( m_drcEngine->IsCancelled() )
return 0;
poly.Simplify( SHAPE_POLY_SET::PM_FAST );
// Sharpen corners
poly.Deflate( widthTolerance / 2, ARC_LOW_DEF,
SHAPE_POLY_SET::ALLOW_ACUTE_CORNERS );
return 1;
};
auto sliver_checker =
[&]( int aItem ) -> size_t
[&]( size_t ii ) -> size_t
{
PCB_LAYER_ID layer = copperLayers[ii];
SHAPE_POLY_SET& poly = layerPolys[ii];
if( m_drcEngine->IsErrorLimitExceeded( DRCE_COPPER_SLIVER ) )
return 0;
// Frequently, in filled areas, some points of the polygons are very near (dist is
// only a few internal units, like 2 or 3 units).
// We skip very small vertices: one cannot really compute a valid orientation of
// such a vertex
// So skip points near than min_len (in internal units).
const int min_len = 3;
for( int jj = 0; jj < poly.OutlineCount(); ++jj )
{
PCB_LAYER_ID layer = copperLayers[aItem];
SHAPE_POLY_SET& poly = layerPolys[aItem];
const std::vector<VECTOR2I>& pts = poly.Outline( jj ).CPoints();
int ptCount = pts.size();
if( m_drcEngine->IsCancelled() )
return 0;
for( int kk = 0; kk < ptCount; ++kk )
{
VECTOR2I pt = pts[ kk ];
VECTOR2I ptPrior = pts[ ( ptCount + kk - 1 ) % ptCount ];
VECTOR2I vPrior = ( ptPrior - pt );
SHAPE_POLY_SET fill;
if( std::abs( vPrior.x ) < min_len && std::abs( vPrior.y ) < min_len && ptCount > 5)
{
ptPrior = pts[ ( ptCount + kk - 2 ) % ptCount ];
vPrior = ( ptPrior - pt );
}
forEachGeometryItem( s_allBasicItems, LSET().set( layer ),
[&]( BOARD_ITEM* item ) -> bool
{
if( dynamic_cast<ZONE*>( item) )
{
ZONE* zone = static_cast<ZONE*>( item );
VECTOR2I ptAfter = pts[ ( kk + 1 ) % ptCount ];
VECTOR2I vAfter = ( ptAfter - pt );
if( !zone->GetIsRuleArea() )
{
fill = zone->GetFill( layer )->CloneDropTriangulation();
fill.Unfracture( SHAPE_POLY_SET::PM_FAST );
if( std::abs( vAfter.x ) < min_len && std::abs( vAfter.y ) < min_len && ptCount > 5 )
{
ptAfter = pts[ ( kk + 2 ) % ptCount ];
vAfter = ( ptAfter - pt );
}
for( int jj = 0; jj < fill.OutlineCount(); ++jj )
poly.AddOutline( fill.Outline( jj ) );
VECTOR2I vIncluded = vPrior.Resize( testLength ) - vAfter.Resize( testLength );
// Report progress on board zones only. Everything
// else is in the noise.
done.fetch_add( 1 );
}
}
else
{
item->TransformShapeWithClearanceToPolygon( poly, layer, 0,
ARC_LOW_DEF,
ERROR_OUTSIDE );
}
if( vIncluded.SquaredEuclideanNorm() < SEG::Square( widthTolerance ) )
{
std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_COPPER_SLIVER );
drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + layerDesc( layer ) );
reportViolation( drce, pt, layer );
}
}
}
if( m_drcEngine->IsCancelled() )
return false;
done.fetch_add( layerEfforts[ layer ] );
return true;
} );
if( m_drcEngine->IsCancelled() )
return 0;
return 1;
};
if( m_drcEngine->IsCancelled() )
return 0;
for( size_t ii = 0; ii < layerCount; ++ii )
{
PCB_LAYER_ID layer = copperLayers[ii];
poly.Simplify( SHAPE_POLY_SET::PM_FAST );
forEachGeometryItem( s_allBasicItems, LSET().set( layer ),
[&]( BOARD_ITEM* item ) -> bool
{
layerEfforts[ layer ] += calc_effort( item, layer );
return true;
} );
// Sharpen corners
poly.Deflate( widthTolerance / 2, ARC_LOW_DEF,
SHAPE_POLY_SET::ALLOW_ACUTE_CORNERS );
total_effort += layerEfforts[ layer ];
}
return 1;
};
total_effort *= 2; // Once for building polys; once for checking slivers
thread_pool& tp = GetKiCadThreadPool();
std::vector<std::future<size_t>> returns;
returns.reserve( copperLayers.size() );
returns.reserve( layerCount );
for( size_t ii = 0; ii < copperLayers.size(); ++ii )
for( size_t ii = 0; ii < layerCount; ++ii )
returns.emplace_back( tp.submit( poly_builder, ii ) );
for( auto& retval : returns )
{
std::future_status status;
do
{
m_drcEngine->ReportProgress( static_cast<double>( done ) / total_effort );
status = retval.wait_for( std::chrono::milliseconds( 100 ) );
} while( status != std::future_status::ready );
}
returns.clear();
returns.reserve( layerCount );
for( size_t ii = 0; ii < layerCount; ++ii )
returns.emplace_back( tp.submit( sliver_checker, ii ) );
for( auto& retval : returns )
@ -182,66 +277,12 @@ bool DRC_TEST_PROVIDER_SLIVER_CHECKER::Run()
do
{
m_drcEngine->ReportProgress( static_cast<double>( zoneLayerCount ) / done );
m_drcEngine->ReportProgress( static_cast<double>( done ) / total_effort );
status = retval.wait_for( std::chrono::milliseconds( 100 ) );
} while( status != std::future_status::ready );
}
for( int ii = 0; ii < layerCount; ++ii )
{
PCB_LAYER_ID layer = copperLayers[ii];
SHAPE_POLY_SET& poly = layerPolys[ii];
if( m_drcEngine->IsErrorLimitExceeded( DRCE_COPPER_SLIVER ) )
continue;
// Frequently, in filled areas, some points of the polygons are very near (dist is only
// a few internal units, like 2 or 3 units.
// We skip very small vertices: one cannot really compute a valid orientation of
// such a vertex
// So skip points near than min_len (in internal units).
const int min_len = 3;
for( int jj = 0; jj < poly.OutlineCount(); ++jj )
{
const std::vector<VECTOR2I>& pts = poly.Outline( jj ).CPoints();
int ptCount = pts.size();
for( int kk = 0; kk < ptCount; ++kk )
{
VECTOR2I pt = pts[ kk ];
VECTOR2I ptPrior = pts[ ( ptCount + kk - 1 ) % ptCount ];
VECTOR2I vPrior = ( ptPrior - pt );
if( std::abs( vPrior.x ) < min_len && std::abs( vPrior.y ) < min_len && ptCount > 5)
{
ptPrior = pts[ ( ptCount + kk - 2 ) % ptCount ];
vPrior = ( ptPrior - pt );
}
VECTOR2I ptAfter = pts[ ( kk + 1 ) % ptCount ];
VECTOR2I vAfter = ( ptAfter - pt );
if( std::abs( vAfter.x ) < min_len && std::abs( vAfter.y ) < min_len && ptCount > 5 )
{
ptAfter = pts[ ( kk + 2 ) % ptCount ];
vAfter = ( ptAfter - pt );
}
VECTOR2I vIncluded = vPrior.Resize( testLength ) - vAfter.Resize( testLength );
if( vIncluded.SquaredEuclideanNorm() < SEG::Square( widthTolerance ) )
{
std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_COPPER_SLIVER );
drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + layerDesc( layer ) );
reportViolation( drce, pt, layer );
}
}
}
}
return true;
}

View File

@ -317,6 +317,20 @@ void BOARD_INSPECTION_TOOL::InspectDRCError( const std::shared_ptr<RC_ITEM>& aDR
reportMax( r->GetUnits(), constraint ) ) );
break;
case DRCE_CONNECTION_WIDTH:
r = m_inspectClearanceDialog->AddPage( _( "Connection Width" ) );
reportHeader( _( "Connection width resolution for:" ), a, b, r );
if( compileError )
reportCompileError( r );
constraint = drcEngine.EvalRules( CONNECTION_WIDTH_CONSTRAINT, a, b, layer, r );
r->Report( "" );
r->Report( wxString::Format( _( "Resolved min connection width constraint: %s." ),
reportMin( r->GetUnits(), constraint ) ) );
break;
case DRCE_VIA_DIAMETER:
r = m_inspectClearanceDialog->AddPage( _( "Via Diameter" ) );
reportHeader( _( "Via diameter resolution for:" ), a, r );

View File

@ -0,0 +1,7 @@
(version 1)
(rule high_current_netclass
(constraint connection_width (min 0.16mm))
(condition "A.NetClass == 'High_current'"))
(rule high_current_area
(constraint connection_width (min 0.16mm))
(condition "A.insideArea('high_current')"))

View File

@ -0,0 +1,141 @@
(kicad_pcb (version 20220621) (generator pcbnew)
(general
(thickness 1.6)
)
(paper "A4")
(layers
(0 "F.Cu" signal)
(31 "B.Cu" signal)
(32 "B.Adhes" user "B.Adhesive")
(33 "F.Adhes" user "F.Adhesive")
(34 "B.Paste" user)
(35 "F.Paste" user)
(36 "B.SilkS" user "B.Silkscreen")
(37 "F.SilkS" user "F.Silkscreen")
(38 "B.Mask" user)
(39 "F.Mask" user)
(40 "Dwgs.User" user "User.Drawings")
(41 "Cmts.User" user "User.Comments")
(42 "Eco1.User" user "User.Eco1")
(43 "Eco2.User" user "User.Eco2")
(44 "Edge.Cuts" user)
(45 "Margin" user)
(46 "B.CrtYd" user "B.Courtyard")
(47 "F.CrtYd" user "F.Courtyard")
(48 "B.Fab" user)
(49 "F.Fab" user)
(50 "User.1" user)
(51 "User.2" user)
(52 "User.3" user)
(53 "User.4" user)
(54 "User.5" user)
(55 "User.6" user)
(56 "User.7" user)
(57 "User.8" user)
(58 "User.9" user)
)
(setup
(stackup
(layer "F.SilkS" (type "Top Silk Screen"))
(layer "F.Paste" (type "Top Solder Paste"))
(layer "F.Mask" (type "Top Solder Mask") (thickness 0.01))
(layer "F.Cu" (type "copper") (thickness 0.035))
(layer "dielectric 1" (type "core") (thickness 1.51) (material "FR4") (epsilon_r 4.5) (loss_tangent 0.02))
(layer "B.Cu" (type "copper") (thickness 0.035))
(layer "B.Mask" (type "Bottom Solder Mask") (thickness 0.01))
(layer "B.Paste" (type "Bottom Solder Paste"))
(layer "B.SilkS" (type "Bottom Silk Screen"))
(copper_finish "None")
(dielectric_constraints no)
)
(pad_to_mask_clearance 0)
(pcbplotparams
(layerselection 0x00010fc_ffffffff)
(plot_on_all_layers_selection 0x0000000_00000000)
(disableapertmacros false)
(usegerberextensions false)
(usegerberattributes true)
(usegerberadvancedattributes true)
(creategerberjobfile true)
(dashed_line_dash_ratio 12.000000)
(dashed_line_gap_ratio 3.000000)
(svgprecision 4)
(plotframeref false)
(viasonmask false)
(mode 1)
(useauxorigin false)
(hpglpennumber 1)
(hpglpenspeed 20)
(hpglpendiameter 15.000000)
(dxfpolygonmode true)
(dxfimperialunits true)
(dxfusepcbnewfont true)
(psnegative false)
(psa4output false)
(plotreference true)
(plotvalue true)
(plotinvisibletext false)
(sketchpadsonfab false)
(subtractmaskfromsilk false)
(outputformat 1)
(mirror false)
(drillshape 1)
(scaleselection 1)
(outputdirectory "")
)
)
(net 0 "")
(net 1 "net_1")
(net 2 "net_HC")
(net 3 "net_2")
(net 4 "net_3")
(gr_rect (start 103.8606 72.2884) (end 121.1326 81.661)
(stroke (width 0.1) (type default)) (fill none) (layer "Edge.Cuts") (tstamp 0fed72c1-482a-43d6-acdf-98ed8c42a640))
(gr_text "<<- no error" (at 117.2972 78.6892) (layer "F.Cu") (tstamp 1cdd5a64-dfb1-4eda-890c-555989cf19dc)
(effects (font (size 0.2 0.2) (thickness 0.04) bold) (justify left bottom))
)
(gr_text "<<- error" (at 117.2972 74.3458) (layer "F.Cu") (tstamp 24703be7-31d2-4cc0-807d-d1af2764829c)
(effects (font (size 0.2 0.2) (thickness 0.04) bold) (justify left bottom))
)
(gr_text "<<- error" (at 117.348 77.6732) (layer "F.Cu") (tstamp 48240202-f51e-42b8-906f-77194bafa596)
(effects (font (size 0.2 0.2) (thickness 0.04) bold) (justify left bottom))
)
(gr_text "High_current rule area:" (at 105.5116 76.327) (layer "F.Cu") (tstamp 5a232032-2a77-4c5c-a932-e0dd3c3db8c9)
(effects (font (size 0.2 0.2) (thickness 0.04) bold) (justify left bottom))
)
(gr_text "High_current netclass:" (at 106.1466 73.9394) (layer "F.Cu") (tstamp dd7b0757-fb61-40bd-84e1-598677c91625)
(effects (font (size 0.2 0.2) (thickness 0.04) bold) (justify left bottom))
)
(gr_text "<<- error" (at 117.094 79.4766) (layer "F.Cu") (tstamp e8c4ea88-467d-4e67-8480-f5f76e9ac3cf)
(effects (font (size 0.2 0.2) (thickness 0.04) bold) (justify left bottom))
)
(segment (start 112.5474 77.5716) (end 106.1212 77.5716) (width 0.25) (layer "F.Cu") (net 1) (tstamp 07a05dc9-fd0c-4bac-8cfe-73f661e50f58))
(segment (start 112.7506 77.5716) (end 116.7384 77.5716) (width 0.25) (layer "F.Cu") (net 1) (tstamp b9c8bd88-3577-41fd-b8a3-f84c3ec54539))
(segment (start 112.8014 74.2442) (end 116.7892 74.2442) (width 0.25) (layer "F.Cu") (net 2) (tstamp 5fd40a5e-8558-46fc-9321-fcda035db40b))
(segment (start 112.5982 74.2442) (end 106.172 74.2442) (width 0.25) (layer "F.Cu") (net 2) (tstamp 7014ab67-9149-4546-9f6e-d99764e15f97))
(segment (start 112.7252 78.5622) (end 116.713 78.5622) (width 0.25) (layer "F.Cu") (net 3) (tstamp 7c6d2fd8-65cf-4c16-ba39-e8c3cd86d13d))
(segment (start 112.522 78.5622) (end 106.0958 78.5622) (width 0.25) (layer "F.Cu") (net 3) (tstamp a38443b8-d7a2-41c0-b6b5-5218a5114002))
(segment (start 108.3818 79.3496) (end 112.5474 79.3496) (width 0.25) (layer "F.Cu") (net 4) (tstamp db40c24a-6762-409a-acaf-cbcefb8e7d40))
(segment (start 112.776 79.3496) (end 116.4082 79.3496) (width 0.25) (layer "F.Cu") (net 4) (tstamp e8d7189c-1bf1-4f6c-abd7-bbbd5fd5f896))
(zone (net 0) (net_name "") (layer "F.Cu") (tstamp b0befc79-e815-4fdd-bd77-757b21ce75e2) (name "high_current") (hatch edge 0.508)
(connect_pads (clearance 0))
(min_thickness 0.254) (filled_areas_thickness no)
(keepout (tracks allowed) (vias allowed) (pads allowed) (copperpour allowed) (footprints allowed))
(fill (thermal_gap 0.508) (thermal_bridge_width 0.508) (island_removal_mode 2) (island_area_min 10))
(polygon
(pts
(xy 118.5418 77.9526)
(xy 105.4354 77.9526)
(xy 105.4354 76.454)
(xy 118.5418 76.454)
)
)
)
)

View File

@ -0,0 +1,262 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"board_outline_line_width": 0.09999999999999999,
"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": false,
"courtyard_line_width": 0.049999999999999996,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.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": false,
"other_line_width": 0.15,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.762,
"height": 1.524,
"width": 1.524
},
"silk_line_width": 0.15,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.15,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.508
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "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",
"footprint_type_mismatch": "warning",
"hole_clearance": "error",
"hole_near_hole": "error",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"npth_inside_courtyard": "ignore",
"overlapping_pads": "warning",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "ignore",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.12,
"min_copper_edge_clearance": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.19999999999999998,
"min_microvia_drill": 0.09999999999999999,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.7999999999999999,
"min_text_thickness": 0.12,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.19999999999999998,
"min_via_annular_width": 0.049999999999999996,
"min_via_diameter": 0.39999999999999997,
"solder_mask_clearance": 0.0,
"solder_mask_min_width": 0.0,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 5,
"td_on_pad_in_zone": false,
"td_onpadsmd": true,
"td_onroundshapesonly": false,
"td_ontrackend": false,
"td_onviapad": true
}
],
"teardrop_parameters": [
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0
],
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
}
],
"zones_allow_external_fillets": false
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "connection_width.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12.0,
"clearance": 0.1,
"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.1,
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6.0
},
{
"bus_width": 12.0,
"clearance": 0.1,
"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": "High_current",
"nets": [
"net_HC"
],
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.25,
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6.0
}
],
"meta": {
"version": 2
},
"net_colors": null
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"specctra_dsn": "",
"step": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"legacy_lib_dir": "",
"legacy_lib_list": []
},
"sheets": [],
"text_variables": {}
}

View File

@ -48,14 +48,15 @@ BOOST_FIXTURE_TEST_CASE( DRCCopperConn, DRC_REGRESSION_TEST_FIXTURE )
{
// Check for minimum copper connection errors
std::vector<wxString> tests =
std::vector<std::pair<wxString, int>> tests =
{
"issue9870"
{ "issue9870", 12 },
{ "connection_width_rules", 3 }
};
for( const wxString& relPath : tests )
for( const std::pair<wxString, int>& test : tests )
{
KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
KI_TEST::LoadBoard( m_settingsManager, test.first, m_board );
KI_TEST::FillZones( m_board.get() );
std::vector<DRC_ITEM> violations;
@ -82,10 +83,10 @@ BOOST_FIXTURE_TEST_CASE( DRCCopperConn, DRC_REGRESSION_TEST_FIXTURE )
bds.m_DRCEngine->RunTests( EDA_UNITS::MILLIMETRES, true, false );
if( violations.size() == 12 )
if( violations.size() == test.second )
{
BOOST_CHECK_EQUAL( 1, 1 ); // quiet "did not check any assertions" warning
BOOST_TEST_MESSAGE( wxString::Format( "DRC connection width: %s, passed", relPath ) );
BOOST_TEST_MESSAGE( wxString::Format( "DRC connection width: %s, passed", test.first ) );
}
else
{
@ -98,7 +99,7 @@ BOOST_FIXTURE_TEST_CASE( DRCCopperConn, DRC_REGRESSION_TEST_FIXTURE )
itemMap ) );
}
BOOST_ERROR( wxString::Format( "DRC connection width: %s, failed", relPath ) );
BOOST_ERROR( wxString::Format( "DRC connection width: %s, failed", test.first ) );
}
}
}