1
/*******************************************************************
3
Part of the Fritzing project - http://fritzing.org
4
Copyright (c) 2007-2011 Fachhochschule Potsdam - http://fh-potsdam.de
6
Fritzing 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.
11
Fritzing 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.
16
You should have received a copy of the GNU General Public License
17
along with Fritzing. If not, see <http://www.gnu.org/licenses/>.
19
********************************************************************
22
$Author: cohen@irascible.com $:
23
$Date: 2011-07-11 14:13:40 +0200 (Mon, 11 Jul 2011) $
25
********************************************************************/
27
#include <QMessageBox>
28
#include <QFileDialog>
29
#include <QSvgRenderer>
31
#include "mainwindow.h"
32
#include "debugdialog.h"
33
#include "fsvgrenderer.h"
34
#include "svg/svg2gerber.h"
35
#include "sketch/pcbsketchwidget.h"
36
#include "svg/svgfilesplitter.h"
37
#include "svg/groundplanegenerator.h"
38
#include "utils/graphicsutils.h"
39
#include "utils/textutils.h"
40
#include "utils/folderutils.h"
41
#include "items/logoitem.h"
43
static QRegExp AaCc("[aAcC]");
45
////////////////////////////////////////////
47
void MainWindow::exportToGerber() {
49
//NOTE: this assumes just one board per sketch
51
// grab the list of parts
52
ItemBase * board = m_pcbGraphicsView->findBoard();
53
// barf an error if there's no board
55
QMessageBox::critical(this, tr("Fritzing"),
56
tr("Your sketch does not have a board yet! Please add a PCB in order to export to Gerber."));
60
QString exportDir = QFileDialog::getExistingDirectory(this, tr("Choose a folder for exporting"),
62
QFileDialog::ShowDirsOnly
63
| QFileDialog::DontResolveSymlinks);
65
if (exportDir.isEmpty()) return;
67
FolderUtils::setOpenSaveFolder(exportDir);
68
exportToGerber(exportDir, board, true);
71
void MainWindow::exportToGerber(const QString & exportDir, ItemBase * board, bool displayMessageBoxes)
74
board = m_pcbGraphicsView->findBoard();
77
DebugDialog::debug("board not found");
81
LayerList viewLayerIDs = ViewLayer::copperLayers(ViewLayer::Bottom);
82
int copperInvalidCount = doCopper(board, viewLayerIDs, "Copper0", "_copperBottom.gbl", exportDir, displayMessageBoxes);
84
if (m_pcbGraphicsView->boardLayers() == 2) {
85
viewLayerIDs = ViewLayer::copperLayers(ViewLayer::Top);
86
copperInvalidCount += doCopper(board, viewLayerIDs, "Copper1", "_copperTop.gtl", exportDir, displayMessageBoxes);
89
LayerList maskLayerIDs = ViewLayer::maskLayers(ViewLayer::Bottom);
90
int maskInvalidCount = doMask(maskLayerIDs, "Mask0", "_maskBottom.gbs", board, exportDir, displayMessageBoxes);
92
if (m_pcbGraphicsView->boardLayers() == 2) {
93
maskLayerIDs = ViewLayer::maskLayers(ViewLayer::Top);
94
maskInvalidCount += doMask(maskLayerIDs, "Mask1", "_maskTop.gts", board, exportDir, displayMessageBoxes);
97
LayerList silkLayerIDs;
98
silkLayerIDs << ViewLayer::Silkscreen1 << ViewLayer::Silkscreen1Label;
99
int silkInvalidCount = doSilk(silkLayerIDs, "Silk1", "_silkTop.gto", board, exportDir, displayMessageBoxes);
100
silkLayerIDs.clear();
101
silkLayerIDs << ViewLayer::Silkscreen0 << ViewLayer::Silkscreen0Label;
102
silkInvalidCount += doSilk(silkLayerIDs, "Silk0", "_silkBottom.gbo", board, exportDir, displayMessageBoxes);
104
// now do it for the outline/contour
105
LayerList outlineLayerIDs;
106
outlineLayerIDs << ViewLayer::Board;
109
QString svgOutline = m_pcbGraphicsView->renderToSVG(FSvgRenderer::printerScale(), outlineLayerIDs, outlineLayerIDs, true, imageSize, board, GraphicsUtils::StandardFritzingDPI, false, false, false, empty);
110
if (svgOutline.isEmpty()) {
111
displayMessage(tr("outline is empty"), displayMessageBoxes);
115
QXmlStreamReader streamReader(svgOutline);
116
QSizeF svgSize = FSvgRenderer::parseForWidthAndHeight(streamReader);
118
QDomDocument domDocument;
122
bool result = domDocument.setContent(svgOutline, &errorStr, &errorLine, &errorColumn);
124
displayMessage(tr("outline file export failure (1)"), displayMessageBoxes);
128
// create copper0 gerber from svg
129
SVG2gerber outlineGerber;
130
int outlineInvalidCount = outlineGerber.convert(svgOutline, m_pcbGraphicsView->boardLayers() == 2, "contour", SVG2gerber::ForOutline, svgSize * GraphicsUtils::StandardFritzingDPI);
131
if (outlineInvalidCount > 0) {
132
outlineInvalidCount = 0;
133
svgOutline = clipToBoard(svgOutline, board, "board");
134
outlineInvalidCount = outlineGerber.convert(svgOutline, m_pcbGraphicsView->boardLayers() == 2, "contour", SVG2gerber::ForOutline, svgSize * GraphicsUtils::StandardFritzingDPI);
137
doDrill(board, exportDir, displayMessageBoxes);
139
// contour / board outline
140
QString contourFile = exportDir + "/" +
141
QFileInfo(m_fileName).fileName().remove(FritzingSketchExtension)
143
QFile contourOut(contourFile);
144
if (!contourOut.open(QIODevice::WriteOnly | QIODevice::Text)) {
145
displayMessage(tr("outline file export failure (2)"), displayMessageBoxes);
149
QTextStream contourStream(&contourOut);
150
contourStream << outlineGerber.getGerber();
152
if (outlineInvalidCount > 0 || silkInvalidCount > 0 || copperInvalidCount > 0 || maskInvalidCount) {
154
if (outlineInvalidCount > 0) s += tr("the board outline layer, ");
155
if (silkInvalidCount > 0) s += tr("silkscreen layer(s), ");
156
if (copperInvalidCount > 0) s += tr("copper layer(s), ");
157
if (maskInvalidCount > 0) s += tr("mask layer(s), ");
159
displayMessage(tr("Unable to translate svg curves in ").arg(s), displayMessageBoxes);
164
int MainWindow::doCopper(ItemBase * board, LayerList & viewLayerIDs, const QString & copperName, const QString & copperSuffix, const QString & exportDir, bool displayMessageBoxes)
168
QString svg = m_pcbGraphicsView->renderToSVG(FSvgRenderer::printerScale(), viewLayerIDs, viewLayerIDs, true, imageSize, board, GraphicsUtils::StandardFritzingDPI, false, false, false, empty);
170
displayMessage(tr("%1 file export failure (1)").arg(copperName), displayMessageBoxes);
174
QXmlStreamReader streamReader(svg);
175
QSizeF svgSize = FSvgRenderer::parseForWidthAndHeight(streamReader);
177
svg = clipToBoard(svg, board, copperName);
179
displayMessage(tr("%1 file export failure (3)").arg(copperName), displayMessageBoxes);
183
// create copper gerber from svg
184
SVG2gerber copperGerber;
185
int copperInvalidCount = copperGerber.convert(svg, m_pcbGraphicsView->boardLayers() == 2, copperName, SVG2gerber::ForNormal, svgSize * GraphicsUtils::StandardFritzingDPI);
187
QString copperFile = exportDir + "/" +
188
QFileInfo(m_fileName).fileName().remove(FritzingSketchExtension)
190
QFile copperOut(copperFile);
191
if (!copperOut.open(QIODevice::WriteOnly | QIODevice::Text)) {
192
displayMessage(tr("%1 file export failure (3)").arg(copperName), displayMessageBoxes);
196
QTextStream copperStream(&copperOut);
197
copperStream << copperGerber.getGerber();
198
copperStream.flush();
201
return copperInvalidCount;
205
int MainWindow::doSilk(LayerList silkLayerIDs, const QString & silkName, const QString & gerberSuffix, ItemBase * board, const QString & exportDir, bool displayMessageBoxes )
209
QString svgSilk = m_pcbGraphicsView->renderToSVG(FSvgRenderer::printerScale(), silkLayerIDs, silkLayerIDs, true, imageSize, board, GraphicsUtils::StandardFritzingDPI, false, false, false, empty);
210
if (svgSilk.isEmpty()) {
211
displayMessage(tr("silk file export failure (1)"), displayMessageBoxes);
216
// don't bother with file
220
QXmlStreamReader streamReader(svgSilk);
221
QSizeF svgSize = FSvgRenderer::parseForWidthAndHeight(streamReader);
223
svgSilk = clipToBoard(svgSilk, board, silkName);
224
if (svgSilk.isEmpty()) {
225
displayMessage(tr("silk export failure"), displayMessageBoxes);
229
// create silk gerber from svg
230
SVG2gerber silkGerber;
231
int silkInvalidCount = silkGerber.convert(svgSilk, m_pcbGraphicsView->boardLayers() == 2, silkName, SVG2gerber::ForNormal, svgSize * GraphicsUtils::StandardFritzingDPI);
233
QString silkFile = exportDir + "/" +
234
QFileInfo(m_fileName).fileName().remove(FritzingSketchExtension)
236
QFile silkOut(silkFile);
237
if (!silkOut.open(QIODevice::WriteOnly | QIODevice::Text)) {
238
displayMessage(tr("silk file export failure (2)"), displayMessageBoxes);
242
QTextStream silkStream(&silkOut);
243
silkStream << silkGerber.getGerber();
247
return silkInvalidCount;
251
int MainWindow::doDrill(ItemBase * board, const QString & exportDir, bool displayMessageBoxes)
253
LayerList drillLayerIDs;
254
drillLayerIDs << ViewLayer::Copper0;
258
QString svgDrill = m_pcbGraphicsView->renderToSVG(FSvgRenderer::printerScale(), drillLayerIDs, drillLayerIDs, true, imageSize, board, GraphicsUtils::StandardFritzingDPI, false, false, false, empty);
259
if (svgDrill.isEmpty()) {
260
displayMessage(tr("drill file export failure (1)"), displayMessageBoxes);
265
// don't bother with file
269
QXmlStreamReader streamReader(svgDrill);
270
QSizeF svgSize = FSvgRenderer::parseForWidthAndHeight(streamReader);
272
svgDrill = clipToBoard(svgDrill, board, "Copper0");
273
if (svgDrill.isEmpty()) {
274
displayMessage(tr("drill export failure"), displayMessageBoxes);
278
// create silk gerber from svg
279
SVG2gerber drillGerber;
280
int drillInvalidCount = drillGerber.convert(svgDrill, m_pcbGraphicsView->boardLayers() == 2, "drill", SVG2gerber::ForDrill, svgSize * GraphicsUtils::StandardFritzingDPI);
284
QString drillFile = exportDir + "/" +
285
QFileInfo(m_fileName).fileName().remove(FritzingSketchExtension)
287
QFile drillOut(drillFile);
288
if (!drillOut.open(QIODevice::WriteOnly | QIODevice::Text)) {
289
displayMessage(tr("drill file export failure (5)"), displayMessageBoxes);
293
QTextStream drillStream(&drillOut);
294
drillStream << drillGerber.getGerber();
298
return drillInvalidCount;
301
int MainWindow::doMask(LayerList maskLayerIDs, const QString &maskName, const QString & gerberSuffix, ItemBase * board, const QString & exportDir, bool displayMessageBoxes )
303
// don't want these in the mask laqyer
304
QList<ItemBase *> copperLogoItems;
305
foreach (QGraphicsItem * item, m_pcbGraphicsView->items()) {
306
CopperLogoItem * logoItem = dynamic_cast<CopperLogoItem *>(item);
307
if (logoItem && logoItem->isVisible()) {
308
copperLogoItems.append(logoItem);
309
logoItem->setVisible(false);
315
QString svgMask = m_pcbGraphicsView->renderToSVG(FSvgRenderer::printerScale(), maskLayerIDs, maskLayerIDs, true, imageSize, board, GraphicsUtils::StandardFritzingDPI, false, false, false, empty);
316
if (svgMask.isEmpty()) {
317
displayMessage(tr("mask file export failure (1)"), displayMessageBoxes);
321
foreach (ItemBase * logoItem, copperLogoItems) {
322
logoItem->setVisible(true);
326
// don't bother with file
330
QDomDocument domDocument;
334
bool result = domDocument.setContent(svgMask, &errorStr, &errorLine, &errorColumn);
336
displayMessage(tr("%1 file export failure (2)").arg(maskName), displayMessageBoxes);
340
QXmlStreamReader streamReader(svgMask);
341
QSizeF svgSize = FSvgRenderer::parseForWidthAndHeight(streamReader);
343
svgMask = clipToBoard(svgMask, board, maskName);
344
if (svgMask.isEmpty()) {
345
displayMessage(tr("mask export failure"), displayMessageBoxes);
349
// create mask gerber from svg
350
SVG2gerber maskGerber;
351
int maskInvalidCount = maskGerber.convert(svgMask, m_pcbGraphicsView->boardLayers() == 2, maskName, SVG2gerber::ForMask, svgSize * GraphicsUtils::StandardFritzingDPI);
353
QString maskFile = exportDir + "/" +
354
QFileInfo(m_fileName).fileName().remove(FritzingSketchExtension)
356
QFile maskOut(maskFile);
357
if (!maskOut.open(QIODevice::WriteOnly | QIODevice::Text)) {
358
displayMessage(tr("mask file export failure (2)"), displayMessageBoxes);
362
QTextStream maskStream(&maskOut);
363
maskStream << maskGerber.getGerber();
367
return maskInvalidCount;
370
void MainWindow::displayMessage(const QString & message, bool displayMessageBoxes) {
371
// don't use QMessageBox if running conversion as a service
372
if (displayMessageBoxes) {
373
QMessageBox::warning(this, tr("Fritzing"), message);
377
DebugDialog::debug(message);
380
QString MainWindow::clipToBoard(QString svgString, ItemBase * board, const QString & layerName) {
381
QDomDocument domDocument1;
385
bool result = domDocument1.setContent(svgString, &errorStr, &errorLine, &errorColumn);
390
QDomDocument domDocument2;
391
domDocument2.setContent(svgString, &errorStr, &errorLine, &errorColumn);
393
bool anyConverted = false;
394
if (TextUtils::squashElement(domDocument1, "text", "", QRegExp())) {
398
// gerber can't handle ellipses that are rotated, so cull them all
399
if (TextUtils::squashElement(domDocument1, "ellipse", "", QRegExp())) {
403
// gerber can't handle paths with curves
404
if (TextUtils::squashElement(domDocument1, "path", "d", AaCc)) {
408
QVector <QDomElement> leaves1;
409
int transformCount1 = 0;
410
QDomElement e1 = domDocument1.documentElement();
411
TextUtils::collectLeaves(e1, transformCount1, leaves1);
413
QVector <QDomElement> leaves2;
414
int transformCount2 = 0;
415
QDomElement e2 = domDocument2.documentElement();
416
TextUtils::collectLeaves(e2, transformCount2, leaves2);
418
qreal res = GraphicsUtils::StandardFritzingDPI;
419
QRectF source = board->boundingRect();
420
int twidth = res * source.width() / FSvgRenderer::printerScale();
421
int theight = res * source.height() / FSvgRenderer::printerScale();
423
svgString = TextUtils::removeXMLEntities(domDocument1.toString());
424
QXmlStreamReader reader(svgString);
425
QSvgRenderer renderer(&reader);
426
bool anyClipped = false;
427
for (int i = 0; i < transformCount1; i++) {
428
QString n = QString::number(i);
429
QRectF bounds = renderer.boundsOnElement(n);
430
QMatrix m = renderer.matrixForElement(n);
431
QDomElement element = leaves1.at(i);
432
QString ms = element.attribute("transform");
434
m *= TextUtils::transformStringToMatrix(ms);
436
QRectF mBounds = m.mapRect(bounds);
437
if (mBounds.left() < 0 || mBounds.top() < 0 || bounds.right() > twidth || bounds.bottom() > theight) {
438
// element is outside of bounds, squash it so it will be clipped
439
element.setTagName("g");
440
anyClipped = anyConverted = true;
445
svgString = TextUtils::removeXMLEntities(domDocument1.toString());
449
for (int i = 0; i < transformCount1; i++) {
450
QDomElement element1 = leaves1.at(i);
451
if (element1.tagName() != "g") {
452
QDomElement element2 = leaves2.at(i);
453
element2.setTagName("g");
457
QString svg = TextUtils::removeXMLEntities(domDocument2.toString());
459
QSize imgSize(twidth, theight);
461
// expand the svg to fill the space of the image
462
QRegExp widthFinder("width=[^i]+in.");
463
int ix = widthFinder.indexIn(svg);
465
svg.replace(ix, widthFinder.cap(0).length(), QString("width=\"%1px\"").arg(twidth));
467
QRegExp heightFinder("height=[^i]+in.");
468
ix = heightFinder.indexIn(svg);
470
svg.replace(ix, heightFinder.cap(0).length(), QString("height=\"%1px\"").arg(theight));
473
QStringList exceptions;
474
exceptions << "none" << "";
475
QString toColor("#000000");
476
QByteArray svgByteArray;
477
SvgFileSplitter::changeColors(svg, toColor, exceptions, svgByteArray);
479
QImage image(imgSize, QImage::Format_RGB32);
480
image.fill(0xffffffff);
481
image.setDotsPerMeterX(res * GraphicsUtils::InchesPerMeter);
482
image.setDotsPerMeterY(res * GraphicsUtils::InchesPerMeter);
483
QRectF target(0, 0, twidth, theight);
485
QSvgRenderer renderer(svgByteArray);
487
painter.begin(&image);
488
renderer.render(&painter, target);
490
image.invertPixels(); // need white pixels on a black background for GroundPlaneGenerator
491
//image.save("output.png");
493
GroundPlaneGenerator gpg;
494
gpg.scanImage(image, image.width(), image.height(), GraphicsUtils::StandardFritzingDPI / res, GraphicsUtils::StandardFritzingDPI, "#ffffff", layerName, false, 1, false, QSizeF(0, 0), 0);
496
if (gpg.newSVGs().count() > 0) {
498
TextUtils::mergeSvg(doc, svgString, "");
499
foreach (QString gsvg, gpg.newSVGs()) {
500
TextUtils::mergeSvg(doc, gsvg, "");
502
svgString = TextUtils::mergeSvgFinish(doc);