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 <pcb_shape.h>
27#include <geometry/circle.h>
28
29#include <nanoflann.hpp>
30
31
33{
34 std::vector<std::pair<VECTOR2I, PCB_SHAPE*>> endpoints;
35
36 PCB_SHAPE_ENDPOINTS_ADAPTOR( const std::vector<PCB_SHAPE*>& shapes )
37 {
38 endpoints.reserve( shapes.size() * 2 );
39
40 for( PCB_SHAPE* shape : shapes )
41 {
42 endpoints.emplace_back( shape->GetStart(), shape );
43 endpoints.emplace_back( shape->GetEnd(), shape );
44 }
45 }
46
47 // Required by nanoflann
48 size_t kdtree_get_point_count() const { return endpoints.size(); }
49
50 // Returns the dim'th component of the idx'th point
51 double kdtree_get_pt( const size_t idx, const size_t dim ) const
52 {
53 if( dim == 0 )
54 return static_cast<double>( endpoints[idx].first.x );
55 else
56 return static_cast<double>( endpoints[idx].first.y );
57 }
58
59 template <class BBOX>
60 bool kdtree_get_bbox( BBOX& ) const
61 {
62 return false;
63 }
64};
65
66using KDTree = nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double, PCB_SHAPE_ENDPOINTS_ADAPTOR>,
68 2 /* dim */ >;
69
70
81static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const VECTOR2I& aPoint, const KDTree& kdTree,
82 const PCB_SHAPE_ENDPOINTS_ADAPTOR& adaptor, double aChainingEpsilon )
83{
84 const double query_pt[2] = { static_cast<double>( aPoint.x ), static_cast<double>( aPoint.y ) };
85
86 uint32_t indices[2];
87 double distances[2];
88 kdTree.knnSearch( query_pt, 2, indices, distances );
89
90 if( distances[0] == std::numeric_limits<double>::max() )
91 return nullptr;
92
93 // Find the closest valid candidate
94 PCB_SHAPE* closest_graphic = nullptr;
95 double closest_dist_sq = aChainingEpsilon * aChainingEpsilon;
96
97 for( size_t i = 0; i < 2; ++i )
98 {
99 if( distances[i] == std::numeric_limits<double>::max() )
100 continue;
101
102 PCB_SHAPE* candidate = adaptor.endpoints[indices[i]].second;
103
104 if( candidate == aShape )
105 continue;
106
107 if( candidate->GetFlags() & SKIP_STRUCT )
108 continue;
109
110 if( distances[i] < closest_dist_sq )
111 {
112 closest_dist_sq = distances[i];
113 closest_graphic = candidate;
114 }
115 }
116
117 return closest_graphic;
118}
119
120
121void ConnectBoardShapes( std::vector<PCB_SHAPE*>& aShapeList, int aChainingEpsilon )
122{
123 if( aShapeList.size() == 0 )
124 return;
125
126 // Pre-build KD-tree
127 PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( aShapeList );
128 KDTree kdTree( 2, adaptor );
129
130 auto closer_to_first = []( const VECTOR2I& aRef, const VECTOR2I& aFirst,
131 const VECTOR2I& aSecond ) -> bool
132 {
133 return ( aRef - aFirst ).SquaredEuclideanNorm() < ( aRef - aSecond ).SquaredEuclideanNorm();
134 };
135
136 auto min_distance_sq = []( const VECTOR2I& aRef, const VECTOR2I& aFirst,
137 const VECTOR2I& aSecond ) -> SEG::ecoord
138 {
139 return std::min( ( aRef - aFirst ).SquaredEuclideanNorm(),
140 ( aRef - aSecond ).SquaredEuclideanNorm() );
141 };
142
143 auto connectPair = [&]( PCB_SHAPE* aPrevShape, PCB_SHAPE* aShape )
144 {
145 bool success = false;
146
147 SHAPE_T shape0 = aPrevShape->GetShape();
148 SHAPE_T shape1 = aShape->GetShape();
149
150 if( shape0 == SHAPE_T::SEGMENT && shape1 == SHAPE_T::SEGMENT )
151 {
152 SEG seg0( aPrevShape->GetStart(), aPrevShape->GetEnd() );
153 SEG seg1( aShape->GetStart(), aShape->GetEnd() );
154 SEG::ecoord d[4];
155 d[0] = ( seg0.A - seg1.A ).SquaredEuclideanNorm();
156 d[1] = ( seg0.A - seg1.B ).SquaredEuclideanNorm();
157 d[2] = ( seg0.B - seg1.A ).SquaredEuclideanNorm();
158 d[3] = ( seg0.B - seg1.B ).SquaredEuclideanNorm();
159
160 int idx = std::min_element( d, d + 4 ) - d;
161 int i0 = idx / 2;
162 int i1 = idx % 2;
163
164 if( seg0.Intersects( seg1 ) || seg0.Angle( seg1 ) > ANGLE_45 )
165 {
166 if( OPT_VECTOR2I inter = seg0.IntersectLines( seg1 ) )
167 {
168 if( i0 == 0 )
169 aPrevShape->SetStart( *inter );
170 else
171 aPrevShape->SetEnd( *inter );
172
173 if( i1 == 0 )
174 aShape->SetStart( *inter );
175 else
176 aShape->SetEnd( *inter );
177
178 success = true;
179 }
180 }
181 }
182 else if( ( shape0 == SHAPE_T::ARC && shape1 == SHAPE_T::SEGMENT )
183 || ( shape0 == SHAPE_T::SEGMENT && shape1 == SHAPE_T::ARC ) )
184 {
185 PCB_SHAPE* arcShape = shape0 == SHAPE_T::ARC ? aPrevShape : aShape;
186 PCB_SHAPE* segShape = shape0 == SHAPE_T::SEGMENT ? aPrevShape : aShape;
187
188 VECTOR2I arcPts[2] = { arcShape->GetStart(), arcShape->GetEnd() };
189 VECTOR2I segPts[2] = { segShape->GetStart(), segShape->GetEnd() };
190
191 SEG::ecoord d[4];
192 d[0] = ( segPts[0] - arcPts[0] ).SquaredEuclideanNorm();
193 d[1] = ( segPts[0] - arcPts[1] ).SquaredEuclideanNorm();
194 d[2] = ( segPts[1] - arcPts[0] ).SquaredEuclideanNorm();
195 d[3] = ( segPts[1] - arcPts[1] ).SquaredEuclideanNorm();
196
197 int idx = std::min_element( d, d + 4 ) - d;
198
199 switch( idx )
200 {
201 case 0: segShape->SetStart( arcPts[0] ); break;
202 case 1: segShape->SetStart( arcPts[1] ); break;
203 case 2: segShape->SetEnd( arcPts[0] ); break;
204 case 3: segShape->SetEnd( arcPts[1] ); break;
205 }
206
207 success = true;
208 }
209 else if( shape0 == SHAPE_T::ARC && shape1 == SHAPE_T::ARC )
210 {
211 PCB_SHAPE* arc0 = aPrevShape;
212 PCB_SHAPE* arc1 = aShape;
213
214 VECTOR2I pts0[2] = { arc0->GetStart(), arc0->GetEnd() };
215 VECTOR2I pts1[2] = { arc1->GetStart(), arc1->GetEnd() };
216
217 SEG::ecoord d[4];
218 d[0] = ( pts0[0] - pts1[0] ).SquaredEuclideanNorm();
219 d[1] = ( pts0[0] - pts1[1] ).SquaredEuclideanNorm();
220 d[2] = ( pts0[1] - pts1[0] ).SquaredEuclideanNorm();
221 d[3] = ( pts0[1] - pts1[1] ).SquaredEuclideanNorm();
222
223 int idx = std::min_element( d, d + 4 ) - d;
224 int i0 = idx / 2;
225 int i1 = idx % 2;
226 VECTOR2I middle = ( pts0[i0] + pts1[i1] ) / 2;
227
228 if( i0 == 0 )
229 arc0->SetArcGeometry( middle, arc0->GetArcMid(), arc0->GetEnd() );
230 else
231 arc0->SetArcGeometry( arc0->GetStart(), arc0->GetArcMid(), middle );
232
233 if( i1 == 0 )
234 arc1->SetArcGeometry( middle, arc1->GetArcMid(), arc1->GetEnd() );
235 else
236 arc1->SetArcGeometry( arc1->GetStart(), arc1->GetArcMid(), middle );
237
238 success = true;
239 }
240 else if( ( shape0 == SHAPE_T::BEZIER && shape1 == SHAPE_T::ARC )
241 || ( shape0 == SHAPE_T::ARC && shape1 == SHAPE_T::BEZIER ) )
242 {
243 PCB_SHAPE* bezShape = shape0 == SHAPE_T::BEZIER ? aPrevShape : aShape;
244 PCB_SHAPE* arcShape = shape0 == SHAPE_T::ARC ? aPrevShape : aShape;
245
246 VECTOR2I bezPts[2] = { bezShape->GetStart(), bezShape->GetEnd() };
247 VECTOR2I arcPts[2] = { arcShape->GetStart(), arcShape->GetEnd() };
248
249 SEG::ecoord d[4];
250 d[0] = ( bezPts[0] - arcPts[0] ).SquaredEuclideanNorm();
251 d[1] = ( bezPts[0] - arcPts[1] ).SquaredEuclideanNorm();
252 d[2] = ( bezPts[1] - arcPts[0] ).SquaredEuclideanNorm();
253 d[3] = ( bezPts[1] - arcPts[1] ).SquaredEuclideanNorm();
254
255 int idx = std::min_element( d, d + 4 ) - d;
256
257 switch( idx )
258 {
259 case 0:
260 {
261 VECTOR2I delta = arcPts[0] - bezPts[0];
262 bezShape->SetStart( arcPts[0] );
263 bezShape->SetBezierC1( bezShape->GetBezierC1() + delta );
264 break;
265 }
266 case 1:
267 {
268 VECTOR2I delta = arcPts[1] - bezPts[0];
269 bezShape->SetStart( arcPts[1] );
270 bezShape->SetBezierC1( bezShape->GetBezierC1() + delta );
271 break;
272 }
273 case 2:
274 {
275 VECTOR2I delta = arcPts[0] - bezPts[1];
276 bezShape->SetEnd( arcPts[0] );
277 bezShape->SetBezierC2( bezShape->GetBezierC2() + delta );
278 break;
279 }
280 case 3:
281 {
282 VECTOR2I delta = arcPts[1] - bezPts[1];
283 bezShape->SetEnd( arcPts[1] );
284 bezShape->SetBezierC2( bezShape->GetBezierC2() + delta );
285 break;
286 }
287 }
288
289 success = true;
290 }
291 else if( ( shape0 == SHAPE_T::BEZIER && shape1 == SHAPE_T::SEGMENT )
292 || ( shape0 == SHAPE_T::SEGMENT && shape1 == SHAPE_T::BEZIER ) )
293 {
294 PCB_SHAPE* bezShape = shape0 == SHAPE_T::BEZIER ? aPrevShape : aShape;
295 PCB_SHAPE* segShape = shape0 == SHAPE_T::SEGMENT ? aPrevShape : aShape;
296
297 VECTOR2I bezPts[2] = { bezShape->GetStart(), bezShape->GetEnd() };
298 VECTOR2I segPts[2] = { segShape->GetStart(), segShape->GetEnd() };
299
300 SEG::ecoord d[4];
301 d[0] = ( segPts[0] - bezPts[0] ).SquaredEuclideanNorm();
302 d[1] = ( segPts[0] - bezPts[1] ).SquaredEuclideanNorm();
303 d[2] = ( segPts[1] - bezPts[0] ).SquaredEuclideanNorm();
304 d[3] = ( segPts[1] - bezPts[1] ).SquaredEuclideanNorm();
305
306 int idx = std::min_element( d, d + 4 ) - d;
307
308 switch( idx )
309 {
310 case 0: segShape->SetStart( bezPts[0] ); break;
311 case 1: segShape->SetStart( bezPts[1] ); break;
312 case 2: segShape->SetEnd( bezPts[0] ); break;
313 case 3: segShape->SetEnd( bezPts[1] ); break;
314 }
315
316 success = true;
317 }
318 else if( shape0 == SHAPE_T::BEZIER && shape1 == SHAPE_T::BEZIER )
319 {
320 PCB_SHAPE* bez0 = aPrevShape;
321 PCB_SHAPE* bez1 = aShape;
322
323 VECTOR2I pts0[2] = { bez0->GetStart(), bez0->GetEnd() };
324 VECTOR2I pts1[2] = { bez1->GetStart(), bez1->GetEnd() };
325
326 SEG::ecoord d[4];
327 d[0] = ( pts0[0] - pts1[0] ).SquaredEuclideanNorm();
328 d[1] = ( pts0[0] - pts1[1] ).SquaredEuclideanNorm();
329 d[2] = ( pts0[1] - pts1[0] ).SquaredEuclideanNorm();
330 d[3] = ( pts0[1] - pts1[1] ).SquaredEuclideanNorm();
331
332 int idx = std::min_element( d, d + 4 ) - d;
333 int i0 = idx / 2;
334 int i1 = idx % 2;
335 VECTOR2I middle = ( pts0[i0] + pts1[i1] ) / 2;
336
337 // Adjust first bezier curve
338 if( i0 == 0 )
339 {
340 VECTOR2I delta = middle - bez0->GetStart();
341 bez0->SetStart( middle );
342 bez0->SetBezierC1( bez0->GetBezierC1() + delta );
343 }
344 else
345 {
346 VECTOR2I delta = middle - bez0->GetEnd();
347 bez0->SetEnd( middle );
348 bez0->SetBezierC2( bez0->GetBezierC2() + delta );
349 }
350
351 // Adjust second bezier curve
352 if( i1 == 0 )
353 {
354 VECTOR2I delta = middle - bez1->GetStart();
355 bez1->SetStart( middle );
356 bez1->SetBezierC1( bez1->GetBezierC1() + delta );
357 }
358 else
359 {
360 VECTOR2I delta = middle - bez1->GetEnd();
361 bez1->SetEnd( middle );
362 bez1->SetBezierC2( bez1->GetBezierC2() + delta );
363 }
364
365 success = true;
366 }
367
368 return success;
369 };
370
371 PCB_SHAPE* graphic = nullptr;
372
373 std::set<PCB_SHAPE*> startCandidates;
374 for( PCB_SHAPE* shape : aShapeList )
375 {
376 if( shape->GetShape() == SHAPE_T::SEGMENT || shape->GetShape() == SHAPE_T::ARC
377 || shape->GetShape() == SHAPE_T::BEZIER )
378 {
379 shape->ClearFlags( SKIP_STRUCT );
380 startCandidates.emplace( shape );
381 }
382 }
383
384 while( startCandidates.size() )
385 {
386 graphic = *startCandidates.begin();
387
388 auto walkFrom = [&]( PCB_SHAPE* curr_graphic, VECTOR2I startPt )
389 {
390 VECTOR2I prevPt = startPt;
391
392 for( ;; )
393 {
394 // Get next closest segment.
395 PCB_SHAPE* nextGraphic =
396 findNext( curr_graphic, prevPt, kdTree, adaptor, aChainingEpsilon );
397
398 if( !nextGraphic )
399 break;
400
401 connectPair( curr_graphic, nextGraphic );
402
403 prevPt = closer_to_first( prevPt, nextGraphic->GetStart(), nextGraphic->GetEnd() )
404 ? nextGraphic->GetEnd()
405 : nextGraphic->GetStart();
406 curr_graphic = nextGraphic;
407 curr_graphic->SetFlags( SKIP_STRUCT );
408 startCandidates.erase( curr_graphic );
409 }
410 };
411
412 const VECTOR2I ptEnd = graphic->GetEnd();
413 const VECTOR2I ptStart = graphic->GetStart();
414
415 PCB_SHAPE* grAtEnd = findNext( graphic, ptEnd, kdTree, adaptor, aChainingEpsilon );
416 PCB_SHAPE* grAtStart = findNext( graphic, ptStart, kdTree, adaptor, aChainingEpsilon );
417
418 bool beginFromEndPt = true;
419
420 // We need to start walking from a point that is closest to a point of another shape.
421 if( grAtEnd && grAtStart )
422 {
423 SEG::ecoord dAtEnd = min_distance_sq( ptEnd, grAtEnd->GetStart(), grAtEnd->GetEnd() );
424
425 SEG::ecoord dAtStart =
426 min_distance_sq( ptStart, grAtStart->GetStart(), grAtStart->GetEnd() );
427
428 beginFromEndPt = dAtEnd <= dAtStart;
429 }
430 else if( grAtEnd )
431 beginFromEndPt = true;
432 else if( grAtStart )
433 beginFromEndPt = false;
434
435 if( beginFromEndPt )
436 {
437 // Do not inline GetEnd / GetStart as endpoints may update
438 walkFrom( graphic, graphic->GetEnd() );
439 walkFrom( graphic, graphic->GetStart() );
440 }
441 else
442 {
443 walkFrom( graphic, graphic->GetStart() );
444 walkFrom( graphic, graphic->GetEnd() );
445 }
446
447 startCandidates.erase( graphic );
448 }
449}
std::vector< VECTOR2I > arcPts(const VECTOR2D &aCenter, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle, double aRadius)
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
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 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