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, you may find one here:
17 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
18 * or you may search the http://www.gnu.org website for the version 2 license,
19 * or you may write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21 */
22
23#include <common.h>
24#include <macros.h>
26#include <footprint.h>
27#include <pad.h>
28#include <pcb_track.h>
29#include <pcb_shape.h>
30#include <zone.h>
31#include <advanced_config.h>
32#include <geometry/shape_rect.h>
33#include <geometry/seg.h>
35#include <drc/drc_item.h>
36#include <drc/drc_rule.h>
39
41
42
43/*
44 Physical creepage tests.
45
46 Errors generated:
47 - DRCE_CREEPAGE
48*/
49
51{
52public:
56
57 virtual ~DRC_TEST_PROVIDER_CREEPAGE() = default;
58
59 virtual bool Run() override;
60
61 virtual const wxString GetName() const override { return wxT( "creepage" ); };
62
63 double GetMaxConstraint( const std::vector<int>& aNetCodes );
64
65private:
66 int testCreepage();
67 int testCreepage( CREEPAGE_GRAPH& aGraph, int aNetCodeA, int aNetCodeB, PCB_LAYER_ID aLayer );
68
69 void CollectBoardEdges( std::vector<BOARD_ITEM*>& aVector,
70 std::vector<std::unique_ptr<PCB_SHAPE>>& aOwned );
71 void CollectNetCodes( std::vector<int>& aVector );
72
73 std::set<std::pair<const BOARD_ITEM*, const BOARD_ITEM*>> m_reportedPairs;
74};
75
76
78{
79 m_board = m_drcEngine->GetBoard();
80 m_reportedPairs.clear();
81
82 if( !m_drcEngine->HasRulesForConstraintType( CREEPAGE_CONSTRAINT ) )
83 {
84 REPORT_AUX( wxT( "No creepage constraints found. Tests not run." ) );
85 return true; // continue with other tests
86 }
87
88 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_CREEPAGE ) )
89 {
90 if( !reportPhase( _( "Checking creepage..." ) ) )
91 return false; // DRC cancelled
92
94 }
95
96 return !m_drcEngine->IsCancelled();
97}
98
99
100int DRC_TEST_PROVIDER_CREEPAGE::testCreepage( CREEPAGE_GRAPH& aGraph, int aNetCodeA, int aNetCodeB,
101 PCB_LAYER_ID aLayer )
102{
103 PCB_TRACK bci1( m_board );
104 PCB_TRACK bci2( m_board );
105 bci1.SetNetCode( aNetCodeA );
106 bci2.SetNetCode( aNetCodeB );
107 bci1.SetLayer( aLayer );
108 bci2.SetLayer( aLayer );
109
110 DRC_CONSTRAINT constraint;
111 constraint = m_drcEngine->EvalRules( CREEPAGE_CONSTRAINT, &bci1, &bci2, aLayer );
112 double creepageValue = constraint.Value().Min();
113 aGraph.SetTarget( creepageValue );
114
115 if( creepageValue <= 0 )
116 return 0;
117
118 // Let's make a quick "clearance test"
119 NETINFO_ITEM* netA = m_board->FindNet( aNetCodeA );
120 NETINFO_ITEM* netB = m_board->FindNet( aNetCodeB );
121
122 if ( !netA || !netB )
123 return 0;
124
125 if ( netA->GetBoundingBox().Distance( netB->GetBoundingBox() ) > creepageValue )
126 return 0;
127
128 std::shared_ptr<GRAPH_NODE> NetA = aGraph.AddNetElements( aNetCodeA, aLayer, creepageValue );
129 std::shared_ptr<GRAPH_NODE> NetB = aGraph.AddNetElements( aNetCodeB, aLayer, creepageValue );
130
131 aGraph.GeneratePaths( creepageValue, aLayer );
132
133 std::vector<std::shared_ptr<GRAPH_NODE>> temp_nodes;
134
135 std::copy_if( aGraph.m_nodes.begin(), aGraph.m_nodes.end(), std::back_inserter( temp_nodes ),
136 []( std::shared_ptr<GRAPH_NODE> aNode )
137 {
138 return !!aNode && aNode->m_parent && !aNode->m_parent->IsConductive()
139 && !aNode->m_connectDirectly && aNode->m_type == GRAPH_NODE::POINT;
140 } );
141
142 alg::for_all_pairs( temp_nodes.begin(), temp_nodes.end(),
143 [&]( std::shared_ptr<GRAPH_NODE> aN1, std::shared_ptr<GRAPH_NODE> aN2 )
144 {
145 if( aN1 == aN2 )
146 return;
147
148 if( !aN1 || !aN2 )
149 return;
150
151 if( !( aN1->m_parent ) || !( aN2->m_parent ) )
152 return;
153
154 if( ( aN1->m_parent ) != ( aN2->m_parent ) )
155 return;
156
157 aN1->m_parent->ConnectChildren( aN1, aN2, aGraph );
158 } );
159
160 std::vector<std::shared_ptr<GRAPH_CONNECTION>> shortestPath;
161 shortestPath.clear();
162 double distance = aGraph.Solve( NetA, NetB, shortestPath );
163
164 if( !shortestPath.empty() && ( shortestPath.size() >= 4 ) && ( distance - creepageValue < 0 ) )
165 {
166 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CREEPAGE );
167 drcItem->SetErrorDetail( formatMsg( _( "(%s creepage %s; actual %s)" ),
168 constraint.GetName(),
169 creepageValue,
170 distance ) );
171 drcItem->SetViolatingRule( constraint.GetParentRule() );
172
173 std::shared_ptr<GRAPH_CONNECTION> gc1 = shortestPath[1];
174 std::shared_ptr<GRAPH_CONNECTION> gc2 = shortestPath[shortestPath.size() - 2];
175
176 if( gc1->n1 && gc2->n2 )
177 {
178 const BOARD_ITEM* item1 = gc1->n1->m_parent->GetParent();
179 const BOARD_ITEM* item2 = gc2->n2->m_parent->GetParent();
180
181 if( m_reportedPairs.insert( std::make_pair( item1, item2 ) ).second )
182 drcItem->SetItems( item1, item2 );
183 else
184 return 1;
185 }
186
187 VECTOR2I startPoint = gc1->m_path.a2;
188 VECTOR2I endPoint = gc2->m_path.a2;
189 std::vector<PCB_SHAPE> path;
190
191 for( const std::shared_ptr<GRAPH_CONNECTION>& gc : shortestPath )
192 gc->GetShapes( path );
193
194 reportViolation( drcItem, gc1->m_path.a2, aLayer,
195 [&]( PCB_MARKER* aMarker )
196 {
197 aMarker->SetPath( path, startPoint, endPoint );
198 } );
199 }
200
201 return 1;
202}
203
204
205double DRC_TEST_PROVIDER_CREEPAGE::GetMaxConstraint( const std::vector<int>& aNetCodes )
206{
207 double maxConstraint = 0;
208 DRC_CONSTRAINT constraint;
209
210 PCB_TRACK bci1( m_board );
211 PCB_TRACK bci2( m_board );
212
213 alg::for_all_pairs( aNetCodes.begin(), aNetCodes.end(),
214 [&]( int aNet1, int aNet2 )
215 {
216 if( aNet1 == aNet2 )
217 return;
218
219 bci1.SetNetCode( aNet1 );
220 bci2.SetNetCode( aNet2 );
221
222 for( PCB_LAYER_ID layer : LSET::AllCuMask( m_board->GetCopperLayerCount() ) )
223 {
224 bci1.SetLayer( layer );
225 bci2.SetLayer( layer );
226 constraint = m_drcEngine->EvalRules( CREEPAGE_CONSTRAINT, &bci1, &bci2, layer );
227 double value = constraint.Value().Min();
228 maxConstraint = value > maxConstraint ? value : maxConstraint;
229 }
230 } );
231
232 return maxConstraint;
233}
234
235
236void DRC_TEST_PROVIDER_CREEPAGE::CollectNetCodes( std::vector<int>& aVector )
237{
238 NETCODES_MAP nets = m_board->GetNetInfo().NetsByNetcode();
239
240 for( auto it = nets.begin(); it != nets.end(); it++ )
241 aVector.push_back( it->first );
242}
243
244
245void DRC_TEST_PROVIDER_CREEPAGE::CollectBoardEdges( std::vector<BOARD_ITEM*>& aVector,
246 std::vector<std::unique_ptr<PCB_SHAPE>>& aOwned )
247{
248 if( !m_board )
249 return;
250
251 const int errorMax = m_board->GetDesignSettings().m_MaxError;
252
253 // The creepage graph and intersection tests only handle SEGMENT/ARC/CIRCLE/
254 // RECTANGLE/POLY, so Bezier curves on Edge.Cuts must be flattened to straight
255 // segments owned by aOwned. Without this, any Bezier stretch of the board edge
256 // is silently ignored and creepage paths pass straight through it.
257 auto addEdgeDrawing = [&]( BOARD_ITEM* aDrawing )
258 {
259 if( !aDrawing || !aDrawing->IsOnLayer( Edge_Cuts ) )
260 return;
261
262 // Downstream code in drc_creepage_utils does a static_cast<PCB_SHAPE*> on
263 // every item in m_boardEdge, so non-shape items (text, dimensions, ...)
264 // placed on Edge.Cuts must not enter the graph.
265 PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( aDrawing );
266
267 if( !shape )
268 return;
269
270 if( shape->GetShape() != SHAPE_T::BEZIER )
271 {
272 aVector.push_back( shape );
273 return;
274 }
275
276 shape->RebuildBezierToSegmentsPointsList( errorMax );
277 const std::vector<VECTOR2I>& pts = shape->GetBezierPoints();
278
279 for( size_t i = 1; i < pts.size(); ++i )
280 {
281 if( pts[i - 1] == pts[i] )
282 continue;
283
284 auto seg = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::SEGMENT );
285 seg->SetStart( pts[i - 1] );
286 seg->SetEnd( pts[i] );
287 aVector.push_back( seg.get() );
288 aOwned.push_back( std::move( seg ) );
289 }
290 };
291
292 for( BOARD_ITEM* drawing : m_board->Drawings() )
293 addEdgeDrawing( drawing );
294
295 for( FOOTPRINT* fp : m_board->Footprints() )
296 {
297 if( !fp )
298 continue;
299
300 for( BOARD_ITEM* drawing : fp->GraphicalItems() )
301 addEdgeDrawing( drawing );
302 }
303
304 for( const PAD* p : m_board->GetPads() )
305 {
306 if( !p )
307 continue;
308
309 if( p->GetAttribute() != PAD_ATTRIB::NPTH )
310 continue;
311
312 std::shared_ptr<SHAPE_SEGMENT> hole = p->GetEffectiveHoleShape();
313
314 if( !hole )
315 continue;
316
317 VECTOR2I ptA = hole->GetSeg().A;
318 VECTOR2I ptB = hole->GetSeg().B;
319 int radius = hole->GetWidth() / 2;
320
321 if( ptA == ptB )
322 {
323 // Circular hole: add as a single circle.
324 auto s = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::CIRCLE );
325 s->SetRadius( radius );
326 s->SetPosition( ptA );
327 aVector.push_back( s.get() );
328 aOwned.push_back( std::move( s ) );
329 }
330 else
331 {
332 // Oblong slot: add the two semicircular end caps and two straight sides.
333 // The slot outline is the border that creepage paths must not cross.
334 VECTOR2I axis = ptB - ptA;
335 VECTOR2I perp = axis.Perpendicular().Resize( radius );
336
337 // Side segments connecting the two end caps.
338 auto seg1 = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::SEGMENT );
339 seg1->SetStart( ptA + perp );
340 seg1->SetEnd( ptB + perp );
341 aVector.push_back( seg1.get() );
342 aOwned.push_back( std::move( seg1 ) );
343
344 auto seg2 = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::SEGMENT );
345 seg2->SetStart( ptA - perp );
346 seg2->SetEnd( ptB - perp );
347 aVector.push_back( seg2.get() );
348 aOwned.push_back( std::move( seg2 ) );
349
350 // Semicircular arc at ptA end (180 degrees, away from ptB).
351 VECTOR2I midA = ptA - axis.Resize( radius );
352 auto arcA = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::ARC );
353 arcA->SetArcGeometry( ptA + perp, midA, ptA - perp );
354 aVector.push_back( arcA.get() );
355 aOwned.push_back( std::move( arcA ) );
356
357 // Semicircular arc at ptB end (180 degrees, away from ptA).
358 VECTOR2I midB = ptB + axis.Resize( radius );
359 auto arcB = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::ARC );
360 arcB->SetArcGeometry( ptB - perp, midB, ptB + perp );
361 aVector.push_back( arcB.get() );
362 aOwned.push_back( std::move( arcB ) );
363 }
364 }
365}
366
367
369{
370 if( !m_board )
371 return -1;
372
373 std::vector<int> netcodes;
374
375 this->CollectNetCodes( netcodes );
376 double maxConstraint = GetMaxConstraint( netcodes );
377
378 if( maxConstraint <= 0 )
379 return 0;
380
381 SHAPE_POLY_SET outline;
382
383 if( !m_board->GetBoardPolygonOutlines( outline, false ) )
384 return -1;
385
386 const DRAWINGS drawings = m_board->Drawings();
387 CREEPAGE_GRAPH graph( *m_board );
388
389 if( ADVANCED_CFG::GetCfg().m_EnableCreepageSlot )
390 graph.m_minGrooveWidth = m_board->GetDesignSettings().m_MinGrooveWidth;
391 else
392 graph.m_minGrooveWidth = 0;
393
394 graph.m_boardOutline = &outline;
395
396 this->CollectBoardEdges( graph.m_boardEdge, graph.m_ownedBoardEdges );
400
401 graph.GeneratePaths( maxConstraint, Edge_Cuts );
402
403 int beNodeSize = graph.m_nodes.size();
404 int beConnectionsSize = graph.m_connections.size();
405 bool prevTestChangedGraph = false;
406
407 size_t current = 0;
408 size_t total = ( netcodes.size() * ( netcodes.size() - 1 ) ) / 2 * m_board->GetCopperLayerCount();
409 LSET layers = m_board->GetLayerSet();
410
411 alg::for_all_pairs( netcodes.begin(), netcodes.end(),
412 [&]( int aNet1, int aNet2 )
413 {
414 if( aNet1 == aNet2 )
415 return;
416
417 for( auto it = layers.copper_layers_begin(); it != layers.copper_layers_end(); ++it )
418 {
419 PCB_LAYER_ID layer = *it;
420
421 reportProgress( current++, total );
422
423 if( prevTestChangedGraph )
424 {
425 size_t vectorSize = graph.m_connections.size();
426
427 for( size_t i = beConnectionsSize; i < vectorSize; i++ )
428 {
429 // We need to remove the connection from its endpoints' lists.
430 graph.RemoveConnection( graph.m_connections[i], false );
431 }
432
433 graph.m_connections.resize( beConnectionsSize, nullptr );
434
435 vectorSize = graph.m_nodes.size();
436 graph.m_nodes.resize( beNodeSize, nullptr );
437
438 // Rebuild m_nodeset to match the surviving board-edge
439 // prefix. Without this, stale per-net nodes from the
440 // previous iteration remain in the set and corrupt
441 // subsequent FindNode/AddNode lookups.
442 graph.m_nodeset.clear();
443
444 for( int i = 0; i < beNodeSize; ++i )
445 {
446 if( graph.m_nodes[i] )
447 graph.m_nodeset.insert( graph.m_nodes[i] );
448 }
449 }
450
451 prevTestChangedGraph = testCreepage( graph, aNet1, aNet2, layer );
452 }
453 } );
454
455 return 1;
456}
457
458
459namespace detail
460{
462}
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
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:797
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 TransformCreepShapesToNodes(std::vector< CREEP_SHAPE * > &aShapes)
SHAPE_POLY_SET * m_boardOutline
std::vector< BOARD_ITEM * > m_boardEdge
void GeneratePaths(double aMaxWeight, PCB_LAYER_ID aLayer)
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:205
MINOPTMAX< int > & Value()
Definition drc_rule.h:198
DRC_RULE * GetParentRule() const
Definition drc_rule.h:201
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:407
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).
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)
SHAPE_T GetShape() const
Definition eda_shape.h:185
void RebuildBezierToSegmentsPointsList(int aMaxError)
Rebuild the m_bezierPoints vertex list that approximate the Bezier curve by a list of segments.
const std::vector< VECTOR2I > & GetBezierPoints() const
Definition eda_shape.h:337
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
T Min() const
Definition minoptmax.h:33
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.
Definition pad.h:55
Represent a set of closed polygons.
constexpr VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition vector2d.h:314
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:385
The common library.
@ DRCE_CREEPAGE
Definition drc_item.h:45
@ CREEPAGE_CONSTRAINT
Definition drc_rule.h:56
#define REPORT_AUX(s)
#define _(s)
@ SEGMENT
Definition eda_shape.h:48
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ Edge_Cuts
Definition layer_ids.h:112
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:84
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
std::map< int, NETINFO_ITEM * > NETCODES_MAP
Definition netinfo.h:187
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
std::deque< BOARD_ITEM * > DRAWINGS
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
std::string path
int radius
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687