/* * 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 #include #include #include #include /** * 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( aL.m_offset, aR.m_offset, 1 ) && KI_TEST::IsWithin( 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& aExp ) { std::vector 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 m_exp_transforms; }; // clang-format off static const std::vector 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 m_exp_transforms; }; // clang-format off static const std::vector 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& aExp ) { std::vector 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 m_exp_names; }; // clang-format off static const std::vector 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()