From 6578a76b72f93bed5c7d1c444507b1ff885b97f0 Mon Sep 17 00:00:00 2001 From: Tomasz Wlostowski Date: Fri, 18 Sep 2020 16:24:09 +0200 Subject: [PATCH] drc: first R-tree based test (silk to pad clearance) --- pcbnew/CMakeLists.txt | 1 + pcbnew/drc/drc_engine.cpp | 44 +- pcbnew/drc/drc_engine.h | 13 +- pcbnew/drc/drc_item.cpp | 10 + pcbnew/drc/drc_item.h | 6 +- pcbnew/drc/drc_rtree.h | 412 +++++++++++++++++++ pcbnew/drc/drc_test_provider.h | 11 + pcbnew/drc/drc_test_provider_silk_to_pad.cpp | 243 +++++++++++ 8 files changed, 719 insertions(+), 21 deletions(-) create mode 100644 pcbnew/drc/drc_rtree.h create mode 100644 pcbnew/drc/drc_test_provider_silk_to_pad.cpp diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 392fa7cc7b..de80b2c211 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -249,6 +249,7 @@ set( PCBNEW_DRC_SRCS drc/drc_test_provider_misc.cpp drc/drc_test_provider_track_width.cpp drc/drc_test_provider_via_diameter.cpp + drc/drc_test_provider_silk_to_pad.cpp ) set( PCBNEW_NETLIST_SRCS diff --git a/pcbnew/drc/drc_engine.cpp b/pcbnew/drc/drc_engine.cpp index e82cf4b958..624f369d82 100644 --- a/pcbnew/drc/drc_engine.cpp +++ b/pcbnew/drc/drc_engine.cpp @@ -127,6 +127,10 @@ void DRC_ENGINE::loadImplicitRules() holeClearanceConstraint.Value().SetMin( 0 ); rule->AddConstraint( courtyardClearanceConstraint ); + DRC_CONSTRAINT silkToPadClearanceConstraint( DRC_CONSTRAINT_TYPE_SILK_TO_PAD ); + silkToPadClearanceConstraint.Value().SetMin( 0 ); + rule->AddConstraint( silkToPadClearanceConstraint ); + // 2) micro-via specific defaults (new DRC doesn't treat microvias in any special way) DRC_RULE* uViaRule = createImplicitRule( _( "board setup micro-via constraints" )); @@ -274,14 +278,14 @@ void DRC_ENGINE::loadRules( const wxFileName& aPath ) { DRC_RULES_PARSER parser( fp, aPath.GetFullPath() ); parser.Parse( rules, m_reporter ); - } + } // Copy the rules into the member variable afterwards so that if Parse() throws then // the possibly malformed rules won't contaminate the current ruleset. for( DRC_RULE* rule : rules ) m_rules.push_back( rule ); - } + } } @@ -402,7 +406,12 @@ void DRC_ENGINE::RunTests( EDA_UNITS aUnits, bool aTestTracksAgainstZones, int phases = 0; for( DRC_TEST_PROVIDER* provider : m_testProviders ) + { + if( provider->IsEnabled() ) + { phases += provider->GetNumPhases(); + } + } m_progressReporter->AddPhases( phases ); } @@ -417,6 +426,9 @@ void DRC_ENGINE::RunTests( EDA_UNITS aUnits, bool aTestTracksAgainstZones, for( DRC_TEST_PROVIDER* provider : m_testProviders ) { + if( !provider->IsEnabled() ) + continue; + drc_dbg( 0, "Running test provider: '%s'\n", provider->GetName() ); ReportAux( wxString::Format( "Run DRC provider: '%s'", provider->GetName() ) ); @@ -479,30 +491,30 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRulesForItems( DRC_CONSTRAINT_TYPE_T aConstraintI if( m_constraintMap.count( aConstraintId ) ) { - std::vector* ruleset = m_constraintMap[ aConstraintId ]; + std::vector* ruleset = m_constraintMap[ aConstraintId ]; - // Last matching rule wins, so process in reverse order - for( int ii = (int) ruleset->size() - 1; ii >= 0; --ii ) - { - const CONSTRAINT_WITH_CONDITIONS* rcons = ruleset->at( ii ); - implicit = rcons->parentRule && rcons->parentRule->m_Implicit; + // Last matching rule wins, so process in reverse order + for( int ii = (int) ruleset->size() - 1; ii >= 0; --ii ) + { + const CONSTRAINT_WITH_CONDITIONS* rcons = ruleset->at( ii ); + implicit = rcons->parentRule && rcons->parentRule->m_Implicit; - REPORT( "" ) + REPORT( "" ) if( aConstraintId == DRC_CONSTRAINT_TYPE_CLEARANCE ) - { + { int clearance = rcons->constraint.m_Value.Min(); REPORT( wxString::Format( implicit ? _( "Checking %s; clearance: %s." ) : _( "Checking rule %s; clearance: %s."), rcons->constraint.GetName(), MessageTextFromValue( UNITS, clearance, true ) ) ) - } - else - { + } + else + { REPORT( wxString::Format( implicit ? _( "Checking %s." ) : _( "Checking rule %s."), rcons->constraint.GetName() ) ) - } + } if( aLayer != UNDEFINED_LAYER && !rcons->layerTest.test( aLayer ) ) { @@ -694,8 +706,8 @@ std::vector DRC_ENGINE::QueryConstraintsById( DRC_CONSTRAINT_TYP if( m_constraintMap.count( constraintID ) ) { - for ( CONSTRAINT_WITH_CONDITIONS* c : *m_constraintMap[ constraintID ] ) - rv.push_back( c->constraint ); + for ( CONSTRAINT_WITH_CONDITIONS* c : *m_constraintMap[constraintID] ) + rv.push_back( c->constraint ); } return rv; diff --git a/pcbnew/drc/drc_engine.h b/pcbnew/drc/drc_engine.h index b7f976b4d9..d98a9c1ae6 100644 --- a/pcbnew/drc/drc_engine.h +++ b/pcbnew/drc/drc_engine.h @@ -81,9 +81,15 @@ std::function& aItem, wxPoint aPos )> DRC_ class DRC_ENGINE { public: - DRC_ENGINE( BOARD* aBoard, BOARD_DESIGN_SETTINGS* aSettings ); + DRC_ENGINE( BOARD* aBoard = nullptr, BOARD_DESIGN_SETTINGS* aSettings = nullptr ); ~DRC_ENGINE(); + void SetBoard( BOARD* aBoard ) { m_board = aBoard; } + BOARD* GetBoard() const { return m_board; } + + void SetDesignSettings( BOARD_DESIGN_SETTINGS* aSettings ) { m_designSettings = aSettings; } + BOARD_DESIGN_SETTINGS* GetDesignSettings() const { return m_designSettings; } + void SetSchematicNetlist( NETLIST* aNetlist ) { m_schematicNetlist = aNetlist; } NETLIST* GetSchematicNetlist() const { return m_schematicNetlist; } @@ -135,9 +141,6 @@ public: void RunTests( EDA_UNITS aUnits = EDA_UNITS::MILLIMETRES, bool aTestTracksAgainstZones = true, bool aReportAllTrackErrors = true, bool aTestFootprints = true ); - BOARD_DESIGN_SETTINGS* GetDesignSettings() const { return m_designSettings; } - - BOARD* GetBoard() const { return m_board; } bool IsErrorLimitExceeded( int error_code ); @@ -165,6 +168,8 @@ public: bool QueryWorstConstraint( DRC_CONSTRAINT_TYPE_T aRuleId, DRC_CONSTRAINT& aConstraint, DRC_CONSTRAINT_QUERY_T aQueryType ); + std::vector GetTestProviders() const { return m_testProviders; }; + private: void addRule( DRC_RULE* rule ) { diff --git a/pcbnew/drc/drc_item.cpp b/pcbnew/drc/drc_item.cpp index ca11158d53..367b33cbbd 100644 --- a/pcbnew/drc/drc_item.cpp +++ b/pcbnew/drc/drc_item.cpp @@ -166,6 +166,14 @@ DRC_ITEM DRC_ITEM::unresolvedVariable( DRCE_UNRESOLVED_VARIABLE, _( "Unresolved text variable" ), wxT( "unresolved_variable" ) ); +DRC_ITEM DRC_ITEM::silkOverPad( DRCE_SILK_OVER_PAD, + _( "Silkscreen overlapping component pad(s)" ), + wxT( "silk_over_pad" ) ); + +DRC_ITEM DRC_ITEM::silkClearance( DRCE_SILK_CLEARANCE, + _( "Silkscreen clearance" ), + wxT( "silk_clearance" ) ); + std::vector> DRC_ITEM::allItemTypes( { DRC_ITEM::unconnectedItems, @@ -238,6 +246,8 @@ std::shared_ptr DRC_ITEM::Create( int aErrorCode ) case DRCE_NET_CONFLICT: return std::make_shared( netConflict ); case DRCE_EXTRA_FOOTPRINT: return std::make_shared( extraFootprint ); case DRCE_UNRESOLVED_VARIABLE: return std::make_shared( unresolvedVariable ); + case DRCE_SILK_OVER_PAD: return std::make_shared( silkOverPad ); + case DRCE_SILK_CLEARANCE: return std::make_shared( silkClearance ); default: wxFAIL_MSG( "Unknown DRC error code" ); diff --git a/pcbnew/drc/drc_item.h b/pcbnew/drc/drc_item.h index f5d14ebb2b..eccb0027f0 100644 --- a/pcbnew/drc/drc_item.h +++ b/pcbnew/drc/drc_item.h @@ -68,8 +68,10 @@ enum PCB_DRC_CODE { DRCE_NET_CONFLICT, // pad net doesn't match netlist DRCE_UNRESOLVED_VARIABLE, + DRCE_SILK_OVER_PAD, // silkscreen over component pad(s) + DRCE_SILK_CLEARANCE, // silk to silk clearance error - DRCE_LAST = DRCE_UNRESOLVED_VARIABLE + DRCE_LAST = DRCE_SILK_CLEARANCE }; @@ -144,6 +146,8 @@ private: static DRC_ITEM extraFootprint; static DRC_ITEM netConflict; static DRC_ITEM unresolvedVariable; + static DRC_ITEM silkOverPad; + static DRC_ITEM silkClearance; private: DRC_RULE* m_violatingRule = nullptr; diff --git a/pcbnew/drc/drc_rtree.h b/pcbnew/drc/drc_rtree.h new file mode 100644 index 0000000000..f12a023f94 --- /dev/null +++ b/pcbnew/drc/drc_rtree.h @@ -0,0 +1,412 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2020 CERN + * + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-3.0.html + * or you may search the http://www.gnu.org website for the version 3 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef DRC_RTREE_H_ +#define DRC_RTREE_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * DRC_RTREE - + * Implements an R-tree for fast spatial and layer indexing of connectable items. + * Non-owning. + */ +class DRC_RTREE +{ +private: + struct ITEM_WITH_SHAPE + { + ITEM_WITH_SHAPE( BOARD_ITEM *aParent, SHAPE* aShape, std::shared_ptr aParentShape = nullptr ) : + parent ( aParent ), + shape ( aShape ), + parentShape( aParentShape ) {}; + BOARD_ITEM* parent; + SHAPE* shape; + std::shared_ptr parentShape; + }; + + using drc_rtree = RTree; + +public: + + + + DRC_RTREE() + { + for( int layer : LSET::AllLayersMask().Seq() ) + m_tree[layer] = new drc_rtree(); + + m_count = 0; + } + + ~DRC_RTREE() + { + for( auto tree : m_tree ) + delete tree; + } + + /** + * Function Insert() + * Inserts an item into the tree. Item's bounding box is taken via its GetBoundingBox() method. + */ + void insert( BOARD_ITEM* aItem ) + { + std::vector subshapes; + + for( int layer : aItem->GetLayerSet().Seq() ) + { + std::shared_ptr itemShape = aItem->GetEffectiveShape( (PCB_LAYER_ID) layer ); + + if( itemShape->HasIndexableSubshapes() ) + { + itemShape->GetIndexableSubshapes( subshapes ); + } + else + { + subshapes.push_back( itemShape.get() ); + } + + for( auto subshape : subshapes ) + { + const BOX2I& bbox = subshape->BBox(); + const int mmin[2] = { bbox.GetX(), bbox.GetY() }; + const int mmax[2] = { bbox.GetRight(), bbox.GetBottom() }; + m_tree[layer]->Insert( mmin, mmax, new ITEM_WITH_SHAPE( aItem, subshape, itemShape ) ); + m_count++; + } + } + } + +#if 0 + /** + * Function Remove() + * Removes an item from the tree. Removal is done by comparing pointers, attempting + * to remove a copy of the item will fail. + */ + bool remove( BOARD_ITEM* aItem ) + { + // First, attempt to remove the item using its given BBox + const EDA_RECT& bbox = aItem->GetBoundingBox(); + const int mmin[2] = { bbox.GetX(), bbox.GetY() }; + const int mmax[2] = { bbox.GetRight(), bbox.GetBottom() }; + bool removed = false; + + for( auto layer : aItem->GetLayerSet().Seq() ) + { + if( ZONE_CONTAINER* zone = dyn_cast( aItem ) ) + { + // Continue removing the zone elements from the tree until they cannot be found + while( !m_tree[int( layer )]->Remove( mmin, mmax, aItem ) ) + ; + + const int mmin2[2] = { INT_MIN, INT_MIN }; + const int mmax2[2] = { INT_MAX, INT_MAX }; + + // If we are not successful ( true == not found ), then we expand + // the search to the full tree + while( !m_tree[int( layer )]->Remove( mmin2, mmax2, aItem ) ) + ; + + // Loop to the next layer + continue; + } + + // The non-zone search expects only a single element in the tree with the same + // pointer aItem + if( m_tree[int( layer )]->Remove( mmin, mmax, aItem ) ) + { + // N.B. We must search the whole tree for the pointer to remove + // because the item may have been moved before we have the chance to + // delete it from the tree + const int mmin2[2] = { INT_MIN, INT_MIN }; + const int mmax2[2] = { INT_MAX, INT_MAX }; + + if( m_tree[int( layer )]->Remove( mmin2, mmax2, aItem ) ) + continue; + } + + removed = true; + } + + m_count -= int( removed ); + + return removed; + } + +#endif + + /** + * Function RemoveAll() + * Removes all items from the RTree + */ + void clear() + { + for( auto tree : m_tree ) + tree->RemoveAll(); + + m_count = 0; + } + +#if 0 + /** + * Determine if a given item exists in the tree. Note that this does not search the full tree + * so if the item has been moved, this will return false when it should be true. + * + * @param aItem Item that may potentially exist in the tree + * @param aRobust If true, search the whole tree, not just the bounding box + * @return true if the item definitely exists, false if it does not exist within bbox + */ + bool contains( BOARD_ITEM* aItem, bool aRobust = false ) + { + const EDA_RECT& bbox = aItem->GetBoundingBox(); + const int mmin[2] = { bbox.GetX(), bbox.GetY() }; + const int mmax[2] = { bbox.GetRight(), bbox.GetBottom() }; + bool found = false; + + auto search = [&found, &aItem]( const ITEM_WITH_SHAPE* aSearchItem ) { + if( aSearchItem->parent == aItem ) + { + found = true; + return false; + } + + return true; + }; + + for( int layer : aItem->GetLayerSet().Seq() ) + { + m_tree[layer]->Search( mmin, mmax, search ); + + if( found ) + break; + } + + if( !found && aRobust ) + { + for( int layer : LSET::AllCuMask().Seq() ) + { + // N.B. We must search the whole tree for the pointer to remove + // because the item may have been moved. We do not expand the item + // layer search as this should not change. + + const int mmin2[2] = { INT_MIN, INT_MIN }; + const int mmax2[2] = { INT_MAX, INT_MAX }; + + m_tree[layer]->Search( mmin2, mmax2, search ); + + if( found ) + break; + } + } + + return found; + } + +#endif + + int QueryColliding( BOARD_ITEM* aRefItem, + PCB_LAYER_ID aRefLayer, + PCB_LAYER_ID aTargetLayer, + std::function aFilter, + std::function aVisitor, + int aClearance + ) + { + // keep track of BOARD_ITEMs that have been already found to collide (some items + // might be build of COMPOUND/triangulated shapes and a single subshape collision + // means we have a hit) + std::unordered_set collidingCompounds; + + EDA_RECT box = aRefItem->GetBoundingBox(); + box.Inflate( aClearance ); + + int min[2] = { box.GetX(), box.GetY() }; + int max[2] = { box.GetRight(), box.GetBottom() }; + + std::shared_ptr refShape = aRefItem->GetEffectiveShape( aRefLayer ); + + auto visit = [&] ( ITEM_WITH_SHAPE* aItem ) -> bool + { + if( collidingCompounds.find( aItem->parent ) != collidingCompounds.end() ) + return true; + + if( !aFilter( aItem->parent ) ) + return true; + + int actual; + + bool colliding = refShape->Collide( aItem->shape, aClearance, &actual ); + + if( colliding ) + { + collidingCompounds.insert( aItem->parent ); + return aVisitor( aItem->parent, actual ); + } + + return true; + }; + + this->m_tree[aTargetLayer]->Search( min, max, visit ); + return 0; + } + +#if 0 + std::vector> GetNearest( const wxPoint &aPoint, + PCB_LAYER_ID aLayer, + int aLimit ) + { + + const int point[2] = { aPoint.x, aPoint.y }; + auto result = m_tree[int( aLayer )]->NearestNeighbors( point, + [aLimit]( std::size_t a_count, int a_maxDist ) -> bool + { + return a_count >= aLimit; + }, + []( BOARD_ITEM* aElement) -> bool + { + // Don't remove any elements from the list + return false; + }, + [aLayer]( const int* a_point, BOARD_ITEM* a_data ) -> int + { + switch( a_data->Type() ) + { + case PCB_TRACE_T: + { + TRACK* track = static_cast( a_data ); + SEG seg( track->GetStart(), track->GetEnd() ); + return seg.Distance( VECTOR2I( a_point[0], a_point[1] ) ) - + ( track->GetWidth() + 1 ) / 2; + } + case PCB_VIA_T: + { + VIA* via = static_cast( a_data ); + return ( VECTOR2I( via->GetPosition() ) - + VECTOR2I( a_point[0], a_point[1] ) ).EuclideanNorm() - + ( via->GetWidth() + 1 ) / 2; + + } + default: + { + VECTOR2I point( a_point[0], a_point[1] ); + int dist = 0; + auto shape = a_data->GetEffectiveShape( aLayer ); + + // Here we use a hack to get the distance by colliding with a large area + // However, we can't use just MAX_INT because we will overflow the collision calculations + shape->Collide( point, std::numeric_limits::max() / 2, &dist); + return dist; + } + } + return 0; + }); + + return result; + } +#endif + + /** + * Returns the number of items in the tree + * @return number of elements in the tree; + */ + size_t size() + { + return m_count; + } + + bool empty() + { + return m_count == 0; + } + + using iterator = typename drc_rtree::Iterator; + + /** + * The DRC_LAYER struct provides a layer-specific auto-range iterator to the RTree. Using + * this struct, one can write lines like: + * + * for( auto item : rtree.OnLayer( In1_Cu ) ) + * + * and iterate over only the RTree items that are on In1 + */ + struct DRC_LAYER + { + DRC_LAYER( drc_rtree* aTree ) : layer_tree( aTree ) + { + m_rect = { { INT_MIN, INT_MIN }, { INT_MAX, INT_MAX } }; + }; + + DRC_LAYER( drc_rtree* aTree, const EDA_RECT aRect ) : layer_tree( aTree ) + { + m_rect = { { aRect.GetX(), aRect.GetY() }, + { aRect.GetRight(), aRect.GetBottom() } }; + }; + + drc_rtree::Rect m_rect; + drc_rtree* layer_tree; + + iterator begin() + { + return layer_tree->begin( m_rect ); + } + + iterator end() + { + return layer_tree->end( m_rect ); + } + }; + + DRC_LAYER OnLayer( PCB_LAYER_ID aLayer ) + { + return DRC_LAYER( m_tree[int( aLayer )] ); + } + + DRC_LAYER Overlapping( PCB_LAYER_ID aLayer, const wxPoint& aPoint, int aAccuracy = 0 ) + { + EDA_RECT rect( aPoint, wxSize( 0, 0 ) ); + rect.Inflate( aAccuracy ); + return DRC_LAYER( m_tree[int( aLayer )], rect ); + } + + DRC_LAYER Overlapping( PCB_LAYER_ID aLayer, const EDA_RECT& aRect ) + { + return DRC_LAYER( m_tree[int( aLayer )], aRect ); + } + + +private: + drc_rtree* m_tree[PCB_LAYER_ID_COUNT]; + size_t m_count; +}; + + +#endif /* DRC_RTREE_H_ */ diff --git a/pcbnew/drc/drc_test_provider.h b/pcbnew/drc/drc_test_provider.h index 2685ac1a7e..34167d383d 100644 --- a/pcbnew/drc/drc_test_provider.h +++ b/pcbnew/drc/drc_test_provider.h @@ -97,6 +97,16 @@ public: return m_isRuleDriven; } + bool IsEnabled() const + { + return m_enabled; + } + + void Enable( bool aEnable ) + { + m_enabled = aEnable; + } + protected: int forEachGeometryItem( const std::vector& aTypes, LSET aLayers, const std::function& aFunc ); @@ -114,6 +124,7 @@ protected: DRC_ENGINE* m_drcEngine; std::unordered_map m_stats; bool m_isRuleDriven = true; + bool m_enabled = true; wxString m_msg; // Allocating strings gets expensive enough to want to avoid it }; diff --git a/pcbnew/drc/drc_test_provider_silk_to_pad.cpp b/pcbnew/drc/drc_test_provider_silk_to_pad.cpp new file mode 100644 index 0000000000..e60f1c08cf --- /dev/null +++ b/pcbnew/drc/drc_test_provider_silk_to_pad.cpp @@ -0,0 +1,243 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +/* + Silk to pads clearance test. Check all pads against silkscreen (mask opening in the pad vs silkscreen) + Errors generated: + - DRCE_SILK_ON_PADS + + TODO: + - tester only looks for edge crossings. it doesn't check if items are inside/outside the board area. +*/ + +namespace test { + +class DRC_TEST_PROVIDER_SILK_TO_PAD : public ::DRC_TEST_PROVIDER +{ +public: + DRC_TEST_PROVIDER_SILK_TO_PAD () + { + } + + virtual ~DRC_TEST_PROVIDER_SILK_TO_PAD() + { + } + + virtual bool Run() override; + + virtual const wxString GetName() const override + { + return "silk_to_pad"; + }; + + virtual const wxString GetDescription() const override + { + return "Tests for silkscreen covering components pads"; + } + + virtual int GetNumPhases() const override + { + return 1; + } + + virtual std::set GetConstraintTypes() const override; + +private: + + BOARD* m_board; + int m_largestClearance; +}; + +}; + + +bool test::DRC_TEST_PROVIDER_SILK_TO_PAD::Run() +{ + m_board = m_drcEngine->GetBoard(); + + DRC_CONSTRAINT worstClearanceConstraint; + m_largestClearance = 0; + + if( m_drcEngine->QueryWorstConstraint( DRC_CONSTRAINT_TYPE_SILK_TO_PAD, + worstClearanceConstraint, DRCCQ_LARGEST_MINIMUM ) ) + { + m_largestClearance = worstClearanceConstraint.m_Value.Min(); + } + + reportAux( "Worst clearance : %d nm", m_largestClearance ); + reportPhase(( "Pad to silkscreen clearances..." )); + + struct SHAPE_ON_LAYER + { + SHAPE_ON_LAYER( std::shared_ptr aShape, PCB_LAYER_ID aLayer ) + : shape ( aShape ), + layer ( aLayer ) + { + } + std::shared_ptr shape; + PCB_LAYER_ID layer; + }; + + DRC_RTREE padTree; + + auto addPadToTree = + [&padTree]( BOARD_ITEM *item ) -> bool + { + padTree.insert( item ); + return true; + }; + + + auto checkClearance = + [&] ( BOARD_ITEM* refItem ) -> bool + { + const PCB_LAYER_ID padOuterLayers[] = { F_Cu, B_Cu }; + const PCB_LAYER_ID padSilkLayers[] = { F_SilkS, B_SilkS }; + + + for( int i = 0; i < 2; i++ ) + { + padTree.QueryColliding( + refItem, + padSilkLayers[i], + padOuterLayers[i], + [&] ( BOARD_ITEM *chkItem ) -> bool + { + return true; + }, + [&] ( BOARD_ITEM *collItem, int distance ) -> bool + { + auto constraint = m_drcEngine->EvalRulesForItems( DRC_CONSTRAINT_TYPE_SILK_TO_PAD, + refItem, collItem ); + + int minClearance = constraint.GetValue().Min(); + + if( !distance || distance < minClearance ) + { + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_SILK_OVER_PAD ); + wxString msg; + + msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ), + constraint.GetParentRule()->m_Name, + MessageTextFromValue( userUnits(), minClearance, true ), + MessageTextFromValue( userUnits(), distance, true ) ); + + drcItem->SetErrorMessage( msg ); + drcItem->SetItems( refItem, collItem ); + drcItem->SetViolatingRule( constraint.GetParentRule() ); + + reportViolation( drcItem, refItem->GetPosition() ); + } + return true; + }, + m_largestClearance + ); + } + + return true; + }; + + int numPads = forEachGeometryItem( { PCB_PAD_T }, LSET::AllTechMask() | LSET::AllCuMask(), addPadToTree ); + + int numSilk = forEachGeometryItem( { PCB_LINE_T, PCB_MODULE_EDGE_T, PCB_TEXT_T, PCB_MODULE_TEXT_T }, + LSET( 2, F_SilkS, B_SilkS ), + checkClearance + ); + + + reportAux( "Tested %d pads against %d silkscreen features.", numPads, numSilk ); + + + +#if 0 + + + for( auto& silkShape : boardOutline ) + { + for( auto& padShape : boardItems ) + { +// printf("BoardT %d\n", boardItem->Type() ); + + auto shape = boardItem->GetEffectiveShape(); + + auto constraint = m_drcEngine->EvalRulesForItems( DRC_CONSTRAINT_TYPE_EDGE_CLEARANCE, + outlineItem, boardItem ); + int minClearance = constraint.GetValue().Min(); + int actual; + + if( refShape->Collide( shape.get(), minClearance, &actual ) ) + { + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_COPPER_EDGE_CLEARANCE ); + wxString msg; + + msg.Printf( drcItem->GetErrorText() + _( " (%s clearance %s; actual %s)" ), + rule->GetName(), + MessageTextFromValue( userUnits(), minClearance, true ), + MessageTextFromValue( userUnits(), actual, true ) ); + + drcItem->SetErrorMessage( msg ); + drcItem->SetItems( outlineItem, boardItem ); + drcItem->SetViolatingRule( rule ); + + reportViolation( drcItem, refShape->Centre()); + } + } + } + + return true; + +#endif + + return true; +} + + +std::set test::DRC_TEST_PROVIDER_SILK_TO_PAD::GetConstraintTypes() const +{ + return { DRC_CONSTRAINT_TYPE_SILK_TO_PAD }; +} + + +namespace detail +{ + static DRC_REGISTER_TEST_PROVIDER dummy; +} \ No newline at end of file