diff --git a/libs/kimath/include/math/vector3.h b/libs/kimath/include/math/vector3.h new file mode 100644 index 0000000000..965ddf5d9d --- /dev/null +++ b/libs/kimath/include/math/vector3.h @@ -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 . + */ + +#ifndef VECTOR3_H_ +#define VECTOR3_H_ + +/** + * VECTOR2_TRAITS + * traits class for VECTOR2. + */ +template +struct VECTOR3_TRAITS +{ + ///> extended range/precision types used by operations involving multiple + ///> multiplications to prevent overflow. + typedef T extended_type; +}; + +template <> +struct VECTOR3_TRAITS +{ + 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 VECTOR3 +{ +public: + typedef typename VECTOR3_TRAITS::extended_type extended_type; + typedef T coord_type; + + static constexpr extended_type ECOORD_MAX = std::numeric_limits::max(); + static constexpr extended_type ECOORD_MIN = std::numeric_limits::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 + VECTOR3( const VECTOR3& aVec ) + { + x = (T) aVec.x; + y = (T) aVec.y; + z = (T) aVec.z; + } + + /// Copy a vector + VECTOR3( const VECTOR3& aVec ) + { + x = aVec.x; + y = aVec.y; + z = aVec.z; + } + + /** + * Function Cross() + * computes cross product of self with aVector + */ + VECTOR3 Cross( const VECTOR3& aVector ) const; + + /** + * Function Dot() + * computes dot product of self with aVector + */ + VECTOR3::extended_type Dot( const VECTOR3& 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 Normalize(); + + /// Equality operator + bool operator==( const VECTOR3& aVector ) const; + + /// Not equality operator + bool operator!=( const VECTOR3& aVector ) const; +}; + + +template +VECTOR3::VECTOR3() +{ + x = y = z = 0.0; +} + + +template +VECTOR3::VECTOR3( T aX, T aY, T aZ ) +{ + x = aX; + y = aY; + z = aZ; +} + + +template +VECTOR3 VECTOR3::Cross( const VECTOR3& aVector ) const +{ + return VECTOR3( ( y * aVector.z ) - ( z * aVector.y ), + ( z * aVector.x ) - ( x * aVector.z ), + ( x * aVector.y ) - ( y * aVector.x ) + ); +} + + +template +typename VECTOR3::extended_type VECTOR3::Dot( const VECTOR3& 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 +T VECTOR3::EuclideanNorm() const +{ + return sqrt( (extended_type) x * x + (extended_type) y * y + (extended_type) z * z ); +} + + +template +VECTOR3 VECTOR3::Normalize() +{ + T norm = EuclideanNorm(); + x /= norm; + y /= norm; + z /= norm; + + return *this; +} + + +template +bool VECTOR3::operator==( VECTOR3 const& aVector ) const +{ + return ( aVector.x == x ) && ( aVector.y == y ) && ( aVector.z == z ); +} + + +template +bool VECTOR3::operator!=( VECTOR3 const& aVector ) const +{ + return ( aVector.x != x ) || ( aVector.y != y ) || ( aVector.z != z ); +} + + +/* Default specializations */ +typedef VECTOR3 VECTOR3D; +typedef VECTOR3 VECTOR3I; +typedef VECTOR3 VECTOR3U; + +#endif // VECTOR3_H_ \ No newline at end of file diff --git a/pcbnew/import_gfx/dxf_import_plugin.cpp b/pcbnew/import_gfx/dxf_import_plugin.cpp index 30241d1eef..3e50ddeb65 100644 --- a/pcbnew/import_gfx/dxf_import_plugin.cpp +++ b/pcbnew/import_gfx/dxf_import_plugin.cpp @@ -40,8 +40,38 @@ /* - * Important note: all DXF coordinates and sizes are converted to mm. - * they will be converted to internal units later. + * Important notes + * 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) (x) + DXF_IMPORT_PLUGIN::DXF_IMPORT_PLUGIN() : DL_CreationAdapter() { 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; + 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 { - m_curr_entity.m_LastCoordinate.x = m_xOffset + vertex->x * getCurrentUnitScale(); - m_curr_entity.m_LastCoordinate.y = m_yOffset - vertex->y * getCurrentUnitScale(); + m_curr_entity.m_LastCoordinate.x = mapX( vertexCoords.x ); + m_curr_entity.m_LastCoordinate.y = mapY( vertexCoords.y ); m_curr_entity.m_PolylineStart = m_curr_entity.m_LastCoordinate; m_curr_entity.m_BulgeVertex = vertex->bulge; m_curr_entity.m_EntityParseStatus = 2; return; } - VECTOR2D seg_end( m_xOffset + vertex->x * getCurrentUnitScale(), - m_yOffset - vertex->y * getCurrentUnitScale() ); + VECTOR2D seg_end( mapX( vertexCoords.x ), mapY( vertexCoords.y ) ); if( std::abs( m_curr_entity.m_BulgeVertex ) < MIN_BULGE ) 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() ); 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 || m_curr_entity.m_EntityType == DL_ENTITY_LWPOLYLINE ) { @@ -420,8 +461,12 @@ void DXF_IMPORT_PLUGIN::addInsert( const DL_InsertData& aData ) if( block == nullptr ) return; - VECTOR2D translation( mapX( aData.ipx ), mapY( aData.ipy ) ); - VECTOR2D scale( mapX( aData.sx ), mapY( aData.sy ) ); + DXF_ARBITRARY_AXIS arbAxis = getArbitraryAxis( getExtrusion() ); + 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() ) { @@ -433,9 +478,13 @@ void DXF_IMPORT_PLUGIN::addInsert( const DL_InsertData& 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() ); double lineWidth = lineWeightToWidth( attributes.getWidth(), layer ); @@ -455,8 +504,11 @@ void DXF_IMPORT_PLUGIN::addArc( const DL_ArcData& aData ) if( m_currentBlock != nullptr ) return; + DXF_ARBITRARY_AXIS arbAxis = getArbitraryAxis( getExtrusion() ); + VECTOR3D centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.cx, aData.cy, aData.cz ) ); + // Init arc centre: - VECTOR2D center( mapX( aData.cx ), mapY( aData.cy ) ); + VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) ); // aData.anglex is in degrees. double startangle = aData.angle1; @@ -465,7 +517,8 @@ void DXF_IMPORT_PLUGIN::addArc( const DL_ArcData& aData ) // Init arc start point VECTOR2D startPoint( aData.radius, 0.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) 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 ) { - VECTOR2D refPoint( mapX( aData.ipx ), mapY( aData.ipy ) ); - VECTOR2D secPoint( mapX( aData.apx ), mapY( aData.apy ) ); + DXF_ARBITRARY_AXIS arbAxis = getArbitraryAxis( getExtrusion() ); + 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 ) { @@ -670,7 +727,10 @@ void DXF_IMPORT_PLUGIN::addMText( const DL_MTextData& aData ) 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: 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_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 ); +} \ No newline at end of file diff --git a/pcbnew/import_gfx/dxf_import_plugin.h b/pcbnew/import_gfx/dxf_import_plugin.h index 106e6d0d77..e3da7d6147 100644 --- a/pcbnew/import_gfx/dxf_import_plugin.h +++ b/pcbnew/import_gfx/dxf_import_plugin.h @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -176,6 +177,16 @@ enum class DXF_IMPORT_UNITS 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. * It depends on the dxflib library. @@ -317,6 +328,18 @@ private: double lineWeightToWidth( int lw, DXF_IMPORT_LAYER* aLayer ); 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 * diff --git a/qa/libs/kimath/CMakeLists.txt b/qa/libs/kimath/CMakeLists.txt index 4223f48ece..49c4dac928 100644 --- a/qa/libs/kimath/CMakeLists.txt +++ b/qa/libs/kimath/CMakeLists.txt @@ -36,6 +36,9 @@ set( KIMATH_SRCS geometry/test_shape_poly_set_iterator.cpp geometry/test_poly_grid_partition.cpp geometry/test_shape_line_chain.cpp + + math/test_vector2.cpp + math/test_vector3.cpp ) add_executable( qa_kimath ${KIMATH_SRCS} ) diff --git a/qa/libs/kimath/math/test_vector2.cpp b/qa/libs/kimath/math/test_vector2.cpp new file mode 100644 index 0000000000..cfb627c1dd --- /dev/null +++ b/qa/libs/kimath/math/test_vector2.cpp @@ -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 + +// Code under test +#include + +/** + * 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() diff --git a/qa/libs/kimath/math/test_vector3.cpp b/qa/libs/kimath/math/test_vector3.cpp new file mode 100644 index 0000000000..4005277f8c --- /dev/null +++ b/qa/libs/kimath/math/test_vector3.cpp @@ -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 + +// Code under test +#include + +/** + * 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()