mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2024-11-24 00:34:47 +00:00
e0fc7b2b88
In order to find elements, we need to have bbox correctly set. ::min() for doubles, just gives the smallest positive value. So negative placed graphics don't trigger the right bbox and they get nopped out of existence. Similarly, following the advice from @msteinbeck, we elevate the degree of the spline before converting it to beziers in order to properly represent in KiCad Fixes https://gitlab.com/kicad/code/kicad/-/issues/11153
1624 lines
50 KiB
C++
1624 lines
50 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
|
|
* Copyright (C) 2019-2024 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 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
|
|
*/
|
|
|
|
// The DXF reader lib (libdxfrw) comes from dxflib project used in QCAD
|
|
// See http://www.ribbonsoft.com
|
|
// Each time a dxf entity is read, a "call back" function is called
|
|
// like void DXF_IMPORT_PLUGIN::addLine( const DL_LineData& data ) when a line is read.
|
|
// this function just add the BOARD entity from dxf parameters (start and end point ...)
|
|
|
|
|
|
#include "dxf_import_plugin.h"
|
|
#include <wx/arrstr.h>
|
|
#include <wx/regex.h>
|
|
#include <geometry/ellipse.h>
|
|
#include <bezier_curves.h>
|
|
|
|
#include <trigo.h>
|
|
#include <macros.h>
|
|
#include <cmath> // isnan
|
|
#include <board.h>
|
|
#include "common.h"
|
|
|
|
|
|
/*
|
|
* 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 arbitrary axis
|
|
* translation even though they are 2D drawings and most of the time
|
|
* the import is fine. Sometimes, against 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
|
|
* 8. There is no sane way to make text look perfect like the original CAD.
|
|
* DXF simply does mpt secifying text/font enough to make it portable.
|
|
* We however make do try to get it somewhat close/visually appealing.
|
|
* 9. We silently drop the z coordinate on 3d polylines
|
|
*/
|
|
|
|
|
|
// 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
|
|
|
|
#define SCALE_FACTOR(x) (x)
|
|
|
|
|
|
DXF_IMPORT_PLUGIN::DXF_IMPORT_PLUGIN() : DL_CreationAdapter()
|
|
{
|
|
m_xOffset = 0.0; // X coord offset for conversion (in mm)
|
|
m_yOffset = 0.0; // Y coord offset for conversion (in mm)
|
|
m_version = 0; // the dxf version, not yet used
|
|
m_defaultThickness = 0.2; // default thickness (in mm)
|
|
m_brdLayer = Dwgs_User; // The default import layer
|
|
m_importAsFPShapes = true;
|
|
m_minX = m_minY = std::numeric_limits<double>::max();
|
|
m_maxX = m_maxY = std::numeric_limits<double>::lowest();
|
|
m_currentUnit = DXF_IMPORT_UNITS::DEFAULT;
|
|
|
|
m_importCoordinatePrecision = 4; // initial value per dxf spec
|
|
m_importAnglePrecision = 0; // initial value per dxf spec
|
|
|
|
// placeholder layer so we can fallback to something later
|
|
auto layer0 = std::make_unique<DXF_IMPORT_LAYER>( "", DXF_IMPORT_LINEWEIGHT_BY_LW_DEFAULT );
|
|
m_layers.push_back( std::move( layer0 ) );
|
|
|
|
m_currentBlock = nullptr;
|
|
}
|
|
|
|
|
|
DXF_IMPORT_PLUGIN::~DXF_IMPORT_PLUGIN()
|
|
{
|
|
}
|
|
|
|
|
|
bool DXF_IMPORT_PLUGIN::Load( const wxString& aFileName )
|
|
{
|
|
try
|
|
{
|
|
return ImportDxfFile( aFileName );
|
|
}
|
|
catch( const std::bad_alloc& )
|
|
{
|
|
m_layers.clear();
|
|
m_blocks.clear();
|
|
m_styles.clear();
|
|
|
|
m_internalImporter.ClearShapes();
|
|
|
|
ReportMsg( _( "Memory was exhausted trying to load the DXF, it may be too large." ) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool DXF_IMPORT_PLUGIN::LoadFromMemory( const wxMemoryBuffer& aMemBuffer )
|
|
{
|
|
try
|
|
{
|
|
return ImportDxfFile( aMemBuffer );
|
|
}
|
|
catch( const std::bad_alloc& )
|
|
{
|
|
m_layers.clear();
|
|
m_blocks.clear();
|
|
m_styles.clear();
|
|
|
|
m_internalImporter.ClearShapes();
|
|
|
|
ReportMsg( _( "Memory was exhausted trying to load the DXF, it may be too large." ) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool DXF_IMPORT_PLUGIN::Import()
|
|
{
|
|
wxCHECK( m_importer, false );
|
|
m_internalImporter.ImportTo( *m_importer );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
double DXF_IMPORT_PLUGIN::GetImageWidth() const
|
|
{
|
|
return m_maxX - m_minX;
|
|
}
|
|
|
|
|
|
double DXF_IMPORT_PLUGIN::GetImageHeight() const
|
|
{
|
|
return m_maxY - m_minY;
|
|
}
|
|
|
|
|
|
BOX2D DXF_IMPORT_PLUGIN::GetImageBBox() const
|
|
{
|
|
BOX2D bbox;
|
|
bbox.SetOrigin( m_minX, m_minY );
|
|
bbox.SetEnd( m_maxX, m_maxY );
|
|
|
|
return bbox;
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::SetImporter( GRAPHICS_IMPORTER* aImporter )
|
|
{
|
|
GRAPHICS_IMPORT_PLUGIN::SetImporter( aImporter );
|
|
|
|
if( m_importer )
|
|
SetDefaultLineWidthMM( m_importer->GetLineWidthMM() );
|
|
}
|
|
|
|
|
|
double DXF_IMPORT_PLUGIN::mapX( double aDxfCoordX )
|
|
{
|
|
return SCALE_FACTOR( m_xOffset + ( aDxfCoordX * getCurrentUnitScale() ) );
|
|
}
|
|
|
|
|
|
double DXF_IMPORT_PLUGIN::mapY( double aDxfCoordY )
|
|
{
|
|
return SCALE_FACTOR( m_yOffset - ( aDxfCoordY * getCurrentUnitScale() ) );
|
|
}
|
|
|
|
|
|
double DXF_IMPORT_PLUGIN::mapDim( double aDxfValue )
|
|
{
|
|
return SCALE_FACTOR( aDxfValue * getCurrentUnitScale() );
|
|
}
|
|
|
|
|
|
bool DXF_IMPORT_PLUGIN::ImportDxfFile( const wxString& aFile )
|
|
{
|
|
DL_Dxf dxf_reader;
|
|
|
|
// wxFopen takes care of unicode filenames across platforms
|
|
FILE* fp = wxFopen( aFile, wxT( "rt" ) );
|
|
|
|
if( fp == nullptr )
|
|
return false;
|
|
|
|
// Note the dxf reader takes care of switching to "C" locale before reading the file
|
|
// and will close the file after reading
|
|
bool success = dxf_reader.in( fp, this );
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
bool DXF_IMPORT_PLUGIN::ImportDxfFile( const wxMemoryBuffer& aMemBuffer )
|
|
{
|
|
DL_Dxf dxf_reader;
|
|
|
|
std::string str( reinterpret_cast<char*>( aMemBuffer.GetData() ), aMemBuffer.GetDataLen() );
|
|
|
|
// Note the dxf reader takes care of switching to "C" locale before reading the file
|
|
// and will close the file after reading
|
|
bool success = dxf_reader.in( str, this );
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::ReportMsg( const wxString& aMessage )
|
|
{
|
|
// Add message to keep trace of not handled dxf entities
|
|
m_messages += aMessage;
|
|
m_messages += '\n';
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addSpline( const DL_SplineData& aData )
|
|
{
|
|
// Called when starting reading a spline
|
|
m_curr_entity.Clear();
|
|
m_curr_entity.m_EntityParseStatus = 1;
|
|
m_curr_entity.m_EntityFlag = aData.flags;
|
|
m_curr_entity.m_EntityType = DL_ENTITY_SPLINE;
|
|
m_curr_entity.m_SplineDegree = aData.degree;
|
|
m_curr_entity.m_SplineTangentStartX = aData.tangentStartX;
|
|
m_curr_entity.m_SplineTangentStartY = aData.tangentStartY;
|
|
m_curr_entity.m_SplineTangentEndX = aData.tangentEndX;
|
|
m_curr_entity.m_SplineTangentEndY = aData.tangentEndY;
|
|
m_curr_entity.m_SplineKnotsCount = aData.nKnots;
|
|
m_curr_entity.m_SplineControlCount = aData.nControl;
|
|
m_curr_entity.m_SplineFitCount = aData.nFit;
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addControlPoint( const DL_ControlPointData& aData )
|
|
{
|
|
// Called for every spline control point, when reading a spline entity
|
|
m_curr_entity.m_SplineControlPointList.emplace_back( aData.x , aData.y, aData.w );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addFitPoint( const DL_FitPointData& aData )
|
|
{
|
|
// Called for every spline fit point, when reading a spline entity
|
|
// we store only the X,Y coord values in a VECTOR2D
|
|
m_curr_entity.m_SplineFitPointList.emplace_back( aData.x, aData.y );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addKnot( const DL_KnotData& aData)
|
|
{
|
|
// Called for every spline knot value, when reading a spline entity
|
|
m_curr_entity.m_SplineKnotsList.push_back( aData.k );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addLayer( const DL_LayerData& aData )
|
|
{
|
|
wxString name = wxString::FromUTF8( aData.name.c_str() );
|
|
|
|
int lw = attributes.getWidth();
|
|
|
|
if( lw == DXF_IMPORT_LINEWEIGHT_BY_LAYER )
|
|
lw = DXF_IMPORT_LINEWEIGHT_BY_LW_DEFAULT;
|
|
|
|
std::unique_ptr<DXF_IMPORT_LAYER> layer = std::make_unique<DXF_IMPORT_LAYER>( name, lw );
|
|
|
|
m_layers.push_back( std::move( layer ) );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addLinetype( const DL_LinetypeData& data )
|
|
{
|
|
#if 0
|
|
wxString name = From_UTF8( data.name.c_str() );
|
|
wxString description = From_UTF8( data.description.c_str() );
|
|
#endif
|
|
}
|
|
|
|
|
|
double DXF_IMPORT_PLUGIN::lineWeightToWidth( int lw, DXF_IMPORT_LAYER* aLayer )
|
|
{
|
|
if( lw == DXF_IMPORT_LINEWEIGHT_BY_LAYER && aLayer != nullptr )
|
|
lw = aLayer->m_lineWeight;
|
|
|
|
// All lineweights >= 0 are always in 100ths of mm
|
|
double mm = m_defaultThickness;
|
|
|
|
if( lw >= 0 )
|
|
mm = lw / 100.0;
|
|
|
|
return SCALE_FACTOR( mm );
|
|
}
|
|
|
|
|
|
DXF_IMPORT_LAYER* DXF_IMPORT_PLUGIN::getImportLayer( const std::string& aLayerName )
|
|
{
|
|
DXF_IMPORT_LAYER* layer = m_layers.front().get();
|
|
wxString layerName = wxString::FromUTF8( aLayerName.c_str() );
|
|
|
|
if( !layerName.IsEmpty() )
|
|
{
|
|
auto resultIt = std::find_if( m_layers.begin(), m_layers.end(),
|
|
[layerName]( const auto& it )
|
|
{
|
|
return it->m_layerName == layerName;
|
|
} );
|
|
|
|
if( resultIt != m_layers.end() )
|
|
layer = resultIt->get();
|
|
}
|
|
|
|
return layer;
|
|
}
|
|
|
|
|
|
DXF_IMPORT_BLOCK* DXF_IMPORT_PLUGIN::getImportBlock( const std::string& aBlockName )
|
|
{
|
|
DXF_IMPORT_BLOCK* block = nullptr;
|
|
wxString blockName = wxString::FromUTF8( aBlockName.c_str() );
|
|
|
|
if( !blockName.IsEmpty() )
|
|
{
|
|
auto resultIt = std::find_if( m_blocks.begin(), m_blocks.end(),
|
|
[blockName]( const auto& it )
|
|
{
|
|
return it->m_name == blockName;
|
|
} );
|
|
|
|
if( resultIt != m_blocks.end() )
|
|
block = resultIt->get();
|
|
}
|
|
|
|
return block;
|
|
}
|
|
|
|
|
|
DXF_IMPORT_STYLE* DXF_IMPORT_PLUGIN::getImportStyle( const std::string& aStyleName )
|
|
{
|
|
DXF_IMPORT_STYLE* style = nullptr;
|
|
wxString styleName = wxString::FromUTF8( aStyleName.c_str() );
|
|
|
|
if( !styleName.IsEmpty() )
|
|
{
|
|
auto resultIt = std::find_if( m_styles.begin(), m_styles.end(),
|
|
[styleName]( const auto& it )
|
|
{
|
|
return it->m_name == styleName;
|
|
} );
|
|
|
|
if( resultIt != m_styles.end() )
|
|
style = resultIt->get();
|
|
}
|
|
|
|
return style;
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addLine( const DL_LineData& aData )
|
|
{
|
|
DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
|
|
double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
|
|
|
|
VECTOR2D start( mapX( aData.x1 ), mapY( aData.y1 ) );
|
|
VECTOR2D end( mapX( aData.x2 ), mapY( aData.y2 ) );
|
|
|
|
GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
|
|
: &m_internalImporter;
|
|
bufferToUse->AddLine( start, end, lineWidth );
|
|
|
|
updateImageLimits( start );
|
|
updateImageLimits( end );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addPolyline(const DL_PolylineData& aData )
|
|
{
|
|
// Convert DXF Polylines into a series of KiCad Lines and Arcs.
|
|
// A Polyline (as opposed to a LWPolyline) may be a 3D line or
|
|
// even a 3D Mesh. The only type of Polyline which is guaranteed
|
|
// to import correctly is a 2D Polyline in X and Y, which is what
|
|
// we assume of all Polylines. The width used is the width of the Polyline.
|
|
// per-vertex line widths, if present, are ignored.
|
|
m_curr_entity.Clear();
|
|
m_curr_entity.m_EntityParseStatus = 1;
|
|
m_curr_entity.m_EntityFlag = aData.flags;
|
|
m_curr_entity.m_EntityType = DL_ENTITY_POLYLINE;
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addVertex( const DL_VertexData& aData )
|
|
{
|
|
if( m_curr_entity.m_EntityParseStatus == 0 )
|
|
return; // Error
|
|
|
|
DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
|
|
double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
|
|
|
|
/* support for per-vertex-encoded linewidth (Cadence uses it) */
|
|
/* linewidths are scaled by 100 in DXF */
|
|
if( aData.startWidth > 0.0 )
|
|
lineWidth = aData.startWidth / 100.0;
|
|
else if ( aData.endWidth > 0.0 )
|
|
lineWidth = aData.endWidth / 100.0;
|
|
|
|
const DL_VertexData* vertex = &aData;
|
|
|
|
MATRIX3x3D 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 = 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( 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 );
|
|
else
|
|
insertArc( m_curr_entity.m_LastCoordinate, seg_end, m_curr_entity.m_BulgeVertex, lineWidth );
|
|
|
|
m_curr_entity.m_LastCoordinate = seg_end;
|
|
m_curr_entity.m_BulgeVertex = vertex->bulge;
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::endEntity()
|
|
{
|
|
DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
|
|
double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
|
|
|
|
if( m_curr_entity.m_EntityType == DL_ENTITY_POLYLINE ||
|
|
m_curr_entity.m_EntityType == DL_ENTITY_LWPOLYLINE )
|
|
{
|
|
// Polyline flags bit 0 indicates closed (1) or open (0) polyline
|
|
if( m_curr_entity.m_EntityFlag & 1 )
|
|
{
|
|
if( std::abs( m_curr_entity.m_BulgeVertex ) < MIN_BULGE )
|
|
{
|
|
insertLine( m_curr_entity.m_LastCoordinate, m_curr_entity.m_PolylineStart,
|
|
lineWidth );
|
|
}
|
|
else
|
|
{
|
|
insertArc( m_curr_entity.m_LastCoordinate, m_curr_entity.m_PolylineStart,
|
|
m_curr_entity.m_BulgeVertex, lineWidth );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( m_curr_entity.m_EntityType == DL_ENTITY_SPLINE )
|
|
insertSpline( lineWidth );
|
|
|
|
m_curr_entity.Clear();
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addBlock( const DL_BlockData& aData )
|
|
{
|
|
wxString name = wxString::FromUTF8( aData.name.c_str() );
|
|
|
|
std::unique_ptr<DXF_IMPORT_BLOCK> block = std::make_unique<DXF_IMPORT_BLOCK>( name, aData.bpx,
|
|
aData.bpy );
|
|
|
|
m_blocks.push_back( std::move( block ) );
|
|
|
|
m_currentBlock = m_blocks.back().get();
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::endBlock()
|
|
{
|
|
m_currentBlock = nullptr;
|
|
}
|
|
|
|
void DXF_IMPORT_PLUGIN::addInsert( const DL_InsertData& aData )
|
|
{
|
|
DXF_IMPORT_BLOCK* block = getImportBlock( aData.name );
|
|
|
|
if( block == nullptr )
|
|
return;
|
|
|
|
MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
|
|
|
|
MATRIX3x3D rot;
|
|
rot.SetRotation( DEG2RAD( -aData.angle ) ); // DL_InsertData angle is in degrees
|
|
|
|
MATRIX3x3D scale;
|
|
scale.SetScale( VECTOR2D( aData.sx, aData.sy ) );
|
|
|
|
MATRIX3x3D trans = ( arbAxis * rot ) * scale;
|
|
VECTOR3D insertCoords = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) );
|
|
|
|
VECTOR2D translation( mapX( insertCoords.x ), mapY( insertCoords.y ) );
|
|
translation -= VECTOR2D( mapX( block->m_baseX ), mapY( block->m_baseY ) );
|
|
|
|
for( const std::unique_ptr<IMPORTED_SHAPE>& shape : block->m_buffer.GetShapes() )
|
|
{
|
|
std::unique_ptr<IMPORTED_SHAPE> newShape = shape->clone();
|
|
|
|
newShape->Transform( trans, translation );
|
|
|
|
m_internalImporter.AddShape( newShape );
|
|
}
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addCircle( const DL_CircleData& aData )
|
|
{
|
|
MATRIX3x3D 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 );
|
|
|
|
GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
|
|
: &m_internalImporter;
|
|
bufferToUse->AddCircle( center, mapDim( aData.radius ), lineWidth, false );
|
|
|
|
VECTOR2D radiusDelta( mapDim( aData.radius ), mapDim( aData.radius ) );
|
|
|
|
updateImageLimits( center + radiusDelta );
|
|
updateImageLimits( center - radiusDelta );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addArc( const DL_ArcData& aData )
|
|
{
|
|
MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
|
|
VECTOR3D centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.cx, aData.cy, aData.cz ) );
|
|
|
|
// Init arc centre:
|
|
VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) );
|
|
|
|
// aData.anglex is in degrees.
|
|
EDA_ANGLE startangle( aData.angle1, DEGREES_T );
|
|
EDA_ANGLE endangle( aData.angle2, DEGREES_T );
|
|
|
|
if( ( arbAxis.GetScale().x < 0 ) != ( arbAxis.GetScale().y < 0 ) )
|
|
{
|
|
startangle = ANGLE_180 - startangle;
|
|
endangle = ANGLE_180 - endangle;
|
|
std::swap( startangle, endangle );
|
|
}
|
|
|
|
// Init arc start point
|
|
VECTOR2D startPoint( aData.radius, 0.0 );
|
|
RotatePoint( startPoint, -startangle );
|
|
VECTOR2D arcStart( mapX( startPoint.x + centerCoords.x ),
|
|
mapY( startPoint.y + centerCoords.y ) );
|
|
|
|
// calculate arc angle (arcs are CCW, and should be < 0 in Pcbnew)
|
|
EDA_ANGLE angle = -( endangle - startangle );
|
|
|
|
if( angle > ANGLE_0 )
|
|
angle -= ANGLE_360;
|
|
|
|
DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
|
|
double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
|
|
|
|
GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
|
|
: &m_internalImporter;
|
|
bufferToUse->AddArc( center, arcStart, angle, lineWidth );
|
|
|
|
VECTOR2D radiusDelta( mapDim( aData.radius ), mapDim( aData.radius ) );
|
|
|
|
updateImageLimits( center + radiusDelta );
|
|
updateImageLimits( center - radiusDelta );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addEllipse( const DL_EllipseData& aData )
|
|
{
|
|
MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
|
|
VECTOR3D centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.cx, aData.cy, aData.cz ) );
|
|
VECTOR3D majorCoords = ocsToWcs( arbAxis, VECTOR3D( aData.mx, aData.my, aData.mz ) );
|
|
|
|
// DXF ellipses store the minor axis length as a ratio to the major axis.
|
|
// The major coords are relative to the center point.
|
|
// For now, we assume ellipses in the XY plane.
|
|
|
|
VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) );
|
|
VECTOR2D major( mapX( majorCoords.x ), mapY( majorCoords.y ) );
|
|
|
|
// DXF elliptical arcs store their angles in radians (unlike circular arcs which use degrees)
|
|
// The arcs wind CCW as in KiCad. The end angle must be greater than the start angle, and if
|
|
// the extrusion direction is negative, the arc winding is CW instead! Note that this is a
|
|
// simplification that assumes the DXF is representing a 2D drawing, and would need to be
|
|
// revisited if we want to import true 3D drawings and "flatten" them to the 2D KiCad plane
|
|
// internally.
|
|
EDA_ANGLE startAngle( aData.angle1, RADIANS_T );
|
|
EDA_ANGLE endAngle( aData.angle2, RADIANS_T );
|
|
|
|
if( startAngle > endAngle )
|
|
endAngle += ANGLE_360;
|
|
|
|
if( aData.ratio == 1.0 )
|
|
{
|
|
double radius = major.EuclideanNorm();
|
|
|
|
if( startAngle == endAngle )
|
|
{
|
|
DL_CircleData circle( aData.cx, aData.cy, aData.cz, radius );
|
|
addCircle( circle );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Angles are relative to major axis
|
|
startAngle -= EDA_ANGLE( major );
|
|
endAngle -= EDA_ANGLE( major );
|
|
|
|
DL_ArcData arc( aData.cx, aData.cy, aData.cz, radius, startAngle.AsDegrees(),
|
|
endAngle.AsDegrees() );
|
|
addArc( arc );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// TODO: testcases for negative extrusion vector; handle it here
|
|
|
|
std::vector<BEZIER<double>> splines;
|
|
ELLIPSE<double> ellipse( center, major, aData.ratio, startAngle, endAngle );
|
|
|
|
TransformEllipseToBeziers( ellipse, splines );
|
|
|
|
DXF_IMPORT_LAYER* layer = getImportLayer( attributes.getLayer() );
|
|
double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );
|
|
|
|
GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
|
|
: &m_internalImporter;
|
|
|
|
for( const BEZIER<double>& b : splines )
|
|
bufferToUse->AddSpline( b.Start, b.C1, b.C2, b.End, lineWidth );
|
|
|
|
// Naive bounding
|
|
updateImageLimits( center + major );
|
|
updateImageLimits( center - major );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addText( const DL_TextData& aData )
|
|
{
|
|
MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
|
|
VECTOR3D refPointCoords = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) );
|
|
VECTOR3D secPointCoords = ocsToWcs( arbAxis, VECTOR3D( std::isnan( aData.apx ) ? 0 : aData.apx,
|
|
std::isnan( aData.apy ) ? 0 : aData.apy,
|
|
std::isnan( aData.apz ) ? 0 : 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.hJustification != 3 && aData.hJustification != 5 )
|
|
{
|
|
VECTOR2D tmp = secPoint;
|
|
secPoint = refPoint;
|
|
refPoint = tmp;
|
|
}
|
|
}
|
|
|
|
wxString text = toNativeString( wxString::FromUTF8( aData.text.c_str() ) );
|
|
|
|
DXF_IMPORT_STYLE* style = getImportStyle( aData.style.c_str() );
|
|
|
|
double textHeight = mapDim( aData.height );
|
|
// The 0.9 factor gives a better height/width base ratio with our font
|
|
double charWidth = textHeight * 0.9;
|
|
|
|
if( style != nullptr )
|
|
charWidth *= style->m_widthFactor;
|
|
|
|
double textWidth = charWidth * text.length(); // Rough approximation
|
|
double textThickness = textHeight/8.0; // Use a reasonable line thickness for this text
|
|
|
|
VECTOR2D bottomLeft(0.0, 0.0);
|
|
VECTOR2D bottomRight(0.0, 0.0);
|
|
VECTOR2D topLeft(0.0, 0.0);
|
|
VECTOR2D topRight(0.0, 0.0);
|
|
|
|
GR_TEXT_H_ALIGN_T hJustify = GR_TEXT_H_ALIGN_LEFT;
|
|
GR_TEXT_V_ALIGN_T vJustify = GR_TEXT_V_ALIGN_BOTTOM;
|
|
|
|
switch( aData.vJustification )
|
|
{
|
|
case 0: //DRW_Text::VBaseLine:
|
|
case 1: //DRW_Text::VBottom:
|
|
vJustify = GR_TEXT_V_ALIGN_BOTTOM;
|
|
|
|
topLeft.y = textHeight;
|
|
topRight.y = textHeight;
|
|
break;
|
|
|
|
case 2: //DRW_Text::VMiddle:
|
|
vJustify = GR_TEXT_V_ALIGN_CENTER;
|
|
|
|
bottomRight.y = -textHeight / 2.0;
|
|
bottomLeft.y = -textHeight / 2.0;
|
|
topLeft.y = textHeight / 2.0;
|
|
topRight.y = textHeight / 2.0;
|
|
break;
|
|
|
|
case 3: //DRW_Text::VTop:
|
|
vJustify = GR_TEXT_V_ALIGN_TOP;
|
|
|
|
bottomLeft.y = -textHeight;
|
|
bottomRight.y = -textHeight;
|
|
break;
|
|
}
|
|
|
|
switch( aData.hJustification )
|
|
{
|
|
case 0: //DRW_Text::HLeft:
|
|
case 3: //DRW_Text::HAligned: // no equivalent options in text pcb.
|
|
case 5: //DRW_Text::HFit: // no equivalent options in text pcb.
|
|
hJustify = GR_TEXT_H_ALIGN_LEFT;
|
|
|
|
bottomRight.x = textWidth;
|
|
topRight.x = textWidth;
|
|
break;
|
|
|
|
case 1: //DRW_Text::HCenter:
|
|
case 4: //DRW_Text::HMiddle: // no equivalent options in text pcb.
|
|
hJustify = GR_TEXT_H_ALIGN_CENTER;
|
|
|
|
bottomLeft.x = -textWidth / 2.0;
|
|
topLeft.x = -textWidth / 2.0;
|
|
bottomRight.x = textWidth / 2.0;
|
|
topRight.x = textWidth / 2.0;
|
|
break;
|
|
|
|
case 2: //DRW_Text::HRight:
|
|
hJustify = GR_TEXT_H_ALIGN_RIGHT;
|
|
|
|
bottomLeft.x = -textWidth;
|
|
topLeft.x = -textWidth;
|
|
break;
|
|
}
|
|
|
|
#if 0
|
|
wxString sty = wxString::FromUTF8( aData.style.c_str() );
|
|
sty = sty.ToLower();
|
|
|
|
if( aData.textgen == 2 )
|
|
{
|
|
// Text dir = left to right;
|
|
} else if( aData.textgen == 4 )
|
|
{
|
|
// Text dir = top to bottom;
|
|
} else
|
|
{
|
|
}
|
|
#endif
|
|
|
|
// dxf_lib imports text angle in radians (although there are no comment about that.
|
|
// So, for the moment, convert this angle to degrees
|
|
double angle_degree = aData.angle * 180 / M_PI;
|
|
// We also need the angle in radians. so convert angle_degree to radians
|
|
// regardless the aData.angle unit
|
|
double angleInRads = angle_degree * M_PI / 180.0;
|
|
double cosine = cos(angleInRads);
|
|
double sine = sin(angleInRads);
|
|
|
|
GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
|
|
: &m_internalImporter;
|
|
bufferToUse->AddText( refPoint, text, textHeight, charWidth, textThickness, angle_degree,
|
|
hJustify, vJustify );
|
|
|
|
// Calculate the boundary box and update the image limits:
|
|
bottomLeft.x = bottomLeft.x * cosine - bottomLeft.y * sine;
|
|
bottomLeft.y = bottomLeft.x * sine + bottomLeft.y * cosine;
|
|
|
|
bottomRight.x = bottomRight.x * cosine - bottomRight.y * sine;
|
|
bottomRight.y = bottomRight.x * sine + bottomRight.y * cosine;
|
|
|
|
topLeft.x = topLeft.x * cosine - topLeft.y * sine;
|
|
topLeft.y = topLeft.x * sine + topLeft.y * cosine;
|
|
|
|
topRight.x = topRight.x * cosine - topRight.y * sine;
|
|
topRight.y = topRight.x * sine + topRight.y * cosine;
|
|
|
|
bottomLeft += refPoint;
|
|
bottomRight += refPoint;
|
|
topLeft += refPoint;
|
|
topRight += refPoint;
|
|
|
|
updateImageLimits( bottomLeft );
|
|
updateImageLimits( bottomRight );
|
|
updateImageLimits( topLeft );
|
|
updateImageLimits( topRight );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addMTextChunk( const std::string& text )
|
|
{
|
|
// If the text string is greater than 250 characters, the string is divided into 250-character
|
|
// chunks, which appear in one or more group 3 codes. If group 3 codes are used, the last group
|
|
// is a group 1 and has fewer than 250 characters
|
|
|
|
m_mtextContent.append( text );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addMText( const DL_MTextData& aData )
|
|
{
|
|
m_mtextContent.append( aData.text );
|
|
|
|
// TODO: determine control codes applied to the whole text?
|
|
wxString text = toNativeString( wxString::FromUTF8( m_mtextContent.c_str() ) );
|
|
|
|
DXF_IMPORT_STYLE* style = getImportStyle( aData.style.c_str() );
|
|
double textHeight = mapDim( aData.height );
|
|
|
|
// The 0.9 factor gives a better height/width base ratio with our font
|
|
double charWidth = textHeight * 0.9;
|
|
|
|
if( style != nullptr )
|
|
charWidth *= style->m_widthFactor;
|
|
|
|
double textWidth = charWidth * text.length(); // Rough approximation
|
|
double textThickness = textHeight/8.0; // Use a reasonable line thickness for this text
|
|
|
|
VECTOR2D bottomLeft(0.0, 0.0);
|
|
VECTOR2D bottomRight(0.0, 0.0);
|
|
VECTOR2D topLeft(0.0, 0.0);
|
|
VECTOR2D topRight(0.0, 0.0);
|
|
|
|
MATRIX3x3D 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:
|
|
GR_TEXT_H_ALIGN_T hJustify = GR_TEXT_H_ALIGN_LEFT;
|
|
GR_TEXT_V_ALIGN_T vJustify = GR_TEXT_V_ALIGN_BOTTOM;
|
|
|
|
if( aData.attachmentPoint <= 3 )
|
|
{
|
|
vJustify = GR_TEXT_V_ALIGN_TOP;
|
|
|
|
bottomLeft.y = -textHeight;
|
|
bottomRight.y = -textHeight;
|
|
}
|
|
else if( aData.attachmentPoint <= 6 )
|
|
{
|
|
vJustify = GR_TEXT_V_ALIGN_CENTER;
|
|
|
|
bottomRight.y = -textHeight / 2.0;
|
|
bottomLeft.y = -textHeight / 2.0;
|
|
topLeft.y = textHeight / 2.0;
|
|
topRight.y = textHeight / 2.0;
|
|
}
|
|
else
|
|
{
|
|
vJustify = GR_TEXT_V_ALIGN_BOTTOM;
|
|
|
|
topLeft.y = textHeight;
|
|
topRight.y = textHeight;
|
|
}
|
|
|
|
if( aData.attachmentPoint % 3 == 1 )
|
|
{
|
|
hJustify = GR_TEXT_H_ALIGN_LEFT;
|
|
|
|
bottomRight.x = textWidth;
|
|
topRight.x = textWidth;
|
|
}
|
|
else if( aData.attachmentPoint % 3 == 2 )
|
|
{
|
|
hJustify = GR_TEXT_H_ALIGN_CENTER;
|
|
|
|
bottomLeft.x = -textWidth / 2.0;
|
|
topLeft.x = -textWidth / 2.0;
|
|
bottomRight.x = textWidth / 2.0;
|
|
topRight.x = textWidth / 2.0;
|
|
}
|
|
else
|
|
{
|
|
hJustify = GR_TEXT_H_ALIGN_RIGHT;
|
|
|
|
bottomLeft.x = -textWidth;
|
|
topLeft.x = -textWidth;
|
|
}
|
|
|
|
#if 0 // These setting have no meaning in Pcbnew
|
|
if( data.alignH == 1 )
|
|
{
|
|
// Text is left to right;
|
|
}
|
|
else if( data.alignH == 3 )
|
|
{
|
|
// Text is top to bottom;
|
|
}
|
|
else
|
|
{
|
|
// use ByStyle;
|
|
}
|
|
|
|
if( aData.alignV == 1 )
|
|
{
|
|
// use AtLeast;
|
|
}
|
|
else
|
|
{
|
|
// useExact;
|
|
}
|
|
#endif
|
|
|
|
// dxf_lib imports text angle in radians (although there are no comment about that.
|
|
// So, for the moment, convert this angle to degrees
|
|
double angle_degree = aData.angle * 180/M_PI;
|
|
// We also need the angle in radians. so convert angle_degree to radians
|
|
// regardless the aData.angle unit
|
|
double angleInRads = angle_degree * M_PI / 180.0;
|
|
double cosine = cos(angleInRads);
|
|
double sine = sin(angleInRads);
|
|
|
|
|
|
GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
|
|
: &m_internalImporter;
|
|
bufferToUse->AddText( textpos, text, textHeight, charWidth, textThickness, angle_degree,
|
|
hJustify, vJustify );
|
|
|
|
bottomLeft.x = bottomLeft.x * cosine - bottomLeft.y * sine;
|
|
bottomLeft.y = bottomLeft.x * sine + bottomLeft.y * cosine;
|
|
|
|
bottomRight.x = bottomRight.x * cosine - bottomRight.y * sine;
|
|
bottomRight.y = bottomRight.x * sine + bottomRight.y * cosine;
|
|
|
|
topLeft.x = topLeft.x * cosine - topLeft.y * sine;
|
|
topLeft.y = topLeft.x * sine + topLeft.y * cosine;
|
|
|
|
topRight.x = topRight.x * cosine - topRight.y * sine;
|
|
topRight.y = topRight.x * sine + topRight.y * cosine;
|
|
|
|
bottomLeft += textpos;
|
|
bottomRight += textpos;
|
|
topLeft += textpos;
|
|
topRight += textpos;
|
|
|
|
updateImageLimits( bottomLeft );
|
|
updateImageLimits( bottomRight );
|
|
updateImageLimits( topLeft );
|
|
updateImageLimits( topRight );
|
|
|
|
m_mtextContent.clear();
|
|
}
|
|
|
|
|
|
double DXF_IMPORT_PLUGIN::getCurrentUnitScale()
|
|
{
|
|
double scale = 1.0;
|
|
|
|
switch( m_currentUnit )
|
|
{
|
|
case DXF_IMPORT_UNITS::INCHES: scale = 25.4; break;
|
|
case DXF_IMPORT_UNITS::FEET: scale = 304.8; break;
|
|
case DXF_IMPORT_UNITS::MILLIMETERS: scale = 1.0; break;
|
|
case DXF_IMPORT_UNITS::CENTIMETERS: scale = 10.0; break;
|
|
case DXF_IMPORT_UNITS::METERS: scale = 1000.0; break;
|
|
case DXF_IMPORT_UNITS::MICROINCHES: scale = 2.54e-5; break;
|
|
case DXF_IMPORT_UNITS::MILS: scale = 0.0254; break;
|
|
case DXF_IMPORT_UNITS::YARDS: scale = 914.4; break;
|
|
case DXF_IMPORT_UNITS::ANGSTROMS: scale = 1.0e-7; break;
|
|
case DXF_IMPORT_UNITS::NANOMETERS: scale = 1.0e-6; break;
|
|
case DXF_IMPORT_UNITS::MICRONS: scale = 1.0e-3; break;
|
|
case DXF_IMPORT_UNITS::DECIMETERS: scale = 100.0; break;
|
|
|
|
default:
|
|
// use the default of 1.0 for:
|
|
// 0: Unspecified Units
|
|
// 3: miles
|
|
// 7: kilometers
|
|
// 15: decameters
|
|
// 16: hectometers
|
|
// 17: gigameters
|
|
// 18: AU
|
|
// 19: lightyears
|
|
// 20: parsecs
|
|
break;
|
|
}
|
|
|
|
return scale;
|
|
}
|
|
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::setVariableInt( const std::string& key, int value, int code )
|
|
{
|
|
// Called for every int variable in the DXF file (e.g. "$INSUNITS").
|
|
|
|
if( key == "$DWGCODEPAGE" )
|
|
{
|
|
m_codePage = value;
|
|
return;
|
|
}
|
|
|
|
if( key == "$AUPREC" )
|
|
{
|
|
m_importAnglePrecision = value;
|
|
return;
|
|
}
|
|
|
|
if( key == "$LUPREC" )
|
|
{
|
|
m_importCoordinatePrecision = value;
|
|
return;
|
|
}
|
|
|
|
if( key == "$INSUNITS" ) // Drawing units
|
|
{
|
|
m_currentUnit = DXF_IMPORT_UNITS::DEFAULT;
|
|
|
|
switch( value )
|
|
{
|
|
case 1: m_currentUnit = DXF_IMPORT_UNITS::INCHES; break;
|
|
case 2: m_currentUnit = DXF_IMPORT_UNITS::FEET; break;
|
|
case 4: m_currentUnit = DXF_IMPORT_UNITS::MILLIMETERS; break;
|
|
case 5: m_currentUnit = DXF_IMPORT_UNITS::CENTIMETERS; break;
|
|
case 6: m_currentUnit = DXF_IMPORT_UNITS::METERS; break;
|
|
case 8: m_currentUnit = DXF_IMPORT_UNITS::MICROINCHES; break;
|
|
case 9: m_currentUnit = DXF_IMPORT_UNITS::MILS; break;
|
|
case 10: m_currentUnit = DXF_IMPORT_UNITS::YARDS; break;
|
|
case 11: m_currentUnit = DXF_IMPORT_UNITS::ANGSTROMS; break;
|
|
case 12: m_currentUnit = DXF_IMPORT_UNITS::NANOMETERS; break;
|
|
case 13: m_currentUnit = DXF_IMPORT_UNITS::MICRONS; break;
|
|
case 14: m_currentUnit = DXF_IMPORT_UNITS::DECIMETERS; break;
|
|
|
|
default:
|
|
// use the default for:
|
|
// 0: Unspecified Units
|
|
// 3: miles
|
|
// 7: kilometers
|
|
// 15: decameters
|
|
// 16: hectometers
|
|
// 17: gigameters
|
|
// 18: AU
|
|
// 19: lightyears
|
|
// 20: parsecs
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::setVariableString( const std::string& key, const std::string& value,
|
|
int code )
|
|
{
|
|
// Called for every string variable in the DXF file (e.g. "$ACADVER").
|
|
}
|
|
|
|
|
|
wxString DXF_IMPORT_PLUGIN::toDxfString( const wxString& aStr )
|
|
{
|
|
wxString res;
|
|
int j = 0;
|
|
|
|
for( unsigned i = 0; i<aStr.length(); ++i )
|
|
{
|
|
int c = aStr[i];
|
|
|
|
if( c > 175 || c < 11 )
|
|
{
|
|
res.append( aStr.Mid( j, i - j ) );
|
|
j = i;
|
|
|
|
switch( c )
|
|
{
|
|
case 0x0A:
|
|
res += wxT( "\\P" );
|
|
break;
|
|
|
|
// diameter:
|
|
#ifdef _WIN32
|
|
// windows, as always, is special.
|
|
case 0x00D8:
|
|
#else
|
|
case 0x2205:
|
|
#endif
|
|
res += wxT( "%%C" );
|
|
break;
|
|
|
|
// degree:
|
|
case 0x00B0:
|
|
res += wxT( "%%D" );
|
|
break;
|
|
|
|
// plus/minus
|
|
case 0x00B1:
|
|
res += wxT( "%%P" );
|
|
break;
|
|
|
|
default:
|
|
j--;
|
|
break;
|
|
}
|
|
|
|
j++;
|
|
}
|
|
}
|
|
|
|
res.append( aStr.Mid( j ) );
|
|
return res;
|
|
}
|
|
|
|
|
|
wxString DXF_IMPORT_PLUGIN::toNativeString( const wxString& aData )
|
|
{
|
|
wxString res;
|
|
size_t i = 0;
|
|
int braces = 0;
|
|
int overbarLevel = -1;
|
|
|
|
// For description, see:
|
|
// https://ezdxf.readthedocs.io/en/stable/dxfinternals/entities/mtext.html
|
|
// https://www.cadforum.cz/en/text-formatting-codes-in-mtext-objects-tip8640
|
|
|
|
for( i = 0; i < aData.length(); i++ )
|
|
{
|
|
switch( (wchar_t) aData[i] )
|
|
{
|
|
case '{': // Text area influenced by the code
|
|
braces++;
|
|
break;
|
|
|
|
case '}':
|
|
if( overbarLevel == braces )
|
|
{
|
|
res << '}';
|
|
overbarLevel = -1;
|
|
}
|
|
braces--;
|
|
break;
|
|
|
|
case '^': // C0 control code
|
|
if( ++i >= aData.length() )
|
|
break;
|
|
|
|
switch( (wchar_t) aData[i] )
|
|
{
|
|
case 'I': res << '\t'; break;
|
|
case 'J': res << '\b'; break;
|
|
case ' ': res << '^'; break;
|
|
default: break;
|
|
}
|
|
break;
|
|
|
|
case '\\':
|
|
{
|
|
if( ++i >= aData.length() )
|
|
break;
|
|
|
|
switch( (wchar_t) aData[i] )
|
|
{
|
|
case 'P': // New paragraph (new line)
|
|
case 'X': // Paragraph wrap on the dimension line (only in dimensions)
|
|
res << '\n';
|
|
break;
|
|
|
|
case '~': // Non-wrapping space, hard space
|
|
res << L'\u00A0';
|
|
break;
|
|
|
|
case 'U': // Unicode character, e.g. \U+ff08
|
|
{
|
|
i += 2;
|
|
wxString codeHex;
|
|
|
|
for( ; codeHex.length() < 4 && i < aData.length(); i++ )
|
|
codeHex << aData[i];
|
|
|
|
unsigned long codeVal = 0;
|
|
|
|
if( codeHex.ToCULong( &codeVal, 16 ) && codeVal != 0 )
|
|
res << wxUniChar( codeVal );
|
|
|
|
i--;
|
|
}
|
|
break;
|
|
|
|
case 'S': // Stacking
|
|
{
|
|
i++;
|
|
wxString stacked;
|
|
|
|
for( ; i < aData.length(); i++ )
|
|
{
|
|
if( aData[i] == ';' )
|
|
break;
|
|
else
|
|
stacked << aData[i];
|
|
}
|
|
|
|
if( stacked.Contains( wxS( "#" ) ) )
|
|
{
|
|
res << '^' << '{';
|
|
res << stacked.BeforeFirst( '#' );
|
|
res << '}' << '/' << '_' << '{';
|
|
res << stacked.AfterFirst( '#' );
|
|
res << '}';
|
|
}
|
|
else
|
|
{
|
|
stacked.Replace( wxS( "^ " ), wxS( "/" ) );
|
|
res << stacked;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'O': // Start overstrike
|
|
if( overbarLevel == -1 )
|
|
{
|
|
res << '~' << '{';
|
|
overbarLevel = braces;
|
|
}
|
|
break;
|
|
case 'o': // Stop overstrike
|
|
if( overbarLevel == braces )
|
|
{
|
|
res << '}';
|
|
overbarLevel = -1;
|
|
}
|
|
break;
|
|
|
|
case 'L': // Start underline
|
|
case 'l': // Stop underline
|
|
case 'K': // Start strike-through
|
|
case 'k': // Stop strike-through
|
|
case 'N': // New column
|
|
// Ignore
|
|
break;
|
|
|
|
case 'p': // Control codes for bullets, numbered paragraphs, tab stops and columns
|
|
case 'Q': // Slanting (obliquing) text by angle
|
|
case 'H': // Text height
|
|
case 'W': // Text width
|
|
case 'F': // Font selection
|
|
case 'f': // Font selection (alternative)
|
|
case 'A': // Alignment
|
|
case 'C': // Color change (ACI colors)
|
|
case 'c': // Color change (truecolor)
|
|
case 'T': // Tracking, char.spacing
|
|
// Skip to ;
|
|
for( ; i < aData.length(); i++ )
|
|
{
|
|
if( aData[i] == ';' )
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default: // Escaped character
|
|
if( ++i >= aData.length() )
|
|
break;
|
|
|
|
res << aData[i];
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default: res << aData[i];
|
|
}
|
|
}
|
|
|
|
if( overbarLevel != -1 )
|
|
{
|
|
res << '}';
|
|
overbarLevel = -1;
|
|
}
|
|
|
|
#if 1
|
|
wxRegEx regexp;
|
|
|
|
// diameter:
|
|
regexp.Compile( wxT( "%%[cC]" ) );
|
|
#ifdef __WINDOWS__
|
|
// windows, as always, is special.
|
|
regexp.Replace( &res, wxChar( 0xD8 ) );
|
|
#else
|
|
// Empty_set, diameter is 0x2300
|
|
regexp.Replace( &res, wxChar( 0x2205 ) );
|
|
#endif
|
|
|
|
// degree:
|
|
regexp.Compile( wxT( "%%[dD]" ) );
|
|
regexp.Replace( &res, wxChar( 0x00B0 ) );
|
|
// plus/minus
|
|
regexp.Compile( wxT( "%%[pP]" ) );
|
|
regexp.Replace( &res, wxChar( 0x00B1 ) );
|
|
#endif
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addTextStyle( const DL_StyleData& aData )
|
|
{
|
|
wxString name = wxString::FromUTF8( aData.name.c_str() );
|
|
|
|
auto style = std::make_unique<DXF_IMPORT_STYLE>( name, aData.fixedTextHeight, aData.widthFactor,
|
|
aData.bold, aData.italic );
|
|
|
|
m_styles.push_back( std::move( style ) );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::addPoint( const DL_PointData& aData )
|
|
{
|
|
MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
|
|
VECTOR3D centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.x, aData.y, aData.z ) );
|
|
VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) );
|
|
|
|
// we emulate points with filled circles
|
|
// set the linewidth to something that even small circles look good with
|
|
// thickness is optional for dxf points
|
|
// note: we had to modify the dxf library to grab the attribute for thickness
|
|
double lineWidth = 0.0001;
|
|
double thickness = mapDim( std::max( aData.thickness, 0.01 ) );
|
|
|
|
GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
|
|
: &m_internalImporter;
|
|
bufferToUse->AddCircle( center, thickness, lineWidth, true );
|
|
|
|
VECTOR2D radiusDelta( SCALE_FACTOR( thickness ), SCALE_FACTOR( thickness ) );
|
|
|
|
updateImageLimits( center + radiusDelta );
|
|
updateImageLimits( center - radiusDelta );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::insertLine( const VECTOR2D& aSegStart,
|
|
const VECTOR2D& aSegEnd, double aWidth )
|
|
{
|
|
VECTOR2D origin( SCALE_FACTOR( aSegStart.x ), SCALE_FACTOR( aSegStart.y ) );
|
|
VECTOR2D end( SCALE_FACTOR( aSegEnd.x ), SCALE_FACTOR( aSegEnd.y ) );
|
|
|
|
GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
|
|
: &m_internalImporter;
|
|
bufferToUse->AddLine( origin, end, aWidth );
|
|
|
|
updateImageLimits( origin );
|
|
updateImageLimits( end );
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::insertArc( const VECTOR2D& aSegStart, const VECTOR2D& aSegEnd,
|
|
double aBulge, double aWidth )
|
|
{
|
|
VECTOR2D segment_startpoint( SCALE_FACTOR( aSegStart.x ), SCALE_FACTOR( aSegStart.y ) );
|
|
VECTOR2D segment_endpoint( SCALE_FACTOR( aSegEnd.x ), SCALE_FACTOR( 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
|
|
VECTOR2D sp( aSegStart.x, -aSegStart.y );
|
|
VECTOR2D 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;
|
|
VECTOR2D center( SCALE_FACTOR( cx ), SCALE_FACTOR( -cy ) );
|
|
VECTOR2D arc_start;
|
|
EDA_ANGLE angle( ang, RADIANS_T );
|
|
|
|
if( ang < 0.0 )
|
|
{
|
|
arc_start = VECTOR2D( SCALE_FACTOR( ep.x ), SCALE_FACTOR( -ep.y ) );
|
|
}
|
|
else
|
|
{
|
|
arc_start = VECTOR2D( SCALE_FACTOR( sp.x ), SCALE_FACTOR( -sp.y ) );
|
|
angle = -angle;
|
|
}
|
|
|
|
GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
|
|
: &m_internalImporter;
|
|
bufferToUse->AddArc( center, arc_start, angle, aWidth );
|
|
|
|
VECTOR2D radiusDelta( SCALE_FACTOR( radius ), SCALE_FACTOR( radius ) );
|
|
|
|
updateImageLimits( center + radiusDelta );
|
|
updateImageLimits( center - radiusDelta );
|
|
}
|
|
|
|
|
|
#include "tinysplinecxx.h"
|
|
|
|
void DXF_IMPORT_PLUGIN::insertSpline( double aWidth )
|
|
{
|
|
#if 0 // Debug only
|
|
wxLogMessage("spl deg %d kn %d ctr %d fit %d",
|
|
m_curr_entity.m_SplineDegree,
|
|
m_curr_entity.m_SplineKnotsList.size(),
|
|
m_curr_entity.m_SplineControlPointList.size(),
|
|
m_curr_entity.m_SplineFitPointList.size() );
|
|
#endif
|
|
|
|
unsigned imax = m_curr_entity.m_SplineControlPointList.size();
|
|
|
|
if( imax < 2 ) // malformed spline
|
|
return;
|
|
|
|
#if 0 // set to 1 to approximate the spline by segments between 2 control points
|
|
VECTOR2D startpoint( mapX( m_curr_entity.m_SplineControlPointList[0].m_x ),
|
|
mapY( m_curr_entity.m_SplineControlPointList[0].m_y ) );
|
|
|
|
for( unsigned int ii = 1; ii < imax; ++ii )
|
|
{
|
|
VECTOR2D endpoint( mapX( m_curr_entity.m_SplineControlPointList[ii].m_x ),
|
|
mapY( m_curr_entity.m_SplineControlPointList[ii].m_y ) );
|
|
|
|
if( startpoint != endpoint )
|
|
{
|
|
m_internalImporter.AddLine( startpoint, endpoint, aWidth );
|
|
|
|
updateImageLimits( startpoint );
|
|
updateImageLimits( endpoint );
|
|
|
|
startpoint = endpoint;
|
|
}
|
|
}
|
|
#else // Use bezier curves, supported by pcbnew, to approximate the spline
|
|
std::vector<double> ctrlp;
|
|
|
|
for( unsigned ii = 0; ii < imax; ++ii )
|
|
{
|
|
ctrlp.push_back( m_curr_entity.m_SplineControlPointList[ii].m_x );
|
|
ctrlp.push_back( m_curr_entity.m_SplineControlPointList[ii].m_y );
|
|
}
|
|
|
|
tinyspline::BSpline beziers;
|
|
std::vector<double> coords;
|
|
|
|
try
|
|
{
|
|
tinyspline::BSpline dxfspline( m_curr_entity.m_SplineControlPointList.size(),
|
|
/* coord dim */ 2, m_curr_entity.m_SplineDegree );
|
|
|
|
dxfspline.setControlPoints( ctrlp );
|
|
dxfspline.setKnots( m_curr_entity.m_SplineKnotsList );
|
|
|
|
if( dxfspline.degree() < 3 )
|
|
dxfspline = dxfspline.elevateDegree( 3 - dxfspline.degree() );
|
|
|
|
beziers = dxfspline.toBeziers();
|
|
coords = beziers.controlPoints();
|
|
}
|
|
catch( const std::runtime_error& ) // tinyspline throws everything including data validation
|
|
// as runtime errors
|
|
{
|
|
// invalid spline definition, drop this block
|
|
ReportMsg( _( "Invalid spline definition encountered" ) );
|
|
return;
|
|
}
|
|
|
|
size_t order = beziers.order();
|
|
size_t dim = beziers.dimension();
|
|
size_t numBeziers = ( coords.size() / dim ) / order;
|
|
|
|
for( size_t i = 0; i < numBeziers; i++ )
|
|
{
|
|
size_t ii = i * dim * order;
|
|
VECTOR2D start( mapX( coords[ ii ] ), mapY( coords[ ii + 1 ] ) );
|
|
VECTOR2D bezierControl1( mapX( coords[ii + 2] ), mapY( coords[ii + 3] ) );
|
|
|
|
// not sure why this happens, but it seems to sometimes slip degree on the final bezier
|
|
VECTOR2D bezierControl2;
|
|
|
|
if( ii + 5 >= coords.size() )
|
|
bezierControl2 = bezierControl1;
|
|
else
|
|
bezierControl2 = VECTOR2D( mapX( coords[ii + 4] ), mapY( coords[ii + 5] ) );
|
|
|
|
VECTOR2D end;
|
|
|
|
if( ii + 7 >= coords.size() )
|
|
end = bezierControl2;
|
|
else
|
|
end = VECTOR2D( mapX( coords[ii + 6] ), mapY( coords[ii + 7] ) );
|
|
|
|
GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
|
|
: &m_internalImporter;
|
|
bufferToUse->AddSpline( start, bezierControl1, bezierControl2, end, aWidth );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void DXF_IMPORT_PLUGIN::updateImageLimits( const VECTOR2D& aPoint )
|
|
{
|
|
m_minX = std::min( aPoint.x, m_minX );
|
|
m_maxX = std::max( aPoint.x, m_maxX );
|
|
|
|
m_minY = std::min( aPoint.y, m_minY );
|
|
m_maxY = std::max( aPoint.y, m_maxY );
|
|
}
|
|
|
|
|
|
MATRIX3x3D 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 MATRIX3x3D{ arbX, arbY, arbZ };
|
|
}
|
|
|
|
|
|
VECTOR3D DXF_IMPORT_PLUGIN::wcsToOcs( const MATRIX3x3D& arbitraryAxis, VECTOR3D point )
|
|
{
|
|
return arbitraryAxis * point;
|
|
}
|
|
|
|
|
|
VECTOR3D DXF_IMPORT_PLUGIN::ocsToWcs( const MATRIX3x3D& 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 ) );
|
|
|
|
MATRIX3x3 world( worldX, worldY, worldZ );
|
|
|
|
return world * point;
|
|
}
|