1 /*
2  * LibrePCB - Professional EDA for everyone!
3  * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4  * https://librepcb.org/
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /*******************************************************************************
21  *  Includes
22  ******************************************************************************/
23 #include "dxfreader.h"
24 
25 #include <dl_creationadapter.h>
26 #include <dl_dxf.h>
27 
28 /*******************************************************************************
29  *  Namespace
30  ******************************************************************************/
31 namespace librepcb {
32 
33 /*******************************************************************************
34  *  Class DxfReaderImpl
35  ******************************************************************************/
36 
37 /**
38  * @brief Private helper class to break dependency to dxflib
39  *
40  * Having this class in the *.cpp file avoid including dxflib headers in our
41  * own header file. This way, dxflib only needs to be in the include path for
42  * the librepcb common library, but not for any other library or application.
43  *
44  * Or in other words: This way, the dependency to dxflib is an implementation
45  * detail which other parts of the code base do not have to care about.
46  */
47 class DxfReaderImpl : public DL_CreationAdapter {
48 public:
DxfReaderImpl(DxfReader & reader)49   DxfReaderImpl(DxfReader& reader)
50     : mReader(reader),
51       mScaleToMm(1),
52       mPolylineClosed(false),
53       mPolylineVertices(0),
54       mPolylinePath() {}
55 
~DxfReaderImpl()56   virtual ~DxfReaderImpl() {}
57 
addPoint(const DL_PointData & data)58   virtual void addPoint(const DL_PointData& data) override {
59     mReader.mPoints.append(point(data.x, data.y));
60   }
61 
addLine(const DL_LineData & data)62   virtual void addLine(const DL_LineData& data) override {
63     mReader.mPolygons.append(
64         Path::line(point(data.x1, data.y1), point(data.x2, data.y2)));
65   }
66 
addArc(const DL_ArcData & data)67   virtual void addArc(const DL_ArcData& data) override {
68     Point center = point(data.cx, data.cy);
69     Length radius = length(data.radius);
70     Angle angle1 = angle(data.angle1);
71     Angle angle2 = angle(data.angle2);
72 
73     Point p1 = center + Point(radius, 0).rotated(angle1);
74     Point p2 = center + Point(radius, 0).rotated(angle2);
75     Angle angle = angle2 - angle1;
76     if (angle < 0) {
77       angle.invert();
78     }
79     mReader.mPolygons.append(Path::line(p1, p2, angle));
80   }
81 
addCircle(const DL_CircleData & data)82   virtual void addCircle(const DL_CircleData& data) override {
83     Length diameter = length(data.radius * 2);
84     if (diameter > 0) {
85       mReader.mCircles.append(
86           DxfReader::Circle{point(data.cx, data.cy), PositiveLength(diameter)});
87     } else {
88       qWarning() << "Circle in DXF file ignored due to invalid diameter.";
89     }
90   }
91 
addEllipse(const DL_EllipseData & data)92   virtual void addEllipse(const DL_EllipseData& data) override {
93     Q_UNUSED(data);
94     qWarning() << "Ellipse in DXF file ignored since it is not supported yet.";
95   }
96 
addPolyline(const DL_PolylineData & data)97   virtual void addPolyline(const DL_PolylineData& data) override {
98     mPolylineClosed = (data.flags & DL_CLOSED_PLINE) != 0;
99     mPolylineVertices = data.number;
100     mPolylinePath = Path();
101   }
102 
addVertex(const DL_VertexData & data)103   virtual void addVertex(const DL_VertexData& data) override {
104     mPolylinePath.addVertex(point(data.x, data.y), bulgeToAngle(data.bulge));
105     if (mPolylinePath.getVertices().count() == mPolylineVertices) {
106       endSequence();
107     }
108   }
109 
endSequence()110   virtual void endSequence() override {
111     if (mPolylinePath.getVertices().count() >= 2) {
112       if (mPolylineClosed && (mPolylinePath.getVertices().count() >= 3)) {
113         mPolylinePath.close();
114       }
115       mReader.mPolygons.append(mPolylinePath);
116     }
117     mPolylinePath = Path();
118   }
119 
setVariableInt(const std::string & key,int value,int code)120   virtual void setVariableInt(const std::string& key, int value,
121                               int code) override {
122     if ((key == "$INSUNITS") && (code == 70)) {
123       // Unit specified in DXF, use corresponding conversion scaling factors.
124       QHash<int, qreal> map = {
125           {0, 1},  // unspecified -> consider as millimeters, the only real unit
126           {1, 25.4},  // inches
127           {2, 304.8},  // feet
128           {4, 1},  // millimeters
129           {5, 10},  // centimeters
130           {6, 1000},  // meters
131           {8, 2.54e-5},  // microinches
132           {9, 0.0254},  // mils
133           {10, 914.4},  // yards
134           {11, 1.0e-7},  // angstroms
135           {12, 1.0e-6},  // nanometers
136           {13, 1.0e-3},  // micrometers
137           {14, 100},  // decimeters
138       };
139       mScaleToMm = map.value(value, 1);  // use millimeters if unit not found
140     }
141   }
142 
143 private:  // Methods
angle(double angle) const144   Angle angle(double angle) const { return Angle::fromDeg(angle); }
bulgeToAngle(double bulge) const145   Angle bulgeToAngle(double bulge) const {
146     // Round to 0.001° to avoid odd numbers like 179.999999°.
147     return Angle::fromRad(std::atan(bulge) * 4).rounded(Angle(1000));
148   }
point(double x,double y) const149   Point point(double x, double y) const {
150     return Point(length(x), length(y));  // can (theoretically) throw
151   }
length(double value) const152   Length length(double value) const {
153     return Length::fromMm(value * mScaleToMm *
154                           mReader.mScaleFactor);  // can (theoretically) throw
155   }
156 
157 private:  // Data
158   DxfReader& mReader;
159   qreal mScaleToMm;
160 
161   // Current polygon state
162   bool mPolylineClosed;
163   int mPolylineVertices;
164   Path mPolylinePath;
165 };
166 
167 /*******************************************************************************
168  *  Constructors / Destructor
169  ******************************************************************************/
170 
DxfReader()171 DxfReader::DxfReader() noexcept : mScaleFactor(1) {
172 }
173 
~DxfReader()174 DxfReader::~DxfReader() noexcept {
175 }
176 
177 /*******************************************************************************
178  *  General Methods
179  ******************************************************************************/
180 
parse(const FilePath & dxfFile)181 void DxfReader::parse(const FilePath& dxfFile) {
182   try {
183     DL_Dxf dxf;
184     DxfReaderImpl helper(*this);
185     if (!dxf.in(dxfFile.toNative().toStdString(), &helper)) {
186       throw RuntimeError(__FILE__, __LINE__,
187                          tr("File does not exist or is not readable."));
188     }
189   } catch (const std::exception& e) {
190     // Since a third party library was used, catch std::exception and convert
191     // it to our own exception type.
192     throw RuntimeError(__FILE__, __LINE__,
193                        tr("Failed to read DXF file \"%1\": %2")
194                            .arg(dxfFile.toNative(), e.what()));
195   }
196 }
197 
198 /*******************************************************************************
199  *  End of File
200  ******************************************************************************/
201 
202 }  // namespace librepcb
203