mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
1) Separate out CONSTRAINT types 2) Filter both source and dest pads/vias for drilled holes when doing hole-to-hole checks. We were checking the items being put into the DRC RTree, but not the items we were scanning. 3) Add hole clearance to Board Setup Constraints panel. Fixes https://gitlab.com/kicad/code/kicad/issues/6546 Fixes https://gitlab.com/kicad/code/kicad/issues/4683
322 lines
10 KiB
C++
322 lines
10 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2004-2020 KiCad Developers.
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <pad.h>
|
|
#include <track.h>
|
|
#include <geometry/shape_segment.h>
|
|
#include <geometry/shape_circle.h>
|
|
#include <drc/drc_item.h>
|
|
#include <drc/drc_rule.h>
|
|
#include <drc/drc_test_provider_clearance_base.h>
|
|
#include "drc_rtree.h"
|
|
|
|
/*
|
|
Holes clearance test. Checks pad and via holes for their mechanical clearances.
|
|
Generated errors:
|
|
- DRCE_DRILLED_HOLES_TOO_CLOSE
|
|
|
|
TODO: vias-in-smd-pads check
|
|
*/
|
|
|
|
class DRC_TEST_PROVIDER_HOLE_CLEARANCE : public DRC_TEST_PROVIDER_CLEARANCE_BASE
|
|
{
|
|
public:
|
|
DRC_TEST_PROVIDER_HOLE_CLEARANCE () :
|
|
DRC_TEST_PROVIDER_CLEARANCE_BASE(),
|
|
m_board( nullptr )
|
|
{
|
|
}
|
|
|
|
virtual ~DRC_TEST_PROVIDER_HOLE_CLEARANCE()
|
|
{
|
|
}
|
|
|
|
virtual bool Run() override;
|
|
|
|
virtual const wxString GetName() const override
|
|
{
|
|
return "hole_clearance";
|
|
};
|
|
|
|
virtual const wxString GetDescription() const override
|
|
{
|
|
return "Tests hole to hole spacing";
|
|
}
|
|
|
|
virtual std::set<DRC_CONSTRAINT_TYPE_T> GetConstraintTypes() const override;
|
|
|
|
int GetNumPhases() const override;
|
|
|
|
private:
|
|
bool testHoleAgainstHole( BOARD_ITEM* aItem, SHAPE_CIRCLE* aHole, BOARD_ITEM* aOther );
|
|
|
|
BOARD* m_board;
|
|
DRC_RTREE m_holeTree;
|
|
};
|
|
|
|
|
|
static std::shared_ptr<SHAPE_CIRCLE> getDrilledHoleShape( BOARD_ITEM* aItem )
|
|
{
|
|
if( aItem->Type() == PCB_VIA_T )
|
|
{
|
|
VIA* via = static_cast<VIA*>( aItem );
|
|
return std::make_shared<SHAPE_CIRCLE>( via->GetCenter(), via->GetDrillValue() / 2 );
|
|
}
|
|
else if( aItem->Type() == PCB_PAD_T )
|
|
{
|
|
PAD* pad = static_cast<PAD*>( aItem );
|
|
return std::make_shared<SHAPE_CIRCLE>( pad->GetPosition(), pad->GetDrillSize().x / 2 );
|
|
}
|
|
|
|
return std::make_shared<SHAPE_CIRCLE>( VECTOR2I( 0, 0 ), 0 );
|
|
}
|
|
|
|
|
|
bool DRC_TEST_PROVIDER_HOLE_CLEARANCE::Run()
|
|
{
|
|
m_board = m_drcEngine->GetBoard();
|
|
|
|
DRC_CONSTRAINT worstClearanceConstraint;
|
|
|
|
if( m_drcEngine->QueryWorstConstraint( HOLE_TO_HOLE_CONSTRAINT, worstClearanceConstraint ) )
|
|
{
|
|
m_largestClearance = worstClearanceConstraint.GetValue().Min();
|
|
reportAux( "Worst hole to hole : %d nm", m_largestClearance );
|
|
}
|
|
else
|
|
{
|
|
reportAux( "No hole to hole constraints found..." );
|
|
return false;
|
|
}
|
|
|
|
// This is the number of tests between 2 calls to the progress bar
|
|
const size_t delta = 50;
|
|
size_t count = 0;
|
|
size_t ii = 0;
|
|
|
|
m_holeTree.clear();
|
|
|
|
auto countItems =
|
|
[&]( BOARD_ITEM* item ) -> bool
|
|
{
|
|
if( item->Type() == PCB_PAD_T )
|
|
++count;
|
|
else if( item->Type() == PCB_VIA_T )
|
|
++count;
|
|
|
|
return true;
|
|
};
|
|
|
|
auto addToHoleTree =
|
|
[&]( BOARD_ITEM* item ) -> bool
|
|
{
|
|
if( !reportProgress( ii++, count, delta ) )
|
|
return false;
|
|
|
|
if( item->Type() == PCB_PAD_T )
|
|
{
|
|
PAD* pad = static_cast<PAD*>( item );
|
|
|
|
// We only care about drilled (ie: round) holes
|
|
if( pad->GetDrillSize().x && pad->GetDrillSize().x == pad->GetDrillSize().y )
|
|
m_holeTree.Insert( item, m_largestClearance, F_Cu );
|
|
}
|
|
else if( item->Type() == PCB_VIA_T )
|
|
{
|
|
VIA* via = static_cast<VIA*>( item );
|
|
|
|
// We only care about mechanically drilled (ie: non-laser) holes
|
|
if( via->GetViaType() == VIATYPE::THROUGH )
|
|
m_holeTree.Insert( item, m_largestClearance, F_Cu );
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
if( !reportPhase( _( "Checking hole to hole clearances..." ) ) )
|
|
return false;
|
|
|
|
forEachGeometryItem( { PCB_PAD_T, PCB_VIA_T }, LSET::AllLayersMask(), countItems );
|
|
|
|
count *= 2; // One for adding to tree; one for checking
|
|
|
|
forEachGeometryItem( { PCB_PAD_T, PCB_VIA_T }, LSET::AllLayersMask(), addToHoleTree );
|
|
|
|
std::map< std::pair<BOARD_ITEM*, BOARD_ITEM*>, int> checkedPairs;
|
|
|
|
for( TRACK* track : m_board->Tracks() )
|
|
{
|
|
if( track->Type() != PCB_VIA_T )
|
|
continue;
|
|
|
|
VIA* via = static_cast<VIA*>( track );
|
|
|
|
if( !reportProgress( ii++, count, delta ) )
|
|
break;
|
|
|
|
// We only care about mechanically drilled (ie: non-laser) holes
|
|
if( via->GetViaType() == VIATYPE::THROUGH )
|
|
{
|
|
std::shared_ptr<SHAPE_CIRCLE> holeShape = getDrilledHoleShape( via );
|
|
|
|
m_holeTree.QueryColliding( via, F_Cu, F_Cu,
|
|
// Filter:
|
|
[&]( BOARD_ITEM* other ) -> bool
|
|
{
|
|
BOARD_ITEM* a = via;
|
|
BOARD_ITEM* b = other;
|
|
|
|
// store canonical order so we don't collide in both directions
|
|
// (a:b and b:a)
|
|
if( static_cast<void*>( a ) > static_cast<void*>( b ) )
|
|
std::swap( a, b );
|
|
|
|
if( checkedPairs.count( { a, b } ) )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
checkedPairs[ { a, b } ] = 1;
|
|
return true;
|
|
}
|
|
},
|
|
// Visitor:
|
|
[&]( BOARD_ITEM* other ) -> bool
|
|
{
|
|
return testHoleAgainstHole( via, holeShape.get(), other );
|
|
},
|
|
m_largestClearance );
|
|
}
|
|
}
|
|
|
|
checkedPairs.clear();
|
|
|
|
for( FOOTPRINT* footprint : m_board->Footprints() )
|
|
{
|
|
for( PAD* pad : footprint->Pads() )
|
|
{
|
|
if( !reportProgress( ii++, count, delta ) )
|
|
break;
|
|
|
|
// We only care about drilled (ie: round) holes
|
|
if( pad->GetDrillSize().x && pad->GetDrillSize().x == pad->GetDrillSize().y )
|
|
{
|
|
std::shared_ptr<SHAPE_CIRCLE> holeShape = getDrilledHoleShape( pad );
|
|
|
|
m_holeTree.QueryColliding( pad, F_Cu, F_Cu,
|
|
// Filter:
|
|
[&]( BOARD_ITEM* other ) -> bool
|
|
{
|
|
BOARD_ITEM* a = pad;
|
|
BOARD_ITEM* b = other;
|
|
|
|
// store canonical order so we don't collide in both directions
|
|
// (a:b and b:a)
|
|
if( static_cast<void*>( a ) > static_cast<void*>( b ) )
|
|
std::swap( a, b );
|
|
|
|
if( checkedPairs.count( { a, b } ) )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
checkedPairs[ { a, b } ] = 1;
|
|
return true;
|
|
}
|
|
},
|
|
// Visitor:
|
|
[&]( BOARD_ITEM* other ) -> bool
|
|
{
|
|
return testHoleAgainstHole( pad, holeShape.get(), other );
|
|
},
|
|
m_largestClearance );
|
|
}
|
|
}
|
|
}
|
|
|
|
reportRuleStatistics();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool DRC_TEST_PROVIDER_HOLE_CLEARANCE::testHoleAgainstHole( BOARD_ITEM* aItem, SHAPE_CIRCLE* aHole,
|
|
BOARD_ITEM* aOther )
|
|
{
|
|
if( m_drcEngine->IsErrorLimitExceeded( DRCE_DRILLED_HOLES_TOO_CLOSE ) )
|
|
return false;
|
|
|
|
std::shared_ptr<SHAPE_CIRCLE> otherHole = getDrilledHoleShape( aItem );
|
|
|
|
// Holes with identical locations are allowable
|
|
if( aHole->GetCenter() == otherHole->GetCenter() )
|
|
return true;
|
|
|
|
int actual = ( aHole->GetCenter() - otherHole->GetCenter() ).EuclideanNorm();
|
|
actual = std::max( 0, actual - aHole->GetRadius() - otherHole->GetRadius() );
|
|
|
|
auto constraint = m_drcEngine->EvalRulesForItems( HOLE_CLEARANCE_CONSTRAINT, aItem, aOther );
|
|
int minClearance = constraint.GetValue().Min();
|
|
|
|
accountCheck( constraint.GetParentRule() );
|
|
|
|
if( actual < minClearance )
|
|
{
|
|
std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_DRILLED_HOLES_TOO_CLOSE );
|
|
|
|
m_msg.Printf( _( "(%s min %s; actual %s)" ),
|
|
constraint.GetName(),
|
|
MessageTextFromValue( userUnits(), minClearance ),
|
|
MessageTextFromValue( userUnits(), actual ) );
|
|
|
|
drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + m_msg );
|
|
drce->SetItems( aItem, aOther );
|
|
drce->SetViolatingRule( constraint.GetParentRule() );
|
|
|
|
reportViolation( drce, (wxPoint) aHole->GetCenter() );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
int DRC_TEST_PROVIDER_HOLE_CLEARANCE::GetNumPhases() const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
|
|
std::set<DRC_CONSTRAINT_TYPE_T> DRC_TEST_PROVIDER_HOLE_CLEARANCE::GetConstraintTypes() const
|
|
{
|
|
return { HOLE_TO_HOLE_CONSTRAINT };
|
|
}
|
|
|
|
|
|
namespace detail
|
|
{
|
|
static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_HOLE_CLEARANCE> dummy;
|
|
}
|