You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

410 lines
12 KiB

/*
* 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 <tools/pcb_grid_helper.h>
#include <geometry/seg.h>
#include <geometry/shape_arc.h>
#include <footprint.h>
#include <pad.h>
#include <pcb_shape.h>
#include <pcb_track.h>
#include <zone.h>
#include <pcb_text.h>
// Mock EDA_ITEM class for testing GetItemGrid
class MOCK_BOARD_ITEM : public BOARD_ITEM
{
public:
MOCK_BOARD_ITEM( KICAD_T aType ) : BOARD_ITEM( nullptr, aType ) {}
// Required virtual functions
wxString GetClass() const override { return "MockEDAItem"; }
void Move( const VECTOR2I& aMoveVector ) override {}
VECTOR2I GetPosition() const override { return VECTOR2I( 0, 0 ); }
void SetPosition( const VECTOR2I& aPos ) override {}
BOARD_ITEM* Clone() const override { return new MOCK_BOARD_ITEM( Type() ); }
// Implement pure virtuals from BOARD_ITEM
double Similarity( const BOARD_ITEM& aItem ) const override { return this == &aItem ? 1.0 : 0.0; }
bool operator==( const BOARD_ITEM& aItem ) const override { return this == &aItem; }
};
// Test fixture for accessing protected members
class PCBGridHelperTestFixture
{
public:
PCBGridHelperTestFixture()
{
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
helper.SetSnap( true );
}
PCB_GRID_HELPER helper;
};
BOOST_AUTO_TEST_SUITE( PCBGridHelperTest )
BOOST_AUTO_TEST_CASE( DefaultConstructor )
{
PCB_GRID_HELPER helper;
// Test default state matches base class
BOOST_CHECK( helper.GetSnap() );
BOOST_CHECK( helper.GetUseGrid() );
// Test that GetSnapped returns nullptr initially
BOOST_CHECK( helper.GetSnapped() == nullptr );
}
BOOST_AUTO_TEST_CASE( AlignToSegmentBasic )
{
PCB_GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
// Horizontal segment
SEG seg( VECTOR2I( 0, 0 ), VECTOR2I( 300, 0 ) );
VECTOR2I result = helper.AlignToSegment( VECTOR2I( 150, 50 ), seg );
// Should snap to grid point that intersects with segment
BOOST_CHECK_EQUAL( result.x, 200 );
BOOST_CHECK_EQUAL( result.y, 0 );
}
BOOST_AUTO_TEST_CASE( AlignToSegmentVertical )
{
PCB_GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
// Vertical segment
SEG seg( VECTOR2I( 200, 0 ), VECTOR2I( 200, 400 ) );
VECTOR2I result = helper.AlignToSegment( VECTOR2I( 150, 150 ), seg );
// Should snap to intersection with vertical line
BOOST_CHECK_EQUAL( result.x, 200 );
BOOST_CHECK_EQUAL( result.y, 200 );
}
BOOST_AUTO_TEST_CASE( AlignToSegmentDiagonal )
{
PCB_GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
// Diagonal segment
SEG seg( VECTOR2I( 0, 0 ), VECTOR2I( 400, 400 ) );
VECTOR2I result = helper.AlignToSegment( VECTOR2I( 150, 250 ), seg );
// 250,250 is the closest point to the nearest grid point to the segment.
// First, it snaps to grid point (200, 300) and then finds the closest point on the segment.
// So the result should be (250, 250).
BOOST_CHECK_EQUAL( result.x, 250 );
BOOST_CHECK_EQUAL( result.y, 250 );
}
BOOST_AUTO_TEST_CASE( AlignToSegmentEndpoints )
{
PCB_GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
SEG seg( VECTOR2I( 50, 50 ), VECTOR2I( 150, 50 ) );
// Point very close to start endpoint
VECTOR2I result = helper.AlignToSegment( VECTOR2I( 55, 55 ), seg );
BOOST_CHECK_EQUAL( result.x, 50 );
BOOST_CHECK_EQUAL( result.y, 50 );
// Point very close to end endpoint
result = helper.AlignToSegment( VECTOR2I( 145, 55 ), seg );
BOOST_CHECK_EQUAL( result.x, 150 );
BOOST_CHECK_EQUAL( result.y, 50 );
}
BOOST_AUTO_TEST_CASE( AlignToSegmentSnapDisabled )
{
PCB_GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
helper.SetSnap( false ); // Disable snapping
SEG seg( VECTOR2I( 0, 0 ), VECTOR2I( 300, 0 ) );
VECTOR2I point( 150, 50 );
VECTOR2I result = helper.AlignToSegment( point, seg );
// Should return grid-aligned point when snap is disabled
VECTOR2I expected = helper.Align( point );
BOOST_CHECK_EQUAL( result.x, expected.x );
BOOST_CHECK_EQUAL( result.y, expected.y );
}
BOOST_AUTO_TEST_CASE( AlignToArcBasic )
{
PCB_GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
// Create a simple arc (quarter circle)
SHAPE_ARC arc( VECTOR2I( 100, 0 ), VECTOR2I( 0, 100 ), ANGLE_90 );
VECTOR2I result = helper.AlignToArc( VECTOR2I( 50, 50 ), arc );
// Should snap to arc endpoints or intersections
BOOST_CHECK( result.x >= 0 );
BOOST_CHECK( result.y >= 0 );
}
// TODO: Fix broken AlignToArc Routine
// BOOST_AUTO_TEST_CASE( AlignToArcEndpoints )
// {
// PCB_GRID_HELPER helper;
// helper.SetGridSize( VECTOR2D( 100, 100 ) );
// helper.SetOrigin( VECTOR2I( 0, 0 ) );
// helper.SetGridSnapping( true );
// SHAPE_ARC arc( VECTOR2I( 100, 0 ), VECTOR2I( 65, 65 ), VECTOR2I( 0, 100 ), 0 );
// // Point close to start
// VECTOR2I result = helper.AlignToArc( VECTOR2I( 95, 5 ), arc );
// BOOST_CHECK_EQUAL( result.x, arc.GetP0().x );
// BOOST_CHECK_EQUAL( result.y, arc.GetP0().y );
// // Point close to end
// result = helper.AlignToArc( VECTOR2I( 5, 95 ), arc );
// BOOST_CHECK_EQUAL( result.x, arc.GetP1().x );
// BOOST_CHECK_EQUAL( result.y, arc.GetP1().y );
// }
BOOST_AUTO_TEST_CASE( AlignToArcSnapDisabled )
{
PCB_GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
helper.SetSnap( false ); // Disable snapping
SHAPE_ARC arc( VECTOR2I( 100, 0 ), VECTOR2I( 0, 100 ), ANGLE_90 );
VECTOR2I point( 50, 50 );
VECTOR2I result = helper.AlignToArc( point, arc );
// Should return grid-aligned point when snap is disabled
VECTOR2I expected = helper.Align( point );
BOOST_CHECK_EQUAL( result.x, expected.x );
BOOST_CHECK_EQUAL( result.y, expected.y );
}
BOOST_AUTO_TEST_CASE( GetItemGridFootprint )
{
PCB_GRID_HELPER helper;
MOCK_BOARD_ITEM footprint( PCB_FOOTPRINT_T );
GRID_HELPER_GRIDS grid = helper.GetItemGrid( &footprint );
BOOST_CHECK_EQUAL( grid, GRID_CONNECTABLE );
}
BOOST_AUTO_TEST_CASE( GetItemGridPad )
{
PCB_GRID_HELPER helper;
MOCK_BOARD_ITEM pad( PCB_PAD_T );
GRID_HELPER_GRIDS grid = helper.GetItemGrid( &pad );
BOOST_CHECK_EQUAL( grid, GRID_CONNECTABLE );
}
BOOST_AUTO_TEST_CASE( GetItemGridText )
{
PCB_GRID_HELPER helper;
MOCK_BOARD_ITEM text( PCB_TEXT_T );
GRID_HELPER_GRIDS grid = helper.GetItemGrid( &text );
BOOST_CHECK_EQUAL( grid, GRID_TEXT );
MOCK_BOARD_ITEM field( PCB_FIELD_T );
grid = helper.GetItemGrid( &field );
BOOST_CHECK_EQUAL( grid, GRID_TEXT );
}
BOOST_AUTO_TEST_CASE( GetItemGridGraphics )
{
PCB_GRID_HELPER helper;
MOCK_BOARD_ITEM shape( PCB_SHAPE_T );
GRID_HELPER_GRIDS grid = helper.GetItemGrid( &shape );
BOOST_CHECK_EQUAL( grid, GRID_GRAPHICS );
MOCK_BOARD_ITEM dimension( PCB_DIMENSION_T );
grid = helper.GetItemGrid( &dimension );
BOOST_CHECK_EQUAL( grid, GRID_GRAPHICS );
MOCK_BOARD_ITEM refImage( PCB_REFERENCE_IMAGE_T );
grid = helper.GetItemGrid( &refImage );
BOOST_CHECK_EQUAL( grid, GRID_GRAPHICS );
MOCK_BOARD_ITEM textbox( PCB_TEXTBOX_T );
grid = helper.GetItemGrid( &textbox );
BOOST_CHECK_EQUAL( grid, GRID_GRAPHICS );
}
BOOST_AUTO_TEST_CASE( GetItemGridTracks )
{
PCB_GRID_HELPER helper;
MOCK_BOARD_ITEM trace( PCB_TRACE_T );
GRID_HELPER_GRIDS grid = helper.GetItemGrid( &trace );
BOOST_CHECK_EQUAL( grid, GRID_WIRES );
MOCK_BOARD_ITEM arc( PCB_ARC_T );
grid = helper.GetItemGrid( &arc );
BOOST_CHECK_EQUAL( grid, GRID_WIRES );
}
BOOST_AUTO_TEST_CASE( GetItemGridVias )
{
PCB_GRID_HELPER helper;
MOCK_BOARD_ITEM via( PCB_VIA_T );
GRID_HELPER_GRIDS grid = helper.GetItemGrid( &via );
BOOST_CHECK_EQUAL( grid, GRID_VIAS );
}
BOOST_AUTO_TEST_CASE( GetItemGridDefault )
{
PCB_GRID_HELPER helper;
// Test with unknown item type
MOCK_BOARD_ITEM unknown( PCB_ZONE_T );
GRID_HELPER_GRIDS grid = helper.GetItemGrid( &unknown );
BOOST_CHECK_EQUAL( grid, GRID_CURRENT );
// Test with nullptr
grid = helper.GetItemGrid( nullptr );
BOOST_CHECK_EQUAL( grid, GRID_CURRENT );
}
BOOST_AUTO_TEST_CASE( GetSnappedInitiallyNull )
{
PCB_GRID_HELPER helper;
BOARD_ITEM* snapped = helper.GetSnapped();
BOOST_CHECK( snapped == nullptr );
}
BOOST_AUTO_TEST_CASE( OnBoardItemRemovedClearsSnap )
{
PCB_GRID_HELPER helper;
// Create a mock board and item - this test verifies the interface exists
// In a real scenario, the snap item would be set by other operations
BOARD board;
MOCK_BOARD_ITEM item( PCB_TRACE_T );
// This should not crash even if no snap item is set
helper.OnBoardItemRemoved( board, static_cast<BOARD_ITEM*>( &item ) );
BOOST_CHECK( helper.GetSnapped() == nullptr );
}
BOOST_AUTO_TEST_CASE( GeometricSnapTolerance )
{
PCB_GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 100, 100 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
SEG seg( VECTOR2I( 0, 0 ), VECTOR2I( 200, 0 ) );
// Test that points very far from segment don't snap to unrealistic locations
VECTOR2I result = helper.AlignToSegment( VECTOR2I( 50, 100000 ), seg );
// Should snap to midpoint of segment
BOOST_CHECK( result == VECTOR2I( 100, 0 ) );
}
BOOST_AUTO_TEST_CASE( SegmentIntersectionPriority )
{
PCB_GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 50, 50 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
// Segment that passes through a grid point
SEG seg( VECTOR2I( 0, 25 ), VECTOR2I( 100, 25 ) );
// Point near a grid intersection with the segment
VECTOR2I result = helper.AlignToSegment( VECTOR2I( 48, 27 ), seg );
// Should snap to the intersection at (50, 25)
BOOST_CHECK_EQUAL( result.x, 50 );
BOOST_CHECK_EQUAL( result.y, 25 );
}
BOOST_AUTO_TEST_CASE( ArcIntersectionWithGrid )
{
PCB_GRID_HELPER helper;
helper.SetGridSize( VECTOR2D( 50, 50 ) );
helper.SetOrigin( VECTOR2I( 0, 0 ) );
helper.SetGridSnapping( true );
// Arc that should intersect grid lines
SHAPE_ARC arc( VECTOR2I( 50, 0 ), VECTOR2I( 50, 50 ), VECTOR2I( 0, 50 ), 0 );
// Point that should snap to an intersection
VECTOR2I result = helper.AlignToArc( VECTOR2I( 25, 25 ), arc );
// Should snap to the mid point of the arc
BOOST_CHECK_EQUAL( result.x, 50 );
BOOST_CHECK_EQUAL( result.y, 50 );
}
BOOST_FIXTURE_TEST_CASE( LargeGridSegmentSnap, PCBGridHelperTestFixture )
{
// Test with larger grid to verify scaling behavior
helper.SetGridSize( VECTOR2D( 1000, 1000 ) );
SEG seg( VECTOR2I( 500, 0 ), VECTOR2I( 500, 2000 ) );
VECTOR2I result = helper.AlignToSegment( VECTOR2I( 400, 800 ), seg );
// Should snap to the segment at grid intersection
BOOST_CHECK_EQUAL( result.x, 500 );
BOOST_CHECK_EQUAL( result.y, 1000 );
}
BOOST_FIXTURE_TEST_CASE( ZeroLengthSegment, PCBGridHelperTestFixture )
{
// Edge case: zero-length segment (point)
SEG seg( VECTOR2I( 100, 100 ), VECTOR2I( 100, 100 ) );
VECTOR2I result = helper.AlignToSegment( VECTOR2I( 95, 95 ), seg );
// Should snap to the point itself
BOOST_CHECK_EQUAL( result.x, 100 );
BOOST_CHECK_EQUAL( result.y, 100 );
}
BOOST_AUTO_TEST_SUITE_END()