Files
kicad-source-mirror/qa/tests/common/test_grid_helper.cpp
T
Seth Hillbrand daa76770db Clear out temp tests
Were used to identify the original issue but don't actually test KiCad
code, so they are superfluous.
2026-02-19 09:40:41 -08:00

422 lines
13 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
#include <tool/grid_helper.h>
void TEST_CLEAR_ANCHORS( GRID_HELPER& helper )
{
helper.clearAnchors();
}
BOOST_AUTO_TEST_SUITE( GridHelperTest )
BOOST_AUTO_TEST_CASE( DefaultConstructor )
{
GRID_HELPER helper;
// Test default state
BOOST_CHECK( helper.GetSnap() );
BOOST_CHECK( helper.GetUseGrid() );
// Test that manual setters work
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 50, 50 ) );
helper.SetGridSnapping( true );
VECTOR2I grid = helper.GetGrid();
BOOST_CHECK_EQUAL( grid.x, 100 );
BOOST_CHECK_EQUAL( grid.y, 100 );
VECTOR2I origin = helper.GetOrigin();
BOOST_CHECK_EQUAL( origin.x, 50 );
BOOST_CHECK_EQUAL( origin.y, 50 );
}
BOOST_AUTO_TEST_CASE( AlignBasic )
{
GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
// Test basic alignment - should round to nearest grid point
VECTOR2I aligned = helper.Align( VECTOR2I( 149, 251 ) );
BOOST_CHECK_EQUAL( aligned.x, 100 );
BOOST_CHECK_EQUAL( aligned.y, 300 );
// Test exact grid points
aligned = helper.Align( VECTOR2I( 200, 300 ) );
BOOST_CHECK_EQUAL( aligned.x, 200 );
BOOST_CHECK_EQUAL( aligned.y, 300 );
// Test negative coordinates
aligned = helper.Align( VECTOR2I( -149, -251 ) );
BOOST_CHECK_EQUAL( aligned.x, -100 );
BOOST_CHECK_EQUAL( aligned.y, -300 );
}
BOOST_AUTO_TEST_CASE( AlignGridWithCustomGrid )
{
GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 50, 50 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
VECTOR2I aligned = helper.AlignGrid( VECTOR2I( 26, 74 ) );
BOOST_CHECK_EQUAL( aligned.x, 50 );
BOOST_CHECK_EQUAL( aligned.y, 50 );
// Test AlignGrid with specific grid parameter
aligned = helper.AlignGrid( VECTOR2I( 26, 74 ), VECTOR2D( 25, 25 ), VECTOR2D( 0, 0 ) );
BOOST_CHECK_EQUAL( aligned.x, 25 );
BOOST_CHECK_EQUAL( aligned.y, 75 );
}
BOOST_AUTO_TEST_CASE( AlignWithOriginOffset )
{
GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 25, 25 ) );
helper.SetGridSnapping( true );
// When grid has an origin offset, alignment should work from the new reference point
VECTOR2I aligned = helper.AlignGrid( VECTOR2I( 149, 251 ) );
BOOST_CHECK_EQUAL( aligned.x, 125 );
BOOST_CHECK_EQUAL( aligned.y, 225 );
}
BOOST_AUTO_TEST_CASE( AlignWithAuxiliaryAxes )
{
GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
// Set auxiliary axis at (75, 75)
helper.SetAuxAxes( true, VECTOR2I( 75, 75 ) );
// Point closer to aux axis than grid should snap to aux axis
VECTOR2I aligned = helper.Align( VECTOR2I( 80, 80 ) );
BOOST_CHECK_EQUAL( aligned.x, 75 ); // Closer to aux axis X
BOOST_CHECK_EQUAL( aligned.y, 75 ); // Closer to aux axis Y
// Point closer to grid than aux axis should snap to grid
aligned = helper.Align( VECTOR2I( 95, 95 ) );
BOOST_CHECK_EQUAL( aligned.x, 100 ); // Closer to grid
BOOST_CHECK_EQUAL( aligned.y, 100 ); // Closer to grid
// Disable aux axes
helper.SetAuxAxes( false );
aligned = helper.Align( VECTOR2I( 80, 80 ) );
BOOST_CHECK_EQUAL( aligned.x, 100 ); // Should snap to grid only
BOOST_CHECK_EQUAL( aligned.y, 100 );
}
BOOST_AUTO_TEST_CASE( GridSnappingDisabled )
{
GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( false ); // Disable grid snapping
// When grid snapping is disabled, Align should return original point
VECTOR2I original( 149, 251 );
VECTOR2I aligned = helper.Align( original );
BOOST_CHECK_EQUAL( aligned.x, original.x );
BOOST_CHECK_EQUAL( aligned.y, original.y );
// AlignGrid should still work regardless of grid snapping setting
aligned = helper.AlignGrid( original );
BOOST_CHECK_EQUAL( aligned.x, 100 );
BOOST_CHECK_EQUAL( aligned.y, 300 );
}
BOOST_AUTO_TEST_CASE( UseGridDisabled )
{
GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
helper.SetUseGrid( false ); // Disable grid usage
// When grid usage is disabled, Align should return original point
VECTOR2I original( 149, 251 );
VECTOR2I aligned = helper.Align( original );
BOOST_CHECK_EQUAL( aligned.x, original.x );
BOOST_CHECK_EQUAL( aligned.y, original.y );
}
BOOST_AUTO_TEST_CASE( AsymmetricGrid )
{
GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 25, 75 ) ); // Different X and Y grid sizes
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
VECTOR2I aligned = helper.Align( VECTOR2I( 30, 100 ) );
BOOST_CHECK_EQUAL( aligned.x, 25 ); // Nearest 25-unit boundary
BOOST_CHECK_EQUAL( aligned.y, 75 ); // Nearest 75-unit boundary
aligned = helper.Align( VECTOR2I( 40, 120 ) );
BOOST_CHECK_EQUAL( aligned.x, 50 ); // Next 25-unit boundary
BOOST_CHECK_EQUAL( aligned.y, 150 ); // Next 75-unit boundary
}
BOOST_AUTO_TEST_CASE( SnapFlags )
{
GRID_HELPER helper;
// Test snap flag getters/setters
BOOST_CHECK( helper.GetSnap() ); // Default should be true
helper.SetSnap( false );
BOOST_CHECK( !helper.GetSnap() );
helper.SetSnap( true );
BOOST_CHECK( helper.GetSnap() );
// Test grid usage flag
BOOST_CHECK( helper.GetUseGrid() ); // Default should be true
helper.SetUseGrid( false );
BOOST_CHECK( !helper.GetUseGrid() );
helper.SetUseGrid( true );
BOOST_CHECK( helper.GetUseGrid() );
}
BOOST_AUTO_TEST_CASE( MaskOperations )
{
GRID_HELPER helper;
// Test mask operations
helper.SetMask( GRID_HELPER::CORNER | GRID_HELPER::OUTLINE );
helper.SetMaskFlag( GRID_HELPER::SNAPPABLE );
helper.ClearMaskFlag( GRID_HELPER::CORNER );
// These don't have getters, so we can't verify the mask state directly
// but we can verify the methods don't crash
}
BOOST_AUTO_TEST_CASE( SkipPoint )
{
GRID_HELPER helper;
// Test skip point operations
helper.SetSkipPoint( VECTOR2I( 100, 100 ) );
helper.ClearSkipPoint();
// These methods should not crash
}
BOOST_AUTO_TEST_CASE( GridTypeAlignment )
{
GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
// Test alignment with specific grid type
VECTOR2I aligned = helper.Align( VECTOR2I( 149, 251 ), GRID_CURRENT );
BOOST_CHECK_EQUAL( aligned.x, 100 );
BOOST_CHECK_EQUAL( aligned.y, 300 );
aligned = helper.AlignGrid( VECTOR2I( 149, 251 ), GRID_CURRENT );
BOOST_CHECK_EQUAL( aligned.x, 100 );
BOOST_CHECK_EQUAL( aligned.y, 300 );
}
BOOST_AUTO_TEST_CASE( EdgeCases )
{
GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 1, 1 ) ); // Very small grid
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
// Test with very small grid
VECTOR2I aligned = helper.Align( VECTOR2I( 5, 5 ) );
BOOST_CHECK_EQUAL( aligned.x, 5 );
BOOST_CHECK_EQUAL( aligned.y, 5 );
// Test with zero point
aligned = helper.Align( VECTOR2I( 0, 0 ) );
BOOST_CHECK_EQUAL( aligned.x, 0 );
BOOST_CHECK_EQUAL( aligned.y, 0 );
// Test with large grid
helper.SetGridSize( VECTOR2D( 10000, 10000 ) );
aligned = helper.Align( VECTOR2I( 3000, 7000 ) );
BOOST_CHECK_EQUAL( aligned.x, 0 ); // Closer to 0 than 10000
BOOST_CHECK_EQUAL( aligned.y, 10000 ); // Closer to 10000 than 0
}
BOOST_AUTO_TEST_CASE( GetGridSize )
{
GRID_HELPER helper;
// Test GetGridSize with different grid types
helper.SetGridSize( VECTOR2D( 50, 75 ) );
VECTOR2D gridSize = helper.GetGridSize( GRID_CURRENT );
BOOST_CHECK_EQUAL( gridSize.x, 50 );
BOOST_CHECK_EQUAL( gridSize.y, 75 );
// Other grid types should return the same in the base implementation
gridSize = helper.GetGridSize( GRID_CONNECTABLE );
BOOST_CHECK_EQUAL( gridSize.x, 50 );
BOOST_CHECK_EQUAL( gridSize.y, 75 );
}
BOOST_AUTO_TEST_CASE( VisibleGrid )
{
GRID_HELPER helper;
helper.SetVisibleGridSize( VECTOR2D( 25, 35 ) );
VECTOR2D visibleGrid = helper.GetVisibleGrid();
BOOST_CHECK_EQUAL( visibleGrid.x, 25 );
BOOST_CHECK_EQUAL( visibleGrid.y, 35 );
}
BOOST_AUTO_TEST_CASE( SnapPointManagement )
{
GRID_HELPER helper;
// Initially should have no snapped point
auto snappedPoint = helper.GetSnappedPoint();
BOOST_CHECK( !snappedPoint.has_value() );
// After clearing anchors, still no snapped point
TEST_CLEAR_ANCHORS( helper );
snappedPoint = helper.GetSnappedPoint();
BOOST_CHECK( !snappedPoint.has_value() );
}
BOOST_AUTO_TEST_CASE( AlignGridWithNonPageOrigin )
{
// Issue #21800: Grid snapping should produce positions that are exact multiples
// of the grid size relative to the grid origin, regardless of display origin setting.
// When grid sizes go through VECTOR2D, floating-point imprecision in the
// VECTOR2D -> VECTOR2I truncation could produce incorrect grid positions.
GRID_HELPER helper;
helper.SetGridSnapping( true );
// PCB IU_PER_MM = 1000000 (nanometers)
constexpr int IU_PER_MM = 1000000;
struct TestCase
{
const char* name;
double gridSizeMM;
int gridOriginX;
int gridOriginY;
int pointX;
int pointY;
int expectedX;
int expectedY;
};
// Test various grid sizes, including ones that don't convert exactly from mm to nm
std::vector<TestCase> cases = {
// 1mm grid with non-zero origin
{ "1mm grid, origin at 47.3mm",
1.0, 47300000, 25300000,
127400000, 95400000,
0, 0 },
// 0.5mm grid
{ "0.5mm grid, origin at 47.3mm",
0.5, 47300000, 25300000,
127400000, 95400000,
0, 0 },
// 0.1mm grid (0.1 is NOT exact in IEEE 754 double)
{ "0.1mm grid, origin at 47.3mm",
0.1, 47300000, 25300000,
127340000, 95340000,
0, 0 },
// 25 mil = 0.635mm (0.635 is NOT exact in IEEE 754 double)
{ "25mil grid, origin at 47.625mm",
0.635, 47625000, 25400000,
127960000, 95250000,
0, 0 },
// 10 mil = 0.254mm (0.254 is NOT exact in IEEE 754 double)
{ "10mil grid, origin at 47.752mm",
0.254, 47752000, 25400000,
127960000, 95250000,
0, 0 },
// 50 mil = 1.27mm
{ "50mil grid, origin at 47.625mm",
1.27, 47625000, 25400000,
127960000, 95250000,
0, 0 },
};
// Compute all expected values using integer grid size (the ground truth)
for( auto& tc : cases )
{
int gridSizeIU = KiROUND( tc.gridSizeMM * IU_PER_MM );
tc.expectedX = KiROUND( double( tc.pointX - tc.gridOriginX ) / gridSizeIU )
* gridSizeIU + tc.gridOriginX;
tc.expectedY = KiROUND( double( tc.pointY - tc.gridOriginY ) / gridSizeIU )
* gridSizeIU + tc.gridOriginY;
}
for( const auto& tc : cases )
{
BOOST_TEST_CONTEXT( tc.name )
{
int gridSizeIU = KiROUND( tc.gridSizeMM * IU_PER_MM );
// Simulate the grid size coming through VECTOR2D (as it does from GAL)
VECTOR2D gridD( tc.gridSizeMM * IU_PER_MM, tc.gridSizeMM * IU_PER_MM );
VECTOR2D offsetD( tc.gridOriginX, tc.gridOriginY );
helper.SetGridSize( gridD );
helper.SetOrigin( VECTOR2I( tc.gridOriginX, tc.gridOriginY ) );
VECTOR2I point( tc.pointX, tc.pointY );
// Test AlignGrid with VECTOR2D parameters (the path that goes through
// implicit VECTOR2D -> VECTOR2I truncation in computeNearest)
VECTOR2I resultD = helper.AlignGrid( point, gridD, offsetD );
// Test AlignGrid with no parameters (uses GetGrid() which rounds properly)
VECTOR2I resultI = helper.AlignGrid( point );
BOOST_CHECK_EQUAL( resultD.x, tc.expectedX );
BOOST_CHECK_EQUAL( resultD.y, tc.expectedY );
BOOST_CHECK_EQUAL( resultI.x, tc.expectedX );
BOOST_CHECK_EQUAL( resultI.y, tc.expectedY );
// Verify that the result is on-grid
BOOST_CHECK_EQUAL( ( resultD.x - tc.gridOriginX ) % gridSizeIU, 0 );
BOOST_CHECK_EQUAL( ( resultD.y - tc.gridOriginY ) % gridSizeIU, 0 );
}
}
}
BOOST_AUTO_TEST_SUITE_END()