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
225BOOST_AUTO_TEST_CASE( ArcEditKeepsSmallSchematicRadius )
226{
227 // 50 mil radius arc in schematic IUs, well under the buggy 100 mil floor.
228 const int radius = schIUScale.MilsToIU( 50 );
229 const VECTOR2I center( 0, 0 );
230 const VECTOR2I start( radius, 0 );
231 const VECTOR2I end( 0, radius );
232 const VECTOR2I mid( KiROUND( radius / std::sqrt( 2.0 ) ),
233 KiROUND( radius / std::sqrt( 2.0 ) ) );
234
236 arc.SetArcGeometry( start, mid, end );
237
238 BOOST_REQUIRE_LT( arc.GetRadius(), schIUScale.MilsToIU( 100 ) );
239
240 // Drag the endpoint a few IU; with the bug the radius snaps up to 100 mil.
241 const VECTOR2I newEnd( 5, radius );
242
243 KI_ARC_EDIT::EditArcEndpointKeepCenter( arc, center, start, mid, newEnd, newEnd, schIUScale );
244 BOOST_CHECK_LT( arc.GetRadius(), schIUScale.MilsToIU( 100 ) );
245
246 // Same for the mid-point helper, which has its own minimum-radius clamp.
247 const VECTOR2I smallerMid( KiROUND( ( radius - 100 ) / std::sqrt( 2.0 ) ),
248 KiROUND( ( radius - 100 ) / std::sqrt( 2.0 ) ) );
249
251 arc2.SetArcGeometry( start, mid, end );
252
253 KI_ARC_EDIT::EditArcMidKeepCenter( arc2, center, start, mid, end, smallerMid, schIUScale );
254 BOOST_CHECK_LT( arc2.GetRadius(), schIUScale.MilsToIU( 100 ) );
255}
256
257
265BOOST_AUTO_TEST_CASE( PolygonBehaviorSurvivesAssignment )
266{
268
269 SHAPE_POLY_SET& poly = shape.GetPolyShape();
270 poly.NewOutline();
271 poly.Append( { 0, 0 } );
272 poly.Append( { 1000000, 0 } );
273 poly.Append( { 1000000, 1000000 } );
274
275 EDA_POLYGON_POINT_EDIT_BEHAVIOR behavior( shape );
276
277 EDIT_POINTS points( nullptr );
278 behavior.MakePoints( points );
279 BOOST_CHECK_EQUAL( points.PointsSize(), 3u );
280
281 EDA_SHAPE_MOCK copy( shape );
282 shape = copy;
283
284 // After assignment, shape.m_poly is a fresh allocation.
285 // The behavior must still work (not use-after-free).
286 EDIT_POINTS points2( nullptr );
287 behavior.MakePoints( points2 );
288 BOOST_CHECK_EQUAL( points2.PointsSize(), 3u );
289
290 BOOST_CHECK( behavior.UpdatePoints( points ) );
291}
292
293
294BOOST_AUTO_TEST_CASE( PolygonBehaviorSignalsRebuildWhenShapeMorphs )
295{
297
298 SHAPE_POLY_SET& poly = shape.GetPolyShape();
299 poly.NewOutline();
300 poly.Append( { 0, 0 } );
301 poly.Append( { 1000000, 0 } );
302 poly.Append( { 1000000, 1000000 } );
303 poly.Append( { 0, 1000000 } );
304
305 EDA_POLYGON_POINT_EDIT_BEHAVIOR behavior( shape );
306
307 EDIT_POINTS points( nullptr );
308 behavior.MakePoints( points );
309 BOOST_CHECK( behavior.UpdatePoints( points ) );
310
312 shape.SetStart( { 0, 0 } );
313 shape.SetEnd( { 1000000, 1000000 } );
314
315 BOOST_CHECK( !behavior.UpdatePoints( points ) );
316}
317
318
319BOOST_AUTO_TEST_CASE( GetPolyPointsPreservesOrderAcrossOutlines )
320{
321 // GetPolyPoints flattens every outline of the poly shape into a single
322 // ordered vector. Verify the count and order are preserved across multiple
323 // outlines so the single up-front reserve does not alter behavior.
325
326 SHAPE_POLY_SET& poly = shape.GetPolyShape();
327
328 poly.NewOutline();
329 poly.Append( { 0, 0 } );
330 poly.Append( { 1000, 0 } );
331 poly.Append( { 1000, 1000 } );
332
333 poly.NewOutline();
334 poly.Append( { 5000, 5000 } );
335 poly.Append( { 6000, 5000 } );
336
337 const std::vector<VECTOR2I> expected = {
338 { 0, 0 }, { 1000, 0 }, { 1000, 1000 }, { 5000, 5000 }, { 6000, 5000 }
339 };
340
341 const std::vector<VECTOR2I> points = shape.GetPolyPoints();
342
343 BOOST_REQUIRE_EQUAL( points.size(), expected.size() );
344
345 for( size_t ii = 0; ii < expected.size(); ++ii )
346 {
347 BOOST_CHECK_EQUAL( points[ii].x, expected[ii].x );
348 BOOST_CHECK_EQUAL( points[ii].y, expected[ii].y );
349 }
350}
351
352
353BOOST_AUTO_TEST_CASE( EllipseBasicAccessors )
354{
355 // Construct a closed ellipse EDA_SHAPE and round-trip every accessor.
357 e.SetEllipseCenter( VECTOR2I( 100, 200 ) );
358 e.SetEllipseMajorRadius( 500 );
359 e.SetEllipseMinorRadius( 300 );
361
362 BOOST_CHECK( e.GetShape() == SHAPE_T::ELLIPSE );
367 BOOST_CHECK_CLOSE( e.GetEllipseRotation().AsDegrees(), 30.0, 1e-6 );
368
369 // Closed ellipse reports itself as a closed shape.
370 BOOST_CHECK( e.IsClosed() );
371}
372
373
374BOOST_AUTO_TEST_CASE( EllipseArcIsOpenCurve )
375{
376 // Elliptical arcs are open
377 // IsClosed() must return false.
379 arc.SetEllipseCenter( VECTOR2I( 0, 0 ) );
380 arc.SetEllipseMajorRadius( 500 );
381 arc.SetEllipseMinorRadius( 300 );
384 arc.SetEllipseEndAngle( EDA_ANGLE( 180.0, DEGREES_T ) );
385
386 BOOST_CHECK( arc.GetShape() == SHAPE_T::ELLIPSE_ARC );
387 BOOST_CHECK( !arc.IsClosed() );
388
389 // Start/end angles round trip through the accessors.
390 BOOST_CHECK_CLOSE( arc.GetEllipseStartAngle().AsDegrees(), 0.0, 1e-6 );
391 BOOST_CHECK_CLOSE( arc.GetEllipseEndAngle().AsDegrees(), 180.0, 1e-6 );
392}
393
394
395BOOST_AUTO_TEST_CASE( EllipsePerimeterForCircleCase )
396{
397 // An ellipse with MajorRadius == MinorRadius is a circle.
398 // Ramanujan's approximation returns 2Ï€r for this case.
400 e.SetEllipseCenter( VECTOR2I( 0, 0 ) );
401 e.SetEllipseMajorRadius( 1000 );
402 e.SetEllipseMinorRadius( 1000 );
404
405 const double expected = 2.0 * M_PI * 1000.0;
406 BOOST_CHECK_CLOSE( e.GetLength(), expected, 1e-6 );
407}
408
409
410BOOST_AUTO_TEST_CASE( EllipseMakeEffectiveShapesNonEmpty )
411{
412 // MakeEffectiveShapes converts the ellipse into primitive shapes that DRC
413 // the router, and exporters consume. Verify it returns at least one shape
414
416 e.SetEllipseCenter( VECTOR2I( 0, 0 ) );
417 e.SetEllipseMajorRadius( 500 );
418 e.SetEllipseMinorRadius( 300 );
420
421 std::vector<SHAPE*> shapes = e.MakeEffectiveShapes();
422 BOOST_CHECK( !shapes.empty() );
423
424 for( SHAPE* s : shapes )
425 delete s;
426}
427
428
429BOOST_AUTO_TEST_CASE( ShortenSegmentForEndingsTrimsStartAndEnd )
430{
431 VECTOR2I start( 0, 0 );
432 VECTOR2I end( 1000, 0 );
433 LINE_ENDING startEnding( LINE_ENDING_STYLE::ARROW, 100, 100 );
434 LINE_ENDING endEnding( LINE_ENDING_STYLE::CIRCLE, 200, 200 );
435
436 BOOST_CHECK( EDA_SHAPE::ShortenSegmentForEndings( start, end, startEnding, endEnding, 20 ) );
437 BOOST_CHECK_EQUAL( start.x, 100 );
438 BOOST_CHECK_EQUAL( start.y, 0 );
439 BOOST_CHECK_EQUAL( end.x, 900 );
440 BOOST_CHECK_EQUAL( end.y, 0 );
441}
442
443
444BOOST_AUTO_TEST_CASE( SegmentOpenArrowLeavesBody )
445{
446 VECTOR2I start( 0, 0 );
447 VECTOR2I end( 1000, 0 );
448 LINE_ENDING startEnding( LINE_ENDING_STYLE::ARROW_OPEN, 500, 500 );
449 LINE_ENDING endEnding;
450
451 BOOST_CHECK( EDA_SHAPE::ShortenSegmentForEndings( start, end, startEnding, endEnding, 20 ) );
452 BOOST_CHECK_EQUAL( start.x, 0 );
453 BOOST_CHECK_EQUAL( start.y, 0 );
454 BOOST_CHECK_EQUAL( end.x, 1000 );
455 BOOST_CHECK_EQUAL( end.y, 0 );
456}
457
458
459BOOST_AUTO_TEST_CASE( SegmentShorteningRejectsOverrun )
460{
461 VECTOR2I start( 0, 0 );
462 VECTOR2I end( 1000, 0 );
463 LINE_ENDING startEnding( LINE_ENDING_STYLE::ARROW, 600, 100 );
464 LINE_ENDING endEnding( LINE_ENDING_STYLE::ARROW, 500, 100 );
465
466 BOOST_CHECK( !EDA_SHAPE::ShortenSegmentForEndings( start, end, startEnding, endEnding, 20 ) );
467 BOOST_CHECK_EQUAL( start.x, 0 );
468 BOOST_CHECK_EQUAL( start.y, 0 );
469 BOOST_CHECK_EQUAL( end.x, 0 );
470 BOOST_CHECK_EQUAL( end.y, 0 );
471}
472
473
474BOOST_AUTO_TEST_CASE( SegmentEndingTangentsPointOutward )
475{
477 shape.SetStart( { 0, 0 } );
478 shape.SetEnd( { 1000, 0 } );
479
480 EDA_ANGLE startTangent;
481 EDA_ANGLE endTangent;
482 shape.GetEndingTangents( startTangent, endTangent, 20 );
483
484 checkAngleClose( startTangent, 180.0 );
485 checkAngleClose( endTangent, 0.0 );
486}
487
488
489BOOST_AUTO_TEST_CASE( OpenArrowBezierTangentsUseDepth )
490{
492 shape.SetStart( { 0, 0 } );
493 shape.SetBezierC1( { 1000, 0 } );
494 shape.SetBezierC2( { 0, 1000 } );
495 shape.SetEnd( { 1000, 1000 } );
497 shape.SetStartEndingLength( 300 );
498 shape.SetStartEndingWidth( 300 );
500 shape.SetEndEndingLength( 300 );
501 shape.SetEndEndingWidth( 300 );
502
503 EDA_ANGLE startTangent;
504 EDA_ANGLE endTangent;
505 shape.GetEndingTangents( startTangent, endTangent, 20 );
506
507 checkAngleInRange( startTangent, -180.0, -90.0 );
508 checkAngleInRange( endTangent, 0.0, 90.0 );
509}
510
511
512BOOST_AUTO_TEST_CASE( LineEndingEndpointsUseShapeOwnedSources )
513{
514 VECTOR2I start;
516
518 segment.SetStart( { 0, 10 } );
519 segment.SetEnd( { 100, 20 } );
520
521 BOOST_CHECK( segment.GetLineEndingEndpoints( start, end ) );
522 checkVectorEqual( start, { 0, 10 } );
523 checkVectorEqual( end, { 100, 20 } );
524
526 arc.SetStart( { 10, 0 } );
527 arc.SetEnd( { 20, 30 } );
528
529 BOOST_CHECK( arc.GetLineEndingEndpoints( start, end ) );
530 checkVectorEqual( start, { 10, 0 } );
531 checkVectorEqual( end, { 20, 30 } );
532
534 bezier.SetStart( { 0, 0 } );
535 bezier.SetBezierC1( { 20, 0 } );
536 bezier.SetBezierC2( { 80, 100 } );
537 bezier.SetEnd( { 100, 100 } );
538 bezier.RebuildBezierToSegmentsPointsList( 1 );
539
540 BOOST_CHECK( bezier.GetLineEndingEndpoints( start, end ) );
541 checkVectorEqual( start, { 0, 0 } );
542 checkVectorEqual( end, { 100, 100 } );
543
544 EDA_SHAPE_MOCK polyShape( SHAPE_T::POLY );
545 SHAPE_POLY_SET poly;
546 poly.NewOutline();
547 poly.Outline( 0 ).SetClosed( false );
548 poly.Append( { 5, 5 } );
549 poly.Append( { 50, 10 } );
550 poly.Append( { 100, 20 } );
551 polyShape.SetPolyShape( poly );
552
553 BOOST_CHECK( polyShape.GetLineEndingEndpoints( start, end ) );
554 checkVectorEqual( start, { 5, 5 } );
555 checkVectorEqual( end, { 100, 20 } );
556}
557
558
559BOOST_AUTO_TEST_CASE( LineEndingEndpointsRejectInvalidPolys )
560{
561 VECTOR2I start;
563
564 EDA_SHAPE_MOCK emptyPoly( SHAPE_T::POLY );
565 BOOST_CHECK( !emptyPoly.GetLineEndingEndpoints( start, end ) );
566
567 EDA_SHAPE_MOCK onePointPoly( SHAPE_T::POLY );
568 SHAPE_POLY_SET poly;
569 poly.NewOutline();
570 poly.Append( { 5, 5 } );
571 onePointPoly.SetPolyShape( poly );
572
573 BOOST_CHECK( !onePointPoly.GetLineEndingEndpoints( start, end ) );
574}
575
576
577BOOST_AUTO_TEST_CASE( EffectiveShapesIncludeClosedEnding )
578{
580 shape.SetStart( { 0, 0 } );
581 shape.SetEnd( { 1000, 0 } );
583 shape.SetEndEndingLength( 400 );
584 shape.SetEndEndingWidth( 400 );
585
586 std::vector<SHAPE*> shapes = shape.MakeLineEndingEffectiveShapes( 20 );
587
588 BOOST_REQUIRE_EQUAL( shapes.size(), 1 );
589
590 BOX2I bbox = shapes[0]->BBox();
591 BOOST_CHECK( bbox.GetLeft() <= 800 );
592 BOOST_CHECK( bbox.GetRight() >= 1200 );
593 BOOST_CHECK( bbox.GetTop() <= -200 );
594 BOOST_CHECK( bbox.GetBottom() >= 200 );
595
596 deleteShapes( shapes );
597}
598
599
600BOOST_AUTO_TEST_CASE( EffectiveShapesIncludeOpenArrowLegs )
601{
603 shape.SetStart( { 0, 0 } );
604 shape.SetEnd( { 1000, 0 } );
606 shape.SetEndEndingLength( 300 );
607 shape.SetEndEndingWidth( 200 );
608 shape.SetEndEndingStrokeWidth( 70 );
609
610 std::vector<SHAPE*> shapes = shape.MakeLineEndingEffectiveShapes( 20 );
611
612 BOOST_REQUIRE_EQUAL( shapes.size(), 2 );
613
614 for( SHAPE* shapePtr : shapes )
615 {
616 const SHAPE_SEGMENT* leg = dynamic_cast<const SHAPE_SEGMENT*>( shapePtr );
617 BOOST_REQUIRE( leg );
618 BOOST_CHECK_EQUAL( leg->GetWidth(), 70 );
619 }
620
621 deleteShapes( shapes );
622}
623
624
625BOOST_AUTO_TEST_CASE( BBoxIncludesCenteredEnding )
626{
628 shape.SetStart( { 0, 0 } );
629 shape.SetEnd( { 1000, 0 } );
631 shape.SetEndEndingLength( 400 );
632 shape.SetEndEndingWidth( 400 );
633
634 BOX2I bbox;
635
636 BOOST_CHECK( shape.GetLineEndingsBoundingBox( bbox, 20 ) );
637 BOOST_CHECK( bbox.GetLeft() <= 800 );
638 BOOST_CHECK( bbox.GetRight() >= 1200 );
639 BOOST_CHECK( bbox.GetTop() <= -200 );
640 BOOST_CHECK( bbox.GetBottom() >= 200 );
641}
642
643
644BOOST_AUTO_TEST_CASE( BBoxUsesRotatedEndingGeometry )
645{
647 shape.SetStart( { 0, 0 } );
648 shape.SetEnd( { 1000, 1000 } );
650 shape.SetEndEndingLength( 400 );
651 shape.SetEndEndingWidth( 400 );
652
653 BOX2I bbox;
654
655 BOOST_CHECK( shape.GetLineEndingsBoundingBox( bbox, 20 ) );
656 BOOST_CHECK( bbox.GetLeft() <= 718 );
657 BOOST_CHECK( bbox.GetRight() >= 1282 );
658 BOOST_CHECK( bbox.GetTop() <= 718 );
659 BOOST_CHECK( bbox.GetBottom() >= 1282 );
660}
661
662
663BOOST_AUTO_TEST_CASE( PolygonConversionAppendsEndings )
664{
666 shape.SetStart( { 0, 0 } );
667 shape.SetEnd( { 1000, 0 } );
668 shape.SetWidth( 20 );
670 shape.SetEndEndingLength( 400 );
671 shape.SetEndEndingWidth( 400 );
672
673 SHAPE_POLY_SET body;
674 SHAPE_POLY_SET withEnding;
675
676 shape.TransformShapeToPolygon( body, 0, 1, ERROR_OUTSIDE );
677 shape.TransformShapeToPolygon( withEnding, 0, 1, ERROR_OUTSIDE );
678 shape.TransformLineEndingsToPolygon( withEnding, 0, 1, ERROR_OUTSIDE, shape.GetWidth() );
679
680 BOX2I bodyBBox = body.BBox();
681 BOX2I endingBBox = withEnding.BBox();
682
683 BOOST_CHECK( endingBBox.GetRight() > bodyBBox.GetRight() );
684 BOOST_CHECK( endingBBox.GetTop() < bodyBBox.GetTop() );
685 BOOST_CHECK( endingBBox.GetBottom() > bodyBBox.GetBottom() );
686}
687
688
689BOOST_AUTO_TEST_CASE( EffectiveShapesShortenArrowBody )
690{
692 shape.SetStart( { 0, 0 } );
693 shape.SetEnd( { 1000, 0 } );
694 shape.SetWidth( 20 );
696 shape.SetEndEndingLength( 200 );
697 shape.SetEndEndingWidth( 200 );
698
699 std::vector<SHAPE*> shapes = shape.MakeEffectiveShapesWithLineEndings( shape.GetWidth() );
700 const SHAPE_SEGMENT* body = nullptr;
701
702 for( SHAPE* shapePtr : shapes )
703 {
704 if( const SHAPE_SEGMENT* segment = dynamic_cast<const SHAPE_SEGMENT*>( shapePtr ) )
705 {
706 body = segment;
707 break;
708 }
709 }
710
711 BOOST_REQUIRE( body );
712 checkVectorEqual( body->GetStart(), { 0, 0 } );
713 checkVectorEqual( body->GetEnd(), { 800, 0 } );
714
715 deleteShapes( shapes );
716}
717
718
719BOOST_AUTO_TEST_CASE( StrokingShapesShortenArrowBodyOnly )
720{
722 shape.SetStart( { 0, 0 } );
723 shape.SetEnd( { 1000, 0 } );
724 shape.SetWidth( 20 );
726 shape.SetEndEndingLength( 200 );
727 shape.SetEndEndingWidth( 200 );
728
729 std::vector<SHAPE*> shapes = shape.MakeEffectiveShapesForStroking( shape.GetWidth() );
730
731 BOOST_REQUIRE_EQUAL( shapes.size(), 1 );
732
733 const SHAPE_SEGMENT* body = dynamic_cast<const SHAPE_SEGMENT*>( shapes.front() );
734 BOOST_REQUIRE( body );
735 checkVectorEqual( body->GetStart(), { 0, 0 } );
736 checkVectorEqual( body->GetEnd(), { 800, 0 } );
737
738 deleteShapes( shapes );
739}
740
741
742BOOST_AUTO_TEST_CASE( StrokingShapesRejectConsumedSegmentBody )
743{
745 shape.SetStart( { 0, 0 } );
746 shape.SetEnd( { 1000, 0 } );
747 shape.SetWidth( 20 );
749 shape.SetStartEndingLength( 600 );
750 shape.SetStartEndingWidth( 200 );
752 shape.SetEndEndingLength( 500 );
753 shape.SetEndEndingWidth( 200 );
754
755 std::vector<SHAPE*> shapes = shape.MakeEffectiveShapesForStroking( shape.GetWidth() );
756
757 BOOST_CHECK( shapes.empty() );
758
759 deleteShapes( shapes );
760}
761
762
763BOOST_AUTO_TEST_CASE( EffectiveShapesHitEndpointSquare )
764{
766 shape.SetStart( { 0, 0 } );
767 shape.SetEnd( { 1000, 0 } );
768 shape.SetWidth( 100 );
770 shape.SetStartEndingLength( 400 );
771 shape.SetStartEndingWidth( 400 );
772
773 std::vector<SHAPE*> shapes = shape.MakeEffectiveShapesWithLineEndings( shape.GetWidth() );
774 SHAPE_SEGMENT probe( VECTOR2I( 0, -150 ), VECTOR2I( 0, 150 ), 20 );
775 bool collides = false;
776
777 for( SHAPE* shapePtr : shapes )
778 {
779 if( shapePtr->Collide( probe.GetSeg(), 0 ) )
780 {
781 collides = true;
782 break;
783 }
784 }
785
786 BOOST_CHECK( collides );
787
788 deleteShapes( shapes );
789}
790
791
792BOOST_AUTO_TEST_CASE( PolygonConversionShortensArrowBody )
793{
795 shape.SetStart( { 0, 0 } );
796 shape.SetEnd( { 1000, 0 } );
797 shape.SetWidth( 100 );
799 shape.SetEndEndingLength( 200 );
800 shape.SetEndEndingWidth( 200 );
801
802 SHAPE_POLY_SET unshortenedBodyPlusEnding;
803 SHAPE_POLY_SET shortenedBodyPlusEnding;
804
805 shape.TransformShapeToPolygon( unshortenedBodyPlusEnding, 0, 1, ERROR_OUTSIDE );
806 shape.TransformLineEndingsToPolygon( unshortenedBodyPlusEnding, 0, 1, ERROR_OUTSIDE, shape.GetWidth() );
807 shape.TransformWithLineEndingsToPolygon( shortenedBodyPlusEnding, 0, 1, ERROR_OUTSIDE );
808
809 BOOST_CHECK_GT( unshortenedBodyPlusEnding.BBox().GetRight(), 1000 );
810 BOOST_CHECK_LE( shortenedBodyPlusEnding.BBox().GetRight(), 1001 );
811}
812
813
814BOOST_AUTO_TEST_CASE( ShortenArcForEndingsKeepsPositiveSweep )
815{
818 shape.SetStartEndingLength( 100 );
819 shape.SetStartEndingWidth( 100 );
821 shape.SetEndEndingLength( 200 );
822 shape.SetEndEndingWidth( 100 );
823
824 EDA_ANGLE startAngle( 10.0, DEGREES_T );
825 EDA_ANGLE arcAngle( 90.0, DEGREES_T );
826 constexpr double radius = 1000.0;
827 constexpr double radToDeg = 180.0 / M_PI;
828
829 BOOST_CHECK( shape.ShortenArcForEndings( startAngle, arcAngle, radius, 20 ) );
830 checkAngleClose( startAngle, 10.0 + 100.0 / radius * radToDeg );
831 checkAngleClose( arcAngle, 90.0 - 300.0 / radius * radToDeg );
832 BOOST_CHECK( arcAngle > ANGLE_0 );
833}
834
835
836BOOST_AUTO_TEST_CASE( ShortenArcForEndingsKeepsNegativeSweep )
837{
840 shape.SetStartEndingLength( 100 );
841 shape.SetStartEndingWidth( 100 );
843 shape.SetEndEndingLength( 200 );
844 shape.SetEndEndingWidth( 100 );
845
846 EDA_ANGLE startAngle( 170.0, DEGREES_T );
847 EDA_ANGLE arcAngle( -90.0, DEGREES_T );
848 constexpr double radius = 1000.0;
849 constexpr double radToDeg = 180.0 / M_PI;
850
851 BOOST_CHECK( shape.ShortenArcForEndings( startAngle, arcAngle, radius, 20 ) );
852 checkAngleClose( startAngle, 170.0 - 100.0 / radius * radToDeg );
853 checkAngleClose( arcAngle, -90.0 + 300.0 / radius * radToDeg );
854 BOOST_CHECK( arcAngle < ANGLE_0 );
855}
856
857
858BOOST_AUTO_TEST_CASE( ArcShorteningRejectsOverrun )
859{
862 shape.SetStartEndingLength( 100 );
863 shape.SetStartEndingWidth( 100 );
865 shape.SetEndEndingLength( 100 );
866 shape.SetEndEndingWidth( 100 );
867
868 EDA_ANGLE startAngle( 30.0, DEGREES_T );
869 EDA_ANGLE arcAngle( 10.0, DEGREES_T );
870
871 BOOST_CHECK( !shape.ShortenArcForEndings( startAngle, arcAngle, 1000.0, 20 ) );
872 checkAngleClose( startAngle, 30.0 );
873 checkAngleClose( arcAngle, 0.0 );
874}
875
876
877BOOST_AUTO_TEST_CASE( BezierNoEndingsReturnsSource )
878{
880 shape.SetStart( { 0, 0 } );
881 shape.SetBezierC1( { 300, 0 } );
882 shape.SetBezierC2( { 700, 0 } );
883 shape.SetEnd( { 1000, 0 } );
884
885 std::optional<BEZIER<double>> curve = shape.ShortenedBezierCurve( 20 );
886
887 BOOST_REQUIRE( curve );
888 checkVectorClose( curve->Start, { 0, 0 } );
889 checkVectorClose( curve->C1, { 300, 0 } );
890 checkVectorClose( curve->C2, { 700, 0 } );
891 checkVectorClose( curve->End, { 1000, 0 } );
892}
893
894
895BOOST_AUTO_TEST_CASE( BezierCurveTrimsBothEnds )
896{
898 shape.SetStart( { 0, 0 } );
899 shape.SetBezierC1( { 300, 0 } );
900 shape.SetBezierC2( { 700, 0 } );
901 shape.SetEnd( { 1000, 0 } );
903 shape.SetStartEndingLength( 100 );
904 shape.SetStartEndingWidth( 100 );
906 shape.SetEndEndingLength( 200 );
907 shape.SetEndEndingWidth( 100 );
908
909 std::optional<BEZIER<double>> curve = shape.ShortenedBezierCurve( 20 );
910
911 BOOST_REQUIRE( curve );
912 checkVectorClose( curve->Start, { 100, 0 }, 1e-3 );
913 checkVectorClose( curve->End, { 800, 0 }, 1e-3 );
914}
915
916
917BOOST_AUTO_TEST_CASE( BezierCurveRejectsOverrun )
918{
920 shape.SetStart( { 0, 0 } );
921 shape.SetBezierC1( { 300, 0 } );
922 shape.SetBezierC2( { 700, 0 } );
923 shape.SetEnd( { 1000, 0 } );
925 shape.SetStartEndingLength( 600 );
926 shape.SetStartEndingWidth( 100 );
928 shape.SetEndEndingLength( 500 );
929 shape.SetEndEndingWidth( 100 );
930
931 BOOST_CHECK( !shape.ShortenedBezierCurve( 20 ) );
932}
933
934
935BOOST_AUTO_TEST_CASE( ShortenedBezierPolylineUsesShortenedCurve )
936{
938 shape.SetStart( { 0, 0 } );
939 shape.SetBezierC1( { 300, 0 } );
940 shape.SetBezierC2( { 700, 0 } );
941 shape.SetEnd( { 1000, 0 } );
943 shape.SetStartEndingLength( 100 );
944 shape.SetStartEndingWidth( 100 );
946 shape.SetEndEndingLength( 200 );
947 shape.SetEndEndingWidth( 100 );
948
949 std::vector<VECTOR2D> points = shape.ShortenedBezierPolyline( 20 );
950
951 BOOST_REQUIRE_GE( points.size(), 2 );
952 checkVectorClose( points.front(), { 100, 0 }, 1e-3 );
953 checkVectorClose( points.back(), { 800, 0 }, 1e-3 );
954}
955
956
957BOOST_AUTO_TEST_CASE( BezierPolylineRejectsOverrun )
958{
960 shape.SetStart( { 0, 0 } );
961 shape.SetBezierC1( { 300, 0 } );
962 shape.SetBezierC2( { 700, 0 } );
963 shape.SetEnd( { 1000, 0 } );
965 shape.SetStartEndingLength( 600 );
966 shape.SetStartEndingWidth( 100 );
968 shape.SetEndEndingLength( 500 );
969 shape.SetEndEndingWidth( 100 );
970
971 BOOST_CHECK( shape.ShortenedBezierPolyline( 20 ).empty() );
972}
973
974
975// Minimal concrete RENDER_SETTINGS; STROKE_PARAMS::Stroke only reads the dash/gap ratios.
977{
978public:
979 KIGFX::COLOR4D GetColor( const KIGFX::VIEW_ITEM* aItem, int aLayer ) const override
980 {
982 }
983
984 const KIGFX::COLOR4D& GetBackgroundColor() const override { return m_color; }
985 void SetBackgroundColor( const KIGFX::COLOR4D& aColor ) override { m_color = aColor; }
986 const KIGFX::COLOR4D& GetGridColor() override { return m_color; }
987 const KIGFX::COLOR4D& GetCursorColor() override { return m_color; }
988
989private:
991};
992
993
994// A dashed Bezier with endings must still be stroked as ONE chain: loose segments would
995// restart the dash pattern at every tessellation vertex (issue #25110) and draw near-solid.
996BOOST_AUTO_TEST_CASE( StrokingShortenedBezierDashes )
997{
999 shape.SetStart( { 0, 0 } );
1000 shape.SetBezierC1( { 30000, 20000 } );
1001 shape.SetBezierC2( { 70000, -20000 } );
1002 shape.SetEnd( { 100000, 0 } );
1003 shape.SetWidth( 100 );
1005 shape.SetStartEndingLength( 2000 );
1006 shape.SetStartEndingWidth( 1000 );
1008 shape.SetEndEndingLength( 3000 );
1009 shape.SetEndEndingWidth( 1000 );
1010
1011 std::vector<SHAPE*> shapes = shape.MakeEffectiveShapesForStroking( shape.GetWidth() );
1012
1013 // The shortened body must come back as a single chain.
1014 BOOST_REQUIRE_EQUAL( shapes.size(), 1 );
1015
1016 const SHAPE_LINE_CHAIN* chain = dynamic_cast<const SHAPE_LINE_CHAIN*>( shapes.front() );
1018 BOOST_REQUIRE_GE( chain->SegmentCount(), 2 );
1019
1020 double chainLength = 0.0;
1021
1022 for( int ii = 0; ii < chain->SegmentCount(); ++ii )
1023 chainLength += VECTOR2D( chain->CSegment( ii ).B - chain->CSegment( ii ).A ).EuclideanNorm();
1024
1025 // Stroke it dashed and glue touching pieces back into runs (a dash spanning a vertex
1026 // arrives as several touching pieces).
1028 std::vector<SEG> pieces;
1029
1031 [&pieces]( const VECTOR2I& a, const VECTOR2I& b )
1032 {
1033 pieces.emplace_back( a, b );
1034 } );
1035
1036 std::vector<double> runs;
1037 double drawn = 0.0;
1038
1039 for( size_t ii = 0; ii < pieces.size(); )
1040 {
1041 double length = VECTOR2D( pieces[ii].B - pieces[ii].A ).EuclideanNorm();
1042 VECTOR2I end = pieces[ii].B;
1043 size_t jj = ii + 1;
1044
1045 while( jj < pieces.size() && pieces[jj].A == end )
1046 {
1047 length += VECTOR2D( pieces[jj].B - pieces[jj].A ).EuclideanNorm();
1048 end = pieces[jj].B;
1049 jj++;
1050 }
1051
1052 runs.push_back( length );
1053 drawn += length;
1054 ii = jj;
1055 }
1056
1057 BOOST_REQUIRE_MESSAGE( runs.size() > 5, "expected many dashes, got " << runs.size() << " run(s)" );
1058
1059 // If the pattern restarted at every tessellation vertex the curve would come out nearly
1060 // solid; the drawn length must instead match the dash/gap duty cycle.
1061 double dash = settings.GetDashLength( shape.GetWidth() );
1062 double gap = settings.GetGapLength( shape.GetWidth() );
1063 double expected = chainLength * dash / ( dash + gap );
1064
1065 BOOST_CHECK_MESSAGE( std::abs( drawn - expected ) < 0.05 * chainLength,
1066 "dashes should cover " << expected << " IU of the " << chainLength
1067 << " IU shortened body, they cover " << drawn );
1068
1069 deleteShapes( shapes );
1070}
1071
1072
1073BOOST_AUTO_TEST_CASE( PolyShorteningTrimsEnds )
1074{
1077 shape.SetStartEndingLength( 100 );
1078 shape.SetStartEndingWidth( 100 );
1080 shape.SetEndEndingLength( 200 );
1081 shape.SetEndEndingWidth( 200 );
1082
1083 VECTOR2D first( 0, 0 );
1084 VECTOR2D second( 1000, 0 );
1085 VECTOR2D penultimate( 1000, 1000 );
1086 VECTOR2D last( 1000, 2000 );
1087
1088 BOOST_CHECK( shape.ShortenPolyForEndings( first, last, second, penultimate, 20 ) );
1089
1090 checkVectorClose( first, { 100, 0 } );
1091 checkVectorClose( last, { 1000, 1900 } );
1092}
1093
1094
1095BOOST_AUTO_TEST_CASE( ShortenPolyForEndingsLeavesOpenArrowBody )
1096{
1099 shape.SetStartEndingLength( 500 );
1100 shape.SetStartEndingWidth( 500 );
1101
1102 VECTOR2D first( 0, 0 );
1103 VECTOR2D second( 1000, 0 );
1104 VECTOR2D penultimate( 1000, 1000 );
1105 VECTOR2D last( 1000, 2000 );
1106
1107 BOOST_CHECK( shape.ShortenPolyForEndings( first, last, second, penultimate, 20 ) );
1108
1109 checkVectorClose( first, { 0, 0 } );
1110 checkVectorClose( last, { 1000, 2000 } );
1111}
1112
1113
1114BOOST_AUTO_TEST_CASE( PolyShorteningRejectsOverrun )
1115{
1118 shape.SetStartEndingLength( 200 );
1119 shape.SetStartEndingWidth( 100 );
1120
1121 VECTOR2D first( 0, 0 );
1122 VECTOR2D second( 100, 0 );
1123 VECTOR2D penultimate( 1000, 0 );
1124 VECTOR2D last( 1100, 0 );
1125
1126 BOOST_CHECK( !shape.ShortenPolyForEndings( first, last, second, penultimate, 20 ) );
1127 checkVectorClose( first, { 0, 0 } );
1128 checkVectorClose( last, { 0, 0 } );
1129}
1130
1131
1132BOOST_AUTO_TEST_CASE( BodyPolyPointsShortenFirstOpenOutline )
1133{
1136 shape.SetStartEndingLength( 100 );
1137 shape.SetStartEndingWidth( 100 );
1138
1139 SHAPE_LINE_CHAIN outline;
1140 outline.Append( VECTOR2I( 0, 0 ) );
1141 outline.Append( VECTOR2I( 1000, 0 ) );
1142 outline.Append( VECTOR2I( 1000, 1000 ) );
1143 outline.SetClosed( false );
1144
1145 std::vector<VECTOR2I> pts;
1146
1147 BOOST_CHECK( shape.GetShortenedBodyPolyPoints( outline, 0, pts, 20 ) );
1148 BOOST_REQUIRE_EQUAL( pts.size(), 3 );
1149 checkVectorEqual( pts.front(), { 100, 0 } );
1150 checkVectorEqual( pts.back(), { 1000, 1000 } );
1151
1152 BOOST_CHECK( shape.GetShortenedBodyPolyPoints( outline, 1, pts, 20 ) );
1153 BOOST_REQUIRE_EQUAL( pts.size(), 3 );
1154 checkVectorEqual( pts.front(), { 0, 0 } );
1155 checkVectorEqual( pts.back(), { 1000, 1000 } );
1156
1157 outline.SetClosed( true );
1158
1159 BOOST_CHECK( shape.GetShortenedBodyPolyPoints( outline, 0, pts, 20 ) );
1160 BOOST_REQUIRE_EQUAL( pts.size(), 3 );
1161 checkVectorEqual( pts.front(), { 0, 0 } );
1162 checkVectorEqual( pts.back(), { 1000, 1000 } );
1163}
1164
@ ERROR_OUTSIDE
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
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:116
"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:549
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
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
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:403
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:279
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:279
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:422
@ DEGREES_T
Definition eda_angle.h:31
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:411
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:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682