/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2018 KiCad Developers, see CHANGELOG.TXT for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include "geom_test_utils.h" struct FilletFixture { }; /** * Declares the FilletFixture struct as the boost test fixture. */ BOOST_FIXTURE_TEST_SUITE( Fillet, FilletFixture ) /* * @brief check that a single segment of a fillet complies with the geometric * constraint: * * 1: The end points are radius from the centre point * 2: The mid point error is acceptable * 3: The segment midpoints are perpendicular to the radius */ void TestFilletSegmentConstraints( const SEG& aSeg, VECTOR2I aRadCentre, int aRadius, int aError ) { const auto diffA = aRadCentre - aSeg.A; const auto diffB = aRadCentre - aSeg.B; const auto diffC = aRadCentre - aSeg.Center(); // Check 1: radii (error of 1 for rounding) BOOST_CHECK_PREDICATE( KI_TEST::IsWithinAndBelow, ( diffA.EuclideanNorm() )( aRadius )( 1 ) ); BOOST_CHECK_PREDICATE( KI_TEST::IsWithinAndBelow, ( diffB.EuclideanNorm() )( aRadius )( 1 ) ); // Check 2: Mid-point error BOOST_CHECK_PREDICATE( KI_TEST::IsWithinAndBelow, ( diffC.EuclideanNorm() )( aRadius )( aError + 1 ) ); // Check 3: Mid-point -> radius centre perpendicular const auto perpendularityMaxError = ( M_PI / 2 ) / 10; BOOST_CHECK_PREDICATE( GEOM_TEST::ArePerpendicular, ( diffC )( aSeg.A - aSeg.B )( perpendularityMaxError ) ); } /** * @brief: Create a square, fillet it, and check a corner for correctness */ void TestSquareFillet( int aSquareSize, int aRadius, int aError ) { using namespace GEOM_TEST; SHAPE_POLY_SET squarePolySet; squarePolySet.AddOutline( MakeSquarePolyLine(aSquareSize, VECTOR2I(0, 0) ) ); SHAPE_POLY_SET filleted = FilletPolySet(squarePolySet, aRadius, aError); // expect a single filleted polygon BOOST_CHECK_EQUAL( filleted.OutlineCount(), 1 ); auto segIter = filleted.IterateSegments(); const VECTOR2I radCentre { aSquareSize / 2 - aRadius, aSquareSize / 2 - aRadius }; int checked = 0; for( ; segIter; segIter++ ) { // Only check the first Quadrant if ( SegmentCompletelyInQuadrant( *segIter, QUADRANT::Q1 ) ) { TestFilletSegmentConstraints( *segIter, radCentre, aRadius, aError ); checked++; } } // we expect there to be at least one segment in the fillet BOOST_CHECK( checked > 0 ); } /** * @brief: Create a square concave corner, fillet and check correctness */ void TestConcaveSquareFillet( int aSquareSize, int aRadius, int aError ) { using namespace GEOM_TEST; SHAPE_POLY_SET polySet; SHAPE_LINE_CHAIN polyLine; /* * L-shape: * ---- * | | * ---- | * | | * -------- */ polyLine.Append( VECTOR2I{ 0, 0 } ); polyLine.Append( VECTOR2I{ 0, aSquareSize / 2 } ); polyLine.Append( VECTOR2I{ aSquareSize / 2 , aSquareSize / 2 } ); polyLine.Append( VECTOR2I{ aSquareSize / 2 , aSquareSize } ); polyLine.Append( VECTOR2I{ aSquareSize, aSquareSize } ); polyLine.Append( VECTOR2I{ aSquareSize, 0 } ); polyLine.SetClosed( true ); polySet.AddOutline( polyLine ); SHAPE_POLY_SET filleted = FilletPolySet(polySet, aRadius, aError); // expect a single filleted polygon BOOST_CHECK_EQUAL( filleted.OutlineCount(), 1 ); auto segIter = filleted.IterateSegments(); const VECTOR2I radCentre { aSquareSize / 2 - aRadius, aSquareSize / 2 + aRadius }; int checked = 0; for( ; segIter; segIter++ ) { // Only check segments around the concave corner if ( SegmentCompletelyWithinRadius( *segIter, radCentre, aRadius + 1) ) { TestFilletSegmentConstraints( *segIter, radCentre, aRadius, aError ); checked++; } } // we expect there to be at least one segment in the fillet BOOST_CHECK( checked > 0 ); } struct SquareFilletTestCase { int squareSize; int radius; int error; }; const std::vector squareFilletCases { { 1000, 120, 10 }, { 1000, 10, 1 }, /* Large error relative to fillet */ { 1000, 10, 5 }, /* Very small error relative to fillet(many segments in interpolation) */ { 70000, 1000, 1 }, }; /** * Tests the SHAPE_POLY_SET::FilletPolygon method against certain geometric * constraints. */ BOOST_AUTO_TEST_CASE( SquareFillet ) { for ( const auto& testCase : squareFilletCases ) { TestSquareFillet( testCase.squareSize, testCase.radius, testCase.error ); } } BOOST_AUTO_TEST_CASE( SquareConcaveFillet ) { for ( const auto& testCase : squareFilletCases ) { TestConcaveSquareFillet( testCase.squareSize, testCase.radius, testCase.error ); } } BOOST_AUTO_TEST_SUITE_END()