7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-20 20:51:42 +00:00

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

LOADING design file

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 ) );
}
}
}