Refactor grid_helper to allow testing

Make view and tool manager optional so that we can implement QA on the
actual snapping
This commit is contained in:
Seth Hillbrand 2025-08-05 16:10:17 -07:00
parent fb6bc9ca11
commit 29eee9f126
16 changed files with 869 additions and 14 deletions

View File

@ -37,8 +37,8 @@
#include <settings/app_settings.h>
GRID_HELPER::GRID_HELPER( TOOL_MANAGER* aToolMgr, int aConstructionLayer ) :
m_toolMgr( aToolMgr ), m_snapManager( m_constructionGeomPreview )
GRID_HELPER::GRID_HELPER() :
m_toolMgr( nullptr ), m_snapManager( m_constructionGeomPreview )
{
m_maskTypes = ALL;
m_enableSnap = true;
@ -46,6 +46,21 @@ GRID_HELPER::GRID_HELPER( TOOL_MANAGER* aToolMgr, int aConstructionLayer ) :
m_enableGrid = true;
m_snapItem = std::nullopt;
m_manualGrid = VECTOR2D( 1, 1 );
m_manualVisibleGrid = VECTOR2D( 1, 1 );
m_manualOrigin = VECTOR2I( 0, 0 );
m_manualGridSnapping = true;
}
GRID_HELPER::GRID_HELPER( TOOL_MANAGER* aToolMgr, int aConstructionLayer ) :
GRID_HELPER()
{
m_toolMgr = aToolMgr;
if( !m_toolMgr )
return;
KIGFX::VIEW* view = m_toolMgr->GetView();
KIGFX::RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
@ -72,11 +87,20 @@ GRID_HELPER::GRID_HELPER( TOOL_MANAGER* aToolMgr, int aConstructionLayer ) :
m_toolMgr->GetToolHolder()->RefreshCanvas();
} );
// Initialise manual values from view for compatibility
m_manualGrid = view->GetGAL()->GetGridSize();
m_manualVisibleGrid = view->GetGAL()->GetVisibleGridSize();
m_manualOrigin = VECTOR2I( view->GetGAL()->GetGridOrigin() );
m_manualGridSnapping = view->GetGAL()->GetGridSnapping();
}
GRID_HELPER::~GRID_HELPER()
{
if( !m_toolMgr )
return;
KIGFX::VIEW& view = *m_toolMgr->GetView();
view.Remove( &m_constructionGeomPreview );
@ -89,6 +113,9 @@ KIGFX::ANCHOR_DEBUG* GRID_HELPER::enableAndGetAnchorDebug()
{
static bool permitted = ADVANCED_CFG::GetCfg().m_EnableSnapAnchorsDebug;
if( !m_toolMgr )
return nullptr;
if( permitted && !m_anchorDebug )
{
KIGFX::VIEW& view = *m_toolMgr->GetView();
@ -103,12 +130,16 @@ KIGFX::ANCHOR_DEBUG* GRID_HELPER::enableAndGetAnchorDebug()
void GRID_HELPER::showConstructionGeometry( bool aShow )
{
m_toolMgr->GetView()->SetVisible( &m_constructionGeomPreview, aShow );
if( m_toolMgr )
m_toolMgr->GetView()->SetVisible( &m_constructionGeomPreview, aShow );
}
void GRID_HELPER::updateSnapPoint( const TYPED_POINT2I& aPoint )
{
if( !m_toolMgr )
return;
m_viewSnapPoint.SetPosition( aPoint.m_point );
m_viewSnapPoint.SetSnapTypes( aPoint.m_types );
@ -121,23 +152,26 @@ void GRID_HELPER::updateSnapPoint( const TYPED_POINT2I& aPoint )
VECTOR2I GRID_HELPER::GetGrid() const
{
VECTOR2D size = m_toolMgr->GetView()->GetGAL()->GetGridSize();
VECTOR2D size = m_toolMgr ? m_toolMgr->GetView()->GetGAL()->GetGridSize() : m_manualGrid;
return VECTOR2I( KiROUND( size.x ), KiROUND( size.y ) );
}
VECTOR2D GRID_HELPER::GetVisibleGrid() const
{
return m_toolMgr->GetView()->GetGAL()->GetVisibleGridSize();
return m_toolMgr ? m_toolMgr->GetView()->GetGAL()->GetVisibleGridSize() : m_manualVisibleGrid;
}
VECTOR2I GRID_HELPER::GetOrigin() const
{
VECTOR2D origin = m_toolMgr->GetView()->GetGAL()->GetGridOrigin();
if( m_toolMgr )
{
VECTOR2D origin = m_toolMgr->GetView()->GetGAL()->GetGridOrigin();
return VECTOR2I( origin );
}
return VECTOR2I( origin );
return m_manualOrigin;
}
@ -160,7 +194,7 @@ GRID_HELPER_GRIDS GRID_HELPER::GetSelectionGrid( const SELECTION& aSelection ) c
VECTOR2D GRID_HELPER::GetGridSize( GRID_HELPER_GRIDS aGrid ) const
{
return m_toolMgr->GetView()->GetGAL()->GetGridSize();
return m_toolMgr ? m_toolMgr->GetView()->GetGAL()->GetGridSize() : m_manualGrid;
}
@ -170,12 +204,14 @@ void GRID_HELPER::SetAuxAxes( bool aEnable, const VECTOR2I& aOrigin )
{
m_auxAxis = aOrigin;
m_viewAxis.SetPosition( aOrigin );
m_toolMgr->GetView()->SetVisible( &m_viewAxis, true );
if( m_toolMgr )
m_toolMgr->GetView()->SetVisible( &m_viewAxis, true );
}
else
{
m_auxAxis = std::optional<VECTOR2I>();
m_toolMgr->GetView()->SetVisible( &m_viewAxis, false );
if( m_toolMgr )
m_toolMgr->GetView()->SetVisible( &m_viewAxis, false );
}
}
@ -227,7 +263,8 @@ VECTOR2I GRID_HELPER::Align( const VECTOR2I& aPoint, const VECTOR2D& aGrid ) con
bool GRID_HELPER::canUseGrid() const
{
return m_enableGrid && m_toolMgr->GetView()->GetGAL()->GetGridSnapping();
return m_enableGrid && ( m_toolMgr ? m_toolMgr->GetView()->GetGAL()->GetGridSnapping()
: m_manualGridSnapping );
}

View File

@ -39,10 +39,18 @@
#include <view/view.h>
#include "ee_grid_helper.h"
EE_GRID_HELPER::EE_GRID_HELPER() :
GRID_HELPER()
{
}
EE_GRID_HELPER::EE_GRID_HELPER( TOOL_MANAGER* aToolMgr ) :
GRID_HELPER( aToolMgr, LAYER_SCHEMATIC_ANCHOR )
{
if( !m_toolMgr )
return;
KIGFX::VIEW* view = m_toolMgr->GetView();
m_viewAxis.SetSize( 20000 );
@ -62,6 +70,9 @@ EE_GRID_HELPER::EE_GRID_HELPER( TOOL_MANAGER* aToolMgr ) :
EE_GRID_HELPER::~EE_GRID_HELPER()
{
if( !m_toolMgr )
return;
KIGFX::VIEW* view = m_toolMgr->GetView();
view->Remove( &m_viewAxis );

View File

@ -29,7 +29,7 @@
#include <math/vector2d.h>
#include <origin_viewitem.h>
#include <tool/grid_helper.h>
#include <sch_selection.h>
#include "sch_selection.h"
class SCH_ITEM;
@ -38,6 +38,7 @@ class EE_GRID_HELPER : public GRID_HELPER
{
public:
EE_GRID_HELPER();
EE_GRID_HELPER( TOOL_MANAGER* aToolMgr );
~EE_GRID_HELPER() override;

View File

@ -32,10 +32,11 @@
#include <preview_items/snap_indicator.h>
#include <preview_items/construction_geom.h>
#include <tool/construction_manager.h>
#include <tool/tool_manager.h>
#include <tool/selection.h>
#include <origin_viewitem.h>
class TOOL_MANAGER; // Forward declaration to avoid hard dependency in tests
class EDA_ITEM;
enum GRID_HELPER_GRIDS : int
@ -52,7 +53,9 @@ enum GRID_HELPER_GRIDS : int
class GRID_HELPER
{
friend void TEST_CLEAR_ANCHORS( GRID_HELPER& helper );
public:
GRID_HELPER();
GRID_HELPER( TOOL_MANAGER* aToolMgr, int aConstructionLayer );
virtual ~GRID_HELPER();
@ -60,6 +63,12 @@ public:
VECTOR2D GetVisibleGrid() const;
VECTOR2I GetOrigin() const;
// Manual setters used when no TOOL_MANAGER/View is available (e.g. in tests)
void SetGridSize( const VECTOR2D& aGrid ) { m_manualGrid = aGrid; }
void SetVisibleGridSize( const VECTOR2D& aGrid ) { m_manualVisibleGrid = aGrid; }
void SetOrigin( const VECTOR2I& aOrigin ) { m_manualOrigin = aOrigin; }
void SetGridSnapping( bool aEnable ) { m_manualGridSnapping = aEnable; }
void SetAuxAxes( bool aEnable, const VECTOR2I& aOrigin = VECTOR2I( 0, 0 ) );
virtual VECTOR2I Align( const VECTOR2I& aPoint, GRID_HELPER_GRIDS aGrid ) const
@ -230,6 +239,12 @@ protected:
KIGFX::SNAP_INDICATOR m_viewSnapPoint;
KIGFX::ORIGIN_VIEWITEM m_viewAxis;
// Manual grid parameters used when no TOOL_MANAGER is provided
VECTOR2D m_manualGrid;
VECTOR2D m_manualVisibleGrid;
VECTOR2I m_manualOrigin;
bool m_manualGridSnapping;
private:
/// Show construction geometry (if any) on the canvas.
KIGFX::CONSTRUCTION_GEOM m_constructionGeomPreview;

View File

@ -44,6 +44,7 @@
#include <pcb_group.h>
#include <tool/edit_points.h>
#include <tool/tool_manager.h>
#include <tools/drawing_tool.h>
#include <tools/generator_tool.h>
#include <tools/pcb_picker_tool.h>

View File

@ -29,6 +29,7 @@ using namespace std::placeholders;
#include <pcbnew_settings.h>
#include <view/view_controls.h>
#include <tool/tool_manager.h>
#include <tools/pcb_grid_helper.h>
#include <wx/log.h>

View File

@ -36,6 +36,7 @@
#include <pcb_generator.h>
#include <pcb_edit_frame.h>
#include <spread_footprints.h>
#include <tool/tool_manager.h>
#include <tools/pcb_actions.h>
#include <tools/pcb_selection_tool.h>
#include <tools/edit_tool.h>

View File

@ -142,10 +142,20 @@ std::optional<int64_t> FindSquareDistanceToItem( const BOARD_ITEM& item, const V
} // namespace
PCB_GRID_HELPER::PCB_GRID_HELPER() :
GRID_HELPER(),
m_magneticSettings( nullptr )
{
}
PCB_GRID_HELPER::PCB_GRID_HELPER( TOOL_MANAGER* aToolMgr, MAGNETIC_SETTINGS* aMagneticSettings ) :
GRID_HELPER( aToolMgr, LAYER_ANCHOR ),
m_magneticSettings( aMagneticSettings )
{
if( !m_toolMgr )
return;
KIGFX::VIEW* view = m_toolMgr->GetView();
KIGFX::RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
KIGFX::COLOR4D auxItemsColor = settings->GetLayerColor( LAYER_AUX_ITEMS );
@ -168,6 +178,9 @@ PCB_GRID_HELPER::PCB_GRID_HELPER( TOOL_MANAGER* aToolMgr, MAGNETIC_SETTINGS* aMa
PCB_GRID_HELPER::~PCB_GRID_HELPER()
{
if( !m_toolMgr )
return;
KIGFX::VIEW* view = m_toolMgr->GetView();
view->Remove( &m_viewAxis );

View File

@ -43,8 +43,10 @@ struct PCB_SELECTION_FILTER_OPTIONS;
class PCB_GRID_HELPER : public GRID_HELPER, public BOARD_LISTENER
{
friend class PCBGridHelperTestFixture;
public:
PCB_GRID_HELPER();
PCB_GRID_HELPER( TOOL_MANAGER* aToolMgr, MAGNETIC_SETTINGS* aMagneticSettings );
~PCB_GRID_HELPER() override;

View File

@ -30,6 +30,7 @@
#include <gal/graphics_abstraction_layer.h>
#include <kiplatform/ui.h>
#include <status_popup.h>
#include <tool/tool_manager.h>
#include <tools/pcb_selection_tool.h>
#include <tools/zone_filler_tool.h>
#include <view/view_controls.h>

View File

@ -57,6 +57,7 @@ set( QA_COMMON_SRCS
test_property_holder.cpp
test_reporting.cpp
test_refdes_utils.cpp
test_grid_helper.cpp
test_richio.cpp
test_text_attributes.cpp
test_title_block.cpp

View File

@ -0,0 +1,312 @@
/*
* 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 ) );
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 still work from (0,0) reference
// AlignGrid doesn't use origin, just pure grid alignment
VECTOR2I aligned = helper.AlignGrid( VECTOR2I( 149, 251 ) );
BOOST_CHECK_EQUAL( aligned.x, 100 );
BOOST_CHECK_EQUAL( aligned.y, 300 );
}
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_SUITE_END()

View File

@ -71,6 +71,7 @@ set( QA_EESCHEMA_SRCS
test_junction_helpers.cpp
test_bus_entry_concurrency.cpp
test_lib_part.cpp
test_ee_grid_helper.cpp
test_netlist_exporter_kicad.cpp
test_ee_item.cpp
test_incremental_netlister.cpp

View File

@ -0,0 +1,47 @@
/*
* 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/ee_grid_helper.h>
#include <sch_text.h>
#include <sch_line.h>
#include <sch_shape.h>
#include <sch_junction.h>
#include <layer_ids.h>
BOOST_AUTO_TEST_SUITE( EEGridHelperTest )
BOOST_AUTO_TEST_CASE( ItemGridClassification )
{
EE_GRID_HELPER helper;
SCH_TEXT text;
BOOST_CHECK_EQUAL( helper.GetItemGrid( &text ), GRID_TEXT );
SCH_LINE wire( VECTOR2I( 0, 0 ), LAYER_WIRE );
BOOST_CHECK_EQUAL( helper.GetItemGrid( &wire ), GRID_WIRES );
SCH_LINE graphic( VECTOR2I( 0, 0 ), LAYER_NOTES );
BOOST_CHECK_EQUAL( helper.GetItemGrid( &graphic ), GRID_GRAPHICS );
SCH_JUNCTION junc;
BOOST_CHECK_EQUAL( helper.GetItemGrid( &junc ), GRID_WIRES );
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -47,6 +47,7 @@ set( QA_PCBNEW_SRCS
test_prettifier.cpp
test_libeval_compiler.cpp
test_reference_image_load.cpp
test_pcb_grid_helper.cpp
test_save_load.cpp
test_tracks_cleaner.cpp
test_triangulation.cpp

View File

@ -0,0 +1,410 @@
/*
* 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()