KiCad PCB EDA Suite
Loading...
Searching...
No Matches
fix_board_shape.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 (C) 2023 Alex Shvartzkop <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include "fix_board_shape.h"
22
23#include <vector>
24#include <algorithm>
25#include <limits>
26#include <base_units.h>
27#include <pcb_shape.h>
28#include <geometry/circle.h>
29
30#include <nanoflann.hpp>
31
32
33// Near-collinear segment ends closer than this are treated as rounding noise and welded
34// together. Larger gaps are assumed to be real features (slots, necks) and left alone.
35static const int WELD_GAP_TOLERANCE = pcbIUScale.mmToIU( 0.01 );
36
37
39{
40 std::vector<std::pair<VECTOR2I, PCB_SHAPE*>> endpoints;
41
42 PCB_SHAPE_ENDPOINTS_ADAPTOR( const std::vector<PCB_SHAPE*>& shapes )
43 {
44 endpoints.reserve( shapes.size() * 2 );
45
46 for( PCB_SHAPE* shape : shapes )
47 {
48 endpoints.emplace_back( shape->GetStart(), shape );
49 endpoints.emplace_back( shape->GetEnd(), shape );
50 }
51 }
52
53 // Required by nanoflann
54 size_t kdtree_get_point_count() const { return endpoints.size(); }
55
56 // Returns the dim'th component of the idx'th point
57 double kdtree_get_pt( const size_t idx, const size_t dim ) const
58 {
59 if( dim == 0 )
60 return static_cast<double>( endpoints[idx].first.x );
61 else
62 return static_cast<double>( endpoints[idx].first.y );
63 }
64
65 template <class BBOX>
66 bool kdtree_get_bbox( BBOX& ) const
67 {
68 return false;
69 }
70};
71
72using KDTree = nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double, PCB_SHAPE_ENDPOINTS_ADAPTOR>,
74 2 /* dim */ >;
75
76
87static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const VECTOR2I& aPoint, const KDTree& kdTree,
88 const PCB_SHAPE_ENDPOINTS_ADAPTOR& adaptor, double aChainingEpsilon )
89{
90 const double query_pt[2] = { static_cast<double>( aPoint.x ), static_cast<double>( aPoint.y ) };
91
92 uint32_t indices[2];
93 double distances[2];
94 kdTree.knnSearch( query_pt, 2, indices, distances );
95
96 if( distances[0] == std::numeric_limits<double>::max() )
97 return nullptr;
98
99 // Find the closest valid candidate
100 PCB_SHAPE* closest_graphic = nullptr;
101 double closest_dist_sq = aChainingEpsilon * aChainingEpsilon;
102
103 for( size_t i = 0; i < 2; ++i )
104 {
105 if( distances[i] == std::numeric_limits<double>::max() )
106 continue;
107
108 PCB_SHAPE* candidate = adaptor.endpoints[indices[i]].second;
109
110 if( candidate == aShape )
111 continue;
112
113 if( candidate->GetFlags() & SKIP_STRUCT )
114 continue;
115
116 if( distances[i] < closest_dist_sq )
117 {
118 closest_dist_sq = distances[i];
119 closest_graphic = candidate;
120 }
121 }
122
123 return closest_graphic;
124}
125
126
127void ConnectBoardShapes( std::vector<PCB_SHAPE*>& aShapeList, int aChainingEpsilon )
128{
129 if( aShapeList.size() == 0 )
130 return;
131
132 // Pre-build KD-tree
133 PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( aShapeList );
134 KDTree kdTree( 2, adaptor );
135
136 auto closer_to_first = []( const VECTOR2I& aRef, const VECTOR2I& aFirst,
137 const VECTOR2I& aSecond ) -> bool
138 {
139 return ( aRef - aFirst ).SquaredEuclideanNorm() < ( aRef - aSecond ).SquaredEuclideanNorm();
140 };
141
142 auto min_distance_sq = []( const VECTOR2I& aRef, const VECTOR2I& aFirst,
143 const VECTOR2I& aSecond ) -> SEG::ecoord
144 {
145 return std::min( ( aRef - aFirst ).SquaredEuclideanNorm(),
146 ( aRef - aSecond ).SquaredEuclideanNorm() );
147 };
148
149 auto connectPair = [&]( PCB_SHAPE* aPrevShape, PCB_SHAPE* aShape )
150 {
151 bool success = false;
152
153 SHAPE_T shape0 = aPrevShape->GetShape();
154 SHAPE_T shape1 = aShape->GetShape();
155
156 if( shape0 == SHAPE_T::SEGMENT && shape1 == SHAPE_T::SEGMENT )
157 {
158 SEG seg0( aPrevShape->GetStart(), aPrevShape->GetEnd() );
159 SEG seg1( aShape->GetStart(), aShape->GetEnd() );
160 SEG::ecoord d[4];
161 d[0] = ( seg0.A - seg1.A ).SquaredEuclideanNorm();
162 d[1] = ( seg0.A - seg1.B ).SquaredEuclideanNorm();
163 d[2] = ( seg0.B - seg1.A ).SquaredEuclideanNorm();
164 d[3] = ( seg0.B - seg1.B ).SquaredEuclideanNorm();
165
166 int idx = std::min_element( d, d + 4 ) - d;
167 int i0 = idx / 2;
168 int i1 = idx % 2;
169
170 if( seg0.Intersects( seg1 ) || seg0.Angle( seg1 ) > ANGLE_45 )
171 {
172 if( OPT_VECTOR2I inter = seg0.IntersectLines( seg1 ) )
173 {
174 if( i0 == 0 )
175 aPrevShape->SetStart( *inter );
176 else
177 aPrevShape->SetEnd( *inter );
178
179 if( i1 == 0 )
180 aShape->SetStart( *inter );
181 else
182 aShape->SetEnd( *inter );
183
184 success = true;
185 }
186 }
187
188 // Near-collinear facets have no real corner to extend to, so weld the closest ends
189 // together, but only for a hairline gap (rounding noise) so real thin features survive
190 if( !success && d[idx] <= SEG::Square( WELD_GAP_TOLERANCE ) )
191 {
192 VECTOR2I mid = ( ( i0 == 0 ? seg0.A : seg0.B ) + ( i1 == 0 ? seg1.A : seg1.B ) ) / 2;
193
194 if( i0 == 0 )
195 aPrevShape->SetStart( mid );
196 else
197 aPrevShape->SetEnd( mid );
198
199 if( i1 == 0 )
200 aShape->SetStart( mid );
201 else
202 aShape->SetEnd( mid );
203
204 success = true;
205 }
206 }
207 else if( ( shape0 == SHAPE_T::ARC && shape1 == SHAPE_T::SEGMENT )
208 || ( shape0 == SHAPE_T::SEGMENT && shape1 == SHAPE_T::ARC ) )
209 {
210 PCB_SHAPE* arcShape = shape0 == SHAPE_T::ARC ? aPrevShape : aShape;
211 PCB_SHAPE* segShape = shape0 == SHAPE_T::SEGMENT ? aPrevShape : aShape;
212
213 VECTOR2I arcPts[2] = { arcShape->GetStart(), arcShape->GetEnd() };
214 VECTOR2I segPts[2] = { segShape->GetStart(), segShape->GetEnd() };
215
216 SEG::ecoord d[4];
217 d[0] = ( segPts[0] - arcPts[0] ).SquaredEuclideanNorm();
218 d[1] = ( segPts[0] - arcPts[1] ).SquaredEuclideanNorm();
219 d[2] = ( segPts[1] - arcPts[0] ).SquaredEuclideanNorm();
220 d[3] = ( segPts[1] - arcPts[1] ).SquaredEuclideanNorm();
221
222 int idx = std::min_element( d, d + 4 ) - d;
223
224 switch( idx )
225 {
226 case 0: segShape->SetStart( arcPts[0] ); break;
227 case 1: segShape->SetStart( arcPts[1] ); break;
228 case 2: segShape->SetEnd( arcPts[0] ); break;
229 case 3: segShape->SetEnd( arcPts[1] ); break;
230 }
231
232 success = true;
233 }
234 else if( shape0 == SHAPE_T::ARC && shape1 == SHAPE_T::ARC )
235 {
236 PCB_SHAPE* arc0 = aPrevShape;
237 PCB_SHAPE* arc1 = aShape;
238
239 VECTOR2I pts0[2] = { arc0->GetStart(), arc0->GetEnd() };
240 VECTOR2I pts1[2] = { arc1->GetStart(), arc1->GetEnd() };
241
242 SEG::ecoord d[4];
243 d[0] = ( pts0[0] - pts1[0] ).SquaredEuclideanNorm();
244 d[1] = ( pts0[0] - pts1[1] ).SquaredEuclideanNorm();
245 d[2] = ( pts0[1] - pts1[0] ).SquaredEuclideanNorm();
246 d[3] = ( pts0[1] - pts1[1] ).SquaredEuclideanNorm();
247
248 int idx = std::min_element( d, d + 4 ) - d;
249 int i0 = idx / 2;
250 int i1 = idx % 2;
251 VECTOR2I middle = ( pts0[i0] + pts1[i1] ) / 2;
252
253 if( i0 == 0 )
254 arc0->SetArcGeometry( middle, arc0->GetArcMid(), arc0->GetEnd() );
255 else
256 arc0->SetArcGeometry( arc0->GetStart(), arc0->GetArcMid(), middle );
257
258 if( i1 == 0 )
259 arc1->SetArcGeometry( middle, arc1->GetArcMid(), arc1->GetEnd() );
260 else
261 arc1->SetArcGeometry( arc1->GetStart(), arc1->GetArcMid(), middle );
262
263 success = true;
264 }
265 else if( ( shape0 == SHAPE_T::BEZIER && shape1 == SHAPE_T::ARC )
266 || ( shape0 == SHAPE_T::ARC && shape1 == SHAPE_T::BEZIER ) )
267 {
268 PCB_SHAPE* bezShape = shape0 == SHAPE_T::BEZIER ? aPrevShape : aShape;
269 PCB_SHAPE* arcShape = shape0 == SHAPE_T::ARC ? aPrevShape : aShape;
270
271 VECTOR2I bezPts[2] = { bezShape->GetStart(), bezShape->GetEnd() };
272 VECTOR2I arcPts[2] = { arcShape->GetStart(), arcShape->GetEnd() };
273
274 SEG::ecoord d[4];
275 d[0] = ( bezPts[0] - arcPts[0] ).SquaredEuclideanNorm();
276 d[1] = ( bezPts[0] - arcPts[1] ).SquaredEuclideanNorm();
277 d[2] = ( bezPts[1] - arcPts[0] ).SquaredEuclideanNorm();
278 d[3] = ( bezPts[1] - arcPts[1] ).SquaredEuclideanNorm();
279
280 int idx = std::min_element( d, d + 4 ) - d;
281
282 switch( idx )
283 {
284 case 0:
285 {
286 VECTOR2I delta = arcPts[0] - bezPts[0];
287 bezShape->SetStart( arcPts[0] );
288 bezShape->SetBezierC1( bezShape->GetBezierC1() + delta );
289 break;
290 }
291 case 1:
292 {
293 VECTOR2I delta = arcPts[1] - bezPts[0];
294 bezShape->SetStart( arcPts[1] );
295 bezShape->SetBezierC1( bezShape->GetBezierC1() + delta );
296 break;
297 }
298 case 2:
299 {
300 VECTOR2I delta = arcPts[0] - bezPts[1];
301 bezShape->SetEnd( arcPts[0] );
302 bezShape->SetBezierC2( bezShape->GetBezierC2() + delta );
303 break;
304 }
305 case 3:
306 {
307 VECTOR2I delta = arcPts[1] - bezPts[1];
308 bezShape->SetEnd( arcPts[1] );
309 bezShape->SetBezierC2( bezShape->GetBezierC2() + delta );
310 break;
311 }
312 }
313
314 success = true;
315 }
316 else if( ( shape0 == SHAPE_T::BEZIER && shape1 == SHAPE_T::SEGMENT )
317 || ( shape0 == SHAPE_T::SEGMENT && shape1 == SHAPE_T::BEZIER ) )
318 {
319 PCB_SHAPE* bezShape = shape0 == SHAPE_T::BEZIER ? aPrevShape : aShape;
320 PCB_SHAPE* segShape = shape0 == SHAPE_T::SEGMENT ? aPrevShape : aShape;
321
322 VECTOR2I bezPts[2] = { bezShape->GetStart(), bezShape->GetEnd() };
323 VECTOR2I segPts[2] = { segShape->GetStart(), segShape->GetEnd() };
324
325 SEG::ecoord d[4];
326 d[0] = ( segPts[0] - bezPts[0] ).SquaredEuclideanNorm();
327 d[1] = ( segPts[0] - bezPts[1] ).SquaredEuclideanNorm();
328 d[2] = ( segPts[1] - bezPts[0] ).SquaredEuclideanNorm();
329 d[3] = ( segPts[1] - bezPts[1] ).SquaredEuclideanNorm();
330
331 int idx = std::min_element( d, d + 4 ) - d;
332
333 switch( idx )
334 {
335 case 0: segShape->SetStart( bezPts[0] ); break;
336 case 1: segShape->SetStart( bezPts[1] ); break;
337 case 2: segShape->SetEnd( bezPts[0] ); break;
338 case 3: segShape->SetEnd( bezPts[1] ); break;
339 }
340
341 success = true;
342 }
343 else if( shape0 == SHAPE_T::BEZIER && shape1 == SHAPE_T::BEZIER )
344 {
345 PCB_SHAPE* bez0 = aPrevShape;
346 PCB_SHAPE* bez1 = aShape;
347
348 VECTOR2I pts0[2] = { bez0->GetStart(), bez0->GetEnd() };
349 VECTOR2I pts1[2] = { bez1->GetStart(), bez1->GetEnd() };
350
351 SEG::ecoord d[4];
352 d[0] = ( pts0[0] - pts1[0] ).SquaredEuclideanNorm();
353 d[1] = ( pts0[0] - pts1[1] ).SquaredEuclideanNorm();
354 d[2] = ( pts0[1] - pts1[0] ).SquaredEuclideanNorm();
355 d[3] = ( pts0[1] - pts1[1] ).SquaredEuclideanNorm();
356
357 int idx = std::min_element( d, d + 4 ) - d;
358 int i0 = idx / 2;
359 int i1 = idx % 2;
360 VECTOR2I middle = ( pts0[i0] + pts1[i1] ) / 2;
361
362 // Adjust first bezier curve
363 if( i0 == 0 )
364 {
365 VECTOR2I delta = middle - bez0->GetStart();
366 bez0->SetStart( middle );
367 bez0->SetBezierC1( bez0->GetBezierC1() + delta );
368 }
369 else
370 {
371 VECTOR2I delta = middle - bez0->GetEnd();
372 bez0->SetEnd( middle );
373 bez0->SetBezierC2( bez0->GetBezierC2() + delta );
374 }
375
376 // Adjust second bezier curve
377 if( i1 == 0 )
378 {
379 VECTOR2I delta = middle - bez1->GetStart();
380 bez1->SetStart( middle );
381 bez1->SetBezierC1( bez1->GetBezierC1() + delta );
382 }
383 else
384 {
385 VECTOR2I delta = middle - bez1->GetEnd();
386 bez1->SetEnd( middle );
387 bez1->SetBezierC2( bez1->GetBezierC2() + delta );
388 }
389
390 success = true;
391 }
392
393 return success;
394 };
395
396 PCB_SHAPE* graphic = nullptr;
397
398 std::set<PCB_SHAPE*> startCandidates;
399 for( PCB_SHAPE* shape : aShapeList )
400 {
401 if( shape->GetShape() == SHAPE_T::SEGMENT || shape->GetShape() == SHAPE_T::ARC
402 || shape->GetShape() == SHAPE_T::BEZIER )
403 {
404 shape->ClearFlags( SKIP_STRUCT );
405 startCandidates.emplace( shape );
406 }
407 }
408
409 while( startCandidates.size() )
410 {
411 graphic = *startCandidates.begin();
412
413 auto walkFrom = [&]( PCB_SHAPE* curr_graphic, VECTOR2I startPt )
414 {
415 VECTOR2I prevPt = startPt;
416
417 for( ;; )
418 {
419 // Get next closest segment.
420 PCB_SHAPE* nextGraphic =
421 findNext( curr_graphic, prevPt, kdTree, adaptor, aChainingEpsilon );
422
423 if( !nextGraphic )
424 break;
425
426 connectPair( curr_graphic, nextGraphic );
427
428 prevPt = closer_to_first( prevPt, nextGraphic->GetStart(), nextGraphic->GetEnd() )
429 ? nextGraphic->GetEnd()
430 : nextGraphic->GetStart();
431 curr_graphic = nextGraphic;
432 curr_graphic->SetFlags( SKIP_STRUCT );
433 startCandidates.erase( curr_graphic );
434 }
435 };
436
437 const VECTOR2I ptEnd = graphic->GetEnd();
438 const VECTOR2I ptStart = graphic->GetStart();
439
440 PCB_SHAPE* grAtEnd = findNext( graphic, ptEnd, kdTree, adaptor, aChainingEpsilon );
441 PCB_SHAPE* grAtStart = findNext( graphic, ptStart, kdTree, adaptor, aChainingEpsilon );
442
443 bool beginFromEndPt = true;
444
445 // We need to start walking from a point that is closest to a point of another shape.
446 if( grAtEnd && grAtStart )
447 {
448 SEG::ecoord dAtEnd = min_distance_sq( ptEnd, grAtEnd->GetStart(), grAtEnd->GetEnd() );
449
450 SEG::ecoord dAtStart =
451 min_distance_sq( ptStart, grAtStart->GetStart(), grAtStart->GetEnd() );
452
453 beginFromEndPt = dAtEnd <= dAtStart;
454 }
455 else if( grAtEnd )
456 beginFromEndPt = true;
457 else if( grAtStart )
458 beginFromEndPt = false;
459
460 if( beginFromEndPt )
461 {
462 // Do not inline GetEnd / GetStart as endpoints may update
463 walkFrom( graphic, graphic->GetEnd() );
464 walkFrom( graphic, graphic->GetStart() );
465 }
466 else
467 {
468 walkFrom( graphic, graphic->GetStart() );
469 walkFrom( graphic, graphic->GetEnd() );
470 }
471
472 startCandidates.erase( graphic );
473 }
474}
std::vector< VECTOR2I > arcPts(const VECTOR2D &aCenter, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle, double aRadius)
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:152
EDA_ITEM_FLAGS GetFlags() const
Definition eda_item.h:155
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:283
SHAPE_T GetShape() const
Definition eda_shape.h:185
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:240
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:190
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:280
VECTOR2I GetArcMid() const
void SetBezierC1(const VECTOR2I &aPt) override
void SetEnd(const VECTOR2I &aEnd) override
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
void SetStart(const VECTOR2I &aStart) override
void SetBezierC2(const VECTOR2I &aPt) override
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I::extended_type ecoord
Definition seg.h:40
VECTOR2I B
Definition seg.h:46
bool Intersects(const SEG &aSeg) const
Definition seg.cpp:436
static SEG::ecoord Square(int a)
Definition seg.h:119
OPT_VECTOR2I IntersectLines(const SEG &aSeg) const
Compute the intersection point of lines passing through ends of (this) and aSeg.
Definition seg.h:216
EDA_ANGLE Angle(const SEG &aOther) const
Determine the smallest angle between two segments.
Definition seg.cpp:107
nanoflann::KDTreeSingleIndexAdaptor< nanoflann::L2_Simple_Adaptor< double, PCB_SHAPE_ENDPOINTS_ADAPTOR >, PCB_SHAPE_ENDPOINTS_ADAPTOR, 2 > KDTree
static bool closer_to_first(VECTOR2I aRef, VECTOR2I aFirst, VECTOR2I aSecond)
Local method which qualifies whether the start or end point of a segment is closest to a point.
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:412
#define SKIP_STRUCT
flag indicating that the structure should be ignored
SHAPE_T
Definition eda_shape.h:44
@ SEGMENT
Definition eda_shape.h:46
static const int WELD_GAP_TOLERANCE
static PCB_SHAPE * findNext(PCB_SHAPE *aShape, const VECTOR2I &aPoint, const KDTree &kdTree, const PCB_SHAPE_ENDPOINTS_ADAPTOR &adaptor, double aChainingEpsilon)
Searches for a PCB_SHAPE matching a given end point or start point in a list.
void ConnectBoardShapes(std::vector< PCB_SHAPE * > &aShapeList, int aChainingEpsilon)
Connects shapes to each other, making continious contours (adjacent shapes will have a common vertex)...
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:35
std::vector< std::pair< VECTOR2I, PCB_SHAPE * > > endpoints
bool kdtree_get_bbox(BBOX &) const
PCB_SHAPE_ENDPOINTS_ADAPTOR(const std::vector< PCB_SHAPE * > &shapes)
double kdtree_get_pt(const size_t idx, const size_t dim) const
int delta
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683