KiCad PCB EDA Suite
test_shape_arc.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) 2018-2020 KiCad Developers, see AUTHORS.TXT for contributors.
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
25#include <geometry/shape_arc.h>
26
28
30#include <qa_utils/numeric.h>
32
33#include "geom_test_utils.h"
34
35BOOST_AUTO_TEST_SUITE( ShapeArc )
36
37
42{
51};
52
59static void CheckArcGeom( const SHAPE_ARC& aArc, const ARC_PROPERTIES& aProps, const int aSynErrIU = 1 )
60{
61 // Angular error - note this can get quite large for very small arcs,
62 // as the integral position rounding has a relatively greater effect
63 const double angle_tol_deg = 1.0;
64
65 // Position error - rounding to nearest integer
66 const int pos_tol = 1;
67
68 BOOST_CHECK_PREDICATE( KI_TEST::IsVecWithinTol<VECTOR2I>,
69 ( aProps.m_start_point )( aProps.m_start_point )( pos_tol ) );
70
71 BOOST_CHECK_PREDICATE( KI_TEST::IsVecWithinTol<VECTOR2I>,
72 ( aArc.GetP1() )( aProps.m_end_point )( pos_tol ) );
73
74 BOOST_CHECK_PREDICATE( KI_TEST::IsVecWithinTol<VECTOR2I>,
75 ( aArc.GetCenter() )( aProps.m_center_point )( aSynErrIU ) );
76
77 BOOST_CHECK_PREDICATE( KI_TEST::IsWithinWrapped<double>,
78 ( aArc.GetCentralAngle().AsDegrees() )( aProps.m_center_angle )( 360.0 )( angle_tol_deg ) );
79
80 BOOST_CHECK_PREDICATE( KI_TEST::IsWithinWrapped<double>,
81 ( aArc.GetStartAngle().AsDegrees() )( aProps.m_start_angle )( 360.0 )( angle_tol_deg ) );
82
83 BOOST_CHECK_PREDICATE( KI_TEST::IsWithinWrapped<double>,
84 ( aArc.GetEndAngle().AsDegrees() )( aProps.m_end_angle )( 360.0 )( angle_tol_deg ) );
85
86 BOOST_CHECK_PREDICATE( KI_TEST::IsWithin<double>,
87 ( aArc.GetRadius() )( aProps.m_radius )( aSynErrIU ) );
88
90 const auto chord = aArc.GetChord();
91
92 BOOST_CHECK_PREDICATE( KI_TEST::IsVecWithinTol<VECTOR2I>,
93 ( chord.A )( aProps.m_start_point )( pos_tol ) );
94
95 BOOST_CHECK_PREDICATE( KI_TEST::IsVecWithinTol<VECTOR2I>,
96 ( chord.B )( aProps.m_end_point )( pos_tol ) );
97
99 BOOST_CHECK_EQUAL( aArc.IsSolid(), true );
100
101 BOOST_CHECK_PREDICATE( KI_TEST::IsBoxWithinTol<BOX2I>,
102 ( aArc.BBox() )( aProps.m_bbox )( pos_tol ) );
103
105}
106
107
114static void CheckArc( const SHAPE_ARC& aArc, const ARC_PROPERTIES& aProps, const int aSynErrIU = 1 )
115{
116 // Check the original arc
117 CheckArcGeom( aArc, aProps, aSynErrIU );
118
119 // Test the Clone function (also tests copy-ctor)
120 std::unique_ptr<SHAPE> new_shape{ aArc.Clone() };
121
122 BOOST_CHECK_EQUAL( new_shape->Type(), SH_ARC );
123
124 SHAPE_ARC* new_arc = dynamic_cast<SHAPE_ARC*>( new_shape.get() );
125
126 BOOST_REQUIRE( new_arc != nullptr );
127
129 CheckArcGeom( *new_arc, aProps, aSynErrIU );
130}
131
136{
137 auto arc = SHAPE_ARC();
138
139 BOOST_CHECK_EQUAL( arc.GetWidth(), 0 );
140
141 static ARC_PROPERTIES null_props{
142 { 0, 0 },
143 { 0, 0 },
144 { 0, 0 },
145 0,
146 0,
147 0,
148 0,
149 };
150
151 CheckArc( arc, null_props );
152}
153
154
161{
165};
166
167
169{
171 std::string m_ctx_name;
172
175
178
181};
182
183
184static const std::vector<ARC_CPA_CASE> arc_cases = {
185 {
186 "C(0,0) 114 + 360 degree",
187 {
188 { 0, 0 },
189 { -306451, 687368 },
190 360,
191 },
192 0,
193 {
194 { 0, 0 },
195 { -306451, 687368 },
196 { -306451, 687368 },
197 360,
198 113.95929,
199 113.95929,
200 752587,
201 { { -752587, -752587 }, { 1505174, 1505174 } },
202 },
203 },
204 {
205 "C(0,0) 180 + 360 degree",
206 {
207 { 0, 0 },
208 { -100, 0 },
209 360,
210 },
211 0,
212 {
213 { 0, 0 },
214 { -100, 0 },
215 { -100, 0 },
216 360,
217 180,
218 180,
219 100,
220 { { -100, -100 }, { 200, 200 } },
221 },
222 },
223 {
224 "C(0,0) 180 + 90 degree",
225 {
226 { 0, 0 },
227 { -100, 0 },
228 90,
229 },
230 0,
231 {
232 { 0, 0 },
233 { -100, 0 },
234 { 0, -100 },
235 90,
236 180,
237 270,
238 100,
239 { { -100, -100 }, { 100, 100 } },
240 },
241 },
242 {
243 "C(100,200) 0 - 30 degree",
244 {
245 { 100, 200 },
246 { 300, 200 },
247 -30,
248 },
249 0,
250 {
251 { 100, 200 },
252 { 300, 200 },
253 { 273, 100 }, // 200 * sin(30) = 100, 200* cos(30) = 173
254 -30,
255 0,
256 330,
257 200,
258 { { 273, 100 }, { 27, 100 } },
259 },
260 },
261 {
262 // This is a "fan shape" which includes the top quadrant point,
263 // so it exercises the bounding box code (centre and end points
264 // do not contain the top quadrant)
265 "C(0,0) 30 + 120 degree",
266 {
267 { 0, 0 },
268 { 17320, 10000 },
269 120,
270 },
271 0,
272 {
273 { 0, 0 },
274 { 17320, 10000 },
275 { -17320, 10000 }, // 200 * sin(30) = 100, 200* cos(30) = 173
276 120,
277 30,
278 150,
279 20000,
280 // bbox defined by: centre, top quadrant point, two endpoints
281 { { -17320, 10000 }, { 17320 * 2, 10000 } },
282 },
283 },
284 {
285 // An arc that covers three quadrant points (L/R, bottom)
286 "C(0,0) 150 + 240 degree",
287 {
288 { 0, 0 },
289 { -17320, 10000 },
290 240,
291 },
292 0,
293 {
294 { 0, 0 },
295 { -17320, 10000 },
296 { 17320, 10000 },
297 240,
298 150,
299 30,
300 20000,
301 // bbox defined by: L/R quads, bottom quad and start/end
302 { { -20000, -20000 }, { 40000, 30000 } },
303 },
304 },
305 {
306 // Same as above but reverse direction
307 "C(0,0) 30 - 300 degree",
308 {
309 { 0, 0 },
310 { 17320, 10000 },
311 -240,
312 },
313 0,
314 {
315 { 0, 0 },
316 { 17320, 10000 },
317 { -17320, 10000 },
318 -240,
319 30,
320 150,
321 20000,
322 // bbox defined by: L/R quads, bottom quad and start/end
323 { { -20000, -20000 }, { 40000, 30000 } },
324 },
325 },
326};
327
328
329BOOST_AUTO_TEST_CASE( BasicCPAGeom )
330{
331 for( const auto& c : arc_cases )
332 {
333 BOOST_TEST_CONTEXT( c.m_ctx_name )
334 {
335
336 const auto this_arc = SHAPE_ARC{ c.m_geom.m_center_point, c.m_geom.m_start_point,
337 EDA_ANGLE( c.m_geom.m_center_angle, DEGREES_T ),
338 c.m_width };
339
340 CheckArc( this_arc, c.m_properties );
341 }
342 }
343}
344
345
346
351{
355};
356
357
359{
361 std::string m_ctx_name;
362
365
368
371};
372
373
374static const std::vector<ARC_TTR_CASE> arc_ttr_cases = {
375 {
376 "90 degree segments intersecting",
377 {
378 { 0, 0, 0, 1000 },
379 { 0, 0, 1000, 0 },
380 1000,
381 },
382 0,
383 {
384 { 1000, 1000 },
385 { 0, 1000 }, //start on first segment
386 { 1000, 0 }, //end on second segment
387 90, //positive angle due to start/end
388 180,
389 270,
390 1000,
391 { { 0, 0 }, { 1000, 1000 } },
392 }
393 },
394 {
395 "45 degree segments intersecting",
396 {
397 { 0, 0, 0, 1000 },
398 { 0, 0, 1000, 1000 },
399 1000,
400 },
401 0,
402 {
403 { 1000, 2414 },
404 { 0, 2414 }, //start on first segment
405 { 1707, 1707 }, //end on second segment
406 135, //positive angle due to start/end
407 180,
408 225,
409 1000,
410 { { 0, 1414 }, { 1707, 1000 } },
411 }
412 },
413 {
414 "135 degree segments intersecting",
415 {
416 { 0, 0, 0, 1000 },
417 { 0, 0, 1000, -1000 },
418 1000,
419 },
420 0,
421 {
422 { 1000, 414 },
423 { 0, 414 }, //start on first segment ( radius * tan(45 /2) )
424 { 293, -293 }, //end on second segment (radius * 1-cos(45)) )
425 45, //positive angle due to start/end
426 180,
427 225,
428 1000,
429 { { 0, -293 }, { 293, 707 } },
430 }
431 }
432
433
434};
435
436
437BOOST_AUTO_TEST_CASE( BasicTTRGeom )
438{
439 for( const auto& c : arc_ttr_cases )
440 {
441 BOOST_TEST_CONTEXT( c.m_ctx_name )
442 {
443 for( int testCase = 0; testCase < 8; ++testCase )
444 {
445 SEG seg1 = c.m_geom.m_segment_1;
446 SEG seg2 = c.m_geom.m_segment_2;
447 ARC_PROPERTIES props = c.m_properties;
448
449 if( testCase > 3 )
450 {
451 //Swap input segments.
452 seg1 = c.m_geom.m_segment_2;
453 seg2 = c.m_geom.m_segment_1;
454
455 //The result should swap start and end points and invert the angles:
456 props.m_end_point = c.m_properties.m_start_point;
457 props.m_start_point = c.m_properties.m_end_point;
458 props.m_start_angle = c.m_properties.m_end_angle;
459 props.m_end_angle = c.m_properties.m_start_angle;
460 props.m_center_angle = -c.m_properties.m_center_angle;
461 }
462
463 //Test all combinations of start and end points for the segments
464 if( ( testCase % 4 ) == 1 || ( testCase % 4 ) == 3 )
465 {
466 //Swap start and end points for seg1
467 VECTOR2I temp = seg1.A;
468 seg1.A = seg1.B;
469 seg1.B = temp;
470 }
471
472 if( ( testCase % 4 ) == 2 || ( testCase % 4 ) == 3 )
473 {
474 //Swap start and end points for seg2
475 VECTOR2I temp = seg2.A;
476 seg2.A = seg2.B;
477 seg2.B = temp;
478 }
479
480 const auto this_arc = SHAPE_ARC{ seg1, seg2,
481 c.m_geom.m_radius, c.m_width };
482
483 // Error of 4 IU permitted for the center and radius calculation
484 CheckArc( this_arc, props, SHAPE_ARC::MIN_PRECISION_IU );
485 }
486 }
487 }
488}
489
490
495{
499};
500
501
503{
505 std::string m_ctx_name;
506
509
512
515};
516
517
518
519static const std::vector<ARC_SEC_CASE> arc_sec_cases = {
520 { "180 deg, clockwise", { { 100, 0 }, { 0, 0 }, { 50, 0 } }, true, { 50, -50 } },
521 { "180 deg, anticlockwise", { { 100, 0 }, { 0, 0 }, { 50, 0 } }, false, { 50, 50 } },
522 { "180 deg flipped, clockwise", { { 0, 0 }, { 100, 0 }, { 50, 0 } }, true, { 50, 50 } },
523 { "180 deg flipped, anticlockwise", { { 0, 0 }, { 100, 0 }, { 50, 0 } }, false, { 50, -50 } },
524 { "90 deg, clockwise", { { -100, 0 }, { 0, 100 }, { 0, 0 } }, true, { -71, 71 } },
525 { "90 deg, anticlockwise", { { -100, 0 }, { 0, 100 }, { 0, 0 } }, false, { 71, -71 } },
526};
527
528
529BOOST_AUTO_TEST_CASE( BasicSECGeom )
530{
531 for( const auto& c : arc_sec_cases )
532 {
533 BOOST_TEST_CONTEXT( c.m_ctx_name )
534 {
535 VECTOR2I start = c.m_geom.m_start;
536 VECTOR2I end = c.m_geom.m_end;
537 VECTOR2I center = c.m_geom.m_center;
538 bool cw = c.m_clockwise;
539
540 SHAPE_ARC this_arc;
541 this_arc.ConstructFromStartEndCenter( start, end, center, cw );
542
543 BOOST_CHECK_EQUAL( this_arc.GetArcMid(), c.m_expected_mid );
544 }
545 }
546}
547
548
550{
551 std::string m_ctx_name;
557};
558
559
560static const std::vector<ARC_PT_COLLIDE_CASE> arc_pt_collide_cases = {
561 { " 270deg, 0 cl, 0 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 100, 0 }, true, 0 },
562 { " 270deg, 0 cl, 90 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 0, 100 }, true, 0 },
563 { " 270deg, 0 cl, 180 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { -100, 0 }, true, 0 },
564 { " 270deg, 0 cl, 270 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 0, -100 }, true, 0 },
565 { " 270deg, 0 cl, 45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 71, 71 }, true, 0 },
566 { " 270deg, 0 cl, -45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 71, -71 }, false, -1 },
567 { "-270deg, 0 cl, 0 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 100, 0 }, true, 0 },
568 { "-270deg, 0 cl, 90 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 0, 100 }, true, 0 },
569 { "-270deg, 0 cl, 180 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { -100, 0 }, true, 0 },
570 { "-270deg, 0 cl, 270 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 0, -100 }, true, 0 },
571 { "-270deg, 0 cl, 45 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 71, 71 }, false, -1 },
572 { "-270deg, 0 cl, -45 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 71, -71 }, true, 0 },
573 { " 270deg, 5 cl, 0 deg, 5 pos X", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 105, 0 }, true, 5 },
574 { " 270deg, 5 cl, 0 deg, 5 pos Y", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 100, -5 }, true, 5 },
575 { " 270deg, 5 cl, 90 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, 105 }, true, 5 },
576 { " 270deg, 5 cl, 180 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { -105, 0 }, true, 5 },
577 { " 270deg, 5 cl, 270 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, -105 }, true, 5 },
578 { " 270deg, 5 cl, 0 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 105, 0 }, true, 5 },
579 { " 270deg, 5 cl, 90 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, 105 }, true, 5 },
580 { " 270deg, 5 cl, 180 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { -105, 0 }, true, 5 },
581 { " 270deg, 5 cl, 270 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, -105 }, true, 5 },
582 { " 270deg, 5 cl, 45 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 74, 75 }, true, 5 }, // 74.246, -74.246
583 { " 270deg, 5 cl, -45 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 74, -75 }, false, -1 }, //74.246, -74.246
584 { " 270deg, 5 cl, 45 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 67, 67 }, true, 5 }, // 67.17, 67.17
585 { " 270deg, 5 cl, -45 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 67, -67 }, false, -1 }, // 67.17, -67.17
586 { " 270deg, 4 cl, 0 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 105, 0 }, false, -1 },
587 { " 270deg, 4 cl, 90 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 0, 105 }, false, -1 },
588 { " 270deg, 4 cl, 180 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { -105, 0 }, false, -1 },
589 { " 270deg, 4 cl, 270 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 0, -105 }, false, -1 },
590 { " 90deg, 0 cl, 0 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 71, -71 }, true, 0 },
591 { " 90deg, 0 cl, 45 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 100, 0 }, true, 0 },
592 { " 90deg, 0 cl, 90 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 71, 71 }, true, 0 },
593 { " 90deg, 0 cl, 135 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 0, -100 }, false, -1 },
594 { " 90deg, 0 cl, -45 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 0, 100 }, false, -1 },
595 { " -90deg, 0 cl, 0 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 71, -71 }, true, 0 },
596 { " -90deg, 0 cl, 45 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 100, 0 }, true, 0 },
597 { " -90deg, 0 cl, 90 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 71, 71 }, true, 0 },
598 { " -90deg, 0 cl, 135 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 0, -100 }, false, -1 },
599 { " -90deg, 0 cl, -45 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 0, 100 }, false, -1 },
600 { "issue 11358",
601 { { 119888000, 60452000 }, { 120904000, 60452000 }, 360.0 },
602 0,
603 { 120395500, 59571830 },
604 true,
605 0 },
606};
607
608
610{
611 for( const auto& c : arc_pt_collide_cases )
612 {
613 BOOST_TEST_CONTEXT( c.m_ctx_name )
614 {
615 SHAPE_ARC arc( c.m_geom.m_center_point, c.m_geom.m_start_point,
616 EDA_ANGLE( c.m_geom.m_center_angle, DEGREES_T ) );
617
618 // Test a zero width arc (distance should equal the clearance)
619 BOOST_TEST_CONTEXT( "Test Clearance" )
620 {
621 int dist = -1;
622 BOOST_CHECK_EQUAL( arc.Collide( c.m_point, c.m_arc_clearance, &dist ),
623 c.m_exp_result );
624 BOOST_CHECK_EQUAL( dist, c.m_exp_distance );
625 }
626
627 // Test by changing the width of the arc (distance should equal zero)
628 BOOST_TEST_CONTEXT( "Test Width" )
629 {
630 int dist = -1;
631 arc.SetWidth( c.m_arc_clearance * 2 );
632 BOOST_CHECK_EQUAL( arc.Collide( c.m_point, 0, &dist ), c.m_exp_result );
633
634 if( c.m_exp_result )
635 BOOST_CHECK_EQUAL( dist, 0 );
636 else
637 BOOST_CHECK_EQUAL( dist, -1 );
638 }
639 }
640 }
641}
642
643
644
645
646
648{
649 std::string m_ctx_name;
655};
656
657
658static const std::vector<ARC_SEG_COLLIDE_CASE> arc_seg_collide_cases = {
659 { "0 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { 100, 0 }, { 50, 0 } }, true, 0 },
660 { "90 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { 0, 100 }, { 0, 50 } }, true, 0 },
661 { "180 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { -100, 0 }, { -50, 0 } }, true, 0 },
662 { "270 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { 0, -100 }, { 0, -50 } }, true, 0 },
663 { "45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { 71, 71 }, { 35, 35 } }, true, 0 },
664 { "-45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { 71, -71 }, { 35, -35 } }, false, -1 },
665 { "seg inside arc start", { { 0, 0 }, { 71, -71 }, 90.0 },
666 10, { { 90, 0 }, { -35, 0 } }, true, 10 },
667 { "seg inside arc end", { { 0, 0 }, { 71, -71 }, 90.0 },
668 10, { { -35, 0 }, { 90, 0 } }, true, 10 },
669 { "large diameter arc", { { 172367922, 82282076 }, { 162530000, 92120000 }, -45.0 },
670 433300, { { 162096732, 92331236 }, { 162096732, 78253268 } }, true, 433268 },
671};
672
673
675{
676 for( const auto& c : arc_seg_collide_cases )
677 {
678 BOOST_TEST_CONTEXT( c.m_ctx_name )
679 {
680 SHAPE_ARC arc( c.m_geom.m_center_point, c.m_geom.m_start_point,
681 EDA_ANGLE( c.m_geom.m_center_angle, DEGREES_T ) );
682
683 // Test a zero width arc (distance should equal the clearance)
684 BOOST_TEST_CONTEXT( "Test Clearance" )
685 {
686 int dist = -1;
687 BOOST_CHECK_EQUAL( arc.Collide( c.m_seg, c.m_arc_clearance, &dist ),
688 c.m_exp_result );
689 BOOST_CHECK_EQUAL( dist, c.m_exp_distance );
690 }
691
692 // Test by changing the width of the arc (distance should equal zero)
693 BOOST_TEST_CONTEXT( "Test Width" )
694 {
695 int dist = -1;
696 arc.SetWidth( c.m_arc_clearance * 2 );
697 BOOST_CHECK_EQUAL( arc.Collide( c.m_seg, 0, &dist ), c.m_exp_result );
698
699 if( c.m_exp_result )
700 BOOST_CHECK_EQUAL( dist, 0 );
701 else
702 BOOST_CHECK_EQUAL( dist, -1 );
703 }
704 }
705 }
706}
707
708
709
711{
712 // Coordinates and dimensions in millimeters
715 double m_start_x;
716 double m_start_y;
718 double m_width;
719
721 {
725
726 return arc;
727 }
728};
729
730
732{
733 std::string m_ctx_name;
738};
739
740
741static const std::vector<ARC_ARC_COLLIDE_CASE> arc_arc_collide_cases = {
742 { "case 1: No intersection",
743 { 73.843527, 74.355869, 71.713528, 72.965869, -76.36664803, 0.2 },
744 { 71.236473, 74.704131, 73.366472, 76.094131, -76.36664803, 0.2 },
745 0,
746 false },
747 { "case 2: No intersection",
748 { 82.542335, 74.825975, 80.413528, 73.435869, -76.4, 0.2 },
749 { 76.491192, 73.839894, 78.619999, 75.23, -76.4, 0.2 },
750 0,
751 false },
752 { "case 3: No intersection",
753 { 89.318807, 74.810106, 87.19, 73.42, -76.4, 0.2 },
754 { 87.045667, 74.632941, 88.826472, 75.794131, -267.9, 0.2 },
755 0,
756 false },
757 { "case 4: Co-centered not intersecting",
758 { 94.665667, 73.772941, 96.446472, 74.934131, -267.9, 0.2 },
759 { 94.665667, 73.772941, 93.6551, 73.025482, -255.5, 0.2 },
760 0,
761 false },
762 { "case 5: Not intersecting, but end points very close",
763 { 72.915251, 80.493054, 73.570159, 81.257692, -260.5, 0.2 },
764 { 73.063537, 82.295989, 71.968628, 81.581351, -255.5, 0.2 },
765 0,
766 false },
767 { "case 6: Coincident centers, colliding due to arc thickness",
768 { 79.279991, 80.67988, 80.3749, 81.394518, -255.5, 0.2 },
769 { 79.279991, 80.67988, 80.3749, 81.694518, -255.5, 0.2 },
770 0,
771 true },
772 { "case 7: Single intersection",
773 { 88.495265, 81.766089, 90.090174, 82.867869, -255.5, 0.2 },
774 { 86.995265, 81.387966, 89.090174, 82.876887, -255.5, 0.2 },
775 0,
776 true },
777 { "case 8: Double intersection",
778 { 96.149734, 81.792126, 94.99, 83.37, -347.2, 0.2 },
779 { 94.857156, 81.240589, 95.91, 83.9, -288.5, 0.2 },
780 0,
781 true },
782 { "case 9: Endpoints within arc width",
783 { 72.915251, 86.493054, 73.970159, 87.257692, -260.5, 0.2 },
784 { 73.063537, 88.295989, 71.968628, 87.581351, -255.5, 0.2 },
785 0,
786 true },
787 { "case 10: Endpoints close, outside, no collision",
788 { 78.915251, 86.393054, 79.970159, 87.157692, 99.5, 0.2 },
789 { 79.063537, 88.295989, 77.968628, 87.581351, -255.5, 0.2 },
790 0,
791 false },
792 { "case 11: Endpoints close, inside, collision due to arc width",
793 { 85.915251, 86.993054, 86.970159, 87.757692, 99.5, 0.2 },
794 { 86.063537, 88.295989, 84.968628, 87.581351, -255.5, 0.2 },
795 0,
796 true },
797 { "case 12: Simulated differential pair meander",
798 { 94.6551, 88.296, 95.6551, 88.296, 90.0, 0.1 },
799 { 94.6551, 88.296, 95.8551, 88.296, 90.0, 0.1 },
800 0.1,
801 false },
802 { "case 13: One arc fully enclosed in other, non-concentric",
803 { 73.77532, 93.413654, 75.70532, 93.883054, 60.0, 0.1 },
804 { 73.86532, 93.393054, 75.86532, 93.393054, 90.0, 0.3 },
805 0,
806 true },
807 { "case 14: One arc fully enclosed in other, concentric",
808 { 79.87532, 93.413654, 81.64532, 94.113054, 60.0, 0.1 },
809 { 79.87532, 93.413654, 81.86532, 93.393054, 90.0, 0.3 },
810 0,
811 true },
812};
813
814
816{
817 for( const auto& c : arc_arc_collide_cases )
818 {
819 BOOST_TEST_CONTEXT( c.m_ctx_name )
820 {
821 SHAPE_ARC arc1( c.m_arc1.GenerateArc() );
822 SHAPE_ARC arc2( c.m_arc2.GenerateArc() );
823
824
825 SHAPE_LINE_CHAIN arc1_slc( c.m_arc1.GenerateArc() );
826 arc1_slc.SetWidth( 0 );
827
828 SHAPE_LINE_CHAIN arc2_slc( c.m_arc2.GenerateArc() );
829 arc2_slc.SetWidth( 0 );
830
831 int actual = 0;
832 VECTOR2I location;
833
834 SHAPE* arc1_sh = &arc1;
835 SHAPE* arc2_sh = &arc2;
836 SHAPE* arc1_slc_sh = &arc1_slc;
837 SHAPE* arc2_slc_sh = &arc2_slc;
838
839 bool result_arc_to_arc =
840 arc1_sh->Collide( arc2_sh, PcbMm2iu( c.m_clearance ), &actual, &location );
841
842 // For arc to chain collisions, we need to re-calculate the clearances because the
843 // SHAPE_LINE_CHAIN is zero width
844 int clearance = PcbMm2iu( c.m_clearance ) + ( arc2.GetWidth() / 2 );
845
846 bool result_arc_to_chain =
847 arc1_sh->Collide( arc2_slc_sh, clearance, &actual, &location );
848
849 clearance = PcbMm2iu( c.m_clearance ) + ( arc1.GetWidth() / 2 );
850 bool result_chain_to_arc =
851 arc1_slc_sh->Collide( arc2_sh, clearance, &actual, &location );
852
853 clearance = ( arc1.GetWidth() / 2 ) + ( arc2.GetWidth() / 2 );
854 bool result_chain_to_chain =
855 arc1_slc_sh->Collide( arc2_slc_sh, clearance, &actual, &location );
856
857 BOOST_CHECK_EQUAL( result_arc_to_arc, c.m_exp_result );
858 BOOST_CHECK_EQUAL( result_arc_to_chain, c.m_exp_result );
859 BOOST_CHECK_EQUAL( result_chain_to_arc, c.m_exp_result );
860 BOOST_CHECK_EQUAL( result_chain_to_chain, c.m_exp_result );
861 }
862 }
863}
864
865
866BOOST_AUTO_TEST_CASE( CollideArcToShapeLineChain )
867{
868 SHAPE_ARC arc( VECTOR2I( 206000000, 140110000 ), VECTOR2I( 201574617, 139229737 ),
869 VECTOR2I( 197822958, 136722959 ), 250000 );
870
871 SHAPE_LINE_CHAIN lc( { VECTOR2I( 159600000, 142500000 ), VECTOR2I( 159600000, 142600000 ),
872 VECTOR2I( 166400000, 135800000 ), VECTOR2I( 166400000, 111600000 ),
873 VECTOR2I( 190576804, 111600000 ), VECTOR2I( 192242284, 113265480 ),
874 VECTOR2I( 192255720, 113265480 ), VECTOR2I( 203682188, 124691948 ),
875 VECTOR2I( 203682188, 140332188 ), VECTOR2I( 206000000, 142650000 ) },
876 false );
877
878
879
880 SHAPE* arc_sh = &arc;
881 SHAPE* lc_sh = &lc;
882
883 BOOST_CHECK_EQUAL( arc_sh->Collide( &lc, 100000 ), true );
884 BOOST_CHECK_EQUAL( lc_sh->Collide( &arc, 100000 ), true );
885
886 SEG seg( VECTOR2I( 203682188, 124691948 ), VECTOR2I( 203682188, 140332188 ) );
887 BOOST_CHECK_EQUAL( arc.Collide( seg, 0 ), true );
888}
889
890
891BOOST_AUTO_TEST_CASE( CollideArcToPolygonApproximation )
892{
893 SHAPE_ARC arc( VECTOR2I( 73843527, 74355869 ), VECTOR2I( 71713528, 72965869 ),
894 EDA_ANGLE( -76.36664803, DEGREES_T ), 1000000 );
895
896 // Create a polyset approximation from the arc - error outside (simulating the zone filler)
897 SHAPE_POLY_SET arcBuffer;
898 int clearance = ( arc.GetWidth() * 3 ) / 2;
899 int polygonApproximationError = SHAPE_ARC::DefaultAccuracyForPCB();
900
901 TransformArcToPolygon( arcBuffer, arc.GetP0(), arc.GetArcMid(), arc.GetP1(),
902 arc.GetWidth() + 2 * clearance,
903 polygonApproximationError, ERROR_OUTSIDE );
904
905 BOOST_REQUIRE_EQUAL( arcBuffer.OutlineCount(), 1 );
906 BOOST_CHECK_EQUAL( arcBuffer.HoleCount( 0 ), 0 );
907
908 // Make a reasonably large rectangular outline around the arc shape
909 BOX2I arcbbox = arc.BBox( clearance * 4 );
910
911 SHAPE_LINE_CHAIN zoneOutline( { arcbbox.GetPosition(),
912 arcbbox.GetPosition() + VECTOR2I( arcbbox.GetWidth(), 0 ),
913 arcbbox.GetEnd(),
914 arcbbox.GetEnd() - VECTOR2I( arcbbox.GetWidth(), 0 )
915 },
916 true );
917
918 // Create a synthetic "zone fill" polygon
919 SHAPE_POLY_SET zoneFill;
920 zoneFill.AddOutline( zoneOutline );
921 zoneFill.AddHole( arcBuffer.Outline( 0 ) );
922 zoneFill.CacheTriangulation( false );
923
924 int actual = 0;
925 VECTOR2I location;
926 int epsilon = polygonApproximationError / 10;
927
928 BOOST_CHECK_EQUAL( zoneFill.Collide( &arc, clearance + epsilon, &actual, &location ), true );
929
930 BOOST_CHECK_EQUAL( zoneFill.Collide( &arc, clearance - epsilon, &actual, &location ), false );
931}
932
933
935{
936 std::string m_ctx_name;
938};
939
940
950bool ArePolylineEndPointsNearCircle( const SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aCentre,
951 int aRad, int aTolerance )
952{
953 std::vector<VECTOR2I> points;
954
955 for( int i = 0; i < aPolyline.PointCount(); ++i )
956 {
957 points.push_back( aPolyline.CPoint( i ) );
958 }
959
960 return GEOM_TEST::ArePointsNearCircle( points, aCentre, aRad, aTolerance );
961}
962
963
973bool ArePolylineMidPointsNearCircle( const SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aCentre,
974 int aRad, int aTolerance )
975{
976 std::vector<VECTOR2I> points;
977
978 for( int i = 0; i < aPolyline.PointCount() - 1; ++i )
979 {
980 const VECTOR2I mid_pt = ( aPolyline.CPoint( i ) + aPolyline.CPoint( i + 1 ) ) / 2;
981 points.push_back( mid_pt );
982 }
983
984 return GEOM_TEST::ArePointsNearCircle( points, aCentre, aRad, aTolerance );
985}
986
987
988BOOST_AUTO_TEST_CASE( ArcToPolyline )
989{
990 const std::vector<ARC_TO_POLYLINE_CASE> cases = {
991 {
992 "Zero rad",
993 {
994 { 0, 0 },
995 { 0, 0 },
996 180,
997 },
998 },
999 {
1000 "Semicircle",
1001 {
1002 { 0, 0 },
1003 { -1000000, 0 },
1004 180,
1005 },
1006 },
1007 {
1008 // check that very small circles don't fall apart and that reverse angles
1009 // work too
1010 "Extremely small semicircle",
1011 {
1012 { 0, 0 },
1013 { -1000, 0 },
1014 -180,
1015 },
1016 },
1017 {
1018 // Make sure it doesn't only work for "easy" angles
1019 "Non-round geometry",
1020 {
1021 { 0, 0 },
1022 { 1234567, 0 },
1023 42.22,
1024 },
1025 },
1026 };
1027
1028 const int width = 0;
1029
1030 // Note: do not expect accuracies around 1 to work. We use integers internally so we're
1031 // liable to rounding errors. In PCBNew accuracy defaults to 5000 and we don't recommend
1032 // anything lower than 1000 (for performance reasons).
1033 const int accuracy = 100;
1034 const int epsilon = 1;
1035
1036 for( const auto& c : cases )
1037 {
1038 BOOST_TEST_CONTEXT( c.m_ctx_name )
1039 {
1040 const SHAPE_ARC this_arc{ c.m_geom.m_center_point, c.m_geom.m_start_point,
1041 EDA_ANGLE( c.m_geom.m_center_angle, DEGREES_T ), width };
1042
1043 const SHAPE_LINE_CHAIN chain = this_arc.ConvertToPolyline( accuracy );
1044
1045 BOOST_TEST_MESSAGE( "Polyline has " << chain.PointCount() << " points" );
1046
1047 // Start point (exactly) where expected
1048 BOOST_CHECK_EQUAL( chain.CPoint( 0 ), c.m_geom.m_start_point );
1049
1050 // End point (exactly) where expected
1051 BOOST_CHECK_EQUAL( chain.CPoint( -1 ), this_arc.GetP1() );
1052
1053 int radius = ( c.m_geom.m_center_point - c.m_geom.m_start_point ).EuclideanNorm();
1054
1055 // Other points within accuracy + epsilon (for rounding) of where they should be
1056 BOOST_CHECK_PREDICATE( ArePolylineEndPointsNearCircle,
1057 ( chain )( c.m_geom.m_center_point )( radius )( accuracy + epsilon ) );
1058
1059 BOOST_CHECK_PREDICATE( ArePolylineMidPointsNearCircle,
1060 ( chain )( c.m_geom.m_center_point )( radius )( accuracy + epsilon ) );
1061 }
1062 }
1063}
1064
1065
1066BOOST_AUTO_TEST_SUITE_END()
const Vec & GetPosition() const
Definition: box2.h:177
coord_type GetWidth() const
Definition: box2.h:180
const Vec GetEnd() const
Definition: box2.h:178
double AsDegrees() const
Definition: eda_angle.h:149
Definition: seg.h:42
VECTOR2I A
Definition: seg.h:49
VECTOR2I B
Definition: seg.h:50
EDA_ANGLE GetCentralAngle() const
Definition: shape_arc.cpp:445
const VECTOR2I & GetArcMid() const
Definition: shape_arc.h:114
SEG GetChord() const
Definition: shape_arc.h:187
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Definition: shape_arc.cpp:352
EDA_ANGLE GetEndAngle() const
Definition: shape_arc.cpp:423
int GetWidth() const
Definition: shape_arc.h:157
void SetWidth(int aWidth)
Definition: shape_arc.h:152
SHAPE_ARC & ConstructFromStartEndCenter(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCenter, bool aClockwise=false, double aWidth=0)
Constructs this arc from the given start, end and center.
Definition: shape_arc.cpp:209
const VECTOR2I & GetP1() const
Definition: shape_arc.h:113
bool Collide(const SEG &aSeg, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the segment aSeg than aClearance,...
Definition: shape_arc.cpp:241
double GetRadius() const
Definition: shape_arc.cpp:455
EDA_ANGLE GetStartAngle() const
Definition: shape_arc.cpp:416
static double DefaultAccuracyForPCB()
Definition: shape_arc.h:221
SHAPE * Clone() const override
Return a dynamically allocated copy of the shape.
Definition: shape_arc.h:82
VECTOR2I GetCenter() const
Definition: shape_arc.cpp:430
const VECTOR2I & GetP0() const
Definition: shape_arc.h:112
bool IsSolid() const override
Definition: shape_arc.h:162
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
void SetWidth(int aWidth)
Set the width of all segments in the chain.
Represent a set of closed polygons.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new hole to the given outline (default: last) and returns its index.
int HoleCount(int aOutline) const
Return the reference to aIndex-th outline in the set.
SHAPE_LINE_CHAIN & Outline(int aIndex)
int OutlineCount() const
Return the number of vertices in a given outline/hole.
An abstract shape on 2D plane.
Definition: shape.h:123
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition: shape.h:178
static const int MIN_PRECISION_IU
This is the minimum precision for all the points in a shape.
Definition: shape.h:128
void TransformArcToPolygon(SHAPE_POLY_SET &aCornerBuffer, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arc to multiple straight segments.
constexpr int PcbMm2iu(double mm)
@ DEGREES_T
Definition: eda_angle.h:31
@ ERROR_OUTSIDE
bool ArePointsNearCircle(const std::vector< VECTOR2< T > > &aPoints, const VECTOR2< T > &aCentre, T aRad, T aTol)
Predicate for checking a set of points is within a certain tolerance of a circle.
Numerical test predicates.
@ SH_ARC
circular arc
Definition: shape.h:51
Info to set up an arc by centre, start point and angle.
ARC_CENTRE_PT_ANGLE m_geom
Geom of the arc.
std::string m_ctx_name
The text context name.
ARC_PROPERTIES m_properties
Expected properties.
int m_width
Arc line width.
double m_center_angle
SHAPE_ARC GenerateArc() const
All properties of an arc (depending on how it's constructed, some of these might be the same as the c...
VECTOR2I m_start_point
VECTOR2I m_end_point
VECTOR2I m_center_point
ARC_CENTRE_PT_ANGLE m_geom
std::string m_ctx_name
The text context name.
bool m_clockwise
clockwise or anti-clockwise?
ARC_START_END_CENTER m_geom
Geom of the arc.
VECTOR2I m_expected_mid
Expected mid-point of the arc.
ARC_CENTRE_PT_ANGLE m_geom
Info to set up an arc start, end and center.
Info to set up an arc by tangent to two segments and a radius.
ARC_CENTRE_PT_ANGLE m_geom
ARC_PROPERTIES m_properties
Expected properties.
ARC_TAN_TAN_RADIUS m_geom
Geom of the arc.
int m_width
Arc line width.
std::string m_ctx_name
The text context name.
bool ArePolylineMidPointsNearCircle(const SHAPE_LINE_CHAIN &aPolyline, const VECTOR2I &aCentre, int aRad, int aTolerance)
Predicate for checking a polyline has all the segment mid points on (near) a circle of given centre a...
static const std::vector< ARC_PT_COLLIDE_CASE > arc_pt_collide_cases
static void CheckArcGeom(const SHAPE_ARC &aArc, const ARC_PROPERTIES &aProps, const int aSynErrIU=1)
Check a SHAPE_ARC against a given set of geometric properties.
static const std::vector< ARC_SEC_CASE > arc_sec_cases
bool ArePolylineEndPointsNearCircle(const SHAPE_LINE_CHAIN &aPolyline, const VECTOR2I &aCentre, int aRad, int aTolerance)
Predicate for checking a polyline has all the points on (near) a circle of given centre and radius.
static const std::vector< ARC_SEG_COLLIDE_CASE > arc_seg_collide_cases
static void CheckArc(const SHAPE_ARC &aArc, const ARC_PROPERTIES &aProps, const int aSynErrIU=1)
Check an arcs geometry and other class functions.
BOOST_AUTO_TEST_CASE(NullCtor)
Check correct handling of filter strings (as used by WX)
static const std::vector< ARC_CPA_CASE > arc_cases
static const std::vector< ARC_TTR_CASE > arc_ttr_cases
static const std::vector< ARC_ARC_COLLIDE_CASE > arc_arc_collide_cases
double EuclideanNorm(const VECTOR2I &vector)
Definition: trigo.h:129
#define BOOST_TEST_CONTEXT(A)
VECTOR2< double > VECTOR2D
Definition: vector2d.h:606
VECTOR2< int > VECTOR2I
Definition: vector2d.h:607