Use polygonal hit testing for module selection

This commit is contained in:
Jon Evans 2018-02-18 19:00:29 -05:00 committed by Wayne Stambaugh
parent c8458bc8ed
commit 01ab8b0584
6 changed files with 83 additions and 23 deletions

View File

@ -1402,19 +1402,19 @@ bool SHAPE_POLY_SET::CollideEdge( const VECTOR2I& aPoint,
} }
bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex ) const bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles ) const
{ {
if( m_polys.size() == 0 ) // empty set? if( m_polys.size() == 0 ) // empty set?
return false; return false;
// If there is a polygon specified, check the condition against that polygon // If there is a polygon specified, check the condition against that polygon
if( aSubpolyIndex >= 0 ) if( aSubpolyIndex >= 0 )
return containsSingle( aP, aSubpolyIndex ); return containsSingle( aP, aSubpolyIndex, aIgnoreHoles );
// In any other case, check it against all polygons in the set // In any other case, check it against all polygons in the set
for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ ) for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
{ {
if( containsSingle( aP, polygonIdx ) ) if( containsSingle( aP, polygonIdx, aIgnoreHoles ) )
return true; return true;
} }
@ -1440,20 +1440,23 @@ void SHAPE_POLY_SET::RemoveVertex( VERTEX_INDEX aIndex )
} }
bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex ) const bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles ) const
{ {
// Check that the point is inside the outline // Check that the point is inside the outline
if( pointInPolygon( aP, m_polys[aSubpolyIndex][0] ) ) if( pointInPolygon( aP, m_polys[aSubpolyIndex][0] ) )
{ {
// Check that the point is not in any of the holes if( !aIgnoreHoles )
for( int holeIdx = 0; holeIdx < HoleCount( aSubpolyIndex ); holeIdx++ )
{ {
const SHAPE_LINE_CHAIN hole = CHole( aSubpolyIndex, holeIdx ); // Check that the point is not in any of the holes
for( int holeIdx = 0; holeIdx < HoleCount( aSubpolyIndex ); holeIdx++ )
{
const SHAPE_LINE_CHAIN hole = CHole( aSubpolyIndex, holeIdx );
// If the point is inside a hole (and not on its edge), // If the point is inside a hole (and not on its edge),
// it is outside of the polygon // it is outside of the polygon
if( pointInPolygon( aP, hole ) && !hole.PointOnEdge( aP ) ) if( pointInPolygon( aP, hole ) && !hole.PointOnEdge( aP ) )
return false; return false;
}
} }
return true; return true;

View File

@ -933,9 +933,15 @@ class SHAPE_POLY_SET : public SHAPE
bool CollideEdge( const VECTOR2I& aPoint, VERTEX_INDEX& aClosestVertex, bool CollideEdge( const VECTOR2I& aPoint, VERTEX_INDEX& aClosestVertex,
int aClearance = 0 ); int aClearance = 0 );
///> Returns true if a given subpolygon contains the point aP. If aSubpolyIndex < 0 /**
///> (default value), checks all polygons in the set * Returns true if a given subpolygon contains the point aP
bool Contains( const VECTOR2I& aP, int aSubpolyIndex = -1 ) const; *
* @param aP is the point to check
* @param aSubpolyIndex is the subpolygon to check, or -1 to check all
* @param aIgnoreHoles controls whether or not internal holes are considered
* @return true if the polygon contains the point
*/
bool Contains( const VECTOR2I& aP, int aSubpolyIndex = -1, bool aIgnoreHoles = false ) const;
///> Returns true if the set is empty (no polygons at all) ///> Returns true if the set is empty (no polygons at all)
bool IsEmpty() const bool IsEmpty() const
@ -1112,10 +1118,11 @@ class SHAPE_POLY_SET : public SHAPE
* the aSubpolyIndex-th polygon will be tested. * the aSubpolyIndex-th polygon will be tested.
* @param aSubpolyIndex is an integer specifying which polygon in the set has to be * @param aSubpolyIndex is an integer specifying which polygon in the set has to be
* checked. * checked.
* @param aIgnoreHoles can be set to true to ignore internal holes in the polygon
* @return bool - true if aP is inside aSubpolyIndex-th polygon; false in any other * @return bool - true if aP is inside aSubpolyIndex-th polygon; false in any other
* case. * case.
*/ */
bool containsSingle( const VECTOR2I& aP, int aSubpolyIndex ) const; bool containsSingle( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles = false ) const;
/** /**
* Operations ChamferPolygon and FilletPolygon are computed under the private chamferFillet * Operations ChamferPolygon and FilletPolygon are computed under the private chamferFillet

View File

@ -138,7 +138,7 @@ void MODULE::TransformPadsShapesWithClearanceToPolygon( PCB_LAYER_ID aLayer,
wxSize margin; wxSize margin;
for( ; pad != NULL; pad = pad->Next() ) for( ; pad != NULL; pad = pad->Next() )
{ {
if( !pad->IsOnLayer(aLayer) ) if( aLayer != UNDEFINED_LAYER && !pad->IsOnLayer(aLayer) )
continue; continue;
// NPTH pads are not drawn on layers if the shape size and pos is the same // NPTH pads are not drawn on layers if the shape size and pos is the same
@ -206,7 +206,8 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet(
int aInflateValue, int aInflateValue,
int aCircleToSegmentsCount, int aCircleToSegmentsCount,
double aCorrectionFactor, double aCorrectionFactor,
int aCircleToSegmentsCountForTexts ) const int aCircleToSegmentsCountForTexts,
bool aIncludeText ) const
{ {
std::vector<TEXTE_MODULE *> texts; // List of TEXTE_MODULE to convert std::vector<TEXTE_MODULE *> texts; // List of TEXTE_MODULE to convert
EDGE_MODULE* outline; EDGE_MODULE* outline;
@ -219,7 +220,8 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet(
{ {
TEXTE_MODULE* text = static_cast<TEXTE_MODULE*>( item ); TEXTE_MODULE* text = static_cast<TEXTE_MODULE*>( item );
if( text->GetLayer() == aLayer && text->IsVisible() ) if( ( aLayer != UNDEFINED_LAYER && text->GetLayer() == aLayer )
&& text->IsVisible() )
texts.push_back( text ); texts.push_back( text );
break; break;
@ -228,7 +230,7 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet(
case PCB_MODULE_EDGE_T: case PCB_MODULE_EDGE_T:
outline = (EDGE_MODULE*) item; outline = (EDGE_MODULE*) item;
if( outline->GetLayer() != aLayer ) if( aLayer != UNDEFINED_LAYER && outline->GetLayer() != aLayer )
break; break;
outline->TransformShapeWithClearanceToPolygon( aCornerBuffer, 0, outline->TransformShapeWithClearanceToPolygon( aCornerBuffer, 0,
@ -240,6 +242,9 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet(
} }
} }
if( !aIncludeText )
return;
// Convert texts sur modules // Convert texts sur modules
if( Reference().GetLayer() == aLayer && Reference().IsVisible() ) if( Reference().GetLayer() == aLayer && Reference().IsVisible() )
texts.push_back( &Reference() ); texts.push_back( &Reference() );

View File

@ -512,6 +512,25 @@ const EDA_RECT MODULE::GetBoundingBox() const
} }
SHAPE_POLY_SET MODULE::GetBoundingPoly() const
{
const int segcountforcircle = 8;
double correctionFactor = 1.0 / cos( M_PI / (segcountforcircle * 2) );
SHAPE_POLY_SET poly;
TransformPadsShapesWithClearanceToPolygon( UNDEFINED_LAYER,
poly, 0, segcountforcircle, correctionFactor );
TransformGraphicShapesWithClearanceToPolygonSet( UNDEFINED_LAYER,
poly, 0, segcountforcircle, correctionFactor, 0, false );
poly.NormalizeAreaOutlines();
poly.Inflate( Millimeter2iu( 0.01 ), segcountforcircle );
return poly;
}
void MODULE::GetMsgPanelInfo( std::vector< MSG_PANEL_ITEM >& aList ) void MODULE::GetMsgPanelInfo( std::vector< MSG_PANEL_ITEM >& aList )
{ {
int nbpad; int nbpad;
@ -607,6 +626,13 @@ bool MODULE::HitTest( const wxPoint& aPosition ) const
} }
bool MODULE::HitTestAccurate( const wxPoint& aPosition ) const
{
auto shape = GetBoundingPoly();
return shape.Contains( aPosition, -1, true );
}
bool MODULE::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const bool MODULE::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const
{ {
EDA_RECT arect = aRect; EDA_RECT arect = aRect;

View File

@ -148,6 +148,12 @@ public:
*/ */
EDA_RECT GetFootprintRect() const; EDA_RECT GetFootprintRect() const;
/**
* Returns a bounding polygon for the shapes and pads in the module
* This operation is slower but more accurate than calculating a bounding box
*/
SHAPE_POLY_SET GetBoundingPoly() const;
// Virtual function // Virtual function
const EDA_RECT GetBoundingBox() const override; const EDA_RECT GetBoundingBox() const override;
@ -336,7 +342,7 @@ public:
* and adds these polygons to aCornerBuffer * and adds these polygons to aCornerBuffer
* Useful to generate a polygonal representation of a footprint * Useful to generate a polygonal representation of a footprint
* in 3D view and plot functions, when a full polygonal approach is needed * in 3D view and plot functions, when a full polygonal approach is needed
* @param aLayer = the current layer: pads on this layer are considered * @param aLayer = the layer to consider, or UNDEFINED_LAYER to consider all
* @param aCornerBuffer = the buffer to store polygons * @param aCornerBuffer = the buffer to store polygons
* @param aInflateValue = an additionnal size to add to pad shapes * @param aInflateValue = an additionnal size to add to pad shapes
* aInflateValue = 0 to have the exact pad size * aInflateValue = 0 to have the exact pad size
@ -366,7 +372,7 @@ public:
* and adds these polygons to aCornerBuffer * and adds these polygons to aCornerBuffer
* Useful to generate a polygonal representation of a footprint * Useful to generate a polygonal representation of a footprint
* in 3D view and plot functions, when a full polygonal approach is needed * in 3D view and plot functions, when a full polygonal approach is needed
* @param aLayer = the current layer: items on this layer are considered * @param aLayer = the layer to consider, or UNDEFINED_LAYER to consider all
* @param aCornerBuffer = the buffer to store polygons * @param aCornerBuffer = the buffer to store polygons
* @param aInflateValue = a value to inflate shapes * @param aInflateValue = a value to inflate shapes
* aInflateValue = 0 to have the exact shape size * aInflateValue = 0 to have the exact shape size
@ -385,7 +391,8 @@ public:
int aInflateValue, int aInflateValue,
int aCircleToSegmentsCount, int aCircleToSegmentsCount,
double aCorrectionFactor, double aCorrectionFactor,
int aCircleToSegmentsCountForTexts = 0 ) const; int aCircleToSegmentsCountForTexts = 0,
bool aIncludeText = true ) const;
/** /**
* @brief TransformGraphicTextWithClearanceToPolygonSet * @brief TransformGraphicTextWithClearanceToPolygonSet
@ -430,6 +437,17 @@ public:
bool HitTest( const wxPoint& aPosition ) const override; bool HitTest( const wxPoint& aPosition ) const override;
/**
* Tests if a point is inside the bounding polygon of the module
*
* The other hit test methods are just checking the bounding box, which
* can be quite inaccurate for rotated or oddly-shaped footprints.
*
* @param aPosition is the point to test
* @return true if aPosition is inside the bounding polygon
*/
bool HitTestAccurate( const wxPoint& aPosition ) const;
bool HitTest( const EDA_RECT& aRect, bool aContained = true, int aAccuracy = 0 ) const override; bool HitTest( const EDA_RECT& aRect, bool aContained = true, int aAccuracy = 0 ) const override;
/** /**

View File

@ -399,7 +399,8 @@ SEARCH_RESULT GENERAL_COLLECTOR::Inspect( EDA_ITEM* testItem, void* testData )
{ {
if( item->HitTest( m_RefPos ) ) if( item->HitTest( m_RefPos ) )
{ {
Append( item ); if( !module || module->HitTestAccurate( m_RefPos ) )
Append( item );
goto exit; goto exit;
} }
} }