KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_eda_shape.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 3
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 */
17
18
20
21#include <cmath>
22
23#include <base_units.h>
24#include <eda_shape.h>
25#include <math/util.h>
27#include <qa_utils/geometry/geometry.h> // For KI_TEST::IsVecWithinTol
28#include <geometry/shape_arc.h> // For SHAPE_ARC::DefaultAccuracyForPCB()
31#include <render_settings.h>
32#include <stroke_params.h>
33
34
35BOOST_AUTO_TEST_SUITE( EdaShape )
36
38{
39public:
40 EDA_SHAPE_MOCK( SHAPE_T aShapeType ) : EDA_SHAPE( aShapeType, 0, FILL_T::NO_FILL ){};
41};
42
43
44static void checkVectorClose( const VECTOR2D& aActual, const VECTOR2D& aExpected, double aTolerance = 1e-6 )
45{
46 BOOST_CHECK_SMALL( aActual.x - aExpected.x, aTolerance );
47 BOOST_CHECK_SMALL( aActual.y - aExpected.y, aTolerance );
48}
49
50
51static void checkVectorEqual( const VECTOR2I& aActual, const VECTOR2I& aExpected )
52{
53 BOOST_CHECK_EQUAL( aActual.x, aExpected.x );
54 BOOST_CHECK_EQUAL( aActual.y, aExpected.y );
55}
56
57
58static void checkAngleClose( const EDA_ANGLE& aActual, double aExpectedDegrees, double aTolerance = 1e-6 )
59{
60 BOOST_CHECK_SMALL( aActual.AsDegrees() - aExpectedDegrees, aTolerance );
61}
62
63
64static void checkAngleInRange( const EDA_ANGLE& aActual, double aMinDegrees, double aMaxDegrees )
65{
66 BOOST_CHECK_MESSAGE( aActual.AsDegrees() > aMinDegrees && aActual.AsDegrees() < aMaxDegrees,
67 "Expected angle " << aActual.AsDegrees() << " to be between " << aMinDegrees << " and "
68 << aMaxDegrees );
69}
70
71
72static void deleteShapes( std::vector<SHAPE*>& aShapes )
73{
74 for( SHAPE* shape : aShapes )
75 delete shape;
76
77 aShapes.clear();
78}
79
80
90
91
92static const std::vector<SET_ANGLE_END_CASE> set_angle_end_cases =
93{
94 {
95 "Issue 13626: clockwise semicircle",
96 {-428880000, 117229160 },
97 {-430060565, 113472820 },
98 180.0,
99 {-431241130, 109716480 },
100 false
101 },
102 {
103 "Issue 13626: anticlockwise arc",
104 { -431241130, 109716480 },
105 { -434923630, 112954230 },
106 -138.46654568595355,
107 { -439827050, 112936200 },
108 true
109 }
110};
111
112
113BOOST_AUTO_TEST_CASE( SetAngleAndEnd )
114{
115 for( const auto& c : set_angle_end_cases )
116 {
117 BOOST_TEST_INFO_SCOPE( c.m_CaseName );
118
120 shape.SetStart( c.m_Start );
121 shape.SetCenter( c.m_Center );
122
123 shape.SetArcAngleAndEnd( EDA_ANGLE( c.m_Angle, DEGREES_T ), true );
124
125 BOOST_CHECK_EQUAL( shape.EndsSwapped(), c.m_ExpectedStartEndSwapped );
126
127 const VECTOR2I newEnd = shape.EndsSwapped() ? shape.GetStart() : shape.GetEnd();
128
131 (newEnd) ( c.m_ExpectedEndBeforeSwap ) ( SHAPE_ARC::DefaultAccuracyForPCB() ) );
132 }
133}
134
135
148
149static const std::vector<SET_ARC_GEOMETRY_CASE> set_arc_geometry_cases = {
150 {
151 // Test that when setting an arc by start/mid/end, the winding
152 // direction is correctly determined (in 15694, this was in FP_SHAPE,
153 // but the logic has since been merged with EDA_SHAPE).
154 "Issue 15694: clockwise arc",
155 { 10000000, 0 },
156 { 0, 10000000 },
157 { -10000000, 0 },
158 { 0, 0 },
159 10000000,
160 false,
161 { -10000000, 0 }, // unchanged
162 180.0,
163 },
164 {
165 "Issue 15694: anticlockwise arc",
166 { -10000000, 0 },
167 { 0, 10000000 },
168 { 10000000, 0 },
169 { 0, 0 },
170 10000000,
171 true,
172 { 10000000, 0 }, // the start is the end after swapping
173 180.0, // angle is positive after swapping
174 },
175};
176
177BOOST_AUTO_TEST_CASE( SetArcGeometry )
178{
179 const double angle_tol = 0.1;
180
181 for( const auto& c : set_arc_geometry_cases )
182 {
183 BOOST_TEST_INFO_SCOPE( c.m_CaseName );
184
186
187 shape.SetArcGeometry( c.m_Start, c.m_Mid, c.m_End );
188
189 const VECTOR2I center = shape.getCenter();
190
193 (center) ( c.m_ExpectedCenter ) ( SHAPE_ARC::DefaultAccuracyForPCB() ) );
194
195 const int radius = shape.GetRadius();
196
199 (radius) ( c.m_ExpectedRadius ) ( SHAPE_ARC::DefaultAccuracyForPCB() ) );
200
201 BOOST_CHECK_EQUAL( shape.EndsSwapped(), c.m_ExpectedStartEndSwapped );
202
203 const VECTOR2I newEnd = shape.EndsSwapped() ? shape.GetStart() : shape.GetEnd();
204
207 (newEnd) ( c.m_ExpectedEndAfterSwap ) ( SHAPE_ARC::DefaultAccuracyForPCB() ) );
208
209 const EDA_ANGLE angle = shape.GetArcAngle();
210
213 ( angle.AsDegrees() )( c.m_ExpectedAngleAfterSwapDeg )( 360.0 )( angle_tol ) );
214
215 // Check that the centre is still correct
216 }
217}
218
223BOOST_AUTO_TEST_CASE( SetArcGeometryKeepsMidWhenEndsSwap )
224{
225 // On the circle but away from the half-angle point, so a recomputed mid cannot match it
226 const VECTOR2I mid( 6000000, 8000000 );
227
228 for( bool swap : { false, true } )
229 {
230 BOOST_TEST_INFO_SCOPE( ( swap ? "swapped" : "unswapped" ) );
231
233
234 if( swap )
235 shape.SetArcGeometry( { -10000000, 0 }, mid, { 10000000, 0 } );
236 else
237 shape.SetArcGeometry( { 10000000, 0 }, mid, { -10000000, 0 } );
238
239 BOOST_REQUIRE_EQUAL( shape.EndsSwapped(), swap );
240 BOOST_CHECK_EQUAL( shape.GetArcMid(), mid );
241 }
242}
243
244
251BOOST_AUTO_TEST_CASE( ArcEditKeepsSmallSchematicRadius )
252{
253 // 50 mil radius arc in schematic IUs, well under the buggy 100 mil floor.
254 const int radius = schIUScale.MilsToIU( 50 );
255 const VECTOR2I center( 0, 0 );
256 const VECTOR2I start( radius, 0 );
257 const VECTOR2I end( 0, radius );
258 const VECTOR2I mid( KiROUND( radius / std::sqrt( 2.0 ) ),
259 KiROUND( radius / std::sqrt( 2.0 ) ) );
260
262 arc.SetArcGeometry( start, mid, end );
263
264 BOOST_REQUIRE_LT( arc.GetRadius(), schIUScale.MilsToIU( 100 ) );
265
266 // Drag the endpoint a few IU; with the bug the radius snaps up to 100 mil.
267 const VECTOR2I newEnd( 5, radius );
268
269 KI_ARC_EDIT::EditArcEndpointKeepCenter( arc, center, start, mid, newEnd, newEnd, schIUScale );
270 BOOST_CHECK_LT( arc.GetRadius(), schIUScale.MilsToIU( 100 ) );
271
272 // Same for the mid-point helper, which has its own minimum-radius clamp.
273 const VECTOR2I smallerMid( KiROUND( ( radius - 100 ) / std::sqrt( 2.0 ) ),
274 KiROUND( ( radius - 100 ) / std::sqrt( 2.0 ) ) );
275
277 arc2.SetArcGeometry( start, mid, end );
278
279 KI_ARC_EDIT::EditArcMidKeepCenter( arc2, center, start, mid, end, smallerMid, schIUScale );
280 BOOST_CHECK_LT( arc2.GetRadius(), schIUScale.MilsToIU( 100 ) );
281}
282
283
291BOOST_AUTO_TEST_CASE( PolygonBehaviorSurvivesAssignment )
292{
294
295 SHAPE_POLY_SET& poly = shape.GetPolyShape();
296 poly.NewOutline();
297 poly.Append( { 0, 0 } );
298 poly.Append( { 1000000, 0 } );
299 poly.Append( { 1000000, 1000000 } );
300
301 EDA_POLYGON_POINT_EDIT_BEHAVIOR behavior( shape );
302
303 EDIT_POINTS points( nullptr );
304 behavior.MakePoints( points );
305 BOOST_CHECK_EQUAL( points.PointsSize(), 3u );
306
307 EDA_SHAPE_MOCK copy( shape );
308 shape = copy;
309
310 // After assignment, shape.m_poly is a fresh allocation.
311 // The behavior must still work (not use-after-free).
312 EDIT_POINTS points2( nullptr );
313 behavior.MakePoints( points2 );
314 BOOST_CHECK_EQUAL( points2.PointsSize(), 3u );
315
316 BOOST_CHECK( behavior.UpdatePoints( points ) );
317}
318
319
320BOOST_AUTO_TEST_CASE( PolygonBehaviorSignalsRebuildWhenShapeMorphs )
321{
323
324 SHAPE_POLY_SET& poly = shape.GetPolyShape();
325 poly.NewOutline();
326 poly.Append( { 0, 0 } );
327 poly.Append( { 1000000, 0 } );
328 poly.Append( { 1000000, 1000000 } );
329 poly.Append( { 0, 1000000 } );
330
331 EDA_POLYGON_POINT_EDIT_BEHAVIOR behavior( shape );
332
333 EDIT_POINTS points( nullptr );
334 behavior.MakePoints( points );
335 BOOST_CHECK( behavior.UpdatePoints( points ) );
336
338 shape.SetStart( { 0, 0 } );
339 shape.SetEnd( { 1000000, 1000000 } );
340
341 BOOST_CHECK( !behavior.UpdatePoints( points ) );
342}
343
344
345BOOST_AUTO_TEST_CASE( GetPolyPointsPreservesOrderAcrossOutlines )
346{
347 // GetPolyPoints flattens every outline of the poly shape into a single
348 // ordered vector. Verify the count and order are preserved across multiple
349 // outlines so the single up-front reserve does not alter behavior.
351
352 SHAPE_POLY_SET& poly = shape.GetPolyShape();
353
354 poly.NewOutline();
355 poly.Append( { 0, 0 } );
356 poly.Append( { 1000, 0 } );
357 poly.Append( { 1000, 1000 } );
358
359 poly.NewOutline();
360 poly.Append( { 5000, 5000 } );
361 poly.Append( { 6000, 5000 } );
362
363 const std::vector<VECTOR2I> expected = {
364 { 0, 0 }, { 1000, 0 }, { 1000, 1000 }, { 5000, 5000 }, { 6000, 5000 }
365 };
366
367 const std::vector<VECTOR2I> points = shape.GetPolyPoints();
368
369 BOOST_REQUIRE_EQUAL( points.size(), expected.size() );
370
371 for( size_t ii = 0; ii < expected.size(); ++ii )
372 {
373 BOOST_CHECK_EQUAL( points[ii].x, expected[ii].x );
374 BOOST_CHECK_EQUAL( points[ii].y, expected[ii].y );
375 }
376}
377
378
379BOOST_AUTO_TEST_CASE( EllipseBasicAccessors )
380{
381 // Construct a closed ellipse EDA_SHAPE and round-trip every accessor.
383 e.SetEllipseCenter( VECTOR2I( 100, 200 ) );
384 e.SetEllipseMajorRadius( 500 );
385 e.SetEllipseMinorRadius( 300 );
387
388 BOOST_CHECK( e.GetShape() == SHAPE_T::ELLIPSE );
393 BOOST_CHECK_CLOSE( e.GetEllipseRotation().AsDegrees(), 30.0, 1e-6 );
394
395 // Closed ellipse reports itself as a closed shape.
396 BOOST_CHECK( e.IsClosed() );
397}
398
399
400BOOST_AUTO_TEST_CASE( EllipseArcIsOpenCurve )
401{
402 // Elliptical arcs are open
403 // IsClosed() must return false.
405 arc.SetEllipseCenter( VECTOR2I( 0, 0 ) );
406 arc.SetEllipseMajorRadius( 500 );
407 arc.SetEllipseMinorRadius( 300 );
410 arc.SetEllipseEndAngle( EDA_ANGLE( 180.0, DEGREES_T ) );
411
412 BOOST_CHECK( arc.GetShape() == SHAPE_T::ELLIPSE_ARC );
413 BOOST_CHECK( !arc.IsClosed() );
414
415 // Start/end angles round trip through the accessors.
416 BOOST_CHECK_CLOSE( arc.GetEllipseStartAngle().AsDegrees(), 0.0, 1e-6 );
417 BOOST_CHECK_CLOSE( arc.GetEllipseEndAngle().AsDegrees(), 180.0, 1e-6 );
418}
419
420
421BOOST_AUTO_TEST_CASE( EllipsePerimeterForCircleCase )
422{
423 // An ellipse with MajorRadius == MinorRadius is a circle.
424 // Ramanujan's approximation returns 2Ï€r for this case.
426 e.SetEllipseCenter( VECTOR2I( 0, 0 ) );
427 e.SetEllipseMajorRadius( 1000 );
428 e.SetEllipseMinorRadius( 1000 );
430
431 const double expected = 2.0 * M_PI * 1000.0;
432 BOOST_CHECK_CLOSE( e.GetLength(), expected, 1e-6 );
433}
434
435
436BOOST_AUTO_TEST_CASE( EllipseMakeEffectiveShapesNonEmpty )
437{
438 // MakeEffectiveShapes converts the ellipse into primitive shapes that DRC
439 // the router, and exporters consume. Verify it returns at least one shape
440
442 e.SetEllipseCenter( VECTOR2I( 0, 0 ) );
443 e.SetEllipseMajorRadius( 500 );
444 e.SetEllipseMinorRadius( 300 );
446
447 std::vector<SHAPE*> shapes = e.MakeEffectiveShapes();
448 BOOST_CHECK( !shapes.empty() );
449
450 for( SHAPE* s : shapes )
451 delete s;
452}
453
454
455BOOST_AUTO_TEST_CASE( ShortenSegmentForEndingsTrimsStartAndEnd )
456{
457 VECTOR2I start( 0, 0 );
458 VECTOR2I end( 1000, 0 );
459 LINE_ENDING startEnding( LINE_ENDING_STYLE::ARROW, 100, 100 );
460 LINE_ENDING endEnding( LINE_ENDING_STYLE::CIRCLE, 200, 200 );
461
462 BOOST_CHECK( EDA_SHAPE::ShortenSegmentForEndings( start, end, startEnding, endEnding, 20 ) );
463 BOOST_CHECK_EQUAL( start.x, 100 );
464 BOOST_CHECK_EQUAL( start.y, 0 );
465 BOOST_CHECK_EQUAL( end.x, 900 );
466 BOOST_CHECK_EQUAL( end.y, 0 );
467}
468
469
470BOOST_AUTO_TEST_CASE( SegmentOpenArrowLeavesBody )
471{
472 VECTOR2I start( 0, 0 );
473 VECTOR2I end( 1000, 0 );
474 LINE_ENDING startEnding( LINE_ENDING_STYLE::ARROW_OPEN, 500, 500 );
475 LINE_ENDING endEnding;
476
477 BOOST_CHECK( EDA_SHAPE::ShortenSegmentForEndings( start, end, startEnding, endEnding, 20 ) );
478 BOOST_CHECK_EQUAL( start.x, 0 );
479 BOOST_CHECK_EQUAL( start.y, 0 );
480 BOOST_CHECK_EQUAL( end.x, 1000 );
481 BOOST_CHECK_EQUAL( end.y, 0 );
482}
483
484
485BOOST_AUTO_TEST_CASE( SegmentShorteningRejectsOverrun )
486{
487 VECTOR2I start( 0, 0 );
488 VECTOR2I end( 1000, 0 );
489 LINE_ENDING startEnding( LINE_ENDING_STYLE::ARROW, 600, 100 );
490 LINE_ENDING endEnding( LINE_ENDING_STYLE::ARROW, 500, 100 );
491
492 BOOST_CHECK( !EDA_SHAPE::ShortenSegmentForEndings( start, end, startEnding, endEnding, 20 ) );
493 BOOST_CHECK_EQUAL( start.x, 0 );
494 BOOST_CHECK_EQUAL( start.y, 0 );
495 BOOST_CHECK_EQUAL( end.x, 0 );
496 BOOST_CHECK_EQUAL( end.y, 0 );
497}
498
499
500BOOST_AUTO_TEST_CASE( SegmentEndingTangentsPointOutward )
501{
503 shape.SetStart( { 0, 0 } );
504 shape.SetEnd( { 1000, 0 } );
505
506 EDA_ANGLE startTangent;
507 EDA_ANGLE endTangent;
508 shape.GetEndingTangents( startTangent, endTangent, 20 );
509
510 checkAngleClose( startTangent, 180.0 );
511 checkAngleClose( endTangent, 0.0 );
512}
513
514
515BOOST_AUTO_TEST_CASE( OpenArrowBezierTangentsUseDepth )
516{
518 shape.SetStart( { 0, 0 } );
519 shape.SetBezierC1( { 1000, 0 } );
520 shape.SetBezierC2( { 0, 1000 } );
521 shape.SetEnd( { 1000, 1000 } );
523 shape.SetStartEndingLength( 300 );
524 shape.SetStartEndingWidth( 300 );
526 shape.SetEndEndingLength( 300 );
527 shape.SetEndEndingWidth( 300 );
528
529 EDA_ANGLE startTangent;
530 EDA_ANGLE endTangent;
531 shape.GetEndingTangents( startTangent, endTangent, 20 );
532
533 checkAngleInRange( startTangent, -180.0, -90.0 );
534 checkAngleInRange( endTangent, 0.0, 90.0 );
535}
536
537
538BOOST_AUTO_TEST_CASE( LineEndingEndpointsUseShapeOwnedSources )
539{
540 VECTOR2I start;
542
544 segment.SetStart( { 0, 10 } );
545 segment.SetEnd( { 100, 20 } );
546
547 BOOST_CHECK( segment.GetLineEndingEndpoints( start, end ) );
548 checkVectorEqual( start, { 0, 10 } );
549 checkVectorEqual( end, { 100, 20 } );
550
552 arc.SetStart( { 10, 0 } );
553 arc.SetEnd( { 20, 30 } );
554
555 BOOST_CHECK( arc.GetLineEndingEndpoints( start, end ) );
556 checkVectorEqual( start, { 10, 0 } );
557 checkVectorEqual( end, { 20, 30 } );
558
560 bezier.SetStart( { 0, 0 } );
561 bezier.SetBezierC1( { 20, 0 } );
562 bezier.SetBezierC2( { 80, 100 } );
563 bezier.SetEnd( { 100, 100 } );
564 bezier.RebuildBezierToSegmentsPointsList( 1 );
565
566 BOOST_CHECK( bezier.GetLineEndingEndpoints( start, end ) );
567 checkVectorEqual( start, { 0, 0 } );
568 checkVectorEqual( end, { 100, 100 } );
569
570 EDA_SHAPE_MOCK polyShape( SHAPE_T::POLY );
571 SHAPE_POLY_SET poly;
572 poly.NewOutline();
573 poly.Outline( 0 ).SetClosed( false );
574 poly.Append( { 5, 5 } );
575 poly.Append( { 50, 10 } );
576 poly.Append( { 100, 20 } );
577 polyShape.SetPolyShape( poly );
578
579 BOOST_CHECK( polyShape.GetLineEndingEndpoints( start, end ) );
580 checkVectorEqual( start, { 5, 5 } );
581 checkVectorEqual( end, { 100, 20 } );
582}
583
584
585BOOST_AUTO_TEST_CASE( LineEndingEndpointsRejectInvalidPolys )
586{
587 VECTOR2I start;
589
590 EDA_SHAPE_MOCK emptyPoly( SHAPE_T::POLY );
591 BOOST_CHECK( !emptyPoly.GetLineEndingEndpoints( start, end ) );
592
593 EDA_SHAPE_MOCK onePointPoly( SHAPE_T::POLY );
594 SHAPE_POLY_SET poly;
595 poly.NewOutline();
596 poly.Append( { 5, 5 } );
597 onePointPoly.SetPolyShape( poly );
598
599 BOOST_CHECK( !onePointPoly.GetLineEndingEndpoints( start, end ) );
600}
601
602
603BOOST_AUTO_TEST_CASE( EffectiveShapesIncludeClosedEnding )
604{
606 shape.SetStart( { 0, 0 } );
607 shape.SetEnd( { 1000, 0 } );
609 shape.SetEndEndingLength( 400 );
610 shape.SetEndEndingWidth( 400 );
611
612 std::vector<SHAPE*> shapes = shape.MakeLineEndingEffectiveShapes( 20 );
613
614 BOOST_REQUIRE_EQUAL( shapes.size(), 1 );
615
616 BOX2I bbox = shapes[0]->BBox();
617 BOOST_CHECK( bbox.GetLeft() <= 800 );
618 BOOST_CHECK( bbox.GetRight() >= 1200 );
619 BOOST_CHECK( bbox.GetTop() <= -200 );
620 BOOST_CHECK( bbox.GetBottom() >= 200 );
621
622 deleteShapes( shapes );
623}
624
625
626BOOST_AUTO_TEST_CASE( EffectiveShapesIncludeOpenArrowLegs )
627{
629 shape.SetStart( { 0, 0 } );
630 shape.SetEnd( { 1000, 0 } );
632 shape.SetEndEndingLength( 300 );
633 shape.SetEndEndingWidth( 200 );
634 shape.SetEndEndingStrokeWidth( 70 );
635
636 std::vector<SHAPE*> shapes = shape.MakeLineEndingEffectiveShapes( 20 );
637
638 BOOST_REQUIRE_EQUAL( shapes.size(), 2 );
639
640 for( SHAPE* shapePtr : shapes )
641 {
642 const SHAPE_SEGMENT* leg = dynamic_cast<const SHAPE_SEGMENT*>( shapePtr );
643 BOOST_REQUIRE( leg );
644 BOOST_CHECK_EQUAL( leg->GetWidth(), 70 );
645 }
646
647 deleteShapes( shapes );
648}
649
650
651BOOST_AUTO_TEST_CASE( BBoxIncludesCenteredEnding )
652{
654 shape.SetStart( { 0, 0 } );
655 shape.SetEnd( { 1000, 0 } );
657 shape.SetEndEndingLength( 400 );
658 shape.SetEndEndingWidth( 400 );
659
660 BOX2I bbox;
661
662 BOOST_CHECK( shape.GetLineEndingsBoundingBox( bbox, 20 ) );
663 BOOST_CHECK( bbox.GetLeft() <= 800 );
664 BOOST_CHECK( bbox.GetRight() >= 1200 );
665 BOOST_CHECK( bbox.GetTop() <= -200 );
666 BOOST_CHECK( bbox.GetBottom() >= 200 );
667}
668
669
670BOOST_AUTO_TEST_CASE( BBoxUsesRotatedEndingGeometry )
671{
673 shape.SetStart( { 0, 0 } );
674 shape.SetEnd( { 1000, 1000 } );
676 shape.SetEndEndingLength( 400 );
677 shape.SetEndEndingWidth( 400 );
678
679 BOX2I bbox;
680
681 BOOST_CHECK( shape.GetLineEndingsBoundingBox( bbox, 20 ) );
682 BOOST_CHECK( bbox.GetLeft() <= 718 );
683 BOOST_CHECK( bbox.GetRight() >= 1282 );
684 BOOST_CHECK( bbox.GetTop() <= 718 );
685 BOOST_CHECK( bbox.GetBottom() >= 1282 );
686}
687
688
689BOOST_AUTO_TEST_CASE( PolygonConversionAppendsEndings )
690{
692 shape.SetStart( { 0, 0 } );
693 shape.SetEnd( { 1000, 0 } );
694 shape.SetWidth( 20 );
696 shape.SetEndEndingLength( 400 );
697 shape.SetEndEndingWidth( 400 );
698
699 SHAPE_POLY_SET body;
700 SHAPE_POLY_SET withEnding;
701
702 shape.TransformShapeToPolygon( body, 0, 1, ERROR_OUTSIDE );
703 shape.TransformShapeToPolygon( withEnding, 0, 1, ERROR_OUTSIDE );
704 shape.TransformLineEndingsToPolygon( withEnding, 0, 1, ERROR_OUTSIDE, shape.GetWidth() );
705
706 BOX2I bodyBBox = body.BBox();
707 BOX2I endingBBox = withEnding.BBox();
708
709 BOOST_CHECK( endingBBox.GetRight() > bodyBBox.GetRight() );
710 BOOST_CHECK( endingBBox.GetTop() < bodyBBox.GetTop() );
711 BOOST_CHECK( endingBBox.GetBottom() > bodyBBox.GetBottom() );
712}
713
714
715BOOST_AUTO_TEST_CASE( EffectiveShapesShortenArrowBody )
716{
718 shape.SetStart( { 0, 0 } );
719 shape.SetEnd( { 1000, 0 } );
720 shape.SetWidth( 20 );
722 shape.SetEndEndingLength( 200 );
723 shape.SetEndEndingWidth( 200 );
724
725 std::vector<SHAPE*> shapes = shape.MakeEffectiveShapesWithLineEndings( shape.GetWidth() );
726 const SHAPE_SEGMENT* body = nullptr;
727
728 for( SHAPE* shapePtr : shapes )
729 {
730 if( const SHAPE_SEGMENT* segment = dynamic_cast<const SHAPE_SEGMENT*>( shapePtr ) )
731 {
732 body = segment;
733 break;
734 }
735 }
736
737 BOOST_REQUIRE( body );
738 checkVectorEqual( body->GetStart(), { 0, 0 } );
739 checkVectorEqual( body->GetEnd(), { 800, 0 } );
740
741 deleteShapes( shapes );
742}
743
744
745BOOST_AUTO_TEST_CASE( StrokingShapesShortenArrowBodyOnly )
746{
748 shape.SetStart( { 0, 0 } );
749 shape.SetEnd( { 1000, 0 } );
750 shape.SetWidth( 20 );
752 shape.SetEndEndingLength( 200 );
753 shape.SetEndEndingWidth( 200 );
754
755 std::vector<SHAPE*> shapes = shape.MakeEffectiveShapesForStroking( shape.GetWidth() );
756
757 BOOST_REQUIRE_EQUAL( shapes.size(), 1 );
758
759 const SHAPE_SEGMENT* body = dynamic_cast<const SHAPE_SEGMENT*>( shapes.front() );
760 BOOST_REQUIRE( body );
761 checkVectorEqual( body->GetStart(), { 0, 0 } );
762 checkVectorEqual( body->GetEnd(), { 800, 0 } );
763
764 deleteShapes( shapes );
765}
766
767
768BOOST_AUTO_TEST_CASE( StrokingShapesRejectConsumedSegmentBody )
769{
771 shape.SetStart( { 0, 0 } );
772 shape.SetEnd( { 1000, 0 } );
773 shape.SetWidth( 20 );
775 shape.SetStartEndingLength( 600 );
776 shape.SetStartEndingWidth( 200 );
778 shape.SetEndEndingLength( 500 );
779 shape.SetEndEndingWidth( 200 );
780
781 std::vector<SHAPE*> shapes = shape.MakeEffectiveShapesForStroking( shape.GetWidth() );
782
783 BOOST_CHECK( shapes.empty() );
784
785 deleteShapes( shapes );
786}
787
788
789BOOST_AUTO_TEST_CASE( EffectiveShapesHitEndpointSquare )
790{
792 shape.SetStart( { 0, 0 } );
793 shape.SetEnd( { 1000, 0 } );
794 shape.SetWidth( 100 );
796 shape.SetStartEndingLength( 400 );
797 shape.SetStartEndingWidth( 400 );
798
799 std::vector<SHAPE*> shapes = shape.MakeEffectiveShapesWithLineEndings( shape.GetWidth() );
800 SHAPE_SEGMENT probe( VECTOR2I( 0, -150 ), VECTOR2I( 0, 150 ), 20 );
801 bool collides = false;
802
803 for( SHAPE* shapePtr : shapes )
804 {
805 if( shapePtr->Collide( probe.GetSeg(), 0 ) )
806 {
807 collides = true;
808 break;
809 }
810 }
811
812 BOOST_CHECK( collides );
813
814 deleteShapes( shapes );
815}
816
817
818BOOST_AUTO_TEST_CASE( PolygonConversionShortensArrowBody )
819{
821 shape.SetStart( { 0, 0 } );
822 shape.SetEnd( { 1000, 0 } );
823 shape.SetWidth( 100 );
825 shape.SetEndEndingLength( 200 );
826 shape.SetEndEndingWidth( 200 );
827
828 SHAPE_POLY_SET unshortenedBodyPlusEnding;
829 SHAPE_POLY_SET shortenedBodyPlusEnding;
830
831 shape.TransformShapeToPolygon( unshortenedBodyPlusEnding, 0, 1, ERROR_OUTSIDE );
832 shape.TransformLineEndingsToPolygon( unshortenedBodyPlusEnding, 0, 1, ERROR_OUTSIDE, shape.GetWidth() );
833 shape.TransformWithLineEndingsToPolygon( shortenedBodyPlusEnding, 0, 1, ERROR_OUTSIDE );
834
835 BOOST_CHECK_GT( unshortenedBodyPlusEnding.BBox().GetRight(), 1000 );
836 BOOST_CHECK_LE( shortenedBodyPlusEnding.BBox().GetRight(), 1001 );
837}
838
839
840BOOST_AUTO_TEST_CASE( ShortenArcForEndingsKeepsPositiveSweep )
841{
844 shape.SetStartEndingLength( 100 );
845 shape.SetStartEndingWidth( 100 );
847 shape.SetEndEndingLength( 200 );
848 shape.SetEndEndingWidth( 100 );
849
850 EDA_ANGLE startAngle( 10.0, DEGREES_T );
851 EDA_ANGLE arcAngle( 90.0, DEGREES_T );
852 constexpr double radius = 1000.0;
853 constexpr double radToDeg = 180.0 / M_PI;
854
855 BOOST_CHECK( shape.ShortenArcForEndings( startAngle, arcAngle, radius, 20 ) );
856 checkAngleClose( startAngle, 10.0 + 100.0 / radius * radToDeg );
857 checkAngleClose( arcAngle, 90.0 - 300.0 / radius * radToDeg );
858 BOOST_CHECK( arcAngle > ANGLE_0 );
859}
860
861
862BOOST_AUTO_TEST_CASE( ShortenArcForEndingsKeepsNegativeSweep )
863{
866 shape.SetStartEndingLength( 100 );
867 shape.SetStartEndingWidth( 100 );
869 shape.SetEndEndingLength( 200 );
870 shape.SetEndEndingWidth( 100 );
871
872 EDA_ANGLE startAngle( 170.0, DEGREES_T );
873 EDA_ANGLE arcAngle( -90.0, DEGREES_T );
874 constexpr double radius = 1000.0;
875 constexpr double radToDeg = 180.0 / M_PI;
876
877 BOOST_CHECK( shape.ShortenArcForEndings( startAngle, arcAngle, radius, 20 ) );
878 checkAngleClose( startAngle, 170.0 - 100.0 / radius * radToDeg );
879 checkAngleClose( arcAngle, -90.0 + 300.0 / radius * radToDeg );
880 BOOST_CHECK( arcAngle < ANGLE_0 );
881}
882
883
884BOOST_AUTO_TEST_CASE( ArcShorteningRejectsOverrun )
885{
888 shape.SetStartEndingLength( 100 );
889 shape.SetStartEndingWidth( 100 );
891 shape.SetEndEndingLength( 100 );
892 shape.SetEndEndingWidth( 100 );
893
894 EDA_ANGLE startAngle( 30.0, DEGREES_T );
895 EDA_ANGLE arcAngle( 10.0, DEGREES_T );
896
897 BOOST_CHECK( !shape.ShortenArcForEndings( startAngle, arcAngle, 1000.0, 20 ) );
898 checkAngleClose( startAngle, 30.0 );
899 checkAngleClose( arcAngle, 0.0 );
900}
901
902
903BOOST_AUTO_TEST_CASE( BezierNoEndingsReturnsSource )
904{
906 shape.SetStart( { 0, 0 } );
907 shape.SetBezierC1( { 300, 0 } );
908 shape.SetBezierC2( { 700, 0 } );
909 shape.SetEnd( { 1000, 0 } );
910
911 std::optional<BEZIER<double>> curve = shape.ShortenedBezierCurve( 20 );
912
913 BOOST_REQUIRE( curve );
914 checkVectorClose( curve->Start, { 0, 0 } );
915 checkVectorClose( curve->C1, { 300, 0 } );
916 checkVectorClose( curve->C2, { 700, 0 } );
917 checkVectorClose( curve->End, { 1000, 0 } );
918}
919
920
921BOOST_AUTO_TEST_CASE( BezierCurveTrimsBothEnds )
922{
924 shape.SetStart( { 0, 0 } );
925 shape.SetBezierC1( { 300, 0 } );
926 shape.SetBezierC2( { 700, 0 } );
927 shape.SetEnd( { 1000, 0 } );
929 shape.SetStartEndingLength( 100 );
930 shape.SetStartEndingWidth( 100 );
932 shape.SetEndEndingLength( 200 );
933 shape.SetEndEndingWidth( 100 );
934
935 std::optional<BEZIER<double>> curve = shape.ShortenedBezierCurve( 20 );
936
937 BOOST_REQUIRE( curve );
938 checkVectorClose( curve->Start, { 100, 0 }, 1e-3 );
939 checkVectorClose( curve->End, { 800, 0 }, 1e-3 );
940}
941
942
943BOOST_AUTO_TEST_CASE( BezierCurveRejectsOverrun )
944{
946 shape.SetStart( { 0, 0 } );
947 shape.SetBezierC1( { 300, 0 } );
948 shape.SetBezierC2( { 700, 0 } );
949 shape.SetEnd( { 1000, 0 } );
951 shape.SetStartEndingLength( 600 );
952 shape.SetStartEndingWidth( 100 );
954 shape.SetEndEndingLength( 500 );
955 shape.SetEndEndingWidth( 100 );
956
957 BOOST_CHECK( !shape.ShortenedBezierCurve( 20 ) );
958}
959
960
961BOOST_AUTO_TEST_CASE( ShortenedBezierPolylineUsesShortenedCurve )
962{
964 shape.SetStart( { 0, 0 } );
965 shape.SetBezierC1( { 300, 0 } );
966 shape.SetBezierC2( { 700, 0 } );
967 shape.SetEnd( { 1000, 0 } );
969 shape.SetStartEndingLength( 100 );
970 shape.SetStartEndingWidth( 100 );
972 shape.SetEndEndingLength( 200 );
973 shape.SetEndEndingWidth( 100 );
974
975 std::vector<VECTOR2D> points = shape.ShortenedBezierPolyline( 20 );
976
977 BOOST_REQUIRE_GE( points.size(), 2 );
978 checkVectorClose( points.front(), { 100, 0 }, 1e-3 );
979 checkVectorClose( points.back(), { 800, 0 }, 1e-3 );
980}
981
982
983BOOST_AUTO_TEST_CASE( BezierPolylineRejectsOverrun )
984{
986 shape.SetStart( { 0, 0 } );
987 shape.SetBezierC1( { 300, 0 } );
988 shape.SetBezierC2( { 700, 0 } );
989 shape.SetEnd( { 1000, 0 } );
991 shape.SetStartEndingLength( 600 );
992 shape.SetStartEndingWidth( 100 );
994 shape.SetEndEndingLength( 500 );
995 shape.SetEndEndingWidth( 100 );
996
997 BOOST_CHECK( shape.ShortenedBezierPolyline( 20 ).empty() );
998}
999
1000
1001// Minimal concrete RENDER_SETTINGS; STROKE_PARAMS::Stroke only reads the dash/gap ratios.
1003{
1004public:
1005 KIGFX::COLOR4D GetColor( const KIGFX::VIEW_ITEM* aItem, int aLayer ) const override
1006 {
1007 return KIGFX::COLOR4D::BLACK;
1008 }
1009
1010 const KIGFX::COLOR4D& GetBackgroundColor() const override { return m_color; }
1011 void SetBackgroundColor( const KIGFX::COLOR4D& aColor ) override { m_color = aColor; }
1012 const KIGFX::COLOR4D& GetGridColor() override { return m_color; }
1013 const KIGFX::COLOR4D& GetCursorColor() override { return m_color; }
1014
1015private:
1017};
1018
1019
1020// A dashed Bezier with endings must still be stroked as ONE chain: loose segments would
1021// restart the dash pattern at every tessellation vertex (issue #25110) and draw near-solid.
1022BOOST_AUTO_TEST_CASE( StrokingShortenedBezierDashes )
1023{
1025 shape.SetStart( { 0, 0 } );
1026 shape.SetBezierC1( { 30000, 20000 } );
1027 shape.SetBezierC2( { 70000, -20000 } );
1028 shape.SetEnd( { 100000, 0 } );
1029 shape.SetWidth( 100 );
1031 shape.SetStartEndingLength( 2000 );
1032 shape.SetStartEndingWidth( 1000 );
1034 shape.SetEndEndingLength( 3000 );
1035 shape.SetEndEndingWidth( 1000 );
1036
1037 std::vector<SHAPE*> shapes = shape.MakeEffectiveShapesForStroking( shape.GetWidth() );
1038
1039 // The shortened body must come back as a single chain.
1040 BOOST_REQUIRE_EQUAL( shapes.size(), 1 );
1041
1042 const SHAPE_LINE_CHAIN* chain = dynamic_cast<const SHAPE_LINE_CHAIN*>( shapes.front() );
1044 BOOST_REQUIRE_GE( chain->SegmentCount(), 2 );
1045
1046 double chainLength = 0.0;
1047
1048 for( int ii = 0; ii < chain->SegmentCount(); ++ii )
1049 chainLength += VECTOR2D( chain->CSegment( ii ).B - chain->CSegment( ii ).A ).EuclideanNorm();
1050
1051 // Stroke it dashed and glue touching pieces back into runs (a dash spanning a vertex
1052 // arrives as several touching pieces).
1054 std::vector<SEG> pieces;
1055
1057 [&pieces]( const VECTOR2I& a, const VECTOR2I& b )
1058 {
1059 pieces.emplace_back( a, b );
1060 } );
1061
1062 std::vector<double> runs;
1063 double drawn = 0.0;
1064
1065 for( size_t ii = 0; ii < pieces.size(); )
1066 {
1067 double length = VECTOR2D( pieces[ii].B - pieces[ii].A ).EuclideanNorm();
1068 VECTOR2I end = pieces[ii].B;
1069 size_t jj = ii + 1;
1070
1071 while( jj < pieces.size() && pieces[jj].A == end )
1072 {
1073 length += VECTOR2D( pieces[jj].B - pieces[jj].A ).EuclideanNorm();
1074 end = pieces[jj].B;
1075 jj++;
1076 }
1077
1078 runs.push_back( length );
1079 drawn += length;
1080 ii = jj;
1081 }
1082
1083 BOOST_REQUIRE_MESSAGE( runs.size() > 5, "expected many dashes, got " << runs.size() << " run(s)" );
1084
1085 // If the pattern restarted at every tessellation vertex the curve would come out nearly
1086 // solid; the drawn length must instead match the dash/gap duty cycle.
1087 double dash = settings.GetDashLength( shape.GetWidth() );
1088 double gap = settings.GetGapLength( shape.GetWidth() );
1089 double expected = chainLength * dash / ( dash + gap );
1090
1091 BOOST_CHECK_MESSAGE( std::abs( drawn - expected ) < 0.05 * chainLength,
1092 "dashes should cover " << expected << " IU of the " << chainLength
1093 << " IU shortened body, they cover " << drawn );
1094
1095 deleteShapes( shapes );
1096}
1097
1098
1099BOOST_AUTO_TEST_CASE( PolyShorteningTrimsEnds )
1100{
1103 shape.SetStartEndingLength( 100 );
1104 shape.SetStartEndingWidth( 100 );
1106 shape.SetEndEndingLength( 200 );
1107 shape.SetEndEndingWidth( 200 );
1108
1109 VECTOR2D first( 0, 0 );
1110 VECTOR2D second( 1000, 0 );
1111 VECTOR2D penultimate( 1000, 1000 );
1112 VECTOR2D last( 1000, 2000 );
1113
1114 BOOST_CHECK( shape.ShortenPolyForEndings( first, last, second, penultimate, 20 ) );
1115
1116 checkVectorClose( first, { 100, 0 } );
1117 checkVectorClose( last, { 1000, 1900 } );
1118}
1119
1120
1121BOOST_AUTO_TEST_CASE( ShortenPolyForEndingsLeavesOpenArrowBody )
1122{
1125 shape.SetStartEndingLength( 500 );
1126 shape.SetStartEndingWidth( 500 );
1127
1128 VECTOR2D first( 0, 0 );
1129 VECTOR2D second( 1000, 0 );
1130 VECTOR2D penultimate( 1000, 1000 );
1131 VECTOR2D last( 1000, 2000 );
1132
1133 BOOST_CHECK( shape.ShortenPolyForEndings( first, last, second, penultimate, 20 ) );
1134
1135 checkVectorClose( first, { 0, 0 } );
1136 checkVectorClose( last, { 1000, 2000 } );
1137}
1138
1139
1140BOOST_AUTO_TEST_CASE( PolyShorteningRejectsOverrun )
1141{
1144 shape.SetStartEndingLength( 200 );
1145 shape.SetStartEndingWidth( 100 );
1146
1147 VECTOR2D first( 0, 0 );
1148 VECTOR2D second( 100, 0 );
1149 VECTOR2D penultimate( 1000, 0 );
1150 VECTOR2D last( 1100, 0 );
1151
1152 BOOST_CHECK( !shape.ShortenPolyForEndings( first, last, second, penultimate, 20 ) );
1153 checkVectorClose( first, { 0, 0 } );
1154 checkVectorClose( last, { 0, 0 } );
1155}
1156
1157
1158BOOST_AUTO_TEST_CASE( BodyPolyPointsShortenFirstOpenOutline )
1159{
1162 shape.SetStartEndingLength( 100 );
1163 shape.SetStartEndingWidth( 100 );
1164
1165 SHAPE_LINE_CHAIN outline;
1166 outline.Append( VECTOR2I( 0, 0 ) );
1167 outline.Append( VECTOR2I( 1000, 0 ) );
1168 outline.Append( VECTOR2I( 1000, 1000 ) );
1169 outline.SetClosed( false );
1170
1171 std::vector<VECTOR2I> pts;
1172
1173 BOOST_CHECK( shape.GetShortenedBodyPolyPoints( outline, 0, pts, 20 ) );
1174 BOOST_REQUIRE_EQUAL( pts.size(), 3 );
1175 checkVectorEqual( pts.front(), { 100, 0 } );
1176 checkVectorEqual( pts.back(), { 1000, 1000 } );
1177
1178 BOOST_CHECK( shape.GetShortenedBodyPolyPoints( outline, 1, pts, 20 ) );
1179 BOOST_REQUIRE_EQUAL( pts.size(), 3 );
1180 checkVectorEqual( pts.front(), { 0, 0 } );
1181 checkVectorEqual( pts.back(), { 1000, 1000 } );
1182
1183 outline.SetClosed( true );
1184
1185 BOOST_CHECK( shape.GetShortenedBodyPolyPoints( outline, 0, pts, 20 ) );
1186 BOOST_REQUIRE_EQUAL( pts.size(), 3 );
1187 checkVectorEqual( pts.front(), { 0, 0 } );
1188 checkVectorEqual( pts.back(), { 1000, 1000 } );
1189}
1190
@ ERROR_OUTSIDE
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:130
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
double AsDegrees() const
Definition eda_angle.h:115
"Standard" polygon editing behavior for EDA_SHAPE polygons.
void MakePoints(EDIT_POINTS &aPoints) override
Construct the initial set of edit points for the item and append to the given list.
bool UpdatePoints(EDIT_POINTS &aPoints) override
Update the list of the edit points for the item.
EDA_SHAPE_MOCK(SHAPE_T aShapeType)
static bool ShortenSegmentForEndings(VECTOR2I &aStart, VECTOR2I &aEnd, const LINE_ENDING &aStartEnding, const LINE_ENDING &aEndEnding, int aLineWidth)
Shorten a segment body for line endings.
EDA_ANGLE GetArcAngle() const
virtual void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:329
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false, bool includeFill=false) const
Convert the shape to a closed polygon.
int GetEllipseMinorRadius() const
Definition eda_shape.h:395
const VECTOR2I & GetEllipseCenter() const
Definition eda_shape.h:377
bool GetLineEndingsBoundingBox(BOX2I &aBBox, int aLineWidth) const
void SetCenter(const VECTOR2I &aCenter)
VECTOR2I getCenter() const
std::vector< SHAPE * > MakeEffectiveShapesWithLineEndings(int aLineWidth) const
Make effective geometry for the shape body shortened for line endings plus the line-ending geometry i...
void SetEndEndingLength(int aLength)
Definition eda_shape.h:195
void SetStartEndingStyle(LINE_ENDING_STYLE aStyle)
Definition eda_shape.h:184
EDA_ANGLE GetEllipseEndAngle() const
Definition eda_shape.h:423
void SetEndEndingWidth(int aWidth)
Definition eda_shape.h:197
int GetEllipseMajorRadius() const
Definition eda_shape.h:386
std::vector< VECTOR2I > GetPolyPoints() const
Duplicate the polygon outlines into a flat list of VECTOR2I points.
void TransformWithLineEndingsToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the shape body shortened for line endings plus line-ending geometry to polygons.
void TransformLineEndingsToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc, int aLineWidth) const
Append only the line-ending geometry associated with this shape to a polygon set.
virtual std::vector< SHAPE * > MakeEffectiveShapes(bool aEdgeOnly=false) const
Make a set of SHAPE objects representing the EDA_SHAPE.
Definition eda_shape.h:551
bool ShortenPolyForEndings(VECTOR2D &aFirst, VECTOR2D &aLast, const VECTOR2D &aSecond, const VECTOR2D &aPenultimate, int aLineWidth) const
SHAPE_POLY_SET & GetPolyShape()
void GetEndingTangents(EDA_ANGLE &aStartTangent, EDA_ANGLE &aEndTangent, int aLineWidth=0) const
Compute outward-facing tangent angles at the start and end of the shape.
EDA_ANGLE GetEllipseRotation() const
Definition eda_shape.h:404
void SetEndEndingStyle(LINE_ENDING_STYLE aStyle)
Definition eda_shape.h:187
virtual void SetEllipseEndAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:416
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:175
std::vector< SHAPE * > MakeEffectiveShapesForStroking(int aLineWidth=-1) const
Make a set of SHAPE objects to hand to STROKE_PARAMS::Stroke().
virtual void SetBezierC2(const VECTOR2I &aPt)
Definition eda_shape.h:367
bool GetShortenedBodyPolyPoints(const SHAPE_LINE_CHAIN &aOutline, int aOutlineIdx, std::vector< VECTOR2I > &aPoints, int aLineWidth) const
Copy an outline and apply line-ending body shortening when applicable.
virtual void SetBezierC1(const VECTOR2I &aPt)
Definition eda_shape.h:364
bool GetLineEndingEndpoints(VECTOR2I &aStartPoint, VECTOR2I &aEndPoint) const
Return the source endpoints used to place line endings.
virtual void SetEllipseRotation(const EDA_ANGLE &aA)
Definition eda_shape.h:397
EDA_SHAPE(SHAPE_T aType, int aLineWidth, FILL_T aFill)
Definition eda_shape.cpp:56
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
bool IsClosed() const
virtual void SetEllipseCenter(const VECTOR2I &aPt)
Definition eda_shape.h:370
void SetStartEndingLength(int aLength)
Definition eda_shape.h:190
void SetEndEndingStrokeWidth(int aWidth)
Definition eda_shape.h:202
virtual void SetEllipseMinorRadius(int aR)
Definition eda_shape.h:388
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
virtual void SetEllipseMajorRadius(int aR)
Definition eda_shape.h:379
virtual void SetShape(SHAPE_T aShape)
Definition eda_shape.h:174
std::vector< SHAPE * > MakeLineEndingEffectiveShapes(int aLineWidth) const
Make the line-ending geometry associated with this shape.
void SetStartEndingWidth(int aWidth)
Definition eda_shape.h:192
EDA_ANGLE GetEllipseStartAngle() const
Definition eda_shape.h:414
bool EndsSwapped() const
Have the start and end points been swapped since they were set?
Definition eda_shape.h:452
virtual void SetEllipseStartAngle(const EDA_ANGLE &aA)
Definition eda_shape.h:407
virtual void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Set the three controlling points for an arc.
double GetLength() const
std::optional< BEZIER< double > > ShortenedBezierCurve(int aLineWidth) const
Return the cubic Bezier curve shortened for line endings.
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Set the end point from the angle center and start.
virtual int GetWidth() const
Definition eda_shape.h:163
bool ShortenArcForEndings(EDA_ANGLE &aStartAngle, EDA_ANGLE &aArcAngle, double aRadius, int aLineWidth) const
Shorten an arc body for line endings.
std::vector< VECTOR2D > ShortenedBezierPolyline(int aLineWidth) const
Return the flattened Bezier polyline after line-ending shortening.
virtual void SetWidth(int aWidth)
virtual void SetPolyShape(const SHAPE_POLY_SET &aShape)
Definition eda_shape.h:514
virtual void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:279
VECTOR2I GetArcMid() const
EDIT_POINTS is a VIEW_ITEM that manages EDIT_POINTs and EDIT_LINEs and draws them.
unsigned int PointsSize() const
Return number of stored EDIT_POINTs.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
static const COLOR4D BLACK
Definition color4d.h:404
Container for all the knowledge about how graphical objects are drawn on any output surface/device.
double GetGapLength(int aLineWidth) const
double GetDashLength(int aLineWidth) const
An abstract base class for deriving all objects that can be added to a VIEW.
Definition view_item.h:82
Decorative shape (arrowhead, circle, square) at the start or end of a graphic line,...
Definition line_ending.h:62
static int DefaultAccuracyForPCB()
Definition shape_arc.h:283
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
Represent a set of closed polygons.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
const SEG & GetSeg() const
VECTOR2I GetEnd() const override
VECTOR2I GetStart() const override
int GetWidth() const override
An abstract shape on 2D plane.
Definition shape.h:124
static void Stroke(const SHAPE *aShape, LINE_STYLE aLineStyle, int aWidth, const KIGFX::RENDER_SETTINGS *aRenderSettings, const std::function< void(const VECTOR2I &a, const VECTOR2I &b)> &aStroker)
const KIGFX::COLOR4D & GetGridColor() override
Return current grid color settings.
KIGFX::COLOR4D GetColor(const KIGFX::VIEW_ITEM *aItem, int aLayer) const override
Returns the color that should be used to draw the specific VIEW_ITEM on the specific layer using curr...
void SetBackgroundColor(const KIGFX::COLOR4D &aColor) override
Set the background color.
const KIGFX::COLOR4D & GetBackgroundColor() const override
Return current background color settings.
const KIGFX::COLOR4D & GetCursorColor() override
Return current cursor color settings.
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
@ DEGREES_T
Definition eda_angle.h:30
FILL_T
Definition eda_fill.h:29
@ NO_FILL
Definition eda_fill.h:30
SHAPE_T
Definition eda_shape.h:54
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ ELLIPSE_ARC
Definition eda_shape.h:63
void EditArcEndpointKeepCenter(EDA_SHAPE &aArc, const VECTOR2I &aCenter, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, const VECTOR2I &aCursor, const EDA_IU_SCALE &aIuScale)
Move an arc endpoint around the existing center, pulling the opposite endpoint along to keep the radi...
void EditArcMidKeepCenter(EDA_SHAPE &aArc, const VECTOR2I &aCenter, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, const VECTOR2I &aCursor, const EDA_IU_SCALE &aIuScale)
Move the mid point of an arc while keeping the center, rotating the endpoints onto the new radius.
bool IsWithinWrapped(T aValue, T aNominal, T aWrap, T aError)
Check if a value is within a tolerance of a nominal value, wrapping to a given val.
Definition numeric.h:39
bool IsWithin(T aValue, T aNominal, T aError)
Check if a value is within a tolerance of a nominal value.
Definition numeric.h:57
bool IsVecWithinTol(const VEC &aVec, const VEC &aExp, typename VEC::coord_type aTol)
Check that both x and y of a vector are within expected error.
Definition geometry.h:51
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
VECTOR2I m_ExpectedEndBeforeSwap
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
static void deleteShapes(std::vector< SHAPE * > &aShapes)
static void checkAngleClose(const EDA_ANGLE &aActual, double aExpectedDegrees, double aTolerance=1e-6)
static void checkAngleInRange(const EDA_ANGLE &aActual, double aMinDegrees, double aMaxDegrees)
static void checkVectorClose(const VECTOR2D &aActual, const VECTOR2D &aExpected, double aTolerance=1e-6)
static const std::vector< SET_ARC_GEOMETRY_CASE > set_arc_geometry_cases
BOOST_AUTO_TEST_CASE(SetAngleAndEnd)
static void checkVectorEqual(const VECTOR2I &aActual, const VECTOR2I &aExpected)
static const std::vector< SET_ANGLE_END_CASE > set_angle_end_cases
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
SHAPE_ARC arc2(c.m_arc2.GenerateArc())
VECTOR2I center
const SHAPE_LINE_CHAIN chain
int radius
BOOST_CHECK_PREDICATE(ArePolylineEndPointsNearCircle,(chain)(c.m_geom.m_center_point)(radius)(accuracy+epsilon))
VECTOR2I end
BOOST_CHECK_EQUAL(result, "25.4")
#define M_PI
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707