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