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_track.h>
31#include <thread_pool.h>
32
35#include <drc/drc_item.h>
37
38
39/*
40 This loads some rule resolvers for the ZONE_FILLER, and checks that pad thermal relief
41 connections have at least the required number of spokes.
42
43 Errors generated:
44 - DRCE_STARVED_THERMAL
45*/
46
48{
49public:
52
54
55 virtual bool Run() override;
56
57 virtual const wxString GetName() const override { return wxT( "zone connections" ); };
58
59private:
60 void testZoneLayer( ZONE* aZone, PCB_LAYER_ID aLayer );
61};
62
63
65{
66 BOARD* board = m_drcEngine->GetBoard();
68 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board->GetConnectivity();
69 DRC_CONSTRAINT constraint;
70 wxString msg;
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 msg = wxString::Format( _( "(layer %s; %d spokes connected to isolated island)" ),
200 board->GetLayerName( aLayer ),
201 ignoredSpokes );
202 pos = ignoredSpokePos;
203 }
204 else
205 {
206 msg = 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->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg );
215 drce->SetItems( aZone, pad );
216 drce->SetViolatingRule( constraint.GetParentRule() );
217
218 reportViolation( drce, pos, aLayer );
219 }
220
221 continue;
222 }
223
224 if( spokes >= minCount ) // We already have enough
225 continue;
226
227 //
228 // See if there are any other manual spokes added:
229 //
230
231 for( PCB_TRACK* track : connectivity->GetConnectedTracks( pad ) )
232 {
233 if( padOutline.PointInside( track->GetStart() ) )
234 {
235 if( aZone->GetFilledPolysList( aLayer )->Collide( track->GetEnd() ) )
236 spokes++;
237 }
238 else if( padOutline.PointInside( track->GetEnd() ) )
239 {
240 if( aZone->GetFilledPolysList( aLayer )->Collide( track->GetStart() ) )
241 spokes++;
242 }
243 }
244
245 //
246 // If we're *only* connected to isolated islands, then ignore the fact that they're
247 // isolated. (We leave that for the connectivity tester, which checks connections on
248 // all layers.)
249 //
250
251 if( spokes == 0 )
252 {
253 spokes += ignoredSpokes;
254 ignoredSpokes = 0;
255 }
256
257 //
258 // And finally report it if there aren't enough:
259 //
260
261 if( spokes < minCount )
262 {
263 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_STARVED_THERMAL );
264 VECTOR2I pos;
265
266 if( ignoredSpokes )
267 {
268 msg = wxString::Format( _( "(layer %s; %d spokes connected to isolated island)" ),
269 board->GetLayerName( aLayer ),
270 ignoredSpokes );
271 pos = ignoredSpokePos;
272 }
273 else
274 {
275 msg = wxString::Format( _( "(layer %s; %s min spoke count %d; actual %d)" ),
276 board->GetLayerName( aLayer ),
277 constraint.GetName(),
278 minCount,
279 spokes );
280 pos = pad->GetPosition();
281 }
282
283 drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg );
284 drce->SetItems( aZone, pad );
285 drce->SetViolatingRule( constraint.GetParentRule() );
286
287 reportViolation( drce, pos, aLayer );
288 }
289 }
290 }
291}
292
293
295{
296 BOARD* board = m_drcEngine->GetBoard();
297 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board->GetConnectivity();
298 DRC_CONSTRAINT constraint;
299
300 if( !reportPhase( _( "Checking thermal reliefs..." ) ) )
301 return false; // DRC cancelled
302
303 std::vector< std::pair<ZONE*, PCB_LAYER_ID> > zoneLayers;
304 std::atomic<size_t> done( 1 );
305 size_t total_effort = 0;
306
307 for( ZONE* zone : board->m_DRCCopperZones )
308 {
309 if( !zone->IsTeardropArea() )
310 {
311 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
312 {
313 zoneLayers.push_back( { zone, layer } );
314 total_effort += zone->GetFilledPolysList( layer )->FullPointCount();
315 }
316 }
317 }
318
319 total_effort = std::max( (size_t) 1, total_effort );
320
322 auto returns = tp.submit_loop( 0, zoneLayers.size(),
323 [&]( const int ii )
324 {
325 if( !m_drcEngine->IsCancelled() )
326 {
327 testZoneLayer( zoneLayers[ii].first, zoneLayers[ii].second );
328 done.fetch_add( zoneLayers[ii].first->GetFilledPolysList( zoneLayers[ii].second )->FullPointCount() );
329 }
330 } );
331
332 for( auto& ret : returns )
333 {
334 std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
335
336 while( status != std::future_status::ready )
337 {
338 reportProgress( done, total_effort );
339 status = ret.wait_for( std::chrono::milliseconds( 250 ) );
340 }
341 }
342
343 return !m_drcEngine->IsCancelled();
344}
345
346
347namespace detail
348{
350}
@ ERROR_OUTSIDE
constexpr int ARC_LOW_DEF
Definition base_units.h:128
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
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:317
std::map< ZONE *, std::map< PCB_LAYER_ID, ISOLATED_ISLANDS > > m_ZoneIsolatedIslandsMap
Definition board.h:1385
std::vector< ZONE * > m_DRCCopperZones
Definition board.h:1381
const FOOTPRINTS & Footprints() const
Definition board.h:358
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:691
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1040
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition board.h:521
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:311
wxString GetName() const
Definition drc_rule.h:170
SEVERITY GetSeverity() const
Definition drc_rule.h:183
ZONE_CONNECTION m_ZoneConnection
Definition drc_rule.h:206
MINOPTMAX< int > m_Value
Definition drc_rule.h:204
DRC_RULE * GetParentRule() const
Definition drc_rule.h:166
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:381
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)
virtual void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::vector< PCB_SHAPE > &aShapes={})
T Min() const
Definition minoptmax.h:33
Definition pad.h:54
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
const std::shared_ptr< SHAPE_POLY_SET > & GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:600
const BOX2I GetBoundingBox() const override
Definition zone.cpp:621
bool IsTeardropArea() const
Definition zone.h:679
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:136
@ DRCE_STARVED_THERMAL
Definition drc_item.h:49
@ 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::thread_pool< 0 > 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