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:
51 {}
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 {
90 return;
91
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().Seq() )
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 std::vector<std::future<int>> returns;
323
324 returns.reserve( zoneLayers.size() );
325
326 for( const std::pair<ZONE*, PCB_LAYER_ID>& zonelayer : zoneLayers )
327 {
328 returns.emplace_back( tp.submit(
329 [&]( ZONE* aZone, PCB_LAYER_ID aLayer ) -> int
330 {
331 if( !m_drcEngine->IsCancelled() )
332 {
333 testZoneLayer( aZone, aLayer );
334 done.fetch_add( aZone->GetFilledPolysList( aLayer )->FullPointCount() );
335 }
336
337 return 0;
338 },
339 zonelayer.first, zonelayer.second ) );
340 }
341
342 for( const std::future<int>& ret : returns )
343 {
344 std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
345
346 while( status != std::future_status::ready )
347 {
348 reportProgress( done, total_effort );
349 status = ret.wait_for( std::chrono::milliseconds( 250 ) );
350 }
351 }
352
353 return !m_drcEngine->IsCancelled();
354}
355
356
357namespace detail
358{
360}
@ ERROR_OUTSIDE
Definition: approximation.h:33
constexpr int ARC_LOW_DEF
Definition: base_units.h:128
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:314
std::map< ZONE *, std::map< PCB_LAYER_ID, ISOLATED_ISLANDS > > m_ZoneIsolatedIslandsMap
Definition: board.h:1352
std::vector< ZONE * > m_DRCCopperZones
Definition: board.h:1348
const FOOTPRINTS & Footprints() const
Definition: board.h:355
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition: board.cpp:611
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:943
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition: board.h:513
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition: box2.h:311
wxString GetName() const
Definition: drc_rule.h:168
SEVERITY GetSeverity() const
Definition: drc_rule.h:181
ZONE_CONNECTION m_ZoneConnection
Definition: drc_rule.h:204
MINOPTMAX< int > m_Value
Definition: drc_rule.h:202
DRC_RULE * GetParentRule() const
Definition: drc_rule.h:164
BOARD * GetBoard() const
Definition: drc_engine.h:95
bool IsErrorLimitExceeded(int error_code)
bool IsCancelled() const
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition: drc_item.cpp:393
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
Represent a DRC "provider" which runs some DRC functions over a BOARD and spits out DRC_ITEM and posi...
virtual bool reportPhase(const wxString &aStageName)
virtual void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, DRC_CUSTOM_MARKER_HANDLER *aCustomHandler=nullptr)
DRC_ENGINE * m_drcEngine
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition: lset.cpp:297
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.
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:595
const BOX2I GetBoundingBox() const override
Definition: zone.cpp:622
bool IsTeardropArea() const
Definition: zone.h:674
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)
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.
Definition: thread_pool.cpp:30
static thread_pool * tp
Definition: thread_pool.cpp:28
BS::thread_pool thread_pool
Definition: thread_pool.h:31
ZONE_CONNECTION
How pads are covered by copper in zone.
Definition: zones.h:47