KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_zone_connections.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
24#include <board.h>
27#include <zone.h>
28#include <footprint.h>
29#include <pad.h>
30#include <pcb_shape.h>
31#include <pcb_track.h>
32#include <thread_pool.h>
33
36#include <drc/drc_item.h>
38
39
40/*
41 This loads some rule resolvers for the ZONE_FILLER, and checks that pad thermal relief
42 connections have at least the required number of spokes.
43
44 Errors generated:
45 - DRCE_STARVED_THERMAL
46*/
47
49{
50public:
53
55
56 virtual bool Run() override;
57
58 virtual const wxString GetName() const override { return wxT( "zone connections" ); };
59
60private:
61 void testZoneLayer( ZONE* aZone, PCB_LAYER_ID aLayer );
62};
63
64
66{
67 BOARD* board = m_drcEngine->GetBoard();
69 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board->GetConnectivity();
70 DRC_CONSTRAINT constraint;
71
72 const std::shared_ptr<SHAPE_POLY_SET>& zoneFill = aZone->GetFilledPolysList( aLayer );
73 ISOLATED_ISLANDS isolatedIslands;
74
75 auto zoneIter = board->m_ZoneIsolatedIslandsMap.find( aZone );
76
77 if( zoneIter != board->m_ZoneIsolatedIslandsMap.end() )
78 {
79 auto layerIter = zoneIter->second.find( aLayer );
80
81 if( layerIter != zoneIter->second.end() )
82 isolatedIslands = layerIter->second;
83 }
84
85 for( FOOTPRINT* footprint : board->Footprints() )
86 {
87 for( PAD* pad : footprint->Pads() )
88 {
89 if( m_drcEngine->IsErrorLimitExceeded( DRCE_STARVED_THERMAL ) )
90 return;
91
92 if( m_drcEngine->IsCancelled() )
93 return;
94
95 //
96 // Quick tests for "connected":
97 //
98
99 if( pad->GetNetCode() != aZone->GetNetCode() || pad->GetNetCode() <= 0 )
100 continue;
101
102 BOX2I item_bbox = pad->GetBoundingBox();
103
104 if( !item_bbox.Intersects( aZone->GetBoundingBox() ) )
105 continue;
106
107 if( !pad->FlashLayer( aLayer ) )
108 continue;
109
110 //
111 // If those passed, do a thorough test:
112 //
113
114 constraint = bds.m_DRCEngine->EvalZoneConnection( pad, aZone, aLayer );
115 ZONE_CONNECTION conn = constraint.m_ZoneConnection;
116
117 if( conn != ZONE_CONNECTION::THERMAL )
118 continue;
119
120 constraint = bds.m_DRCEngine->EvalRules( MIN_RESOLVED_SPOKES_CONSTRAINT, pad, aZone, aLayer );
121 int minCount = constraint.m_Value.Min();
122
123 if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE || minCount <= 0 )
124 continue;
125
126 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
127 int mid_gap = constraint.m_Value.Min() / 2;
128
129 SHAPE_POLY_SET padPoly;
130 pad->TransformShapeToPolygon( padPoly, aLayer, mid_gap, ARC_LOW_DEF, ERROR_OUTSIDE );
131
132 SHAPE_LINE_CHAIN& padOutline = padPoly.Outline( 0 );
133 BOX2I padBBox( padOutline.BBox() );
134 int spokes = 0;
135 int ignoredSpokes = 0;
136 VECTOR2I ignoredSpokePos;
137
138 for( int jj = 0; jj < zoneFill->OutlineCount(); ++jj )
139 {
140 std::vector<SHAPE_LINE_CHAIN::INTERSECTION> intersections;
141
142 zoneFill->Outline( jj ).Intersect( padOutline, intersections, true, &padBBox );
143
144 std::vector<SHAPE_LINE_CHAIN::INTERSECTION> unique_intersections;
145
146 for( const SHAPE_LINE_CHAIN::INTERSECTION& i : intersections )
147 {
148 const auto found = std::find_if(
149 std::begin( unique_intersections ), std::end( unique_intersections ),
150 [&]( const SHAPE_LINE_CHAIN::INTERSECTION& j ) -> bool
151 {
152 return ( j.p == i.p );
153 } );
154
155 if( found == std::end( unique_intersections ) )
156 unique_intersections.emplace_back( i );
157 }
158
159 // If we connect to an island that only connects to a single item then we *are*
160 // that item. Thermal spokes to this (otherwise isolated) island don't provide
161 // electrical connectivity to anything, so we don't count them.
162 if( unique_intersections.size() >= 2 )
163 {
164 if( alg::contains( isolatedIslands.m_SingleConnectionOutlines, jj ) )
165 {
166 ignoredSpokes += (int) unique_intersections.size() / 2;
167 ignoredSpokePos = ( unique_intersections[0].p + unique_intersections[1].p ) / 2;
168 }
169 else
170 {
171 spokes += (int) unique_intersections.size() / 2;
172 }
173 }
174 }
175
176 if( spokes == 0 && ignoredSpokes == 0 ) // Not connected at all
177 continue;
178
179 int customSpokes = 0;
180
181 if( pad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
182 {
183 for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
184 {
185 if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
186 customSpokes++;
187 }
188 }
189
190 if( customSpokes > 0 )
191 {
192 if( spokes < customSpokes )
193 {
194 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_STARVED_THERMAL );
195 VECTOR2I pos;
196
197 if( ignoredSpokes )
198 {
199 drce->SetErrorDetail( wxString::Format( _( "(layer %s; %d spokes connected to isolated island)" ),
200 board->GetLayerName( aLayer ),
201 ignoredSpokes ) );
202 pos = ignoredSpokePos;
203 }
204 else
205 {
206 drce->SetErrorDetail( wxString::Format( _( "(layer %s; %s custom spoke count %d; actual %d)" ),
207 board->GetLayerName( aLayer ),
208 constraint.GetName(),
209 customSpokes,
210 spokes ) );
211 pos = pad->GetPosition();
212 }
213
214 drce->SetItems( aZone, pad );
215 drce->SetViolatingRule( constraint.GetParentRule() );
216
217 reportViolation( drce, pos, aLayer );
218 }
219
220 continue;
221 }
222
223 if( spokes >= minCount ) // We already have enough
224 continue;
225
226 //
227 // See if there are any other manual spokes added:
228 //
229
230 for( PCB_TRACK* track : connectivity->GetConnectedTracks( pad ) )
231 {
232 if( padOutline.PointInside( track->GetStart() ) )
233 {
234 if( aZone->GetFilledPolysList( aLayer )->Collide( track->GetEnd() ) )
235 spokes++;
236 }
237 else if( padOutline.PointInside( track->GetEnd() ) )
238 {
239 if( aZone->GetFilledPolysList( aLayer )->Collide( track->GetStart() ) )
240 spokes++;
241 }
242 }
243
244 for( BOARD_CONNECTED_ITEM* item : connectivity->GetConnectedItems( pad, EXCLUDE_ZONES ) )
245 {
246 PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
247
248 if( !shape || !shape->IsOnLayer( aLayer ) )
249 continue;
250
251 std::vector<VECTOR2I> connectionPts = shape->GetConnectionPoints();
252
253 for( const VECTOR2I& pt : connectionPts )
254 {
255 if( padOutline.PointInside( pt ) )
256 {
257 for( const VECTOR2I& other : connectionPts )
258 {
259 if( other != pt && zoneFill->Collide( other ) )
260 {
261 spokes++;
262 break;
263 }
264 }
265
266 break;
267 }
268 }
269 }
270
271 //
272 // If we're *only* connected to isolated islands, then ignore the fact that they're
273 // isolated. (We leave that for the connectivity tester, which checks connections on
274 // all layers.)
275 //
276
277 if( spokes == 0 )
278 {
279 spokes += ignoredSpokes;
280 ignoredSpokes = 0;
281 }
282
283 //
284 // And finally report it if there aren't enough:
285 //
286
287 if( spokes < minCount )
288 {
289 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_STARVED_THERMAL );
290 VECTOR2I pos;
291
292 if( ignoredSpokes )
293 {
294 drce->SetErrorDetail( wxString::Format( _( "(layer %s; %d spokes connected to isolated island)" ),
295 board->GetLayerName( aLayer ),
296 ignoredSpokes ) );
297 pos = ignoredSpokePos;
298 }
299 else
300 {
301 drce->SetErrorDetail( wxString::Format( _( "(layer %s; %s min spoke count %d; actual %d)" ),
302 board->GetLayerName( aLayer ),
303 constraint.GetName(),
304 minCount,
305 spokes ) );
306 pos = pad->GetPosition();
307 }
308
309 drce->SetItems( aZone, pad );
310 drce->SetViolatingRule( constraint.GetParentRule() );
311
312 reportViolation( drce, pos, aLayer );
313 }
314 }
315 }
316}
317
318
320{
321 BOARD* board = m_drcEngine->GetBoard();
322 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board->GetConnectivity();
323 DRC_CONSTRAINT constraint;
324
325 if( !reportPhase( _( "Checking thermal reliefs..." ) ) )
326 return false; // DRC cancelled
327
328 std::vector< std::pair<ZONE*, PCB_LAYER_ID> > zoneLayers;
329 std::atomic<size_t> done( 1 );
330 size_t total_effort = 0;
331
332 for( ZONE* zone : board->m_DRCCopperZones )
333 {
334 if( !zone->IsTeardropArea() )
335 {
336 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
337 {
338 zoneLayers.push_back( { zone, layer } );
339 total_effort += zone->GetFilledPolysList( layer )->FullPointCount();
340 }
341 }
342 }
343
344 total_effort = std::max( (size_t) 1, total_effort );
345
347 auto returns = tp.submit_loop( 0, zoneLayers.size(),
348 [&]( const int ii )
349 {
350 if( !m_drcEngine->IsCancelled() )
351 {
352 testZoneLayer( zoneLayers[ii].first, zoneLayers[ii].second );
353 done.fetch_add( zoneLayers[ii].first->GetFilledPolysList( zoneLayers[ii].second )->FullPointCount() );
354 }
355 } );
356
357 for( auto& ret : returns )
358 {
359 std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
360
361 while( status != std::future_status::ready )
362 {
363 reportProgress( done, total_effort );
364 status = ret.wait_for( std::chrono::milliseconds( 250 ) );
365 }
366 }
367
368 return !m_drcEngine->IsCancelled();
369}
370
371
372namespace detail
373{
375}
@ ERROR_OUTSIDE
constexpr int ARC_LOW_DEF
Definition base_units.h:128
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
Container for design settings for a BOARD object.
std::shared_ptr< DRC_ENGINE > m_DRCEngine
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
std::map< ZONE *, std::map< PCB_LAYER_ID, ISOLATED_ISLANDS > > m_ZoneIsolatedIslandsMap
Definition board.h:1490
std::vector< ZONE * > m_DRCCopperZones
Definition board.h:1486
const FOOTPRINTS & Footprints() const
Definition board.h:363
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:730
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1084
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition board.h:563
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:311
wxString GetName() const
Definition drc_rule.h:194
SEVERITY GetSeverity() const
Definition drc_rule.h:207
ZONE_CONNECTION m_ZoneConnection
Definition drc_rule.h:230
MINOPTMAX< int > m_Value
Definition drc_rule.h:228
DRC_RULE * GetParentRule() const
Definition drc_rule.h:190
DRC_CONSTRAINT EvalRules(DRC_CONSTRAINT_T aConstraintType, const BOARD_ITEM *a, const BOARD_ITEM *b, PCB_LAYER_ID aLayer, REPORTER *aReporter=nullptr)
DRC_CONSTRAINT EvalZoneConnection(const BOARD_ITEM *a, const BOARD_ITEM *b, PCB_LAYER_ID aLayer, REPORTER *aReporter=nullptr)
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:400
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
virtual const wxString GetName() const override
void testZoneLayer(ZONE *aZone, PCB_LAYER_ID aLayer)
virtual ~DRC_TEST_PROVIDER_ZONE_CONNECTIONS()=default
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 *){})
T Min() const
Definition minoptmax.h:33
Definition pad.h:55
std::vector< VECTOR2I > GetConnectionPoints() const
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Represent a set of closed polygons.
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
int FullPointCount() const
Return the number of points in the shape poly set.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
Handle a list of polygons defining a copper zone.
Definition zone.h:74
std::shared_ptr< SHAPE_POLY_SET > GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:607
const BOX2I GetBoundingBox() const override
Definition zone.cpp:646
bool IsTeardropArea() const
Definition zone.h:690
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:137
#define EXCLUDE_ZONES
@ DRCE_STARVED_THERMAL
Definition drc_item.h:50
@ MIN_RESOLVED_SPOKES_CONSTRAINT
Definition drc_rule.h:65
@ THERMAL_RELIEF_GAP_CONSTRAINT
Definition drc_rule.h:63
#define _(s)
@ SEGMENT
Definition eda_shape.h:45
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:100
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
@ RPT_SEVERITY_IGNORE
A struct recording the isolated and single-pad islands within a zone.
Definition zone.h:61
std::vector< int > m_SingleConnectionOutlines
Definition zone.h:63
Represent an intersection between two line segments.
VECTOR2I p
Point of intersection between our and their.
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:31
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
ZONE_CONNECTION
How pads are covered by copper in zone.
Definition zones.h:47
@ THERMAL
Use thermal relief for pads.
Definition zones.h:50