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