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-08-15 01:36:25 +0200 (Mon, 15 Aug 2011) $
25
********************************************************************/
27
#include <QMessageBox>
28
#include <QFileDialog>
29
#include <QSvgRenderer>
31
#include "gerbergenerator.h"
32
#include "../debugdialog.h"
33
#include "../fsvgrenderer.h"
34
#include "../sketch/pcbsketchwidget.h"
35
#include "svgfilesplitter.h"
36
#include "groundplanegenerator.h"
37
#include "../utils/graphicsutils.h"
38
#include "../utils/textutils.h"
39
#include "../utils/folderutils.h"
40
#include "../items/logoitem.h"
42
static QRegExp AaCc("[aAcCqQtTsS]");
44
////////////////////////////////////////////
46
void GerberGenerator::exportToGerber(const QString & filename, const QString & exportDir, ItemBase * board, PCBSketchWidget * sketchWidget, bool displayMessageBoxes)
49
board = sketchWidget->findBoard();
52
DebugDialog::debug("board not found");
56
LayerList viewLayerIDs = ViewLayer::copperLayers(ViewLayer::Bottom);
57
int copperInvalidCount = doCopper(board, sketchWidget, viewLayerIDs, "Copper0", "_copperBottom.gbl", filename, exportDir, displayMessageBoxes);
59
if (sketchWidget->boardLayers() == 2) {
60
viewLayerIDs = ViewLayer::copperLayers(ViewLayer::Top);
61
copperInvalidCount += doCopper(board, sketchWidget, viewLayerIDs, "Copper1", "_copperTop.gtl", filename, exportDir, displayMessageBoxes);
64
LayerList maskLayerIDs = ViewLayer::maskLayers(ViewLayer::Bottom);
65
int maskInvalidCount = doMask(maskLayerIDs, "Mask0", "_maskBottom.gbs", board, sketchWidget, filename, exportDir, displayMessageBoxes);
67
if (sketchWidget->boardLayers() == 2) {
68
maskLayerIDs = ViewLayer::maskLayers(ViewLayer::Top);
69
maskInvalidCount += doMask(maskLayerIDs, "Mask1", "_maskTop.gts", board, sketchWidget, filename, exportDir, displayMessageBoxes);
72
LayerList silkLayerIDs;
73
silkLayerIDs << ViewLayer::Silkscreen1 << ViewLayer::Silkscreen1Label;
74
int silkInvalidCount = doSilk(silkLayerIDs, "Silk1", "_silkTop.gto", board, sketchWidget, filename, exportDir, displayMessageBoxes);
76
silkLayerIDs << ViewLayer::Silkscreen0 << ViewLayer::Silkscreen0Label;
77
silkInvalidCount += doSilk(silkLayerIDs, "Silk0", "_silkBottom.gbo", board, sketchWidget, filename, exportDir, displayMessageBoxes);
79
// now do it for the outline/contour
80
LayerList outlineLayerIDs;
81
outlineLayerIDs << ViewLayer::Board;
84
QString svgOutline = sketchWidget->renderToSVG(FSvgRenderer::printerScale(), outlineLayerIDs, outlineLayerIDs, true, imageSize, board, GraphicsUtils::StandardFritzingDPI, false, false, false, empty);
85
if (svgOutline.isEmpty()) {
86
displayMessage(QObject::tr("outline is empty"), displayMessageBoxes);
90
QXmlStreamReader streamReader(svgOutline);
91
QSizeF svgSize = FSvgRenderer::parseForWidthAndHeight(streamReader);
93
QDomDocument domDocument;
97
bool result = domDocument.setContent(svgOutline, &errorStr, &errorLine, &errorColumn);
99
displayMessage(QObject::tr("outline file export failure (1)"), displayMessageBoxes);
103
// create copper0 gerber from svg
104
SVG2gerber outlineGerber;
105
int outlineInvalidCount = outlineGerber.convert(svgOutline, sketchWidget->boardLayers() == 2, "contour", SVG2gerber::ForOutline, svgSize * GraphicsUtils::StandardFritzingDPI);
106
if (outlineInvalidCount > 0) {
107
outlineInvalidCount = 0;
108
svgOutline = clipToBoard(svgOutline, board, "board", SVG2gerber::ForOutline);
109
outlineInvalidCount = outlineGerber.convert(svgOutline, sketchWidget->boardLayers() == 2, "contour", SVG2gerber::ForOutline, svgSize * GraphicsUtils::StandardFritzingDPI);
112
// contour / board outline
113
QString contourFile = exportDir + "/" +
114
QFileInfo(filename).fileName().remove(FritzingSketchExtension)
116
QFile contourOut(contourFile);
117
if (!contourOut.open(QIODevice::WriteOnly | QIODevice::Text)) {
118
displayMessage(QObject::tr("outline file export failure (2)"), displayMessageBoxes);
122
QTextStream contourStream(&contourOut);
123
contourStream << outlineGerber.getGerber();
125
doDrill(board, sketchWidget, filename, exportDir, displayMessageBoxes);
127
if (outlineInvalidCount > 0 || silkInvalidCount > 0 || copperInvalidCount > 0 || maskInvalidCount) {
129
if (outlineInvalidCount > 0) s += QObject::tr("the board outline layer, ");
130
if (silkInvalidCount > 0) s += QObject::tr("silkscreen layer(s), ");
131
if (copperInvalidCount > 0) s += QObject::tr("copper layer(s), ");
132
if (maskInvalidCount > 0) s += QObject::tr("mask layer(s), ");
134
displayMessage(QObject::tr("Unable to translate svg curves in ").arg(s), displayMessageBoxes);
139
int GerberGenerator::doCopper(ItemBase * board, PCBSketchWidget * sketchWidget, LayerList & viewLayerIDs, const QString & copperName, const QString & copperSuffix, const QString & filename, const QString & exportDir, bool displayMessageBoxes)
143
QString svg = sketchWidget->renderToSVG(FSvgRenderer::printerScale(), viewLayerIDs, viewLayerIDs, true, imageSize, board, GraphicsUtils::StandardFritzingDPI, false, false, false, empty);
145
displayMessage(QObject::tr("%1 file export failure (1)").arg(copperName), displayMessageBoxes);
149
QXmlStreamReader streamReader(svg);
150
QSizeF svgSize = FSvgRenderer::parseForWidthAndHeight(streamReader);
152
svg = clipToBoard(svg, board, copperName, SVG2gerber::ForNormal);
154
displayMessage(QObject::tr("%1 file export failure (3)").arg(copperName), displayMessageBoxes);
158
// create copper gerber from svg
159
SVG2gerber copperGerber;
160
int copperInvalidCount = copperGerber.convert(svg, sketchWidget->boardLayers() == 2, copperName, SVG2gerber::ForNormal, svgSize * GraphicsUtils::StandardFritzingDPI);
162
QString copperFile = exportDir + "/" +
163
QFileInfo(filename).fileName().remove(FritzingSketchExtension)
165
QFile copperOut(copperFile);
166
if (!copperOut.open(QIODevice::WriteOnly | QIODevice::Text)) {
167
displayMessage(QObject::tr("%1 file export failure (3)").arg(copperName), displayMessageBoxes);
171
QTextStream copperStream(&copperOut);
172
copperStream << copperGerber.getGerber();
173
copperStream.flush();
176
return copperInvalidCount;
180
int GerberGenerator::doSilk(LayerList silkLayerIDs, const QString & silkName, const QString & gerberSuffix, ItemBase * board, PCBSketchWidget * sketchWidget, const QString & filename, const QString & exportDir, bool displayMessageBoxes )
184
QString svgSilk = sketchWidget->renderToSVG(FSvgRenderer::printerScale(), silkLayerIDs, silkLayerIDs, true, imageSize, board, GraphicsUtils::StandardFritzingDPI, false, false, false, empty);
185
if (svgSilk.isEmpty()) {
186
displayMessage(QObject::tr("silk file export failure (1)"), displayMessageBoxes);
191
// don't bother with file
195
//QFile f(silkName + "original.svg");
196
//f.open(QFile::WriteOnly);
197
//QTextStream fs(&f);
201
QXmlStreamReader streamReader(svgSilk);
202
QSizeF svgSize = FSvgRenderer::parseForWidthAndHeight(streamReader);
204
svgSilk = clipToBoard(svgSilk, board, silkName, SVG2gerber::ForNormal);
205
if (svgSilk.isEmpty()) {
206
displayMessage(QObject::tr("silk export failure"), displayMessageBoxes);
210
//QFile f2(silkName + "clipped.svg");
211
//f2.open(QFile::WriteOnly);
212
//QTextStream fs2(&f2);
217
// create silk gerber from svg
218
SVG2gerber silkGerber;
219
int silkInvalidCount = silkGerber.convert(svgSilk, sketchWidget->boardLayers() == 2, silkName, SVG2gerber::ForNormal, svgSize * GraphicsUtils::StandardFritzingDPI);
221
QString silkFile = exportDir + "/" +
222
QFileInfo(filename).fileName().remove(FritzingSketchExtension)
224
QFile silkOut(silkFile);
225
if (!silkOut.open(QIODevice::WriteOnly | QIODevice::Text)) {
226
displayMessage(QObject::tr("silk file export failure (2)"), displayMessageBoxes);
230
QTextStream silkStream(&silkOut);
231
silkStream << silkGerber.getGerber();
235
return silkInvalidCount;
239
int GerberGenerator::doDrill(ItemBase * board, PCBSketchWidget * sketchWidget, const QString & filename, const QString & exportDir, bool displayMessageBoxes)
241
LayerList drillLayerIDs;
242
drillLayerIDs << ViewLayer::Copper0;
246
QString svgDrill = sketchWidget->renderToSVG(FSvgRenderer::printerScale(), drillLayerIDs, drillLayerIDs, true, imageSize, board, GraphicsUtils::StandardFritzingDPI, false, false, false, empty);
247
if (svgDrill.isEmpty()) {
248
displayMessage(QObject::tr("drill file export failure (1)"), displayMessageBoxes);
253
// don't bother with file
257
QXmlStreamReader streamReader(svgDrill);
258
QSizeF svgSize = FSvgRenderer::parseForWidthAndHeight(streamReader);
260
svgDrill = clipToBoard(svgDrill, board, "Copper0", SVG2gerber::ForDrill);
261
if (svgDrill.isEmpty()) {
262
displayMessage(QObject::tr("drill export failure"), displayMessageBoxes);
266
// create silk gerber from svg
267
SVG2gerber drillGerber;
268
int drillInvalidCount = drillGerber.convert(svgDrill, sketchWidget->boardLayers() == 2, "drill", SVG2gerber::ForDrill, svgSize * GraphicsUtils::StandardFritzingDPI);
272
QString drillFile = exportDir + "/" +
273
QFileInfo(filename).fileName().remove(FritzingSketchExtension)
275
QFile drillOut(drillFile);
276
if (!drillOut.open(QIODevice::WriteOnly | QIODevice::Text)) {
277
displayMessage(QObject::tr("drill file export failure (5)"), displayMessageBoxes);
281
QTextStream drillStream(&drillOut);
282
drillStream << drillGerber.getGerber();
286
return drillInvalidCount;
289
int GerberGenerator::doMask(LayerList maskLayerIDs, const QString &maskName, const QString & gerberSuffix, ItemBase * board, PCBSketchWidget * sketchWidget, const QString & filename, const QString & exportDir, bool displayMessageBoxes )
291
// don't want these in the mask laqyer
292
QList<ItemBase *> copperLogoItems;
293
foreach (QGraphicsItem * item, sketchWidget->items()) {
294
CopperLogoItem * logoItem = dynamic_cast<CopperLogoItem *>(item);
295
if (logoItem && logoItem->isVisible()) {
296
copperLogoItems.append(logoItem);
297
logoItem->setVisible(false);
303
QString svgMask = sketchWidget->renderToSVG(FSvgRenderer::printerScale(), maskLayerIDs, maskLayerIDs, true, imageSize, board, GraphicsUtils::StandardFritzingDPI, false, false, false, empty);
304
if (svgMask.isEmpty()) {
305
displayMessage(QObject::tr("mask file export failure (1)"), displayMessageBoxes);
309
foreach (ItemBase * logoItem, copperLogoItems) {
310
logoItem->setVisible(true);
314
// don't bother with file
318
QDomDocument domDocument;
322
bool result = domDocument.setContent(svgMask, &errorStr, &errorLine, &errorColumn);
324
displayMessage(QObject::tr("%1 file export failure (2)").arg(maskName), displayMessageBoxes);
328
QXmlStreamReader streamReader(svgMask);
329
QSizeF svgSize = FSvgRenderer::parseForWidthAndHeight(streamReader);
331
svgMask = clipToBoard(svgMask, board, maskName, SVG2gerber::ForMask);
332
if (svgMask.isEmpty()) {
333
displayMessage(QObject::tr("mask export failure"), displayMessageBoxes);
337
// create mask gerber from svg
338
SVG2gerber maskGerber;
339
int maskInvalidCount = maskGerber.convert(svgMask, sketchWidget->boardLayers() == 2, maskName, SVG2gerber::ForMask, svgSize * GraphicsUtils::StandardFritzingDPI);
341
QString maskFile = exportDir + "/" +
342
QFileInfo(filename).fileName().remove(FritzingSketchExtension)
344
QFile maskOut(maskFile);
345
if (!maskOut.open(QIODevice::WriteOnly | QIODevice::Text)) {
346
displayMessage(QObject::tr("mask file export failure (2)"), displayMessageBoxes);
350
QTextStream maskStream(&maskOut);
351
maskStream << maskGerber.getGerber();
355
return maskInvalidCount;
358
void GerberGenerator::displayMessage(const QString & message, bool displayMessageBoxes) {
359
// don't use QMessageBox if running conversion as a service
360
if (displayMessageBoxes) {
361
QMessageBox::warning(NULL, QObject::tr("Fritzing"), message);
365
DebugDialog::debug(message);
368
QString GerberGenerator::clipToBoard(QString svgString, ItemBase * board, const QString & layerName, SVG2gerber::ForWhy forWhy) {
369
QDomDocument domDocument1;
373
bool result = domDocument1.setContent(svgString, &errorStr, &errorLine, &errorColumn);
378
QDomDocument domDocument2;
379
domDocument2.setContent(svgString, &errorStr, &errorLine, &errorColumn);
381
bool anyConverted = false;
382
if (TextUtils::squashElement(domDocument1, "text", "", QRegExp())) {
386
// gerber can't handle ellipses that are rotated, so cull them all
387
if (TextUtils::squashElement(domDocument1, "ellipse", "", QRegExp())) {
391
if (TextUtils::squashElement(domDocument1, "rect", "rx", QRegExp())) {
395
if (TextUtils::squashElement(domDocument1, "rect", "ry", QRegExp())) {
399
// gerber can't handle paths with curves
400
if (TextUtils::squashElement(domDocument1, "path", "d", AaCc)) {
404
QVector <QDomElement> leaves1;
405
int transformCount1 = 0;
406
QDomElement e1 = domDocument1.documentElement();
407
TextUtils::collectLeaves(e1, transformCount1, leaves1);
409
QVector <QDomElement> leaves2;
410
int transformCount2 = 0;
411
QDomElement e2 = domDocument2.documentElement();
412
TextUtils::collectLeaves(e2, transformCount2, leaves2);
414
double res = GraphicsUtils::StandardFritzingDPI;
415
QRectF source = board->boundingRect();
416
int twidth = res * source.width() / FSvgRenderer::printerScale();
417
int theight = res * source.height() / FSvgRenderer::printerScale();
419
svgString = TextUtils::removeXMLEntities(domDocument1.toString());
420
QXmlStreamReader reader(svgString);
421
QSvgRenderer renderer(&reader);
422
bool anyClipped = false;
423
for (int i = 0; i < transformCount1; i++) {
424
QString n = QString::number(i);
425
QRectF bounds = renderer.boundsOnElement(n);
426
QMatrix m = renderer.matrixForElement(n);
427
QDomElement element = leaves1.at(i);
428
QString ms = element.attribute("transform");
430
m *= TextUtils::transformStringToMatrix(ms);
432
QRectF mBounds = m.mapRect(bounds);
433
if (mBounds.left() < 0 || mBounds.top() < 0 || bounds.right() > twidth || bounds.bottom() > theight) {
434
// element is outside of bounds, squash it so it will be clipped
435
element.setTagName("g");
436
anyClipped = anyConverted = true;
441
svgString = TextUtils::removeXMLEntities(domDocument1.toString());
445
for (int i = 0; i < transformCount1; i++) {
446
QDomElement element1 = leaves1.at(i);
447
if (element1.tagName() != "g") {
448
QDomElement element2 = leaves2.at(i);
449
element2.setTagName("g");
453
QString svg = TextUtils::removeXMLEntities(domDocument2.toString());
455
QSize imgSize(twidth, theight);
457
// expand the svg to fill the space of the image
458
QRegExp widthFinder("width=[^i]+in.");
459
int ix = widthFinder.indexIn(svg);
461
svg.replace(ix, widthFinder.cap(0).length(), QString("width=\"%1px\"").arg(twidth));
463
QRegExp heightFinder("height=[^i]+in.");
464
ix = heightFinder.indexIn(svg);
466
svg.replace(ix, heightFinder.cap(0).length(), QString("height=\"%1px\"").arg(theight));
469
QStringList exceptions;
470
exceptions << "none" << "";
471
QString toColor("#000000");
472
QByteArray svgByteArray;
473
SvgFileSplitter::changeColors(svg, toColor, exceptions, svgByteArray);
475
QImage image(imgSize, QImage::Format_RGB32);
476
image.fill(0xffffffff);
477
image.setDotsPerMeterX(res * GraphicsUtils::InchesPerMeter);
478
image.setDotsPerMeterY(res * GraphicsUtils::InchesPerMeter);
479
QRectF target(0, 0, twidth, theight);
481
QSvgRenderer renderer(svgByteArray);
483
painter.begin(&image);
484
renderer.render(&painter, target);
486
image.invertPixels(); // need white pixels on a black background for GroundPlaneGenerator
487
//image.save("output.png");
489
GroundPlaneGenerator gpg;
490
if (forWhy == SVG2gerber::ForOutline) {
491
int tinyWidth = source.width() / FSvgRenderer::printerScale();
492
int tinyHeight = source.height() / FSvgRenderer::printerScale();
493
QRectF tinyTarget(0, 0, tinyWidth, tinyHeight);
494
QImage tinyImage(tinyWidth, tinyHeight, QImage::Format_RGB32);
496
painter.begin(&tinyImage);
497
renderer.render(&painter, tinyTarget);
499
tinyImage.invertPixels(); // need white pixels on a black background for GroundPlaneGenerator
500
gpg.scanOutline(image, image.width(), image.height(), GraphicsUtils::StandardFritzingDPI / res, GraphicsUtils::StandardFritzingDPI, "#ffffff", layerName, false, 1, false, QSizeF(0, 0), 0);
503
gpg.scanImage(image, image.width(), image.height(), GraphicsUtils::StandardFritzingDPI / res, GraphicsUtils::StandardFritzingDPI, "#ffffff", layerName, false, 1, false, QSizeF(0, 0), 0);
506
if (gpg.newSVGs().count() > 0) {
508
TextUtils::mergeSvg(doc, svgString, "");
509
foreach (QString gsvg, gpg.newSVGs()) {
510
TextUtils::mergeSvg(doc, gsvg, "");
512
svgString = TextUtils::mergeSvgFinish(doc);