← Back to team overview

kicad-developers team mailing list archive

PATCH: various fixes to DXF import

 

The attached patch makes the following changes to import DXF:

1. correctly implements scaling based on DXF $INSUNITS -
at least where INSUNITS is sensible. I ignore units like miles,
gigameters, AU, lightyear, parsec ...

2. correctly implements LWPolylines, adding an ARC segment
rather than a line segment when bulge != 0.  A number of users
have been complaining about getting the wrong board shape
(chamfered corners rather than rounded corners) on DXF Import,
now they shouldn't complain.

The patch has been made against the main branch r6633. I
have tested polylines with +/- bulge and arcs < 180deg and
> 180 deg. I think I have covered all cases of arcs but if anyone
has DXF test files with special cases, please try them.

The patch is small enough and fixes significant bugs so I
think it should be considered for inclusion in a Stable fix.
Due to a more recent commit in the main branch (r6630)
a little care is required applying to the Stable branch as
this patch undoes the changes in r6630.

I can't find any bug reports affected by this patch using
'dxf' as the sole search criterion.

- Cirilo
=== modified file 'pcbnew/import_dxf/dxf2brd_items.cpp'
--- pcbnew/import_dxf/dxf2brd_items.cpp	2016-03-20 03:51:02 +0000
+++ pcbnew/import_dxf/dxf2brd_items.cpp	2016-03-21 00:36:27 +0000
@@ -46,11 +46,17 @@
 #include <class_drawsegment.h>
 #include <class_pcb_text.h>
 #include <convert_from_iu.h>
+#include <drw_base.h>
+
+// minimum bulge value before resorting to a line segment;
+// the value 0.0218 is equivalent to about 5 degrees arc,
+#define MIN_BULGE 0.0218
 
 DXF2BRD_CONVERTER::DXF2BRD_CONVERTER() : DRW_Interface()
 {
     m_xOffset   = 0.0;      // X coord offset for conversion (in mm)
     m_yOffset   = 0.0;      // Y coord offset for conversion (in mm)
+    m_DXF2mm    = 1.0;      // The scale factor to convert DXF units to mm
     m_version   = 0;
     m_defaultThickness = 0.1;
     m_brdLayer = Dwgs_User;
@@ -65,19 +71,19 @@
 // coordinate conversions from dxf to internal units
 int DXF2BRD_CONVERTER::mapX( double aDxfCoordX )
 {
-    return Millimeter2iu( m_xOffset + ( aDxfCoordX * DXF_UNITS_PER_MM ) );
+    return Millimeter2iu( m_xOffset + ( aDxfCoordX * m_DXF2mm ) );
 }
 
 
 int DXF2BRD_CONVERTER::mapY( double aDxfCoordY )
 {
-    return Millimeter2iu( m_yOffset - ( aDxfCoordY * DXF_UNITS_PER_MM ) );
+    return Millimeter2iu( m_yOffset - ( aDxfCoordY * m_DXF2mm ) );
 }
 
 
 int DXF2BRD_CONVERTER::mapDim( double aDxfValue )
 {
-    return Millimeter2iu( aDxfValue * DXF_UNITS_PER_MM );
+    return Millimeter2iu( aDxfValue * m_DXF2mm );
 }
 
 
@@ -175,8 +181,11 @@
     // The import is a simplified import: the width of segment is
     // (obviously constant and is the width of the DRW_LWPolyline.
     // the variable width of each vertex (when exists) is not used.
-    wxPoint lwpolyline_startpoint;
-    wxPoint segment_startpoint;
+    wxRealPoint seg_start;
+    wxRealPoint poly_start;
+    double bulge = 0.0;
+    int lineWidth = mapDim( aData.thickness == 0 ? m_defaultThickness
+                            : aData.thickness );
 
     for( unsigned ii = 0; ii < aData.vertlist.size(); ii++ )
     {
@@ -184,35 +193,31 @@
 
         if( ii == 0 )
         {
-            segment_startpoint.x  = mapX( vertex->x );
-            segment_startpoint.y  = mapY( vertex->y );
-            lwpolyline_startpoint = segment_startpoint;
+            seg_start.x = m_xOffset + vertex->x * m_DXF2mm;
+            seg_start.y = m_yOffset - vertex->y * m_DXF2mm;
+            bulge = vertex->bulge;
+            poly_start = seg_start;
             continue;
         }
 
-        DRAWSEGMENT*    segm = new DRAWSEGMENT( NULL );
-
-        segm->SetLayer( ToLAYER_ID( m_brdLayer ) );
-        segm->SetStart( segment_startpoint );
-        wxPoint segment_endpoint( mapX( vertex->x ), mapY( vertex->y ) );
-        segm->SetEnd( segment_endpoint );
-        segm->SetWidth( mapDim( aData.thickness == 0 ? m_defaultThickness
-                                : aData.thickness ) );
-        m_newItemsList.push_back( segm );
-        segment_startpoint = segment_endpoint;
+        wxRealPoint seg_end( m_xOffset + vertex->x * m_DXF2mm, m_yOffset - vertex->y * m_DXF2mm );
+
+        if( std::abs( bulge ) < MIN_BULGE )
+            insertLine( seg_start, seg_end, lineWidth );
+        else
+            insertArc( seg_start, seg_end, bulge, lineWidth );
+
+        bulge = vertex->bulge;
+        seg_start = seg_end;
     }
 
     // LWPolyline flags bit 0 indicates closed (1) or open (0) polyline
     if( aData.flags & 1 )
     {
-        DRAWSEGMENT*    closing_segm = new DRAWSEGMENT( NULL );
-
-        closing_segm->SetLayer( ToLAYER_ID( m_brdLayer ) );
-        closing_segm->SetStart( segment_startpoint );
-        closing_segm->SetEnd( lwpolyline_startpoint );
-        closing_segm->SetWidth( mapDim( aData.thickness == 0 ? m_defaultThickness
-                                : aData.thickness ) );
-        m_newItemsList.push_back( closing_segm );
+        if( std::abs( bulge ) < MIN_BULGE )
+            insertLine( seg_start, poly_start, lineWidth );
+        else
+            insertArc( seg_start, poly_start, bulge, lineWidth );
     }
 }
 
@@ -475,6 +480,7 @@
 void DXF2BRD_CONVERTER::addHeader( const DRW_Header* data )
 {
     std::map<std::string, DRW_Variant*>::const_iterator it;
+    m_DXF2mm = 1.0; // assume no scale factor
 
     for( it = data->vars.begin(); it != data->vars.end(); ++it )
     {
@@ -485,6 +491,71 @@
             DRW_Variant* var = (*it).second;
             m_codePage = ( *var->content.s );
         }
+        else if( key == "$INSUNITS" )
+        {
+            DRW_Variant* var = (*it).second;
+
+            switch( var->content.i )
+            {
+                case 1:     // inches
+                    m_DXF2mm = 25.4;
+                    break;
+
+                case 2:     // feet
+                    m_DXF2mm = 304.8;
+                    break;
+
+                case 5:     // centimeters
+                    m_DXF2mm = 10.0;
+                    break;
+
+                case 6:     // meters
+                    m_DXF2mm = 1000.0;
+                    break;
+
+                case 8:     // microinches
+                    m_DXF2mm = 2.54e-5;
+                    break;
+
+                case 9:     // mils
+                    m_DXF2mm = 0.0254;
+                    break;
+
+                case 10:    // yards
+                    m_DXF2mm = 914.4;
+                    break;
+
+                case 11:    // Angstroms
+                    m_DXF2mm = 1.0e-7;
+                    break;
+
+                case 12:    // nanometers
+                    m_DXF2mm = 1.0e-6;
+                    break;
+
+                case 13:    // micrometers
+                    m_DXF2mm = 1.0e-3;
+                    break;
+
+                case 14:    // decimeters
+                    m_DXF2mm = 100.0;
+                    break;
+
+                default:
+                    // use the default of 1.0 for:
+                    // 0: Unspecified Units
+                    // 4: mm
+                    // 3: miles
+                    // 7: kilometers
+                    // 15: decameters
+                    // 16: hectometers
+                    // 17: gigameters
+                    // 18: AU
+                    // 19: lightyears
+                    // 20: parsecs
+                    break;
+            }
+        }
     }
 }
 
@@ -632,3 +703,99 @@
 {
     // TODO
 }
+
+
+void DXF2BRD_CONVERTER::insertLine( const wxRealPoint& aSegStart,
+                                    const wxRealPoint& aSegEnd, int aWidth )
+{
+    DRAWSEGMENT*    segm = new DRAWSEGMENT( NULL );
+    wxPoint segment_startpoint( Millimeter2iu( aSegStart.x ), Millimeter2iu( aSegStart.y ) );
+    wxPoint segment_endpoint( Millimeter2iu( aSegEnd.x ), Millimeter2iu( aSegEnd.y ) );
+
+    segm->SetLayer( ToLAYER_ID( m_brdLayer ) );
+    segm->SetStart( segment_startpoint );
+    segm->SetEnd( segment_endpoint );
+    segm->SetWidth( aWidth );
+
+    m_newItemsList.push_back( segm );
+    return;
+}
+
+
+void DXF2BRD_CONVERTER::insertArc( const wxRealPoint& aSegStart, const wxRealPoint& aSegEnd,
+                                   double aBulge, int aWidth )
+{
+    DRAWSEGMENT*    segm = new DRAWSEGMENT( NULL );
+
+    wxPoint segment_startpoint( Millimeter2iu( aSegStart.x ), Millimeter2iu( aSegStart.y ) );
+    wxPoint segment_endpoint( Millimeter2iu( aSegEnd.x ), Millimeter2iu( aSegEnd.y ) );
+
+    // ensure aBulge represents an angle from +/- ( 0 .. approx 359.8 deg )
+    if( aBulge < -2000.0 )
+        aBulge = -2000.0;
+    else if( aBulge > 2000.0 )
+        aBulge = 2000.0;
+
+    double ang = 4.0 * atan( aBulge );
+
+    // reflect the Y values to put everything in a RHCS
+    wxRealPoint sp( aSegStart.x, -aSegStart.y );
+    wxRealPoint ep( aSegEnd.x, -aSegEnd.y );
+    // angle from end->start
+    double offAng = atan2( ep.y - sp.y, ep.x - sp.x );
+    // length of subtended segment = 1/2 distance between the 2 points
+    double d = 0.5 * sqrt( (sp.x - ep.x) * (sp.x - ep.x) + (sp.y - ep.y) * (sp.y - ep.y) );
+    // midpoint of the subtended segment
+    double xm   = ( sp.x + ep.x ) * 0.5;
+    double ym   = ( sp.y + ep.y ) * 0.5;
+    double radius = d / sin( ang * 0.5 );
+
+    if( radius < 0.0 )
+        radius = -radius;
+
+    // calculate the height of the triangle with base d and hypotenuse r
+    double dh2 = radius * radius - d * d;
+
+    // this should only ever happen due to rounding errors when r == d
+    if( dh2 < 0.0 )
+        dh2 = 0.0;
+
+    double h = sqrt( dh2 );
+
+    if( ang < 0.0 )
+        offAng -= M_PI_2;
+    else
+        offAng += M_PI_2;
+
+    // for angles greater than 180 deg we need to flip the
+    // direction in which the arc center is found relative
+    // to the midpoint of the subtended segment.
+    if( ang < -M_PI )
+        offAng += M_PI;
+    else if( ang > M_PI )
+        offAng -= M_PI;
+
+    // center point
+    double cx = h * cos( offAng ) + xm;
+    double cy = h * sin( offAng ) + ym;
+
+    segm->SetLayer( ToLAYER_ID( m_brdLayer ) );
+    segm->SetShape( S_ARC );
+    segm->SetCenter( wxPoint( Millimeter2iu( cx ), Millimeter2iu( -cy ) ) );
+
+    if( ang < 0.0 )
+    {
+        segm->SetArcStart( wxPoint( Millimeter2iu( ep.x ), Millimeter2iu( -ep.y ) ) );
+        segm->SetAngle( RAD2DECIDEG( ang ) );
+    }
+    else
+    {
+        segm->SetArcStart( wxPoint( Millimeter2iu( sp.x ), Millimeter2iu( -sp.y ) ) );
+        segm->SetAngle( RAD2DECIDEG( -ang ) );
+    }
+
+    segm->SetWidth( aWidth );
+
+    m_newItemsList.push_back( segm );
+    return;
+}

=== modified file 'pcbnew/import_dxf/dxf2brd_items.h'
--- pcbnew/import_dxf/dxf2brd_items.h	2016-03-20 03:51:02 +0000
+++ pcbnew/import_dxf/dxf2brd_items.h	2016-03-21 00:44:02 +0000
@@ -34,13 +34,6 @@
 class BOARD_ITEM;
 
 /**
- * Conversion factor for DXF units to millimeters
- * It seems DRW_Interface always converts DXF coordinates in mm
- * (to be confirmed)
- */
-static const double DXF_UNITS_PER_MM = 1.0;
-
-/**
  * This format filter class can import and export DXF files.
  * It depends on the dxflib library.
  *
@@ -53,7 +46,8 @@
     double m_xOffset;       // X coord offset for conversion (in mm)
     double m_yOffset;       // Y coord offset for conversion (in mm)
     double m_defaultThickness;  // default line thickness for conversion (in mm)
-    int m_brdLayer;         // The board layer to place imported dfx items
+    double m_DXF2mm;        // The scale factor to convert DXF units to mm
+    int m_brdLayer;         // The board layer to place imported DXF items
     int m_version;          // the dxf version, not used here
     std::string m_codePage; // The code page, not used here
 
@@ -103,6 +97,11 @@
     int mapY( double aDxfCoordY );
     int mapDim( double aDxfValue );
 
+    // Functions to aid in the creation of a LWPolyline
+    void insertLine( const wxRealPoint& aSegStart, const wxRealPoint& aSegEnd, int aWidth );
+    void insertArc( const wxRealPoint& aSegStart, const wxRealPoint& aSegEnd,
+                    double aBulge, int aWidth );
+
     // Methods from DRW_CreationInterface:
     // They are "call back" fonctions, called when the corresponding object
     // is read in dxf file


Follow ups