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 CHANGELOG.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 
24 #include <geometry/shape_arc.h>
25 
27 
31 
32 #include "geom_test_utils.h"
33 
34 BOOST_AUTO_TEST_SUITE( ShapeArc )
35 
36 
41 {
46  double m_start_angle;
47  double m_end_angle;
48  int m_radius;
50 };
51 
58 static void CheckArcGeom( const SHAPE_ARC& aArc, const ARC_PROPERTIES& aProps, const int aSynErrIU = 1 )
59 {
60  // Angular error - note this can get quite large for very small arcs,
61  // as the integral position rounding has a relatively greater effect
62  const double angle_tol_deg = 1.0;
63 
64  // Position error - rounding to nearest integer
65  const int pos_tol = 1;
66 
67  BOOST_CHECK_PREDICATE( KI_TEST::IsVecWithinTol<VECTOR2I>,
68  ( aProps.m_start_point )( aProps.m_start_point )( pos_tol ) );
69  BOOST_CHECK_PREDICATE(
70  KI_TEST::IsVecWithinTol<VECTOR2I>, ( aArc.GetP1() )( aProps.m_end_point )( pos_tol ) );
71  BOOST_CHECK_PREDICATE( KI_TEST::IsVecWithinTol<VECTOR2I>,
72  ( aArc.GetCenter() )( aProps.m_center_point )( aSynErrIU ) );
73  BOOST_CHECK_PREDICATE( KI_TEST::IsWithinWrapped<double>,
74  ( aArc.GetCentralAngle() )( aProps.m_center_angle )( 360.0 )( angle_tol_deg ) );
75  BOOST_CHECK_PREDICATE( KI_TEST::IsWithinWrapped<double>,
76  ( aArc.GetStartAngle() )( aProps.m_start_angle )( 360.0 )( angle_tol_deg ) );
77  BOOST_CHECK_PREDICATE( KI_TEST::IsWithinWrapped<double>,
78  ( aArc.GetEndAngle() )( aProps.m_end_angle )( 360.0 )( angle_tol_deg ) );
79  BOOST_CHECK_PREDICATE(
80  KI_TEST::IsWithin<double>, ( aArc.GetRadius() )( aProps.m_radius )( aSynErrIU ) );
81 
83  const auto chord = aArc.GetChord();
84 
85  BOOST_CHECK_PREDICATE(
86  KI_TEST::IsVecWithinTol<VECTOR2I>, ( chord.A )( aProps.m_start_point )( pos_tol ) );
87  BOOST_CHECK_PREDICATE(
88  KI_TEST::IsVecWithinTol<VECTOR2I>, ( chord.B )( aProps.m_end_point )( pos_tol ) );
89 
91  BOOST_CHECK_EQUAL( aArc.IsSolid(), true );
92 
93  BOOST_CHECK_PREDICATE(
94  KI_TEST::IsBoxWithinTol<BOX2I>, ( aArc.BBox() )( aProps.m_bbox )( pos_tol ) );
95 
97 }
98 
99 
106 static void CheckArc( const SHAPE_ARC& aArc, const ARC_PROPERTIES& aProps, const int aSynErrIU = 1 )
107 {
108  // Check the original arc
109  CheckArcGeom( aArc, aProps, aSynErrIU );
110 
111  // Test the Clone function (also tests copy-ctor)
112  std::unique_ptr<SHAPE> new_shape{ aArc.Clone() };
113 
114  BOOST_CHECK_EQUAL( new_shape->Type(), SH_ARC );
115 
116  SHAPE_ARC* new_arc = dynamic_cast<SHAPE_ARC*>( new_shape.get() );
117 
118  BOOST_REQUIRE( new_arc != nullptr );
119 
121  CheckArcGeom( *new_arc, aProps, aSynErrIU );
122 }
123 
128 {
129  auto arc = SHAPE_ARC();
130 
131  BOOST_CHECK_EQUAL( arc.GetWidth(), 0 );
132 
133  static ARC_PROPERTIES null_props{
134  { 0, 0 },
135  { 0, 0 },
136  { 0, 0 },
137  0,
138  0,
139  0,
140  0,
141  };
142 
143  CheckArc( arc, null_props );
144 }
145 
146 
153 {
157 };
158 
159 
161 {
163  std::string m_ctx_name;
164 
167 
169  int m_width;
170 
173 };
174 
175 
176 static const std::vector<ARC_CPA_CASE> arc_cases = {
177  {
178  "C(0,0) 114 + 360 degree",
179  {
180  { 0, 0 },
181  { -306451, 687368 },
182  360,
183  },
184  0,
185  {
186  { 0, 0 },
187  { -306451, 687368 },
188  { -306451, 687368 },
189  360,
190  113.95929,
191  113.95929,
192  752587,
193  { { -752587, -752587 }, { 1505174, 1505174 } },
194  },
195  },
196  {
197  "C(0,0) 180 + 360 degree",
198  {
199  { 0, 0 },
200  { -100, 0 },
201  360,
202  },
203  0,
204  {
205  { 0, 0 },
206  { -100, 0 },
207  { -100, 0 },
208  360,
209  180,
210  180,
211  100,
212  { { -100, -100 }, { 200, 200 } },
213  },
214  },
215  {
216  "C(0,0) 180 + 90 degree",
217  {
218  { 0, 0 },
219  { -100, 0 },
220  90,
221  },
222  0,
223  {
224  { 0, 0 },
225  { -100, 0 },
226  { 0, -100 },
227  90,
228  180,
229  270,
230  100,
231  { { -100, -100 }, { 100, 100 } },
232  },
233  },
234  {
235  "C(100,200) 0 - 30 degree",
236  {
237  { 100, 200 },
238  { 300, 200 },
239  -30,
240  },
241  0,
242  {
243  { 100, 200 },
244  { 300, 200 },
245  { 273, 100 }, // 200 * sin(30) = 100, 200* cos(30) = 173
246  -30,
247  0,
248  330,
249  200,
250  { { 273, 100 }, { 27, 100 } },
251  },
252  },
253  {
254  // This is a "fan shape" which includes the top quadrant point,
255  // so it exercises the bounding box code (centre and end points
256  // do not contain the top quadrant)
257  "C(0,0) 30 + 120 degree",
258  {
259  { 0, 0 },
260  { 17320, 10000 },
261  120,
262  },
263  0,
264  {
265  { 0, 0 },
266  { 17320, 10000 },
267  { -17320, 10000 }, // 200 * sin(30) = 100, 200* cos(30) = 173
268  120,
269  30,
270  150,
271  20000,
272  // bbox defined by: centre, top quadrant point, two endpoints
273  { { -17320, 10000 }, { 17320 * 2, 10000 } },
274  },
275  },
276  {
277  // An arc that covers three quadrant points (L/R, bottom)
278  "C(0,0) 150 + 240 degree",
279  {
280  { 0, 0 },
281  { -17320, 10000 },
282  240,
283  },
284  0,
285  {
286  { 0, 0 },
287  { -17320, 10000 },
288  { 17320, 10000 },
289  240,
290  150,
291  30,
292  20000,
293  // bbox defined by: L/R quads, bottom quad and start/end
294  { { -20000, -20000 }, { 40000, 30000 } },
295  },
296  },
297  {
298  // Same as above but reverse direction
299  "C(0,0) 30 - 300 degree",
300  {
301  { 0, 0 },
302  { 17320, 10000 },
303  -240,
304  },
305  0,
306  {
307  { 0, 0 },
308  { 17320, 10000 },
309  { -17320, 10000 },
310  -240,
311  30,
312  150,
313  20000,
314  // bbox defined by: L/R quads, bottom quad and start/end
315  { { -20000, -20000 }, { 40000, 30000 } },
316  },
317  },
318 };
319 
320 
321 BOOST_AUTO_TEST_CASE( BasicCPAGeom )
322 {
323  for( const auto& c : arc_cases )
324  {
325  BOOST_TEST_CONTEXT( c.m_ctx_name )
326  {
327 
328  const auto this_arc = SHAPE_ARC{ c.m_geom.m_center_point, c.m_geom.m_start_point,
329  c.m_geom.m_center_angle, c.m_width };
330 
331  CheckArc( this_arc, c.m_properties );
332  }
333  }
334 }
335 
336 
337 
342 {
345  int m_radius;
346 };
347 
348 
350 {
352  std::string m_ctx_name;
353 
356 
358  int m_width;
359 
362 };
363 
364 
365 static const std::vector<ARC_TTR_CASE> arc_ttr_cases = {
366  {
367  "90 degree segments intersecting",
368  {
369  { 0, 0, 0, 1000 },
370  { 0, 0, 1000, 0 },
371  1000,
372  },
373  0,
374  {
375  { 1000, 1000 },
376  { 0, 1000 }, //start on first segment
377  { 1000, 0 }, //end on second segment
378  90, //positive angle due to start/end
379  180,
380  270,
381  1000,
382  { { 0, 0 }, { 1000, 1000 } },
383  }
384  },
385  {
386  "45 degree segments intersecting",
387  {
388  { 0, 0, 0, 1000 },
389  { 0, 0, 1000, 1000 },
390  1000,
391  },
392  0,
393  {
394  { 1000, 2414 },
395  { 0, 2414 }, //start on first segment
396  { 1707, 1707 }, //end on second segment
397  135, //positive angle due to start/end
398  180,
399  225,
400  1000,
401  { { 0, 1414 }, { 1707, 1000 } },
402  }
403  },
404  {
405  "135 degree segments intersecting",
406  {
407  { 0, 0, 0, 1000 },
408  { 0, 0, 1000, -1000 },
409  1000,
410  },
411  0,
412  {
413  { 1000, 414 },
414  { 0, 414 }, //start on first segment ( radius * tan(45 /2) )
415  { 293, -293 }, //end on second segment (radius * 1-cos(45)) )
416  45, //positive angle due to start/end
417  180,
418  225,
419  1000,
420  { { 0, -293 }, { 293, 707 } },
421  }
422  }
423 
424 
425 };
426 
427 
428 BOOST_AUTO_TEST_CASE( BasicTTRGeom )
429 {
430  for( const auto& c : arc_ttr_cases )
431  {
432  BOOST_TEST_CONTEXT( c.m_ctx_name )
433  {
434  for( int testCase = 0; testCase < 8; ++testCase )
435  {
436  SEG seg1 = c.m_geom.m_segment_1;
437  SEG seg2 = c.m_geom.m_segment_2;
438  ARC_PROPERTIES props = c.m_properties;
439 
440  if( testCase > 3 )
441  {
442  //Swap input segments.
443  seg1 = c.m_geom.m_segment_2;
444  seg2 = c.m_geom.m_segment_1;
445 
446  //The result should swap start and end points and invert the angles:
447  props.m_end_point = c.m_properties.m_start_point;
448  props.m_start_point = c.m_properties.m_end_point;
449  props.m_start_angle = c.m_properties.m_end_angle;
450  props.m_end_angle = c.m_properties.m_start_angle;
451  props.m_center_angle = -c.m_properties.m_center_angle;
452  }
453 
454  //Test all combinations of start and end points for the segments
455  if( ( testCase % 4 ) == 1 || ( testCase % 4 ) == 3 )
456  {
457  //Swap start and end points for seg1
458  VECTOR2I temp = seg1.A;
459  seg1.A = seg1.B;
460  seg1.B = temp;
461  }
462 
463  if( ( testCase % 4 ) == 2 || ( testCase % 4 ) == 3 )
464  {
465  //Swap start and end points for seg2
466  VECTOR2I temp = seg2.A;
467  seg2.A = seg2.B;
468  seg2.B = temp;
469  }
470 
471  const auto this_arc = SHAPE_ARC{ seg1, seg2,
472  c.m_geom.m_radius, c.m_width };
473 
474  // Error of 4 IU permitted for the center and radius calculation
475  CheckArc( this_arc, props, SHAPE_ARC::MIN_PRECISION_IU );
476  }
477  }
478  }
479 }
480 
481 
482 
484 {
485  std::string m_ctx_name;
487 };
488 
489 
499 bool ArePolylineEndPointsNearCircle( const SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aCentre,
500  int aRad, int aTolerance )
501 {
502  std::vector<VECTOR2I> points;
503 
504  for( int i = 0; i < aPolyline.PointCount(); ++i )
505  {
506  points.push_back( aPolyline.CPoint( i ) );
507  }
508 
509  return GEOM_TEST::ArePointsNearCircle( points, aCentre, aRad, aTolerance );
510 }
511 
512 
522 bool ArePolylineMidPointsNearCircle( const SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aCentre,
523  int aRad, int aTolerance )
524 {
525  std::vector<VECTOR2I> points;
526 
527  for( int i = 0; i < aPolyline.PointCount() - 1; ++i )
528  {
529  const VECTOR2I mid_pt = ( aPolyline.CPoint( i ) + aPolyline.CPoint( i + 1 ) ) / 2;
530  points.push_back( mid_pt );
531  }
532 
533  return GEOM_TEST::ArePointsNearCircle( points, aCentre, aRad, aTolerance );
534 }
535 
536 
537 BOOST_AUTO_TEST_CASE( ArcToPolyline )
538 {
539  const std::vector<ARC_TO_POLYLINE_CASE> cases = {
540  {
541  "Zero rad",
542  {
543  { 0, 0 },
544  { 0, 0 },
545  180,
546  },
547  },
548  {
549  "Semicircle",
550  {
551  { 0, 0 },
552  { -1000000, 0 },
553  180,
554  },
555  },
556  {
557  // check that very small circles don't fall apart and that reverse angles
558  // work too
559  "Extremely small semicircle",
560  {
561  { 0, 0 },
562  { -1000, 0 },
563  -180,
564  },
565  },
566  {
567  // Make sure it doesn't only work for "easy" angles
568  "Non-round geometry",
569  {
570  { 0, 0 },
571  { 1234567, 0 },
572  42.22,
573  },
574  },
575  };
576 
577  const int width = 0;
578 
579  // Note: do not expect accuracies around 1 to work. We use integers internally so we're
580  // liable to rounding errors. In PCBNew accuracy defaults to 5000 and we don't recommend
581  // anything lower than 1000 (for performance reasons).
582  const int accuracy = 100;
583  const int epsilon = 1;
584 
585  for( const auto& c : cases )
586  {
587  BOOST_TEST_CONTEXT( c.m_ctx_name )
588  {
589  const SHAPE_ARC this_arc{ c.m_geom.m_center_point, c.m_geom.m_start_point,
590  c.m_geom.m_center_angle, width };
591 
592  const SHAPE_LINE_CHAIN chain = this_arc.ConvertToPolyline( accuracy );
593 
594  BOOST_TEST_MESSAGE( "Polyline has " << chain.PointCount() << " points" );
595 
596  // Start point (exactly) where expected
597  BOOST_CHECK_EQUAL( chain.CPoint( 0 ), c.m_geom.m_start_point );
598 
599  // End point (exactly) where expected
600  BOOST_CHECK_EQUAL( chain.CPoint( -1 ), this_arc.GetP1() );
601 
602  int radius = ( c.m_geom.m_center_point - c.m_geom.m_start_point ).EuclideanNorm();
603 
604  // Other points within accuracy + epsilon (for rounding) of where they should be
605  BOOST_CHECK_PREDICATE( ArePolylineEndPointsNearCircle,
606  ( chain )( c.m_geom.m_center_point )( radius )( accuracy + epsilon ) );
607 
608  BOOST_CHECK_PREDICATE( ArePolylineMidPointsNearCircle,
609  ( chain )( c.m_geom.m_center_point )( radius )( accuracy + epsilon ) );
610  }
611  }
612 }
613 
614 
615 BOOST_AUTO_TEST_SUITE_END()
double EuclideanNorm(const wxPoint &vector)
Euclidean norm of a 2D vector.
Definition: trigo.h:134
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.
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
ARC_TAN_TAN_RADIUS m_geom
Geom of the arc.
double GetRadius() const
Definition: shape_arc.cpp:364
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...
double GetStartAngle() const
Definition: shape_arc.cpp:325
int PointCount() const
Function PointCount()
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.
std::string m_ctx_name
The text context name.
const VECTOR2I & CPoint(int aIndex) const
Function Point()
VECTOR2I m_end_point
int m_width
Arc line width.
ARC_PROPERTIES m_properties
Expected properties.
VECTOR2I m_center_point
std::string m_ctx_name
The text context name.
static const int MIN_PRECISION_IU
This is the minimum precision for all the points in the arc shape.
Definition: shape_arc.h:41
SEG GetChord() const
Definition: shape_arc.h:126
compound shape, consisting of multiple simple shapes
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.
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)
SHAPE * Clone() const override
Function Clone()
Definition: shape_arc.h:80
double GetEndAngle() const
Definition: shape_arc.cpp:335
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:39
bool IsSolid() const override
Definition: shape_arc.h:107
SHAPE_LINE_CHAIN.
VECTOR2I A
Definition: seg.h:47
double GetCentralAngle() const
Definition: shape_arc.cpp:351
ARC_CENTRE_PT_ANGLE m_geom
Geom of the arc.
const BOX2I BBox(int aClearance=0) const override
Function BBox()
Definition: shape_arc.cpp:284
Numerical test predicates.
Info to set up an arc by centre, start point and angle.
int m_width
Definition: shape_arc.h:162
int m_width
Arc line width.
static const std::vector< ARC_CPA_CASE > arc_cases
const VECTOR2I & GetP1() const
Definition: shape_arc.h:86
VECTOR2I GetCenter() const
Definition: shape_arc.cpp:345
VECTOR2I B
Definition: seg.h:48