1 /*******************************************************************
2 
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2015 Fachhochschule Potsdam - http://fh-potsdam.de
5 
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.
10 
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.
15 
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/>.
18 
19 ********************************************************************
20 
21 $Revision: 6976 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-21 09:50:09 +0200 (So, 21. Apr 2013) $
24 
25 ********************************************************************/
26 
27 #include <QMessageBox>
28 #include <QFileDialog>
29 #include <QSvgRenderer>
30 #include <qmath.h>
31 
32 #include "gerbergenerator.h"
33 #include "../debugdialog.h"
34 #include "../fsvgrenderer.h"
35 #include "../sketch/pcbsketchwidget.h"
36 #include "../connectors/connectoritem.h"
37 #include "../connectors/svgidlayer.h"
38 #include "svgfilesplitter.h"
39 #include "groundplanegenerator.h"
40 #include "../utils/graphicsutils.h"
41 #include "../utils/textutils.h"
42 #include "../utils/folderutils.h"
43 #include "../version/version.h"
44 
45 static const QRegExp AaCc("[aAcCqQtTsS]");
46 static const QRegExp MFinder("([mM])\\s*([0-9.]*)[\\s,]*([0-9.]*)");
47 const QRegExp GerberGenerator::MultipleZs("z\\s*[^\\s]");
48 
49 const QString GerberGenerator::SilkTopSuffix = "_silkTop.gto";
50 const QString GerberGenerator::SilkBottomSuffix = "_silkBottom.gbo";
51 const QString GerberGenerator::CopperTopSuffix = "_copperTop.gtl";
52 const QString GerberGenerator::CopperBottomSuffix = "_copperBottom.gbl";
53 const QString GerberGenerator::MaskTopSuffix = "_maskTop.gts";
54 const QString GerberGenerator::MaskBottomSuffix = "_maskBottom.gbs";
55 const QString GerberGenerator::PasteMaskTopSuffix = "_pasteMaskTop.gtp";
56 const QString GerberGenerator::PasteMaskBottomSuffix = "_pasteMaskBottom.gbp";
57 const QString GerberGenerator::DrillSuffix = "_drill.txt";
58 const QString GerberGenerator::OutlineSuffix = "_contour.gm1";
59 const QString GerberGenerator::MagicBoardOutlineID = "boardoutline";
60 
61 const double GerberGenerator::MaskClearanceMils = 5;
62 
63 ////////////////////////////////////////////
64 
pixelsCollide(QImage * image1,QImage * image2,int x1,int y1,int x2,int y2)65 bool pixelsCollide(QImage * image1, QImage * image2, int x1, int y1, int x2, int y2) {
66 	for (int y = y1; y < y2; y++) {
67 		for (int x = x1; x < x2; x++) {
68 			QRgb p1 = image1->pixel(x, y);
69 			if (p1 == 0xffffffff) continue;
70 
71 			QRgb p2 = image2->pixel(x, y);
72 			if (p2 == 0xffffffff) continue;
73 
74 			//DebugDialog::debug(QString("p1:%1 p2:%2").arg(p1, 0, 16).arg(p2, 0, 16));
75 
76 			return true;
77 		}
78 	}
79 
80 	return false;
81 }
82 
83 ////////////////////////////////////////////
84 
exportToGerber(const QString & prefix,const QString & exportDir,ItemBase * board,PCBSketchWidget * sketchWidget,bool displayMessageBoxes)85 void GerberGenerator::exportToGerber(const QString & prefix, const QString & exportDir, ItemBase * board, PCBSketchWidget * sketchWidget, bool displayMessageBoxes)
86 {
87 	if (board == NULL) {
88         int boardCount;
89 		board = sketchWidget->findSelectedBoard(boardCount);
90 		if (boardCount == 0) {
91 			DebugDialog::debug("board not found");
92 			return;
93 		}
94 		if (board == NULL) {
95 			DebugDialog::debug("multiple boards found");
96 			return;
97 		}
98 	}
99 
100     exportPickAndPlace(prefix, exportDir, board, sketchWidget, displayMessageBoxes);
101 
102 	LayerList viewLayerIDs = ViewLayer::copperLayers(ViewLayer::NewBottom);
103 	int copperInvalidCount = doCopper(board, sketchWidget, viewLayerIDs, "Copper0", CopperBottomSuffix, prefix, exportDir, displayMessageBoxes);
104 
105     if (sketchWidget->boardLayers() == 2) {
106 		viewLayerIDs = ViewLayer::copperLayers(ViewLayer::NewTop);
107 		copperInvalidCount += doCopper(board, sketchWidget, viewLayerIDs, "Copper1", CopperTopSuffix, prefix, exportDir, displayMessageBoxes);
108 	}
109 
110 	LayerList maskLayerIDs = ViewLayer::maskLayers(ViewLayer::NewBottom);
111 	QString maskBottom, maskTop;
112 	int maskInvalidCount = doMask(maskLayerIDs, "Mask0", MaskBottomSuffix, board, sketchWidget, prefix, exportDir, displayMessageBoxes, maskBottom);
113 
114 	if (sketchWidget->boardLayers() == 2) {
115 		maskLayerIDs = ViewLayer::maskLayers(ViewLayer::NewTop);
116 		maskInvalidCount += doMask(maskLayerIDs, "Mask1", MaskTopSuffix, board, sketchWidget, prefix, exportDir, displayMessageBoxes, maskTop);
117 	}
118 
119 	maskLayerIDs = ViewLayer::maskLayers(ViewLayer::NewBottom);
120 	int pasteMaskInvalidCount = doPasteMask(maskLayerIDs, "PasteMask0", PasteMaskBottomSuffix, board, sketchWidget, prefix, exportDir, displayMessageBoxes);
121 
122 	if (sketchWidget->boardLayers() == 2) {
123 		maskLayerIDs = ViewLayer::maskLayers(ViewLayer::NewTop);
124 		pasteMaskInvalidCount += doPasteMask(maskLayerIDs, "PasteMask1", PasteMaskTopSuffix, board, sketchWidget, prefix, exportDir, displayMessageBoxes);
125 	}
126 
127     LayerList silkLayerIDs = ViewLayer::silkLayers(ViewLayer::NewTop);
128 	int silkInvalidCount = doSilk(silkLayerIDs, "Silk1", SilkTopSuffix, board, sketchWidget, prefix, exportDir, displayMessageBoxes, maskTop);
129     silkLayerIDs = ViewLayer::silkLayers(ViewLayer::NewBottom);
130 	silkInvalidCount += doSilk(silkLayerIDs, "Silk0", SilkBottomSuffix, board, sketchWidget, prefix, exportDir, displayMessageBoxes, maskBottom);
131 
132     // now do it for the outline/contour
133     LayerList outlineLayerIDs = ViewLayer::outlineLayers();
134     bool empty;
135     QString svgOutline = renderTo(outlineLayerIDs, board, sketchWidget, empty);
136     if (empty || svgOutline.isEmpty()) {
137         displayMessage(QObject::tr("outline is empty"), displayMessageBoxes);
138         return;
139     }
140 
141 	svgOutline = cleanOutline(svgOutline);
142     // at this point svgOutline must be a single element; a path element may contain cutouts
143     QMultiHash<long, ConnectorItem *> treatAsCircle;
144 	svgOutline = clipToBoard(svgOutline, board, "board", SVG2gerber::ForOutline, "", displayMessageBoxes, treatAsCircle);
145 	QSizeF svgSize = TextUtils::parseForWidthAndHeight(svgOutline);
146 
147     // create outline gerber from svg
148     SVG2gerber outlineGerber;
149 	int outlineInvalidCount = outlineGerber.convert(svgOutline, sketchWidget->boardLayers() == 2, "contour", SVG2gerber::ForOutline, svgSize * GraphicsUtils::StandardFritzingDPI);
150 
151 	//DebugDialog::debug(QString("outline output: %1").arg(outlineGerber.getGerber()));
152 	saveEnd("contour", exportDir, prefix, OutlineSuffix, displayMessageBoxes, outlineGerber);
153 
154 	doDrill(board, sketchWidget, prefix, exportDir, displayMessageBoxes);
155 
156 	if (outlineInvalidCount > 0 || silkInvalidCount > 0 || copperInvalidCount > 0 || maskInvalidCount || pasteMaskInvalidCount) {
157 		QString s;
158 		if (outlineInvalidCount > 0) s += QObject::tr("the board outline layer, ");
159 		if (silkInvalidCount > 0) s += QObject::tr("silkscreen layer(s), ");
160 		if (copperInvalidCount > 0) s += QObject::tr("copper layer(s), ");
161 		if (maskInvalidCount > 0) s += QObject::tr("mask layer(s), ");
162 		if (pasteMaskInvalidCount > 0) s += QObject::tr("paste mask layer(s), ");
163 		s.chop(2);
164 		displayMessage(QObject::tr("Unable to translate svg curves in %1").arg(s), displayMessageBoxes);
165 	}
166 
167 }
168 
doCopper(ItemBase * board,PCBSketchWidget * sketchWidget,LayerList & viewLayerIDs,const QString & copperName,const QString & copperSuffix,const QString & filename,const QString & exportDir,bool displayMessageBoxes)169 int GerberGenerator::doCopper(ItemBase * board, PCBSketchWidget * sketchWidget, LayerList & viewLayerIDs, const QString & copperName, const QString & copperSuffix, const QString & filename, const QString & exportDir, bool displayMessageBoxes)
170 {
171     bool empty;
172 	QString svg = renderTo(viewLayerIDs, board, sketchWidget, empty);
173 	if (empty || svg.isEmpty()) {
174 		displayMessage(QObject::tr("%1 layer export is empty.").arg(copperName), displayMessageBoxes);
175 		return 0;
176 	}
177 
178     QMultiHash<long, ConnectorItem *> treatAsCircle;
179     foreach (QGraphicsItem * item, sketchWidget->scene()->collidingItems(board)) {
180         ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
181         if (connectorItem == NULL) continue;
182         if (!connectorItem->isPath()) continue;
183         if (connectorItem->radius() == 0) continue;
184 
185         treatAsCircle.insert(connectorItem->attachedToID(), connectorItem);
186     }
187 
188 	QSizeF svgSize = TextUtils::parseForWidthAndHeight(svg);
189 
190 	svg = clipToBoard(svg, board, copperName, SVG2gerber::ForCopper, "", displayMessageBoxes, treatAsCircle);
191 	if (svg.isEmpty()) {
192 		displayMessage(QObject::tr("%1 layer export is empty (case 2).").arg(copperName), displayMessageBoxes);
193 		return 0;
194 	}
195 
196 	return doEnd(svg, sketchWidget->boardLayers(), copperName, SVG2gerber::ForCopper, svgSize * GraphicsUtils::StandardFritzingDPI, exportDir, filename, copperSuffix, displayMessageBoxes);
197 }
198 
199 
doSilk(LayerList silkLayerIDs,const QString & silkName,const QString & gerberSuffix,ItemBase * board,PCBSketchWidget * sketchWidget,const QString & filename,const QString & exportDir,bool displayMessageBoxes,const QString & clipString)200 int GerberGenerator::doSilk(LayerList silkLayerIDs, const QString & silkName, const QString & gerberSuffix, ItemBase * board, PCBSketchWidget * sketchWidget, const QString & filename, const QString & exportDir, bool displayMessageBoxes, const QString & clipString)
201 {
202 
203 	bool empty;
204 	QString svgSilk = renderTo(silkLayerIDs, board, sketchWidget, empty);
205     if (empty || svgSilk.isEmpty()) {
206         if (silkLayerIDs.contains(ViewLayer::Silkscreen1)) {
207 		    displayMessage(QObject::tr("silk layer %1 export is empty").arg(silkName), displayMessageBoxes);
208         }
209         return 0;
210     }
211 
212 	//QFile f(silkName + "original.svg");
213 	//f.open(QFile::WriteOnly);
214 	//QTextStream fs(&f);
215 	//fs << svgSilk;
216 	//f.close();
217 
218 	QSizeF svgSize = TextUtils::parseForWidthAndHeight(svgSilk);
219 
220     QMultiHash<long, ConnectorItem *> treatAsCircle;
221 	svgSilk = clipToBoard(svgSilk, board, silkName, SVG2gerber::ForSilk, clipString, displayMessageBoxes, treatAsCircle);
222 	if (svgSilk.isEmpty()) {
223 		displayMessage(QObject::tr("silk export failure"), displayMessageBoxes);
224 		return 0;
225 	}
226 
227 	//QFile f2(silkName + "clipped.svg");
228 	//f2.open(QFile::WriteOnly);
229 	//QTextStream fs2(&f2);
230 	//fs2 << svgSilk;
231 	//f2.close();
232 
233 	return doEnd(svgSilk, sketchWidget->boardLayers(), silkName, SVG2gerber::ForSilk, svgSize * GraphicsUtils::StandardFritzingDPI, exportDir, filename, gerberSuffix, displayMessageBoxes);
234 }
235 
236 
doDrill(ItemBase * board,PCBSketchWidget * sketchWidget,const QString & filename,const QString & exportDir,bool displayMessageBoxes)237 int GerberGenerator::doDrill(ItemBase * board, PCBSketchWidget * sketchWidget, const QString & filename, const QString & exportDir, bool displayMessageBoxes)
238 {
239     LayerList drillLayerIDs;
240     drillLayerIDs << ViewLayer::drillLayers();
241 
242 	bool empty;
243 	QString svgDrill = renderTo(drillLayerIDs, board, sketchWidget, empty);
244     if (empty || svgDrill.isEmpty()) {
245 		displayMessage(QObject::tr("exported drill file is empty"), displayMessageBoxes);
246         return 0;
247     }
248 
249 	QSizeF svgSize = TextUtils::parseForWidthAndHeight(svgDrill);
250     QMultiHash<long, ConnectorItem *> treatAsCircle;
251     foreach (QGraphicsItem * item, sketchWidget->scene()->collidingItems(board)) {
252         ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
253         if (connectorItem == NULL) continue;
254         if (!connectorItem->isPath()) continue;
255         if (connectorItem->radius() == 0) continue;
256 
257         treatAsCircle.insert(connectorItem->attachedToID(), connectorItem);
258     }
259 
260 	svgDrill = clipToBoard(svgDrill, board, "Copper0", SVG2gerber::ForDrill, "", displayMessageBoxes, treatAsCircle);
261 	if (svgDrill.isEmpty()) {
262 		displayMessage(QObject::tr("drill export failure"), displayMessageBoxes);
263 		return 0;
264 	}
265 
266 	return doEnd(svgDrill, sketchWidget->boardLayers(), "drill", SVG2gerber::ForDrill, svgSize * GraphicsUtils::StandardFritzingDPI, exportDir, filename, DrillSuffix, displayMessageBoxes);
267 }
268 
doMask(LayerList maskLayerIDs,const QString & maskName,const QString & gerberSuffix,ItemBase * board,PCBSketchWidget * sketchWidget,const QString & filename,const QString & exportDir,bool displayMessageBoxes,QString & clipString)269 int GerberGenerator::doMask(LayerList maskLayerIDs, const QString &maskName, const QString & gerberSuffix, ItemBase * board, PCBSketchWidget * sketchWidget, const QString & filename, const QString & exportDir, bool displayMessageBoxes, QString & clipString)
270 {
271 	// don't want these in the mask laqyer
272 	QList<ItemBase *> copperLogoItems;
273 	sketchWidget->hideCopperLogoItems(copperLogoItems);
274 
275 	bool empty;
276 	QString svgMask = renderTo(maskLayerIDs, board, sketchWidget, empty);
277 	sketchWidget->restoreCopperLogoItems(copperLogoItems);
278 
279     if (empty || svgMask.isEmpty()) {
280 		displayMessage(QObject::tr("exported mask layer %1 is empty").arg(maskName), displayMessageBoxes);
281         return 0;
282     }
283 
284 	svgMask = TextUtils::expandAndFill(svgMask, "black", MaskClearanceMils * 2);
285 	if (svgMask.isEmpty()) {
286 		displayMessage(QObject::tr("%1 mask export failure (2)").arg(maskName), displayMessageBoxes);
287 		return 0;
288 	}
289 
290 	QSizeF svgSize = TextUtils::parseForWidthAndHeight(svgMask);
291     QMultiHash<long, ConnectorItem *> treatAsCircle;
292 	svgMask = clipToBoard(svgMask, board, maskName, SVG2gerber::ForCopper, "", displayMessageBoxes, treatAsCircle);
293 	if (svgMask.isEmpty()) {
294 		displayMessage(QObject::tr("mask export failure"), displayMessageBoxes);
295 		return 0;
296 	}
297 
298 	clipString = svgMask;
299 
300 	return doEnd(svgMask, sketchWidget->boardLayers(), maskName, SVG2gerber::ForCopper, svgSize * GraphicsUtils::StandardFritzingDPI, exportDir, filename, gerberSuffix, displayMessageBoxes);
301 }
302 
doPasteMask(LayerList maskLayerIDs,const QString & maskName,const QString & gerberSuffix,ItemBase * board,PCBSketchWidget * sketchWidget,const QString & filename,const QString & exportDir,bool displayMessageBoxes)303 int GerberGenerator::doPasteMask(LayerList maskLayerIDs, const QString &maskName, const QString & gerberSuffix, ItemBase * board, PCBSketchWidget * sketchWidget, const QString & filename, const QString & exportDir, bool displayMessageBoxes)
304 {
305 	// don't want these in the mask laqyer
306 	QList<ItemBase *> copperLogoItems;
307 	sketchWidget->hideCopperLogoItems(copperLogoItems);
308 	QList<ItemBase *> holes;
309 	sketchWidget->hideHoles(holes);
310 
311 	bool empty;
312 	QString svgMask = renderTo(maskLayerIDs, board, sketchWidget, empty);
313 	sketchWidget->restoreCopperLogoItems(copperLogoItems);
314 	sketchWidget->restoreCopperLogoItems(holes);
315 
316     if (empty || svgMask.isEmpty()) {
317 		displayMessage(QObject::tr("exported paste mask layer is empty"), displayMessageBoxes);
318         return 0;
319     }
320 
321     svgMask = sketchWidget->makePasteMask(svgMask, board, GraphicsUtils::StandardFritzingDPI, maskLayerIDs);
322     if (svgMask.isEmpty()) return 0;
323 
324 	QSizeF svgSize = TextUtils::parseForWidthAndHeight(svgMask);
325     QMultiHash<long, ConnectorItem *> treatAsCircle;
326 	svgMask = clipToBoard(svgMask, board, maskName, SVG2gerber::ForCopper, "", displayMessageBoxes, treatAsCircle);
327 	if (svgMask.isEmpty()) {
328 		displayMessage(QObject::tr("mask export failure"), displayMessageBoxes);
329 		return 0;
330 	}
331 
332 	return doEnd(svgMask, sketchWidget->boardLayers(), maskName, SVG2gerber::ForCopper, svgSize * GraphicsUtils::StandardFritzingDPI, exportDir, filename, gerberSuffix, displayMessageBoxes);
333 }
334 
doEnd(const QString & svg,int boardLayers,const QString & layerName,SVG2gerber::ForWhy forWhy,QSizeF svgSize,const QString & exportDir,const QString & prefix,const QString & suffix,bool displayMessageBoxes)335 int GerberGenerator::doEnd(const QString & svg, int boardLayers, const QString & layerName, SVG2gerber::ForWhy forWhy, QSizeF svgSize,
336 							const QString & exportDir, const QString & prefix, const QString & suffix, bool displayMessageBoxes)
337 {
338     // create mask gerber from svg
339     SVG2gerber gerber;
340 	int invalidCount = gerber.convert(svg, boardLayers == 2, layerName, forWhy, svgSize);
341 
342 	saveEnd(layerName, exportDir, prefix, suffix, displayMessageBoxes, gerber);
343 
344 	return invalidCount;
345 }
346 
saveEnd(const QString & layerName,const QString & exportDir,const QString & prefix,const QString & suffix,bool displayMessageBoxes,SVG2gerber & gerber)347 bool GerberGenerator::saveEnd(const QString & layerName, const QString & exportDir, const QString & prefix, const QString & suffix, bool displayMessageBoxes, SVG2gerber & gerber)
348 {
349 
350     QString outname = exportDir + "/" +  prefix + suffix;
351     QFile out(outname);
352 	if (!out.open(QIODevice::WriteOnly | QIODevice::Text)) {
353 		displayMessage(QObject::tr("%1 layer: unable to save to '%2'").arg(layerName).arg(outname), displayMessageBoxes);
354 		return false;
355 	}
356 
357     QTextStream stream(&out);
358     stream << gerber.getGerber();
359 	stream.flush();
360 	out.close();
361 	return true;
362 
363 }
364 
displayMessage(const QString & message,bool displayMessageBoxes)365 void GerberGenerator::displayMessage(const QString & message, bool displayMessageBoxes) {
366 	// don't use QMessageBox if running conversion as a service
367 	if (displayMessageBoxes) {
368 		QMessageBox::warning(NULL, QObject::tr("Fritzing"), message);
369 		return;
370 	}
371 
372 	DebugDialog::debug(message);
373 }
374 
clipToBoard(QString svgString,ItemBase * board,const QString & layerName,SVG2gerber::ForWhy forWhy,const QString & clipString,bool displayMessageBoxes,QMultiHash<long,ConnectorItem * > & treatAsCircle)375 QString GerberGenerator::clipToBoard(QString svgString, ItemBase * board, const QString & layerName, SVG2gerber::ForWhy forWhy, const QString & clipString, bool displayMessageBoxes, QMultiHash<long, ConnectorItem *> & treatAsCircle) {
376 	QRectF source = board->sceneBoundingRect();
377 	source.moveTo(0, 0);
378 	return clipToBoard(svgString, source, layerName, forWhy, clipString, displayMessageBoxes, treatAsCircle);
379 }
380 
clipToBoard(QString svgString,QRectF & boardRect,const QString & layerName,SVG2gerber::ForWhy forWhy,const QString & clipString,bool displayMessageBoxes,QMultiHash<long,ConnectorItem * > & treatAsCircle)381 QString GerberGenerator::clipToBoard(QString svgString, QRectF & boardRect, const QString & layerName, SVG2gerber::ForWhy forWhy, const QString & clipString, bool displayMessageBoxes, QMultiHash<long, ConnectorItem *> & treatAsCircle) {
382 	// document 1 will contain svg that is easy to convert to gerber
383 	QDomDocument domDocument1;
384 	QString errorStr;
385 	int errorLine;
386 	int errorColumn;
387 	bool result = domDocument1.setContent(svgString, &errorStr, &errorLine, &errorColumn);
388 	if (!result) {
389 		return "";
390 	}
391 
392 	QDomElement root1 = domDocument1.documentElement();
393 	if (root1.firstChildElement().isNull()) {
394 		return "";
395 	}
396 
397     if (forWhy != SVG2gerber::ForDrill) {
398         QDomNodeList nodeList = root1.elementsByTagName("circle");
399         QList<QDomElement> justHoles;
400         for (int i = 0; i < nodeList.count(); i++) {
401             QDomElement circle = nodeList.at(i).toElement();
402             if (circle.attribute("id").contains(FSvgRenderer::NonConnectorName)) {
403                 double sw = circle.attribute("stroke-width").toDouble();
404                 if (sw == 0) {
405                     justHoles << circle;
406                 }
407             }
408         }
409         foreach (QDomElement circle, justHoles) {
410             circle.setTagName("g");
411         }
412     }
413 
414     handleDonuts(root1, treatAsCircle);
415 
416     bool multipleContours = false;
417     if (forWhy == SVG2gerber::ForOutline) {
418         multipleContours = dealWithMultipleContours(root1, displayMessageBoxes);
419     }
420 
421 	// document 2 will contain svg that must be rasterized for gerber conversion
422 	QDomDocument domDocument2 = domDocument1.cloneNode(true).toDocument();
423 
424 	bool anyConverted = false;
425     if (TextUtils::squashElement(domDocument1, "text", "", QRegExp())) {
426         anyConverted = true;
427 	}
428 
429 	// gerber can't handle ellipses that are rotated, so cull them all
430     if (TextUtils::squashElement(domDocument1, "ellipse", "", QRegExp())) {
431 		anyConverted = true;
432     }
433 
434     if (TextUtils::squashElement(domDocument1, "rect", "rx", QRegExp())) {
435 		anyConverted = true;
436     }
437 
438     if (TextUtils::squashElement(domDocument1, "rect", "ry", QRegExp())) {
439 		anyConverted = true;
440     }
441 
442 	// gerber can't handle paths with curves
443     if (TextUtils::squashElement(domDocument1, "path", "d", AaCc)) {
444 		anyConverted = true;
445     }
446 
447 	// gerber can't handle multiple subpaths if there are intersections
448     if (TextUtils::squashElement(domDocument1, "path", "d", MultipleZs)) {
449 		anyConverted = true;
450     }
451 
452     if (TextUtils::squashElement(domDocument1, "image", "", QRegExp())) {
453 		anyConverted = true;
454     }
455 
456     // can't handle scaled paths very well. There is probably a deeper bug that needs to be chased down.
457     // is this only necessary for contour view?
458     QDomNodeList nodeList = root1.elementsByTagName("path");
459     for (int i = 0; i < nodeList.count(); i++) {
460         QDomNode parent = nodeList.at(i);
461         while (!parent.isNull()) {
462             QString transformString = parent.toElement().attribute("transform");
463             if (!transformString.isNull()) {
464                 QMatrix matrix = TextUtils::transformStringToMatrix(transformString);
465                 QTransform transform(matrix);
466                 if (transform.isScaling()) {
467                     nodeList.at(i).toElement().setTagName("g");
468                     anyConverted = true;
469                     break;
470                 }
471 
472             }
473 
474             parent = parent.parentNode();
475         }
476     }
477 
478 	QVector <QDomElement> leaves1;
479 	int transformCount1 = 0;
480     QDomElement e1 = domDocument1.documentElement();
481     TextUtils::collectLeaves(e1, transformCount1, leaves1);
482 
483 	QVector <QDomElement> leaves2;
484 	int transformCount2 = 0;
485     QDomElement e2 = domDocument2.documentElement();
486     TextUtils::collectLeaves(e2, transformCount2, leaves2);
487 
488 	double res = GraphicsUtils::StandardFritzingDPI;
489 	// convert from pixel dpi to StandardFritzingDPI
490 	QRectF sourceRes(boardRect.left() * res / GraphicsUtils::SVGDPI, boardRect.top() * res / GraphicsUtils::SVGDPI,
491 					 boardRect.width() * res / GraphicsUtils::SVGDPI, boardRect.height() * res / GraphicsUtils::SVGDPI);
492 	int twidth = sourceRes.width();
493 	int theight = sourceRes.height();
494 	QSize imgSize(twidth + 2, theight + 2);
495 	QRectF target(0, 0, twidth, theight);
496 
497 	QImage * clipImage = NULL;
498 	if (!clipString.isEmpty()) {
499 		clipImage = new QImage(imgSize, QImage::Format_Mono);
500 		clipImage->fill(0xffffffff);
501 		clipImage->setDotsPerMeterX(res * GraphicsUtils::InchesPerMeter);
502 		clipImage->setDotsPerMeterY(res * GraphicsUtils::InchesPerMeter);
503 
504 		QXmlStreamReader reader(clipString);
505 		QSvgRenderer renderer(&reader);
506 		QPainter painter;
507 		painter.begin(clipImage);
508 		renderer.render(&painter, target);
509 		painter.end();
510 
511 #ifndef QT_NO_DEBUG
512         clipImage->save(FolderUtils::getUserDataStorePath("") + "/clip.png");
513 #endif
514 
515 	}
516 
517 	svgString = TextUtils::removeXMLEntities(domDocument1.toString());
518 
519     QList<QDomElement> possibleHoles;
520 	QXmlStreamReader reader(svgString);
521 	QSvgRenderer renderer(&reader);
522 	bool anyClipped = false;
523     if (forWhy != SVG2gerber::ForOutline) {
524 	    for (int i = 0; i < transformCount1; i++) {
525 		    QString n = QString::number(i);
526 		    QRectF bounds = renderer.boundsOnElement(n);
527 		    QMatrix m = renderer.matrixForElement(n);
528 		    QDomElement element = leaves1.at(i);
529 		    QRectF mBounds = m.mapRect(bounds);
530 		    if (mBounds.left() < sourceRes.left() - 0.1|| mBounds.top() < sourceRes.top() - 0.1 || mBounds.right() > sourceRes.right() + 0.1 || mBounds.bottom() > sourceRes.bottom() + 0.1) {
531 			    if (element.tagName() == "circle") {
532                     possibleHoles.append(element);
533                 }
534                 // element is outside of bounds--squash it so it will be clipped
535 			    // we don't care if the board shape is irregular
536 			    // since anything printed between the shape and the bounding rectangle
537 			    // will be physically clipped when the board is cut out
538 			    element.setTagName("g");
539 			    anyClipped = anyConverted = true;
540 		    }
541 	    }
542     }
543 
544 
545     if (possibleHoles.count() > 0) {
546         QList<QDomElement> newHoles;
547         int ix = 0;
548         foreach (QDomElement element, possibleHoles) {
549             QDomElement newElement = element.cloneNode(false).toElement();
550             double radius = element.attribute("r").toDouble();
551             double sw = element.attribute("stroke-width").toDouble();
552             element.parentNode().insertAfter(newElement, element);
553             newElement.setAttribute("id", QString("__%1__").arg(ix++));
554             newElement.setAttribute("stroke-width", 0);
555             newElement.setAttribute("r", QString::number(radius - (sw / 2)));
556             newElement.setTagName("circle");
557             newHoles.append(newElement);
558         }
559 
560 	    QSvgRenderer renderer(domDocument1.toByteArray());
561         for (int i = newHoles.count() - 1; i >= 0; i--) {
562             QString id = QString("__%1__").arg(i);
563 		    QRectF bounds = renderer.boundsOnElement(id);
564 		    QMatrix m = renderer.matrixForElement(id);
565 		    QDomElement newElement = newHoles.at(i);
566 		    QRectF mBounds = m.mapRect(bounds);
567 		    if (mBounds.left() < sourceRes.left() - 0.1 || mBounds.top() < sourceRes.top() - 0.1 || mBounds.right() > sourceRes.right() + 0.1 || mBounds.bottom() > sourceRes.bottom() + 0.1) {
568                 // hole is still clipped
569                 newHoles.removeAt(i);
570                 newElement.parentNode().removeChild(newElement);
571             }
572             else {
573                 // enlarge it a little due to aliasing when the clipped portion is converted to raster and back
574                 double radius = newElement.attribute("r").toDouble();
575                 radius += 4;
576                 newElement.setAttribute("r", QString::number(radius));
577                 newElement.setAttribute("stroke-width", 2);
578             }
579         }
580     }
581 
582 	if (clipImage) {
583 		QImage another(imgSize, QImage::Format_Mono);
584 		another.fill(0xffffffff);
585 		another.setDotsPerMeterX(res * GraphicsUtils::InchesPerMeter);
586 		another.setDotsPerMeterY(res * GraphicsUtils::InchesPerMeter);
587 
588 		svgString = TextUtils::removeXMLEntities(domDocument1.toString());
589 		QXmlStreamReader reader(svgString);
590 		QSvgRenderer renderer(&reader);
591 		QPainter painter;
592 		painter.begin(&another);
593 		renderer.render(&painter, target);
594 		painter.end();
595 
596 		for (int i = 0; i < transformCount1; i++) {
597 			QDomElement element = leaves1.at(i);
598 			if (element.tagName().compare("g") == 0) {
599 				// element is already converted to raster space, we'll clip it later
600 				continue;
601 			}
602 
603 			QString n = QString::number(i);
604 			QRectF bounds = renderer.boundsOnElement(n);
605 			QMatrix m = renderer.matrixForElement(n);
606 			QRectF mBounds = m.mapRect(bounds);
607 
608 			int x1 = qFloor(qMax(0.0, mBounds.left() - sourceRes.left()));          // atmel compiler fails without cast
609 			int x2 = qCeil(qMin(sourceRes.width(), mBounds.right() - sourceRes.left()));
610 			int y1 = qFloor(qMax(0.0, mBounds.top() - sourceRes.top()));            // atmel compiler fails without cast
611 			int y2 = qCeil(qMin(sourceRes.height(), mBounds.bottom() - sourceRes.top()));
612 
613 			if (pixelsCollide(&another, clipImage, x1, y1, x2, y2)) {
614 				element.setTagName("g");
615 				anyClipped = anyConverted = true;
616 			}
617 		}
618 	}
619 
620 	if (anyClipped) {
621 		// svg has been changed by clipping process so get the string again
622 		svgString = TextUtils::removeXMLEntities(domDocument1.toString());
623 	}
624 
625     if (anyConverted) {
626 		for (int i = 0; i < transformCount1; i++) {
627 			QDomElement element1 = leaves1.at(i);
628 			if (element1.tagName().compare("g") != 0) {
629 				// document 1 element svg can be directly converted to gerber
630 				// so remove it from document 2
631 				QDomElement element2 = leaves2.at(i);
632 				element2.setTagName("g");
633 			}
634 		}
635 
636 
637 		// expand the svg to fill the space of the image
638 		QDomElement root2 = domDocument2.documentElement();
639 		root2.setAttribute("width", QString("%1px").arg(twidth));
640 		root2.setAttribute("height", QString("%1px").arg(theight));
641 		if (boardRect.x() != 0 || boardRect.y() != 0) {
642 			QString viewBox = root2.attribute("viewBox");
643 			QStringList coords = viewBox.split(" ", QString::SkipEmptyParts);
644 			coords[0] = QString::number(sourceRes.left());
645 			coords[1] = QString::number(sourceRes.top());
646 			root2.setAttribute("viewBox", coords.join(" "));
647 		}
648 
649 		QStringList exceptions;
650 		exceptions << "none" << "";
651 		QString toColor("#000000");
652 		SvgFileSplitter::changeColors(root2, toColor, exceptions);
653 
654         QImage image(imgSize, QImage::Format_Mono);
655 		image.setDotsPerMeterX(res * GraphicsUtils::InchesPerMeter);
656 		image.setDotsPerMeterY(res * GraphicsUtils::InchesPerMeter);
657 
658         if (forWhy == SVG2gerber::ForOutline) {
659             QDomNodeList paths = root2.elementsByTagName("path");
660             if (paths.count() == 0) {
661                 // some non-path element makes up the outline
662                 mergeOutlineElement(image, target, res, domDocument2, svgString, 0, layerName);
663             }
664             else {
665                 for (int p = 0; p < paths.count(); p++) {
666                     QDomElement path = paths.at(p).toElement();
667                     path.setTagName("g");
668                 }
669                 for (int p = 0; p < paths.count(); p++) {
670                     QDomElement path = paths.at(p).toElement();
671                     path.setTagName("path");
672                     if (p > 0) {
673                         paths.at(p - 1).toElement().setTagName("g");
674                     }
675                     mergeOutlineElement(image, target, res, domDocument2, svgString, p, layerName);
676                 }
677             }
678 		}
679         else {
680 		    image.fill(0xffffffff);
681 		    QByteArray svg = TextUtils::removeXMLEntities(domDocument2.toString()).toUtf8();
682 		    QSvgRenderer renderer(svg);
683 		    QPainter painter;
684 		    painter.begin(&image);
685 		    renderer.render(&painter, target);
686 		    painter.end();
687 		    image.invertPixels();				// need white pixels on a black background for GroundPlaneGenerator
688 
689     #ifndef QT_NO_DEBUG
690 		    image.save(FolderUtils::getUserDataStorePath("") + "/preclip_output.png");
691     #endif
692 
693 		    if (clipImage != NULL) {
694 			    // can this be done with a single blt using composition mode
695 			    // if not, grab a scanline instead of testing every pixel
696 			    for (int y = 0; y < theight; y++) {
697 				    for (int x = 0; x < twidth; x++) {
698 					    if (clipImage->pixel(x, y) != 0xffffffff) {
699 						    image.setPixel(x, y, 0);
700 					    }
701 				    }
702 			    }
703 		    }
704 
705     #ifndef QT_NO_DEBUG
706 		    image.save(FolderUtils::getUserDataStorePath("") + "/output.png");
707     #endif
708 
709             QString path = makePath(image, res / GraphicsUtils::StandardFritzingDPI, "#000000");
710             svgString.replace("</svg>", path + "</svg>");
711 
712             /*
713 
714 		    GroundPlaneGenerator gpg;
715 		    gpg.setLayerName(layerName);
716 		    gpg.setMinRunSize(1, 1);
717 			gpg.scanImage(image, image.width(), image.height(), GraphicsUtils::StandardFritzingDPI / res, GraphicsUtils::StandardFritzingDPI, "#000000", false, false, QSizeF(0, 0), 0, sourceRes.topLeft());
718 		    if (gpg.newSVGs().count() > 0) {
719                 svgString = gpg.mergeSVGs(svgString, "");
720 		    }
721 
722             */
723 		}
724 	}
725 
726 	if (clipImage) delete clipImage;
727 
728     return QString(svgString);
729 }
730 
cleanOutline(const QString & outlineSvg)731 QString GerberGenerator::cleanOutline(const QString & outlineSvg)
732 {
733 	QDomDocument doc;
734 	doc.setContent(outlineSvg);
735 	QList<QDomElement> leaves;
736     QDomElement root = doc.documentElement();
737     TextUtils::collectLeaves(root, leaves);
738 	QDomNodeList textNodes = root.elementsByTagName("text");
739 	for (int t = 0; t < textNodes.count(); t++) {
740 		leaves << textNodes.at(t).toElement();
741 	}
742 
743 	if (leaves.count() == 0) return "";
744 	if (leaves.count() == 1) return outlineSvg;
745 
746 	if (leaves.count() > 1) {
747 		for (int i = 0; i < leaves.count(); i++) {
748 			QDomElement leaf = leaves.at(i);
749 			if (leaf.attribute("id", "").compare(MagicBoardOutlineID) == 0) {
750 				for (int j = 0; j < leaves.count(); j++) {
751 					if (i != j) {
752 						QDomElement jleaf = leaves.at(j);
753 						jleaf.parentNode().removeChild(jleaf);
754 					}
755 				}
756 
757 				return doc.toString();
758 			}
759 		}
760 	}
761 
762 	if (leaves.count() == 0) return "";
763 
764 	return outlineSvg;
765 }
766 
mergeOutlineElement(QImage & image,QRectF & target,double res,QDomDocument & document,QString & svgString,int ix,const QString & layerName)767 void GerberGenerator::mergeOutlineElement(QImage & image, QRectF & target, double res, QDomDocument & document, QString & svgString, int ix, const QString & layerName) {
768 
769     image.fill(0xffffffff);
770 	QByteArray svg = TextUtils::removeXMLEntities(document.toString()).toUtf8();
771 
772 	QSvgRenderer renderer(svg);
773 	QPainter painter;
774 	painter.begin(&image);
775 	renderer.render(&painter, target);
776 	painter.end();
777 	image.invertPixels();				// need white pixels on a black background for GroundPlaneGenerator
778 
779     #ifndef QT_NO_DEBUG
780 		image.save(QString("%2/output%1.png").arg(ix).arg(FolderUtils::getUserDataStorePath("")));
781     #else
782         Q_UNUSED(ix);
783     #endif
784 
785 	GroundPlaneGenerator gpg;
786 	gpg.setLayerName(layerName);
787 	gpg.setMinRunSize(1, 1);
788     gpg.scanOutline(image, image.width(), image.height(), GraphicsUtils::StandardFritzingDPI / res, GraphicsUtils::StandardFritzingDPI, "#000000", false, false, QSizeF(0, 0), 0);
789 	if (gpg.newSVGs().count() > 0) {
790         svgString = gpg.mergeSVGs(svgString, "");
791 	}
792 }
793 
makePath(QImage & image,double unit,const QString & colorString)794 QString GerberGenerator::makePath(QImage & image, double unit, const QString & colorString)
795 {
796     double halfUnit = unit / 2;
797     QString paths;
798     int lineCount = 0;
799     for (int y = 0; y < image.height(); y++) {
800 		bool inWhite = false;
801 		int whiteStart = 0;
802 		for (int x = 0; x < image.width(); x++) {
803 			QRgb current = image.pixel(x, y);
804 			if (inWhite) {
805 				if (current == 0xffffffff) {
806 					// another white pixel, keep moving
807 					continue;
808 				}
809 
810 				// got black: close up this segment;
811 				inWhite = false;
812                 paths += QString("M%1,%2L%3,%2 ").arg(whiteStart + halfUnit).arg(y + halfUnit).arg(x - 1 + halfUnit);
813                 if (++lineCount == 10) {
814                     lineCount = 0;
815                     paths += "\n";
816                 }
817 			}
818 			else {
819 				if (current != 0xffffffff) {
820 					// another black pixel, keep moving
821 					continue;
822 				}
823 
824 				inWhite = true;
825 				whiteStart = x;
826 			}
827 		}
828 	}
829 
830     QString path = QString("<path fill='none' stroke='%1' stroke-width='%2' stroke-linecap='square' d='").arg(colorString).arg(unit);
831     return path + paths + "' />\n";
832 }
833 
dealWithMultipleContours(QDomElement & root,bool displayMessageBoxes)834 bool GerberGenerator::dealWithMultipleContours(QDomElement & root, bool displayMessageBoxes) {
835     bool multipleContours = false;
836     bool contoursOK = true;
837 
838     // split path into multiple contours
839     QDomNodeList paths = root.elementsByTagName("path");
840     // should only be one
841     for (int p = 0; p < paths.count() && contoursOK; p++) {
842         QDomElement path = paths.at(p).toElement();
843         QString originalPath = path.attribute("d", "").trimmed();
844         if (MultipleZs.indexIn(originalPath) < 0) continue;
845 
846         multipleContours = true;
847         QStringList subpaths = path.attribute("d").split("z", QString::SkipEmptyParts);
848         foreach (QString subpath, subpaths) {
849             if (!subpath.trimmed().startsWith("m", Qt::CaseInsensitive)) {
850                 contoursOK = false;
851                 break;
852             }
853         }
854     }
855 
856     if (!multipleContours) return false;
857 
858     if (!contoursOK) {
859         QString msg =
860             QObject::tr("Fritzing is unable to process the cutouts in this custom PCB shape. ") +
861             QObject::tr("You may need to reload the shape SVG. ") +
862             QObject::tr("Fritzing requires that you make cutouts using a shape 'subtraction' or 'difference' operation in your vector graphics editor.");
863         displayMessage(msg, displayMessageBoxes);
864         return false;
865     }
866 
867     for (int p = 0; p < paths.count(); p++) {
868         QDomElement path = paths.at(p).toElement();
869         QString originalPath = path.attribute("d", "").trimmed();
870         if (MultipleZs.indexIn(originalPath) >= 0) {
871             QStringList subpaths = path.attribute("d").split("z", QString::SkipEmptyParts);
872             QString priorM;
873             MFinder.indexIn(subpaths.at(0).trimmed());
874             priorM += MFinder.cap(1) + MFinder.cap(2) + "," + MFinder.cap(3) + " ";
875             for (int i = 1; i < subpaths.count(); i++) {
876                 QDomElement newPath = path.cloneNode(true).toElement();
877                 QString z = ((i < subpaths.count() - 1) || originalPath.endsWith("z", Qt::CaseInsensitive)) ? "z" : "";
878                 QString d = subpaths.at(i).trimmed() + z;
879                 MFinder.indexIn(d);
880                 if (d.startsWith("m", Qt::CaseSensitive)) {
881                     d = priorM + d;
882                 }
883                 priorM += MFinder.cap(1) + MFinder.cap(2) + "," + MFinder.cap(3) + " ";
884                 newPath.setAttribute("d",  d);
885                 path.parentNode().appendChild(newPath);
886             }
887             path.setAttribute("d", subpaths.at(0) + "z");
888         }
889     }
890 
891     return true;
892 }
893 
exportPickAndPlace(const QString & prefix,const QString & exportDir,ItemBase * board,PCBSketchWidget * sketchWidget,bool displayMessageBoxes)894 void GerberGenerator::exportPickAndPlace(const QString & prefix, const QString & exportDir, ItemBase * board, PCBSketchWidget * sketchWidget, bool displayMessageBoxes)
895 {
896     QPointF bottomLeft = board->sceneBoundingRect().bottomLeft();
897     QSet<ItemBase *> itemBases;
898     foreach (QGraphicsItem * item, sketchWidget->scene()->collidingItems(board)) {
899         ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
900         if (itemBase == NULL) continue;
901         if (itemBase == board) continue;
902         if (itemBase->itemType() == ModelPart::Wire) continue;
903 
904         itemBase = itemBase->layerKinChief();
905         if (!itemBase->isEverVisible()) continue;
906         if (itemBase == board) continue;
907 
908         itemBases.insert(itemBase->layerKinChief());
909     }
910 
911     QString outname = exportDir + "/" + prefix + "_pnp.txt";
912     QFile out(outname);
913 	if (!out.open(QIODevice::WriteOnly | QIODevice::Text)) {
914 		displayMessage(QObject::tr("Unable to save pick and place file: %2").arg(outname), displayMessageBoxes);
915 		return;
916 	}
917 
918     QTextStream stream(&out);
919     stream << "*Pick And Place List\n"
920         << "*Company=\n"
921         << "*Author=\n"
922         //*Tel=
923         //*Fax=
924         << "*eMail=\n"
925         << "*\n"
926         << QString("*Project=%1\n").arg(prefix)
927         // *Variant=<alle Bauteile>
928         << QString("*Date=%1\n").arg(QTime::currentTime().toString())
929         << QString("*CreatedBy=Fritzing %1\n").arg(Version::versionString())
930         << "*\n"
931         << "*\n*Coordinates in mm, always center of component\n"
932         << "*Origin 0/0=Lower left corner of PCB\n"
933         << "*Rotation in degree (0-360, math. pos.)\n"
934         << "*\n"
935         << "*No;Value;Package;X;Y;Rotation;Side;Name\n"
936         ;
937 
938     QStringList valueKeys;
939     valueKeys << "resistance" << "capacitance" << "inductance" << "voltage"  << "current" << "power";
940 
941     int ix = 1;
942     foreach (ItemBase * itemBase, itemBases) {
943         QString value;
944         foreach (QString valueKey, valueKeys) {
945             value = itemBase->modelPart()->localProp(valueKey).toString();
946             if (!value.isEmpty()) break;
947 
948             value = itemBase->modelPart()->properties().value(valueKey);
949             if (!value.isEmpty()) break;
950         }
951 
952         QPointF loc = itemBase->sceneBoundingRect().center();
953         QTransform transform = itemBase->transform();
954         // doesn't account for scaling
955         double angle = atan2(transform.m12(), transform.m11()) * 180 / M_PI;
956         // No;Value;Package;X;Y;Rotation;Side;Name
957         QString string = QString("%1;%2;%3;%4;%5;%6;%7;%8\n")
958             .arg(ix++)
959             .arg(value)
960             .arg(itemBase->modelPart()->properties().value("package"))
961             .arg(GraphicsUtils::pixels2mm(loc.x() - bottomLeft.x(), GraphicsUtils::SVGDPI))
962             .arg(GraphicsUtils::pixels2mm(loc.y() - bottomLeft.y(), GraphicsUtils::SVGDPI))
963             .arg(angle)
964             .arg(itemBase->viewLayerID() == ViewLayer::Copper1 ? "Top" : "Bottom")
965             .arg(itemBase->instanceTitle())
966         ;
967         stream << string;
968         stream.flush();
969     }
970 
971     out.close();
972 }
973 
handleDonuts(QDomElement & root1,QMultiHash<long,ConnectorItem * > & treatAsCircle)974 void GerberGenerator::handleDonuts(QDomElement & root1, QMultiHash<long, ConnectorItem *> & treatAsCircle) {
975     // most of this would not be necessary if we cached cleaned SVGs
976 
977     static const QString unique("%%%%%%%%%%%%%%%%%%%%%%%%_________________________________%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
978 
979     QDomNodeList nodeList = root1.elementsByTagName("path");
980     if (treatAsCircle.count() > 0) {
981         QStringList ids;
982         foreach (ConnectorItem * connectorItem, treatAsCircle.values()) {
983             ItemBase * itemBase = connectorItem->attachedTo();
984             SvgIdLayer * svgIdLayer = connectorItem->connector()->fullPinInfo(itemBase->viewID(), itemBase->viewLayerID());
985             DebugDialog::debug(QString("treat as circle %1").arg(svgIdLayer->m_svgId));
986             ids << svgIdLayer->m_svgId;
987         }
988 
989         for (int n = 0; n < nodeList.count(); n++) {
990             QDomElement path = nodeList.at(n).toElement();
991             QString id = path.attribute("id");
992             if (id.isEmpty()) continue;
993 
994             DebugDialog::debug(QString("checking for %1").arg(id));
995             if (!ids.contains(id)) continue;
996 
997             QString pid;
998             ConnectorItem * connectorItem = NULL;
999             for (QDomElement parent = path.parentNode().toElement(); !parent.isNull(); parent = parent.parentNode().toElement()) {
1000                 pid = parent.attribute("partID");
1001                 if (pid.isEmpty()) continue;
1002 
1003                 QList<ConnectorItem *> connectorItems = treatAsCircle.values(pid.toLong());
1004                 if (connectorItems.count() == 0) break;
1005 
1006                 foreach (ConnectorItem * candidate, connectorItems) {
1007                     ItemBase * itemBase = candidate->attachedTo();
1008                     SvgIdLayer * svgIdLayer = candidate->connector()->fullPinInfo(itemBase->viewID(), itemBase->viewLayerID());
1009                     if (svgIdLayer->m_svgId == id) {
1010                         connectorItem = candidate;
1011                         break;
1012                     }
1013                 }
1014 
1015                 if (connectorItem) break;
1016             }
1017             if (connectorItem == NULL) continue;
1018 
1019             //QString string;
1020             //QTextStream stream(&string);
1021             //path.save(stream, 0);
1022             //DebugDialog::debug("path " + string);
1023 
1024             connectorItem->debugInfo("make path");
1025             path.setAttribute("id", unique);
1026             QSvgRenderer renderer;
1027             renderer.load(root1.ownerDocument().toByteArray());
1028             QRectF bounds = renderer.boundsOnElement(unique);
1029             path.removeAttribute("id");
1030 
1031             QDomElement circle = root1.ownerDocument().createElement("circle");
1032             path.parentNode().insertBefore(circle, path);
1033             circle.setAttribute("id", id);
1034             QPointF p = bounds.center();
1035             circle.setAttribute("cx", QString::number(p.x()));
1036             circle.setAttribute("cy", QString::number(p.y()));
1037             circle.setAttribute("r", QString::number(connectorItem->radius() * GraphicsUtils::StandardFritzingDPI / GraphicsUtils::SVGDPI));
1038             circle.setAttribute("stroke-width", QString::number(connectorItem->strokeWidth() * GraphicsUtils::StandardFritzingDPI / GraphicsUtils::SVGDPI));
1039 
1040         }
1041     }
1042 }
1043 
renderTo(const LayerList & layers,ItemBase * board,PCBSketchWidget * sketchWidget,bool & empty)1044 QString GerberGenerator::renderTo(const LayerList & layers, ItemBase * board, PCBSketchWidget * sketchWidget, bool & empty) {
1045     RenderThing renderThing;
1046     renderThing.printerScale = GraphicsUtils::SVGDPI;
1047     renderThing.blackOnly = true;
1048     renderThing.dpi = GraphicsUtils::StandardFritzingDPI;
1049     renderThing.hideTerminalPoints = true;
1050     renderThing.selectedItems = renderThing.renderBlocker = false;
1051 	QString svg = sketchWidget->renderToSVG(renderThing, board, layers);
1052     empty = renderThing.empty;
1053     return svg;
1054 }
1055