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