Add implicit rule generation for keepout areas.

Also implements collision detection for SHAPE_POLY_SET.

Fixes https://gitlab.com/kicad/code/kicad/issues/6105
This commit is contained in:
Jeff Young 2020-10-22 00:59:40 +01:00
parent 7d09681f9e
commit 9ff49277e1
8 changed files with 280 additions and 36 deletions

View File

@ -122,7 +122,7 @@ typedef const INSPECTOR_FUNC& INSPECTOR; /// std::function passed to nested u
#define OBSOLETE_2 (1 << 21) ///< Not presently used
#define BEGIN_ONPAD (1 << 22) ///< Pcbnew: flag set for track segment starting on a pad
#define END_ONPAD (1 << 23) ///< Pcbnew: flag set for track segment ending on a pad
#define OBSOLETE_3 (1 << 24) ///< Not presently used
#define HOLE_PROXY (1 << 24) ///< Indicates the BOARD_ITEM is a proxy for its hole
#define IS_ROLLOVER (1 << 25) ///< Rollover active. Used for hyperlink highlighting.
#define BRIGHTENED (1 << 26) ///< item is drawn with a bright contour

View File

@ -27,6 +27,7 @@
#define __SHAPE_ARC_H
#include <geometry/shape.h>
#include <include/convert_to_biu.h>
#include <math/vector2d.h> // for VECTOR2I
class SHAPE_LINE_CHAIN;
@ -134,14 +135,14 @@ public:
/**
* Constructs a SHAPE_LINE_CHAIN of segments from a given arc
* @param aAccuracy maximum divergence from true arc given in internal units
* ** Note that the default of 500.0 here is given using ARC_DEF_HIGH_ACCURACY
* for pcbnew units. This is to allow common geometry collision functions
* ** Note that the default is ARC_HIGH_DEF in PCBNew units
* This is to allow common geometry collision functions
* Other programs should call this using explicit accuracy values
* TODO: unify KiCad internal units
*
* @return a SHAPE_LINE_CHAIN
*/
const SHAPE_LINE_CHAIN ConvertToPolyline( double aAccuracy = 500.0 ) const;
const SHAPE_LINE_CHAIN ConvertToPolyline( double aAccuracy = 0.005 * PCB_IU_PER_MM ) const;
private:

View File

@ -1039,6 +1039,22 @@ class SHAPE_POLY_SET : public SHAPE
*/
bool PointOnEdge( const VECTOR2I& aP ) const;
/**
* Function Collide()
*
* Checks if the boundary of shape (this) lies closer to the shape aShape than aClearance,
* indicating a collision.
* @param aShape shape to check collision against
* @param aClearance minimum clearance
* @param aActual [out] an optional pointer to an int to store the actual distance in the
* event of a collision.
* @param aLocation [out] an option pointer to a point to store a nearby location in the
* event of a collision.
* @return true, if there is a collision.
*/
bool Collide( const SHAPE* aShape, int aClearance = 0, int* aActual = nullptr,
VECTOR2I* aLocation = nullptr ) const override;
/**
* Function Collide
* Checks whether the point aP is either inside or on the edge of the polygon set.

View File

@ -50,7 +50,10 @@
#include <math/util.h> // for KiROUND, rescale
#include <math/vector2d.h> // for VECTOR2I, VECTOR2D, VECTOR2
#include <md5_hash.h>
#include <libs/kimath/include/geometry/shape_segment.h>
#include <libs/kimath/include/geometry/shape_circle.h>
#include <libs/kimath/include/geometry/shape_simple.h>
#include <libs/kimath/include/geometry/shape_compound.h>
using namespace ClipperLib;
@ -1267,6 +1270,84 @@ bool SHAPE_POLY_SET::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
}
bool SHAPE_POLY_SET::Collide( const SHAPE* aShape, int aClearance, int* aActual,
VECTOR2I* aLocation ) const
{
// A couple of simple cases are worth trying before we fall back on triangulation.
if( aShape->Type() == SH_SEGMENT )
{
const SHAPE_SEGMENT* segment = static_cast<const SHAPE_SEGMENT*>( aShape );
int extra = segment->GetWidth() / 2;
if( Collide( segment->GetSeg(), aClearance + extra, aActual, aLocation ) )
{
if( aActual )
*aActual = std::max( 0, *aActual - extra );
return true;
}
return false;
}
if( aShape->Type() == SH_CIRCLE )
{
const SHAPE_CIRCLE* circle = static_cast<const SHAPE_CIRCLE*>( aShape );
int extra = circle->GetRadius();
if( Collide( circle->GetCenter(), aClearance + extra, aActual, aLocation ) )
{
if( aActual )
*aActual = std::max( 0, *aActual - extra );
return true;
}
return false;
}
const_cast<SHAPE_POLY_SET*>( this )->CacheTriangulation( true );
int actual = INT_MAX;
VECTOR2I location;
for( auto& tpoly : m_triangulatedPolys )
{
for ( auto& tri : tpoly->Triangles() )
{
int triActual;
VECTOR2I triLocation;
if( aShape->Collide( &tri, aClearance, &triActual, &triLocation ) )
{
if( !aActual && !aLocation )
return true;
if( triActual < actual )
{
actual = triActual;
location = triLocation;
}
}
}
}
if( actual < INT_MAX )
{
if( aActual )
*aActual = std::max( 0, actual );
if( aLocation )
*aLocation = location;
return true;
}
return false;
}
void SHAPE_POLY_SET::RemoveAllContours()
{
m_polys.clear();
@ -2148,8 +2229,10 @@ bool SHAPE_POLY_SET::HasIndexableSubshapes() const
size_t SHAPE_POLY_SET::GetIndexableSubshapeCount() const
{
size_t n = 0;
for( auto& t : m_triangulatedPolys )
n += t->GetTriangleCount();
return n;
}

View File

@ -30,6 +30,7 @@
#include <drc/drc_rule.h>
#include <drc/drc_rule_condition.h>
#include <drc/drc_test_provider.h>
#include <class_track.h>
void drcPrintDebugMessage( int level, const wxString& msg, const char *function, int line )
{
@ -140,9 +141,9 @@ void DRC_ENGINE::loadImplicitRules()
// 2) micro-via specific defaults (new DRC doesn't treat microvias in any special way)
DRC_RULE* uViaRule = createImplicitRule( _( "board setup micro-via constraints" ));
DRC_RULE* uViaRule = createImplicitRule( _( "board setup micro-via constraints" ) );
uViaRule->m_Condition = new DRC_RULE_CONDITION ( "A.Via_Type == 'micro_via'" );
uViaRule->m_Condition = new DRC_RULE_CONDITION( "A.Via_Type == 'micro_via'" );
DRC_CONSTRAINT uViaDrillConstraint( DRC_CONSTRAINT_TYPE_HOLE_SIZE );
uViaDrillConstraint.Value().SetMin( bds.m_MicroViasMinDrill );
@ -161,9 +162,9 @@ void DRC_ENGINE::loadImplicitRules()
if( !bds.m_BlindBuriedViaAllowed )
{
DRC_RULE* bbViaRule = createImplicitRule( _( "board setup constraints" ));
DRC_RULE* bbViaRule = createImplicitRule( _( "board setup constraints" ) );
bbViaRule->m_Condition = new DRC_RULE_CONDITION ( "A.Via_Type == 'buried_via'" );
bbViaRule->m_Condition = new DRC_RULE_CONDITION( "A.Via_Type == 'buried_via'" );
DRC_CONSTRAINT disallowConstraint( DRC_CONSTRAINT_TYPE_DISALLOW );
disallowConstraint.m_DisallowFlags = DRC_DISALLOW_BB_VIAS;
@ -319,6 +320,67 @@ void DRC_ENGINE::loadImplicitRules()
for( DRC_RULE* ncRule : netclassItemSpecificRules )
addRule( ncRule );
// 3) keepout area rules
auto addKeepoutConstraint =
[&rule]( int aConstraint )
{
DRC_CONSTRAINT disallowConstraint( DRC_CONSTRAINT_TYPE_DISALLOW );
disallowConstraint.m_DisallowFlags = aConstraint;
rule->AddConstraint( disallowConstraint );
};
auto isKeepoutZone =
[]( ZONE_CONTAINER* aZone )
{
return aZone->GetIsRuleArea() && ( aZone->GetDoNotAllowTracks()
|| aZone->GetDoNotAllowVias()
|| aZone->GetDoNotAllowPads()
|| aZone->GetDoNotAllowCopperPour()
|| aZone->GetDoNotAllowFootprints() );
};
std::vector<ZONE_CONTAINER*> keepoutZones;
for( ZONE_CONTAINER* zone : m_board->Zones() )
{
if( isKeepoutZone( zone ) )
keepoutZones.push_back( zone );
}
for( MODULE* footprint : m_board->Modules() )
{
for( ZONE_CONTAINER* zone : footprint->Zones() )
{
if( isKeepoutZone( zone ) )
keepoutZones.push_back( zone );
}
}
for( ZONE_CONTAINER* zone : keepoutZones )
{
if( zone->GetZoneName().IsEmpty() )
zone->SetZoneName( KIID().AsString() );
rule = createImplicitRule( _( "keepout area" ) );
rule->m_Condition = new DRC_RULE_CONDITION( wxString::Format( "A.insideArea('%s')",
zone->GetZoneName() ) );
if( zone->GetDoNotAllowTracks() )
addKeepoutConstraint( DRC_DISALLOW_TRACKS );
if( zone->GetDoNotAllowVias() )
addKeepoutConstraint( DRC_DISALLOW_VIAS );
if( zone->GetDoNotAllowPads() )
addKeepoutConstraint( DRC_DISALLOW_PADS );
if( zone->GetDoNotAllowCopperPour() )
addKeepoutConstraint( DRC_DISALLOW_ZONES );
if( zone->GetDoNotAllowFootprints() )
addKeepoutConstraint( DRC_DISALLOW_FOOTPRINTS );
}
ReportAux( wxString::Format( "Building %d implicit netclass rules",
(int) netclassClearanceRules.size() ) );
}
@ -557,6 +619,15 @@ void DRC_ENGINE::RunTests( EDA_UNITS aUnits, bool aTestTracksAgainstZones,
m_errorLimits[ ii ] = INT_MAX;
}
for( ZONE_CONTAINER* zone : m_board->Zones() )
zone->CacheBoundingBox();
for( MODULE* module : m_board->Modules() )
{
for( ZONE_CONTAINER* zone : module->Zones() )
zone->CacheBoundingBox();
}
for( DRC_TEST_PROVIDER* provider : m_testProviders )
{
if( !provider->IsEnabled() )
@ -677,6 +748,45 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRulesForItems( DRC_CONSTRAINT_TYPE_T aConstraintI
c->constraint.GetName(),
MessageTextFromValue( UNITS, clearance ) ) )
}
else if( aConstraintId == DRC_CONSTRAINT_TYPE_DISALLOW )
{
int mask;
if( a->GetFlags() & HOLE_PROXY )
{
mask = DRC_DISALLOW_HOLES;
}
else if( a->Type() == PCB_VIA_T )
{
if( static_cast<const VIA*>( a )->GetViaType() == VIATYPE::BLIND_BURIED )
mask = DRC_DISALLOW_VIAS | DRC_DISALLOW_BB_VIAS;
else if( static_cast<const VIA*>( a )->GetViaType() == VIATYPE::MICROVIA )
mask = DRC_DISALLOW_VIAS | DRC_DISALLOW_MICRO_VIAS;
else
mask = DRC_DISALLOW_VIAS;
}
else
{
switch( a->Type() )
{
case PCB_TRACE_T: mask = DRC_DISALLOW_TRACKS; break;
case PCB_ARC_T: mask = DRC_DISALLOW_TRACKS; break;
case PCB_PAD_T: mask = DRC_DISALLOW_PADS; break;
case PCB_MODULE_T: mask = DRC_DISALLOW_FOOTPRINTS; break;
case PCB_SHAPE_T: mask = DRC_DISALLOW_GRAPHICS; break;
case PCB_FP_SHAPE_T: mask = DRC_DISALLOW_GRAPHICS; break;
case PCB_TEXT_T: mask = DRC_DISALLOW_TEXTS; break;
case PCB_FP_TEXT_T: mask = DRC_DISALLOW_TEXTS; break;
case PCB_ZONE_AREA_T: mask = DRC_DISALLOW_ZONES; break;
case PCB_FP_ZONE_AREA_T: mask = DRC_DISALLOW_ZONES; break;
case PCB_LOCATE_HOLE_T: mask = DRC_DISALLOW_HOLES; break;
default: mask = 0; break;
}
}
if( ( c->constraint.m_DisallowFlags & mask ) == 0 )
return false;
}
else
{
REPORT( wxString::Format( _( "Checking %s." ),

View File

@ -522,7 +522,7 @@ void DRC_TEST_PROVIDER_COPPER_CLEARANCE::doTrackDrc( TRACK* aRefSeg, PCB_LAYER_I
if( zone->GetFilledPolysList( aLayer ).IsEmpty() )
continue;
if( !refSegInflatedBB.Intersects( zone->GetBoundingBox() ) )
if( !refSegInflatedBB.Intersects( zone->GetCachedBoundingBox() ) )
continue;
int halfWidth = refSegWidth / 2;

View File

@ -73,29 +73,43 @@ bool DRC_TEST_PROVIDER_DISALLOW::Run()
if( !reportPhase( _( "Checking keepouts & disallow constraints..." ) ) )
return false;
auto checkItem = [&]( BOARD_ITEM *item ) -> bool
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_ALLOWED_ITEMS ) )
return false;
auto doCheckItem =
[&]( BOARD_ITEM* item )
{
auto constraint = m_drcEngine->EvalRulesForItems( DRC_CONSTRAINT_TYPE_DISALLOW,
item );
if( constraint.m_DisallowFlags )
{
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_ALLOWED_ITEMS );
auto constraint = m_drcEngine->EvalRulesForItems( DRC_CONSTRAINT_TYPE_DISALLOW, item );
m_msg.Printf( drcItem->GetErrorText() + wxS( " (%s)" ),
constraint.GetName() );
if( constraint.m_DisallowFlags )
{
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_ALLOWED_ITEMS );
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( item );
drcItem->SetViolatingRule( constraint.GetParentRule() );
m_msg.Printf( drcItem->GetErrorText() + wxS( " (%s)" ),
constraint.GetName() );
reportViolation( drcItem, item->GetPosition());
}
};
drcItem->SetErrorMessage( m_msg );
drcItem->SetItems( item );
drcItem->SetViolatingRule( constraint.GetParentRule() );
auto checkItem =
[&]( BOARD_ITEM* item ) -> bool
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_ALLOWED_ITEMS ) )
return false;
reportViolation( drcItem, item->GetPosition());
}
item->ClearFlags( HOLE_PROXY );
doCheckItem( item );
return true;
};
if( item->Type() == PCB_VIA_T || item->Type() == PCB_PAD_T )
{
item->SetFlags( HOLE_PROXY );
doCheckItem( item );
}
return true;
};
forEachGeometryItem( {}, LSET::AllLayersMask(), checkItem );

View File

@ -24,10 +24,9 @@
#include <cstdio>
#include <memory>
#include <reporter.h>
#include <class_board.h>
#include <class_track.h>
#include <geometry/shape_segment.h>
#include <pcb_expr_evaluator.h>
#include <connectivity/connectivity_data.h>
@ -35,7 +34,7 @@
#include <connectivity/from_to_cache.h>
#include <drc/drc_engine.h>
#include <libs/kimath/include/geometry/shape_circle.h>
bool exprFromTo( LIBEVAL::CONTEXT* aCtx, void* self )
{
@ -241,14 +240,35 @@ static void insideArea( LIBEVAL::CONTEXT* aCtx, void* self )
if( zone )
{
SHAPE_POLY_SET testPoly;
if( !zone->GetCachedBoundingBox().Intersects( item->GetBoundingBox() ) )
return;
item->TransformShapeWithClearanceToPolygon( testPoly, context->GetLayer(), 0,
ARC_LOW_DEF, ERROR_INSIDE );
testPoly.BooleanIntersection( *zone->Outline(), SHAPE_POLY_SET::PM_FAST );
if( item->GetFlags() & HOLE_PROXY )
{
if( item->Type() == PCB_PAD_T )
{
D_PAD* pad = static_cast<D_PAD*>( item );
const SHAPE_SEGMENT* holeShape = pad->GetEffectiveHoleShape();
if( testPoly.OutlineCount() )
result->Set( 1.0 );
if( zone->Outline()->Collide( holeShape ) )
result->Set( 1.0 );
}
else if( item->Type() == PCB_VIA_T )
{
VIA* via = static_cast<VIA*>( item );
const SHAPE_CIRCLE holeShape( via->GetPosition(), via->GetDrillValue() );
if( zone->Outline()->Collide( &holeShape ) )
result->Set( 1.0 );
}
}
else
{
std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape( context->GetLayer() );
if( zone->Outline()->Collide( itemShape.get() ) )
result->Set( 1.0 );
}
}
}