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