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 
35 BOOST_AUTO_TEST_SUITE( ShapeArc )
36 
37 
42 {
47  double m_start_angle;
48  double m_end_angle;
49  int m_radius;
51 };
52 
59 static 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  BOOST_CHECK_PREDICATE(
71  KI_TEST::IsVecWithinTol<VECTOR2I>, ( aArc.GetP1() )( aProps.m_end_point )( pos_tol ) );
72  BOOST_CHECK_PREDICATE( KI_TEST::IsVecWithinTol<VECTOR2I>,
73  ( aArc.GetCenter() )( aProps.m_center_point )( aSynErrIU ) );
74  BOOST_CHECK_PREDICATE( KI_TEST::IsWithinWrapped<double>,
75  ( aArc.GetCentralAngle() )( aProps.m_center_angle )( 360.0 )( angle_tol_deg ) );
76  BOOST_CHECK_PREDICATE( KI_TEST::IsWithinWrapped<double>,
77  ( aArc.GetStartAngle() )( aProps.m_start_angle )( 360.0 )( angle_tol_deg ) );
78  BOOST_CHECK_PREDICATE( KI_TEST::IsWithinWrapped<double>,
79  ( aArc.GetEndAngle() )( aProps.m_end_angle )( 360.0 )( angle_tol_deg ) );
80  BOOST_CHECK_PREDICATE(
81  KI_TEST::IsWithin<double>, ( aArc.GetRadius() )( aProps.m_radius )( aSynErrIU ) );
82 
84  const auto chord = aArc.GetChord();
85 
86  BOOST_CHECK_PREDICATE(
87  KI_TEST::IsVecWithinTol<VECTOR2I>, ( chord.A )( aProps.m_start_point )( pos_tol ) );
88  BOOST_CHECK_PREDICATE(
89  KI_TEST::IsVecWithinTol<VECTOR2I>, ( chord.B )( aProps.m_end_point )( pos_tol ) );
90 
92  BOOST_CHECK_EQUAL( aArc.IsSolid(), true );
93 
94  BOOST_CHECK_PREDICATE(
95  KI_TEST::IsBoxWithinTol<BOX2I>, ( aArc.BBox() )( aProps.m_bbox )( pos_tol ) );
96 
98 }
99 
100 
107 static void CheckArc( const SHAPE_ARC& aArc, const ARC_PROPERTIES& aProps, const int aSynErrIU = 1 )
108 {
109  // Check the original arc
110  CheckArcGeom( aArc, aProps, aSynErrIU );
111 
112  // Test the Clone function (also tests copy-ctor)
113  std::unique_ptr<SHAPE> new_shape{ aArc.Clone() };
114 
115  BOOST_CHECK_EQUAL( new_shape->Type(), SH_ARC );
116 
117  SHAPE_ARC* new_arc = dynamic_cast<SHAPE_ARC*>( new_shape.get() );
118 
119  BOOST_REQUIRE( new_arc != nullptr );
120 
122  CheckArcGeom( *new_arc, aProps, aSynErrIU );
123 }
124 
129 {
130  auto arc = SHAPE_ARC();
131 
132  BOOST_CHECK_EQUAL( arc.GetWidth(), 0 );
133 
134  static ARC_PROPERTIES null_props{
135  { 0, 0 },
136  { 0, 0 },
137  { 0, 0 },
138  0,
139  0,
140  0,
141  0,
142  };
143 
144  CheckArc( arc, null_props );
145 }
146 
147 
154 {
158 };
159 
160 
162 {
164  std::string m_ctx_name;
165 
168 
170  int m_width;
171 
174 };
175 
176 
177 static const std::vector<ARC_CPA_CASE> arc_cases = {
178  {
179  "C(0,0) 114 + 360 degree",
180  {
181  { 0, 0 },
182  { -306451, 687368 },
183  360,
184  },
185  0,
186  {
187  { 0, 0 },
188  { -306451, 687368 },
189  { -306451, 687368 },
190  360,
191  113.95929,
192  113.95929,
193  752587,
194  { { -752587, -752587 }, { 1505174, 1505174 } },
195  },
196  },
197  {
198  "C(0,0) 180 + 360 degree",
199  {
200  { 0, 0 },
201  { -100, 0 },
202  360,
203  },
204  0,
205  {
206  { 0, 0 },
207  { -100, 0 },
208  { -100, 0 },
209  360,
210  180,
211  180,
212  100,
213  { { -100, -100 }, { 200, 200 } },
214  },
215  },
216  {
217  "C(0,0) 180 + 90 degree",
218  {
219  { 0, 0 },
220  { -100, 0 },
221  90,
222  },
223  0,
224  {
225  { 0, 0 },
226  { -100, 0 },
227  { 0, -100 },
228  90,
229  180,
230  270,
231  100,
232  { { -100, -100 }, { 100, 100 } },
233  },
234  },
235  {
236  "C(100,200) 0 - 30 degree",
237  {
238  { 100, 200 },
239  { 300, 200 },
240  -30,
241  },
242  0,
243  {
244  { 100, 200 },
245  { 300, 200 },
246  { 273, 100 }, // 200 * sin(30) = 100, 200* cos(30) = 173
247  -30,
248  0,
249  330,
250  200,
251  { { 273, 100 }, { 27, 100 } },
252  },
253  },
254  {
255  // This is a "fan shape" which includes the top quadrant point,
256  // so it exercises the bounding box code (centre and end points
257  // do not contain the top quadrant)
258  "C(0,0) 30 + 120 degree",
259  {
260  { 0, 0 },
261  { 17320, 10000 },
262  120,
263  },
264  0,
265  {
266  { 0, 0 },
267  { 17320, 10000 },
268  { -17320, 10000 }, // 200 * sin(30) = 100, 200* cos(30) = 173
269  120,
270  30,
271  150,
272  20000,
273  // bbox defined by: centre, top quadrant point, two endpoints
274  { { -17320, 10000 }, { 17320 * 2, 10000 } },
275  },
276  },
277  {
278  // An arc that covers three quadrant points (L/R, bottom)
279  "C(0,0) 150 + 240 degree",
280  {
281  { 0, 0 },
282  { -17320, 10000 },
283  240,
284  },
285  0,
286  {
287  { 0, 0 },
288  { -17320, 10000 },
289  { 17320, 10000 },
290  240,
291  150,
292  30,
293  20000,
294  // bbox defined by: L/R quads, bottom quad and start/end
295  { { -20000, -20000 }, { 40000, 30000 } },
296  },
297  },
298  {
299  // Same as above but reverse direction
300  "C(0,0) 30 - 300 degree",
301  {
302  { 0, 0 },
303  { 17320, 10000 },
304  -240,
305  },
306  0,
307  {
308  { 0, 0 },
309  { 17320, 10000 },
310  { -17320, 10000 },
311  -240,
312  30,
313  150,
314  20000,
315  // bbox defined by: L/R quads, bottom quad and start/end
316  { { -20000, -20000 }, { 40000, 30000 } },
317  },
318  },
319 };
320 
321 
322 BOOST_AUTO_TEST_CASE( BasicCPAGeom )
323 {
324  for( const auto& c : arc_cases )
325  {
326  BOOST_TEST_CONTEXT( c.m_ctx_name )
327  {
328 
329  const auto this_arc = SHAPE_ARC{ c.m_geom.m_center_point, c.m_geom.m_start_point,
330  c.m_geom.m_center_angle, c.m_width };
331 
332  CheckArc( this_arc, c.m_properties );
333  }
334  }
335 }
336 
337 
338 
343 {
346  int m_radius;
347 };
348 
349 
351 {
353  std::string m_ctx_name;
354 
357 
359  int m_width;
360 
363 };
364 
365 
366 static const std::vector<ARC_TTR_CASE> arc_ttr_cases = {
367  {
368  "90 degree segments intersecting",
369  {
370  { 0, 0, 0, 1000 },
371  { 0, 0, 1000, 0 },
372  1000,
373  },
374  0,
375  {
376  { 1000, 1000 },
377  { 0, 1000 }, //start on first segment
378  { 1000, 0 }, //end on second segment
379  90, //positive angle due to start/end
380  180,
381  270,
382  1000,
383  { { 0, 0 }, { 1000, 1000 } },
384  }
385  },
386  {
387  "45 degree segments intersecting",
388  {
389  { 0, 0, 0, 1000 },
390  { 0, 0, 1000, 1000 },
391  1000,
392  },
393  0,
394  {
395  { 1000, 2414 },
396  { 0, 2414 }, //start on first segment
397  { 1707, 1707 }, //end on second segment
398  135, //positive angle due to start/end
399  180,
400  225,
401  1000,
402  { { 0, 1414 }, { 1707, 1000 } },
403  }
404  },
405  {
406  "135 degree segments intersecting",
407  {
408  { 0, 0, 0, 1000 },
409  { 0, 0, 1000, -1000 },
410  1000,
411  },
412  0,
413  {
414  { 1000, 414 },
415  { 0, 414 }, //start on first segment ( radius * tan(45 /2) )
416  { 293, -293 }, //end on second segment (radius * 1-cos(45)) )
417  45, //positive angle due to start/end
418  180,
419  225,
420  1000,
421  { { 0, -293 }, { 293, 707 } },
422  }
423  }
424 
425 
426 };
427 
428 
429 BOOST_AUTO_TEST_CASE( BasicTTRGeom )
430 {
431  for( const auto& c : arc_ttr_cases )
432  {
433  BOOST_TEST_CONTEXT( c.m_ctx_name )
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
476  CheckArc( this_arc, props, SHAPE_ARC::MIN_PRECISION_IU );
477  }
478  }
479  }
480 }
481 
482 
487 {
491 };
492 
493 
495 {
497  std::string m_ctx_name;
498 
501 
504 
507 };
508 
509 
510 
511 static const std::vector<ARC_SEC_CASE> arc_sec_cases = {
512  { "180 deg, clockwise", { { 100, 0 }, { 0, 0 }, { 50, 0 } }, true, { 50, -50 } },
513  { "180 deg, anticlockwise", { { 100, 0 }, { 0, 0 }, { 50, 0 } }, false, { 50, 50 } },
514  { "180 deg flipped, clockwise", { { 0, 0 }, { 100, 0 }, { 50, 0 } }, true, { 50, 50 } },
515  { "180 deg flipped, anticlockwise", { { 0, 0 }, { 100, 0 }, { 50, 0 } }, false, { 50, -50 } },
516  { "90 deg, clockwise", { { -100, 0 }, { 0, 100 }, { 0, 0 } }, true, { -71, 71 } },
517  { "90 deg, anticlockwise", { { -100, 0 }, { 0, 100 }, { 0, 0 } }, false, { 71, -71 } },
518 };
519 
520 
521 BOOST_AUTO_TEST_CASE( BasicSECGeom )
522 {
523  for( const auto& c : arc_sec_cases )
524  {
525  BOOST_TEST_CONTEXT( c.m_ctx_name )
526  {
527  VECTOR2I start = c.m_geom.m_start;
528  VECTOR2I end = c.m_geom.m_end;
529  VECTOR2I center = c.m_geom.m_center;
530  bool cw = c.m_clockwise;
531 
532  SHAPE_ARC this_arc;
533  this_arc.ConstructFromStartEndCenter( start, end, center, cw );
534 
535  BOOST_CHECK_EQUAL( this_arc.GetArcMid(), c.m_expected_mid );
536  }
537  }
538 }
539 
540 
542 {
543  std::string m_ctx_name;
549 };
550 
551 
552 static const std::vector<ARC_PT_COLLIDE_CASE> arc_pt_collide_cases = {
553  { " 270deg, 0 cl, 0 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 100, 0 }, true, 0 },
554  { " 270deg, 0 cl, 90 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 0, 100 }, true, 0 },
555  { " 270deg, 0 cl, 180 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { -100, 0 }, true, 0 },
556  { " 270deg, 0 cl, 270 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 0, -100 }, true, 0 },
557  { " 270deg, 0 cl, 45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 71, 71 }, true, 0 },
558  { " 270deg, 0 cl, -45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 71, -71 }, false, -1 },
559  { "-270deg, 0 cl, 0 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 100, 0 }, true, 0 },
560  { "-270deg, 0 cl, 90 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 0, 100 }, true, 0 },
561  { "-270deg, 0 cl, 180 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { -100, 0 }, true, 0 },
562  { "-270deg, 0 cl, 270 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 0, -100 }, true, 0 },
563  { "-270deg, 0 cl, 45 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 71, 71 }, false, -1 },
564  { "-270deg, 0 cl, -45 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 71, -71 }, true, 0 },
565  { " 270deg, 5 cl, 0 deg, 5 pos X", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 105, 0 }, true, 5 },
566  { " 270deg, 5 cl, 0 deg, 5 pos Y", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 100, -5 }, true, 5 },
567  { " 270deg, 5 cl, 90 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, 105 }, true, 5 },
568  { " 270deg, 5 cl, 180 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { -105, 0 }, true, 5 },
569  { " 270deg, 5 cl, 270 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, -105 }, true, 5 },
570  { " 270deg, 5 cl, 0 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 105, 0 }, true, 5 },
571  { " 270deg, 5 cl, 90 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, 105 }, true, 5 },
572  { " 270deg, 5 cl, 180 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { -105, 0 }, true, 5 },
573  { " 270deg, 5 cl, 270 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, -105 }, true, 5 },
574  { " 270deg, 5 cl, 45 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 75, 74 }, true, 5 },
575  { " 270deg, 5 cl, -45 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 75, -74 }, false, -1 },
576  { " 270deg, 5 cl, 45 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 67, 68 }, true, 5 },
577  { " 270deg, 5 cl, -45 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 67, -68 }, false, -1 },
578  { " 270deg, 4 cl, 0 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 105, 0 }, false, -1 },
579  { " 270deg, 4 cl, 90 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 0, 105 }, false, -1 },
580  { " 270deg, 4 cl, 180 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { -105, 0 }, false, -1 },
581  { " 270deg, 4 cl, 270 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 0, -105 }, false, -1 },
582  { " 90deg, 0 cl, 0 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 71, -71 }, true, 0 },
583  { " 90deg, 0 cl, 45 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 100, 0 }, true, 0 },
584  { " 90deg, 0 cl, 90 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 71, 71 }, true, 0 },
585  { " 90deg, 0 cl, 135 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 0, -100 }, false, -1 },
586  { " 90deg, 0 cl, -45 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 0, 100 }, false, -1 },
587  { " -90deg, 0 cl, 0 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 71, -71 }, true, 0 },
588  { " -90deg, 0 cl, 45 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 100, 0 }, true, 0 },
589  { " -90deg, 0 cl, 90 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 71, 71 }, true, 0 },
590  { " -90deg, 0 cl, 135 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 0, -100 }, false, -1 },
591  { " -90deg, 0 cl, -45 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 0, 100 }, false, -1 },
592 };
593 
594 
596 {
597  for( const auto& c : arc_pt_collide_cases )
598  {
599  BOOST_TEST_CONTEXT( c.m_ctx_name )
600  {
601  SHAPE_ARC arc( c.m_geom.m_center_point, c.m_geom.m_start_point,
602  c.m_geom.m_center_angle );
603 
604  // Test a zero width arc (distance should equal the clearance)
605  BOOST_TEST_CONTEXT( "Test Clearance" )
606  {
607  int dist = -1;
608  BOOST_CHECK_EQUAL( arc.Collide( c.m_point, c.m_arc_clearance, &dist ),
609  c.m_exp_result );
610  BOOST_CHECK_EQUAL( dist, c.m_exp_distance );
611  }
612 
613  // Test by changing the width of the arc (distance should equal zero)
614  BOOST_TEST_CONTEXT( "Test Width" )
615  {
616  int dist = -1;
617  arc.SetWidth( c.m_arc_clearance * 2 );
618  BOOST_CHECK_EQUAL( arc.Collide( c.m_point, 0, &dist ), c.m_exp_result );
619 
620  if( c.m_exp_result )
621  BOOST_CHECK_EQUAL( dist, 0 );
622  else
623  BOOST_CHECK_EQUAL( dist, -1 );
624  }
625  }
626  }
627 }
628 
629 
631 {
632  // Coordinates and dimensions in millimeters
633  double m_center_x;
634  double m_center_y;
635  double m_start_x;
636  double m_start_y;
638  double m_width;
639 
641  {
645 
646  return arc;
647  }
648 };
649 
650 
652 {
653  std::string m_ctx_name;
656  double m_clearance;
658 };
659 
660 
661 static const std::vector<ARC_ARC_COLLIDE_CASE> arc_arc_collide_cases = {
662  { "case 1: No intersection",
663  { 73.843527, 74.355869, 71.713528, 72.965869, -76.36664803, 0.2 },
664  { 71.236473, 74.704131, 73.366472, 76.094131, -76.36664803, 0.2 },
665  0,
666  false },
667  { "case 2: No intersection",
668  { 82.542335, 74.825975, 80.413528, 73.435869, -76.4, 0.2 },
669  { 76.491192, 73.839894, 78.619999, 75.23, -76.4, 0.2 },
670  0,
671  false },
672  { "case 3: No intersection",
673  { 89.318807, 74.810106, 87.19, 73.42, -76.4, 0.2 },
674  { 87.045667, 74.632941, 88.826472, 75.794131, -267.9, 0.2 },
675  0,
676  false },
677  { "case 4: Co-centered not intersecting",
678  { 94.665667, 73.772941, 96.446472, 74.934131, -267.9, 0.2 },
679  { 94.665667, 73.772941, 93.6551, 73.025482, -255.5, 0.2 },
680  0,
681  false },
682  { "case 5: Not intersecting, but end points very close",
683  { 72.915251, 80.493054, 73.570159, 81.257692, -260.5, 0.2 },
684  { 73.063537, 82.295989, 71.968628, 81.581351, -255.5, 0.2 },
685  0,
686  false },
687  { "case 6: Coincident centers, colliding due to arc thickness",
688  { 79.279991, 80.67988, 80.3749, 81.394518, -255.5, 0.2 },
689  { 79.279991, 80.67988, 80.3749, 81.694518, -255.5, 0.2 },
690  0,
691  true },
692  { "case 7: Single intersection",
693  { 88.495265, 81.766089, 90.090174, 82.867869, -255.5, 0.2 },
694  { 86.995265, 81.387966, 89.090174, 82.876887, -255.5, 0.2 },
695  0,
696  true },
697  { "case 8: Double intersection",
698  { 96.149734, 81.792126, 94.99, 83.37, -347.2, 0.2 },
699  { 94.857156, 81.240589, 95.91, 83.9, -288.5, 0.2 },
700  0,
701  true },
702  { "case 9: Endpoints within arc width",
703  { 72.915251, 86.493054, 73.970159, 87.257692, -260.5, 0.2 },
704  { 73.063537, 88.295989, 71.968628, 87.581351, -255.5, 0.2 },
705  0,
706  true },
707  { "case 10: Endpoints close, outside, no collision",
708  { 78.915251, 86.393054, 79.970159, 87.157692, 99.5, 0.2 },
709  { 79.063537, 88.295989, 77.968628, 87.581351, -255.5, 0.2 },
710  0,
711  false },
712  { "case 11: Endpoints close, inside, collision due to arc width",
713  { 85.915251, 86.993054, 86.970159, 87.757692, 99.5, 0.2 },
714  { 86.063537, 88.295989, 84.968628, 87.581351, -255.5, 0.2 },
715  0,
716  true },
717  { "case 12: Simulated differential pair meander",
718  { 94.6551, 88.295989, 95.6551, 88.295989, 90.0, 0.1 },
719  { 94.6551, 88.295989, 95.8551, 88.295989, 90.0, 0.1 },
720  // Offset needed due to rounding errors of integer coordinates
722  false },
723  { "case 13: One arc fully enclosed in other, non-concentric",
724  { 73.77532, 93.413654, 75.70532, 93.883054, 60.0, 0.1 },
725  { 73.86532, 93.393054, 75.86532, 93.393054, 90.0, 0.3 },
726  0,
727  true },
728  { "case 14: One arc fully enclosed in other, concentric",
729  { 79.87532, 93.413654, 81.64532, 94.113054, 60.0, 0.1 },
730  { 79.87532, 93.413654, 81.86532, 93.393054, 90.0, 0.3 },
731  0,
732  true },
733 };
734 
735 
736 BOOST_AUTO_TEST_CASE( CollideArc )
737 {
738  for( const auto& c : arc_arc_collide_cases )
739  {
740  BOOST_TEST_CONTEXT( c.m_ctx_name )
741  {
742  SHAPE_ARC arc1( c.m_arc1.GenerateArc() );
743  SHAPE_ARC arc2( c.m_arc2.GenerateArc() );
744 
745  int actual = 0;
746  VECTOR2I location;
747 
748  bool result = arc1.Collide( &arc2, PcbMm2iu( c.m_clearance ), &actual, &location );
749 
750  BOOST_CHECK_EQUAL( result, c.m_exp_result );
751  }
752  }
753 }
754 
755 
756 BOOST_AUTO_TEST_CASE( CollideArcToPolygonApproximation )
757 {
758  SHAPE_ARC arc( VECTOR2I( 73843527, 74355869 ), VECTOR2I( 71713528, 72965869 ), -76.36664803,
759  2000000 );
760 
761  // Create a polyset approximation from the arc - error outside (simulating the zone filler)
762  SHAPE_POLY_SET arcBuffer;
763  int clearance = ( arc.GetWidth() * 3 ) / 2;
764  int polygonApproximationError = SHAPE_ARC::DefaultAccuracyForPCB();
765 
766  TransformArcToPolygon( arcBuffer, wxPoint( arc.GetP0() ), wxPoint( arc.GetArcMid() ),
767  wxPoint( arc.GetP1() ), arc.GetWidth() + 2 * clearance,
768  polygonApproximationError, ERROR_OUTSIDE );
769 
770  BOOST_REQUIRE_EQUAL( arcBuffer.OutlineCount(), 1 );
771  BOOST_CHECK_EQUAL( arcBuffer.HoleCount( 0 ), 0 );
772 
773  // Make a reasonably large rectangular outline around the arc shape
774  BOX2I arcbbox = arc.BBox( clearance * 4 );
775 
776  SHAPE_LINE_CHAIN zoneOutline( { arcbbox.GetPosition(),
777  arcbbox.GetPosition() + VECTOR2I( arcbbox.GetWidth(), 0 ),
778  arcbbox.GetEnd(),
779  arcbbox.GetEnd() - VECTOR2I( arcbbox.GetWidth(), 0 )
780  },
781  true );
782 
783  // Create a synthetic "zone fill" polygon
784  SHAPE_POLY_SET zoneFill;
785  zoneFill.AddOutline( zoneOutline );
786  zoneFill.AddHole( arcBuffer.Outline( 0 ) );
787 
788  int actual = 0;
789  VECTOR2I location;
790 
791  int tol = SHAPE_ARC::MIN_PRECISION_IU;
792 
793  BOOST_CHECK_EQUAL( zoneFill.Collide( &arc, clearance - tol, &actual, &location ), false );
794 
795  BOOST_CHECK_EQUAL( zoneFill.Collide( &arc, clearance * 2, &actual, &location ), true );
796 
797  BOOST_CHECK( KI_TEST::IsWithin( actual, clearance, polygonApproximationError ) );
798 }
799 
800 
802 {
803  std::string m_ctx_name;
805 };
806 
807 
817 bool ArePolylineEndPointsNearCircle( const SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aCentre,
818  int aRad, int aTolerance )
819 {
820  std::vector<VECTOR2I> points;
821 
822  for( int i = 0; i < aPolyline.PointCount(); ++i )
823  {
824  points.push_back( aPolyline.CPoint( i ) );
825  }
826 
827  return GEOM_TEST::ArePointsNearCircle( points, aCentre, aRad, aTolerance );
828 }
829 
830 
840 bool ArePolylineMidPointsNearCircle( const SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aCentre,
841  int aRad, int aTolerance )
842 {
843  std::vector<VECTOR2I> points;
844 
845  for( int i = 0; i < aPolyline.PointCount() - 1; ++i )
846  {
847  const VECTOR2I mid_pt = ( aPolyline.CPoint( i ) + aPolyline.CPoint( i + 1 ) ) / 2;
848  points.push_back( mid_pt );
849  }
850 
851  return GEOM_TEST::ArePointsNearCircle( points, aCentre, aRad, aTolerance );
852 }
853 
854 
855 BOOST_AUTO_TEST_CASE( ArcToPolyline )
856 {
857  const std::vector<ARC_TO_POLYLINE_CASE> cases = {
858  {
859  "Zero rad",
860  {
861  { 0, 0 },
862  { 0, 0 },
863  180,
864  },
865  },
866  {
867  "Semicircle",
868  {
869  { 0, 0 },
870  { -1000000, 0 },
871  180,
872  },
873  },
874  {
875  // check that very small circles don't fall apart and that reverse angles
876  // work too
877  "Extremely small semicircle",
878  {
879  { 0, 0 },
880  { -1000, 0 },
881  -180,
882  },
883  },
884  {
885  // Make sure it doesn't only work for "easy" angles
886  "Non-round geometry",
887  {
888  { 0, 0 },
889  { 1234567, 0 },
890  42.22,
891  },
892  },
893  };
894 
895  const int width = 0;
896 
897  // Note: do not expect accuracies around 1 to work. We use integers internally so we're
898  // liable to rounding errors. In PCBNew accuracy defaults to 5000 and we don't recommend
899  // anything lower than 1000 (for performance reasons).
900  const int accuracy = 100;
901  const int epsilon = 1;
902 
903  for( const auto& c : cases )
904  {
905  BOOST_TEST_CONTEXT( c.m_ctx_name )
906  {
907  const SHAPE_ARC this_arc{ c.m_geom.m_center_point, c.m_geom.m_start_point,
908  c.m_geom.m_center_angle, width };
909 
910  const SHAPE_LINE_CHAIN chain = this_arc.ConvertToPolyline( accuracy );
911 
912  BOOST_TEST_MESSAGE( "Polyline has " << chain.PointCount() << " points" );
913 
914  // Start point (exactly) where expected
915  BOOST_CHECK_EQUAL( chain.CPoint( 0 ), c.m_geom.m_start_point );
916 
917  // End point (exactly) where expected
918  BOOST_CHECK_EQUAL( chain.CPoint( -1 ), this_arc.GetP1() );
919 
920  int radius = ( c.m_geom.m_center_point - c.m_geom.m_start_point ).EuclideanNorm();
921 
922  // Other points within accuracy + epsilon (for rounding) of where they should be
923  BOOST_CHECK_PREDICATE( ArePolylineEndPointsNearCircle,
924  ( chain )( c.m_geom.m_center_point )( radius )( accuracy + epsilon ) );
925 
926  BOOST_CHECK_PREDICATE( ArePolylineMidPointsNearCircle,
927  ( chain )( c.m_geom.m_center_point )( radius )( accuracy + epsilon ) );
928  }
929  }
930 }
931 
932 
933 BOOST_AUTO_TEST_SUITE_END()
double EuclideanNorm(const wxPoint &vector)
Euclidean norm of a 2D vector.
Definition: trigo.h:146
Info to set up an arc by tangent to two segments and a radius.
ARC_CENTRE_PT_ANGLE m_geom
bool IsWithin(T aValue, T aNominal, T aError)
Check if a value is within a tolerance of a nominal value.
Definition: numeric.h:57
int OutlineCount() const
Return the number of vertices in a given outline/hole.
ARC_PROPERTIES m_properties
Expected properties.
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
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:229
ARC_TAN_TAN_RADIUS m_geom
Geom of the arc.
double GetRadius() const
Definition: shape_arc.cpp:492
static void CheckArc(const SHAPE_ARC &aArc, const ARC_PROPERTIES &aProps, const int aSynErrIU=1)
Check an arcs geometry and other class functions.
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...
constexpr double PcbIu2mm(int iu)
double GetStartAngle() const
Definition: shape_arc.cpp:444
static double DefaultAccuracyForPCB()
Definition: shape_arc.h:220
VECTOR2< int > VECTOR2I
Definition: vector2d.h:623
int PointCount() const
Return the number of points (vertices) in this line chain.
std::string m_ctx_name
The text context name.
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.
BOOST_CHECK(v2.Cross(v1)==1)
bool m_clockwise
clockwise or anti-clockwise?
double m_center_angle
std::string m_ctx_name
The text context name.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
VECTOR2I m_end_point
int m_width
Arc line width.
VECTOR2< double > VECTOR2D
Definition: vector2d.h:622
ARC_PROPERTIES m_properties
Expected properties.
Represent a set of closed polygons.
Info to set up an arc start, end and center.
SHAPE_LINE_CHAIN & Outline(int aIndex)
const VECTOR2I & GetP0() const
Definition: shape_arc.h:111
static const int MIN_PRECISION_IU
This is the minimum precision for all the points in a shape.
Definition: shape.h:122
VECTOR2I m_center_point
std::string m_ctx_name
The text context name.
const VECTOR2I & GetArcMid() const
Definition: shape_arc.h:113
static const std::vector< ARC_PT_COLLIDE_CASE > arc_pt_collide_cases
SEG GetChord() const
Definition: shape_arc.h:186
circular arc
Definition: shape.h:50
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.
SHAPE_ARC GenerateArc() const
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.
#define BOOST_TEST_CONTEXT(A)
int HoleCount(int aOutline) const
Return the reference to aIndex-th outline in the set.
VECTOR2I m_expected_mid
Expected mid-point of the arc.
SHAPE * Clone() const override
Return a dynamically allocated copy of the shape.
Definition: shape_arc.h:81
double GetEndAngle() const
Definition: shape_arc.cpp:454
static const std::vector< ARC_TTR_CASE > arc_ttr_cases
BOOST_AUTO_TEST_CASE(NullCtor)
Check correct handling of filter strings (as used by WX)
Definition: seg.h:40
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new hole to the given outline (default: last) and returns its index.
int GetWidth() const
Definition: shape_arc.h:156
bool IsSolid() const override
Definition: shape_arc.h:161
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:201
Represent a polyline (an zero-thickness chain of connected line segments).
VECTOR2I A
Definition: seg.h:48
double GetCentralAngle() const
Definition: shape_arc.cpp:479
ARC_CENTRE_PT_ANGLE m_geom
Geom of the arc.
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:380
static const std::vector< ARC_SEC_CASE > arc_sec_cases
void TransformArcToPolygon(SHAPE_POLY_SET &aCornerBuffer, const wxPoint &aStart, const wxPoint &aMid, const wxPoint &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arc to multiple straight segments.
Numerical test predicates.
static const std::vector< ARC_ARC_COLLIDE_CASE > arc_arc_collide_cases
void SetWidth(int aWidth)
Definition: shape_arc.h:151
Info to set up an arc by centre, start point and angle.
ARC_CENTRE_PT_ANGLE m_geom
ARC_START_END_CENTER m_geom
Geom of the arc.
int m_width
Definition: shape_arc.h:262
int m_width
Arc line width.
static const std::vector< ARC_CPA_CASE > arc_cases
const VECTOR2I & GetP1() const
Definition: shape_arc.h:112
VECTOR2I GetCenter() const
Definition: shape_arc.cpp:464
constexpr int PcbMm2iu(double mm)
VECTOR2I B
Definition: seg.h:49