kicad/qa/unittests/common/test_array_options.cpp

514 lines
16 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2019 KiCad Developers, see AUTHORS.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
*/
/**
* @file test_array_options.cpp
* Test suite for #ARRAY_OPTIONS
*/
#include <qa_utils/geometry/geometry.h>
#include <qa_utils/wx_utils/unit_test_utils.h>
#include <base_units.h>
#include <trigo.h>
#include <array_options.h>
/**
* Define a stream function for logging this type.
*
* TODO: convert to boost_test_print_type when Boost minver > 1.64
*/
std::ostream& operator<<( std::ostream& os, const ARRAY_OPTIONS::TRANSFORM& aObj )
{
os << "TRANSFORM[ " << aObj.m_offset << " r " << aObj.m_rotation.AsDegrees() << "deg"
<< " ]";
return os;
}
/**
* Predicate to see if a #ARRAY_OPTIONS::TRANSFORM is equal or nearly equal
*/
bool TransformIsClose( const ARRAY_OPTIONS::TRANSFORM& aL, const ARRAY_OPTIONS::TRANSFORM& aR )
{
return KI_TEST::IsVecWithinTol<VECTOR2I>( aL.m_offset, aR.m_offset, 1 )
&& KI_TEST::IsWithin<double>( aL.m_rotation.AsDegrees(),
aR.m_rotation.AsDegrees(), 0.001 );
}
/**
* Generate all array transforms for an array descriptor and compare
* against a list of expected transforms
* @param aOpts the array descriptor
* @param aPos the position of the reference item
* @param aExp expected transform list
*/
void CheckArrayTransforms( const ARRAY_OPTIONS& aOpts, const VECTOR2I& aPos,
const std::vector<ARRAY_OPTIONS::TRANSFORM>& aExp )
{
std::vector<ARRAY_OPTIONS::TRANSFORM> transforms;
for( int i = 0; i < aOpts.GetArraySize(); ++i )
{
transforms.push_back( aOpts.GetTransform( i, aPos ) );
}
BOOST_CHECK_EQUAL( transforms.size(), aExp.size() );
for( unsigned i = 0; i < std::min( transforms.size(), aExp.size() ); ++i )
{
BOOST_TEST_CONTEXT( "Index " << i )
{
BOOST_CHECK_PREDICATE( TransformIsClose, ( transforms[i] )( aExp[i] ) );
}
}
}
/**
* Declare the test suite
*/
BOOST_AUTO_TEST_SUITE( ArrayOptions )
struct GRID_ARRAY_GEOM_PARAMS
{
int m_nx;
int m_ny;
VECTOR2I m_delta;
VECTOR2I m_offset;
int m_stagger;
bool m_stagger_by_row;
bool m_alternate_numbers;
bool m_h_then_v;
};
struct GRID_ARRAY_TEST_CASE
{
std::string m_case_name;
GRID_ARRAY_GEOM_PARAMS m_geom;
VECTOR2I m_item_pos;
std::vector<ARRAY_OPTIONS::TRANSFORM> m_exp_transforms;
};
// clang-format off
static const std::vector<GRID_ARRAY_TEST_CASE> grid_geom_cases = {
{
"2x3 rect grid",
{
2,
3,
{ schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 2 ) },
{ 0, 0 },
1,
true,
false,
true,
},
{ 0, 0 },
{
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 4 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 4 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
},
},
{
"2x3 offset grid",
{
2,
3,
{ schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 2 ) },
{ schIUScale.mmToIU( 0.1 ), schIUScale.mmToIU( 0.2 ) },
1,
true,
false,
true,
},
{ 0, 0 },
{
// add the offsets for each positions
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 0.2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 0.1 ), schIUScale.mmToIU( 2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2.1 ), schIUScale.mmToIU( 2.2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 0.2 ), schIUScale.mmToIU( 4.0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2.2 ), schIUScale.mmToIU( 4.2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
},
},
{
"2x3 stagger rows",
{
2,
3,
{ schIUScale.mmToIU( 3 ), schIUScale.mmToIU( 2 ) },
{ 0, 0 },
3,
true,
false,
true,
},
{ 0, 0 },
{
// add the offsets for each positions
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 3 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 1 ), schIUScale.mmToIU( 2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 4 ), schIUScale.mmToIU( 2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 4 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 5 ), schIUScale.mmToIU( 4 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
},
},
{
"2x3 stagger cols",
{
2,
3,
{ schIUScale.mmToIU( 3 ), schIUScale.mmToIU( 2 ) },
{ 0, 0 },
2,
false,
false,
true,
},
{ 0, 0 },
{
// add the offsets for each positions
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 3 ), schIUScale.mmToIU( 1 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 3 ), schIUScale.mmToIU( 3 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 4 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 3 ), schIUScale.mmToIU( 5 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
},
},
{
"2x3 rect alternate",
{
2,
3,
{ schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 2 ) },
{ 0, 0 },
1,
true,
true,
true,
},
{ 0, 0 },
{
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 4 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 4 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
},
},
{
"2x3 rect v then h",
{
2,
3,
{ schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 2 ) },
{ 0, 0 },
1,
true,
false,
false,
},
{ 0, 0 },
{
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 4 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 2 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( 2 ), schIUScale.mmToIU( 4 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
},
},
};
// clang-format on
/**
* Test of grid array geometry
*/
BOOST_AUTO_TEST_CASE( GridGeometry )
{
for( const auto& c : grid_geom_cases )
{
BOOST_TEST_CONTEXT( c.m_case_name )
{
ARRAY_GRID_OPTIONS grid_opts;
grid_opts.m_nx = c.m_geom.m_nx;
grid_opts.m_ny = c.m_geom.m_ny;
grid_opts.m_delta = c.m_geom.m_delta;
grid_opts.m_offset = c.m_geom.m_offset;
grid_opts.m_stagger = c.m_geom.m_stagger;
grid_opts.m_stagger_rows = c.m_geom.m_stagger_by_row;
grid_opts.m_reverseNumberingAlternate = c.m_geom.m_alternate_numbers;
grid_opts.m_horizontalThenVertical = c.m_geom.m_h_then_v;
CheckArrayTransforms( grid_opts, c.m_item_pos, c.m_exp_transforms );
}
}
}
struct CIRC_ARRAY_GEOM_PARAMS
{
int n;
double angle_offset;
VECTOR2I centre;
bool rotate;
};
struct CIRC_ARRAY_TEST_CASE
{
std::string m_case_name;
CIRC_ARRAY_GEOM_PARAMS m_geom;
VECTOR2I m_item_pos;
std::vector<ARRAY_OPTIONS::TRANSFORM> m_exp_transforms;
};
// clang-format off
static const std::vector<CIRC_ARRAY_TEST_CASE> circ_geom_cases = {
{
"Quad, no rotate items",
{
4,
0,
{ 0, 0 },
false,
},
{ schIUScale.mmToIU( 10 ), 0 },
{
// diamond shape
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( -10 ), schIUScale.mmToIU( -10 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( -20 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ {schIUScale.mmToIU( -10 ), schIUScale.mmToIU( 10 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
},
},
{
"Quad, rotate items",
{
4,
0,
{ 0, 0 },
true,
},
{ schIUScale.mmToIU( 10 ), 0 },
{
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
{ { schIUScale.mmToIU( -10 ), schIUScale.mmToIU( -10 ) }, EDA_ANGLE( 90.0, DEGREES_T ) },
{ { schIUScale.mmToIU( -20 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 180.0, DEGREES_T ) },
{ {schIUScale.mmToIU( -10 ), schIUScale.mmToIU( 10 ) }, EDA_ANGLE( 270.0, DEGREES_T ) },
},
},
{
"Three pts, 90 deg angle",
{
3,
45.0,
{ 0, 0 },
true,
},
{ schIUScale.mmToIU( 10 ), 0 },
{
{ { schIUScale.mmToIU( 0 ), schIUScale.mmToIU( 0 ) }, EDA_ANGLE( 0.0, DEGREES_T ) },
// 10 * [ 1-sin(45), sin(45) ]
{ { schIUScale.mmToIU( -2.9289321881 ), schIUScale.mmToIU( -7.0710678118 ) }, EDA_ANGLE( 45.0, DEGREES_T ) },
{ { schIUScale.mmToIU( -10 ), schIUScale.mmToIU( -10 ) }, EDA_ANGLE( 90.0, DEGREES_T ) },
},
},
};
// clang-format on
/**
* Test of circular array geometry
*/
BOOST_AUTO_TEST_CASE( CircularGeometry )
{
for( const auto& c : circ_geom_cases )
{
BOOST_TEST_CONTEXT( c.m_case_name )
{
ARRAY_CIRCULAR_OPTIONS grid_opts;
grid_opts.m_nPts = c.m_geom.n;
grid_opts.m_angle = EDA_ANGLE( c.m_geom.angle_offset, DEGREES_T );
grid_opts.m_centre = c.m_geom.centre;
grid_opts.m_rotateItems = c.m_geom.rotate;
CheckArrayTransforms( grid_opts, c.m_item_pos, c.m_exp_transforms );
}
}
}
/**
* Generate all array names and check against expected
* @param aOpts the array descriptor
* @param aExp expected name list
*/
void CheckArrayNumbering( const ARRAY_OPTIONS& aOpts, const std::vector<std::string>& aExp )
{
std::vector<std::string> names;
for( int i = 0; i < aOpts.GetArraySize(); ++i )
{
names.push_back( aOpts.GetItemNumber( i ).ToStdString() );
}
BOOST_CHECK_EQUAL_COLLECTIONS( names.begin(), names.end(), aExp.begin(), aExp.end() );
}
struct GRID_ARRAY_NAMING_PARAMS
{
ARRAY_AXIS::NUMBERING_TYPE m_pri_type;
ARRAY_AXIS::NUMBERING_TYPE m_sec_type;
std::string m_start_at_x;
std::string m_start_at_y;
bool m_2d_numbering;
bool m_h_then_v;
int m_nx;
int m_ny;
};
struct GRID_ARRAY_NAMING_CASE
{
std::string m_case_name;
GRID_ARRAY_NAMING_PARAMS m_prms;
std::vector<std::string> m_exp_names;
};
// clang-format off
static const std::vector<GRID_ARRAY_NAMING_CASE> grid_name_cases = {
{
"Linear grid",
{
ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC,
ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC, // doesn't matter here
"1",
"2",
false,
false, // doesn't matter
2,
3,
},
{ "1", "2", "3", "4", "5", "6" },
},
{
// Tests a 2d grid
"2D grid A1",
{
ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_ALPHA_FULL,
ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC,
"A",
"1",
true,
true,
2,
3,
},
{ "A1", "B1", "A2", "B2", "A3", "B3" },
},
{
// Tests a 2d grid
"2D grid 11",
{
ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC,
ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC,
"1",
"1",
true,
false,
2,
3,
},
// moving down the "long axis" first
// so the first coordinate has a range of 1-3, the second 1-2
{ "11", "21", "31", "12", "22", "32" },
},
{
// Tests a 2d grid, with different types and offsets (and alphabet wrap)
"2D grid offsets",
{
ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC,
ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_ALPHA_FULL,
"5",
"Z",
true,
true,
2,
3,
},
{ "5Z", "6Z", "5AA", "6AA", "5AB", "6AB" },
},
};
// clang-format on
/**
* Test of grid array geometry
*/
BOOST_AUTO_TEST_CASE( GridNaming )
{
for( const auto& c : grid_name_cases )
{
BOOST_TEST_CONTEXT( c.m_case_name )
{
ARRAY_GRID_OPTIONS grid_opts;
grid_opts.m_nx = c.m_prms.m_nx;
grid_opts.m_ny = c.m_prms.m_ny;
grid_opts.m_horizontalThenVertical = c.m_prms.m_h_then_v;
grid_opts.m_pri_axis.SetAxisType( c.m_prms.m_pri_type );
grid_opts.m_sec_axis.SetAxisType( c.m_prms.m_sec_type );
grid_opts.m_pri_axis.SetOffset( c.m_prms.m_start_at_x );
grid_opts.m_sec_axis.SetOffset( c.m_prms.m_start_at_y );
grid_opts.m_2dArrayNumbering = c.m_prms.m_2d_numbering;
// other grid settings (geom) can be defaulted, as they don't affect numbering
CheckArrayNumbering( grid_opts, c.m_exp_names );
}
}
}
BOOST_AUTO_TEST_SUITE_END()