KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_creepage.cpp
Go to the documentation of this file.
1/*
2 * Copyright The KiCad Developers.
3 * Copyright (C) 2024 Fabien Corona f.corona<at>laposte.net
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19#include <common.h>
20#include <macros.h>
22#include <footprint.h>
23#include <pad.h>
24#include <pcb_track.h>
25#include <pcb_shape.h>
26#include <zone.h>
27#include <advanced_config.h>
28#include <geometry/shape_rect.h>
29#include <geometry/seg.h>
31#include <drc/drc_item.h>
32#include <drc/drc_rule.h>
36
38
39
40/*
41 Physical creepage tests.
42
43 Errors generated:
44 - DRCE_CREEPAGE
45*/
46
48{
49public:
53
54 virtual ~DRC_TEST_PROVIDER_CREEPAGE() = default;
55
56 virtual bool Run() override;
57
58 virtual const wxString GetName() const override { return wxT( "creepage" ); };
59
60 double GetMaxConstraint();
61
62private:
63 int testCreepage();
64 int testCreepage( CREEPAGE_GRAPH& aGraph, int aNetCodeA, int aNetCodeB, PCB_LAYER_ID aLayer,
65 double aMaxCreepage, bool aHasConditional );
66
68 int testCreepageV2( const std::vector<int>& aNetcodes, double aMaxCreepage, bool aHasConditional );
69
71 DRC_CONSTRAINT placeholderConstraint( int aNetCodeA, int aNetCodeB, PCB_LAYER_ID aLayer );
72 void reportCreepageViolation( const CREEPAGE_RESULT& aResult, const DRC_CONSTRAINT& aConstraint,
73 PCB_LAYER_ID aLayer );
74
75 void CollectBoardEdges( std::vector<BOARD_ITEM*>& aVector,
76 std::vector<std::unique_ptr<PCB_SHAPE>>& aOwned );
77 void CollectNetCodes( std::vector<int>& aVector );
78
79 std::set<std::pair<const BOARD_ITEM*, const BOARD_ITEM*>> m_reportedPairs;
80};
81
82
84{
85 m_board = m_drcEngine->GetBoard();
86 m_reportedPairs.clear();
87
88 if( !m_drcEngine->HasRulesForConstraintType( CREEPAGE_CONSTRAINT ) )
89 {
90 REPORT_AUX( wxT( "No creepage constraints found. Tests not run." ) );
91 return true; // continue with other tests
92 }
93
94 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_CREEPAGE ) )
95 {
96 if( !reportPhase( _( "Checking creepage..." ) ) )
97 return false; // DRC cancelled
98
100 }
101
102 return !m_drcEngine->IsCancelled();
103}
104
105
107 PCB_LAYER_ID aLayer )
108{
109 PCB_TRACK bci1( m_board );
110 PCB_TRACK bci2( m_board );
111 bci1.SetNetCode( aNetCodeA );
112 bci2.SetNetCode( aNetCodeB );
113 bci1.SetLayer( aLayer );
114 bci2.SetLayer( aLayer );
115
116 return m_drcEngine->EvalRules( CREEPAGE_CONSTRAINT, &bci1, &bci2, aLayer );
117}
118
119
120int DRC_TEST_PROVIDER_CREEPAGE::testCreepage( CREEPAGE_GRAPH& aGraph, int aNetCodeA, int aNetCodeB,
121 PCB_LAYER_ID aLayer, double aMaxCreepage,
122 bool aHasConditional )
123{
124 // Placeholders at the origin never satisfy intersectsArea(), so conditional rules need a
125 // worst-case target and a per-path resolve below. Path cost scales with target, hence the split
126 DRC_CONSTRAINT netConstraint;
127 double target;
128
129 if( aHasConditional )
130 {
131 target = aMaxCreepage;
132 }
133 else
134 {
135 netConstraint = placeholderConstraint( aNetCodeA, aNetCodeB, aLayer );
136 target = netConstraint.Value().Min();
137 }
138
139 aGraph.SetTarget( target );
140
141 if( target <= 0 )
142 return 0;
143
144 // Let's make a quick "clearance test"
145 NETINFO_ITEM* netA = m_board->FindNet( aNetCodeA );
146 NETINFO_ITEM* netB = m_board->FindNet( aNetCodeB );
147
148 if ( !netA || !netB )
149 return 0;
150
151 if ( netA->GetBoundingBox().Distance( netB->GetBoundingBox() ) > target )
152 return 0;
153
154 std::shared_ptr<GRAPH_NODE> NetA = aGraph.AddNetElements( aNetCodeA, aLayer, target );
155 std::shared_ptr<GRAPH_NODE> NetB = aGraph.AddNetElements( aNetCodeB, aLayer, target );
156
157 aGraph.GeneratePaths( target, aLayer );
158
159 std::vector<std::shared_ptr<GRAPH_NODE>> temp_nodes;
160
161 std::copy_if( aGraph.m_nodes.begin(), aGraph.m_nodes.end(), std::back_inserter( temp_nodes ),
162 []( std::shared_ptr<GRAPH_NODE> aNode )
163 {
164 return !!aNode && aNode->m_parent && !aNode->m_parent->IsConductive()
165 && !aNode->m_connectDirectly && aNode->m_type == GRAPH_NODE::POINT;
166 } );
167
168 alg::for_all_pairs( temp_nodes.begin(), temp_nodes.end(),
169 [&]( std::shared_ptr<GRAPH_NODE> aN1, std::shared_ptr<GRAPH_NODE> aN2 )
170 {
171 if( aN1 == aN2 )
172 return;
173
174 if( !aN1 || !aN2 )
175 return;
176
177 if( !( aN1->m_parent ) || !( aN2->m_parent ) )
178 return;
179
180 if( ( aN1->m_parent ) != ( aN2->m_parent ) )
181 return;
182
183 aN1->m_parent->ConnectChildren( aN1, aN2, aGraph );
184 } );
185
186 std::vector<std::shared_ptr<GRAPH_CONNECTION>> shortestPath;
187 shortestPath.clear();
188 double distance = aGraph.Solve( NetA, NetB, shortestPath );
189
190 if( shortestPath.empty() || shortestPath.size() < 4 )
191 return 1;
192
193 std::shared_ptr<GRAPH_CONNECTION> gc1 = shortestPath[1];
194 std::shared_ptr<GRAPH_CONNECTION> gc2 = shortestPath[shortestPath.size() - 2];
195
196 const BOARD_ITEM* item1 = gc1->n1 && gc1->n1->m_parent ? gc1->n1->m_parent->GetParent() : nullptr;
197 const BOARD_ITEM* item2 = gc2->n2 && gc2->n2->m_parent ? gc2->n2->m_parent->GetParent() : nullptr;
198
199 DRC_CONSTRAINT constraint;
200
201 if( !aHasConditional )
202 constraint = netConstraint;
203 else if( item1 && item2 )
204 constraint = m_drcEngine->EvalRules( CREEPAGE_CONSTRAINT, item1, item2, aLayer );
205 else
206 constraint = placeholderConstraint( aNetCodeA, aNetCodeB, aLayer );
207
208 double creepageValue = constraint.Value().Min();
209
210 if( creepageValue <= 0 || distance - creepageValue >= 0 )
211 return 1;
212
213 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CREEPAGE );
214 drcItem->SetErrorDetail( formatMsg( _( "(%s creepage %s; actual %s)" ),
215 constraint.GetName(),
216 creepageValue,
217 distance ) );
218 drcItem->SetViolatingRule( constraint.GetParentRule() );
219
220 if( item1 && item2 )
221 {
222 if( m_reportedPairs.insert( std::make_pair( item1, item2 ) ).second )
223 drcItem->SetItems( item1, item2 );
224 else
225 return 1;
226 }
227
228 VECTOR2I startPoint = gc1->m_path.a2;
229 VECTOR2I endPoint = gc2->m_path.a2;
230 std::vector<PCB_SHAPE> path;
231
232 for( const std::shared_ptr<GRAPH_CONNECTION>& gc : shortestPath )
233 gc->GetShapes( path );
234
235 reportViolation( drcItem, gc1->m_path.a2, aLayer,
236 [&]( PCB_MARKER* aMarker )
237 {
238 aMarker->SetPath( path, startPoint, endPoint );
239 } );
240
241 return 1;
242}
243
244
246{
247 // Upper bound to size the graph only; the governing rule is resolved per path in testCreepage.
248 // Per-pair placeholder eval would miss conditional rules raising the value inside an area
249 DRC_CONSTRAINT worst;
250
251 if( m_drcEngine->QueryWorstConstraint( CREEPAGE_CONSTRAINT, worst ) )
252 return worst.Value().Min();
253
254 return 0;
255}
256
257
258void DRC_TEST_PROVIDER_CREEPAGE::CollectNetCodes( std::vector<int>& aVector )
259{
260 NETCODES_MAP nets = m_board->GetNetInfo().NetsByNetcode();
261
262 for( auto it = nets.begin(); it != nets.end(); it++ )
263 aVector.push_back( it->first );
264}
265
266
267void DRC_TEST_PROVIDER_CREEPAGE::CollectBoardEdges( std::vector<BOARD_ITEM*>& aVector,
268 std::vector<std::unique_ptr<PCB_SHAPE>>& aOwned )
269{
270 if( !m_board )
271 return;
272
273 BuildCreepageBoardEdges( *m_board, aVector, aOwned, nullptr );
274}
275
276
278{
279 if( !m_board )
280 return -1;
281
282 std::vector<int> netcodes;
283
284 this->CollectNetCodes( netcodes );
285 double maxConstraint = GetMaxConstraint();
286
287 if( maxConstraint <= 0 )
288 return 0;
289
290 // No conditional rule means the per-pair placeholder target is exact and the graph stays tight
291 bool hasConditional = m_drcEngine->HasConditionalConstraint( CREEPAGE_CONSTRAINT );
292
293 if( ADVANCED_CFG::GetCfg().m_RealtimeCreepage )
294 return testCreepageV2( netcodes, maxConstraint, hasConditional );
295
296 SHAPE_POLY_SET outline;
297
298 // Subtract NPTH holes from the outline polygon so candidate-path midpoint tests
299 // reject creepage segments routed through slot interiors. Without subtraction,
300 // a midpoint inside an NPTH oval still counts as "inside the board" and the
301 // creepage validator accepts straight-through-slot paths (issue #24286).
302 bool hasValidOutline = m_board->GetBoardPolygonOutlines( outline, false, nullptr, false, true );
303
304 const DRAWINGS drawings = m_board->Drawings();
305 CREEPAGE_GRAPH graph( *m_board );
306
307 if( ADVANCED_CFG::GetCfg().m_EnableCreepageSlot )
308 graph.m_minGrooveWidth = m_board->GetDesignSettings().m_MinGrooveWidth;
309 else
310 graph.m_minGrooveWidth = 0;
311
312 graph.m_boardOutline = hasValidOutline ? &outline : nullptr;
313
314 this->CollectBoardEdges( graph.m_boardEdge, graph.m_ownedBoardEdges );
318
319 graph.GeneratePaths( maxConstraint, Edge_Cuts );
320
321 int beNodeSize = graph.m_nodes.size();
322 int beConnectionsSize = graph.m_connections.size();
323 bool prevTestChangedGraph = false;
324
325 size_t current = 0;
326 size_t total = ( netcodes.size() * ( netcodes.size() - 1 ) ) / 2 * m_board->GetCopperLayerCount();
327 LSET layers = m_board->GetLayerSet();
328
329 alg::for_all_pairs( netcodes.begin(), netcodes.end(),
330 [&]( int aNet1, int aNet2 )
331 {
332 if( aNet1 == aNet2 )
333 return;
334
335 for( auto it = layers.copper_layers_begin(); it != layers.copper_layers_end(); ++it )
336 {
337 PCB_LAYER_ID layer = *it;
338
339 reportProgress( current++, total );
340
341 if( prevTestChangedGraph )
342 graph.TruncateToPrefix( beNodeSize, beConnectionsSize );
343
344 prevTestChangedGraph = testCreepage( graph, aNet1, aNet2, layer,
345 maxConstraint, hasConditional );
346 }
347 } );
348
349 return 1;
350}
351
352
354 const DRC_CONSTRAINT& aConstraint,
355 PCB_LAYER_ID aLayer )
356{
357 if( !aResult.m_itemA || !aResult.m_itemB )
358 return;
359
360 if( !m_reportedPairs.insert( std::make_pair( aResult.m_itemA, aResult.m_itemB ) ).second )
361 return;
362
363 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CREEPAGE );
364 drcItem->SetErrorDetail( formatMsg( _( "(%s creepage %s; actual %s)" ), aConstraint.GetName(),
365 aConstraint.GetValue().Min(), aResult.m_distance ) );
366 drcItem->SetViolatingRule( aConstraint.GetParentRule() );
367 drcItem->SetItems( aResult.m_itemA, aResult.m_itemB );
368
369 // reportViolation invokes the callback inline, so the marker can reference aResult directly
370 reportViolation( drcItem, aResult.m_start, aLayer,
371 [&]( PCB_MARKER* aMarker )
372 {
373 aMarker->SetPath( aResult.m_path, aResult.m_start, aResult.m_end );
374 } );
375}
376
377
378int DRC_TEST_PROVIDER_CREEPAGE::testCreepageV2( const std::vector<int>& aNetcodes, double aMaxCreepage,
379 bool aHasConditional )
380{
381 CREEPAGE_ENGINE engine( *m_board );
382
383 if( ADVANCED_CFG::GetCfg().m_EnableCreepageSlot )
384 engine.SetMinGrooveWidth( m_board->GetDesignSettings().m_MinGrooveWidth );
385
386 LSET layers = m_board->GetLayerSet();
387 size_t current = 0;
388 size_t total = ( aNetcodes.size() * ( aNetcodes.size() - 1 ) ) / 2 * m_board->GetCopperLayerCount();
389
390 alg::for_all_pairs( aNetcodes.begin(), aNetcodes.end(),
391 [&]( int aNet1, int aNet2 )
392 {
393 if( aNet1 == aNet2 )
394 return;
395
396 for( auto it = layers.copper_layers_begin(); it != layers.copper_layers_end(); ++it )
397 {
398 PCB_LAYER_ID layer = *it;
399
400 reportProgress( current++, total );
401
402 // Conditional rules solve at worst-case so no path is pruned, then resolve
403 // from the conductors the engine returns; otherwise the placeholder is exact
404 DRC_CONSTRAINT netConstraint;
405 double target;
406
407 if( aHasConditional )
408 {
409 target = aMaxCreepage;
410 }
411 else
412 {
413 netConstraint = placeholderConstraint( aNet1, aNet2, layer );
414 target = netConstraint.Value().Min();
415 }
416
417 std::optional<CREEPAGE_RESULT> result =
418 engine.SolveNetPairWholeBoard( aNet1, aNet2, layer, target );
419
420 if( !result || !result->m_itemA || !result->m_itemB )
421 continue;
422
423 DRC_CONSTRAINT constraint =
424 aHasConditional
425 ? m_drcEngine->EvalRules( CREEPAGE_CONSTRAINT, result->m_itemA,
426 result->m_itemB, layer )
427 : netConstraint;
428 double creepageValue = constraint.Value().Min();
429
430 if( creepageValue > 0 && result->m_distance - creepageValue < 0 )
431 reportCreepageViolation( *result, constraint, layer );
432 }
433 } );
434
435 return 1;
436}
437
438
439namespace detail
440{
442}
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
virtual bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
ecoord_type Distance(const Vec &aP) const
Definition box2.h:792
Reusable creepage solver shared by the batch DRC provider and the realtime drag overlay.
void SetMinGrooveWidth(int aWidth)
A graph with nodes and connections for creepage calculation.
void SetTarget(double aTarget)
double Solve(std::shared_ptr< GRAPH_NODE > &aFrom, std::shared_ptr< GRAPH_NODE > &aTo, std::vector< std::shared_ptr< GRAPH_CONNECTION > > &aResult)
std::vector< CREEP_SHAPE * > m_shapeCollection
void GeneratePaths(double aMaxWeight, PCB_LAYER_ID aLayer, const std::set< int > *aRelevantNets=nullptr)
Generate creepage paths between graph nodes.
void TransformCreepShapesToNodes(std::vector< CREEP_SHAPE * > &aShapes)
SHAPE_POLY_SET * m_boardOutline
std::vector< BOARD_ITEM * > m_boardEdge
std::vector< std::shared_ptr< GRAPH_NODE > > m_nodes
std::vector< std::shared_ptr< GRAPH_CONNECTION > > m_connections
std::shared_ptr< GRAPH_NODE > AddNetElements(int aNetCode, PCB_LAYER_ID aLayer, int aMaxCreepage)
std::vector< std::unique_ptr< PCB_SHAPE > > m_ownedBoardEdges
wxString GetName() const
Definition drc_rule.h:208
MINOPTMAX< int > & Value()
Definition drc_rule.h:201
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:200
DRC_RULE * GetParentRule() const
Definition drc_rule.h:204
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:444
virtual ~DRC_TEST_PROVIDER_CREEPAGE()=default
std::set< std::pair< const BOARD_ITEM *, const BOARD_ITEM * > > m_reportedPairs
virtual const wxString GetName() const override
DRC_CONSTRAINT placeholderConstraint(int aNetCodeA, int aNetCodeB, PCB_LAYER_ID aLayer)
Constraint from geometry-less net/layer placeholders; exact only when no rule is conditional.
void CollectBoardEdges(std::vector< BOARD_ITEM * > &aVector, std::vector< std::unique_ptr< PCB_SHAPE > > &aOwned)
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
void reportCreepageViolation(const CREEPAGE_RESULT &aResult, const DRC_CONSTRAINT &aConstraint, PCB_LAYER_ID aLayer)
void CollectNetCodes(std::vector< int > &aVector)
int testCreepageV2(const std::vector< int > &aNetcodes, double aMaxCreepage, bool aHasConditional)
Realtime (V2) batch path, gated by the RealtimeCreepage advanced config flag.
virtual bool reportPhase(const wxString &aStageName)
void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::function< void(PCB_MARKER *)> &aPathGenerator=[](PCB_MARKER *){})
wxString formatMsg(const wxString &aFormatString, const wxString &aSource, double aConstraint, double aActual, EDA_DATA_TYPE aDataType=EDA_DATA_TYPE::DISTANCE)
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
T Min() const
Definition minoptmax.h:29
Handle the data for a net.
Definition netinfo.h:50
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Represent a set of closed polygons.
void BuildCreepageBoardEdges(BOARD &aBoard, std::vector< BOARD_ITEM * > &aVector, std::vector< std::unique_ptr< PCB_SHAPE > > &aOwned, const std::set< const BOARD_ITEM * > *aExclude)
Collect the board-edge items used by the creepage graph.
@ DRCE_CREEPAGE
Definition drc_item.h:42
@ CREEPAGE_CONSTRAINT
Definition drc_rule.h:52
#define REPORT_AUX(s)
#define _(s)
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Edge_Cuts
Definition layer_ids.h:108
This file contains miscellaneous commonly used macros and functions.
void for_all_pairs(_InputIterator __first, _InputIterator __last, _Function __f)
Apply a function to every possible pair of elements of a sequence.
Definition kicad_algo.h:80
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
std::map< int, NETINFO_ITEM * > NETCODES_MAP
Definition netinfo.h:225
std::deque< BOARD_ITEM * > DRAWINGS
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
Result of a single creepage query between two nets on one layer.
const BOARD_ITEM * m_itemB
const BOARD_ITEM * m_itemA
std::string path
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683