KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_shape_arc.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, 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.3 },
775 { 79.279991, 80.67988, 80.3749, 81.694518, -255.5, 0.3 },
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 { "case 15: Arcs separated by clearance",
819 { 303.7615, 149.9252, 303.695968, 149.925237, 90.0262, 0.065 },
820 { 303.6345, 149.2637, 303.634523, 148.85619, 89.9957, 0.065 },
821 0.15,
822 false },
823};
824
825
827{
828 for( const auto& c : arc_arc_collide_cases )
829 {
830 BOOST_TEST_CONTEXT( c.m_ctx_name )
831 {
832 SHAPE_ARC arc1( c.m_arc1.GenerateArc() );
833 SHAPE_ARC arc2( c.m_arc2.GenerateArc() );
834
835
836 SHAPE_LINE_CHAIN arc1_slc( c.m_arc1.GenerateArc() );
837 arc1_slc.SetWidth( 0 );
838
839 SHAPE_LINE_CHAIN arc2_slc( c.m_arc2.GenerateArc() );
840 arc2_slc.SetWidth( 0 );
841
842 int actual = 0;
843 VECTOR2I location;
844
845 SHAPE* arc1_sh = &arc1;
846 SHAPE* arc2_sh = &arc2;
847 SHAPE* arc1_slc_sh = &arc1_slc;
848 SHAPE* arc2_slc_sh = &arc2_slc;
849
850 bool result_arc_to_arc = arc1_sh->Collide( arc2_sh, pcbIUScale.mmToIU( c.m_clearance ),
851 &actual, &location );
852
853 // For arc to chain collisions, we need to re-calculate the clearances because the
854 // SHAPE_LINE_CHAIN is zero width
855 int clearance = pcbIUScale.mmToIU( c.m_clearance ) + ( arc2.GetWidth() / 2 );
856
857 bool result_arc_to_chain =
858 arc1_sh->Collide( arc2_slc_sh, clearance, &actual, &location );
859
860 clearance = pcbIUScale.mmToIU( c.m_clearance ) + ( arc1.GetWidth() / 2 );
861 bool result_chain_to_arc =
862 arc1_slc_sh->Collide( arc2_sh, clearance, &actual, &location );
863
864 clearance = ( arc1.GetWidth() / 2 ) + ( arc2.GetWidth() / 2 );
865 bool result_chain_to_chain =
866 arc1_slc_sh->Collide( arc2_slc_sh, clearance, &actual, &location );
867
868 BOOST_CHECK_EQUAL( result_arc_to_arc, c.m_exp_result );
869 BOOST_CHECK_EQUAL( result_arc_to_chain, c.m_exp_result );
870 BOOST_CHECK_EQUAL( result_chain_to_arc, c.m_exp_result );
871 BOOST_CHECK_EQUAL( result_chain_to_chain, c.m_exp_result );
872 }
873 }
874}
875
876
877BOOST_AUTO_TEST_CASE( CollideArcToShapeLineChain )
878{
879 SHAPE_ARC arc( VECTOR2I( 206000000, 140110000 ), VECTOR2I( 201574617, 139229737 ),
880 VECTOR2I( 197822958, 136722959 ), 250000 );
881
882 SHAPE_LINE_CHAIN lc( { VECTOR2I( 159600000, 142500000 ), VECTOR2I( 159600000, 142600000 ),
883 VECTOR2I( 166400000, 135800000 ), VECTOR2I( 166400000, 111600000 ),
884 VECTOR2I( 190576804, 111600000 ), VECTOR2I( 192242284, 113265480 ),
885 VECTOR2I( 192255720, 113265480 ), VECTOR2I( 203682188, 124691948 ),
886 VECTOR2I( 203682188, 140332188 ), VECTOR2I( 206000000, 142650000 ) },
887 false );
888
889
890
891 SHAPE* arc_sh = &arc;
892 SHAPE* lc_sh = &lc;
893
894 BOOST_CHECK_EQUAL( arc_sh->Collide( &lc, 100000 ), true );
895 BOOST_CHECK_EQUAL( lc_sh->Collide( &arc, 100000 ), true );
896
897 SEG seg( VECTOR2I( 203682188, 124691948 ), VECTOR2I( 203682188, 140332188 ) );
898 BOOST_CHECK_EQUAL( arc.Collide( seg, 0 ), true );
899}
900
901
902BOOST_AUTO_TEST_CASE( CollideArcToPolygonApproximation )
903{
904 SHAPE_ARC arc( VECTOR2I( 73843527, 74355869 ), VECTOR2I( 71713528, 72965869 ),
905 EDA_ANGLE( -76.36664803, DEGREES_T ), 1000000 );
906
907 // Create a polyset approximation from the arc - error outside (simulating the zone filler)
908 SHAPE_POLY_SET arcBuffer;
909 int clearance = ( arc.GetWidth() * 3 ) / 2;
910 int polygonApproximationError = SHAPE_ARC::DefaultAccuracyForPCB();
911
912 TransformArcToPolygon( arcBuffer, arc.GetP0(), arc.GetArcMid(), arc.GetP1(),
913 arc.GetWidth() + 2 * clearance,
914 polygonApproximationError, ERROR_OUTSIDE );
915
916 BOOST_REQUIRE_EQUAL( arcBuffer.OutlineCount(), 1 );
917 BOOST_CHECK_EQUAL( arcBuffer.HoleCount( 0 ), 0 );
918
919 // Make a reasonably large rectangular outline around the arc shape
920 BOX2I arcbbox = arc.BBox( clearance * 4 );
921
922 SHAPE_LINE_CHAIN zoneOutline( { arcbbox.GetPosition(),
923 arcbbox.GetPosition() + VECTOR2I( arcbbox.GetWidth(), 0 ),
924 arcbbox.GetEnd(),
925 arcbbox.GetEnd() - VECTOR2I( arcbbox.GetWidth(), 0 )
926 },
927 true );
928
929 // Create a synthetic "zone fill" polygon
930 SHAPE_POLY_SET zoneFill;
931 zoneFill.AddOutline( zoneOutline );
932 zoneFill.AddHole( arcBuffer.Outline( 0 ) );
933 zoneFill.CacheTriangulation( false );
934
935 int actual = 0;
936 VECTOR2I location;
937 int epsilon = polygonApproximationError / 10;
938
939 BOOST_CHECK_EQUAL( zoneFill.Collide( &arc, clearance + epsilon, &actual, &location ), true );
940
941 BOOST_CHECK_EQUAL( zoneFill.Collide( &arc, clearance - epsilon, &actual, &location ), false );
942}
943
944
946{
947 std::string m_ctx_name;
949};
950
951
961bool ArePolylineEndPointsNearCircle( const SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aCentre,
962 int aRad, int aTolerance )
963{
964 std::vector<VECTOR2I> points;
965
966 for( int i = 0; i < aPolyline.PointCount(); ++i )
967 {
968 points.push_back( aPolyline.CPoint( i ) );
969 }
970
971 return GEOM_TEST::ArePointsNearCircle( points, aCentre, aRad, aTolerance );
972}
973
974
984bool ArePolylineMidPointsNearCircle( const SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aCentre,
985 int aRad, int aTolerance )
986{
987 std::vector<VECTOR2I> points;
988
989 for( int i = 0; i < aPolyline.PointCount() - 1; ++i )
990 {
991 const VECTOR2I mid_pt = ( aPolyline.CPoint( i ) + aPolyline.CPoint( i + 1 ) ) / 2;
992 points.push_back( mid_pt );
993 }
994
995 return GEOM_TEST::ArePointsNearCircle( points, aCentre, aRad, aTolerance );
996}
997
998
999BOOST_AUTO_TEST_CASE( ArcToPolyline )
1000{
1001 const std::vector<ARC_TO_POLYLINE_CASE> cases = {
1002 {
1003 "Zero rad",
1004 {
1005 { 0, 0 },
1006 { 0, 0 },
1007 180,
1008 },
1009 },
1010 {
1011 "Semicircle",
1012 {
1013 { 0, 0 },
1014 { -1000000, 0 },
1015 180,
1016 },
1017 },
1018 {
1019 // check that very small circles don't fall apart and that reverse angles
1020 // work too
1021 "Extremely small semicircle",
1022 {
1023 { 0, 0 },
1024 { -1000, 0 },
1025 -180,
1026 },
1027 },
1028 {
1029 // Make sure it doesn't only work for "easy" angles
1030 "Non-round geometry",
1031 {
1032 { 0, 0 },
1033 { 1234567, 0 },
1034 42.22,
1035 },
1036 },
1037 };
1038
1039 const int width = 0;
1040
1041 // Note: do not expect accuracies around 1 to work. We use integers internally so we're
1042 // liable to rounding errors. In PCBNew accuracy defaults to 5000 and we don't recommend
1043 // anything lower than 1000 (for performance reasons).
1044 const int accuracy = 100;
1045 const int epsilon = 1;
1046
1047 for( const auto& c : cases )
1048 {
1049 BOOST_TEST_CONTEXT( c.m_ctx_name )
1050 {
1051 const SHAPE_ARC this_arc{ c.m_geom.m_center_point, c.m_geom.m_start_point,
1052 EDA_ANGLE( c.m_geom.m_center_angle, DEGREES_T ), width };
1053
1054 const SHAPE_LINE_CHAIN chain = this_arc.ConvertToPolyline( accuracy );
1055
1056 BOOST_TEST_MESSAGE( "Polyline has " << chain.PointCount() << " points" );
1057
1058 // Start point (exactly) where expected
1059 BOOST_CHECK_EQUAL( chain.CPoint( 0 ), c.m_geom.m_start_point );
1060
1061 // End point (exactly) where expected
1062 BOOST_CHECK_EQUAL( chain.CPoint( -1 ), this_arc.GetP1() );
1063
1064 int radius = ( c.m_geom.m_center_point - c.m_geom.m_start_point ).EuclideanNorm();
1065
1066 // Other points within accuracy + epsilon (for rounding) of where they should be
1067 BOOST_CHECK_PREDICATE( ArePolylineEndPointsNearCircle,
1068 ( chain )( c.m_geom.m_center_point )( radius )( accuracy + epsilon ) );
1069
1070 BOOST_CHECK_PREDICATE( ArePolylineMidPointsNearCircle,
1071 ( chain )( c.m_geom.m_center_point )( radius )( accuracy + epsilon ) );
1072 }
1073 }
1074}
1075
1076
@ 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
void SetWidth(int aWidth)
Definition: shape_arc.h:205
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
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.
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)
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...
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
VECTOR2< int32_t > VECTOR2I
Definition: vector2d.h:695
VECTOR2< double > VECTOR2D
Definition: vector2d.h:694