You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1631 lines
50 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
10 months ago
2 years ago
2 years ago
2 years ago
10 months ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. // The DXF reader lib (libdxfrw) comes from dxflib project used in QCAD
  25. // See http://www.ribbonsoft.com
  26. // Each time a dxf entity is read, a "call back" function is called
  27. // like void DXF_IMPORT_PLUGIN::addLine( const DL_LineData& data ) when a line is read.
  28. // this function just add the BOARD entity from dxf parameters (start and end point ...)
  29. #include "dxf_import_plugin.h"
  30. #include <wx/arrstr.h>
  31. #include <wx/regex.h>
  32. #include <geometry/ellipse.h>
  33. #include <bezier_curves.h>
  34. #include <trigo.h>
  35. #include <macros.h>
  36. #include <cmath> // isnan
  37. #include <board.h>
  38. #include "common.h"
  39. /*
  40. * Important notes
  41. * 1. All output coordinates of this importer are in mm
  42. * 2. DXFs have a concept of world (WCS) and object coordinates (OCS)
  43. 3. The following objects are world coordinates:
  44. - Line
  45. - Point
  46. - Polyline (3D)
  47. - Vertex (3D)
  48. - Polymesh
  49. - Polyface
  50. - Viewport
  51. 4. The following entities are object coordinates
  52. - Circle
  53. - Arc
  54. - Solid
  55. - Trace
  56. - Attrib
  57. - Shape
  58. - Insert
  59. - Polyline (2D)
  60. - Vertex (2D)
  61. - LWPolyline
  62. - Hatch
  63. - Image
  64. - Text
  65. * 5. Object coordinates must be run through the arbitrary axis
  66. * translation even though they are 2D drawings and most of the time
  67. * the import is fine. Sometimes, against all logic, CAD tools like
  68. * SolidWorks may randomly insert circles "mirror" that must be unflipped
  69. * by following the object to world conversion
  70. * 6. Blocks are virtual groups, blocks must be placed by a INSERT entity
  71. * 7. Blocks may be repeated multiple times
  72. * 8. There is no sane way to make text look perfect like the original CAD.
  73. * DXF simply does mpt specifying text/font enough to make it portable.
  74. * We however make do try to get it somewhat close/visually appealing.
  75. * 9. We silently drop the z coordinate on 3d polylines
  76. */
  77. // minimum bulge value before resorting to a line segment;
  78. // the value 0.0218 is equivalent to about 5 degrees arc,
  79. #define MIN_BULGE 0.0218
  80. #define SCALE_FACTOR(x) (x)
  81. DXF_IMPORT_PLUGIN::DXF_IMPORT_PLUGIN() : DL_CreationAdapter()
  82. {
  83. m_xOffset = 0.0; // X coord offset for conversion (in mm)
  84. m_yOffset = 0.0; // Y coord offset for conversion (in mm)
  85. m_version = 0; // the dxf version, not yet used
  86. m_defaultThickness = 0.2; // default thickness (in mm)
  87. m_brdLayer = Dwgs_User; // The default import layer
  88. m_importAsFPShapes = true;
  89. m_minX = m_minY = std::numeric_limits<double>::max();
  90. m_maxX = m_maxY = std::numeric_limits<double>::lowest();
  91. m_currentUnit = DXF_IMPORT_UNITS::DEFAULT;
  92. m_importCoordinatePrecision = 4; // initial value per dxf spec
  93. m_importAnglePrecision = 0; // initial value per dxf spec
  94. // placeholder layer so we can fallback to something later
  95. auto layer0 = std::make_unique<DXF_IMPORT_LAYER>( "", DXF_IMPORT_LINEWEIGHT_BY_LW_DEFAULT );
  96. m_layers.push_back( std::move( layer0 ) );
  97. m_currentBlock = nullptr;
  98. }
  99. DXF_IMPORT_PLUGIN::~DXF_IMPORT_PLUGIN()
  100. {
  101. }
  102. bool DXF_IMPORT_PLUGIN::Load( const wxString& aFileName )
  103. {
  104. try
  105. {
  106. return ImportDxfFile( aFileName );
  107. }
  108. catch( const std::bad_alloc& )
  109. {
  110. m_layers.clear();
  111. m_blocks.clear();
  112. m_styles.clear();
  113. m_internalImporter.ClearShapes();
  114. ReportMsg( _( "Memory was exhausted trying to load the DXF, it may be too large." ) );
  115. return false;
  116. }
  117. }
  118. bool DXF_IMPORT_PLUGIN::LoadFromMemory( const wxMemoryBuffer& aMemBuffer )
  119. {
  120. try
  121. {
  122. return ImportDxfFile( aMemBuffer );
  123. }
  124. catch( const std::bad_alloc& )
  125. {
  126. m_layers.clear();
  127. m_blocks.clear();
  128. m_styles.clear();
  129. m_internalImporter.ClearShapes();
  130. ReportMsg( _( "Memory was exhausted trying to load the DXF, it may be too large." ) );
  131. return false;
  132. }
  133. }
  134. bool DXF_IMPORT_PLUGIN::Import()
  135. {
  136. wxCHECK( m_importer, false );
  137. m_internalImporter.ImportTo( *m_importer );
  138. return true;
  139. }
  140. double DXF_IMPORT_PLUGIN::GetImageWidth() const
  141. {
  142. return m_maxX - m_minX;
  143. }
  144. double DXF_IMPORT_PLUGIN::GetImageHeight() const
  145. {
  146. return m_maxY - m_minY;
  147. }
  148. BOX2D DXF_IMPORT_PLUGIN::GetImageBBox() const
  149. {
  150. BOX2D bbox;
  151. bbox.SetOrigin( m_minX, m_minY );
  152. bbox.SetEnd( m_maxX, m_maxY );
  153. return bbox;
  154. }
  155. void DXF_IMPORT_PLUGIN::SetImporter( GRAPHICS_IMPORTER* aImporter )
  156. {
  157. GRAPHICS_IMPORT_PLUGIN::SetImporter( aImporter );
  158. if( m_importer )
  159. SetDefaultLineWidthMM( m_importer->GetLineWidthMM() );
  160. }
  161. double DXF_IMPORT_PLUGIN::mapX( double aDxfCoordX )
  162. {
  163. return SCALE_FACTOR( m_xOffset + ( aDxfCoordX * getCurrentUnitScale() ) );
  164. }
  165. double DXF_IMPORT_PLUGIN::mapY( double aDxfCoordY )
  166. {
  167. return SCALE_FACTOR( m_yOffset - ( aDxfCoordY * getCurrentUnitScale() ) );
  168. }
  169. double DXF_IMPORT_PLUGIN::mapDim( double aDxfValue )
  170. {
  171. return SCALE_FACTOR( aDxfValue * getCurrentUnitScale() );
  172. }
  173. bool DXF_IMPORT_PLUGIN::ImportDxfFile( const wxString& aFile )
  174. {
  175. DL_Dxf dxf_reader;
  176. // wxFopen takes care of unicode filenames across platforms
  177. FILE* fp = wxFopen( aFile, wxT( "rt" ) );
  178. if( fp == nullptr )
  179. return false;
  180. // Note the dxf reader takes care of switching to "C" locale before reading the file
  181. // and will close the file after reading
  182. bool success = dxf_reader.in( fp, this );
  183. return success;
  184. }
  185. bool DXF_IMPORT_PLUGIN::ImportDxfFile( const wxMemoryBuffer& aMemBuffer )
  186. {
  187. DL_Dxf dxf_reader;
  188. std::string str( reinterpret_cast<char*>( aMemBuffer.GetData() ), aMemBuffer.GetDataLen() );
  189. // Note the dxf reader takes care of switching to "C" locale before reading the file
  190. // and will close the file after reading
  191. bool success = dxf_reader.in( str, this );
  192. return success;
  193. }
  194. void DXF_IMPORT_PLUGIN::ReportMsg( const wxString& aMessage )
  195. {
  196. // Add message to keep trace of not handled dxf entities
  197. m_messages += aMessage;
  198. m_messages += '\n';
  199. }
  200. void DXF_IMPORT_PLUGIN::addSpline( const DL_SplineData& aData )
  201. {
  202. // Called when starting reading a spline
  203. m_curr_entity.Clear();
  204. m_curr_entity.m_EntityParseStatus = 1;
  205. m_curr_entity.m_EntityFlag = aData.flags;
  206. m_curr_entity.m_EntityType = DL_ENTITY_SPLINE;
  207. m_curr_entity.m_SplineDegree = aData.degree;
  208. m_curr_entity.m_SplineTangentStartX = aData.tangentStartX;
  209. m_curr_entity.m_SplineTangentStartY = aData.tangentStartY;
  210. m_curr_entity.m_SplineTangentEndX = aData.tangentEndX;
  211. m_curr_entity.m_SplineTangentEndY = aData.tangentEndY;
  212. m_curr_entity.m_SplineKnotsCount = aData.nKnots;
  213. m_curr_entity.m_SplineControlCount = aData.nControl;
  214. m_curr_entity.m_SplineFitCount = aData.nFit;
  215. }
  216. void DXF_IMPORT_PLUGIN::addControlPoint( const DL_ControlPointData& aData )
  217. {
  218. // Called for every spline control point, when reading a spline entity
  219. m_curr_entity.m_SplineControlPointList.emplace_back( aData.x , aData.y, aData.w );
  220. }
  221. void DXF_IMPORT_PLUGIN::addFitPoint( const DL_FitPointData& aData )
  222. {
  223. // Called for every spline fit point, when reading a spline entity
  224. // we store only the X,Y coord values in a VECTOR2D
  225. m_curr_entity.m_SplineFitPointList.emplace_back( aData.x, aData.y );
  226. }
  227. void DXF_IMPORT_PLUGIN::addKnot( const DL_KnotData& aData)
  228. {
  229. // Called for every spline knot value, when reading a spline entity
  230. m_curr_entity.m_SplineKnotsList.push_back( aData.k );
  231. }
  232. void DXF_IMPORT_PLUGIN::addLayer( const DL_LayerData& aData )
  233. {
  234. wxString name = wxString::FromUTF8( aData.name.c_str() );
  235. int lw = attributes.getWidth();
  236. if( lw == DXF_IMPORT_LINEWEIGHT_BY_LAYER )
  237. lw = DXF_IMPORT_LINEWEIGHT_BY_LW_DEFAULT;
  238. std::unique_ptr<DXF_IMPORT_LAYER> layer = std::make_unique<DXF_IMPORT_LAYER>( name, lw );
  239. m_layers.push_back( std::move( layer ) );
  240. }
  241. void DXF_IMPORT_PLUGIN::addLinetype( const DL_LinetypeData& data )
  242. {
  243. #if 0
  244. wxString name = From_UTF8( data.name.c_str() );
  245. wxString description = From_UTF8( data.description.c_str() );
  246. #endif
  247. }
  248. double DXF_IMPORT_PLUGIN::lineWeightToWidth( int lw, DXF_IMPORT_LAYER* aLayer )
  249. {
  250. if( lw == DXF_IMPORT_LINEWEIGHT_BY_LAYER && aLayer != nullptr )
  251. lw = aLayer->m_lineWeight;
  252. // All lineweights >= 0 are always in 100ths of mm
  253. double mm = m_defaultThickness;
  254. if( lw >= 0 )
  255. mm = lw / 100.0;
  256. return SCALE_FACTOR( mm );
  257. }
  258. DXF_IMPORT_LAYER* DXF_IMPORT_PLUGIN::getImportLayer( const std::string& aLayerName )
  259. {
  260. DXF_IMPORT_LAYER* layer = m_layers.front().get();
  261. wxString layerName = wxString::FromUTF8( aLayerName.c_str() );
  262. if( !layerName.IsEmpty() )
  263. {
  264. auto resultIt = std::find_if( m_layers.begin(), m_layers.end(),
  265. [layerName]( const auto& it )
  266. {
  267. return it->m_layerName == layerName;
  268. } );
  269. if( resultIt != m_layers.end() )
  270. layer = resultIt->get();
  271. }
  272. return layer;
  273. }
  274. DXF_IMPORT_BLOCK* DXF_IMPORT_PLUGIN::getImportBlock( const std::string& aBlockName )
  275. {
  276. DXF_IMPORT_BLOCK* block = nullptr;
  277. wxString blockName = wxString::FromUTF8( aBlockName.c_str() );
  278. if( !blockName.IsEmpty() )
  279. {
  280. auto resultIt = std::find_if( m_blocks.begin(), m_blocks.end(),
  281. [blockName]( const auto& it )
  282. {
  283. return it->m_name == blockName;
  284. } );
  285. if( resultIt != m_blocks.end() )
  286. block = resultIt->get();
  287. }
  288. return block;
  289. }
  290. DXF_IMPORT_STYLE* DXF_IMPORT_PLUGIN::getImportStyle( const std::string& aStyleName )
  291. {
  292. DXF_IMPORT_STYLE* style = nullptr;
  293. wxString styleName = wxString::FromUTF8( aStyleName.c_str() );
  294. if( !styleName.IsEmpty() )
  295. {
  296. auto resultIt = std::find_if( m_styles.begin(), m_styles.end(),
  297. [styleName]( const auto& it )
  298. {
  299. return it->m_name == styleName;
  300. } );
  301. if( resultIt != m_styles.end() )
  302. style = resultIt->get();
  303. }
  304. return style;
  305. }
  306. void DXF_IMPORT_PLUGIN::addLine( const DL_LineData& aData )
  307. {
  308. DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
  309. double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
  310. VECTOR2D start( mapX( aData.x1 ), mapY( aData.y1 ) );
  311. VECTOR2D end( mapX( aData.x2 ), mapY( aData.y2 ) );
  312. GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
  313. : &m_internalImporter;
  314. bufferToUse->AddLine( start, end, lineWidth );
  315. updateImageLimits( start );
  316. updateImageLimits( end );
  317. }
  318. void DXF_IMPORT_PLUGIN::addPolyline(const DL_PolylineData& aData )
  319. {
  320. // Convert DXF Polylines into a series of KiCad Lines and Arcs.
  321. // A Polyline (as opposed to a LWPolyline) may be a 3D line or
  322. // even a 3D Mesh. The only type of Polyline which is guaranteed
  323. // to import correctly is a 2D Polyline in X and Y, which is what
  324. // we assume of all Polylines. The width used is the width of the Polyline.
  325. // per-vertex line widths, if present, are ignored.
  326. m_curr_entity.Clear();
  327. m_curr_entity.m_EntityParseStatus = 1;
  328. m_curr_entity.m_EntityFlag = aData.flags;
  329. m_curr_entity.m_EntityType = DL_ENTITY_POLYLINE;
  330. }
  331. void DXF_IMPORT_PLUGIN::addVertex( const DL_VertexData& aData )
  332. {
  333. if( m_curr_entity.m_EntityParseStatus == 0 )
  334. return; // Error
  335. DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
  336. double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
  337. /* support for per-vertex-encoded linewidth (Cadence uses it) */
  338. /* linewidths are scaled by 100 in DXF */
  339. if( aData.startWidth > 0.0 )
  340. lineWidth = aData.startWidth / 100.0;
  341. else if ( aData.endWidth > 0.0 )
  342. lineWidth = aData.endWidth / 100.0;
  343. const DL_VertexData* vertex = &aData;
  344. MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
  345. VECTOR3D vertexCoords = ocsToWcs( arbAxis, VECTOR3D( vertex->x, vertex->y, vertex->z ) );
  346. if( m_curr_entity.m_EntityParseStatus == 1 ) // This is the first vertex of an entity
  347. {
  348. m_curr_entity.m_LastCoordinate.x = mapX( vertexCoords.x );
  349. m_curr_entity.m_LastCoordinate.y = mapY( vertexCoords.y );
  350. m_curr_entity.m_PolylineStart = m_curr_entity.m_LastCoordinate;
  351. m_curr_entity.m_BulgeVertex = vertex->bulge;
  352. m_curr_entity.m_EntityParseStatus = 2;
  353. return;
  354. }
  355. VECTOR2D seg_end( mapX( vertexCoords.x ), mapY( vertexCoords.y ) );
  356. if( std::abs( m_curr_entity.m_BulgeVertex ) < MIN_BULGE )
  357. insertLine( m_curr_entity.m_LastCoordinate, seg_end, lineWidth );
  358. else
  359. insertArc( m_curr_entity.m_LastCoordinate, seg_end, m_curr_entity.m_BulgeVertex,
  360. lineWidth );
  361. m_curr_entity.m_LastCoordinate = seg_end;
  362. m_curr_entity.m_BulgeVertex = vertex->bulge;
  363. }
  364. void DXF_IMPORT_PLUGIN::endEntity()
  365. {
  366. DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
  367. double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
  368. if( m_curr_entity.m_EntityType == DL_ENTITY_POLYLINE ||
  369. m_curr_entity.m_EntityType == DL_ENTITY_LWPOLYLINE )
  370. {
  371. // Polyline flags bit 0 indicates closed (1) or open (0) polyline
  372. if( m_curr_entity.m_EntityFlag & 1 )
  373. {
  374. if( std::abs( m_curr_entity.m_BulgeVertex ) < MIN_BULGE )
  375. {
  376. insertLine( m_curr_entity.m_LastCoordinate, m_curr_entity.m_PolylineStart,
  377. lineWidth );
  378. }
  379. else
  380. {
  381. insertArc( m_curr_entity.m_LastCoordinate, m_curr_entity.m_PolylineStart,
  382. m_curr_entity.m_BulgeVertex, lineWidth );
  383. }
  384. }
  385. }
  386. if( m_curr_entity.m_EntityType == DL_ENTITY_SPLINE )
  387. insertSpline( lineWidth );
  388. m_curr_entity.Clear();
  389. }
  390. void DXF_IMPORT_PLUGIN::addBlock( const DL_BlockData& aData )
  391. {
  392. wxString name = wxString::FromUTF8( aData.name.c_str() );
  393. std::unique_ptr<DXF_IMPORT_BLOCK> block = std::make_unique<DXF_IMPORT_BLOCK>( name, aData.bpx,
  394. aData.bpy );
  395. m_blocks.push_back( std::move( block ) );
  396. m_currentBlock = m_blocks.back().get();
  397. }
  398. void DXF_IMPORT_PLUGIN::endBlock()
  399. {
  400. m_currentBlock = nullptr;
  401. }
  402. void DXF_IMPORT_PLUGIN::addInsert( const DL_InsertData& aData )
  403. {
  404. DXF_IMPORT_BLOCK* block = getImportBlock( aData.name );
  405. if( block == nullptr )
  406. return;
  407. MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
  408. MATRIX3x3D rot;
  409. rot.SetRotation( DEG2RAD( -aData.angle ) ); // DL_InsertData angle is in degrees
  410. MATRIX3x3D scale;
  411. scale.SetScale( VECTOR2D( aData.sx, aData.sy ) );
  412. MATRIX3x3D trans = ( arbAxis * rot ) * scale;
  413. VECTOR3D insertCoords = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) );
  414. VECTOR2D translation( mapX( insertCoords.x ), mapY( insertCoords.y ) );
  415. translation -= VECTOR2D( mapX( block->m_baseX ), mapY( block->m_baseY ) );
  416. for( const std::unique_ptr<IMPORTED_SHAPE>& shape : block->m_buffer.GetShapes() )
  417. {
  418. std::unique_ptr<IMPORTED_SHAPE> newShape = shape->clone();
  419. newShape->Transform( trans, translation );
  420. m_internalImporter.AddShape( newShape );
  421. }
  422. }
  423. void DXF_IMPORT_PLUGIN::addCircle( const DL_CircleData& aData )
  424. {
  425. MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
  426. VECTOR3D centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.cx, aData.cy, aData.cz ) );
  427. VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) );
  428. DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
  429. double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
  430. GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
  431. : &m_internalImporter;
  432. bufferToUse->AddCircle( center, mapDim( aData.radius ), lineWidth, false );
  433. VECTOR2D radiusDelta( mapDim( aData.radius ), mapDim( aData.radius ) );
  434. updateImageLimits( center + radiusDelta );
  435. updateImageLimits( center - radiusDelta );
  436. }
  437. void DXF_IMPORT_PLUGIN::addArc( const DL_ArcData& aData )
  438. {
  439. MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
  440. VECTOR3D centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.cx, aData.cy, aData.cz ) );
  441. // Init arc centre:
  442. VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) );
  443. // aData.anglex is in degrees.
  444. EDA_ANGLE startangle( aData.angle1, DEGREES_T );
  445. EDA_ANGLE endangle( aData.angle2, DEGREES_T );
  446. if( ( arbAxis.GetScale().x < 0 ) != ( arbAxis.GetScale().y < 0 ) )
  447. {
  448. startangle = ANGLE_180 - startangle;
  449. endangle = ANGLE_180 - endangle;
  450. std::swap( startangle, endangle );
  451. }
  452. // Init arc start point
  453. VECTOR2D startPoint( aData.radius, 0.0 );
  454. RotatePoint( startPoint, -startangle );
  455. VECTOR2D arcStart( mapX( startPoint.x + centerCoords.x ),
  456. mapY( startPoint.y + centerCoords.y ) );
  457. // calculate arc angle (arcs are CCW, and should be < 0 in Pcbnew)
  458. EDA_ANGLE angle = -( endangle - startangle );
  459. if( angle > ANGLE_0 )
  460. angle -= ANGLE_360;
  461. DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
  462. double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
  463. GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
  464. : &m_internalImporter;
  465. bufferToUse->AddArc( center, arcStart, angle, lineWidth );
  466. VECTOR2D radiusDelta( mapDim( aData.radius ), mapDim( aData.radius ) );
  467. updateImageLimits( center + radiusDelta );
  468. updateImageLimits( center - radiusDelta );
  469. }
  470. void DXF_IMPORT_PLUGIN::addEllipse( const DL_EllipseData& aData )
  471. {
  472. MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
  473. VECTOR3D centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.cx, aData.cy, aData.cz ) );
  474. VECTOR3D majorCoords = ocsToWcs( arbAxis, VECTOR3D( aData.mx, aData.my, aData.mz ) );
  475. // DXF ellipses store the minor axis length as a ratio to the major axis.
  476. // The major coords are relative to the center point.
  477. // For now, we assume ellipses in the XY plane.
  478. VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) );
  479. VECTOR2D major( mapX( majorCoords.x ), mapY( majorCoords.y ) );
  480. // DXF elliptical arcs store their angles in radians (unlike circular arcs which use degrees)
  481. // The arcs wind CCW as in KiCad. The end angle must be greater than the start angle, and if
  482. // the extrusion direction is negative, the arc winding is CW instead! Note that this is a
  483. // simplification that assumes the DXF is representing a 2D drawing, and would need to be
  484. // revisited if we want to import true 3D drawings and "flatten" them to the 2D KiCad plane
  485. // internally.
  486. EDA_ANGLE startAngle( aData.angle1, RADIANS_T );
  487. EDA_ANGLE endAngle( aData.angle2, RADIANS_T );
  488. if( startAngle > endAngle )
  489. endAngle += ANGLE_360;
  490. if( aData.ratio == 1.0 )
  491. {
  492. double radius = major.EuclideanNorm();
  493. if( startAngle == endAngle )
  494. {
  495. DL_CircleData circle( aData.cx, aData.cy, aData.cz, radius );
  496. addCircle( circle );
  497. return;
  498. }
  499. else
  500. {
  501. // Angles are relative to major axis
  502. startAngle -= EDA_ANGLE( major );
  503. endAngle -= EDA_ANGLE( major );
  504. DL_ArcData arc( aData.cx, aData.cy, aData.cz, radius, startAngle.AsDegrees(),
  505. endAngle.AsDegrees() );
  506. addArc( arc );
  507. return;
  508. }
  509. }
  510. // TODO: testcases for negative extrusion vector; handle it here
  511. std::vector<BEZIER<double>> splines;
  512. ELLIPSE<double> ellipse( center, major, aData.ratio, startAngle, endAngle );
  513. TransformEllipseToBeziers( ellipse, splines );
  514. DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
  515. double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
  516. GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
  517. : &m_internalImporter;
  518. for( const BEZIER<double>& b : splines )
  519. bufferToUse->AddSpline( b.Start, b.C1, b.C2, b.End, lineWidth );
  520. // Naive bounding
  521. updateImageLimits( center + major );
  522. updateImageLimits( center - major );
  523. }
  524. void DXF_IMPORT_PLUGIN::addText( const DL_TextData& aData )
  525. {
  526. MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
  527. VECTOR3D refPointCoords = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) );
  528. VECTOR3D secPointCoords =
  529. ocsToWcs( arbAxis, VECTOR3D( std::isnan( aData.apx ) ? 0 : aData.apx,
  530. std::isnan( aData.apy ) ? 0 : aData.apy,
  531. std::isnan( aData.apz ) ? 0 : aData.apz ) );
  532. VECTOR2D refPoint( mapX( refPointCoords.x ), mapY( refPointCoords.y ) );
  533. VECTOR2D secPoint( mapX( secPointCoords.x ), mapY( secPointCoords.y ) );
  534. if( aData.vJustification != 0 || aData.hJustification != 0 || aData.hJustification == 4 )
  535. {
  536. if( aData.hJustification != 3 && aData.hJustification != 5 )
  537. {
  538. VECTOR2D tmp = secPoint;
  539. secPoint = refPoint;
  540. refPoint = tmp;
  541. }
  542. }
  543. wxString text = toNativeString( wxString::FromUTF8( aData.text.c_str() ) );
  544. DXF_IMPORT_STYLE* style = getImportStyle( aData.style.c_str() );
  545. double textHeight = mapDim( aData.height );
  546. // The 0.9 factor gives a better height/width base ratio with our font
  547. double charWidth = textHeight * 0.9;
  548. if( style != nullptr )
  549. charWidth *= style->m_widthFactor;
  550. double textWidth = charWidth * text.length(); // Rough approximation
  551. double textThickness = textHeight / 8.0; // Use a reasonable line thickness for this text
  552. VECTOR2D bottomLeft( 0.0, 0.0 );
  553. VECTOR2D bottomRight( 0.0, 0.0 );
  554. VECTOR2D topLeft( 0.0, 0.0 );
  555. VECTOR2D topRight( 0.0, 0.0 );
  556. GR_TEXT_H_ALIGN_T hJustify = GR_TEXT_H_ALIGN_LEFT;
  557. GR_TEXT_V_ALIGN_T vJustify = GR_TEXT_V_ALIGN_BOTTOM;
  558. switch( aData.vJustification )
  559. {
  560. case 0: //DRW_Text::VBaseLine:
  561. case 1: //DRW_Text::VBottom:
  562. vJustify = GR_TEXT_V_ALIGN_BOTTOM;
  563. topLeft.y = textHeight;
  564. topRight.y = textHeight;
  565. break;
  566. case 2: //DRW_Text::VMiddle:
  567. vJustify = GR_TEXT_V_ALIGN_CENTER;
  568. bottomRight.y = -textHeight / 2.0;
  569. bottomLeft.y = -textHeight / 2.0;
  570. topLeft.y = textHeight / 2.0;
  571. topRight.y = textHeight / 2.0;
  572. break;
  573. case 3: //DRW_Text::VTop:
  574. vJustify = GR_TEXT_V_ALIGN_TOP;
  575. bottomLeft.y = -textHeight;
  576. bottomRight.y = -textHeight;
  577. break;
  578. }
  579. switch( aData.hJustification )
  580. {
  581. case 0: //DRW_Text::HLeft:
  582. case 3: //DRW_Text::HAligned: // no equivalent options in text pcb.
  583. case 5: //DRW_Text::HFit: // no equivalent options in text pcb.
  584. hJustify = GR_TEXT_H_ALIGN_LEFT;
  585. bottomRight.x = textWidth;
  586. topRight.x = textWidth;
  587. break;
  588. case 1: //DRW_Text::HCenter:
  589. case 4: //DRW_Text::HMiddle: // no equivalent options in text pcb.
  590. hJustify = GR_TEXT_H_ALIGN_CENTER;
  591. bottomLeft.x = -textWidth / 2.0;
  592. topLeft.x = -textWidth / 2.0;
  593. bottomRight.x = textWidth / 2.0;
  594. topRight.x = textWidth / 2.0;
  595. break;
  596. case 2: //DRW_Text::HRight:
  597. hJustify = GR_TEXT_H_ALIGN_RIGHT;
  598. bottomLeft.x = -textWidth;
  599. topLeft.x = -textWidth;
  600. break;
  601. }
  602. #if 0
  603. wxString sty = wxString::FromUTF8( aData.style.c_str() );
  604. sty = sty.ToLower();
  605. if( aData.textgen == 2 )
  606. {
  607. // Text dir = left to right;
  608. } else if( aData.textgen == 4 )
  609. {
  610. // Text dir = top to bottom;
  611. } else
  612. {
  613. }
  614. #endif
  615. // dxf_lib imports text angle in radians (although there are no comment about that.
  616. // So, for the moment, convert this angle to degrees
  617. double angle_degree = aData.angle * 180 / M_PI;
  618. // We also need the angle in radians. so convert angle_degree to radians
  619. // regardless the aData.angle unit
  620. double angleInRads = angle_degree * M_PI / 180.0;
  621. double cosine = cos(angleInRads);
  622. double sine = sin(angleInRads);
  623. GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
  624. : &m_internalImporter;
  625. bufferToUse->AddText( refPoint, text, textHeight, charWidth, textThickness, angle_degree,
  626. hJustify, vJustify );
  627. // Calculate the boundary box and update the image limits:
  628. bottomLeft.x = bottomLeft.x * cosine - bottomLeft.y * sine;
  629. bottomLeft.y = bottomLeft.x * sine + bottomLeft.y * cosine;
  630. bottomRight.x = bottomRight.x * cosine - bottomRight.y * sine;
  631. bottomRight.y = bottomRight.x * sine + bottomRight.y * cosine;
  632. topLeft.x = topLeft.x * cosine - topLeft.y * sine;
  633. topLeft.y = topLeft.x * sine + topLeft.y * cosine;
  634. topRight.x = topRight.x * cosine - topRight.y * sine;
  635. topRight.y = topRight.x * sine + topRight.y * cosine;
  636. bottomLeft += refPoint;
  637. bottomRight += refPoint;
  638. topLeft += refPoint;
  639. topRight += refPoint;
  640. updateImageLimits( bottomLeft );
  641. updateImageLimits( bottomRight );
  642. updateImageLimits( topLeft );
  643. updateImageLimits( topRight );
  644. }
  645. void DXF_IMPORT_PLUGIN::addMTextChunk( const std::string& text )
  646. {
  647. // If the text string is greater than 250 characters, the string is divided into 250-character
  648. // chunks, which appear in one or more group 3 codes. If group 3 codes are used, the last group
  649. // is a group 1 and has fewer than 250 characters
  650. m_mtextContent.append( text );
  651. }
  652. void DXF_IMPORT_PLUGIN::addMText( const DL_MTextData& aData )
  653. {
  654. m_mtextContent.append( aData.text );
  655. // TODO: determine control codes applied to the whole text?
  656. wxString text = toNativeString( wxString::FromUTF8( m_mtextContent.c_str() ) );
  657. DXF_IMPORT_STYLE* style = getImportStyle( aData.style.c_str() );
  658. double textHeight = mapDim( aData.height );
  659. // The 0.9 factor gives a better height/width base ratio with our font
  660. double charWidth = textHeight * 0.9;
  661. if( style != nullptr )
  662. charWidth *= style->m_widthFactor;
  663. double textWidth = charWidth * text.length(); // Rough approximation
  664. double textThickness = textHeight/8.0; // Use a reasonable line thickness for this text
  665. VECTOR2D bottomLeft(0.0, 0.0);
  666. VECTOR2D bottomRight(0.0, 0.0);
  667. VECTOR2D topLeft(0.0, 0.0);
  668. VECTOR2D topRight(0.0, 0.0);
  669. MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
  670. VECTOR3D textposCoords = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) );
  671. VECTOR2D textpos( mapX( textposCoords.x ), mapY( textposCoords.y ) );
  672. // Initialize text justifications:
  673. GR_TEXT_H_ALIGN_T hJustify = GR_TEXT_H_ALIGN_LEFT;
  674. GR_TEXT_V_ALIGN_T vJustify = GR_TEXT_V_ALIGN_BOTTOM;
  675. if( aData.attachmentPoint <= 3 )
  676. {
  677. vJustify = GR_TEXT_V_ALIGN_TOP;
  678. bottomLeft.y = -textHeight;
  679. bottomRight.y = -textHeight;
  680. }
  681. else if( aData.attachmentPoint <= 6 )
  682. {
  683. vJustify = GR_TEXT_V_ALIGN_CENTER;
  684. bottomRight.y = -textHeight / 2.0;
  685. bottomLeft.y = -textHeight / 2.0;
  686. topLeft.y = textHeight / 2.0;
  687. topRight.y = textHeight / 2.0;
  688. }
  689. else
  690. {
  691. vJustify = GR_TEXT_V_ALIGN_BOTTOM;
  692. topLeft.y = textHeight;
  693. topRight.y = textHeight;
  694. }
  695. if( aData.attachmentPoint % 3 == 1 )
  696. {
  697. hJustify = GR_TEXT_H_ALIGN_LEFT;
  698. bottomRight.x = textWidth;
  699. topRight.x = textWidth;
  700. }
  701. else if( aData.attachmentPoint % 3 == 2 )
  702. {
  703. hJustify = GR_TEXT_H_ALIGN_CENTER;
  704. bottomLeft.x = -textWidth / 2.0;
  705. topLeft.x = -textWidth / 2.0;
  706. bottomRight.x = textWidth / 2.0;
  707. topRight.x = textWidth / 2.0;
  708. }
  709. else
  710. {
  711. hJustify = GR_TEXT_H_ALIGN_RIGHT;
  712. bottomLeft.x = -textWidth;
  713. topLeft.x = -textWidth;
  714. }
  715. #if 0 // These setting have no meaning in Pcbnew
  716. if( data.alignH == 1 )
  717. {
  718. // Text is left to right;
  719. }
  720. else if( data.alignH == 3 )
  721. {
  722. // Text is top to bottom;
  723. }
  724. else
  725. {
  726. // use ByStyle;
  727. }
  728. if( aData.alignV == 1 )
  729. {
  730. // use AtLeast;
  731. }
  732. else
  733. {
  734. // useExact;
  735. }
  736. #endif
  737. // dxf_lib imports text angle in radians (although there are no comment about that.
  738. // So, for the moment, convert this angle to degrees
  739. double angle_degree = aData.angle * 180/M_PI;
  740. // We also need the angle in radians. so convert angle_degree to radians
  741. // regardless the aData.angle unit
  742. double angleInRads = angle_degree * M_PI / 180.0;
  743. double cosine = cos(angleInRads);
  744. double sine = sin(angleInRads);
  745. GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
  746. : &m_internalImporter;
  747. bufferToUse->AddText( textpos, text, textHeight, charWidth, textThickness, angle_degree,
  748. hJustify, vJustify );
  749. bottomLeft.x = bottomLeft.x * cosine - bottomLeft.y * sine;
  750. bottomLeft.y = bottomLeft.x * sine + bottomLeft.y * cosine;
  751. bottomRight.x = bottomRight.x * cosine - bottomRight.y * sine;
  752. bottomRight.y = bottomRight.x * sine + bottomRight.y * cosine;
  753. topLeft.x = topLeft.x * cosine - topLeft.y * sine;
  754. topLeft.y = topLeft.x * sine + topLeft.y * cosine;
  755. topRight.x = topRight.x * cosine - topRight.y * sine;
  756. topRight.y = topRight.x * sine + topRight.y * cosine;
  757. bottomLeft += textpos;
  758. bottomRight += textpos;
  759. topLeft += textpos;
  760. topRight += textpos;
  761. updateImageLimits( bottomLeft );
  762. updateImageLimits( bottomRight );
  763. updateImageLimits( topLeft );
  764. updateImageLimits( topRight );
  765. m_mtextContent.clear();
  766. }
  767. double DXF_IMPORT_PLUGIN::getCurrentUnitScale()
  768. {
  769. double scale = 1.0;
  770. switch( m_currentUnit )
  771. {
  772. case DXF_IMPORT_UNITS::INCH: scale = 25.4; break;
  773. case DXF_IMPORT_UNITS::FEET: scale = 304.8; break;
  774. case DXF_IMPORT_UNITS::MM: scale = 1.0; break;
  775. case DXF_IMPORT_UNITS::CM: scale = 10.0; break;
  776. case DXF_IMPORT_UNITS::METERS: scale = 1000.0; break;
  777. case DXF_IMPORT_UNITS::MICROINCHES: scale = 2.54e-5; break;
  778. case DXF_IMPORT_UNITS::MILS: scale = 0.0254; break;
  779. case DXF_IMPORT_UNITS::YARDS: scale = 914.4; break;
  780. case DXF_IMPORT_UNITS::ANGSTROMS: scale = 1.0e-7; break;
  781. case DXF_IMPORT_UNITS::NANOMETERS: scale = 1.0e-6; break;
  782. case DXF_IMPORT_UNITS::MICRONS: scale = 1.0e-3; break;
  783. case DXF_IMPORT_UNITS::DECIMETERS: scale = 100.0; break;
  784. default:
  785. // use the default of 1.0 for:
  786. // 0: Unspecified Units
  787. // 3: miles
  788. // 7: kilometers
  789. // 15: decameters
  790. // 16: hectometers
  791. // 17: gigameters
  792. // 18: AU
  793. // 19: lightyears
  794. // 20: parsecs
  795. break;
  796. }
  797. return scale;
  798. }
  799. void DXF_IMPORT_PLUGIN::setVariableInt( const std::string& key, int value, int code )
  800. {
  801. // Called for every int variable in the DXF file (e.g. "$INSUNITS").
  802. if( key == "$DWGCODEPAGE" )
  803. {
  804. m_codePage = value;
  805. return;
  806. }
  807. if( key == "$AUPREC" )
  808. {
  809. m_importAnglePrecision = value;
  810. return;
  811. }
  812. if( key == "$LUPREC" )
  813. {
  814. m_importCoordinatePrecision = value;
  815. return;
  816. }
  817. if( key == "$INSUNITS" ) // Drawing units
  818. {
  819. m_currentUnit = DXF_IMPORT_UNITS::DEFAULT;
  820. switch( value )
  821. {
  822. case 1: m_currentUnit = DXF_IMPORT_UNITS::INCH; break;
  823. case 2: m_currentUnit = DXF_IMPORT_UNITS::FEET; break;
  824. case 4: m_currentUnit = DXF_IMPORT_UNITS::MM; break;
  825. case 5: m_currentUnit = DXF_IMPORT_UNITS::CM; break;
  826. case 6: m_currentUnit = DXF_IMPORT_UNITS::METERS; break;
  827. case 8: m_currentUnit = DXF_IMPORT_UNITS::MICROINCHES; break;
  828. case 9: m_currentUnit = DXF_IMPORT_UNITS::MILS; break;
  829. case 10: m_currentUnit = DXF_IMPORT_UNITS::YARDS; break;
  830. case 11: m_currentUnit = DXF_IMPORT_UNITS::ANGSTROMS; break;
  831. case 12: m_currentUnit = DXF_IMPORT_UNITS::NANOMETERS; break;
  832. case 13: m_currentUnit = DXF_IMPORT_UNITS::MICRONS; break;
  833. case 14: m_currentUnit = DXF_IMPORT_UNITS::DECIMETERS; break;
  834. default:
  835. // use the default for:
  836. // 0: Unspecified Units
  837. // 3: miles
  838. // 7: kilometers
  839. // 15: decameters
  840. // 16: hectometers
  841. // 17: gigameters
  842. // 18: AU
  843. // 19: lightyears
  844. // 20: parsecs
  845. break;
  846. }
  847. return;
  848. }
  849. }
  850. void DXF_IMPORT_PLUGIN::setVariableString( const std::string& key, const std::string& value,
  851. int code )
  852. {
  853. // Called for every string variable in the DXF file (e.g. "$ACADVER").
  854. }
  855. wxString DXF_IMPORT_PLUGIN::toDxfString( const wxString& aStr )
  856. {
  857. wxString res;
  858. int j = 0;
  859. for( unsigned i = 0; i<aStr.length(); ++i )
  860. {
  861. int c = aStr[i];
  862. if( c > 175 || c < 11 )
  863. {
  864. res.append( aStr.Mid( j, i - j ) );
  865. j = i;
  866. switch( c )
  867. {
  868. case 0x0A:
  869. res += wxT( "\\P" );
  870. break;
  871. // diameter:
  872. #ifdef _WIN32
  873. // windows, as always, is special.
  874. case 0x00D8:
  875. #else
  876. case 0x2205:
  877. #endif
  878. res += wxT( "%%C" );
  879. break;
  880. // degree:
  881. case 0x00B0:
  882. res += wxT( "%%D" );
  883. break;
  884. // plus/minus
  885. case 0x00B1:
  886. res += wxT( "%%P" );
  887. break;
  888. default:
  889. j--;
  890. break;
  891. }
  892. j++;
  893. }
  894. }
  895. res.append( aStr.Mid( j ) );
  896. return res;
  897. }
  898. wxString DXF_IMPORT_PLUGIN::toNativeString( const wxString& aData )
  899. {
  900. wxString res;
  901. size_t i = 0;
  902. int braces = 0;
  903. int overbarLevel = -1;
  904. // For description, see:
  905. // https://ezdxf.readthedocs.io/en/stable/dxfinternals/entities/mtext.html
  906. // https://www.cadforum.cz/en/text-formatting-codes-in-mtext-objects-tip8640
  907. for( i = 0; i < aData.length(); i++ )
  908. {
  909. switch( (wchar_t) aData[i] )
  910. {
  911. case '{': // Text area influenced by the code
  912. braces++;
  913. break;
  914. case '}':
  915. if( overbarLevel == braces )
  916. {
  917. res << '}';
  918. overbarLevel = -1;
  919. }
  920. braces--;
  921. break;
  922. case '^': // C0 control code
  923. if( ++i >= aData.length() )
  924. break;
  925. switch( (wchar_t) aData[i] )
  926. {
  927. case 'I': res << '\t'; break;
  928. case 'J': res << '\b'; break;
  929. case ' ': res << '^'; break;
  930. default: break;
  931. }
  932. break;
  933. case '\\':
  934. {
  935. if( ++i >= aData.length() )
  936. break;
  937. switch( (wchar_t) aData[i] )
  938. {
  939. case 'P': // New paragraph (new line)
  940. case 'X': // Paragraph wrap on the dimension line (only in dimensions)
  941. res << '\n';
  942. break;
  943. case '~': // Non-wrapping space, hard space
  944. res << L'\u00A0';
  945. break;
  946. case 'U': // Unicode character, e.g. \U+ff08
  947. {
  948. i += 2;
  949. wxString codeHex;
  950. for( ; codeHex.length() < 4 && i < aData.length(); i++ )
  951. codeHex << aData[i];
  952. unsigned long codeVal = 0;
  953. if( codeHex.ToCULong( &codeVal, 16 ) && codeVal != 0 )
  954. res << wxUniChar( codeVal );
  955. i--;
  956. }
  957. break;
  958. case 'S': // Stacking
  959. {
  960. i++;
  961. wxString stacked;
  962. for( ; i < aData.length(); i++ )
  963. {
  964. if( aData[i] == ';' )
  965. break;
  966. else
  967. stacked << aData[i];
  968. }
  969. if( stacked.Contains( wxS( "#" ) ) )
  970. {
  971. res << '^' << '{';
  972. res << stacked.BeforeFirst( '#' );
  973. res << '}' << '/' << '_' << '{';
  974. res << stacked.AfterFirst( '#' );
  975. res << '}';
  976. }
  977. else
  978. {
  979. stacked.Replace( wxS( "^ " ), wxS( "/" ) );
  980. res << stacked;
  981. }
  982. }
  983. break;
  984. case 'O': // Start overstrike
  985. if( overbarLevel == -1 )
  986. {
  987. res << '~' << '{';
  988. overbarLevel = braces;
  989. }
  990. break;
  991. case 'o': // Stop overstrike
  992. if( overbarLevel == braces )
  993. {
  994. res << '}';
  995. overbarLevel = -1;
  996. }
  997. break;
  998. case 'L': // Start underline
  999. case 'l': // Stop underline
  1000. case 'K': // Start strike-through
  1001. case 'k': // Stop strike-through
  1002. case 'N': // New column
  1003. // Ignore
  1004. break;
  1005. case 'p': // Control codes for bullets, numbered paragraphs, tab stops and columns
  1006. case 'Q': // Slanting (obliquing) text by angle
  1007. case 'H': // Text height
  1008. case 'W': // Text width
  1009. case 'F': // Font selection
  1010. case 'f': // Font selection (alternative)
  1011. case 'A': // Alignment
  1012. case 'C': // Color change (ACI colors)
  1013. case 'c': // Color change (truecolor)
  1014. case 'T': // Tracking, char.spacing
  1015. // Skip to ;
  1016. for( ; i < aData.length(); i++ )
  1017. {
  1018. if( aData[i] == ';' )
  1019. break;
  1020. }
  1021. break;
  1022. default: // Escaped character
  1023. if( ++i >= aData.length() )
  1024. break;
  1025. res << aData[i];
  1026. break;
  1027. }
  1028. }
  1029. break;
  1030. default: res << aData[i];
  1031. }
  1032. }
  1033. if( overbarLevel != -1 )
  1034. {
  1035. res << '}';
  1036. overbarLevel = -1;
  1037. }
  1038. #if 1
  1039. wxRegEx regexp;
  1040. // diameter:
  1041. regexp.Compile( wxT( "%%[cC]" ) );
  1042. #ifdef __WINDOWS__
  1043. // windows, as always, is special.
  1044. regexp.Replace( &res, wxChar( 0xD8 ) );
  1045. #else
  1046. // Empty_set, diameter is 0x2300
  1047. regexp.Replace( &res, wxChar( 0x2205 ) );
  1048. #endif
  1049. // degree:
  1050. regexp.Compile( wxT( "%%[dD]" ) );
  1051. regexp.Replace( &res, wxChar( 0x00B0 ) );
  1052. // plus/minus
  1053. regexp.Compile( wxT( "%%[pP]" ) );
  1054. regexp.Replace( &res, wxChar( 0x00B1 ) );
  1055. #endif
  1056. return res;
  1057. }
  1058. void DXF_IMPORT_PLUGIN::addTextStyle( const DL_StyleData& aData )
  1059. {
  1060. wxString name = wxString::FromUTF8( aData.name.c_str() );
  1061. auto style = std::make_unique<DXF_IMPORT_STYLE>( name, aData.fixedTextHeight, aData.widthFactor,
  1062. aData.bold, aData.italic );
  1063. m_styles.push_back( std::move( style ) );
  1064. }
  1065. void DXF_IMPORT_PLUGIN::addPoint( const DL_PointData& aData )
  1066. {
  1067. MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
  1068. VECTOR3D centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.x, aData.y, aData.z ) );
  1069. VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) );
  1070. // we emulate points with filled circles
  1071. // set the linewidth to something that even small circles look good with
  1072. // thickness is optional for dxf points
  1073. // note: we had to modify the dxf library to grab the attribute for thickness
  1074. double lineWidth = 0.0001;
  1075. double thickness = mapDim( std::max( aData.thickness, 0.01 ) );
  1076. GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
  1077. : &m_internalImporter;
  1078. bufferToUse->AddCircle( center, thickness, lineWidth, true );
  1079. VECTOR2D radiusDelta( SCALE_FACTOR( thickness ), SCALE_FACTOR( thickness ) );
  1080. updateImageLimits( center + radiusDelta );
  1081. updateImageLimits( center - radiusDelta );
  1082. }
  1083. void DXF_IMPORT_PLUGIN::insertLine( const VECTOR2D& aSegStart,
  1084. const VECTOR2D& aSegEnd, double aWidth )
  1085. {
  1086. VECTOR2D origin( SCALE_FACTOR( aSegStart.x ), SCALE_FACTOR( aSegStart.y ) );
  1087. VECTOR2D end( SCALE_FACTOR( aSegEnd.x ), SCALE_FACTOR( aSegEnd.y ) );
  1088. GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
  1089. : &m_internalImporter;
  1090. bufferToUse->AddLine( origin, end, aWidth );
  1091. updateImageLimits( origin );
  1092. updateImageLimits( end );
  1093. }
  1094. void DXF_IMPORT_PLUGIN::insertArc( const VECTOR2D& aSegStart, const VECTOR2D& aSegEnd,
  1095. double aBulge, double aWidth )
  1096. {
  1097. VECTOR2D segment_startpoint( SCALE_FACTOR( aSegStart.x ), SCALE_FACTOR( aSegStart.y ) );
  1098. VECTOR2D segment_endpoint( SCALE_FACTOR( aSegEnd.x ), SCALE_FACTOR( aSegEnd.y ) );
  1099. // ensure aBulge represents an angle from +/- ( 0 .. approx 359.8 deg )
  1100. if( aBulge < -2000.0 )
  1101. aBulge = -2000.0;
  1102. else if( aBulge > 2000.0 )
  1103. aBulge = 2000.0;
  1104. double ang = 4.0 * atan( aBulge );
  1105. // reflect the Y values to put everything in a RHCS
  1106. VECTOR2D sp( aSegStart.x, -aSegStart.y );
  1107. VECTOR2D ep( aSegEnd.x, -aSegEnd.y );
  1108. // angle from end->start
  1109. double offAng = atan2( ep.y - sp.y, ep.x - sp.x );
  1110. // length of subtended segment = 1/2 distance between the 2 points
  1111. double d = 0.5 * sqrt( ( sp.x - ep.x ) * ( sp.x - ep.x ) + ( sp.y - ep.y ) * ( sp.y - ep.y ) );
  1112. // midpoint of the subtended segment
  1113. double xm = ( sp.x + ep.x ) * 0.5;
  1114. double ym = ( sp.y + ep.y ) * 0.5;
  1115. double radius = d / sin( ang * 0.5 );
  1116. if( radius < 0.0 )
  1117. radius = -radius;
  1118. // calculate the height of the triangle with base d and hypotenuse r
  1119. double dh2 = radius * radius - d * d;
  1120. // this should only ever happen due to rounding errors when r == d
  1121. if( dh2 < 0.0 )
  1122. dh2 = 0.0;
  1123. double h = sqrt( dh2 );
  1124. if( ang < 0.0 )
  1125. offAng -= M_PI_2;
  1126. else
  1127. offAng += M_PI_2;
  1128. // for angles greater than 180 deg we need to flip the
  1129. // direction in which the arc center is found relative
  1130. // to the midpoint of the subtended segment.
  1131. if( ang < -M_PI )
  1132. offAng += M_PI;
  1133. else if( ang > M_PI )
  1134. offAng -= M_PI;
  1135. // center point
  1136. double cx = h * cos( offAng ) + xm;
  1137. double cy = h * sin( offAng ) + ym;
  1138. VECTOR2D center( SCALE_FACTOR( cx ), SCALE_FACTOR( -cy ) );
  1139. VECTOR2D arc_start;
  1140. EDA_ANGLE angle( ang, RADIANS_T );
  1141. if( ang < 0.0 )
  1142. {
  1143. arc_start = VECTOR2D( SCALE_FACTOR( ep.x ), SCALE_FACTOR( -ep.y ) );
  1144. }
  1145. else
  1146. {
  1147. arc_start = VECTOR2D( SCALE_FACTOR( sp.x ), SCALE_FACTOR( -sp.y ) );
  1148. angle = -angle;
  1149. }
  1150. GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
  1151. : &m_internalImporter;
  1152. bufferToUse->AddArc( center, arc_start, angle, aWidth );
  1153. VECTOR2D radiusDelta( SCALE_FACTOR( radius ), SCALE_FACTOR( radius ) );
  1154. updateImageLimits( center + radiusDelta );
  1155. updateImageLimits( center - radiusDelta );
  1156. }
  1157. #include "tinysplinecxx.h"
  1158. void DXF_IMPORT_PLUGIN::insertSpline( double aWidth )
  1159. {
  1160. #if 0 // Debug only
  1161. wxLogMessage( "spl deg %d kn %d ctr %d fit %d",
  1162. m_curr_entity.m_SplineDegree,
  1163. m_curr_entity.m_SplineKnotsList.size(),
  1164. m_curr_entity.m_SplineControlPointList.size(),
  1165. m_curr_entity.m_SplineFitPointList.size() );
  1166. #endif
  1167. unsigned imax = m_curr_entity.m_SplineControlPointList.size();
  1168. if( imax < 2 ) // malformed spline
  1169. return;
  1170. #if 0 // set to 1 to approximate the spline by segments between 2 control points
  1171. VECTOR2D startpoint( mapX( m_curr_entity.m_SplineControlPointList[0].m_x ),
  1172. mapY( m_curr_entity.m_SplineControlPointList[0].m_y ) );
  1173. for( unsigned int ii = 1; ii < imax; ++ii )
  1174. {
  1175. VECTOR2D endpoint( mapX( m_curr_entity.m_SplineControlPointList[ii].m_x ),
  1176. mapY( m_curr_entity.m_SplineControlPointList[ii].m_y ) );
  1177. if( startpoint != endpoint )
  1178. {
  1179. m_internalImporter.AddLine( startpoint, endpoint, aWidth );
  1180. updateImageLimits( startpoint );
  1181. updateImageLimits( endpoint );
  1182. startpoint = endpoint;
  1183. }
  1184. }
  1185. #else // Use bezier curves, supported by pcbnew, to approximate the spline
  1186. std::vector<double> ctrlp;
  1187. for( unsigned ii = 0; ii < imax; ++ii )
  1188. {
  1189. ctrlp.push_back( m_curr_entity.m_SplineControlPointList[ii].m_x );
  1190. ctrlp.push_back( m_curr_entity.m_SplineControlPointList[ii].m_y );
  1191. }
  1192. tinyspline::BSpline beziers;
  1193. std::vector<double> coords;
  1194. try
  1195. {
  1196. tinyspline::BSpline dxfspline( m_curr_entity.m_SplineControlPointList.size(),
  1197. /* coord dim */ 2, m_curr_entity.m_SplineDegree );
  1198. dxfspline.setControlPoints( ctrlp );
  1199. dxfspline.setKnots( m_curr_entity.m_SplineKnotsList );
  1200. if( dxfspline.degree() < 3 )
  1201. dxfspline = dxfspline.elevateDegree( 3 - dxfspline.degree() );
  1202. beziers = dxfspline.toBeziers();
  1203. coords = beziers.controlPoints();
  1204. }
  1205. catch( const std::runtime_error& ) // tinyspline throws everything including data validation
  1206. // as runtime errors
  1207. {
  1208. // invalid spline definition, drop this block
  1209. ReportMsg( _( "Invalid spline definition encountered" ) );
  1210. return;
  1211. }
  1212. size_t order = beziers.order();
  1213. size_t dim = beziers.dimension();
  1214. size_t numBeziers = ( coords.size() / dim ) / order;
  1215. for( size_t i = 0; i < numBeziers; i++ )
  1216. {
  1217. size_t ii = i * dim * order;
  1218. VECTOR2D start( mapX( coords[ ii ] ), mapY( coords[ ii + 1 ] ) );
  1219. VECTOR2D bezierControl1( mapX( coords[ii + 2] ), mapY( coords[ii + 3] ) );
  1220. // not sure why this happens, but it seems to sometimes slip degree on the final bezier
  1221. VECTOR2D bezierControl2;
  1222. if( ii + 5 >= coords.size() )
  1223. bezierControl2 = bezierControl1;
  1224. else
  1225. bezierControl2 = VECTOR2D( mapX( coords[ii + 4] ), mapY( coords[ii + 5] ) );
  1226. VECTOR2D end;
  1227. if( ii + 7 >= coords.size() )
  1228. end = bezierControl2;
  1229. else
  1230. end = VECTOR2D( mapX( coords[ii + 6] ), mapY( coords[ii + 7] ) );
  1231. GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
  1232. : &m_internalImporter;
  1233. bufferToUse->AddSpline( start, bezierControl1, bezierControl2, end, aWidth );
  1234. }
  1235. #endif
  1236. }
  1237. void DXF_IMPORT_PLUGIN::updateImageLimits( const VECTOR2D& aPoint )
  1238. {
  1239. m_minX = std::min( aPoint.x, m_minX );
  1240. m_maxX = std::max( aPoint.x, m_maxX );
  1241. m_minY = std::min( aPoint.y, m_minY );
  1242. m_maxY = std::max( aPoint.y, m_maxY );
  1243. }
  1244. MATRIX3x3D DXF_IMPORT_PLUGIN::getArbitraryAxis( DL_Extrusion* aData )
  1245. {
  1246. VECTOR3D arbZ, arbX, arbY;
  1247. double direction[3];
  1248. aData->getDirection( direction );
  1249. arbZ = VECTOR3D( direction[0], direction[1], direction[2] ).Normalize();
  1250. if( ( abs( arbZ.x ) < ( 1.0 / 64.0 ) ) && ( abs( arbZ.y ) < ( 1.0 / 64.0 ) ) )
  1251. arbX = VECTOR3D( 0, 1, 0 ).Cross( arbZ ).Normalize();
  1252. else
  1253. arbX = VECTOR3D( 0, 0, 1 ).Cross( arbZ ).Normalize();
  1254. arbY = arbZ.Cross( arbX ).Normalize();
  1255. return MATRIX3x3D{ arbX, arbY, arbZ };
  1256. }
  1257. VECTOR3D DXF_IMPORT_PLUGIN::wcsToOcs( const MATRIX3x3D& arbitraryAxis, VECTOR3D point )
  1258. {
  1259. return arbitraryAxis * point;
  1260. }
  1261. VECTOR3D DXF_IMPORT_PLUGIN::ocsToWcs( const MATRIX3x3D& arbitraryAxis, VECTOR3D point )
  1262. {
  1263. VECTOR3D worldX = wcsToOcs( arbitraryAxis, VECTOR3D( 1, 0, 0 ) );
  1264. VECTOR3D worldY = wcsToOcs( arbitraryAxis, VECTOR3D( 0, 1, 0 ) );
  1265. VECTOR3D worldZ = wcsToOcs( arbitraryAxis, VECTOR3D( 0, 0, 1 ) );
  1266. MATRIX3x3 world( worldX, worldY, worldZ );
  1267. return world * point;
  1268. }