← Back to team overview

kicad-developers team mailing list archive

[PATCH] pcbnew: Improved complex board shape handling from DXF imports

 

Hi,

Over the past weekend I saw a forum question/discussion re. the DXF import for complex board shapes (particularly elliptical shapes). A quick test with a elliptical shape indicated that it did not work properly.

I thought it would be fun to dig into that and found that typically these shapes tend to be made of many very small segments (exports from FreeCAD tended to be 0.05mm in length).

There were a few spots in the specctra_export fillBOUNDARY function that struggled with these small segments. Part of that was the use of the close_enough function in checking which end of a segment to use when the segment length was similar in size as the proximity setting. The other problem was that the check on the closing of the polygon that was also using the close_enough test and possibly closed the segment prematurely leaving segments 'dangling' that then got possibly got processed later but no longer could become a closed polygon anymore.

This patch improves on the board outline and keepout tracing particularly from data imported from DXF files. It takes a more accurate decision as to which end of a segment to append to the polygon and only terminates the polygon when no further segments can be found that are close. Error messages are updated to make it clear if the error is with the board outline or with the keep out and shows the coordinates of the start and the end of the polygon so far with the estimated length between them and also how many remaining segments there are to be processed.

So while this is an improvement in the complex shape processing, in testing this I found a couple of interesting issues/quirks around DXF files that may have to be addressed either in the code or in some tutorial/documentation at some point:

* Ellipsis in segments in DXF files tend not to be closed. This becomes quite apparent when segment length is relatively long. When the segment length is very small (0.05mm) this is barely noticeable and it then falls within the current limit of 0.05 mm that allows the code to close the polygon. Using FreeCAD in Draft mode the Max Spline Length can be adjusted in the preferences.

* When exporting a DXF from a 3D model of a board shape in FreeCAD, the DXF seems to have both the top and the bottom of the board in the export. The effect is that the board outline shows in pcbnew, but in the 3D viewer, there is no board visible. This confused me quite a bit but this is due to the first ellipsis being processed as the outline segment and the second ellipsis is processed as the keepout layer that produces a hole the same size of the board. As such no board is visible. There maybe something that can be done about this in the actual DXF import.

Regards,

Marco

=== modified file 'pcbnew/specctra_export.cpp'
--- pcbnew/specctra_export.cpp    2015-03-16 17:39:35 +0000
+++ pcbnew/specctra_export.cpp    2015-06-15 11:54:10 +0000
@@ -82,7 +82,7 @@
 static unsigned close_ness(  const wxPoint& aLeft, const wxPoint& aRight )
 {
     // Don't need an accurate distance calculation, just something
-    // approximating it, for relative orering.
+    // approximating it, for relative ordering.
return unsigned( abs( aLeft.x - aRight.x ) + abs( aLeft.y - aRight.y ) );
 }

@@ -104,6 +104,23 @@
 }


+/**
+ * Function close_first
+ * is a local method of qualifying if either the start of end point of a segment is closest to a point.
+ *
+ * @param aReference is the reference point
+ * @param aFirst is the first point
+ * @param aSecond is the second point
+ * @return bool - true if the the first point is closer to the reference, otherwise false.
+ */
+inline bool close_first( const wxPoint& aReference, const wxPoint& aFirst, const wxPoint& aSecond )
+{
+    // We don't use an accurate distance calculation, just something
+    // approximating to find the closest to the reference.
+ return close_ness( aReference, aFirst ) <= close_ness( aReference, aSecond );
+}
+
+
 // see wxPcbStruct.h
 void PCB_EDIT_FRAME::ExportToSpecctra( wxCommandEvent& event )
 {
@@ -929,6 +946,7 @@

// Find edge point with minimum x, this should be in the outer polygon
         // which will define the perimeter Edge.Cuts polygon.
+
         wxPoint xmin    = wxPoint( INT_MAX, 0 );
         int     xmini   = 0;

@@ -955,7 +973,7 @@
                 break;

             case S_ARC:
-                // freerouter does not yet understand arcs, so approximate
+                // Freerouter does not yet understand arcs, so approximate
                 // an arc with a series of short lines and put those
                 // line segments into the !same! PATH.
                 {
@@ -1023,9 +1041,13 @@
         items.Remove( xmini );

// Set maximum proximity threshold for point to point nearness metric for
-        // board perimeter only, not interior keepouts yet.
- prox = Millimeter2iu( 0.01 ); // should be enough to fix rounding issues - // is arc start and end point calculations
+        // board perimeter only, not interior keep outs yet.
+ prox = Millimeter2iu( 0.05 ); // Should be enough to fix rounding issues + // in arc start and end point calculations + // Also FreeCAD DXF export typically leaves + // a gap at the end of the polygon that can + // be around the segment length used.
+                                        // So 0.01mm was too small.

         // Output the Edge.Cuts perimeter as circle or polygon.
         if( graphic->GetShape() == S_CIRCLE )
@@ -1034,8 +1056,10 @@
         }
         else
         {
+            // Polygon start point. Arbitrarily chosen end of the
+            // segment and build the poly from here.
+
             wxPoint startPt = wxPoint( graphic->GetEnd() );
-
             prevPt = graphic->GetEnd();
             path->AppendPoint( mapPt( prevPt ) );

@@ -1050,19 +1074,27 @@
                     {
                         wxPoint  nextPt;

- if( !close_enough( prevPt, graphic->GetStart(), prox ) )
-                        {
- wxASSERT( close_enough( prevPt, graphic->GetEnd(), prox ) ); + // Use the line segment end point furthest away from + // prevPt as we assume the other end to be ON prevPt or
+                        // very close to it.
+
+ if( close_first( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
+                        {
+                            nextPt = graphic->GetEnd();
+                        }
+                        else
+                        {
                             nextPt = graphic->GetStart();
                         }
-                        else
-                        {
- wxASSERT( close_enough( prevPt, graphic->GetStart(), prox ) );
-                            nextPt = graphic->GetEnd();
-                        }

                         path->AppendPoint( mapPt( nextPt ) );
                         prevPt = nextPt;
+
+                        printf( "line GetStart()=%d,%d GetEnd()=%d,%d\n",
+                                graphic->GetStart().x,
+                                graphic->GetStart().y,
+                                graphic->GetEnd().x,
+                                graphic->GetEnd().y );
                     }
                     break;

@@ -1115,27 +1147,43 @@
                     break;
                 }

- if( close_enough( startPt, prevPt, prox ) ) // the polygon is closed.
-                    break;
-
+                // Get next closest segment.
+
                 graphic = findPoint( prevPt, &items, prox );

-                if( !graphic )
+                // If there are no more close segments, check if the board
+                // outline polygon can be closed.
+
+                if( !graphic )
                 {
-                    wxString error = wxString::Format(
- _( "Unable to find the next segment with an endpoint of (%s mm, %s mm).\n" - "Edit Edge.Cuts perimeter graphics, making them contiguous polygons each." ), - GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ), - GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) )
-                        );
-                    ThrowIOError( error );
+                    if( !close_enough( startPt, prevPt, prox ) )
+                    {
+                        wxString error = wxString::Format(
+ _( "The board outline polygon with start (x: %s mm, y: %s mm)\n" + "and end point (x: %s mm, y: %s mm) seem to have a gap\n"
+                               "of approximately %s mm.\n\n"
+ "Edit the Edge.Cuts perimeter graphics to make the\n"
+                               "board outline a contiguous polygon.\n\n"
+ "There are %d remaining unprocessed line segments.\n" ), + GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( startPt.x ).c_str() ) ), + GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( startPt.y ).c_str() ) ), + GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ), + GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) ), + GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( close_ness( startPt, prevPt ) ).c_str() ) ),
+                            items.GetCount()
+                            );
+
+                        ThrowIOError( error );
+                    }
+                    break;
                 }
             }
         }

- // Output the interior Edge.Cuts graphics as keepouts, using nearness metric
-        // for sloppy graphical items.
-        prox = Millimeter2iu( 0.25 );
+ // Output the interior Edge.Cuts graphics as keepouts, using same nearness + // metric as the board edge as otherwise we have trouble completing complex
+        // polygons.
+        prox = Millimeter2iu( 0.05 );

         while( items.GetCount() )
         {
@@ -1155,11 +1203,15 @@
             }
             else
             {
+                // Polygon start point. Arbitrarily chosen end of the
+                // segment and build the poly from here.
+
                 wxPoint startPt( graphic->GetEnd() );
                 prevPt = graphic->GetEnd();
                 poly_ko->AppendPoint( mapPt( prevPt ) );

- // do not append the other end point yet, this first 'graphic' might be an arc + // Do not append the other end point yet, this first 'graphic'
+                // might be an arc
                 for(;;)
                 {
                     switch( graphic->GetShape() )
@@ -1168,16 +1220,18 @@
                         {
                             wxPoint nextPt;

- if( !close_enough( prevPt, graphic->GetStart(), prox ) )
-                            {
- wxASSERT( close_enough( prevPt, graphic->GetEnd(), prox ) ); + // Use the line segment end point furthest away from + // prevPt as we assume the other end to be ON prevPt or
+                            // very close to it.
+
+ if( close_first( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
+                            {
+                                nextPt = graphic->GetEnd();
+                            }
+                            else
+                            {
                                 nextPt = graphic->GetStart();
                             }
-                            else
-                            {
- wxASSERT( close_enough( prevPt, graphic->GetStart(), prox ) );
-                                nextPt = graphic->GetEnd();
-                            }

                             prevPt = nextPt;
                             poly_ko->AppendPoint( mapPt( prevPt ) );
@@ -1185,7 +1239,7 @@
                         break;

                     case S_ARC:
- // freerouter does not yet understand arcs, so approximate + // Freerouter does not yet understand arcs, so approximate // an arc with a series of short lines and put those
                         // line segments into the !same! PATH.
                         {
@@ -1233,22 +1287,36 @@
                         }
                         break;
                     }
-
-                    if( close_enough( startPt, prevPt, prox ) )
-                        break;
-
+
+                    // Get next closest segment.
+
                     graphic = findPoint( prevPt, &items, prox );

+ // If there are no more close segments, check if polygon
+                    // can be closed.
+
                     if( !graphic )
                     {
-                        wxString error = wxString::Format(
- _( "Unable to find the next segment with an endpoint of (%s mm, %s mm).\n" - "Edit Edge.Cuts interior graphics, making them contiguous polygons each." ), - GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ), - GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) )
-                            );
+                        if( !close_enough( startPt, prevPt, prox ) )
+                        {
+                            wxString error = wxString::Format(
+ _( "The keep out polygon with start (x: %s mm, y: %s mm)\n" + "and end point (x: %s mm, y: %s mm) seems to have a gap\n"
+                                   "of approximately %s mm.\n\n"
+ "Edit the Edge.Cuts perimeter graphics, making them\n"
+                                   "all contiguous polygons.\n\n"
+ "There are %d remaining unprocessed line segments.\n" ), + GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( startPt.x ).c_str() ) ), + GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( startPt.y ).c_str() ) ), + GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ), + GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) ), + GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( close_ness( startPt, prevPt ) ).c_str() ) ),
+                                items.GetCount()
+                                );

-                        ThrowIOError( error );
+                            ThrowIOError( error );
+                        }
+                        break;
                     }
                 }
             }



--
Marco Hess
Through IP Pty. Ltd. - AUSTRALIA
www.through-ip.com  | marco.hess@xxxxxxxxxxxxxx
p: +61 407 78 55 66 | f: +61 8 8121 6191