← Back to team overview

kicad-developers team mailing list archive

[PATCH] Use polygonal hit testing for module selection

 

Hi all,

The attached patch adds some plumbing to calculate and make use of a
polygonal bounding area for modules.  It fixes the below issue and in
general improves the accuracy of selection in my testing.

This mechanism could be extended to other objects besides modules if it's
useful.  I figured I'd start by sending out this patch to get feedback, and
if it gets merged, look for other areas where we could improve things by
using polygons instead of bounding boxes.

https://bugs.launchpad.net/kicad/+bug/1749077

-Jon
From 0710b3f0cc831faf064fdb6fc9108606d6919e01 Mon Sep 17 00:00:00 2001
From: Jon Evans <jon@xxxxxxxxxxxxx>
Date: Sun, 18 Feb 2018 12:09:00 -0500
Subject: [PATCH] Use polygonal hit testing for module selection

Fixes: lp:1749077
* https://bugs.launchpad.net/kicad/+bug/1749077
---
 pcbnew/board_items_to_polygon_shape_transform.cpp | 13 ++++++---
 pcbnew/class_module.cpp                           | 32 +++++++++++++++++++++++
 pcbnew/class_module.h                             | 24 ++++++++++++++---
 pcbnew/collectors.cpp                             |  3 ++-
 4 files changed, 64 insertions(+), 8 deletions(-)

diff --git a/pcbnew/board_items_to_polygon_shape_transform.cpp b/pcbnew/board_items_to_polygon_shape_transform.cpp
index fd3e763b0..4d5200e61 100644
--- a/pcbnew/board_items_to_polygon_shape_transform.cpp
+++ b/pcbnew/board_items_to_polygon_shape_transform.cpp
@@ -138,7 +138,7 @@ void MODULE::TransformPadsShapesWithClearanceToPolygon( PCB_LAYER_ID aLayer,
     wxSize margin;
     for( ; pad != NULL; pad = pad->Next() )
     {
-        if( !pad->IsOnLayer(aLayer) )
+        if( aLayer != UNDEFINED_LAYER && !pad->IsOnLayer(aLayer) )
             continue;
 
         // 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             aCircleToSegmentsCount,
                         double          aCorrectionFactor,
-                        int             aCircleToSegmentsCountForTexts ) const
+                        int             aCircleToSegmentsCountForTexts,
+                        bool            aIncludeText ) const
 {
     std::vector<TEXTE_MODULE *> texts;  // List of TEXTE_MODULE to convert
     EDGE_MODULE* outline;
@@ -219,7 +220,8 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet(
             {
                 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 );
 
                 break;
@@ -228,7 +230,7 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet(
         case PCB_MODULE_EDGE_T:
             outline = (EDGE_MODULE*) item;
 
-            if( outline->GetLayer() != aLayer )
+            if( aLayer != UNDEFINED_LAYER && outline->GetLayer() != aLayer )
                 break;
 
             outline->TransformShapeWithClearanceToPolygon( aCornerBuffer, 0,
@@ -240,6 +242,9 @@ void MODULE::TransformGraphicShapesWithClearanceToPolygonSet(
         }
     }
 
+    if( !aIncludeText )
+        return;
+
     // Convert texts sur modules
     if( Reference().GetLayer() == aLayer && Reference().IsVisible() )
         texts.push_back( &Reference() );
diff --git a/pcbnew/class_module.cpp b/pcbnew/class_module.cpp
index c201a0f5d..0250d40ed 100644
--- a/pcbnew/class_module.cpp
+++ b/pcbnew/class_module.cpp
@@ -512,6 +512,29 @@ 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, temp;
+
+    TransformPadsShapesWithClearanceToPolygon( UNDEFINED_LAYER,
+            temp, 0, segcountforcircle, correctionFactor );
+
+    poly.BooleanAdd( temp, SHAPE_POLY_SET::PM_FAST );
+
+    TransformGraphicShapesWithClearanceToPolygonSet( UNDEFINED_LAYER,
+            temp, 0, segcountforcircle, correctionFactor, 0, false );
+
+    poly.BooleanAdd( temp, SHAPE_POLY_SET::PM_FAST );
+
+    poly.Fracture( SHAPE_POLY_SET::PM_FAST );
+    poly.Inflate( Millimeter2iu( 0.01 ), segcountforcircle );
+
+    return poly;
+}
+
+
 void MODULE::GetMsgPanelInfo( std::vector< MSG_PANEL_ITEM >& aList )
 {
     int      nbpad;
@@ -607,6 +630,15 @@ bool MODULE::HitTest( const wxPoint& aPosition ) const
 }
 
 
+bool MODULE::HitTestAccurate( const wxPoint& aPosition ) const
+{
+    const int clearance = 10;
+
+    auto shape = GetBoundingPoly();
+    return shape.Collide( aPosition, clearance );
+}
+
+
 bool MODULE::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const
 {
     EDA_RECT arect = aRect;
diff --git a/pcbnew/class_module.h b/pcbnew/class_module.h
index 3eca1a015..ac5d1c2ec 100644
--- a/pcbnew/class_module.h
+++ b/pcbnew/class_module.h
@@ -148,6 +148,12 @@ public:
      */
     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
     const EDA_RECT GetBoundingBox() const override;
 
@@ -336,7 +342,7 @@ public:
      * and adds these polygons to aCornerBuffer
      * Useful to generate a polygonal representation of a footprint
      * 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 aInflateValue = an additionnal size to add to pad shapes
      *          aInflateValue = 0 to have the exact pad size
@@ -366,7 +372,7 @@ public:
      * and adds these polygons to aCornerBuffer
      * Useful to generate a polygonal representation of a footprint
      * 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 aInflateValue = a value to inflate shapes
      *          aInflateValue = 0 to have the exact shape size
@@ -385,7 +391,8 @@ public:
             int aInflateValue,
             int aCircleToSegmentsCount,
             double aCorrectionFactor,
-            int aCircleToSegmentsCountForTexts = 0 ) const;
+            int aCircleToSegmentsCountForTexts = 0,
+            bool aIncludeText = true ) const;
 
     /**
      * @brief TransformGraphicTextWithClearanceToPolygonSet
@@ -430,6 +437,17 @@ public:
 
     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;
 
     /**
diff --git a/pcbnew/collectors.cpp b/pcbnew/collectors.cpp
index 8b3e7d37a..cd692adfc 100644
--- a/pcbnew/collectors.cpp
+++ b/pcbnew/collectors.cpp
@@ -399,7 +399,8 @@ SEARCH_RESULT GENERAL_COLLECTOR::Inspect( EDA_ITEM* testItem, void* testData )
                 {
                     if( item->HitTest( m_RefPos ) )
                     {
-                        Append( item );
+                        if( !module || module->HitTestAccurate( m_RefPos ) )
+                            Append( item );
                         goto exit;
                     }
                 }
-- 
2.14.1


Follow ups