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 "boardclipperpathgenerator.h"
24 
25 #include "../board.h"
26 #include "../items/bi_device.h"
27 #include "../items/bi_footprint.h"
28 #include "../items/bi_footprintpad.h"
29 #include "../items/bi_hole.h"
30 #include "../items/bi_netline.h"
31 #include "../items/bi_netsegment.h"
32 #include "../items/bi_plane.h"
33 #include "../items/bi_polygon.h"
34 #include "../items/bi_via.h"
35 
36 #include <librepcb/common/geometry/polygon.h>
37 #include <librepcb/common/graphics/graphicslayer.h>
38 #include <librepcb/common/utils/clipperhelpers.h>
39 #include <librepcb/library/pkg/footprint.h>
40 
41 #include <QtCore>
42 
43 /*******************************************************************************
44  *  Namespace
45  ******************************************************************************/
46 namespace librepcb {
47 namespace project {
48 
49 /*******************************************************************************
50  *  Constructors / Destructor
51  ******************************************************************************/
52 
BoardClipperPathGenerator(Board & board,const PositiveLength & maxArcTolerance)53 BoardClipperPathGenerator::BoardClipperPathGenerator(
54     Board& board, const PositiveLength& maxArcTolerance) noexcept
55   : mBoard(board), mMaxArcTolerance(maxArcTolerance), mPaths() {
56 }
57 
~BoardClipperPathGenerator()58 BoardClipperPathGenerator::~BoardClipperPathGenerator() noexcept {
59 }
60 
61 /*******************************************************************************
62  *  General Methods
63  ******************************************************************************/
64 
addBoardOutline()65 void BoardClipperPathGenerator::addBoardOutline() {
66   // board polygons
67   foreach (const BI_Polygon* polygon, mBoard.getPolygons()) {
68     if (polygon->getPolygon().getLayerName() != GraphicsLayer::sBoardOutlines) {
69       continue;
70     }
71     ClipperHelpers::unite(
72         mPaths,
73         ClipperHelpers::convert(polygon->getPolygon().getPath(),
74                                 mMaxArcTolerance));
75   }
76 
77   // footprint polygons
78   foreach (const BI_Device* device, mBoard.getDeviceInstances()) {
79     for (const Polygon& polygon : device->getLibFootprint().getPolygons()) {
80       if (polygon.getLayerName() != GraphicsLayer::sBoardOutlines) {
81         continue;
82       }
83       Path path = polygon.getPath();
84       path.rotate(device->getFootprint().getRotation());
85       if (device->getFootprint().getIsMirrored()) path.mirror(Qt::Horizontal);
86       path.translate(device->getFootprint().getPosition());
87       ClipperHelpers::unite(mPaths,
88                             ClipperHelpers::convert(path, mMaxArcTolerance));
89     }
90   }
91 }
92 
addHoles(const Length & offset)93 void BoardClipperPathGenerator::addHoles(const Length& offset) {
94   // board holes
95   foreach (const BI_Hole* hole, mBoard.getHoles()) {
96     Length diameter = hole->getHole().getDiameter() + (offset * 2);
97     if (diameter <= 0) {
98       continue;
99     }
100     Path path =
101         Path::circle(PositiveLength(diameter)).translated(hole->getPosition());
102     ClipperHelpers::unite(mPaths,
103                           ClipperHelpers::convert(path, mMaxArcTolerance));
104   }
105 
106   // footprint holes
107   foreach (const BI_Device* device, mBoard.getDeviceInstances()) {
108     for (const Hole& hole : device->getLibFootprint().getHoles()) {
109       Length diameter = hole.getDiameter() + (offset * 2);
110       if (diameter <= 0) {
111         continue;
112       }
113       Path path =
114           Path::circle(PositiveLength(diameter)).translated(hole.getPosition());
115       path.rotate(device->getFootprint().getRotation());
116       if (device->getFootprint().getIsMirrored()) path.mirror(Qt::Horizontal);
117       path.translate(device->getFootprint().getPosition());
118       ClipperHelpers::unite(mPaths,
119                             ClipperHelpers::convert(path, mMaxArcTolerance));
120     }
121   }
122 }
123 
addCopper(const QString & layerName,const NetSignal * netsignal)124 void BoardClipperPathGenerator::addCopper(const QString& layerName,
125                                           const NetSignal* netsignal) {
126   // polygons
127   foreach (const BI_Polygon* polygon, mBoard.getPolygons()) {
128     if ((polygon->getPolygon().getLayerName() != layerName) ||
129         (netsignal != nullptr)) {
130       continue;
131     }
132     // outline
133     if (polygon->getPolygon().getLineWidth() > 0) {
134       QVector<Path> paths = polygon->getPolygon().getPath().toOutlineStrokes(
135           PositiveLength(*polygon->getPolygon().getLineWidth()));
136       foreach (const Path& p, paths) {
137         ClipperHelpers::unite(mPaths,
138                               ClipperHelpers::convert(p, mMaxArcTolerance));
139       }
140     }
141     // area (only fill closed paths, for consistency with the appearance in the
142     // board editor and Gerber output)
143     if (polygon->getPolygon().isFilled() &&
144         polygon->getPolygon().getPath().isClosed()) {
145       ClipperHelpers::unite(
146           mPaths,
147           ClipperHelpers::convert(polygon->getPolygon().getPath(),
148                                   mMaxArcTolerance));
149     }
150   }
151 
152   // stroke texts
153   foreach (const BI_StrokeText* text, mBoard.getStrokeTexts()) {
154     if ((text->getText().getLayerName() != layerName) ||
155         (netsignal != nullptr)) {
156       continue;
157     }
158     PositiveLength width(qMax(*text->getText().getStrokeWidth(), Length(1)));
159     foreach (Path path, text->getText().getPaths()) {
160       path.rotate(text->getText().getRotation());
161       if (text->getText().getMirrored()) path.mirror(Qt::Horizontal);
162       path.translate(text->getText().getPosition());
163       QVector<Path> paths = path.toOutlineStrokes(width);
164       foreach (const Path& p, paths) {
165         ClipperHelpers::unite(mPaths,
166                               ClipperHelpers::convert(p, mMaxArcTolerance));
167       }
168     }
169   }
170 
171   // planes
172   foreach (const BI_Plane* plane, mBoard.getPlanes()) {
173     if ((plane->getLayerName() != layerName) ||
174         (&plane->getNetSignal() != netsignal)) {
175       continue;
176     }
177     foreach (const Path& p, plane->getFragments()) {
178       ClipperHelpers::unite(mPaths,
179                             ClipperHelpers::convert(p, mMaxArcTolerance));
180     }
181   }
182 
183   // devices
184   foreach (const BI_Device* device, mBoard.getDeviceInstances()) {
185     const BI_Footprint& footprint = device->getFootprint();
186 
187     // polygons
188     for (const Polygon& polygon : device->getLibFootprint().getPolygons()) {
189       QString polygonLayer = *polygon.getLayerName();
190       if (footprint.getIsMirrored()) {
191         polygonLayer = GraphicsLayer::getMirroredLayerName(polygonLayer);
192       }
193       if ((polygonLayer != layerName) || (netsignal != nullptr)) {
194         continue;
195       }
196       Path path = polygon.getPath();
197       path.rotate(footprint.getRotation());
198       if (footprint.getIsMirrored()) path.mirror(Qt::Horizontal);
199       path.translate(footprint.getPosition());
200       // outline
201       if (polygon.getLineWidth() > 0) {
202         QVector<Path> paths =
203             path.toOutlineStrokes(PositiveLength(*polygon.getLineWidth()));
204         foreach (const Path& p, paths) {
205           ClipperHelpers::unite(mPaths,
206                                 ClipperHelpers::convert(p, mMaxArcTolerance));
207         }
208       }
209       // area (only fill closed paths, for consistency with the appearance in
210       // the board editor and Gerber output)
211       if (polygon.isFilled() && path.isClosed()) {
212         ClipperHelpers::unite(mPaths,
213                               ClipperHelpers::convert(path, mMaxArcTolerance));
214       }
215     }
216 
217     // circles
218     for (const Circle& circle : device->getLibFootprint().getCircles()) {
219       QString circleLayer = *circle.getLayerName();
220       if (footprint.getIsMirrored()) {
221         circleLayer = GraphicsLayer::getMirroredLayerName(circleLayer);
222       }
223       if ((circleLayer != layerName) || (netsignal != nullptr)) {
224         continue;
225       }
226       Point absolutePos = circle.getCenter();
227       absolutePos.rotate(footprint.getRotation());
228       if (footprint.getIsMirrored()) absolutePos.mirror(Qt::Horizontal);
229       absolutePos += footprint.getPosition();
230       Path path = Path::circle(circle.getDiameter());
231       path.translate(absolutePos);
232       // outline
233       if (circle.getLineWidth() > 0) {
234         QVector<Path> paths =
235             path.toOutlineStrokes(PositiveLength(*circle.getLineWidth()));
236         foreach (const Path& p, paths) {
237           ClipperHelpers::unite(mPaths,
238                                 ClipperHelpers::convert(p, mMaxArcTolerance));
239         }
240       }
241       // area
242       if (circle.isFilled()) {
243         ClipperHelpers::unite(mPaths,
244                               ClipperHelpers::convert(path, mMaxArcTolerance));
245       }
246     }
247 
248     // stroke texts
249     foreach (const BI_StrokeText* text, footprint.getStrokeTexts()) {
250       // Do *not* mirror layer since it is independent of the device!
251       if ((*text->getText().getLayerName() != layerName) ||
252           (netsignal != nullptr)) {
253         continue;
254       }
255       PositiveLength width(qMax(*text->getText().getStrokeWidth(), Length(1)));
256       foreach (Path path, text->getText().getPaths()) {
257         path.rotate(text->getText().getRotation());
258         if (text->getText().getMirrored()) path.mirror(Qt::Horizontal);
259         path.translate(text->getText().getPosition());
260         foreach (const Path& p, path.toOutlineStrokes(width)) {
261           ClipperHelpers::unite(mPaths,
262                                 ClipperHelpers::convert(p, mMaxArcTolerance));
263         }
264       }
265     }
266 
267     // pads
268     foreach (const BI_FootprintPad* pad, footprint.getPads()) {
269       if ((!pad->isOnLayer(layerName)) ||
270           (pad->getCompSigInstNetSignal() != netsignal)) {
271         continue;
272       }
273       ClipperHelpers::unite(
274           mPaths,
275           ClipperHelpers::convert(pad->getSceneOutline(), mMaxArcTolerance));
276     }
277   }
278 
279   // net segment items
280   foreach (const BI_NetSegment* netsegment, mBoard.getNetSegments()) {
281     if (&netsegment->getNetSignal() != netsignal) {
282       continue;
283     }
284 
285     // vias
286     foreach (const BI_Via* via, netsegment->getVias()) {
287       if (!via->isOnLayer(layerName)) {
288         continue;
289       }
290       ClipperHelpers::unite(
291           mPaths,
292           ClipperHelpers::convert(via->getVia().getSceneOutline(),
293                                   mMaxArcTolerance));
294     }
295 
296     // netlines
297     foreach (const BI_NetLine* netline, netsegment->getNetLines()) {
298       if (&netline->getLayer().getName() != layerName) {
299         continue;
300       }
301       ClipperHelpers::unite(mPaths,
302                             ClipperHelpers::convert(netline->getSceneOutline(),
303                                                     mMaxArcTolerance));
304     }
305   }
306 }
307 
308 /*******************************************************************************
309  *  End of File
310  ******************************************************************************/
311 
312 }  // namespace project
313 }  // namespace librepcb
314