Optimize zone-zone clearance checks

Improve the CREEPAGE_GRAPH:::GeneratePaths to skip unused checks.
Handle zone-zone paralellism better

Fixes https://gitlab.com/kicad/code/kicad/-/issues/21353
This commit is contained in:
Seth Hillbrand 2025-07-21 10:29:18 -07:00
parent 4c03ab8ebb
commit faeaee824a
5 changed files with 197 additions and 207 deletions

View File

@ -2245,7 +2245,7 @@ void CREEPAGE_GRAPH::Addshape( const SHAPE& aShape, std::shared_ptr<GRAPH_NODE>&
} }
} }
void CREEPAGE_GRAPH::GeneratePaths( double aMaxWeight, PCB_LAYER_ID aLayer, bool aClearance ) void CREEPAGE_GRAPH::GeneratePaths( double aMaxWeight, PCB_LAYER_ID aLayer )
{ {
std::vector<std::shared_ptr<GRAPH_NODE>> nodes; std::vector<std::shared_ptr<GRAPH_NODE>> nodes;
std::mutex nodes_lock; std::mutex nodes_lock;
@ -2265,102 +2265,134 @@ void CREEPAGE_GRAPH::GeneratePaths( double aMaxWeight, PCB_LAYER_ID aLayer, bool
&& gn1->m_net < gn2->m_net ); && gn1->m_net < gn2->m_net );
} ); } );
auto processNodes = [&]( size_t i, size_t j ) -> bool // Build parent -> net -> nodes mapping for efficient filtering
std::unordered_map<const BOARD_ITEM*, std::unordered_map<int, std::vector<std::shared_ptr<GRAPH_NODE>>>> parent_net_groups;
std::vector<const BOARD_ITEM*> parent_keys;
for( const auto& gn : nodes )
{ {
for( size_t ii = i; ii < j; ii++ ) const BOARD_ITEM* parent = gn->m_parent->GetParent();
if( parent_net_groups[parent].empty() )
parent_keys.push_back( parent );
parent_net_groups[parent][gn->m_net].push_back( gn );
}
// Generate work items: compare nodes between different parents only
struct WorkItem {
std::shared_ptr<GRAPH_NODE> node1, node2;
};
std::vector<WorkItem> work_items;
for( size_t i = 0; i < parent_keys.size(); ++i )
{
for( size_t j = i + 1; j < parent_keys.size(); ++j )
{
const auto& group1_nets = parent_net_groups[parent_keys[i]];
const auto& group2_nets = parent_net_groups[parent_keys[j]];
for( const auto& [net1, nodes1] : group1_nets )
{ {
std::shared_ptr<GRAPH_NODE> gn1 = nodes[ii]; for( const auto& [net2, nodes2] : group2_nets )
for( size_t jj = ii + 1; jj < nodes.size(); jj++ )
{ {
std::shared_ptr<GRAPH_NODE> gn2 = nodes[jj]; // Skip if same net and both nets have only conductive nodes
if( net1 == net2 )
if( gn1->m_parent->GetParent() == gn2->m_parent->GetParent() )
continue;
if( ( gn1->m_net == gn2->m_net ) && ( gn1->m_parent->IsConductive() )
&& ( gn2->m_parent->IsConductive() ) )
continue;
for( PATH_CONNECTION pc : GetPaths( gn1->m_parent, gn2->m_parent, aMaxWeight ) )
{ {
std::vector<const BOARD_ITEM*> IgnoreForTest; bool all_conductive_1 = std::all_of( nodes1.begin(), nodes1.end(),
IgnoreForTest.push_back( gn1->m_parent->GetParent() ); []( const auto& n ) { return n->m_parent->IsConductive(); } );
IgnoreForTest.push_back( gn2->m_parent->GetParent() ); bool all_conductive_2 = std::all_of( nodes2.begin(), nodes2.end(),
[]( const auto& n ) { return n->m_parent->IsConductive(); } );
if( !pc.isValid( m_board, aLayer, m_boardEdge, IgnoreForTest, m_boardOutline, if( all_conductive_1 && all_conductive_2 )
{ false, true }, m_minGrooveWidth ) )
continue; continue;
std::shared_ptr<GRAPH_NODE>* connect1 = &gn1;
std::shared_ptr<GRAPH_NODE>* connect2 = &gn2;
std::shared_ptr<GRAPH_NODE> gnt1 = nullptr;
std::shared_ptr<GRAPH_NODE> gnt2 = nullptr;
std::lock_guard<std::mutex> lock( nodes_lock );
if( gn1->m_parent->GetType() != CREEP_SHAPE::TYPE::POINT )
{
gnt1 = AddNode( GRAPH_NODE::POINT, gn1->m_parent, pc.a1 );
gnt1->m_connectDirectly = false;
if( gn1->m_parent->IsConductive() )
{
std::shared_ptr<GRAPH_CONNECTION> gc = AddConnection( gn1, gnt1 );
if( gc )
gc->m_path.m_show = false;
}
connect1 = &gnt1;
}
if( gn2->m_parent->GetType() != CREEP_SHAPE::TYPE::POINT )
{
gnt2 = AddNode( GRAPH_NODE::POINT, gn2->m_parent, pc.a2 );
gnt2->m_connectDirectly = false;
if( gn2->m_parent->IsConductive() )
{
std::shared_ptr<GRAPH_CONNECTION> gc = AddConnection( gn2, gnt2 );
if( gc )
gc->m_path.m_show = false;
}
connect2 = &gnt2;
}
AddConnection( *connect1, *connect2, pc );
} }
} // for jj
} // for ii
return true; // Add all node pairs from these net groups
}; for( const auto& gn1 : nodes1 )
for( const auto& gn2 : nodes2 )
work_items.push_back( { gn1, gn2 } );
}
}
}
}
// Running in the clearance test, we are already in a parallelized loop, so parallelizing again auto processWorkItems = [&]( size_t start_idx, size_t end_idx ) -> bool
// will run into deadlock as all threads start waiting {
if( aClearance ) for( size_t idx = start_idx; idx < end_idx; ++idx )
processNodes( 0, nodes.size() ); {
auto [gn1, gn2] = work_items[idx];
for( PATH_CONNECTION pc : GetPaths( gn1->m_parent, gn2->m_parent, aMaxWeight ) )
{
std::vector<const BOARD_ITEM*> IgnoreForTest = {
gn1->m_parent->GetParent(), gn2->m_parent->GetParent()
};
if( !pc.isValid( m_board, aLayer, m_boardEdge, IgnoreForTest, m_boardOutline,
{ false, true }, m_minGrooveWidth ) )
continue;
std::shared_ptr<GRAPH_NODE> connect1 = gn1, connect2 = gn2;
std::lock_guard<std::mutex> lock( nodes_lock );
// Handle non-point node1
if( gn1->m_parent->GetType() != CREEP_SHAPE::TYPE::POINT )
{
auto gnt1 = AddNode( GRAPH_NODE::POINT, gn1->m_parent, pc.a1 );
gnt1->m_connectDirectly = false;
connect1 = gnt1;
if( gn1->m_parent->IsConductive() )
{
if( auto gc = AddConnection( gn1, gnt1 ) )
gc->m_path.m_show = false;
}
}
// Handle non-point node2
if( gn2->m_parent->GetType() != CREEP_SHAPE::TYPE::POINT )
{
auto gnt2 = AddNode( GRAPH_NODE::POINT, gn2->m_parent, pc.a2 );
gnt2->m_connectDirectly = false;
connect2 = gnt2;
if( gn2->m_parent->IsConductive() )
{
if( auto gc = AddConnection( gn2, gnt2 ) )
gc->m_path.m_show = false;
}
}
AddConnection( connect1, connect2, pc );
}
}
return true;
};
// If the number of tasks is high enough, this indicates that the calling process
// has already parallelized the work, so we can process all items in one go.
if( tp.get_tasks_total() >= tp.get_thread_count() - 4 )
{
processWorkItems( 0, work_items.size() );
}
else else
{ {
auto ret = tp.parallelize_loop( nodes.size(), processNodes ); auto ret = tp.parallelize_loop( work_items.size(), processWorkItems );
for( size_t ii = 0; ii < ret.size(); ii++ ) for( size_t ii = 0; ii < ret.size(); ii++ )
{ {
std::future<bool>& r = ret[ii]; std::future<bool>& r = ret[ii];
if( r.valid() ) if( !r.valid() )
{ continue;
std::future_status status = r.wait_for( std::chrono::seconds( 0 ) );
while( status != std::future_status::ready ) while( r.wait_for( std::chrono::milliseconds( 100 ) ) != std::future_status::ready ){}
{
status = r.wait_for( std::chrono::milliseconds( 100 ) );
}
}
} }
} }
} }
void CREEPAGE_GRAPH::Trim( double aWeightLimit ) void CREEPAGE_GRAPH::Trim( double aWeightLimit )
{ {
std::vector<std::shared_ptr<GRAPH_CONNECTION>> toRemove; std::vector<std::shared_ptr<GRAPH_CONNECTION>> toRemove;

View File

@ -69,7 +69,7 @@ struct PATH_CONNECTION
bool isValid( const BOARD& aBoard, PCB_LAYER_ID aLayer, bool isValid( const BOARD& aBoard, PCB_LAYER_ID aLayer,
const std::vector<BOARD_ITEM*>& aBoardEdges, const std::vector<BOARD_ITEM*>& aBoardEdges,
const std::vector<const BOARD_ITEM*>& aIgnoreForTest, SHAPE_POLY_SET* aOutline, const std::vector<const BOARD_ITEM*>& aIgnoreForTest, SHAPE_POLY_SET* aOutline,
const std::pair<bool, bool>& aTestLocalConcavity, int aMinGrooveWidth ) const std::pair<bool, bool>& aTestLocalConcavity, int aMinGrooveWidth ) const
{ {
if( !aOutline ) if( !aOutline )
return true; // We keep the segment if there is a problem return true; // We keep the segment if there is a problem
@ -760,7 +760,7 @@ public:
double Solve( std::shared_ptr<GRAPH_NODE>& aFrom, std::shared_ptr<GRAPH_NODE>& aTo, double Solve( std::shared_ptr<GRAPH_NODE>& aFrom, std::shared_ptr<GRAPH_NODE>& aTo,
std::vector<std::shared_ptr<GRAPH_CONNECTION>>& aResult ); std::vector<std::shared_ptr<GRAPH_CONNECTION>>& aResult );
void GeneratePaths( double aMaxWeight, PCB_LAYER_ID aLayer, bool aClearance ); void GeneratePaths( double aMaxWeight, PCB_LAYER_ID aLayer );
std::shared_ptr<GRAPH_NODE> AddNetElements( int aNetCode, PCB_LAYER_ID aLayer, int aMaxCreepage ); std::shared_ptr<GRAPH_NODE> AddNetElements( int aNetCode, PCB_LAYER_ID aLayer, int aMaxCreepage );

View File

@ -112,7 +112,7 @@ void DRC_TEST_PROVIDER_CLEARANCE_BASE::ReportAndShowPathCuToCu( std::shared_ptr<
graph.Addshape( *( aItem1->GetEffectiveShape( layer ) ), NetA, nullptr ); graph.Addshape( *( aItem1->GetEffectiveShape( layer ) ), NetA, nullptr );
graph.Addshape( *( aItem2->GetEffectiveShape( layer ) ), NetB, nullptr ); graph.Addshape( *( aItem2->GetEffectiveShape( layer ) ), NetB, nullptr );
graph.GeneratePaths( aDistance * 2, layer, true ); graph.GeneratePaths( aDistance * 2, layer );
double minValue = aDistance * 2; double minValue = aDistance * 2;
GRAPH_CONNECTION* minGc = nullptr; GRAPH_CONNECTION* minGc = nullptr;

View File

@ -1203,83 +1203,94 @@ void DRC_TEST_PROVIDER_COPPER_CLEARANCE::testZonesToZones()
std::vector<std::map<PCB_LAYER_ID, std::vector<SEG>>> poly_segments; std::vector<std::map<PCB_LAYER_ID, std::vector<SEG>>> poly_segments;
poly_segments.resize( m_board->m_DRCCopperZones.size() ); poly_segments.resize( m_board->m_DRCCopperZones.size() );
// Contains the index for zoneA, zoneB, the conflict point, the actual clearance, the thread_pool& tp = GetKiCadThreadPool();
// constraint, and the layer std::atomic<size_t> done( 0 );
using REPORT_DATA = std::tuple<int, int, VECTOR2I, int, DRC_CONSTRAINT, PCB_LAYER_ID>; size_t count = 0;
std::vector<std::future<REPORT_DATA>> futures; auto reportZoneZoneViolation = [this]( ZONE* zoneA, ZONE* zoneB, VECTOR2I& pt, int actual,
thread_pool& tp = GetKiCadThreadPool(); const DRC_CONSTRAINT& constraint, PCB_LAYER_ID layer ) -> void
std::atomic<size_t> done( 1 ); {
std::shared_ptr<DRC_ITEM> drce;
auto checkZones = if( constraint.IsNull() )
[this, testClearance, testIntersects, &poly_segments, &done] {
( int zoneA_idx, int zoneB_idx, bool sameNet, PCB_LAYER_ID layer ) -> REPORT_DATA drce = DRC_ITEM::Create( DRCE_ZONES_INTERSECT );
wxString msg = _( "(intersecting zones must have distinct priorities)" );
drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg );
drce->SetItems( zoneA, zoneB );
reportViolation( drce, pt, layer );
}
else
{
drce = DRC_ITEM::Create( DRCE_CLEARANCE );
wxString msg = formatMsg( _( "(%s clearance %s; actual %s)" ), constraint.GetName(),
constraint.GetValue().Min(), std::max( actual, 0 ) );
drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg );
drce->SetItems( zoneA, zoneB );
drce->SetViolatingRule( constraint.GetParentRule() );
ReportAndShowPathCuToCu( drce, pt, layer, zoneA, zoneB, layer, actual );
}
};
auto checkZones = [this, testClearance, testIntersects, reportZoneZoneViolation, &poly_segments,
&done]( int zoneA_idx, int zoneB_idx, bool sameNet, PCB_LAYER_ID layer ) -> void
{
ZONE* zoneA = m_board->m_DRCCopperZones[zoneA_idx];
ZONE* zoneB = m_board->m_DRCCopperZones[zoneB_idx];
int actual = 0;
VECTOR2I pt;
if( sameNet && testIntersects )
{
if( zoneA->Outline()->Collide( zoneB->Outline(), 0, &actual, &pt ) )
{ {
ZONE* zoneA = m_board->m_DRCCopperZones[zoneA_idx]; done.fetch_add( 1 );
ZONE* zoneB = m_board->m_DRCCopperZones[zoneB_idx]; reportZoneZoneViolation( zoneA, zoneB, pt, actual, DRC_CONSTRAINT(), layer );
int actual = 0; return;
VECTOR2I pt; }
}
else if( !sameNet && testClearance )
{
DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( CLEARANCE_CONSTRAINT, zoneA, zoneB, layer );
int clearance = constraint.GetValue().Min();
if( sameNet && testIntersects ) if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance > 0 )
{
std::map<VECTOR2I, int> conflictPoints;
std::vector<SEG>& refSegments = poly_segments[zoneA_idx][layer];
std::vector<SEG>& testSegments = poly_segments[zoneB_idx][layer];
// Iterate through all the segments in zoneA
for( SEG& refSegment : refSegments )
{ {
if( zoneA->Outline()->Collide( zoneB->Outline(), 0, &actual, &pt ) ) // Iterate through all the segments in zoneB
for( SEG& testSegment : testSegments )
{ {
done.fetch_add( 1 ); // We have ensured that the 'A' segment starts before the 'B' segment, so if the
return std::make_tuple( zoneA_idx, zoneB_idx, pt, 0, DRC_CONSTRAINT(), layer ); // 'A' segment ends before the 'B' segment starts, we can skip to the next 'A'
} if( refSegment.B.x < testSegment.A.x )
} break;
else if( !sameNet && testClearance )
{
DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( CLEARANCE_CONSTRAINT, zoneA, zoneB, layer );
int clearance = constraint.GetValue().Min();
if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance > 0 ) int64_t dist_sq = 0;
{ VECTOR2I other_pt;
std::map<VECTOR2I, int> conflictPoints; refSegment.NearestPoints( testSegment, pt, other_pt, dist_sq );
actual = std::floor( std::sqrt( dist_sq ) + 0.5 );
std::vector<SEG>& refSegments = poly_segments[zoneA_idx][layer]; if( actual < clearance )
std::vector<SEG>& testSegments = poly_segments[zoneB_idx][layer];
// Iterate through all the segments in zoneA
for( SEG& refSegment : refSegments )
{ {
int ax1 = refSegment.A.x; done.fetch_add( 1 );
int ay1 = refSegment.A.y; reportZoneZoneViolation( zoneA, zoneB, pt, actual, constraint, layer );
int ax2 = refSegment.B.x; return;
int ay2 = refSegment.B.y;
// Iterate through all the segments in zoneB
for( SEG& testSegment : testSegments )
{
// Build test segment
int bx1 = testSegment.A.x;
int by1 = testSegment.A.y;
int bx2 = testSegment.B.x;
int by2 = testSegment.B.y;
// We have ensured that the 'A' segment starts before the 'B' segment, so if the
// 'A' segment ends before the 'B' segment starts, we can skip to the next 'A'
if( ax2 < bx1 )
break;
int64_t dist_sq = 0;
VECTOR2I other_pt;
refSegment.NearestPoints( testSegment, pt, other_pt, dist_sq );
actual = std::floor( std::sqrt( dist_sq ) + 0.5 );
if( actual < clearance )
{
done.fetch_add( 1 );
return std::make_tuple( zoneA_idx, zoneB_idx, pt, actual, constraint, layer );
}
}
} }
} }
} }
}
}
done.fetch_add( 1 ); done.fetch_add( 1 );
return std::make_tuple( -1, -1, VECTOR2I(), 0, DRC_CONSTRAINT(), F_Cu ); };
};
for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, m_board->GetCopperLayerCount() ) ) for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, m_board->GetCopperLayerCount() ) )
@ -1312,8 +1323,6 @@ void DRC_TEST_PROVIDER_COPPER_CLEARANCE::testZonesToZones()
} }
} }
std::vector<std::pair<int, int>> zonePairs;
for( size_t ia = 0; ia < m_board->m_DRCCopperZones.size(); ia++ ) for( size_t ia = 0; ia < m_board->m_DRCCopperZones.size(); ia++ )
{ {
ZONE* zoneA = m_board->m_DRCCopperZones[ia]; ZONE* zoneA = m_board->m_DRCCopperZones[ia];
@ -1356,75 +1365,24 @@ void DRC_TEST_PROVIDER_COPPER_CLEARANCE::testZonesToZones()
if( !polyA->BBoxFromCaches().Intersects( polyB->BBoxFromCaches() ) ) if( !polyA->BBoxFromCaches().Intersects( polyB->BBoxFromCaches() ) )
continue; continue;
futures.push_back( tp.submit( checkZones, ia, ia2, sameNet, layer ) ); count++;
tp.push_task( checkZones, ia, ia2, sameNet, layer );
} }
} }
} }
size_t count = futures.size(); while( true )
for( std::future<REPORT_DATA>& task : futures )
{ {
if( !task.valid() ) reportProgress( done, count );
continue;
std::future_status result; if( m_drcEngine->IsCancelled() )
break;
while( true ) if( tp.wait_for_tasks_duration( std::chrono::milliseconds( 250 ) ) )
{ break;
result = task.wait_for( std::chrono::milliseconds( 250 ) );
reportProgress( done, count );
if( m_drcEngine->IsCancelled() )
break;
if( result == std::future_status::ready )
{
REPORT_DATA data = task.get();
int zoneA_idx = std::get<0>( data );
int zoneB_idx = std::get<1>( data );
VECTOR2I pt = std::get<2>( data );
int actual = std::get<3>( data );
DRC_CONSTRAINT constraint = std::get<4>( data );
PCB_LAYER_ID layer = std::get<5>( data );
if( zoneA_idx >= 0 )
{
ZONE* zoneA = m_board->m_DRCCopperZones[zoneA_idx];
ZONE* zoneB = m_board->m_DRCCopperZones[zoneB_idx];
std::shared_ptr<DRC_ITEM> drce;
if( constraint.IsNull() )
{
drce = DRC_ITEM::Create( DRCE_ZONES_INTERSECT );
wxString msg = _( "(intersecting zones must have distinct priorities)" );
drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg );
drce->SetItems( zoneA, zoneB );
reportViolation( drce, pt, layer );
}
else
{
drce = DRC_ITEM::Create( DRCE_CLEARANCE );
wxString msg = formatMsg( _( "(%s clearance %s; actual %s)" ),
constraint.GetName(),
constraint.GetValue().Min(),
std::max( actual, 0 ) );
drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg );
drce->SetItems( zoneA, zoneB );
drce->SetViolatingRule( constraint.GetParentRule() );
ReportAndShowPathCuToCu( drce, pt, layer, zoneA, zoneB, layer, actual );
}
}
break;
}
}
} }
}
}
namespace detail namespace detail
{ {

View File

@ -137,7 +137,7 @@ int DRC_TEST_PROVIDER_CREEPAGE::testCreepage( CREEPAGE_GRAPH& aGraph, int aNetCo
std::shared_ptr<GRAPH_NODE> NetB = aGraph.AddNetElements( aNetCodeB, aLayer, creepageValue ); std::shared_ptr<GRAPH_NODE> NetB = aGraph.AddNetElements( aNetCodeB, aLayer, creepageValue );
aGraph.GeneratePaths( creepageValue, aLayer, false ); aGraph.GeneratePaths( creepageValue, aLayer );
std::vector<std::shared_ptr<GRAPH_NODE>> temp_nodes; std::vector<std::shared_ptr<GRAPH_NODE>> temp_nodes;
@ -347,7 +347,7 @@ int DRC_TEST_PROVIDER_CREEPAGE::testCreepage()
graph.RemoveDuplicatedShapes(); graph.RemoveDuplicatedShapes();
graph.TransformCreepShapesToNodes( graph.m_shapeCollection ); graph.TransformCreepShapesToNodes( graph.m_shapeCollection );
graph.GeneratePaths( maxConstraint, Edge_Cuts, false ); graph.GeneratePaths( maxConstraint, Edge_Cuts );
int beNodeSize = graph.m_nodes.size(); int beNodeSize = graph.m_nodes.size();
int beConnectionsSize = graph.m_connections.size(); int beConnectionsSize = graph.m_connections.size();