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