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
71 const std::shared_ptr<SHAPE_POLY_SET>& zoneFill = aZone->GetFilledPolysList( aLayer );
72 ISOLATED_ISLANDS isolatedIslands;
73
74 auto zoneIter = board->m_ZoneIsolatedIslandsMap.find( aZone );
75
76 if( zoneIter != board->m_ZoneIsolatedIslandsMap.end() )
77 {
78 auto layerIter = zoneIter->second.find( aLayer );
79
80 if( layerIter != zoneIter->second.end() )
81 isolatedIslands = layerIter->second;
82 }
83
84 for( FOOTPRINT* footprint : board->Footprints() )
85 {
86 for( PAD* pad : footprint->Pads() )
87 {
88 if( m_drcEngine->IsErrorLimitExceeded( DRCE_STARVED_THERMAL ) )
89 return;
90
91 if( m_drcEngine->IsCancelled() )
92 return;
93
94 //
95 // Quick tests for "connected":
96 //
97
98 if( pad->GetNetCode() != aZone->GetNetCode() || pad->GetNetCode() <= 0 )
99 continue;
100
101 BOX2I item_bbox = pad->GetBoundingBox();
102
103 if( !item_bbox.Intersects( aZone->GetBoundingBox() ) )
104 continue;
105
106 if( !pad->FlashLayer( aLayer ) )
107 continue;
108
109 //
110 // If those passed, do a thorough test:
111 //
112
113 constraint = bds.m_DRCEngine->EvalZoneConnection( pad, aZone, aLayer );
114 ZONE_CONNECTION conn = constraint.m_ZoneConnection;
115
116 if( conn != ZONE_CONNECTION::THERMAL )
117 continue;
118
119 constraint = bds.m_DRCEngine->EvalRules( MIN_RESOLVED_SPOKES_CONSTRAINT, pad, aZone, aLayer );
120 int minCount = constraint.m_Value.Min();
121
122 if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE || minCount <= 0 )
123 continue;
124
125 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
126 int mid_gap = constraint.m_Value.Min() / 2;
127
128 SHAPE_POLY_SET padPoly;
129 pad->TransformShapeToPolygon( padPoly, aLayer, mid_gap, ARC_LOW_DEF, ERROR_OUTSIDE );
130
131 SHAPE_LINE_CHAIN& padOutline = padPoly.Outline( 0 );
132 BOX2I padBBox( padOutline.BBox() );
133 int spokes = 0;
134 int ignoredSpokes = 0;
135 VECTOR2I ignoredSpokePos;
136
137 for( int jj = 0; jj < zoneFill->OutlineCount(); ++jj )
138 {
139 std::vector<SHAPE_LINE_CHAIN::INTERSECTION> intersections;
140
141 zoneFill->Outline( jj ).Intersect( padOutline, intersections, true, &padBBox );
142
143 std::vector<SHAPE_LINE_CHAIN::INTERSECTION> unique_intersections;
144
145 for( const SHAPE_LINE_CHAIN::INTERSECTION& i : intersections )
146 {
147 const auto found = std::find_if(
148 std::begin( unique_intersections ), std::end( unique_intersections ),
149 [&]( const SHAPE_LINE_CHAIN::INTERSECTION& j ) -> bool
150 {
151 return ( j.p == i.p );
152 } );
153
154 if( found == std::end( unique_intersections ) )
155 unique_intersections.emplace_back( i );
156 }
157
158 // If we connect to an island that only connects to a single item then we *are*
159 // that item. Thermal spokes to this (otherwise isolated) island don't provide
160 // electrical connectivity to anything, so we don't count them.
161 if( unique_intersections.size() >= 2 )
162 {
163 if( alg::contains( isolatedIslands.m_SingleConnectionOutlines, jj ) )
164 {
165 ignoredSpokes += (int) unique_intersections.size() / 2;
166 ignoredSpokePos = ( unique_intersections[0].p + unique_intersections[1].p ) / 2;
167 }
168 else
169 {
170 spokes += (int) unique_intersections.size() / 2;
171 }
172 }
173 }
174
175 if( spokes == 0 && ignoredSpokes == 0 ) // Not connected at all
176 continue;
177
178 int customSpokes = 0;
179
180 if( pad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
181 {
182 for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
183 {
184 if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
185 customSpokes++;
186 }
187 }
188
189 if( customSpokes > 0 )
190 {
191 if( spokes < customSpokes )
192 {
193 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_STARVED_THERMAL );
194 VECTOR2I pos;
195
196 if( ignoredSpokes )
197 {
198 drce->SetErrorDetail( wxString::Format( _( "(layer %s; %d spokes connected to isolated island)" ),
199 board->GetLayerName( aLayer ),
200 ignoredSpokes ) );
201 pos = ignoredSpokePos;
202 }
203 else
204 {
205 drce->SetErrorDetail( wxString::Format( _( "(layer %s; %s custom spoke count %d; actual %d)" ),
206 board->GetLayerName( aLayer ),
207 constraint.GetName(),
208 customSpokes,
209 spokes ) );
210 pos = pad->GetPosition();
211 }
212
213 drce->SetItems( aZone, pad );
214 drce->SetViolatingRule( constraint.GetParentRule() );
215
216 reportViolation( drce, pos, aLayer );
217 }
218
219 continue;
220 }
221
222 if( spokes >= minCount ) // We already have enough
223 continue;
224
225 //
226 // See if there are any other manual spokes added:
227 //
228
229 for( PCB_TRACK* track : connectivity->GetConnectedTracks( pad ) )
230 {
231 if( padOutline.PointInside( track->GetStart() ) )
232 {
233 if( aZone->GetFilledPolysList( aLayer )->Collide( track->GetEnd() ) )
234 spokes++;
235 }
236 else if( padOutline.PointInside( track->GetEnd() ) )
237 {
238 if( aZone->GetFilledPolysList( aLayer )->Collide( track->GetStart() ) )
239 spokes++;
240 }
241 }
242
243 //
244 // If we're *only* connected to isolated islands, then ignore the fact that they're
245 // isolated. (We leave that for the connectivity tester, which checks connections on
246 // all layers.)
247 //
248
249 if( spokes == 0 )
250 {
251 spokes += ignoredSpokes;
252 ignoredSpokes = 0;
253 }
254
255 //
256 // And finally report it if there aren't enough:
257 //
258
259 if( spokes < minCount )
260 {
261 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_STARVED_THERMAL );
262 VECTOR2I pos;
263
264 if( ignoredSpokes )
265 {
266 drce->SetErrorDetail( wxString::Format( _( "(layer %s; %d spokes connected to isolated island)" ),
267 board->GetLayerName( aLayer ),
268 ignoredSpokes ) );
269 pos = ignoredSpokePos;
270 }
271 else
272 {
273 drce->SetErrorDetail( wxString::Format( _( "(layer %s; %s min spoke count %d; actual %d)" ),
274 board->GetLayerName( aLayer ),
275 constraint.GetName(),
276 minCount,
277 spokes ) );
278 pos = pad->GetPosition();
279 }
280
281 drce->SetItems( aZone, pad );
282 drce->SetViolatingRule( constraint.GetParentRule() );
283
284 reportViolation( drce, pos, aLayer );
285 }
286 }
287 }
288}
289
290
292{
293 BOARD* board = m_drcEngine->GetBoard();
294 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board->GetConnectivity();
295 DRC_CONSTRAINT constraint;
296
297 if( !reportPhase( _( "Checking thermal reliefs..." ) ) )
298 return false; // DRC cancelled
299
300 std::vector< std::pair<ZONE*, PCB_LAYER_ID> > zoneLayers;
301 std::atomic<size_t> done( 1 );
302 size_t total_effort = 0;
303
304 for( ZONE* zone : board->m_DRCCopperZones )
305 {
306 if( !zone->IsTeardropArea() )
307 {
308 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
309 {
310 zoneLayers.push_back( { zone, layer } );
311 total_effort += zone->GetFilledPolysList( layer )->FullPointCount();
312 }
313 }
314 }
315
316 total_effort = std::max( (size_t) 1, total_effort );
317
319 auto returns = tp.submit_loop( 0, zoneLayers.size(),
320 [&]( const int ii )
321 {
322 if( !m_drcEngine->IsCancelled() )
323 {
324 testZoneLayer( zoneLayers[ii].first, zoneLayers[ii].second );
325 done.fetch_add( zoneLayers[ii].first->GetFilledPolysList( zoneLayers[ii].second )->FullPointCount() );
326 }
327 } );
328
329 for( auto& ret : returns )
330 {
331 std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
332
333 while( status != std::future_status::ready )
334 {
335 reportProgress( done, total_effort );
336 status = ret.wait_for( std::chrono::milliseconds( 250 ) );
337 }
338 }
339
340 return !m_drcEngine->IsCancelled();
341}
342
343
344namespace detail
345{
347}
@ 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:322
std::map< ZONE *, std::map< PCB_LAYER_ID, ISOLATED_ISLANDS > > m_ZoneIsolatedIslandsMap
Definition board.h:1417
std::vector< ZONE * > m_DRCCopperZones
Definition board.h:1413
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:715
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1069
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition board.h:538
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
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:596
const BOX2I GetBoundingBox() const override
Definition zone.cpp:613
bool IsTeardropArea() const
Definition zone.h:676
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: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