← Back to team overview

kicad-developers team mailing list archive

Enhancement for DRC during track drawing

 

I'm using this since a few days and I'm finding it useful. Here's the
patch (I don't want to commit it without some feedback, it's on the "for
me it works" quality/testing level)

It does two things:

1) Implements the 'lean on track' behaviour even for single segment and
   free direction mode; it 'pulls back' the track *along the original
   vector* so that clearance is kept. Well, most of the time :D

2) It adds an indicator (think about the autocad yellow snap markers) on
   the 'head' of the dragged track when the pushing effect is invoked or
   when local drc fails even after the push action (there could be
   another track)

The indicator is a cross for failure and a triangle for track pushed.
It's useful since it means that you're tracking out of grid (I had
problem with that and had to retrack a whole trunk of more than 20
tracks :D). This is especially important since DRC works as >, 
not as >=, so for a 0,2mm clearance I have to use 0,1999 to pass
(clearly the push action put tracks at 0,1999 and *that* fails the board
vendor's DRC!).

Also another thing I'd like to add is 'leaning to' via and pads, when
I've some inspiration...


-- 
Lorenzo Marcantonio
Logos Srl
=== modified file 'pcbnew/editrack.cpp'
--- pcbnew/editrack.cpp	2013-03-31 13:41:41 +0000
+++ pcbnew/editrack.cpp	2013-04-11 14:23:26 +0000
@@ -460,7 +460,7 @@
                     // creates a lock point if not exists
         {
              // Creates a lock point, if not already exists:
-            wxPoint hp = g_CurrentTrackSegment->GetEnd(); 
+            wxPoint hp = g_CurrentTrackSegment->GetEnd();
             LockPoint = GetBoard()->CreateLockPoint( hp, (TRACK*) LockPoint, &s_ItemsListPicker );
             g_CurrentTrackSegment->SetEnd(hp);
         }
@@ -535,7 +535,8 @@
 }
 
 
-TRACK* LocateIntrusion( TRACK* listStart, TRACK* aTrack, LAYER_NUM aLayer, const wxPoint& aRef )
+TRACK* LocateIntrusion( TRACK* listStart, TRACK* aTrack, LAYER_NUM aLayer,
+                        const wxPoint& aRef )
 {
     int     net   = aTrack->GetNet();
     int     width = aTrack->GetWidth();
@@ -584,6 +585,9 @@
  * clearance zone. This gives us a cheap mechanism for drawing tracks that
  * tightly follow others, independent of grid settings.
  *
+ * It also return status on the operation in the two boolean parameters
+ * (they'll be used to draw the indicator during drawing)
+ *
  * KNOWN BUGS:
  * - we do the same sort of search and calculation up to three times:
  *   1) we search for magnetic hits (in controle.cpp)
@@ -593,66 +597,199 @@
  *   the magnetic hit instead of solving the violation
  * - should locate conflicting tracks also when we're crossing over them
  */
-static void PushTrack( EDA_DRAW_PANEL* panel )
+bool Join( wxPoint* aIntersectPoint, wxPoint a0, wxPoint a1, wxPoint b0, wxPoint b1 );
+
+static void PushTrack( EDA_DRAW_PANEL* panel, bool aSingleTrack,
+                       bool *aFailed, bool *aPushed )
 {
     PCB_SCREEN* screen = ( (PCB_BASE_FRAME*) (panel->GetParent()) )->GetScreen();
     BOARD*  pcb    = ( (PCB_BASE_FRAME*) (panel->GetParent()) )->GetBoard();
     wxPoint cursor = screen->GetCrossHairPosition();
-    wxPoint cv, vec, n;
-    TRACK*  track = g_CurrentTrackSegment;
-    TRACK*  other;
-    double  det;
-    int     dist;
-    double  f;
-
-    other = LocateIntrusion( pcb->m_Track, track, screen->m_Active_Layer, screen->RefPos( true ) );
+    TRACK*  track  = g_CurrentTrackSegment;
+    TRACK*  other  = LocateIntrusion( pcb->m_Track, track,
+                                      screen->m_Active_Layer,
+                                      track->GetEnd() );
+    // Be optimistic...
+    *aFailed = false;
+    *aPushed = false;
 
     // are we currently pointing into a conflicting trace ?
     if( !other )
         return;
 
+    // Is the other trace actually in our same net?
     if( other->GetNet() == track->GetNet() )
         return;
 
-    cv  = cursor - other->GetStart();
-    vec = other->GetEnd() - other->GetStart();
-
-    det = (double) cv.x * vec.y - (double) cv.y * vec.x;
-
-    // cursor is right at the center of the old track
-    if( !det )
-        return;
-
-    dist = (track->GetWidth() + 1) / 2 + (other->GetWidth() + 1) / 2 + track->GetClearance( other ) + 2;
+    wxPoint cv  = cursor - other->GetStart();
+    wxPoint vec = other->GetEnd() - other->GetStart();
 
     /*
      * DRC wants >, so +1.
      * We may have a quantization error of 1/sqrt(2), so +1 again.
      */
+    int dist = (track->GetWidth() + 1) / 2 + (other->GetWidth() + 1) / 2
+             + track->GetClearance( other ) + 2;
 
-    // Vector "n" is perpendicular to "other", pointing towards the cursor.
-    if( det > 0 )
+    if( !aSingleTrack )
     {
-        n.x = vec.y;
-        n.y = -vec.x;
+        double det = (double) cv.x * vec.y - (double) cv.y * vec.x;
+
+        // cursor is right at the center of the old track
+        if( !det )
+        {
+            *aFailed = true;
+            return;
+        }
+
+        // Vector "n" is perpendicular to "other", pointing towards the cursor.
+        wxPoint n;
+        if( det > 0 )
+        {
+            n.x = vec.y;
+            n.y = -vec.x;
+        }
+        else
+        {
+            n.x = -vec.y;
+            n.y = vec.x;
+        }
+
+        double f = dist / hypot( double(n.x), double(n.y) );
+        n.x = KiROUND( f * n.x );
+        n.y = KiROUND( f * n.y );
+
+        wxPoint hp = track->GetEnd();
+        Project( &hp, cursor, other );
+        track->SetEnd( hp + n );
     }
     else
     {
-        n.x = -vec.y;
-        n.y = vec.x;
-    }
-
-    f   = dist / hypot( double(n.x), double(n.y) );
-    n.x = KiROUND( f * n.x );
-    n.y = KiROUND( f * n.y );
-
-    wxPoint hp = track->GetEnd();
-    Project( &hp, cursor, other );
-    track->SetEnd( hp + n );
-}
-
-
-//Helpre function: Draws Via circle and Via Clearence circle.
+        /* Another way to see the solution: find the intercept between
+           tracks and back off the required distance */
+        wxPoint xsect;
+        wxPoint tk_base = track->GetStart();
+        wxPoint tk_vec = track->GetEnd() - tk_base;
+        wxPoint ot_rebased = other->GetStart() - tk_base;
+        wxPoint ot_vec = other->GetEnd() - other->GetStart();
+
+        double det = double(ot_vec.y) * double(tk_vec.x)
+                   - double(ot_vec.x) * double(tk_vec.y);
+
+        // This will be (near) zero with parallel tracks
+        if( std::abs( det ) > 1e-6 )
+        {
+            double proj = double(ot_vec.y) * double(ot_rebased.x)
+                        - double(ot_vec.x) * double(ot_rebased.y);
+            proj /= det;
+
+            // xc, yc is the projected crossing point between tracks
+            double xc = tk_base.x + proj * tk_vec.x;
+            double yc = tk_base.y + proj * tk_vec.y;
+
+            /* A more or less ingenious way to solve a triangle with the
+               law of sines, only having the cosine of the angle: the
+               cosine can be had from the cross product, and Euler
+               identity give us the sine. The angle is necessarily acute
+               since this is a right triangle. *Or* it could be 90 deg
+               if we're approaching head on on the track, so the cos
+               would be come out as 0. Most of the time however it would
+               be 0.707..., i.e. 45 degrees.  */
+            double cosgamma = ( double(ot_vec.x) * double(tk_vec.x)
+                    + double(ot_vec.y) * double(tk_vec.y) )
+                / (hypot( ot_vec.x, ot_vec.y ) * hypot( tk_vec.x, tk_vec.y ));
+            double singamma = sqrt( 1.0 - cosgamma * cosgamma );
+
+            /* When track are nearly parallel the intersection would
+               happen *behind* the start of the track; this is an
+               annoyance, since (most probably) the other track has
+               ended before (but we don't considered where). TODO it
+               could be solved but in practice it seems to happen only
+               with free angled tracks */
+            double backoff = dist / singamma;
+
+            // Base vector for the new track (to back off)
+            double tl = hypot( tk_vec.x, tk_vec.y );
+
+            /* Distance from the intersection and the track origin
+               (we're going back from the intersection but don't want to
+               pass over the origin) */
+            double xdist = hypot( xc - tk_base.x, yc - tk_base.y );
+            if( xdist - backoff > 0 )
+            {
+                double xu = tk_vec.x / tl;
+                double yu = tk_vec.y / tl;
+
+                wxPoint newend( KiROUND( xc - xu * backoff ),
+                                KiROUND( yc - yu * backoff ) );
+                track->SetEnd( newend );
+            }
+            else
+            {
+                // That would have reversed the track, mark it as a failure
+                *aFailed = true;
+            }
+        }
+        else
+        {
+            *aFailed = true;
+        }
+    }
+    *aPushed = true;
+
+    // After the push, recheck for further collisions
+    other  = LocateIntrusion( pcb->m_Track, track,
+                              screen->m_Active_Layer,
+                              track->GetEnd() );
+
+    // OK, we moved the track but the coast is clear now
+    if( !other )
+        return;
+
+    // No issue, if the net is the same
+    if( other->GetNet() == track->GetNet() )
+        return;
+
+    // Ouch... we run into something else...
+    *aFailed = true;
+}
+
+static void DrawPushFailIndicator( EDA_RECT *aPanelClipBox, wxDC *aDC,
+                                   bool aTrackPushed, bool aPushFailed,
+                                   const wxPoint &aPos )
+{
+    // Pick a good size (arbitrary!)
+    int len = aDC->DeviceToLogicalXRel( 15 );
+    int width = aDC->DeviceToLogicalXRel( 3 );
+
+    if( aPushFailed || aTrackPushed )
+    {
+        GRSetDrawMode( aDC, GR_XOR );
+        if( aPushFailed )
+        {
+            GRLine( aPanelClipBox, aDC,
+                    aPos.x - len, aPos.y - len,
+                    aPos.x + len, aPos.y + len, width, WHITE );
+            GRLine( aPanelClipBox, aDC,
+                    aPos.x + len, aPos.y - len,
+                    aPos.x - len, aPos.y + len, width, WHITE );
+        }
+        else
+        {
+            GRLine( aPanelClipBox, aDC,
+                    aPos.x, aPos.y - len,
+                    aPos.x + len, aPos.y + len, width, WHITE );
+            GRLine( aPanelClipBox, aDC,
+                    aPos.x + len, aPos.y + len,
+                    aPos.x - len, aPos.y + len, width, WHITE );
+            GRLine( aPanelClipBox, aDC,
+                    aPos.x - len, aPos.y + len,
+                    aPos.x, aPos.y - len, width, WHITE );
+        }
+    }
+}
+
+// Helper function: Draws Via circle and Via Clearance circle.
 inline void DrawViaCirclesWhenEditingNewTrack( EDA_RECT* aPanelClipBox,
                                                wxDC* aDC, const wxPoint& aPos,
                                                int aViaRadius,
@@ -670,6 +807,11 @@
 void ShowNewTrackWhenMovingCursor( EDA_DRAW_PANEL* aPanel, wxDC* aDC, const wxPoint& aPosition,
                                    bool aErase )
 {
+    // Keep track of this for a warning indication
+    // It's static to allow erasing on the following call
+    static bool s_track_pushed;
+    static bool s_push_failed;
+
 //    D( g_CurrentTrackList.VerifyListIntegrity(); );
 
     PCB_SCREEN*     screen = (PCB_SCREEN*) aPanel->GetScreen();
@@ -706,6 +848,8 @@
             DrawViaCirclesWhenEditingNewTrack( panelClipBox, aDC, g_CurrentTrackSegment->GetEnd(),
                                                boardViaRadius, viaRadiusWithClearence, color);
         }
+        DrawPushFailIndicator( panelClipBox, aDC, s_track_pushed, s_push_failed,
+                               g_CurrentTrackSegment->GetEnd() );
     }
 #endif
     // MacOSX seems to need this.
@@ -738,7 +882,7 @@
             g_CurrentTrackSegment->SetEnd( screen->GetCrossHairPosition() );
 
             if( g_Drc_On )
-                PushTrack( aPanel );
+                PushTrack( aPanel, false, &s_push_failed, &s_track_pushed );
 
             ComputeBreakPoint( g_CurrentTrackSegment,
                                g_CurrentTrackList.GetCount(),
@@ -756,11 +900,17 @@
                                       &hp.x,
                                       &hp.y );
             g_CurrentTrackSegment->SetEnd(hp);
+
+            if( g_Drc_On )
+                PushTrack( aPanel, true, &s_push_failed, &s_track_pushed );
+
         }
     }
     else    // Here the angle is arbitrary
     {
         g_CurrentTrackSegment->SetEnd( screen->GetCrossHairPosition() );
+        if( g_Drc_On )
+            PushTrack( aPanel, true, &s_push_failed, &s_track_pushed );
     }
 
     // Redraw the new track
@@ -776,6 +926,8 @@
                                            boardViaRadius, viaRadiusWithClearence, color);
 
     }
+    DrawPushFailIndicator( panelClipBox, aDC, s_track_pushed, s_push_failed,
+                           g_CurrentTrackSegment->GetEnd() );
 
     /* Display info about current segment and the full new track:
      *  Choose the interesting segment: because we are using a 2 segments step,