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( const std::vector<int>& aNetCodes );
61
62private:
63 int testCreepage();
64 int testCreepage( CREEPAGE_GRAPH& aGraph, int aNetCodeA, int aNetCodeB, PCB_LAYER_ID aLayer );
65
67 int testCreepageV2( const std::vector<int>& aNetcodes );
68 void reportCreepageViolation( const CREEPAGE_RESULT& aResult, const DRC_CONSTRAINT& aConstraint,
69 PCB_LAYER_ID aLayer );
70
71 void CollectBoardEdges( std::vector<BOARD_ITEM*>& aVector,
72 std::vector<std::unique_ptr<PCB_SHAPE>>& aOwned );
73 void CollectNetCodes( std::vector<int>& aVector );
74
75 std::set<std::pair<const BOARD_ITEM*, const BOARD_ITEM*>> m_reportedPairs;
76};
77
78
80{
81 m_board = m_drcEngine->GetBoard();
82 m_reportedPairs.clear();
83
84 if( !m_drcEngine->HasRulesForConstraintType( CREEPAGE_CONSTRAINT ) )
85 {
86 REPORT_AUX( wxT( "No creepage constraints found. Tests not run." ) );
87 return true; // continue with other tests
88 }
89
90 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_CREEPAGE ) )
91 {
92 if( !reportPhase( _( "Checking creepage..." ) ) )
93 return false; // DRC cancelled
94
96 }
97
98 return !m_drcEngine->IsCancelled();
99}
100
101
102int DRC_TEST_PROVIDER_CREEPAGE::testCreepage( CREEPAGE_GRAPH& aGraph, int aNetCodeA, int aNetCodeB,
103 PCB_LAYER_ID aLayer )
104{
105 PCB_TRACK bci1( m_board );
106 PCB_TRACK bci2( m_board );
107 bci1.SetNetCode( aNetCodeA );
108 bci2.SetNetCode( aNetCodeB );
109 bci1.SetLayer( aLayer );
110 bci2.SetLayer( aLayer );
111
112 DRC_CONSTRAINT constraint;
113 constraint = m_drcEngine->EvalRules( CREEPAGE_CONSTRAINT, &bci1, &bci2, aLayer );
114 double creepageValue = constraint.Value().Min();
115 aGraph.SetTarget( creepageValue );
116
117 if( creepageValue <= 0 )
118 return 0;
119
120 // Let's make a quick "clearance test"
121 NETINFO_ITEM* netA = m_board->FindNet( aNetCodeA );
122 NETINFO_ITEM* netB = m_board->FindNet( aNetCodeB );
123
124 if ( !netA || !netB )
125 return 0;
126
127 if ( netA->GetBoundingBox().Distance( netB->GetBoundingBox() ) > creepageValue )
128 return 0;
129
130 std::shared_ptr<GRAPH_NODE> NetA = aGraph.AddNetElements( aNetCodeA, aLayer, creepageValue );
131 std::shared_ptr<GRAPH_NODE> NetB = aGraph.AddNetElements( aNetCodeB, aLayer, creepageValue );
132
133 aGraph.GeneratePaths( creepageValue, aLayer );
134
135 std::vector<std::shared_ptr<GRAPH_NODE>> temp_nodes;
136
137 std::copy_if( aGraph.m_nodes.begin(), aGraph.m_nodes.end(), std::back_inserter( temp_nodes ),
138 []( std::shared_ptr<GRAPH_NODE> aNode )
139 {
140 return !!aNode && aNode->m_parent && !aNode->m_parent->IsConductive()
141 && !aNode->m_connectDirectly && aNode->m_type == GRAPH_NODE::POINT;
142 } );
143
144 alg::for_all_pairs( temp_nodes.begin(), temp_nodes.end(),
145 [&]( std::shared_ptr<GRAPH_NODE> aN1, std::shared_ptr<GRAPH_NODE> aN2 )
146 {
147 if( aN1 == aN2 )
148 return;
149
150 if( !aN1 || !aN2 )
151 return;
152
153 if( !( aN1->m_parent ) || !( aN2->m_parent ) )
154 return;
155
156 if( ( aN1->m_parent ) != ( aN2->m_parent ) )
157 return;
158
159 aN1->m_parent->ConnectChildren( aN1, aN2, aGraph );
160 } );
161
162 std::vector<std::shared_ptr<GRAPH_CONNECTION>> shortestPath;
163 shortestPath.clear();
164 double distance = aGraph.Solve( NetA, NetB, shortestPath );
165
166 if( !shortestPath.empty() && ( shortestPath.size() >= 4 ) && ( distance - creepageValue < 0 ) )
167 {
168 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CREEPAGE );
169 drcItem->SetErrorDetail( formatMsg( _( "(%s creepage %s; actual %s)" ),
170 constraint.GetName(),
171 creepageValue,
172 distance ) );
173 drcItem->SetViolatingRule( constraint.GetParentRule() );
174
175 std::shared_ptr<GRAPH_CONNECTION> gc1 = shortestPath[1];
176 std::shared_ptr<GRAPH_CONNECTION> gc2 = shortestPath[shortestPath.size() - 2];
177
178 if( gc1->n1 && gc2->n2 )
179 {
180 const BOARD_ITEM* item1 = gc1->n1->m_parent->GetParent();
181 const BOARD_ITEM* item2 = gc2->n2->m_parent->GetParent();
182
183 if( m_reportedPairs.insert( std::make_pair( item1, item2 ) ).second )
184 drcItem->SetItems( item1, item2 );
185 else
186 return 1;
187 }
188
189 VECTOR2I startPoint = gc1->m_path.a2;
190 VECTOR2I endPoint = gc2->m_path.a2;
191 std::vector<PCB_SHAPE> path;
192
193 for( const std::shared_ptr<GRAPH_CONNECTION>& gc : shortestPath )
194 gc->GetShapes( path );
195
196 reportViolation( drcItem, gc1->m_path.a2, aLayer,
197 [&]( PCB_MARKER* aMarker )
198 {
199 aMarker->SetPath( path, startPoint, endPoint );
200 } );
201 }
202
203 return 1;
204}
205
206
207double DRC_TEST_PROVIDER_CREEPAGE::GetMaxConstraint( const std::vector<int>& aNetCodes )
208{
209 double maxConstraint = 0;
210 DRC_CONSTRAINT constraint;
211
212 PCB_TRACK bci1( m_board );
213 PCB_TRACK bci2( m_board );
214
215 alg::for_all_pairs( aNetCodes.begin(), aNetCodes.end(),
216 [&]( int aNet1, int aNet2 )
217 {
218 if( aNet1 == aNet2 )
219 return;
220
221 bci1.SetNetCode( aNet1 );
222 bci2.SetNetCode( aNet2 );
223
224 for( PCB_LAYER_ID layer : LSET::AllCuMask( m_board->GetCopperLayerCount() ) )
225 {
226 bci1.SetLayer( layer );
227 bci2.SetLayer( layer );
228 constraint = m_drcEngine->EvalRules( CREEPAGE_CONSTRAINT, &bci1, &bci2, layer );
229 double value = constraint.Value().Min();
230 maxConstraint = value > maxConstraint ? value : maxConstraint;
231 }
232 } );
233
234 return maxConstraint;
235}
236
237
238void DRC_TEST_PROVIDER_CREEPAGE::CollectNetCodes( std::vector<int>& aVector )
239{
240 NETCODES_MAP nets = m_board->GetNetInfo().NetsByNetcode();
241
242 for( auto it = nets.begin(); it != nets.end(); it++ )
243 aVector.push_back( it->first );
244}
245
246
247void DRC_TEST_PROVIDER_CREEPAGE::CollectBoardEdges( std::vector<BOARD_ITEM*>& aVector,
248 std::vector<std::unique_ptr<PCB_SHAPE>>& aOwned )
249{
250 if( !m_board )
251 return;
252
253 BuildCreepageBoardEdges( *m_board, aVector, aOwned, nullptr );
254}
255
256
258{
259 if( !m_board )
260 return -1;
261
262 std::vector<int> netcodes;
263
264 this->CollectNetCodes( netcodes );
265 double maxConstraint = GetMaxConstraint( netcodes );
266
267 if( maxConstraint <= 0 )
268 return 0;
269
270 if( ADVANCED_CFG::GetCfg().m_RealtimeCreepage )
271 return testCreepageV2( netcodes );
272
273 SHAPE_POLY_SET outline;
274
275 // Subtract NPTH holes from the outline polygon so candidate-path midpoint tests
276 // reject creepage segments routed through slot interiors. Without subtraction,
277 // a midpoint inside an NPTH oval still counts as "inside the board" and the
278 // creepage validator accepts straight-through-slot paths (issue #24286).
279 bool hasValidOutline = m_board->GetBoardPolygonOutlines( outline, false, nullptr, false, true );
280
281 const DRAWINGS drawings = m_board->Drawings();
282 CREEPAGE_GRAPH graph( *m_board );
283
284 if( ADVANCED_CFG::GetCfg().m_EnableCreepageSlot )
285 graph.m_minGrooveWidth = m_board->GetDesignSettings().m_MinGrooveWidth;
286 else
287 graph.m_minGrooveWidth = 0;
288
289 graph.m_boardOutline = hasValidOutline ? &outline : nullptr;
290
291 this->CollectBoardEdges( graph.m_boardEdge, graph.m_ownedBoardEdges );
295
296 graph.GeneratePaths( maxConstraint, Edge_Cuts );
297
298 int beNodeSize = graph.m_nodes.size();
299 int beConnectionsSize = graph.m_connections.size();
300 bool prevTestChangedGraph = false;
301
302 size_t current = 0;
303 size_t total = ( netcodes.size() * ( netcodes.size() - 1 ) ) / 2 * m_board->GetCopperLayerCount();
304 LSET layers = m_board->GetLayerSet();
305
306 alg::for_all_pairs( netcodes.begin(), netcodes.end(),
307 [&]( int aNet1, int aNet2 )
308 {
309 if( aNet1 == aNet2 )
310 return;
311
312 for( auto it = layers.copper_layers_begin(); it != layers.copper_layers_end(); ++it )
313 {
314 PCB_LAYER_ID layer = *it;
315
316 reportProgress( current++, total );
317
318 if( prevTestChangedGraph )
319 graph.TruncateToPrefix( beNodeSize, beConnectionsSize );
320
321 prevTestChangedGraph = testCreepage( graph, aNet1, aNet2, layer );
322 }
323 } );
324
325 return 1;
326}
327
328
330 const DRC_CONSTRAINT& aConstraint,
331 PCB_LAYER_ID aLayer )
332{
333 if( !aResult.m_itemA || !aResult.m_itemB )
334 return;
335
336 if( !m_reportedPairs.insert( std::make_pair( aResult.m_itemA, aResult.m_itemB ) ).second )
337 return;
338
339 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CREEPAGE );
340 drcItem->SetErrorDetail( formatMsg( _( "(%s creepage %s; actual %s)" ), aConstraint.GetName(),
341 aResult.m_constraint, aResult.m_distance ) );
342 drcItem->SetViolatingRule( aConstraint.GetParentRule() );
343 drcItem->SetItems( aResult.m_itemA, aResult.m_itemB );
344
345 // reportViolation invokes the callback inline, so the marker can reference aResult directly
346 reportViolation( drcItem, aResult.m_start, aLayer,
347 [&]( PCB_MARKER* aMarker )
348 {
349 aMarker->SetPath( aResult.m_path, aResult.m_start, aResult.m_end );
350 } );
351}
352
353
354int DRC_TEST_PROVIDER_CREEPAGE::testCreepageV2( const std::vector<int>& aNetcodes )
355{
356 CREEPAGE_ENGINE engine( *m_board );
357
358 if( ADVANCED_CFG::GetCfg().m_EnableCreepageSlot )
359 engine.SetMinGrooveWidth( m_board->GetDesignSettings().m_MinGrooveWidth );
360
361 LSET layers = m_board->GetLayerSet();
362 size_t current = 0;
363 size_t total = ( aNetcodes.size() * ( aNetcodes.size() - 1 ) ) / 2 * m_board->GetCopperLayerCount();
364
365 alg::for_all_pairs( aNetcodes.begin(), aNetcodes.end(),
366 [&]( int aNet1, int aNet2 )
367 {
368 if( aNet1 == aNet2 )
369 return;
370
371 for( auto it = layers.copper_layers_begin(); it != layers.copper_layers_end(); ++it )
372 {
373 PCB_LAYER_ID layer = *it;
374
375 reportProgress( current++, total );
376
377 PCB_TRACK bci1( m_board );
378 PCB_TRACK bci2( m_board );
379 bci1.SetNetCode( aNet1 );
380 bci2.SetNetCode( aNet2 );
381 bci1.SetLayer( layer );
382 bci2.SetLayer( layer );
383
384 DRC_CONSTRAINT constraint =
385 m_drcEngine->EvalRules( CREEPAGE_CONSTRAINT, &bci1, &bci2, layer );
386 double creepageValue = constraint.Value().Min();
387
388 std::optional<CREEPAGE_RESULT> result =
389 engine.SolveNetPairWholeBoard( aNet1, aNet2, layer, creepageValue );
390
391 if( result )
392 reportCreepageViolation( *result, constraint, layer );
393 }
394 } );
395
396 return 1;
397}
398
399
400namespace detail
401{
403}
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:81
ecoord_type Distance(const Vec &aP) const
Definition box2.h:793
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:204
MINOPTMAX< int > & Value()
Definition drc_rule.h:197
DRC_RULE * GetParentRule() const
Definition drc_rule.h:200
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:417
virtual ~DRC_TEST_PROVIDER_CREEPAGE()=default
double GetMaxConstraint(const std::vector< int > &aNetCodes)
std::set< std::pair< const BOARD_ITEM *, const BOARD_ITEM * > > m_reportedPairs
virtual const wxString GetName() const override
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).
int testCreepageV2(const std::vector< int > &aNetcodes)
Realtime (V2) engine batch path, selected by the RealtimeCreepage advanced config flag.
void reportCreepageViolation(const CREEPAGE_RESULT &aResult, const DRC_CONSTRAINT &aConstraint, PCB_LAYER_ID aLayer)
void CollectNetCodes(std::vector< int > &aVector)
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:46
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Represent a set of closed polygons.
The common library.
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:41
@ 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:215
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