Add handling of DXF arbitrary axis/extrusions

This fixes some DXFs imports where unforunately CAD tools like SolidWorks
randomly decide to mirror circle definitions across the "z" axis (resulting in x or y axis flips in 2d)
Most likely live projection from 3D to 2D drawings introduces this.
However this is DXF specification to describe it so obtusely with vectors for a 2d drawing.
This commit is contained in:
Marek Roszko 2020-11-23 17:26:52 -05:00
parent 44f4d41bba
commit 45598f2933
6 changed files with 455 additions and 14 deletions

View File

@ -0,0 +1,193 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef VECTOR3_H_
#define VECTOR3_H_
/**
* VECTOR2_TRAITS
* traits class for VECTOR2.
*/
template <class T>
struct VECTOR3_TRAITS
{
///> extended range/precision types used by operations involving multiple
///> multiplications to prevent overflow.
typedef T extended_type;
};
template <>
struct VECTOR3_TRAITS<int>
{
typedef int64_t extended_type;
};
/**
* VECTOR3
* defines a general 3D-vector.
*
* This class uses templates to be universal. Several operators are provided to help
* easy implementing of linear algebra equations.
*
*/
template <class T = int>
class VECTOR3
{
public:
typedef typename VECTOR3_TRAITS<T>::extended_type extended_type;
typedef T coord_type;
static constexpr extended_type ECOORD_MAX = std::numeric_limits<extended_type>::max();
static constexpr extended_type ECOORD_MIN = std::numeric_limits<extended_type>::min();
T x, y, z;
/// Construct a 3D-vector with x, y = 0
VECTOR3();
/// Construct a vector with given components x, y
VECTOR3( T x, T y, T z );
/// Initializes a vector from another specialization. Beware of rouding
/// issues.
template <typename CastingType>
VECTOR3( const VECTOR3<CastingType>& aVec )
{
x = (T) aVec.x;
y = (T) aVec.y;
z = (T) aVec.z;
}
/// Copy a vector
VECTOR3( const VECTOR3<T>& aVec )
{
x = aVec.x;
y = aVec.y;
z = aVec.z;
}
/**
* Function Cross()
* computes cross product of self with aVector
*/
VECTOR3<T> Cross( const VECTOR3<T>& aVector ) const;
/**
* Function Dot()
* computes dot product of self with aVector
*/
VECTOR3<T>::extended_type Dot( const VECTOR3<T>& aVector ) const;
/**
* Function Euclidean Norm
* computes the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
* It is used to calculate the length of the vector.
* @return Scalar, the euclidean norm
*/
T EuclideanNorm() const;
/**
* Function Normalize()
* computes the normalized vector
*/
VECTOR3<T> Normalize();
/// Equality operator
bool operator==( const VECTOR3<T>& aVector ) const;
/// Not equality operator
bool operator!=( const VECTOR3<T>& aVector ) const;
};
template <class T>
VECTOR3<T>::VECTOR3()
{
x = y = z = 0.0;
}
template <class T>
VECTOR3<T>::VECTOR3( T aX, T aY, T aZ )
{
x = aX;
y = aY;
z = aZ;
}
template <class T>
VECTOR3<T> VECTOR3<T>::Cross( const VECTOR3<T>& aVector ) const
{
return VECTOR3<T>( ( y * aVector.z ) - ( z * aVector.y ),
( z * aVector.x ) - ( x * aVector.z ),
( x * aVector.y ) - ( y * aVector.x )
);
}
template <class T>
typename VECTOR3<T>::extended_type VECTOR3<T>::Dot( const VECTOR3<T>& aVector ) const
{
return extended_type{x} * extended_type{aVector.x}
+ extended_type{y} * extended_type{aVector.y}
+ extended_type{z} * extended_type{aVector.z};
}
template <class T>
T VECTOR3<T>::EuclideanNorm() const
{
return sqrt( (extended_type) x * x + (extended_type) y * y + (extended_type) z * z );
}
template <class T>
VECTOR3<T> VECTOR3<T>::Normalize()
{
T norm = EuclideanNorm();
x /= norm;
y /= norm;
z /= norm;
return *this;
}
template <class T>
bool VECTOR3<T>::operator==( VECTOR3<T> const& aVector ) const
{
return ( aVector.x == x ) && ( aVector.y == y ) && ( aVector.z == z );
}
template <class T>
bool VECTOR3<T>::operator!=( VECTOR3<T> const& aVector ) const
{
return ( aVector.x != x ) || ( aVector.y != y ) || ( aVector.z != z );
}
/* Default specializations */
typedef VECTOR3<double> VECTOR3D;
typedef VECTOR3<int> VECTOR3I;
typedef VECTOR3<unsigned int> VECTOR3U;
#endif // VECTOR3_H_

View File

@ -40,8 +40,38 @@
/* /*
* Important note: all DXF coordinates and sizes are converted to mm. * Important notes
* they will be converted to internal units later. * 1. All output coordinates of this importer are in mm
* 2. DXFs have a concept of world (WCS) and object coordinates (OCS)
3. The following objects are world coordinates:
- Line
- Point
- Polyline (3D)
- Vertex (3D)
- Polymesh
- Polyface
- Viewport
4. The following entities are object coordinates
- Circle
- Arc
- Solid
- Trace
- Attrib
- Shape
- Insert
- Polyline (2D)
- Vertex (2D)
- LWPolyline
- Hatch
- Image
- Text
* 5. Object coordinates must be run through the arbtirary axis
* translation even though they are 2D drawings and most of the time
* the import is fine. Sometimes, agaisnt all logic, CAD tools like
* SolidWorks may randomly insert circles "mirror" that must be unflipped
* by following the object to world conversion
* 6. Blocks are virtual groups, blocks must be placed by a INSERT entity
* 7. Blocks may be repeated multiple times
*/ */
@ -52,6 +82,7 @@
//#define SCALE_FACTOR(x) millimeter2iu(x) /* no longer used */ //#define SCALE_FACTOR(x) millimeter2iu(x) /* no longer used */
#define SCALE_FACTOR(x) (x) #define SCALE_FACTOR(x) (x)
DXF_IMPORT_PLUGIN::DXF_IMPORT_PLUGIN() : DL_CreationAdapter() DXF_IMPORT_PLUGIN::DXF_IMPORT_PLUGIN() : DL_CreationAdapter()
{ {
m_xOffset = 0.0; // X coord offset for conversion (in mm) m_xOffset = 0.0; // X coord offset for conversion (in mm)
@ -342,18 +373,20 @@ void DXF_IMPORT_PLUGIN::addVertex( const DL_VertexData& aData )
const DL_VertexData* vertex = &aData; const DL_VertexData* vertex = &aData;
DXF_ARBITRARY_AXIS arbAxis = getArbitraryAxis( getExtrusion() );
VECTOR3D vertexCoords = ocsToWcs( arbAxis, VECTOR3D( vertex->x, vertex->y, vertex->z ) );
if( m_curr_entity.m_EntityParseStatus == 1 ) // This is the first vertex of an entity if( m_curr_entity.m_EntityParseStatus == 1 ) // This is the first vertex of an entity
{ {
m_curr_entity.m_LastCoordinate.x = m_xOffset + vertex->x * getCurrentUnitScale(); m_curr_entity.m_LastCoordinate.x = mapX( vertexCoords.x );
m_curr_entity.m_LastCoordinate.y = m_yOffset - vertex->y * getCurrentUnitScale(); m_curr_entity.m_LastCoordinate.y = mapY( vertexCoords.y );
m_curr_entity.m_PolylineStart = m_curr_entity.m_LastCoordinate; m_curr_entity.m_PolylineStart = m_curr_entity.m_LastCoordinate;
m_curr_entity.m_BulgeVertex = vertex->bulge; m_curr_entity.m_BulgeVertex = vertex->bulge;
m_curr_entity.m_EntityParseStatus = 2; m_curr_entity.m_EntityParseStatus = 2;
return; return;
} }
VECTOR2D seg_end( m_xOffset + vertex->x * getCurrentUnitScale(), VECTOR2D seg_end( mapX( vertexCoords.x ), mapY( vertexCoords.y ) );
m_yOffset - vertex->y * getCurrentUnitScale() );
if( std::abs( m_curr_entity.m_BulgeVertex ) < MIN_BULGE ) if( std::abs( m_curr_entity.m_BulgeVertex ) < MIN_BULGE )
insertLine( m_curr_entity.m_LastCoordinate, seg_end, lineWidth ); insertLine( m_curr_entity.m_LastCoordinate, seg_end, lineWidth );
@ -371,6 +404,14 @@ void DXF_IMPORT_PLUGIN::endEntity()
DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() ); DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
double lineWidth = lineWeightToWidth( attributes.getWidth(), layer ); double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
// skip 3d polylines we obviously can't support
// TODO: maybe inform the user somehow this was encountered?
if( m_curr_entity.m_EntityFlag & DL_POLYLINE3D )
{
m_curr_entity.Clear();
return;
}
if( m_curr_entity.m_EntityType == DL_ENTITY_POLYLINE || if( m_curr_entity.m_EntityType == DL_ENTITY_POLYLINE ||
m_curr_entity.m_EntityType == DL_ENTITY_LWPOLYLINE ) m_curr_entity.m_EntityType == DL_ENTITY_LWPOLYLINE )
{ {
@ -420,8 +461,12 @@ void DXF_IMPORT_PLUGIN::addInsert( const DL_InsertData& aData )
if( block == nullptr ) if( block == nullptr )
return; return;
VECTOR2D translation( mapX( aData.ipx ), mapY( aData.ipy ) ); DXF_ARBITRARY_AXIS arbAxis = getArbitraryAxis( getExtrusion() );
VECTOR2D scale( mapX( aData.sx ), mapY( aData.sy ) ); VECTOR3D insertCoords = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) );
VECTOR2D translation( mapX( insertCoords.x ), mapY( insertCoords.y ) );
// TODO: implement handling of scale
for( auto& shape : block->m_buffer.GetShapes() ) for( auto& shape : block->m_buffer.GetShapes() )
{ {
@ -433,9 +478,13 @@ void DXF_IMPORT_PLUGIN::addInsert( const DL_InsertData& aData )
} }
} }
void DXF_IMPORT_PLUGIN::addCircle( const DL_CircleData& aData ) void DXF_IMPORT_PLUGIN::addCircle( const DL_CircleData& aData )
{ {
VECTOR2D center( mapX( aData.cx ), mapY( aData.cy ) ); DXF_ARBITRARY_AXIS arbAxis = getArbitraryAxis( getExtrusion() );
VECTOR3D centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.cx, aData.cy, aData.cz ) );
VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) );
DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() ); DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
double lineWidth = lineWeightToWidth( attributes.getWidth(), layer ); double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
@ -455,8 +504,11 @@ void DXF_IMPORT_PLUGIN::addArc( const DL_ArcData& aData )
if( m_currentBlock != nullptr ) if( m_currentBlock != nullptr )
return; return;
DXF_ARBITRARY_AXIS arbAxis = getArbitraryAxis( getExtrusion() );
VECTOR3D centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.cx, aData.cy, aData.cz ) );
// Init arc centre: // Init arc centre:
VECTOR2D center( mapX( aData.cx ), mapY( aData.cy ) ); VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) );
// aData.anglex is in degrees. // aData.anglex is in degrees.
double startangle = aData.angle1; double startangle = aData.angle1;
@ -465,7 +517,8 @@ void DXF_IMPORT_PLUGIN::addArc( const DL_ArcData& aData )
// Init arc start point // Init arc start point
VECTOR2D startPoint( aData.radius, 0.0 ); VECTOR2D startPoint( aData.radius, 0.0 );
startPoint = startPoint.Rotate( startangle * M_PI / 180.0 ); startPoint = startPoint.Rotate( startangle * M_PI / 180.0 );
VECTOR2D arcStart( mapX( startPoint.x + aData.cx ), mapY( startPoint.y + aData.cy ) ); VECTOR2D arcStart(
mapX( startPoint.x + centerCoords.x ), mapY( startPoint.y + centerCoords.y ) );
// calculate arc angle (arcs are CCW, and should be < 0 in Pcbnew) // calculate arc angle (arcs are CCW, and should be < 0 in Pcbnew)
double angle = -( endangle - startangle ); double angle = -( endangle - startangle );
@ -489,8 +542,12 @@ void DXF_IMPORT_PLUGIN::addArc( const DL_ArcData& aData )
void DXF_IMPORT_PLUGIN::addText( const DL_TextData& aData ) void DXF_IMPORT_PLUGIN::addText( const DL_TextData& aData )
{ {
VECTOR2D refPoint( mapX( aData.ipx ), mapY( aData.ipy ) ); DXF_ARBITRARY_AXIS arbAxis = getArbitraryAxis( getExtrusion() );
VECTOR2D secPoint( mapX( aData.apx ), mapY( aData.apy ) ); VECTOR3D refPointCoords = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) );
VECTOR3D secPointCoords = ocsToWcs( arbAxis, VECTOR3D( aData.apx, aData.apy, aData.apz ) );
VECTOR2D refPoint( mapX( refPointCoords.x ), mapY( refPointCoords.y ) );
VECTOR2D secPoint( mapX( secPointCoords.x ), mapY( secPointCoords.y ) );
if( aData.vJustification != 0 || aData.hJustification != 0 || aData.hJustification == 4 ) if( aData.vJustification != 0 || aData.hJustification != 0 || aData.hJustification == 4 )
{ {
@ -670,7 +727,10 @@ void DXF_IMPORT_PLUGIN::addMText( const DL_MTextData& aData )
text = tmp; text = tmp;
} }
VECTOR2D textpos( mapX( aData.ipx ), mapY( aData.ipy ) ); DXF_ARBITRARY_AXIS arbAxis = getArbitraryAxis( getExtrusion() );
VECTOR3D textposCoords = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) );
VECTOR2D textpos( mapX( textposCoords.x ), mapY( textposCoords.y ) );
// Initialize text justifications: // Initialize text justifications:
EDA_TEXT_HJUSTIFY_T hJustify = GR_TEXT_HJUSTIFY_LEFT; EDA_TEXT_HJUSTIFY_T hJustify = GR_TEXT_HJUSTIFY_LEFT;
@ -1267,3 +1327,56 @@ void DXF_IMPORT_PLUGIN::updateImageLimits( const VECTOR2D& aPoint )
m_minY = std::min( aPoint.y, m_minY ); m_minY = std::min( aPoint.y, m_minY );
m_maxY = std::max( aPoint.y, m_maxY ); m_maxY = std::max( aPoint.y, m_maxY );
} }
DXF_ARBITRARY_AXIS DXF_IMPORT_PLUGIN::getArbitraryAxis( DL_Extrusion* aData )
{
VECTOR3D arbZ, arbX, arbY;
double direction[3];
aData->getDirection( direction );
arbZ = VECTOR3D( direction[0], direction[1], direction[2] ).Normalize();
if( ( abs( arbZ.x ) < ( 1.0 / 64.0 ) ) && ( abs( arbZ.y ) < ( 1.0 / 64.0 ) ) )
{
arbX = VECTOR3D( 0, 1, 0 ).Cross( arbZ ).Normalize();
}
else
{
arbX = VECTOR3D( 0, 0, 1 ).Cross( arbZ ).Normalize();
}
arbY = arbZ.Cross( arbX ).Normalize();
return DXF_ARBITRARY_AXIS{ arbX, arbY, arbZ };
}
VECTOR3D DXF_IMPORT_PLUGIN::wcsToOcs( const DXF_ARBITRARY_AXIS& arbitraryAxis, VECTOR3D point )
{
double x, y, z;
x = point.x * arbitraryAxis.vecX.x + point.y * arbitraryAxis.vecX.y
+ point.z * arbitraryAxis.vecX.z;
y = point.x * arbitraryAxis.vecY.x + point.y * arbitraryAxis.vecY.y
+ point.z * arbitraryAxis.vecY.z;
z = point.x * arbitraryAxis.vecZ.x + point.y * arbitraryAxis.vecZ.y
+ point.z * arbitraryAxis.vecZ.z;
return VECTOR3D( x, y, z );
}
VECTOR3D DXF_IMPORT_PLUGIN::ocsToWcs( const DXF_ARBITRARY_AXIS& arbitraryAxis, VECTOR3D point )
{
VECTOR3D worldX = wcsToOcs( arbitraryAxis, VECTOR3D( 1, 0, 0 ) );
VECTOR3D worldY = wcsToOcs( arbitraryAxis, VECTOR3D( 0, 1, 0 ) );
VECTOR3D worldZ = wcsToOcs( arbitraryAxis, VECTOR3D( 0, 0, 1 ) );
double x, y, z;
x = point.x * worldX.x + point.y * worldX.y + point.z * worldX.z;
y = point.x * worldY.x + point.y * worldY.y + point.z * worldY.z;
z = point.x * worldZ.x + point.y * worldZ.y + point.z * worldZ.z;
return VECTOR3D( x, y, z );
}

View File

@ -30,6 +30,7 @@
#include <dl_creationadapter.h> #include <dl_creationadapter.h>
#include <dl_dxf.h> #include <dl_dxf.h>
#include <math/vector3.h>
#include <wildcards_and_files_ext.h> #include <wildcards_and_files_ext.h>
#include <wx/wx.h> #include <wx/wx.h>
@ -176,6 +177,16 @@ enum class DXF_IMPORT_UNITS
PARSECS = 20 PARSECS = 20
}; };
/**
* Helper class representing the DXF specification's "arbitrary axis"
*/
struct DXF_ARBITRARY_AXIS
{
VECTOR3D vecX;
VECTOR3D vecY;
VECTOR3D vecZ;
};
/** /**
* This class import DXF ASCII files and convert basic entities to board entities. * This class import DXF ASCII files and convert basic entities to board entities.
* It depends on the dxflib library. * It depends on the dxflib library.
@ -317,6 +328,18 @@ private:
double lineWeightToWidth( int lw, DXF_IMPORT_LAYER* aLayer ); double lineWeightToWidth( int lw, DXF_IMPORT_LAYER* aLayer );
double getCurrentUnitScale(); double getCurrentUnitScale();
DXF_ARBITRARY_AXIS getArbitraryAxis( DL_Extrusion* aData );
/***
* Converts a given world coordinate point to object coordinate using the given arbitrary axis vectors
*/
VECTOR3D wcsToOcs( const DXF_ARBITRARY_AXIS& arbitraryAxis, VECTOR3D point );
/***
* Converts a given object coordinate point to world coordinate using the given arbitrary axis vectors
*/
VECTOR3D ocsToWcs( const DXF_ARBITRARY_AXIS& arbitraryAxis, VECTOR3D point );
/** /**
* Returns the import layer data * Returns the import layer data
* *

View File

@ -36,6 +36,9 @@ set( KIMATH_SRCS
geometry/test_shape_poly_set_iterator.cpp geometry/test_shape_poly_set_iterator.cpp
geometry/test_poly_grid_partition.cpp geometry/test_poly_grid_partition.cpp
geometry/test_shape_line_chain.cpp geometry/test_shape_line_chain.cpp
math/test_vector2.cpp
math/test_vector3.cpp
) )
add_executable( qa_kimath ${KIMATH_SRCS} ) add_executable( qa_kimath ${KIMATH_SRCS} )

View File

@ -0,0 +1,54 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 KiCad Developers, see CHANGELOG.TXT for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/**
* Test suite for KiCad math code.
*/
#include <unit_test_utils/unit_test_utils.h>
// Code under test
#include <math/vector2d.h>
/**
* Declare the test suite
*/
BOOST_AUTO_TEST_SUITE( VECTOR2TESTS )
BOOST_AUTO_TEST_CASE( test_cross_product, *boost::unit_test::tolerance( 0.000001 ) )
{
VECTOR2I v1(0, 1);
VECTOR2I v2(1, 0);
BOOST_CHECK( v2.Cross( v1 ) == 1 );
}
BOOST_AUTO_TEST_CASE( test_dot_product, *boost::unit_test::tolerance( 0.000001 ) )
{
VECTOR2I v1( 0, 1 );
VECTOR2I v2( 1, 0 );
BOOST_CHECK( v2.Dot( v1 ) == 0 );
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -0,0 +1,55 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 KiCad Developers, see CHANGELOG.TXT for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/**
* Test suite for KiCad math code.
*/
#include <unit_test_utils/unit_test_utils.h>
// Code under test
#include <math/vector3.h>
/**
* Declare the test suite
*/
BOOST_AUTO_TEST_SUITE( VECTOR3TESTS )
BOOST_AUTO_TEST_CASE( test_cross_product, *boost::unit_test::tolerance( 0.000001 ) )
{
VECTOR3I v1( 0, 1, 2 );
VECTOR3I v2( 2, 1, 0 );
BOOST_CHECK( v1.Cross( v2 ) == VECTOR3I(-2, 4 ,-2) );
}
BOOST_AUTO_TEST_CASE( test_dot_product, *boost::unit_test::tolerance( 0.000001 ) )
{
VECTOR3I v1( 0, 1, 2 );
VECTOR3I v2( 2, 1, 0 );
BOOST_CHECK( v1.Dot( v2 ) == 1 );
}
BOOST_AUTO_TEST_SUITE_END()