KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_sliver_checker.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 <atomic>
21#include <board.h>
23#include <zone.h>
24#include <footprint.h>
25#include <pcb_shape.h>
27#include <drc/drc_rule.h>
28#include <drc/drc_item.h>
30#include <advanced_config.h>
31#include <progress_reporter.h>
32#include <thread_pool.h>
33
34/*
35 Checks for slivers in copper layers
36
37 Errors generated:
38 - DRCE_COPPER_SLIVER
39*/
40
41
42static void addItemPolysWithEndings( BOARD_ITEM* aItem, SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance,
43 int aError, ERROR_LOC aErrorLoc )
44{
45 if( aItem->Type() == PCB_SHAPE_T )
46 {
47 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
48 shape->TransformWithLineEndingsToPolygon( aBuffer, aClearance, aError, aErrorLoc );
49 }
50 else
51 {
52 aItem->TransformShapeToPolygon( aBuffer, aLayer, aClearance, aError, aErrorLoc );
53 }
54}
55
56
58{
59public:
62
64
65 virtual bool Run() override;
66
67 virtual const wxString GetName() const override { return wxT( "sliver checker" ); };
68
69private:
70 wxString layerDesc( PCB_LAYER_ID aLayer );
71};
72
73
75{
76 return wxString::Format( wxT( "(%s)" ), m_drcEngine->GetBoard()->GetLayerName( aLayer ) );
77}
78
79
81{
82 if( m_drcEngine->IsErrorLimitExceeded( DRCE_COPPER_SLIVER ) )
83 return true; // Continue with other tests
84
85 if( !reportPhase( _( "Running sliver detection on copper layers..." ) ) )
86 return false; // DRC cancelled
87
88 int64_t widthTolerance = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_SliverWidthTolerance );
89 int64_t squared_width = widthTolerance * widthTolerance;
90
91 double angleTolerance = ADVANCED_CFG::GetCfg().m_SliverAngleTolerance;
92 double cosangleTol = 2.0 * cos( DEG2RAD( angleTolerance ) );
93 LSEQ copperLayers = LSET::AllCuMask( m_drcEngine->GetBoard()->GetCopperLayerCount() ).Seq();
94 int layerCount = copperLayers.size();
95
96 // Report progress on board zones only. Everything else is in the noise.
97 int zoneLayerCount = 0;
98 std::atomic<size_t> done( 1 );
99
100 for( PCB_LAYER_ID layer : copperLayers )
101 {
102 for( ZONE* zone : m_drcEngine->GetBoard()->Zones() )
103 {
104 if( !zone->GetIsRuleArea() && zone->IsOnLayer( layer ) )
105 zoneLayerCount++;
106 }
107 }
108
109 PROGRESS_REPORTER* reporter = m_drcEngine->GetProgressReporter();
110
111 if( reporter && reporter->IsCancelled() )
112 return false; // DRC cancelled
113
114 std::vector<SHAPE_POLY_SET> layerPolys( layerCount );
115
116 auto build_layer_polys =
117 [&]( int layerIdx ) -> size_t
118 {
119 PCB_LAYER_ID layer = copperLayers[layerIdx];
120 SHAPE_POLY_SET& poly = layerPolys[layerIdx];
121
122 if( m_drcEngine->IsCancelled() )
123 return 0;
124
125 SHAPE_POLY_SET fill;
126
128 [&]( BOARD_ITEM* item ) -> bool
129 {
130 if( ZONE* zone = dynamic_cast<ZONE*>( item) )
131 {
132 if( !zone->GetIsRuleArea() )
133 {
134 if( SHAPE_POLY_SET* zoneFill = zone->GetFill( layer ) )
135 {
136 fill = zoneFill->CloneDropTriangulation();
137 poly.Append( fill );
138 }
139
140 // Report progress on board zones only. Everything else is
141 // in the noise.
142 done.fetch_add( 1 );
143 }
144 }
145 else
146 {
147 addItemPolysWithEndings( item, poly, layer, 0, ARC_LOW_DEF, ERROR_INSIDE );
148 }
149
150 if( m_drcEngine->IsCancelled() )
151 return false;
152
153 return true;
154 } );
155
156
157 if( m_drcEngine->IsCancelled() )
158 return 0;
159
160 poly.Simplify();
161
162 return 1;
163 };
164
166
167 auto returns = tp.submit_loop( 0, copperLayers.size(), build_layer_polys );
168
169 for( auto& ret : returns )
170 {
171 std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
172
173 while( status != std::future_status::ready )
174 {
175 reportProgress( zoneLayerCount, done );
176 status = ret.wait_for( std::chrono::milliseconds( 250 ) );
177 }
178 }
179
180 for( int ii = 0; ii < layerCount; ++ii )
181 {
182 PCB_LAYER_ID layer = copperLayers[ii];
183 SHAPE_POLY_SET& poly = layerPolys[ii];
184
185 if( m_drcEngine->IsErrorLimitExceeded( DRCE_COPPER_SLIVER ) )
186 continue;
187
188 // Frequently, in filled areas, some points of the polygons are very near (dist is only
189 // a few internal units, like 2 or 3 units.
190 // We skip very small vertices: one cannot really compute a valid orientation of
191 // such a vertex
192 // So skip points near than min_len (in internal units).
193 const int min_len = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_SliverMinimumLength );
194
195 for( int jj = 0; jj < poly.OutlineCount(); ++jj )
196 {
197 const std::vector<VECTOR2I>& pts = poly.Outline( jj ).CPoints();
198 int ptCount = pts.size();
199 int offset = 0;
200
201 auto area = [&]( const VECTOR2I& p, const VECTOR2I& q, const VECTOR2I& r ) -> VECTOR2I::extended_type
202 {
203 return static_cast<VECTOR2I::extended_type>( q.y - p.y ) * ( r.x - q.x ) -
204 static_cast<VECTOR2I::extended_type>( q.x - p.x ) * ( r.y - q.y );
205 };
206
207 auto isLocallyInside = [&]( int aA, int aB ) -> bool
208 {
209 int prev = ( ptCount + aA - 1 ) % ptCount;
210 int next = ( aA + 1 ) % ptCount;
211
212 if( area( pts[prev], pts[aA], pts[next] ) < 0 )
213 return area( pts[aA], pts[aB], pts[next] ) >= 0 && area( pts[aA], pts[prev], pts[aB] ) >= 0;
214 else
215 return area( pts[aA], pts[aB], pts[prev] ) < 0 || area( pts[aA], pts[next], pts[aB] ) < 0;
216 };
217
218 if( ptCount <= 5 )
219 continue;
220
221 for( int kk = 0; kk < ptCount; kk += offset )
222 {
223 int prior_index = ( ptCount + kk - 1 ) % ptCount;
224 int next_index = ( kk + 1 ) % ptCount;
225 VECTOR2I pt = pts[ kk ];
226 VECTOR2I ptPrior = pts[ prior_index ];
227 VECTOR2I vPrior = ( ptPrior - pt );
228 int forward_offset = 1;
229
230 offset = 1;
231
232 while( std::abs( vPrior.x ) < min_len && std::abs( vPrior.y ) < min_len
233 && offset < ptCount )
234 {
235 pt = pts[ ( kk + offset++ ) % ptCount ];
236 vPrior = ( ptPrior - pt );
237 }
238
239 if( offset >= ptCount )
240 break;
241
242 VECTOR2I ptAfter = pts[ next_index ];
243 VECTOR2I vAfter = ( ptAfter - pt );
244
245 while( std::abs( vAfter.x ) < min_len && std::abs( vAfter.y ) < min_len
246 && forward_offset < ptCount )
247 {
248 next_index = ( kk + forward_offset++ ) % ptCount;
249 ptAfter = pts[ next_index ];
250 vAfter = ( ptAfter - pt );
251 }
252
253 if( offset >= ptCount )
254 break;
255
256 // Negative dot product means that the angle is > 90°
257 if( vPrior.Dot( vAfter ) <= 0 )
258 continue;
259
260 if( !isLocallyInside( prior_index, next_index ) )
261 continue;
262
263 VECTOR2I vIncluded = ptAfter - ptPrior;
264 double arm1 = vPrior.SquaredEuclideanNorm();
265 double arm2 = vAfter.SquaredEuclideanNorm();
266 double opp = vIncluded.SquaredEuclideanNorm();
267
268 double cos_ang = std::abs( ( opp - arm1 - arm2 ) / ( std::sqrt( arm1 ) * std::sqrt( arm2 ) ) );
269
270 if( cos_ang > cosangleTol && 2.0 - cos_ang > std::numeric_limits<float>::epsilon() && opp > squared_width )
271 {
272 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_COPPER_SLIVER );
273 drce->SetErrorDetail( layerDesc( layer ) );
274 reportViolation( drce, pt, layer );
275 }
276 }
277 }
278 }
279
280 return true;
281}
282
283
284namespace detail
285{
287}
ERROR_LOC
When approximating an arc or circle, should the error be placed on the outside or inside of the curve...
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
constexpr int ARC_LOW_DEF
Definition base_units.h:136
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the item shape to a closed polygon.
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:444
virtual ~DRC_TEST_PROVIDER_SLIVER_CHECKER()=default
virtual const wxString GetName() const override
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
virtual bool reportPhase(const wxString &aStageName)
int forEachGeometryItem(const std::vector< KICAD_T > &aTypes, const LSET &aLayers, const std::function< bool(BOARD_ITEM *)> &aFunc)
void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::function< void(PCB_MARKER *)> &aPathGenerator=[](PCB_MARKER *){})
static std::vector< KICAD_T > s_allBasicItems
virtual bool reportProgress(size_t aCount, size_t aSize, size_t aDelta=1)
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
void TransformWithLineEndingsToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the shape body shortened for line endings plus line-ending geometry to polygons.
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition lseq.h:47
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:309
A progress reporter interface for use in multi-threaded environments.
const std::vector< VECTOR2I > & CPoints() const
Represent a set of closed polygons.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int OutlineCount() const
Return the number of outlines in the set.
SHAPE_POLY_SET CloneDropTriangulation() const
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:303
VECTOR2_TRAITS< int32_t >::extended_type extended_type
Definition vector2d.h:69
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:542
Handle a list of polygons defining a copper zone.
Definition zone.h:70
@ DRCE_COPPER_SLIVER
Definition drc_item.h:96
static void addItemPolysWithEndings(BOARD_ITEM *aItem, SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc)
#define _(s)
double m_SliverAngleTolerance
Sliver angle to tolerance for DRC.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
CITER next(CITER it)
Definition ptree.cpp:120
IbisParser parser & reporter
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
double DEG2RAD(double deg)
Definition trigo.h:172
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683