KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_creepage_engine.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include <board.h>
27#include <core/kicad_algo.h>
28#include <footprint.h>
29#include <netinfo.h>
30#include <pad.h>
31
32
34 m_board( aBoard )
35{
36}
37
38
40{
42
43 // Subtract NPTH holes from the outline so paths through slot interiors are rejected (#24286)
44 bool hasOutline = m_board.GetBoardPolygonOutlines( aOutline, false, nullptr, false, true );
45
47 aGraph.m_boardOutline = hasOutline ? &aOutline : nullptr;
48
52}
53
54
56{
57 std::vector<std::shared_ptr<GRAPH_NODE>> temp_nodes;
58
59 std::copy_if( aGraph.m_nodes.begin(), aGraph.m_nodes.end(), std::back_inserter( temp_nodes ),
60 []( const std::shared_ptr<GRAPH_NODE>& aNode )
61 {
62 return !!aNode && aNode->m_parent && !aNode->m_parent->IsConductive()
63 && !aNode->m_connectDirectly && aNode->m_type == GRAPH_NODE::POINT;
64 } );
65
66 alg::for_all_pairs( temp_nodes.begin(), temp_nodes.end(),
67 [&]( const std::shared_ptr<GRAPH_NODE>& aN1,
68 const std::shared_ptr<GRAPH_NODE>& aN2 )
69 {
70 if( aN1 == aN2 || !aN1 || !aN2 )
71 return;
72
73 if( !aN1->m_parent || !aN2->m_parent )
74 return;
75
76 if( aN1->m_parent != aN2->m_parent )
77 return;
78
79 aN1->m_parent->ConnectChildren( const_cast<std::shared_ptr<GRAPH_NODE>&>( aN1 ),
80 const_cast<std::shared_ptr<GRAPH_NODE>&>( aN2 ),
81 aGraph );
82 } );
83}
84
85
86std::optional<CREEPAGE_RESULT>
87CREEPAGE_ENGINE::extractResult( const std::vector<std::shared_ptr<GRAPH_CONNECTION>>& aPath,
88 double aDistance, int aNetA, int aNetB, double aConstraint,
89 int aNearMargin )
90{
91 if( aPath.empty() || aPath.size() < 4 )
92 return std::nullopt;
93
94 // aNearMargin == 0 reproduces the legacy "distance < constraint" test exactly; the overlay
95 // passes a positive margin to also surface near misses
96 if( !( aDistance - aConstraint < aNearMargin ) )
97 return std::nullopt;
98
100 result.m_netA = aNetA;
101 result.m_netB = aNetB;
102 result.m_distance = aDistance;
103 result.m_constraint = aConstraint;
104 result.m_violation = aDistance < aConstraint;
105
106 std::shared_ptr<GRAPH_CONNECTION> gc1 = aPath[1];
107 std::shared_ptr<GRAPH_CONNECTION> gc2 = aPath[aPath.size() - 2];
108
109 result.m_start = gc1->m_path.a2;
110 result.m_end = gc2->m_path.a2;
111
112 // Connections [1] and [size-2] sit on real shape nodes; the path ends are zero-weight links
113 // to the per-net virtual nodes
114 if( gc1->n1 && gc1->n1->m_parent )
115 result.m_itemA = gc1->n1->m_parent->GetParent();
116
117 if( gc2->n2 && gc2->n2->m_parent )
118 result.m_itemB = gc2->n2->m_parent->GetParent();
119
120 for( const std::shared_ptr<GRAPH_CONNECTION>& gc : aPath )
121 gc->GetShapes( result.m_path );
122
123 return result;
124}
125
126
127std::optional<CREEPAGE_RESULT> CREEPAGE_ENGINE::SolveNetPairWholeBoard( int aNetA, int aNetB,
128 PCB_LAYER_ID aLayer,
129 double aConstraint )
130{
131 if( aConstraint <= 0 )
132 return std::nullopt;
133
134 NETINFO_ITEM* netA = m_board.FindNet( aNetA );
135 NETINFO_ITEM* netB = m_board.FindNet( aNetB );
136
137 if( !netA || !netB )
138 return std::nullopt;
139
140 if( netA->GetBoundingBox().Distance( netB->GetBoundingBox() ) > aConstraint )
141 return std::nullopt;
142
143 CREEPAGE_GRAPH graph( m_board );
144 SHAPE_POLY_SET outline;
145 populateBoardEdgeGraph( graph, outline );
146
147 graph.GeneratePaths( aConstraint, Edge_Cuts );
148 graph.SetTarget( aConstraint );
149
150 std::shared_ptr<GRAPH_NODE> nodeA = graph.AddNetElements( aNetA, aLayer, aConstraint );
151 std::shared_ptr<GRAPH_NODE> nodeB = graph.AddNetElements( aNetB, aLayer, aConstraint );
152
153 graph.GeneratePaths( aConstraint, aLayer );
154
155 connectChildren( graph );
156
157 std::vector<std::shared_ptr<GRAPH_CONNECTION>> shortestPath;
158 double distance = graph.Solve( nodeA, nodeB, shortestPath );
159
160 return extractResult( shortestPath, distance, aNetA, aNetB, aConstraint, 0 );
161}
162
163
164std::map<int, std::shared_ptr<GRAPH_NODE>> CREEPAGE_ENGINE::addNetsInRegion( const BOX2I& aRegion )
165{
166 std::map<int, std::shared_ptr<GRAPH_NODE>> added;
167
168 // Iterate in net-code order so node creation order matches the whole-board solve
169 for( const auto& [netCode, net] : m_board.GetNetInfo().NetsByNetcode() )
170 {
171 if( !net || netCode <= 0 )
172 continue;
173
174 if( !m_affectedNets.count( netCode ) )
175 {
176 auto it = m_staticNetBBoxes.find( netCode );
177
178 if( it == m_staticNetBBoxes.end() || !it->second.Intersects( aRegion ) )
179 continue;
180 }
181
182 added[netCode] = m_graph->AddNetElements( netCode, m_layer, m_margin );
183 }
184
185 return added;
186}
187
188
189void CREEPAGE_ENGINE::BeginInteractive( PCB_LAYER_ID aLayer, const std::set<int>& aAffectedNets,
190 const std::set<const BOARD_ITEM*>& aMovingItems, int aMargin,
191 std::function<double( int, int )> aConstraintFn )
192{
193 m_interactive = true;
194 m_layer = aLayer;
195 m_affectedNets = aAffectedNets;
196 m_movingItems = aMovingItems;
197 m_constraintFn = std::move( aConstraintFn );
198 m_margin = aMargin;
199
200 m_staticNetBBoxes.clear();
201
202 for( const auto& [netCode, net] : m_board.GetNetInfo().NetsByNetcode() )
203 {
204 if( net && netCode > 0 && !m_affectedNets.count( netCode ) )
205 m_staticNetBBoxes.emplace( netCode, net->GetBoundingBox() );
206 }
207
209
211}
212
213
215{
216 m_graph = std::make_unique<CREEPAGE_GRAPH>( m_board );
217 m_outline = std::make_unique<SHAPE_POLY_SET>();
219 m_graph->SetTarget( m_margin );
220
221 // The board-edge sub-graph is pure board geometry, independent of any track, so it is the
222 // cacheable prefix. Copper-layer paths depend on every track through the collision test and
223 // must be rebuilt each frame.
224 m_graph->GeneratePaths( m_margin, Edge_Cuts );
226
227 m_staticNodeCount = m_graph->m_nodes.size();
228 m_staticConnCount = m_graph->m_connections.size();
229}
230
231
233{
234 auto isNPTH = []( const PAD* aPad )
235 {
236 return aPad && aPad->GetAttribute() == PAD_ATTRIB::NPTH;
237 };
238
239 for( const BOARD_ITEM* item : m_movingItems )
240 {
241 if( !item )
242 continue;
243
244 if( item->Type() == PCB_PAD_T && isNPTH( static_cast<const PAD*>( item ) ) )
245 return true;
246
247 if( item->Type() == PCB_FOOTPRINT_T )
248 {
249 for( const PAD* pad : static_cast<const FOOTPRINT*>( item )->Pads() )
250 {
251 if( isNPTH( pad ) )
252 return true;
253 }
254 }
255 }
256
257 return false;
258}
259
260
261std::vector<CREEPAGE_RESULT> CREEPAGE_ENGINE::Update( int aNearMargin )
262{
263 std::vector<CREEPAGE_RESULT> results;
264
265 if( !m_interactive || !m_graph )
266 return results;
267
268 if( m_dynamicEdges )
270 else
271 m_graph->TruncateToPrefix( m_staticNodeCount, m_staticConnCount );
272
273 BOX2I roi;
274
275 for( const BOARD_ITEM* item : m_movingItems )
276 {
277 if( item )
278 roi.Merge( item->GetBoundingBox() );
279 }
280
281 roi.Inflate( m_margin );
282
283 std::map<int, std::shared_ptr<GRAPH_NODE>> netNodes = addNetsInRegion( roi );
284
285 m_graph->GeneratePaths( m_margin, m_layer, &m_affectedNets );
287
288 auto solve = [&]( int aNetA, std::shared_ptr<GRAPH_NODE>& aNodeA, int aNetB,
289 std::shared_ptr<GRAPH_NODE>& aNodeB )
290 {
291 if( !aNodeA || !aNodeB )
292 return;
293
294 double constraint = m_constraintFn ? m_constraintFn( aNetA, aNetB ) : 0.0;
295
296 if( constraint <= 0 )
297 return;
298
299 std::vector<std::shared_ptr<GRAPH_CONNECTION>> path;
300 double distance = m_graph->Solve( aNodeA, aNodeB, path );
301
302 std::optional<CREEPAGE_RESULT> r =
303 extractResult( path, distance, aNetA, aNetB, constraint, aNearMargin );
304
305 if( r )
306 results.push_back( *r );
307 };
308
309 for( int affected : m_affectedNets )
310 {
311 auto itA = netNodes.find( affected );
312
313 if( itA == netNodes.end() )
314 continue;
315
316 for( auto& [netB, nodeB] : netNodes )
317 {
318 if( netB == affected )
319 continue;
320
321 // Avoid solving an affected-affected pair twice
322 if( m_affectedNets.count( netB ) && netB < affected )
323 continue;
324
325 solve( affected, itA->second, netB, nodeB );
326 }
327 }
328
329 return results;
330}
331
332
334{
335 m_interactive = false;
336 m_dynamicEdges = false;
337 m_graph.reset();
338 m_outline.reset();
339 m_affectedNets.clear();
340 m_movingItems.clear();
341 m_staticNetBBoxes.clear();
342 m_constraintFn = nullptr;
345}
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:80
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:320
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:554
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:654
ecoord_type Distance(const Vec &aP) const
Definition box2.h:793
std::map< int, BOX2I > m_staticNetBBoxes
bool movingItemsHaveNPTH() const
True when any dragged item carries an NPTH hole, making the board-edge graph position dependent and t...
std::function< double(int, int)> m_constraintFn
std::set< int > m_affectedNets
void populateBoardEdgeGraph(CREEPAGE_GRAPH &aGraph, SHAPE_POLY_SET &aOutline)
Build the board-edge geometry into aGraph: groove width, outline (NPTH-subtracted),...
std::set< const BOARD_ITEM * > m_movingItems
std::vector< CREEPAGE_RESULT > Update(int aNearMargin=0)
Recompute creepage for the dragged nets at the items' current board positions.
CREEPAGE_ENGINE(BOARD &aBoard)
void connectChildren(CREEPAGE_GRAPH &aGraph)
Run the same-parent ConnectChildren pass the legacy solver runs before Dijkstra.
std::optional< CREEPAGE_RESULT > extractResult(const std::vector< std::shared_ptr< GRAPH_CONNECTION > > &aPath, double aDistance, int aNetA, int aNetB, double aConstraint, int aNearMargin)
Extract a CREEPAGE_RESULT from a solved shortest path, or nullopt if not a violation.
std::map< int, std::shared_ptr< GRAPH_NODE > > addNetsInRegion(const BOX2I &aRegion)
Add net elements for the affected nets plus every other net whose bbox intersects aRegion.
void buildBoardEdgePrefix()
(Re)build the board-edge visibility sub-graph from the board at its current state and record it as th...
void BeginInteractive(PCB_LAYER_ID aLayer, const std::set< int > &aAffectedNets, const std::set< const BOARD_ITEM * > &aMovingItems, int aMargin, std::function< double(int, int)> aConstraintFn)
Begin an interactive drag session.
std::unique_ptr< SHAPE_POLY_SET > m_outline
std::unique_ptr< CREEPAGE_GRAPH > m_graph
std::optional< CREEPAGE_RESULT > SolveNetPairWholeBoard(int aNetA, int aNetB, PCB_LAYER_ID aLayer, double aConstraint)
Solve a single net pair against the whole board on one layer, building a fresh graph.
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::shared_ptr< GRAPH_NODE > AddNetElements(int aNetCode, PCB_LAYER_ID aLayer, int aMaxCreepage)
std::vector< std::unique_ptr< PCB_SHAPE > > m_ownedBoardEdges
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.
Definition pad.h:61
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.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Edge_Cuts
Definition layer_ids.h:108
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
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
Result of a single creepage query between two nets on one layer.
std::string path
wxString result
Test unit parsing edge cases and error handling.
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:79
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80