KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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 The 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, see <https://www.gnu.org/licenses/>.
18 */
19
21#include <boost/test/data/test_case.hpp>
22
23#include <limits>
24
26#include <geometry/shape_arc.h>
29#include <trigo.h>
30
32#include <qa_utils/numeric.h>
33
34#include "geom_test_utils.h"
35
36BOOST_AUTO_TEST_SUITE( ShapeArc )
37
38
53
60static void CheckArcGeom( const SHAPE_ARC& aArc, const ARC_PROPERTIES& aProps, const int aSynErrIU = 1 )
61{
62 // Angular error - note this can get quite large for very small arcs,
63 // as the integral position rounding has a relatively greater effect
64 const double angle_tol_deg = 2.0;
65
66 // Position error - rounding to nearest integer
67 const int pos_tol = 1;
68
70 ( aProps.m_start_point )( aProps.m_start_point )( pos_tol ) );
71
73 ( aArc.GetP1() )( aProps.m_end_point )( pos_tol ) );
74
76 ( aArc.GetCenter() )( aProps.m_center_point )( aSynErrIU ) );
77
79 ( aArc.GetCentralAngle().AsDegrees() )( aProps.m_center_angle )( 360.0 )( angle_tol_deg ) );
80
82 ( aArc.GetStartAngle().AsDegrees() )( aProps.m_start_angle )( 360.0 )( angle_tol_deg ) );
83
85 ( aArc.GetEndAngle().AsDegrees() )( aProps.m_end_angle )( 360.0 )( angle_tol_deg ) );
86
88 ( aArc.GetRadius() )( aProps.m_radius )( aSynErrIU ) );
89
90 // Angle normalization contracts
91 BOOST_TEST( aArc.GetStartAngle().AsDegrees() >= 0.0 );
92 BOOST_TEST( aArc.GetStartAngle().AsDegrees() <= 360.0 );
93
94 BOOST_TEST( aArc.GetEndAngle().AsDegrees() >= 0.0 );
95 BOOST_TEST( aArc.GetEndAngle().AsDegrees() <= 360.0 );
96
97 BOOST_TEST( aArc.GetCentralAngle().AsDegrees() >= -360.0 );
98 BOOST_TEST( aArc.GetCentralAngle().AsDegrees() <= 360.0 );
99
101 const SEG chord = aArc.GetChord();
102
104 ( chord.A )( aProps.m_start_point )( pos_tol ) );
105
107 ( chord.B )( aProps.m_end_point )( pos_tol ) );
108
110 BOOST_CHECK_EQUAL( aArc.IsSolid(), true );
111
113 ( aArc.BBox() )( aProps.m_bbox )( pos_tol ) );
114
116}
117
118
125static void CheckArc( const SHAPE_ARC& aArc, const ARC_PROPERTIES& aProps, const int aSynErrIU = 1 )
126{
127 // Check the original arc
128 CheckArcGeom( aArc, aProps, aSynErrIU );
129
130 // Test the Clone function (also tests copy-ctor)
131 std::unique_ptr<SHAPE> new_shape{ aArc.Clone() };
132
133 BOOST_REQUIRE_EQUAL( new_shape->Type(), SH_ARC );
134
135 SHAPE_ARC* new_arc = dynamic_cast<SHAPE_ARC*>( new_shape.get() );
136
137 BOOST_REQUIRE( new_arc != nullptr );
138
140 CheckArcGeom( *new_arc, aProps, aSynErrIU );
141}
142
147{
148 auto arc = SHAPE_ARC();
149
150 BOOST_CHECK_EQUAL( arc.GetWidth(), 0 );
151
152 static ARC_PROPERTIES null_props{
153 { 0, 0 },
154 { 0, 0 },
155 { 0, 0 },
156 0,
157 0,
158 0,
159 0,
160 };
161
162 CheckArc( arc, null_props );
163}
164
165
175
187
188
189static const std::vector<ARC_SME_CASE> arc_sme_cases = {
190 {
191 "S(-100,0), M(0,100), E(100,0)",
192 {
193 { -100, 0 },
194 { 0, 100 },
195 { 100, 0 },
196 },
197 0,
198 {
199 { 0, 0 },
200 { -100, 0 },
201 { 100, 0 },
202 180,
203 180,
204 0,
205 100,
206 { { -100, 0 }, { 200, 100 } },
207 },
208 },
209 {
210 "S(100,0), M(0,100), E(-100,0) (reversed)",
211 {
212 { 100, 0 },
213 { 0, 100 },
214 { -100, 0 },
215 },
216 0,
217 {
218 { 0, 0 },
219 { 100, 0 },
220 { -100, 0 },
221 -180,
222 0,
223 180,
224 100,
225 { { -100, 0 }, { 200, 100 } },
226 },
227 },
228 {
229 // This data has a midpoint not exactly at the midway point of the arc.
230 // This should be corrected by the constructor.
231 // The mid point should be at about (-71, -71) for a 270 degree arc, with the
232 // bottom right quadrant open.
233 "S(100,0), M(-100,0), E(0,100) (bad midpoint)",
234 {
235 { 100, 0 },
236 { -100, 0 },
237 { 0, 100 },
238 },
239 0,
240 {
241 { 0, 0 },
242 { 100, 0 },
243 { 0, 100 },
244 -270,
245 0,
246 90,
247 100,
248 { { -100, -100 }, { 200, 200 } },
249 },
250 }
251};
252
253
254BOOST_DATA_TEST_CASE( BasicSMEGeom, boost::unit_test::data::make( arc_sme_cases ), c )
255{
256 const SHAPE_ARC this_arc{
257 c.m_geom.m_start_point,
258 c.m_geom.m_mid_point,
259 c.m_geom.m_end_point,
260 c.m_width,
261 };
262
263 CheckArc( this_arc, c.m_properties );
264}
265
266
276
277
289
290
291static const std::vector<ARC_CPA_CASE> arc_cases = {
292 {
293 "C(0,0) 114 + 360 degree",
294 {
295 { 0, 0 },
296 { -306451, 687368 },
297 360,
298 },
299 0,
300 {
301 { 0, 0 },
302 { -306451, 687368 },
303 { -306451, 687368 },
304 360,
305 113.95929,
306 113.95929,
307 752587,
308 { { -752587, -752587 }, { 1505174, 1505174 } },
309 },
310 },
311 {
312 "C(0,0) 180 + 360 degree",
313 {
314 { 0, 0 },
315 { -100, 0 },
316 360,
317 },
318 0,
319 {
320 { 0, 0 },
321 { -100, 0 },
322 { -100, 0 },
323 360,
324 180,
325 180,
326 100,
327 { { -100, -100 }, { 200, 200 } },
328 },
329 },
330 {
331 "C(0,0) 180 + 90 degree",
332 {
333 { 0, 0 },
334 { -100, 0 },
335 90,
336 },
337 0,
338 {
339 { 0, 0 },
340 { -100, 0 },
341 { 0, -100 },
342 90,
343 180,
344 270,
345 100,
346 { { -100, -100 }, { 100, 100 } },
347 },
348 },
349 {
350 "C(100,200) 0 - 30 degree",
351 {
352 { 100, 200 },
353 { 300, 200 },
354 -30,
355 },
356 0,
357 {
358 { 100, 200 },
359 { 300, 200 },
360 { 273, 100 }, // 200 * sin(30) = 100, 200* cos(30) = 173
361 -30,
362 0,
363 330,
364 200,
365 { { 273, 100 }, { 27, 100 } },
366 },
367 },
368 {
369 // This is a "fan shape" which includes the top quadrant point,
370 // so it exercises the bounding box code (centre and end points
371 // do not contain the top quadrant)
372 "C(0,0) 30 + 120 degree",
373 {
374 { 0, 0 },
375 { 17320, 10000 },
376 120,
377 },
378 0,
379 {
380 { 0, 0 },
381 { 17320, 10000 },
382 { -17320, 10000 }, // 200 * sin(30) = 100, 200* cos(30) = 173
383 120,
384 30,
385 150,
386 20000,
387 // bbox defined by: centre, top quadrant point, two endpoints
388 { { -17320, 10000 }, { 17320 * 2, 10000 } },
389 },
390 },
391 {
392 // An arc that covers three quadrant points (L/R, bottom)
393 "C(0,0) 150 + 240 degree",
394 {
395 { 0, 0 },
396 { -17320, 10000 },
397 240,
398 },
399 0,
400 {
401 { 0, 0 },
402 { -17320, 10000 },
403 { 17320, 10000 },
404 240,
405 150,
406 30,
407 20000,
408 // bbox defined by: L/R quads, bottom quad and start/end
409 { { -20000, -20000 }, { 40000, 30000 } },
410 },
411 },
412 {
413 // Same as above but reverse direction
414 "C(0,0) 30 - 300 degree",
415 {
416 { 0, 0 },
417 { 17320, 10000 },
418 -240,
419 },
420 0,
421 {
422 { 0, 0 },
423 { 17320, 10000 },
424 { -17320, 10000 },
425 -240,
426 30,
427 150,
428 20000,
429 // bbox defined by: L/R quads, bottom quad and start/end
430 { { -20000, -20000 }, { 40000, 30000 } },
431 },
432 },
433};
434
435
436BOOST_DATA_TEST_CASE( BasicCPAGeom, boost::unit_test::data::make( arc_cases ), c )
437{
438 const SHAPE_ARC this_arc{
439 c.m_geom.m_center_point,
440 c.m_geom.m_start_point,
441 EDA_ANGLE( c.m_geom.m_center_angle, DEGREES_T ),
442 c.m_width,
443 };
444
445 CheckArc( this_arc, c.m_properties );
446}
447
448
449
459
460
472
473
474static const std::vector<ARC_TTR_CASE> arc_ttr_cases = {
475 {
476 "90 degree segments intersecting",
477 {
478 { 0, 0, 0, 1000 },
479 { 0, 0, 1000, 0 },
480 1000,
481 },
482 0,
483 {
484 { 1000, 1000 },
485 { 0, 1000 }, //start on first segment
486 { 1000, 0 }, //end on second segment
487 90, //positive angle due to start/end
488 180,
489 270,
490 1000,
491 { { 0, 0 }, { 1000, 1000 } },
492 }
493 },
494 {
495 "45 degree segments intersecting",
496 {
497 { 0, 0, 0, 1000 },
498 { 0, 0, 1000, 1000 },
499 1000,
500 },
501 0,
502 {
503 { 1000, 2414 },
504 { 0, 2414 }, //start on first segment
505 { 1707, 1707 }, //end on second segment
506 135, //positive angle due to start/end
507 180,
508 315,
509 1000,
510 { { 0, 1414 }, { 1707, 1000 } },
511 }
512 },
513 {
514 "135 degree segments intersecting",
515 {
516 { 0, 0, 0, 1000 },
517 { 0, 0, 1000, -1000 },
518 1000,
519 },
520 0,
521 {
522 { 1000, 414 },
523 { 0, 414 }, //start on first segment ( radius * tan(45 /2) )
524 { 293, -293 }, //end on second segment (radius * 1-cos(45)) )
525 45, //positive angle due to start/end
526 180,
527 225,
528 1000,
529 { { 0, -293 }, { 293, 707 } },
530 }
531 }
532
533
534};
535
536
537BOOST_DATA_TEST_CASE( BasicTTRGeom, boost::unit_test::data::make( arc_ttr_cases ), c )
538{
539 for( int testCase = 0; testCase < 8; ++testCase )
540 {
541 SEG seg1 = c.m_geom.m_segment_1;
542 SEG seg2 = c.m_geom.m_segment_2;
543 ARC_PROPERTIES props = c.m_properties;
544
545 if( testCase > 3 )
546 {
547 //Swap input segments.
548 seg1 = c.m_geom.m_segment_2;
549 seg2 = c.m_geom.m_segment_1;
550
551 //The result should swap start and end points and invert the angles:
552 props.m_end_point = c.m_properties.m_start_point;
553 props.m_start_point = c.m_properties.m_end_point;
554 props.m_start_angle = c.m_properties.m_end_angle;
555 props.m_end_angle = c.m_properties.m_start_angle;
556 props.m_center_angle = -c.m_properties.m_center_angle;
557 }
558
559 //Test all combinations of start and end points for the segments
560 if( ( testCase % 4 ) == 1 || ( testCase % 4 ) == 3 )
561 {
562 //Swap start and end points for seg1
563 VECTOR2I temp = seg1.A;
564 seg1.A = seg1.B;
565 seg1.B = temp;
566 }
567
568 if( ( testCase % 4 ) == 2 || ( testCase % 4 ) == 3 )
569 {
570 //Swap start and end points for seg2
571 VECTOR2I temp = seg2.A;
572 seg2.A = seg2.B;
573 seg2.B = temp;
574 }
575
576 const auto this_arc = SHAPE_ARC{ seg1, seg2,
577 c.m_geom.m_radius, c.m_width };
578
579 // Error of 4 IU permitted for the center and radius calculation
581 }
582}
583
584
588struct ARC_START_END_CENTER
589{
590 VECTOR2I m_start;
591 VECTOR2I m_end;
592 VECTOR2I m_center;
593};
594
595
597{
599 ARC_START_END_CENTER m_geom;
600
603
606};
607
608
609
610static const std::vector<ARC_SEC_CASE> arc_sec_cases = {
611 { "180 deg, clockwise", { { 100, 0 }, { 0, 0 }, { 50, 0 } }, true, { 50, -50 } },
612 { "180 deg, anticlockwise", { { 100, 0 }, { 0, 0 }, { 50, 0 } }, false, { 50, 50 } },
613 { "180 deg flipped, clockwise", { { 0, 0 }, { 100, 0 }, { 50, 0 } }, true, { 50, 50 } },
614 { "180 deg flipped, anticlockwise", { { 0, 0 }, { 100, 0 }, { 50, 0 } }, false, { 50, -50 } },
615 { "90 deg, clockwise", { { -100, 0 }, { 0, 100 }, { 0, 0 } }, true, { -71, 71 } },
616 { "90 deg, anticlockwise", { { -100, 0 }, { 0, 100 }, { 0, 0 } }, false, { 71, -71 } },
617};
618
619
620BOOST_DATA_TEST_CASE( BasicSECGeom, boost::unit_test::data::make( arc_sec_cases ), c )
621{
622 VECTOR2I start = c.m_geom.m_start;
623 VECTOR2I end = c.m_geom.m_end;
624 VECTOR2I center = c.m_geom.m_center;
625 bool cw = c.m_clockwise;
626
628 this_arc.ConstructFromStartEndCenter( start, end, center, cw );
629
630 BOOST_CHECK_EQUAL( this_arc.GetArcMid(), c.m_expected_mid );
631}
632
633
643
644static const std::vector<ARC_CICLE_COLLIDE_CASE> arc_circle_collide_cases = {
645 { " Issue 20336, large arc", { { 183000000, 65710001}, {150496913, 147587363},{116291153, 66406583}}, 2000000 / 2, {116300000, 133100000}, 300000, true, 53319 }
646};
647
648
649BOOST_DATA_TEST_CASE( CollideCircle, boost::unit_test::data::make( arc_circle_collide_cases ), c )
650{
651 SHAPE_ARC arc( c.m_geom.m_start_point, c.m_geom.m_mid_point, c.m_geom.m_end_point, 0 );
652 SHAPE_CIRCLE circle( c.m_circle_center, c.m_circle_radius );
653
654 // Test a zero width arc (distance should equal the clearance)
655 BOOST_TEST_CONTEXT( "Test Clearance" )
656 {
657 int dist = -1;
658 BOOST_CHECK_EQUAL( arc.Collide( &circle, c.m_arc_clearance, &dist ), c.m_exp_result );
659 BOOST_CHECK_EQUAL( dist, c.m_exp_distance );
660 }
661
662 // Test by changing the width of the arc (distance should equal zero)
663 BOOST_TEST_CONTEXT( "Test Width" )
664 {
665 int dist = -1;
666 arc.SetWidth( c.m_arc_clearance * 2 );
667 BOOST_CHECK_EQUAL( arc.Collide( &circle, 0, &dist ), c.m_exp_result );
668
669 if( c.m_exp_result )
670 BOOST_CHECK_EQUAL( dist, 0 );
671 else
672 BOOST_CHECK_EQUAL( dist, -1 );
673 }
674}
675
676
685
686
687static const std::vector<ARC_PT_COLLIDE_CASE> arc_pt_collide_cases = {
688 { " 270deg, 0 cl, 0 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 100, 0 }, true, 0 },
689 { " 270deg, 0 cl, 90 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 0, 100 }, true, 0 },
690 { " 270deg, 0 cl, 180 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { -100, 0 }, true, 0 },
691 { " 270deg, 0 cl, 270 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 0, -100 }, true, 0 },
692 { " 270deg, 0 cl, 45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 71, 71 }, true, 0 },
693 { " 270deg, 0 cl, -45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 71, -71 }, false, -1 },
694 { "-270deg, 0 cl, 0 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 100, 0 }, true, 0 },
695 { "-270deg, 0 cl, 90 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 0, 100 }, true, 0 },
696 { "-270deg, 0 cl, 180 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { -100, 0 }, true, 0 },
697 { "-270deg, 0 cl, 270 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 0, -100 }, true, 0 },
698 { "-270deg, 0 cl, 45 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 71, 71 }, false, -1 },
699 { "-270deg, 0 cl, -45 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 71, -71 }, true, 0 },
700 { " 270deg, 5 cl, 0 deg, 5 pos X", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 105, 0 }, true, 5 },
701 { " 270deg, 5 cl, 0 deg, 5 pos Y", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 100, -5 }, true, 5 },
702 { " 270deg, 5 cl, 90 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, 105 }, true, 5 },
703 { " 270deg, 5 cl, 180 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { -105, 0 }, true, 5 },
704 { " 270deg, 5 cl, 270 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, -105 }, true, 5 },
705 { " 270deg, 5 cl, 0 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 105, 0 }, true, 5 },
706 { " 270deg, 5 cl, 90 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, 105 }, true, 5 },
707 { " 270deg, 5 cl, 180 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { -105, 0 }, true, 5 },
708 { " 270deg, 5 cl, 270 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, -105 }, true, 5 },
709 { " 270deg, 5 cl, 45 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 74, 75 }, true, 5 }, // 74.246, -74.246
710 { " 270deg, 5 cl, -45 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 74, -75 }, false, -1 }, //74.246, -74.246
711 { " 270deg, 5 cl, 45 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 67, 67 }, true, 5 }, // 67.17, 67.17
712 { " 270deg, 5 cl, -45 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 67, -67 }, false, -1 }, // 67.17, -67.17
713 { " 270deg, 4 cl, 0 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 105, 0 }, false, -1 },
714 { " 270deg, 4 cl, 90 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 0, 105 }, false, -1 },
715 { " 270deg, 4 cl, 180 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { -105, 0 }, false, -1 },
716 { " 270deg, 4 cl, 270 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 0, -105 }, false, -1 },
717 { " 90deg, 0 cl, 0 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 71, -71 }, true, 0 },
718 { " 90deg, 0 cl, 45 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 100, 0 }, true, 0 },
719 { " 90deg, 0 cl, 90 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 71, 71 }, true, 0 },
720 { " 90deg, 0 cl, 135 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 0, -100 }, false, -1 },
721 { " 90deg, 0 cl, -45 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 0, 100 }, false, -1 },
722 { " -90deg, 0 cl, 0 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 71, -71 }, true, 0 },
723 { " -90deg, 0 cl, 45 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 100, 0 }, true, 0 },
724 { " -90deg, 0 cl, 90 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 71, 71 }, true, 0 },
725 { " -90deg, 0 cl, 135 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 0, -100 }, false, -1 },
726 { " -90deg, 0 cl, -45 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 0, 100 }, false, -1 },
727 { "issue 11358 collide",
728 { { 119888000, 60452000 }, { 120904000, 60452000 }, 360.0 },
729 0,
730 { 120395500, 59571830 },
731 true,
732 0 },
733 { "issue 11358 dist",
734 { { 119888000, 60452000 }, { 120904000, 60452000 }, 360.0 },
735 100,
736 { 118872050, 60452000 },
737 true,
738 50 },
739};
740
741
742BOOST_DATA_TEST_CASE( CollidePt, boost::unit_test::data::make( arc_pt_collide_cases ), c )
743{
744 SHAPE_ARC arc( c.m_geom.m_center_point, c.m_geom.m_start_point,
745 EDA_ANGLE( c.m_geom.m_center_angle, DEGREES_T ) );
746
747 // Test a zero width arc (distance should equal the clearance)
748 BOOST_TEST_CONTEXT( "Test Clearance" )
749 {
750 int dist = -1;
751 BOOST_CHECK_EQUAL( arc.Collide( c.m_point, c.m_arc_clearance, &dist ),
752 c.m_exp_result );
753 BOOST_CHECK_EQUAL( dist, c.m_exp_distance );
754 }
755
756 // Test by changing the width of the arc (distance should equal zero)
757 BOOST_TEST_CONTEXT( "Test Width" )
758 {
759 int dist = -1;
760 arc.SetWidth( c.m_arc_clearance * 2 );
761 BOOST_CHECK_EQUAL( arc.Collide( c.m_point, 0, &dist ), c.m_exp_result );
762
763 if( c.m_exp_result )
764 BOOST_CHECK_EQUAL( dist, 0 );
765 else
766 BOOST_CHECK_EQUAL( dist, -1 );
767 }
768}
769
779
780
781static const std::vector<ARC_SEG_COLLIDE_CASE> arc_seg_collide_cases = {
782 { "0 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { 100, 0 }, { 50, 0 } }, true, 0, { 100, 0 } },
783 { "90 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { 0, 100 }, { 0, 50 } }, true, 0, { 0, 100 } },
784 { "180 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { -100, 0 }, { -50, 0 } }, true, 0, { -100, 0 } },
785 { "270 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { 0, -100 }, { 0, -50 } }, true, 0, { 0, -100 } },
786 { "45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { 71, 71 }, { 35, 35 } }, true, 0, { 70, 70 } },
787 { "-45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { { 71, -71 }, { 35, -35 } }, false, -1, { 0, 0 } },
788 { "seg inside arc start", { { 0, 0 }, { 71, -71 }, 90.0 },
789 10, { { 90, 0 }, { -35, 0 } }, true, 10, { 100, 0 } },
790 { "seg inside arc end", { { 0, 0 }, { 71, -71 }, 90.0 },
791 10, { { -35, 0 }, { 90, 0 } }, true, 10, { 100, 0 } },
792 { "large diameter arc", { { 172367922, 82282076 }, { 162530000, 92120000 }, -45.0 },
793 433300, { { 162096732, 92331236 }, { 162096732, 78253268 } }, true, 433268, { 162530000, 92120000 } },
794 { "upside down collide", { { 26250000, 16520000 }, { 28360000, 16520000 }, 90.0 },
795 0, { { 27545249, 18303444 }, { 27545249, 18114500 } }, true, 0, { 27545249, 18185662 } }
796};
797
798
799BOOST_DATA_TEST_CASE( CollideSeg, boost::unit_test::data::make( arc_seg_collide_cases ), c )
800{
801 SHAPE_ARC arc( c.m_geom.m_center_point, c.m_geom.m_start_point,
802 EDA_ANGLE( c.m_geom.m_center_angle, DEGREES_T ) );
803
804 // Test a zero width arc (distance should equal the clearance)
805 BOOST_TEST_CONTEXT( "Test Clearance" )
806 {
807 int dist = -1;
808 BOOST_CHECK_EQUAL( arc.Collide( c.m_seg, c.m_arc_clearance, &dist ),
809 c.m_exp_result );
810 BOOST_CHECK_EQUAL( dist, c.m_exp_distance );
811 }
812
813 // Test by changing the width of the arc (distance should equal zero)
814 BOOST_TEST_CONTEXT( "Test Width" )
815 {
816 int dist = -1;
817 arc.SetWidth( c.m_arc_clearance * 2 );
818 BOOST_CHECK_EQUAL( arc.Collide( c.m_seg, 0, &dist ), c.m_exp_result );
819
820 if( c.m_exp_result )
821 BOOST_CHECK_EQUAL( dist, 0 );
822 else
823 BOOST_CHECK_EQUAL( dist, -1 );
824 }
825
826 BOOST_TEST_CONTEXT( "Test Collide Point" )
827 {
828 VECTOR2I collide_point;
829 int dist = -1;
830
831 if( c.m_exp_result )
832 {
833 arc.Collide( c.m_seg, c.m_arc_clearance, &dist, &collide_point );
834 BOOST_CHECK_EQUAL( collide_point, c.m_collide_point );
835 }
836 }
837}
838
840{
841 // Coordinates and dimensions in millimeters
844 double m_start_x;
845 double m_start_y;
847 double m_width;
848
850 {
851 SHAPE_ARC arc( VECTOR2D( pcbIUScale.mmToIU( m_center_x ), pcbIUScale.mmToIU( m_center_y ) ),
852 VECTOR2D( pcbIUScale.mmToIU( m_start_x ), pcbIUScale.mmToIU( m_start_y ) ),
854
855 return arc;
856 }
857};
858
859
867
868
869static const std::vector<ARC_ARC_COLLIDE_CASE> arc_arc_collide_cases = {
870 { "case 1: No intersection",
871 { 73.843527, 74.355869, 71.713528, 72.965869, -76.36664803, 0.2 },
872 { 71.236473, 74.704131, 73.366472, 76.094131, -76.36664803, 0.2 },
873 0,
874 false },
875 { "case 2: No intersection",
876 { 82.542335, 74.825975, 80.413528, 73.435869, -76.4, 0.2 },
877 { 76.491192, 73.839894, 78.619999, 75.23, -76.4, 0.2 },
878 0,
879 false },
880 { "case 3: No intersection",
881 { 89.318807, 74.810106, 87.19, 73.42, -76.4, 0.2 },
882 { 87.045667, 74.632941, 88.826472, 75.794131, -267.9, 0.2 },
883 0,
884 false },
885 { "case 4: Co-centered not intersecting",
886 { 94.665667, 73.772941, 96.446472, 74.934131, -267.9, 0.2 },
887 { 94.665667, 73.772941, 93.6551, 73.025482, -255.5, 0.2 },
888 0,
889 false },
890 { "case 5: Not intersecting, but end points very close",
891 { 72.915251, 80.493054, 73.570159, 81.257692, -260.5, 0.2 },
892 { 73.063537, 82.295989, 71.968628, 81.581351, -255.5, 0.2 },
893 0,
894 false },
895 { "case 6: Coincident centers, colliding due to arc thickness",
896 { 79.279991, 80.67988, 80.3749, 81.394518, -255.5, 0.3 },
897 { 79.279991, 80.67988, 80.3749, 81.694518, -255.5, 0.3 },
898 0,
899 true },
900 { "case 7: Single intersection",
901 { 88.495265, 81.766089, 90.090174, 82.867869, -255.5, 0.2 },
902 { 86.995265, 81.387966, 89.090174, 82.876887, -255.5, 0.2 },
903 0,
904 true },
905 { "case 8: Double intersection",
906 { 96.149734, 81.792126, 94.99, 83.37, -347.2, 0.2 },
907 { 94.857156, 81.240589, 95.91, 83.9, -288.5, 0.2 },
908 0,
909 true },
910 { "case 9: Endpoints within arc width",
911 { 72.915251, 86.493054, 73.970159, 87.257692, -260.5, 0.2 },
912 { 73.063537, 88.295989, 71.968628, 87.581351, -255.5, 0.2 },
913 0,
914 true },
915 { "case 10: Endpoints close, outside, no collision",
916 { 78.915251, 86.393054, 79.970159, 87.157692, 99.5, 0.2 },
917 { 79.063537, 88.295989, 77.968628, 87.581351, -255.5, 0.2 },
918 0,
919 false },
920 { "case 11: Endpoints close, inside, collision due to arc width",
921 { 85.915251, 86.993054, 86.970159, 87.757692, 99.5, 0.2 },
922 { 86.063537, 88.295989, 84.968628, 87.581351, -255.5, 0.2 },
923 0,
924 true },
925 { "case 12: Simulated differential pair length-tuning",
926 { 94.6551, 88.296, 95.6551, 88.296, 90.0, 0.1 },
927 { 94.6551, 88.296, 95.8551, 88.296, 90.0, 0.1 },
928 0.1,
929 false },
930 { "case 13: One arc fully enclosed in other, non-concentric",
931 { 73.77532, 93.413654, 75.70532, 93.883054, 60.0, 0.1 },
932 { 73.86532, 93.393054, 75.86532, 93.393054, 90.0, 0.3 },
933 0,
934 true },
935 { "case 14: One arc fully enclosed in other, concentric",
936 { 79.87532, 93.413654, 81.64532, 94.113054, 60.0, 0.1 },
937 { 79.87532, 93.413654, 81.86532, 93.393054, 90.0, 0.3 },
938 0,
939 true },
940 { "case 15: Arcs separated by clearance",
941 { 303.7615, 149.9252, 303.695968, 149.925237, 90.0262, 0.065 },
942 { 303.6345, 149.2637, 303.634523, 148.85619, 89.9957, 0.065 },
943 0.15,
944 false },
945};
946
947
948BOOST_DATA_TEST_CASE( CollideArc, boost::unit_test::data::make( arc_arc_collide_cases ), c )
949{
950 SHAPE_ARC arc1( c.m_arc1.GenerateArc() );
951 SHAPE_ARC arc2( c.m_arc2.GenerateArc() );
952
953
954 SHAPE_LINE_CHAIN arc1_slc( c.m_arc1.GenerateArc() );
955 arc1_slc.SetWidth( 0 );
956
957 SHAPE_LINE_CHAIN arc2_slc( c.m_arc2.GenerateArc() );
958 arc2_slc.SetWidth( 0 );
959
960 int actual = 0;
962
963 SHAPE* arc1_sh = &arc1;
967
968 bool result_arc_to_arc = arc1_sh->Collide( arc2_sh, pcbIUScale.mmToIU( c.m_clearance ),
969 &actual, &location );
970
971 // For arc to chain collisions, we need to re-calculate the clearances because the
972 // SHAPE_LINE_CHAIN is zero width
973 int clearance = pcbIUScale.mmToIU( c.m_clearance ) + ( arc2.GetWidth() / 2 );
974
976
977 clearance = pcbIUScale.mmToIU( c.m_clearance ) + ( arc1.GetWidth() / 2 );
979
980 clearance = ( arc1.GetWidth() / 2 ) + ( arc2.GetWidth() / 2 );
982
987}
988
989
990BOOST_AUTO_TEST_CASE( CollideArcToShapeLineChain )
991{
992 SHAPE_ARC arc( VECTOR2I( 206000000, 140110000 ), VECTOR2I( 201574617, 139229737 ),
993 VECTOR2I( 197822958, 136722959 ), 250000 );
994
995 SHAPE_LINE_CHAIN lc( { VECTOR2I( 159600000, 142500000 ), VECTOR2I( 159600000, 142600000 ),
996 VECTOR2I( 166400000, 135800000 ), VECTOR2I( 166400000, 111600000 ),
997 VECTOR2I( 190576804, 111600000 ), VECTOR2I( 192242284, 113265480 ),
998 VECTOR2I( 192255720, 113265480 ), VECTOR2I( 203682188, 124691948 ),
999 VECTOR2I( 203682188, 140332188 ), VECTOR2I( 206000000, 142650000 ) },
1000 false );
1001
1002
1003
1004 SHAPE* arc_sh = &arc;
1005 SHAPE* lc_sh = &lc;
1006
1007 BOOST_CHECK_EQUAL( arc_sh->Collide( &lc, 100000 ), true );
1008 BOOST_CHECK_EQUAL( lc_sh->Collide( &arc, 100000 ), true );
1009
1010 SEG seg( VECTOR2I( 203682188, 124691948 ), VECTOR2I( 203682188, 140332188 ) );
1011 BOOST_CHECK_EQUAL( arc.Collide( seg, 0 ), true );
1012}
1013
1014
1015BOOST_AUTO_TEST_CASE( CollideArcToPolygonApproximation )
1016{
1017 SHAPE_ARC arc( VECTOR2I( 73843527, 74355869 ), VECTOR2I( 71713528, 72965869 ),
1018 EDA_ANGLE( -76.36664803, DEGREES_T ), 1000000 );
1019
1020 // Create a polyset approximation from the arc - error outside (simulating the zone filler)
1021 SHAPE_POLY_SET arcBuffer;
1022 int clearance = ( arc.GetWidth() * 3 ) / 2;
1023 int polygonApproximationError = SHAPE_ARC::DefaultAccuracyForPCB();
1024
1025 TransformArcToPolygon( arcBuffer, arc.GetP0(), arc.GetArcMid(), arc.GetP1(),
1026 arc.GetWidth() + 2 * clearance,
1027 polygonApproximationError, ERROR_OUTSIDE );
1028
1029 BOOST_REQUIRE_EQUAL( arcBuffer.OutlineCount(), 1 );
1030 BOOST_CHECK_EQUAL( arcBuffer.HoleCount( 0 ), 0 );
1031
1032 // Make a reasonably large rectangular outline around the arc shape
1033 BOX2I arcbbox = arc.BBox( clearance * 4 );
1034
1035 SHAPE_LINE_CHAIN zoneOutline( { arcbbox.GetPosition(),
1036 arcbbox.GetPosition() + VECTOR2I( arcbbox.GetWidth(), 0 ),
1037 arcbbox.GetEnd(),
1038 arcbbox.GetEnd() - VECTOR2I( arcbbox.GetWidth(), 0 )
1039 },
1040 true );
1041
1042 // Create a synthetic "zone fill" polygon
1043 SHAPE_POLY_SET zoneFill;
1044 zoneFill.AddOutline( zoneOutline );
1045 zoneFill.AddHole( arcBuffer.Outline( 0 ) );
1046 zoneFill.CacheTriangulation();
1047
1048 int actual = 0;
1050 int epsilon = polygonApproximationError / 10;
1051
1052 BOOST_CHECK_EQUAL( zoneFill.Collide( &arc, clearance + epsilon, &actual, &location ), true );
1053
1054 BOOST_CHECK_EQUAL( zoneFill.Collide( &arc, clearance - epsilon, &actual, &location ), false );
1055}
1056
1057
1062
1063
1073bool ArePolylineEndPointsNearCircle( const SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aCentre,
1074 int aRad, int aTolerance )
1075{
1076 std::vector<VECTOR2I> points;
1077
1078 for( int i = 0; i < aPolyline.PointCount(); ++i )
1079 {
1080 points.push_back( aPolyline.CPoint( i ) );
1081 }
1082
1083 return GEOM_TEST::ArePointsNearCircle( points, aCentre, aRad, aTolerance );
1084}
1085
1086
1096bool ArePolylineMidPointsNearCircle( const SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aCentre,
1097 int aRad, int aTolerance )
1098{
1099 std::vector<VECTOR2I> points;
1100
1101 for( int i = 0; i < aPolyline.PointCount() - 1; ++i )
1102 {
1103 const VECTOR2I mid_pt = ( aPolyline.CPoint( i ) + aPolyline.CPoint( i + 1 ) ) / 2;
1104 points.push_back( mid_pt );
1105 }
1106
1107 return GEOM_TEST::ArePointsNearCircle( points, aCentre, aRad, aTolerance );
1108}
1109
1110
1111const std::vector<ARC_TO_POLYLINE_CASE> ArcToPolyline_cases{
1112 {
1113 "Zero rad",
1114 {
1115 { 0, 0 },
1116 { 0, 0 },
1117 180,
1118 },
1119 },
1120 {
1121 "Semicircle",
1122 {
1123 { 0, 0 },
1124 { -1000000, 0 },
1125 180,
1126 },
1127 },
1128 {
1129 // check that very small circles don't fall apart and that reverse angles
1130 // work too
1131 "Extremely small semicircle",
1132 {
1133 { 0, 0 },
1134 { -1000, 0 },
1135 -180,
1136 },
1137 },
1138 {
1139 // Make sure it doesn't only work for "easy" angles
1140 "Non-round geometry",
1141 {
1142 { 0, 0 },
1143 { 1234567, 0 },
1144 42.22,
1145 },
1146 },
1147};
1148
1149
1150BOOST_DATA_TEST_CASE( ArcToPolyline, boost::unit_test::data::make( ArcToPolyline_cases ), c )
1151{
1152 const int width = 0;
1153
1154 // Note: do not expect accuracies around 1 to work. We use integers internally so we're
1155 // liable to rounding errors. In PCBNew accuracy defaults to 5000 and we don't recommend
1156 // anything lower than 1000 (for performance reasons).
1157 const int accuracy = 100;
1158 const int epsilon = 1;
1159
1160 const SHAPE_ARC this_arc{ c.m_geom.m_center_point, c.m_geom.m_start_point,
1161 EDA_ANGLE( c.m_geom.m_center_angle, DEGREES_T ), width };
1162
1163 const SHAPE_LINE_CHAIN chain = this_arc.ConvertToPolyline( accuracy );
1164
1165 BOOST_TEST_MESSAGE( "Polyline has " << chain.PointCount() << " points" );
1166
1167 // Start point (exactly) where expected
1168 BOOST_CHECK_EQUAL( chain.CPoint( 0 ), c.m_geom.m_start_point );
1169
1170 // End point (exactly) where expected
1171 BOOST_CHECK_EQUAL( chain.CLastPoint(), this_arc.GetP1() );
1172
1173 int radius = ( c.m_geom.m_center_point - c.m_geom.m_start_point ).EuclideanNorm();
1174
1175 // Other points within accuracy + epsilon (for rounding) of where they should be
1177 ( chain )( c.m_geom.m_center_point )( radius )( accuracy + epsilon ) );
1178
1180 ( chain )( c.m_geom.m_center_point )( radius )( accuracy + epsilon ) );
1181}
1182
1183
1192BOOST_AUTO_TEST_CASE( TransformShallowArcToPolygon )
1193{
1194 SHAPE_POLY_SET buffer;
1195
1196 // Create an arc where the mid-point is only slightly off the start-end line.
1197 // This creates a very large radius arc that previously caused integer overflow.
1198 const VECTOR2I start( 0, 0 );
1199 const VECTOR2I end( 10000000, 0 ); // 10mm chord length
1200 const VECTOR2I mid( 5000000, 5 ); // Mid-point only 5nm off the line
1201
1202 const int width = 250000; // 0.25mm track width
1203 const int aError = 5000; // Default error tolerance
1204
1205 // This should not crash or produce invalid geometry
1206 TransformArcToPolygon( buffer, start, mid, end, width, aError, ERROR_INSIDE );
1207
1208 // Should produce at least one outline
1209 BOOST_CHECK( buffer.OutlineCount() >= 1 );
1210
1211 // The outline should be valid (closed, has points)
1212 if( buffer.OutlineCount() > 0 )
1213 {
1214 const SHAPE_LINE_CHAIN& outline = buffer.COutline( 0 );
1215 BOOST_CHECK( outline.IsClosed() );
1216 BOOST_CHECK( outline.PointCount() >= 3 );
1217
1218 // The bounding box should be reasonable (roughly the track width around the chord)
1219 BOX2I bbox = outline.BBox();
1220 BOOST_CHECK( bbox.GetWidth() <= end.x + width * 2 );
1221 BOOST_CHECK( bbox.GetHeight() <= width * 2 + 100 ); // Allow some tolerance
1222 }
1223}
1224
1225
1230BOOST_AUTO_TEST_CASE( TransformVeryShallowArcToPolygon )
1231{
1232 SHAPE_POLY_SET buffer;
1233
1234 // Create an arc that is effectively a straight line - mid-point essentially on the line.
1235 // This should be detected by IsEffectiveLine() and handled as a line segment.
1236 const VECTOR2I start( 0, 0 );
1237 const VECTOR2I end( 50000000, 0 ); // 50mm chord length
1238 const VECTOR2I mid( 25000000, 1 ); // Mid-point only 1nm off the line
1239
1240 const int width = 250000; // 0.25mm track width
1241 const int aError = 5000;
1242
1243 // This should not crash and should produce valid geometry
1244 TransformArcToPolygon( buffer, start, mid, end, width, aError, ERROR_INSIDE );
1245
1246 BOOST_CHECK( buffer.OutlineCount() >= 1 );
1247
1248 if( buffer.OutlineCount() > 0 )
1249 {
1250 const SHAPE_LINE_CHAIN& outline = buffer.COutline( 0 );
1251 BOOST_CHECK( outline.IsClosed() );
1252 BOOST_CHECK( outline.PointCount() >= 3 );
1253 }
1254}
1255
1256
1261BOOST_AUTO_TEST_CASE( TransformIssue22475ArcToPolygon )
1262{
1263 SHAPE_POLY_SET buffer;
1264
1265 // Values approximating one of the problematic arcs from issue #22475:
1266 // radius=24.35 mm, distToMid=12960 nm, chord=1.62 mm, angle=-3.8 deg
1267 // Compute start, mid, end points for such an arc
1268 const VECTOR2I start( 0, 0 );
1269 const VECTOR2I end( 1620000, 0 ); // 1.62mm chord length
1270 const VECTOR2I mid( 810000, 12960 ); // Mid-point 12960nm (12.96µm) off the line
1271
1272 const int width = 200000; // 0.2mm track width
1273 const int aError = 5000;
1274
1275 // Before the fix, this should create a polygon with very large extent
1276 // After the fix, it should create a reasonable oval-shaped polygon
1277 TransformArcToPolygon( buffer, start, mid, end, width, aError, ERROR_INSIDE );
1278
1279 BOOST_REQUIRE( buffer.OutlineCount() >= 1 );
1280
1281 const SHAPE_LINE_CHAIN& outline = buffer.COutline( 0 );
1282 BOOST_CHECK( outline.IsClosed() );
1283 BOOST_CHECK( outline.PointCount() >= 3 );
1284
1285 BOX2I bbox = outline.BBox();
1286
1287 // The bounding box should be reasonable - roughly chord + 2*width wide, 2*width high
1288 // With the fix (treating as oval), width should be ~1620000 + 2*200000 = 2020000
1289 // Height should be ~2*200000 = 400000
1290 // Without the fix, the height could be enormous due to the large arc radius
1291
1292 // Check that the polygon isn't ridiculously large
1293 BOOST_CHECK_MESSAGE( bbox.GetWidth() <= 3000000,
1294 wxString::Format( "Polygon width %lld is too large (expected ~2020000)", (long long)bbox.GetWidth() ) );
1295 BOOST_CHECK_MESSAGE( bbox.GetHeight() <= 1000000,
1296 wxString::Format( "Polygon height %lld is too large (expected ~400000)", (long long)bbox.GetHeight() ) );
1297}
1298
1299
1305BOOST_AUTO_TEST_CASE( CollideNearlyFlatArcDoesNotOverflow )
1306{
1307 // Values from the core dump: a nearly-flat arc with enormous radius
1308 const VECTOR2I start( 68208364, -8000 );
1309 const VECTOR2I mid( 771364, 500000 );
1310 const VECTOR2I end( 35224335, -7999 );
1311 const int width = 1270000;
1312
1313 SHAPE_ARC arc( start, mid, end, width );
1314
1315 // Radius should be near or above INT_MAX/2, triggering the segment fallback
1316 BOOST_CHECK( arc.GetRadius() >= (double) std::numeric_limits<int>::max() / 2.0 );
1317
1318 // Point near the arc endpoints. Must not crash.
1319 const VECTOR2I testPt( 35224298, -5381 );
1320 int actual = 0;
1322
1323 BOOST_CHECK_NO_THROW( arc.Collide( testPt, 635000, &actual, &location ) );
1324
1325 // Segment near the arc. Must not crash.
1326 const SEG testSeg( VECTOR2I( 35224298, -5381 ), VECTOR2I( 35696364, -32988651 ) );
1327
1328 BOOST_CHECK_NO_THROW( arc.Collide( testSeg, 635000, &actual, &location ) );
1329}
1330
1331
1343BOOST_AUTO_TEST_CASE( DegenerateArcCoincidentPoints )
1344{
1345 SHAPE_ARC arc( VECTOR2I( 135674000, 84576744 ),
1346 VECTOR2I( 135673999, 84576744 ),
1347 VECTOR2I( 135673998, 84576744 ),
1348 100000 );
1349
1350 BOOST_CHECK_LT( arc.GetRadius(), 10.0 );
1351
1353 BOOST_CHECK_LT( poly.BBox().GetWidth(), 1000 ); // < 1 µm
1354 BOOST_CHECK_LT( poly.BBox().GetHeight(), 1000 );
1355 BOOST_CHECK_LT( arc.GetLength(), 1000.0 );
1356}
1357
1358
1359// Collinear points from the reported board. The coincident start/mid sends CalcArcCenter() to
1360// the chord midpoint, which makes start and end antipodal and the raw sweep a clean half turn
1361BOOST_AUTO_TEST_CASE( CollinearArcSweepIsNotAFullTurn )
1362{
1363 const SHAPE_ARC arc( VECTOR2I( 2275000, 3123714 ),
1364 VECTOR2I( 2275000, 3123715 ),
1365 VECTOR2I( 2275000, 3123720 ),
1366 127000 );
1367
1368 BOOST_CHECK_LT( std::abs( arc.GetCentralAngle().AsDegrees() ), 1.0 );
1369
1370 // The 6 nm chord is the whole run; a fabricated sweep inflates this without bound
1371 BOOST_CHECK_CLOSE( arc.GetLength(), 6.0, 1.0 );
1372
1373 const SHAPE_LINE_CHAIN poly = arc.ConvertToPolyline();
1374 BOOST_CHECK_LT( poly.BBox().GetWidth(), 10 );
1375 BOOST_CHECK_LT( poly.BBox().GetHeight(), 10 );
1376}
1377
1378
1379// The guard must not catch major arcs, which are the only users of the full-turn correction
1380BOOST_AUTO_TEST_CASE( CurvedArcsKeepTheirSweep )
1381{
1382 const SHAPE_ARC quarter( VECTOR2I( 1000000, 0 ), VECTOR2I( 707107, 707107 ),
1383 VECTOR2I( 0, 1000000 ), 127000 );
1384
1385 BOOST_CHECK_CLOSE( quarter.GetCentralAngle().AsDegrees(), 90.0, 0.01 );
1386
1387 const SHAPE_ARC major( VECTOR2I( 1000000, 0 ), VECTOR2I( -1000000, 0 ),
1388 VECTOR2I( 0, -1000000 ), 127000 );
1389
1390 BOOST_CHECK_CLOSE( major.GetCentralAngle().AsDegrees(), 270.0, 0.01 );
1391}
1392
1393
1394// Coincident start/mid plus a distant end previously produced a center far off from the inputs
1395BOOST_AUTO_TEST_CASE( CalcArcCenterTwoCoincidentStartMid )
1396{
1397 const VECTOR2D start( 0.0, 0.0 );
1398 const VECTOR2D mid ( 0.0, 0.0 );
1399 const VECTOR2D end ( 1000.0, 0.0 );
1400
1401 VECTOR2D center = CalcArcCenter( start, mid, end );
1402
1403 BOOST_CHECK_CLOSE( center.x, 500.0, 1e-9 );
1404 BOOST_CHECK_SMALL( center.y, 1e-9 );
1405}
1406
1407
1408BOOST_AUTO_TEST_CASE( CalcArcCenterTwoCoincidentMidEnd )
1409{
1410 const VECTOR2D start( -1000.0, 0.0 );
1411 const VECTOR2D mid ( 0.0, 0.0 );
1412 const VECTOR2D end ( 0.0, 0.0 );
1413
1414 VECTOR2D center = CalcArcCenter( start, mid, end );
1415
1416 BOOST_CHECK_CLOSE( center.x, -500.0, 1e-9 );
1417 BOOST_CHECK_SMALL( center.y, 1e-9 );
1418}
1419
1420
1421BOOST_AUTO_TEST_CASE( CalcArcCenterTwoCoincidentStartEnd )
1422{
1423 // Coincident start/end with a distinct mid is a 360-degree arc, center is midpoint to mid
1424 const VECTOR2D start( 0.0, 0.0 );
1425 const VECTOR2D mid ( 1000.0, 0.0 );
1426 const VECTOR2D end ( 0.0, 0.0 );
1427
1428 VECTOR2D center = CalcArcCenter( start, mid, end );
1429
1430 BOOST_CHECK_CLOSE( center.x, 500.0, 1e-9 );
1431 BOOST_CHECK_SMALL( center.y, 1e-9 );
1432}
1433
1434
1435// Three near-coincident points collapse to the centroid via the bbox guard, not the pairwise guard
1436BOOST_AUTO_TEST_CASE( CalcArcCenterThreeNearCoincident )
1437{
1438 const VECTOR2D start( 100.0, 200.0 );
1439 const VECTOR2D mid ( 101.0, 200.0 );
1440 const VECTOR2D end ( 100.0, 201.0 );
1441
1442 VECTOR2D center = CalcArcCenter( start, mid, end );
1443
1444 BOOST_CHECK_CLOSE( center.x, ( start.x + mid.x + end.x ) / 3.0, 1e-9 );
1445 BOOST_CHECK_CLOSE( center.y, ( start.y + mid.y + end.y ) / 3.0, 1e-9 );
1446}
1447
1448
1449// A thin arc must not trip the coincident-point guards despite its short chord and small sagitta
1450BOOST_AUTO_TEST_CASE( CalcArcCenterThinArcNotDegenerate )
1451{
1452 const VECTOR2D start( 0.0, 0.0 );
1453 const VECTOR2D mid ( 10.0, 1.0 );
1454 const VECTOR2D end ( 20.0, 0.0 );
1455
1456 VECTOR2D center = CalcArcCenter( start, mid, end );
1457
1458 double rs = ( center - start ).EuclideanNorm();
1459 double rm = ( center - mid ).EuclideanNorm();
1460 double re = ( center - end ).EuclideanNorm();
1461
1462 BOOST_CHECK_CLOSE( rs, rm, 0.01 );
1463 BOOST_CHECK_CLOSE( rm, re, 0.01 );
1464
1465 // True circumradius is 50.5 IU; a wrongly-triggered midpoint guard would give ~10 IU
1466 BOOST_CHECK_GT( rs, 40.0 );
1467}
1468
1469
1470// A few-IU arc rounds its own mid point off the true circle, but the three points still span a
1471// healthy triangle. Reading that as a coincident pair collapses the centre onto the chord and
1472// turns a quarter turn into a reflex sweep
1473BOOST_AUTO_TEST_CASE( CalcArcCenterFewUnitArcKeepsItsCircumcircle )
1474{
1475 const SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 5, 0 ), ANGLE_90 );
1476
1477 BOOST_CHECK_LT( std::abs( arc.GetCentralAngle().AsDegrees() ), 180.0 );
1478
1479 // The chord midpoint fallback sits at (3, 3), inside the arc it is supposed to circumscribe
1480 BOOST_CHECK_GT( ( arc.GetCenter() - arc.GetArcMid() ).EuclideanNorm(), arc.GetRadius() / 2.0 );
1481}
1482
1483
1484// A board-scale arc must not regress from the new pairwise coincidence guards
1485BOOST_AUTO_TEST_CASE( CalcArcCenterBoardScaleSanity )
1486{
1487 const double R = 50000000.0;
1488 const VECTOR2D start( R, 0.0 );
1489 const VECTOR2D mid ( 0.0, R );
1490 const VECTOR2D end ( -R, 0.0 );
1491
1492 VECTOR2D center = CalcArcCenter( start, mid, end );
1493
1494 BOOST_CHECK_SMALL( center.x, 1.0 );
1495 BOOST_CHECK_SMALL( center.y, 1.0 );
1496}
1497
1498
@ ERROR_OUTSIDE
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr const Vec & GetPosition() const
Definition box2.h:208
constexpr const Vec GetEnd() const
Definition box2.h:209
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr size_type GetHeight() const
Definition box2.h:212
double AsDegrees() const
Definition eda_angle.h:116
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
EDA_ANGLE GetCentralAngle() const
Get the "central angle" of the arc - this is the angle at the point of the "pie slice".
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:116
SEG GetChord() const
Definition shape_arc.h:243
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
int GetWidth() const override
Definition shape_arc.h:211
EDA_ANGLE GetEndAngle() const
double GetLength() const
const SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError=DefaultAccuracyForPCB(), int *aActualError=nullptr) const
Construct a SHAPE_LINE_CHAIN of segments from a given arc.
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
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,...
static int DefaultAccuracyForPCB()
Definition shape_arc.h:279
double GetRadius() const
EDA_ANGLE GetStartAngle() const
SHAPE * Clone() const override
Return a dynamically allocated copy of the shape.
Definition shape_arc.h:84
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
bool IsSolid() const override
Definition shape_arc.h:216
const VECTOR2I & GetCenter() const
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool IsClosed() const override
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.
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.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
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.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
An abstract shape on 2D plane.
Definition shape.h:124
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:179
static const int MIN_PRECISION_IU
This is the minimum precision for all the points in a shape.
Definition shape.h:129
void TransformArcToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arc to multiple straight segments.
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:424
@ DEGREES_T
Definition eda_angle.h:31
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.
bool IsWithinWrapped(T aValue, T aNominal, T aWrap, T aError)
Check if a value is within a tolerance of a nominal value, wrapping to a given val.
Definition numeric.h:39
bool IsBoxWithinTol(const BOX &aBox, const BOX &aExp, typename BOX::coord_type aTol)
Check that a box is close enough to another box.
Definition geometry.h:61
bool IsWithin(T aValue, T aNominal, T aError)
Check if a value is within a tolerance of a nominal value.
Definition numeric.h:57
bool IsVecWithinTol(const VEC &aVec, const VEC &aExp, typename VEC::coord_type aTol)
Check that both x and y of a vector are within expected error.
Definition geometry.h:51
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
Numerical test predicates.
const double epsilon
@ SH_ARC
circular arc
Definition shape.h:50
Info to set up an arc by centre, start point and angle.
ARC_START_MID_END m_geom
ARC_CENTRE_PT_ANGLE m_geom
Geom of the arc.
ARC_PROPERTIES m_properties
Expected properties.
int m_width
Arc line width.
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_center_point
ARC_CENTRE_PT_ANGLE m_geom
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
ARC_START_MID_END m_geom
Geom of the arc.
int m_width
Arc line width.
ARC_PROPERTIES m_properties
Expected properties.
Info to set up an arc by start, mid and end points.
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.
A named data-driven test case.
BOOST_DATA_TEST_CASE(ConvertToKicadUnit, boost::unit_test::data::make(altium_to_kicad_unit), input_value, expected_result)
Test conversation from Altium internal units into KiCad internal units.
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST(netlist.find("R_G1 ARM_OUT1 DIE_B R='0.001 / ((SW_STATE)") !=std::string::npos)
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...
bool cw
SHAPE_ARC arc2(c.m_arc2.GenerateArc())
VECTOR2I center
bool result_chain_to_chain
const SHAPE_LINE_CHAIN chain
static const std::vector< ARC_PT_COLLIDE_CASE > arc_pt_collide_cases
int radius
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.
BOOST_CHECK_PREDICATE(ArePolylineEndPointsNearCircle,(chain)(c.m_geom.m_center_point)(radius)(accuracy+epsilon))
static const std::vector< ARC_SEC_CASE > arc_sec_cases
SHAPE_LINE_CHAIN arc1_slc(c.m_arc1.GenerateArc())
SHAPE * arc1_sh
BOOST_CHECK_EQUAL(this_arc.GetArcMid(), c.m_expected_mid)
static const std::vector< ARC_SME_CASE > arc_sme_cases
const std::vector< ARC_TO_POLYLINE_CASE > ArcToPolyline_cases
SHAPE * arc2_sh
VECTOR2I end
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)
BOOST_TEST_CONTEXT("Test Clearance")
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
int clearance
VECTOR2I location
SHAPE_ARC this_arc
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
static const std::vector< ARC_CICLE_COLLIDE_CASE > arc_circle_collide_cases
int actual
static const std::vector< ARC_CPA_CASE > arc_cases
bool result_arc_to_chain
static const std::vector< ARC_TTR_CASE > arc_ttr_cases
SHAPE * arc2_slc_sh
const int accuracy
SHAPE_LINE_CHAIN arc2_slc(c.m_arc2.GenerateArc())
static const std::vector< ARC_ARC_COLLIDE_CASE > arc_arc_collide_cases
bool result_arc_to_arc
SHAPE * arc1_slc_sh
bool result_chain_to_arc
const VECTOR2I CalcArcCenter(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Determine the center of an arc or circle given three points on its circumference.
Definition trigo.cpp:562
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682