2019-01-11 22:03:45 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
2021-10-06 02:46:53 +00:00
|
|
|
* Copyright (C) 2018-2021 KiCad Developers, see AUTHORS.TXT for contributors.
|
2019-01-11 22:03:45 +00:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2021-02-06 22:20:31 +00:00
|
|
|
#include <qa_utils/wx_utils/unit_test_utils.h>
|
2019-01-11 22:03:45 +00:00
|
|
|
|
|
|
|
#include <pcbnew_utils/board_construction_utils.h>
|
|
|
|
#include <pcbnew_utils/board_file_utils.h>
|
2020-11-12 20:19:22 +00:00
|
|
|
#include <board.h>
|
2021-06-06 19:33:45 +00:00
|
|
|
#include <board_design_settings.h>
|
2020-11-12 20:19:22 +00:00
|
|
|
#include <footprint.h>
|
2020-11-14 18:11:28 +00:00
|
|
|
#include <pcb_marker.h>
|
2020-09-14 17:54:14 +00:00
|
|
|
#include <drc/drc_engine.h>
|
2020-03-16 11:05:01 +00:00
|
|
|
#include <drc/drc_item.h>
|
2020-02-28 00:05:40 +00:00
|
|
|
#include <widgets/ui_common.h>
|
2019-01-11 22:03:45 +00:00
|
|
|
|
|
|
|
#include "../board_test_utils.h"
|
|
|
|
|
|
|
|
|
|
|
|
struct COURTYARD_TEST_FIXTURE
|
|
|
|
{
|
|
|
|
const KI_TEST::BOARD_DUMPER m_dumper;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
BOOST_FIXTURE_TEST_SUITE( DrcCourtyardInvalid, COURTYARD_TEST_FIXTURE )
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2020-11-13 15:15:52 +00:00
|
|
|
* A simple mock footprint with a set of courtyard rectangles and some other information
|
2019-01-11 22:03:45 +00:00
|
|
|
*/
|
2020-11-13 15:15:52 +00:00
|
|
|
struct COURTYARD_INVALID_TEST_FP
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
2020-11-13 15:15:52 +00:00
|
|
|
std::string m_refdes; /// Footprint Ref-Des (for identifying DRC errors)
|
2020-03-16 11:05:01 +00:00
|
|
|
std::vector<SEG> m_segs; /// List of segments that will be placed on the courtyard
|
2020-11-13 15:15:52 +00:00
|
|
|
VECTOR2I m_pos; /// Footprint position
|
2019-01-11 22:03:45 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct COURTYARD_INVALID_INFO
|
|
|
|
{
|
|
|
|
std::string m_refdes;
|
2020-03-16 11:05:01 +00:00
|
|
|
int m_drc_error_code;
|
2019-01-11 22:03:45 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
std::ostream& operator<<( std::ostream& os, const COURTYARD_INVALID_INFO& aInvalid )
|
|
|
|
{
|
|
|
|
os << "COURTYARD_INVALID_INFO[ " << aInvalid.m_refdes;
|
|
|
|
os << ", code: " << aInvalid.m_drc_error_code << "]";
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct COURTYARD_INVALID_CASE
|
|
|
|
{
|
2020-11-13 15:15:52 +00:00
|
|
|
std::string m_case_name;
|
|
|
|
std::vector<COURTYARD_INVALID_TEST_FP> m_mods;
|
|
|
|
std::vector<COURTYARD_INVALID_INFO> m_exp_errors;
|
2019-01-11 22:03:45 +00:00
|
|
|
};
|
|
|
|
|
2020-03-16 11:05:01 +00:00
|
|
|
|
2019-01-11 22:03:45 +00:00
|
|
|
// clang-format off
|
|
|
|
static const std::vector<COURTYARD_INVALID_CASE> courtyard_invalid_cases =
|
|
|
|
{
|
|
|
|
{
|
2020-10-21 03:48:06 +00:00
|
|
|
// Empty board has no footprints to be invalid
|
2019-01-11 22:03:45 +00:00
|
|
|
"empty board",
|
|
|
|
{},
|
|
|
|
{},
|
|
|
|
},
|
|
|
|
{
|
2020-10-21 03:48:06 +00:00
|
|
|
"single footprint, no courtyard",
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
{
|
|
|
|
"U1",
|
|
|
|
{}, // Empty courtyard layer
|
|
|
|
{ 0, 0 },
|
|
|
|
},
|
|
|
|
},
|
2020-10-21 03:48:06 +00:00
|
|
|
{ // one error: the footprint has no courtyard
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
"U1",
|
2020-05-07 13:35:50 +00:00
|
|
|
DRCE_MISSING_COURTYARD,
|
2019-01-11 22:03:45 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2020-10-21 03:48:06 +00:00
|
|
|
"single footprint, unclosed courtyard",
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
{
|
|
|
|
"U1",
|
|
|
|
{ // Unclosed polygon
|
|
|
|
{ { 0, 0 }, { 0, Millimeter2iu( 10 ) } },
|
|
|
|
{ { 0, Millimeter2iu( 10 ) }, { Millimeter2iu( 10 ), Millimeter2iu( 10 ) } },
|
|
|
|
},
|
|
|
|
{ 0, 0 },
|
|
|
|
},
|
|
|
|
},
|
2020-10-21 03:48:06 +00:00
|
|
|
{ // one error: the footprint has malformed courtyard
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
"U1",
|
2020-05-07 13:35:50 +00:00
|
|
|
DRCE_MALFORMED_COURTYARD,
|
2019-01-11 22:03:45 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2020-10-21 03:48:06 +00:00
|
|
|
"single footprint, disjoint courtyard",
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
{
|
|
|
|
"U1",
|
|
|
|
{ // Unclosed polygon - two disjoint segments
|
|
|
|
{ { 0, 0 }, { 0, Millimeter2iu( 10 ) } },
|
|
|
|
{ { Millimeter2iu( 10 ), 0 }, { Millimeter2iu( 10 ), Millimeter2iu( 10 ) } },
|
|
|
|
},
|
|
|
|
{ 0, 0 },
|
|
|
|
},
|
|
|
|
},
|
2020-11-13 15:15:52 +00:00
|
|
|
{ // one error: the footprint has malformed courtyard
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
"U1",
|
2020-11-13 15:15:52 +00:00
|
|
|
DRCE_MALFORMED_COURTYARD,
|
2019-01-11 22:03:45 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2020-10-21 03:48:06 +00:00
|
|
|
"two footprints, one OK, one malformed",
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
{
|
|
|
|
"U1",
|
|
|
|
{ // Closed polygon - triangle
|
|
|
|
{
|
|
|
|
{ 0, 0 },
|
|
|
|
{ 0, Millimeter2iu( 10 ) },
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{ 0, Millimeter2iu( 10 ) },
|
|
|
|
{ Millimeter2iu( 10 ), Millimeter2iu( 10 ) }
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{ Millimeter2iu( 10 ), Millimeter2iu( 10 ) },
|
|
|
|
{ 0, 0 }
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{ 0, 0 },
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"U2",
|
|
|
|
{ // Un-Closed polygon - one seg
|
|
|
|
{
|
|
|
|
{ 0, 0 },
|
|
|
|
{ 0, Millimeter2iu( 10 ) },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{ 0, 0 },
|
|
|
|
},
|
|
|
|
},
|
2020-11-13 15:15:52 +00:00
|
|
|
{ // one error: the second footprint has malformed courtyard
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
"U2",
|
2020-11-13 15:15:52 +00:00
|
|
|
DRCE_MALFORMED_COURTYARD,
|
2019-01-11 22:03:45 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
// clang-format on
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2020-11-13 15:15:52 +00:00
|
|
|
* Construct a #FOOTPRINT to use in a courtyard test from a #COURTYARD_TEST_FP
|
2019-01-11 22:03:45 +00:00
|
|
|
* definition.
|
|
|
|
*/
|
2020-11-13 15:15:52 +00:00
|
|
|
std::unique_ptr<FOOTPRINT> MakeInvalidCourtyardTestFP( BOARD& aBoard,
|
|
|
|
const COURTYARD_INVALID_TEST_FP& aFPDef )
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
2020-11-13 15:15:52 +00:00
|
|
|
std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( &aBoard );
|
2019-01-11 22:03:45 +00:00
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
for( const SEG& seg : aFPDef.m_segs )
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
const PCB_LAYER_ID layer = F_CrtYd; // aRect.m_front ? F_CrtYd : B_CrtYd;
|
|
|
|
const int width = Millimeter2iu( 0.1 );
|
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
KI_TEST::DrawSegment( *footprint, seg, width, layer );
|
2019-01-11 22:03:45 +00:00
|
|
|
}
|
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
footprint->SetReference( aFPDef.m_refdes );
|
2019-01-11 22:03:45 +00:00
|
|
|
|
|
|
|
// As of 2019-01-17, this has to go after adding the courtyards,
|
|
|
|
// or all the poly sets are empty when DRC'd
|
2020-11-13 15:15:52 +00:00
|
|
|
footprint->SetPosition( (wxPoint) aFPDef.m_pos );
|
2019-01-11 22:03:45 +00:00
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
return footprint;
|
2019-01-11 22:03:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
std::unique_ptr<BOARD> MakeBoard( const std::vector<COURTYARD_INVALID_TEST_FP>& aTestFPDefs )
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
auto board = std::make_unique<BOARD>();
|
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
for( const COURTYARD_INVALID_TEST_FP& fpDef : aTestFPDefs )
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
2020-11-13 15:15:52 +00:00
|
|
|
std::unique_ptr<FOOTPRINT> footprint = MakeInvalidCourtyardTestFP( *board, fpDef );
|
2019-01-11 22:03:45 +00:00
|
|
|
|
2020-11-13 15:15:52 +00:00
|
|
|
board->Add( footprint.release() );
|
2019-01-11 22:03:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return board;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2020-11-14 18:11:28 +00:00
|
|
|
* Check if a #PCB_MARKER is described by a particular #COURTYARD_INVALID_INFO object.
|
2019-01-11 22:03:45 +00:00
|
|
|
*/
|
2020-11-14 18:11:28 +00:00
|
|
|
static bool InvalidMatchesExpected( BOARD& aBoard, const PCB_MARKER& aMarker,
|
2020-03-16 11:05:01 +00:00
|
|
|
const COURTYARD_INVALID_INFO& aInvalid )
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
2020-08-11 13:33:16 +00:00
|
|
|
auto reporter = std::static_pointer_cast<DRC_ITEM>( aMarker.GetRCItem() );
|
2020-11-13 15:15:52 +00:00
|
|
|
const FOOTPRINT* item_a = dynamic_cast<FOOTPRINT*>( aBoard.GetItem( reporter->GetMainItemID() ) );
|
2019-01-11 22:03:45 +00:00
|
|
|
|
2021-06-09 19:32:58 +00:00
|
|
|
// This one is more than just a mismatch!
|
2020-04-24 13:36:10 +00:00
|
|
|
if( reporter->GetAuxItemID() != niluuid )
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
BOOST_WARN_MESSAGE( false, "Expected no auxiliary item for invalid courtyard DRC." );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-07-06 17:32:34 +00:00
|
|
|
if( item_a == nullptr )
|
|
|
|
{
|
|
|
|
BOOST_ERROR( "Could not get board DRC item." );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-11 22:03:45 +00:00
|
|
|
if( item_a->GetReference() != aInvalid.m_refdes )
|
|
|
|
return false;
|
|
|
|
|
2020-03-16 11:05:01 +00:00
|
|
|
if( reporter->GetErrorCode() != aInvalid.m_drc_error_code )
|
2019-01-11 22:03:45 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check that the produced markers match the expected. This does NOT
|
|
|
|
* check ordering, as that is not part of the contract of the DRC function.
|
|
|
|
*
|
|
|
|
* @param aMarkers list of markers produced by the DRC
|
|
|
|
* @param aCollisions list of expected collisions
|
|
|
|
*/
|
2020-05-17 14:40:06 +00:00
|
|
|
static void CheckInvalidsMatchExpected( BOARD& aBoard,
|
2020-11-14 18:11:28 +00:00
|
|
|
const std::vector<std::unique_ptr<PCB_MARKER>>& aMarkers,
|
2020-05-17 14:40:06 +00:00
|
|
|
const std::vector<COURTYARD_INVALID_INFO>& aExpInvalids )
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
KI_TEST::CheckUnorderedMatches( aExpInvalids, aMarkers,
|
2019-07-01 21:01:33 +00:00
|
|
|
[&]( const COURTYARD_INVALID_INFO& aInvalid,
|
2020-11-14 18:11:28 +00:00
|
|
|
const std::unique_ptr<PCB_MARKER>& aMarker )
|
2019-07-01 21:01:33 +00:00
|
|
|
{
|
2019-01-11 22:03:45 +00:00
|
|
|
return InvalidMatchesExpected( aBoard, *aMarker, aInvalid );
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-05-17 14:40:06 +00:00
|
|
|
void DoCourtyardInvalidTest( const COURTYARD_INVALID_CASE& aCase,
|
|
|
|
const KI_TEST::BOARD_DUMPER& aDumper )
|
2019-01-11 22:03:45 +00:00
|
|
|
{
|
|
|
|
auto board = MakeBoard( aCase.m_mods );
|
|
|
|
|
|
|
|
// Dump if env var set
|
|
|
|
aDumper.DumpBoardToFile( *board, aCase.m_case_name );
|
|
|
|
|
2020-05-31 21:42:04 +00:00
|
|
|
BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
|
|
|
|
|
|
|
|
// do the overlap tests - that's a different test, but if not set,
|
|
|
|
// the invalid courtyard checks don't run either
|
|
|
|
bds.m_DRCSeverities[ DRCE_OVERLAPPING_FOOTPRINTS ] = RPT_SEVERITY_ERROR;
|
|
|
|
|
|
|
|
// we will also check for missing courtyards here
|
|
|
|
bds.m_DRCSeverities[ DRCE_MISSING_COURTYARD ] = RPT_SEVERITY_ERROR;
|
2019-01-11 22:03:45 +00:00
|
|
|
|
|
|
|
// list of markers to collect
|
2020-11-14 18:11:28 +00:00
|
|
|
std::vector<std::unique_ptr<PCB_MARKER>> markers;
|
2019-01-11 22:03:45 +00:00
|
|
|
|
2020-09-14 17:54:14 +00:00
|
|
|
DRC_ENGINE drcEngine( board.get(), &board->GetDesignSettings() );
|
|
|
|
|
|
|
|
drcEngine.InitEngine( wxFileName() );
|
|
|
|
|
|
|
|
drcEngine.SetViolationHandler(
|
|
|
|
[&]( const std::shared_ptr<DRC_ITEM>& aItem, wxPoint aPos )
|
2020-02-28 00:05:40 +00:00
|
|
|
{
|
2020-09-14 17:54:14 +00:00
|
|
|
if( aItem->GetErrorCode() == DRCE_OVERLAPPING_FOOTPRINTS
|
2020-09-15 20:49:51 +00:00
|
|
|
|| aItem->GetErrorCode() == DRCE_MALFORMED_COURTYARD
|
2020-09-14 17:54:14 +00:00
|
|
|
|| aItem->GetErrorCode() == DRCE_MISSING_COURTYARD )
|
|
|
|
{
|
2020-11-14 18:11:28 +00:00
|
|
|
markers.push_back( std::make_unique<PCB_MARKER>( aItem, aPos ) );
|
2020-09-14 17:54:14 +00:00
|
|
|
}
|
2020-02-28 00:05:40 +00:00
|
|
|
} );
|
2019-01-11 22:03:45 +00:00
|
|
|
|
2020-10-28 17:55:57 +00:00
|
|
|
drcEngine.RunTests( EDA_UNITS::MILLIMETRES, true, false );
|
2019-01-11 22:03:45 +00:00
|
|
|
|
|
|
|
CheckInvalidsMatchExpected( *board, markers, aCase.m_exp_errors );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE( InvalidCases )
|
|
|
|
{
|
|
|
|
for( const auto& c : courtyard_invalid_cases )
|
|
|
|
{
|
|
|
|
BOOST_TEST_CONTEXT( c.m_case_name )
|
|
|
|
{
|
|
|
|
DoCourtyardInvalidTest( c, m_dumper );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-21 03:48:06 +00:00
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|