1 /*******************************************************************
2 
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2014 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: 6980 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-22 01:45:43 +0200 (Mo, 22. Apr 2013) $
24 
25 ********************************************************************/
26 
27 #include "panelizer.h"
28 #include "../debugdialog.h"
29 #include "../sketch/pcbsketchwidget.h"
30 #include "../utils/textutils.h"
31 #include "../utils/graphicsutils.h"
32 #include "../utils/folderutils.h"
33 #include "../utils/folderutils.h"
34 #include "../items/resizableboard.h"
35 #include "../items/logoitem.h"
36 #include "../items/groundplane.h"
37 #include "../fsvgrenderer.h"
38 #include "../fapplication.h"
39 #include "../svg/gerbergenerator.h"
40 #include "../referencemodel/referencemodel.h"
41 #include "../version/version.h"
42 #include "../processeventblocker.h"
43 #include "../connectors/connectoritem.h"
44 #include "../connectors/svgidlayer.h"
45 
46 #include "cmrouter/tileutils.h"
47 
48 #include <QFile>
49 #include <QDomDocument>
50 #include <QDomElement>
51 #include <QDir>
52 #include <qmath.h>
53 #include <limits>
54 #include <QPrinter>
55 
56 static int OutlineLayer = 0;
57 static int SilkTopLayer = 0;
58 static QString PanelizerOutputPath;
59 static QSet<QString> PanelizerFileNames;
60 
61 ///////////////////////////////////////////////////////////
62 
collectTexts(const QString & svg,QStringList & strings)63 void collectTexts(const QString & svg, QStringList & strings) {
64     QDomDocument doc;
65     doc.setContent(svg);
66     QDomElement root = doc.documentElement();
67     QDomNodeList domNodeList = root.elementsByTagName("text");
68 	for (int i = 0; i < domNodeList.count(); i++) {
69         QDomElement textElement = domNodeList.at(i).toElement();
70         QString string;
71         QDomNodeList childList = textElement.childNodes();
72 	    for (int j = 0; j < childList.count(); j++) {
73 		    QDomNode child = childList.item(j);
74 		    if (child.isText()) {
75 			    string.append(child.nodeValue());
76 		    }
77 	    }
78         strings.append(string);
79     }
80 }
81 
82 
byOptionalPriority(PanelItem * p1,PanelItem * p2)83 bool byOptionalPriority(PanelItem * p1, PanelItem * p2) {
84     return p1->optionalPriority > p2->optionalPriority;
85 }
86 
areaGreaterThan(PanelItem * p1,PanelItem * p2)87 bool areaGreaterThan(PanelItem * p1, PanelItem * p2)
88 {
89 	return p1->boardSizeInches.width() * p1->boardSizeInches.height() > p2->boardSizeInches.width() * p2->boardSizeInches.height();
90 }
91 
allSpaces(Tile * tile,UserData userData)92 int allSpaces(Tile * tile, UserData userData) {
93 	QList<Tile*> * tiles = (QList<Tile*> *) userData;
94 	if (TiGetType(tile) == Tile::SPACE) {
95 		tiles->append(tile);
96 		return 0;
97 	}
98 
99 	tiles->clear();
100 	return 1;			// stop the search
101 }
102 
allObstacles(Tile * tile,UserData userData)103 int allObstacles(Tile * tile, UserData userData) {
104 	if (TiGetType(tile) == Tile::OBSTACLE) {
105 		QList<Tile*> * obstacles = (QList<Tile*> *) userData;
106 		obstacles->append(tile);
107 
108 	}
109 
110 	return 0;
111 }
112 
113 static int PlanePairIndex = 0;
114 
115 static double Worst = std::numeric_limits<double>::max() / 4;
116 
roomOn(Tile * tile,TileRect & tileRect,BestPlace * bestPlace)117 int roomOn(Tile * tile, TileRect & tileRect, BestPlace * bestPlace)
118 {
119 	int w = tileRect.xmaxi - tileRect.xmini;
120 	int h = tileRect.ymaxi - tileRect.ymini;
121 	if (bestPlace->width <= w && bestPlace->height <= h) {
122 		bestPlace->bestTile = tile;
123 		return 1;
124 	}
125 
126 	TileRect temp;
127 	temp.xmini = tileRect.xmini;
128 	temp.xmaxi = temp.xmini + bestPlace->width;
129 	temp.ymini = tileRect.ymini;
130 	temp.ymaxi = temp.ymini + bestPlace->height;
131 	QList<Tile*> spaces;
132 	TiSrArea(tile, bestPlace->plane, &temp, allSpaces, &spaces);
133 	if (spaces.count()) {
134 		bestPlace->bestTile = tile;
135 		return 1;
136 	}
137 
138 	return 0;
139 }
140 
roomAnywhere(Tile * tile,UserData userData)141 int roomAnywhere(Tile * tile, UserData userData)
142 {
143 	if (TiGetType(tile) != Tile::SPACE) return 0;
144 
145 	BestPlace * bestPlace = (BestPlace *) userData;
146 	TileRect tileRect;
147 	TiToRect(tile, &tileRect);
148 
149 	return roomOn(tile, tileRect, bestPlace);
150 }
151 
roomOnTop(Tile * tile,UserData userData)152 int roomOnTop(Tile * tile, UserData userData)
153 {
154 	if (TiGetType(tile) != Tile::SPACE) return 0;
155 
156 	BestPlace * bestPlace = (BestPlace *) userData;
157 	TileRect tileRect;
158 	TiToRect(tile, &tileRect);
159 
160 	if (tileRect.ymini != bestPlace->maxRect.ymini) return 0;
161 
162 	return roomOn(tile, tileRect, bestPlace);
163 }
164 
roomOnBottom(Tile * tile,UserData userData)165 int roomOnBottom(Tile * tile, UserData userData)
166 {
167 	if (TiGetType(tile) != Tile::SPACE) return 0;
168 
169 	BestPlace * bestPlace = (BestPlace *) userData;
170 	TileRect tileRect;
171 	TiToRect(tile, &tileRect);
172 
173 	if (tileRect.ymaxi != bestPlace->maxRect.ymaxi) return 0;
174 
175 	return roomOn(tile, tileRect, bestPlace);
176 }
177 
roomOnLeft(Tile * tile,UserData userData)178 int roomOnLeft(Tile * tile, UserData userData)
179 {
180 	if (TiGetType(tile) != Tile::SPACE) return 0;
181 
182 	BestPlace * bestPlace = (BestPlace *) userData;
183 	TileRect tileRect;
184 	TiToRect(tile, &tileRect);
185 
186 	if (tileRect.xmini != bestPlace->maxRect.xmini) return 0;
187 
188 	return roomOn(tile, tileRect, bestPlace);
189 }
190 
roomOnRight(Tile * tile,UserData userData)191 int roomOnRight(Tile * tile, UserData userData)
192 {
193 	if (TiGetType(tile) != Tile::SPACE) return 0;
194 
195 	BestPlace * bestPlace = (BestPlace *) userData;
196 	TileRect tileRect;
197 	TiToRect(tile, &tileRect);
198 
199 	if (tileRect.xmaxi != bestPlace->maxRect.xmaxi) return 0;
200 
201 	return roomOn(tile, tileRect, bestPlace);
202 }
203 
204 
BestPlace()205 BestPlace::BestPlace() {
206     bestTile = NULL;
207     bestArea = Worst;
208 }
209 
PanelItem()210 PanelItem::PanelItem() {
211     produced = 0;
212     boardID = 0;
213     refPanelItem = NULL;
214 }
215 
PanelItem(PanelItem * from)216 PanelItem::PanelItem(PanelItem * from) {
217 	this->produced = from->produced;
218 	this->boardName = from->boardName;
219 	this->path = from->path;
220 	this->required = from->required;
221 	this->maxOptional = from->maxOptional;
222 	this->optionalPriority = from->optionalPriority;
223 	this->boardSizeInches = from->boardSizeInches;
224 	this->boardID = from->boardID;
225     this->refPanelItem = from;
226 }
227 
228 /////////////////////////////////////////////////////////////////////////////////
229 
panelize(FApplication * app,const QString & panelFilename,bool customPartsOnly)230 void Panelizer::panelize(FApplication * app, const QString & panelFilename, bool customPartsOnly)
231 {
232     QString msg = "panelize";
233     if (customPartsOnly) msg += " custom parts";
234     initPanelizerOutput(panelFilename, msg);
235 
236 	QFile panelizerFile(panelFilename);
237 
238     QFileInfo info(panelFilename);
239     QDir copyDir = info.absoluteDir();
240     copyDir.cd("copies");
241     if (!copyDir.exists()) {
242 		writePanelizerOutput(QString("unable to create 'copies' folder in '%1'").arg(info.absoluteDir().absolutePath()));
243 		return;
244 	}
245 
246 
247 	QString errorStr;
248 	int errorLine;
249 	int errorColumn;
250 
251 	DebugDialog::setEnabled(true);
252 
253 	QDomDocument panelizerDocument;
254 	if (!panelizerDocument.setContent(&panelizerFile, true, &errorStr, &errorLine, &errorColumn)) {
255 		writePanelizerOutput(QString("Unable to parse '%1': '%2' line:%3 column:%4").arg(panelFilename).arg(errorStr).arg(errorLine).arg(errorColumn));
256 		return;
257 	}
258 
259 	QDomElement panelizerRoot = panelizerDocument.documentElement();
260 	if (panelizerRoot.isNull() || panelizerRoot.tagName() != "panelizer") {
261 		writePanelizerOutput(QString("root element is not 'panelizer'"));
262 		return;
263 	}
264 
265 	PanelParams panelParams;
266 	if (!initPanelParams(panelizerRoot, panelParams)) return;
267 
268     QFileInfo pinfo(panelFilename);
269 	QDir outputDir = pinfo.absoluteDir();
270 
271     if (customPartsOnly) {
272         outputDir.mkdir("custom");
273         outputDir.cd("custom");
274     }
275 
276 	outputDir.mkdir("svg");
277 	outputDir.mkdir("gerber");
278 	outputDir.mkdir("fz");
279 
280 	QDir svgDir(outputDir);
281 	svgDir.cd("svg");
282 	if (!svgDir.exists()) {
283 		writePanelizerOutput(QString("unable to create svg folder in '%1'").arg(pinfo.absolutePath()));
284 		return;
285 	}
286 
287 	DebugDialog::debug(QString("svg folder '%1'\n").arg(svgDir.absolutePath()));
288 
289 	QDir gerberDir(outputDir);
290 	gerberDir.cd("gerber");
291 	if (!gerberDir.exists()) {
292 		writePanelizerOutput(QString("unable to create gerber folder in '%1'").arg(pinfo.absolutePath()));
293 		return;
294 	}
295 
296 	DebugDialog::debug(QString("gerber folder '%1'\n").arg(gerberDir.absolutePath()));
297 
298 	QDir fzDir(outputDir);
299 	fzDir.cd("fz");
300 	if (!fzDir.exists()) {
301 		writePanelizerOutput(QString("unable to create fz folder in '%1'").arg(pinfo.absolutePath()));
302 		return;
303 	}
304 
305 	DebugDialog::debug(QString("fz folder '%1'\n").arg(fzDir.absolutePath()));
306 
307 
308 	QDomElement boards = panelizerRoot.firstChildElement("boards");
309 	QDomElement board = boards.firstChildElement("board");
310 	if (board.isNull()) {
311 		writePanelizerOutput(QString("no <board> elements found"));
312 		return;
313 	}
314 
315 	QHash<QString, QString> fzzFilePaths;
316 	QDomElement paths = panelizerRoot.firstChildElement("paths");
317 	QDomElement path = paths.firstChildElement("path");
318 	if (path.isNull()) {
319 		writePanelizerOutput(QString("no <path> elements found"));
320 		return;
321 	}
322 
323 	collectFiles(pinfo.absoluteDir(), path, fzzFilePaths);
324     if (fzzFilePaths.count() == 0) {
325 		writePanelizerOutput(QString("no fzz files found in paths"));
326 		return;
327 	}
328 
329 	board = boards.firstChildElement("board");
330 	if (!checkBoards(board, fzzFilePaths)) return;
331 
332 	app->createUserDataStoreFolderStructure();
333 	app->registerFonts();
334 	app->loadReferenceModel("", false);
335 
336     QList<LayerThing> layerThingList;
337 	layerThingList.append(LayerThing("outline", ViewLayer::outlineLayers(), SVG2gerber::ForOutline, GerberGenerator::OutlineSuffix));
338 	layerThingList.append(LayerThing("copper_top", ViewLayer::copperLayers(ViewLayer::NewTop), SVG2gerber::ForCopper, GerberGenerator::CopperTopSuffix));
339 	layerThingList.append(LayerThing("copper_bottom", ViewLayer::copperLayers(ViewLayer::NewBottom), SVG2gerber::ForCopper, GerberGenerator::CopperBottomSuffix));
340 	layerThingList.append(LayerThing("mask_top", ViewLayer::maskLayers(ViewLayer::NewTop), SVG2gerber::ForMask, GerberGenerator:: MaskTopSuffix));
341 	layerThingList.append(LayerThing("mask_bottom", ViewLayer::maskLayers(ViewLayer::NewBottom), SVG2gerber::ForMask, GerberGenerator::MaskBottomSuffix));
342 	layerThingList.append(LayerThing("paste_mask_top", ViewLayer::maskLayers(ViewLayer::NewTop), SVG2gerber::ForPasteMask, GerberGenerator:: PasteMaskTopSuffix));
343 	layerThingList.append(LayerThing("paste_mask_bottom", ViewLayer::maskLayers(ViewLayer::NewBottom), SVG2gerber::ForPasteMask, GerberGenerator::PasteMaskBottomSuffix));
344 	layerThingList.append(LayerThing("silk_top", ViewLayer::silkLayers(ViewLayer::NewTop), SVG2gerber::ForSilk, GerberGenerator::SilkTopSuffix));
345 	layerThingList.append(LayerThing("silk_bottom", ViewLayer::silkLayers(ViewLayer::NewBottom), SVG2gerber::ForSilk, GerberGenerator::SilkBottomSuffix));
346 	layerThingList.append(LayerThing("drill", ViewLayer::drillLayers(), SVG2gerber::ForDrill, GerberGenerator::DrillSuffix));
347 
348     for (int i = 0; i < layerThingList.count(); i++) {
349         LayerThing layerThing = layerThingList.at(i);
350         if (layerThing.name.compare("outline") == 0) OutlineLayer = i;
351         else if (layerThing.name.compare("silk_top") == 0) SilkTopLayer = i;
352     }
353 
354 	QList<PanelItem *> refPanelItems;
355 	board = boards.firstChildElement("board");
356 	if (!openWindows(board, fzzFilePaths, app, panelParams, fzDir, svgDir, refPanelItems, layerThingList, customPartsOnly, copyDir)) return;
357 
358 	QList<PlanePair *> planePairs;
359     QList<PanelItem *> insertPanelItems;
360     int duplicates = bestFitLoop(refPanelItems, panelParams, customPartsOnly, planePairs, insertPanelItems, svgDir);
361 
362     foreach (PanelItem * panelItem, insertPanelItems) {
363         if (panelItem->refPanelItem) {
364             panelItem->refPanelItem->produced += duplicates;
365         }
366     }
367 
368     QString custom = customPartsOnly ? "custom." : "";
369 
370 	foreach (PlanePair * planePair, planePairs) {
371 		planePair->layoutSVG += "</svg>";
372 		QString fname = svgDir.absoluteFilePath(QString("%1%2.panel_%3.layout.svg").arg(custom).arg(panelParams.prefix).arg(planePair->index));
373         TextUtils::writeUtf8(fname, planePair->layoutSVG);
374 	}
375 
376 	foreach (PlanePair * planePair, planePairs) {
377 		for (int i = 0; i < layerThingList.count(); i++) {
378 			planePair->svgs << TextUtils::makeSVGHeader(1, GraphicsUtils::StandardFritzingDPI, planePair->panelWidth, planePair->panelHeight);
379 		}
380 
381         QList<PanelItem *> needToRotate;
382 		foreach (PanelItem * panelItem, insertPanelItems) {
383 			if (panelItem->planePair != planePair) continue;
384 
385 			DebugDialog::debug(QString("placing %1 on panel %2").arg(panelItem->boardName).arg(planePair->index));
386 
387             doOnePanelItem(planePair, layerThingList, panelItem, svgDir);
388 		}
389 
390 
391 		DebugDialog::debug("after placement");
392 		QString prefix = QString("%1%2.panel_%3").arg(custom).arg(panelParams.prefix).arg(planePair->index);
393 		for (int i = 0; i < planePair->svgs.count(); i++) {
394 			if (planePair->svgs.at(i).isEmpty()) continue;
395 
396 			planePair->svgs.replace(i, planePair->svgs.at(i) + "</svg>");
397 
398 			QString fname = svgDir.absoluteFilePath(QString("%1.%2.svg").arg(prefix).arg(layerThingList.at(i).name));
399 			TextUtils::writeUtf8(fname, planePair->svgs.at(i));
400 
401 			QString suffix = layerThingList.at(i).suffix;
402 			DebugDialog::debug("converting " + prefix + " " + suffix);
403 			QSizeF svgSize(planePair->panelWidth, planePair->panelHeight);
404 			SVG2gerber::ForWhy forWhy = layerThingList.at(i).forWhy;
405 			if (forWhy == SVG2gerber::ForMask || forWhy == SVG2gerber::ForPasteMask) forWhy = SVG2gerber::ForCopper;
406 			GerberGenerator::doEnd(planePair->svgs.at(i), 2, layerThingList.at(i).name, forWhy, svgSize * GraphicsUtils::StandardFritzingDPI, gerberDir.absolutePath(), prefix, suffix, false);
407 			DebugDialog::debug("after converting " + prefix + " " + suffix);
408 		}
409 
410 		QDomDocument doc;
411 		QString merger = planePair->svgs.at(OutlineLayer);
412 		merger.replace("black", "#90f0a0");
413 		merger.replace("#000000", "#90f0a0");
414 		merger.replace("fill-opacity=\"0.5\"", "fill-opacity=\"1\"");
415 
416         //DebugDialog::debug("outline identification");
417         //DebugDialog::debug(merger);
418         //DebugDialog::debug("");
419 
420 		TextUtils::mergeSvg(doc, merger, "");
421 		merger = planePair->svgs.at(SilkTopLayer);
422 		merger.replace("black", "#909090");
423 		merger.replace("#000000", "#909090");
424 
425         //DebugDialog::debug("silk identification");
426         //DebugDialog::debug(merger);
427         //DebugDialog::debug("");
428 
429 		TextUtils::mergeSvg(doc, merger, "");
430 		merger = planePair->layoutSVG;				// layout
431 		merger.replace("'red'", "'none'");		// hide background rect
432 
433         //DebugDialog::debug("layout identification");
434         //DebugDialog::debug(merger);
435         //DebugDialog::debug("");
436 
437 		TextUtils::mergeSvg(doc, merger, "");
438 		merger = TextUtils::mergeSvgFinish(doc);
439 
440         int endix = merger.lastIndexOf("</svg>");
441         QString text = "<text x='%1' y='%2' text-anchor='end' fill='#000000' stroke='none' stroke-width='0' font-size='250' font-family='OCRA'>%3 panel %4  %5 %6</text>\n";
442         text = text
443                 .arg(planePair->panelWidth * GraphicsUtils::StandardFritzingDPI)
444                 .arg(planePair->panelHeight * GraphicsUtils::StandardFritzingDPI + (250 / 2))
445                 .arg(outputDir.dirName())
446                 .arg(planePair->index)
447                 .arg(panelParams.prefix)
448                 .arg(QTime::currentTime().toString())
449                 ;
450 
451         merger.insert(endix, text);
452 
453 		QString fname = svgDir.absoluteFilePath(QString("%1.%2.svg").arg(prefix).arg("identification"));
454 		TextUtils::writeUtf8(fname, merger);
455 
456         // save to pdf
457 		QPrinter printer(QPrinter::HighResolution);
458 		printer.setOutputFormat(QPrinter::PdfFormat);
459         QString pdfname = fname;
460         pdfname.replace(".svg", ".pdf");
461 		printer.setOutputFileName(pdfname);
462 		int res = printer.resolution();
463 
464 		// now convert to pdf
465 		QSvgRenderer svgRenderer;
466 		svgRenderer.load(merger.toLatin1());
467 		double trueWidth = planePair->panelWidth;
468 		double trueHeight = planePair->panelHeight;
469 		QRectF target(0, 0, trueWidth * res, trueHeight * res);
470 
471 		QSizeF psize((target.width() + printer.paperRect().width() - printer.width()) / res,
472 						(target.height() + printer.paperRect().height() - printer.height()) / res);
473 		printer.setPaperSize(psize, QPrinter::Inch);
474 
475 		QPainter painter;
476 		if (painter.begin(&printer))
477 		{
478 			svgRenderer.render(&painter, target);
479 		}
480 
481 		painter.end();
482 	}
483 
484     boards = panelizerRoot.firstChildElement("boards");
485     board = boards.firstChildElement("board");
486     while (!board.isNull()) {
487         foreach (PanelItem * panelItem, refPanelItems) {
488             if (panelItem->path.endsWith("/" + board.attribute("name"))) {
489                 board.setAttribute("produced", panelItem->produced);
490                 break;
491             }
492         }
493         board = board.nextSiblingElement("board");
494     }
495 
496     TextUtils::writeUtf8(panelFilename, panelizerDocument.toString(4));
497 
498     QString message = QString("Panelizer finished: %1 panel(s), with %2 additional copy(ies) for each panel").arg(planePairs.count()).arg(duplicates - 1);
499     writePanelizerOutput("--------------------------------");
500     writePanelizerOutput(message);
501     QMessageBox::information(NULL, QObject::tr("Fritzing"), message);
502 }
503 
504 
bestFit(QList<PanelItem * > & insertPanelItems,PanelParams & panelParams,QList<PlanePair * > & planePairs,bool customPartsOnly)505 void Panelizer::bestFit(QList<PanelItem *> & insertPanelItems, PanelParams & panelParams, QList<PlanePair *> & planePairs, bool customPartsOnly)
506 {
507 	foreach (PanelItem * panelItem, insertPanelItems) {
508 		bestFitOne(panelItem, panelParams, planePairs, true, customPartsOnly);
509 	}
510 }
511 
bestFitOne(PanelItem * panelItem,PanelParams & panelParams,QList<PlanePair * > & planePairs,bool createNew,bool customPartsOnly)512 bool Panelizer::bestFitOne(PanelItem * panelItem, PanelParams & panelParams, QList<PlanePair *> & planePairs, bool createNew, bool customPartsOnly)
513 {
514 	//DebugDialog::debug(QString("panel %1").arg(panelItem->boardName));
515 	BestPlace bestPlace1, bestPlace2;
516 	bestPlace1.rotate90 = bestPlace2.rotate90 = false;
517 	bestPlace1.width = bestPlace2.width = realToTile(panelItem->boardSizeInches.width() + panelParams.panelSpacing);
518 	bestPlace1.height = bestPlace2.height = realToTile(panelItem->boardSizeInches.height() + panelParams.panelSpacing);
519 	int ppix = 0;
520 	while (ppix < planePairs.count()) {
521 		PlanePair *  planePair = planePairs.at(ppix);
522 		bestPlace1.plane = planePair->thePlane;
523 		bestPlace2.plane = planePair->thePlane90;
524 		bestPlace1.maxRect = planePair->tilePanelRect;
525 		TiSrArea(NULL, planePair->thePlane, &planePair->tilePanelRect, placeBestFit, &bestPlace1);
526         if (customPartsOnly) {
527             // make sure board is rotated
528             bestPlace1.bestTile = NULL;
529             DebugDialog::debug("forcing rotation");
530         }
531 		if (bestPlace1.bestTile == NULL) {
532 			bestPlace2.maxRect = planePair->tilePanelRect90;
533 			TiSrArea(NULL, planePair->thePlane90, &planePair->tilePanelRect90, placeBestFit, &bestPlace2);
534 		}
535 		if (bestPlace1.bestTile == NULL && bestPlace2.bestTile == NULL ) {
536 			if (++ppix < planePairs.count()) {
537 				// try next panel
538 				continue;
539 			}
540 
541 			if (!createNew) {
542 				return false;
543 			}
544 
545 			// create next panel
546 			planePair = makePlanePair(panelParams, true);
547 			planePairs << planePair;
548 			DebugDialog::debug(QString("ran out of room placing %1").arg(panelItem->boardName));
549 			continue;
550 		}
551 
552 		bool use2 = false;
553 		if (bestPlace1.bestTile == NULL) {
554 			use2 = true;
555 		}
556 		else if (bestPlace2.bestTile == NULL) {
557 		}
558 		else {
559 			// never actually get here
560 			use2 = bestPlace2.bestArea < bestPlace1.bestArea;
561 		}
562 
563 		if (use2) {
564 			tileUnrotate90(bestPlace2.bestTileRect, bestPlace1.bestTileRect);
565 			bestPlace1.rotate90 = !bestPlace2.rotate90;
566 		}
567 
568 		panelItem->x = tileToReal(bestPlace1.bestTileRect.xmini) ;
569 		panelItem->y = tileToReal(bestPlace1.bestTileRect.ymini);
570 		panelItem->rotate90 = bestPlace1.rotate90;
571 
572 		DebugDialog::debug(QString("setting rotate90:%1 %2").arg(panelItem->rotate90).arg(panelItem->path));
573 		panelItem->planePair = planePair;
574 
575 		TileRect tileRect;
576 		tileRect.xmini = bestPlace1.bestTileRect.xmini;
577 		tileRect.ymini = bestPlace1.bestTileRect.ymini;
578 		if (bestPlace1.rotate90) {
579 			tileRect.xmaxi = tileRect.xmini + bestPlace1.height;
580 			tileRect.ymaxi = tileRect.ymini + bestPlace1.width;
581 		}
582 		else {
583 			tileRect.ymaxi = tileRect.ymini + bestPlace1.height;
584 			tileRect.xmaxi = tileRect.xmini + bestPlace1.width;
585 		}
586 
587 		double w = panelItem->boardSizeInches.width();
588 		double h = panelItem->boardSizeInches.height();
589 		if (panelItem->rotate90) {
590 			w = h;
591 			h = panelItem->boardSizeInches.width();
592 		}
593 
594 		planePair->layoutSVG += QString("<rect x='%1' y='%2' width='%3' height='%4' stroke='none' fill='red'/>\n")
595 			.arg(panelItem->x * GraphicsUtils::StandardFritzingDPI)
596 			.arg(panelItem->y * GraphicsUtils::StandardFritzingDPI)
597 			.arg(GraphicsUtils::StandardFritzingDPI * w)
598 			.arg(GraphicsUtils::StandardFritzingDPI * h);
599 
600 		QStringList strings = QFileInfo(panelItem->path).completeBaseName().split("_");
601         if (strings.count() >= 5) {
602             QString start = strings.takeFirst();
603             QStringList middle;
604             middle << strings.at(0) << strings.at(1) << strings.at(2);
605             strings.removeAt(0);
606             strings.removeAt(0);
607             strings.removeAt(0);
608             QString end = strings.join("_");
609             strings.clear();
610             strings << start << middle.join(" ") << end;
611         }
612 		double cx = GraphicsUtils::StandardFritzingDPI * (panelItem->x + (w / 2));
613 		int fontSize1 = 250;
614 		int fontSize2 = 150;
615 		int fontSize = fontSize1;
616 		double cy = GraphicsUtils::StandardFritzingDPI * (panelItem->y + (h  / 2));
617 		cy -= ((strings.count() - 1) * fontSize2 / 2);
618 		foreach (QString string, strings) {
619 			planePair->layoutSVG += QString("<text x='%1' y='%2' anchor='middle' font-family='%5' stroke='none' fill='#000000' text-anchor='middle' font-size='%3'>%4</text>\n")
620 				.arg(cx)
621 				.arg(cy)
622 				.arg(fontSize)
623 				.arg(string)
624                 .arg(OCRAFontName);
625 			cy += fontSize;
626 			if (fontSize == fontSize1) fontSize = fontSize2;
627 		}
628 
629 		TiInsertTile(planePair->thePlane, &tileRect, NULL, Tile::OBSTACLE);
630 		TileRect tileRect90;
631 		tileRotate90(tileRect, tileRect90);
632 		TiInsertTile(planePair->thePlane90, &tileRect90, NULL, Tile::OBSTACLE);
633 
634 		return true;
635 	}
636 
637 	DebugDialog::debug("bestFitOne should never reach here");
638 	return false;
639 }
640 
makePlanePair(PanelParams & panelParams,bool big)641 PlanePair * Panelizer::makePlanePair(PanelParams & panelParams, bool big)
642 {
643 	PlanePair * planePair = new PlanePair;
644 
645     if (big) {
646         foreach (PanelType * panelType, panelParams.panelTypes) {
647             if (panelType->name == "big") {
648                 planePair->panelWidth = panelType->width;
649                 planePair->panelHeight = panelType->height;
650                 break;
651             }
652         }
653     }
654     else {
655         foreach (PanelType * panelType, panelParams.panelTypes) {
656             if (panelType->name == "small") {
657                 planePair->panelWidth = panelType->width;
658                 planePair->panelHeight = panelType->height;
659                 break;
660             }
661         }
662     }
663 
664 	// for debugging
665 	planePair->layoutSVG = TextUtils::makeSVGHeader(1, GraphicsUtils::StandardFritzingDPI, planePair->panelWidth, planePair->panelHeight);
666 	planePair->index = PlanePairIndex++;
667 
668 	Tile * bufferTile = TiAlloc();
669 	TiSetType(bufferTile, Tile::BUFFER);
670 	TiSetBody(bufferTile, NULL);
671 
672 	QRectF panelRect(0, 0, planePair->panelWidth + panelParams.panelSpacing - panelParams.panelBorder,
673 							planePair->panelHeight + panelParams.panelSpacing - panelParams.panelBorder);
674 
675     int l = fasterRealToTile(panelRect.left() - 10);
676     int t = fasterRealToTile(panelRect.top() - 10);
677     int r = fasterRealToTile(panelRect.right() + 10);
678     int b = fasterRealToTile(panelRect.bottom() + 10);
679     SETLEFT(bufferTile, l);
680     SETYMIN(bufferTile, t);
681 
682 	planePair->thePlane = TiNewPlane(bufferTile, l, t, r, b);
683 
684     SETRIGHT(bufferTile, r);
685 	SETYMAX(bufferTile, b);
686 
687 	qrectToTile(panelRect, planePair->tilePanelRect);
688 	TiInsertTile(planePair->thePlane, &planePair->tilePanelRect, NULL, Tile::SPACE);
689 
690 	QMatrix matrix90;
691 	matrix90.rotate(90);
692 	QRectF panelRect90 = matrix90.mapRect(panelRect);
693 
694 	Tile * bufferTile90 = TiAlloc();
695 	TiSetType(bufferTile90, Tile::BUFFER);
696 	TiSetBody(bufferTile90, NULL);
697 
698     l = fasterRealToTile(panelRect90.left() - 10);
699     t = fasterRealToTile(panelRect90.top() - 10);
700     r = fasterRealToTile(panelRect90.right() + 10);
701     b = fasterRealToTile(panelRect90.bottom() + 10);
702     SETLEFT(bufferTile90, l);
703     SETYMIN(bufferTile90, t);
704 
705 	planePair->thePlane90 = TiNewPlane(bufferTile90, l, t, r, b);
706 
707     SETRIGHT(bufferTile90, r);
708 	SETYMAX(bufferTile90, b);
709 
710 	qrectToTile(panelRect90, planePair->tilePanelRect90);
711 	TiInsertTile(planePair->thePlane90, &planePair->tilePanelRect90, NULL, Tile::SPACE);
712 
713 	return planePair;
714 }
715 
collectFiles(const QDir & outputFolder,QDomElement & path,QHash<QString,QString> & fzzFilePaths)716 void Panelizer::collectFiles(const QDir & outputFolder, QDomElement & path, QHash<QString, QString> & fzzFilePaths)
717 {
718     QList<QDir> dirList;
719     dirList << outputFolder;
720 
721 	while (!path.isNull()) {
722 		QDomNode node = path.firstChild();
723 		if (!node.isText()) {
724 			DebugDialog::debug(QString("missing text in <path> element"));
725 			return;
726 		}
727 
728         QString p = node.nodeValue().trimmed();
729         DebugDialog::debug("p folder " + p);
730         QString savep = p;
731         if (savep.startsWith(".")) {
732             p = outputFolder.absolutePath() + "/" + savep;
733         }
734 
735 		QDir dir(p);
736 		if (!dir.exists()) {
737 			DebugDialog::debug(QString("Directory '%1' doesn't exist").arg(p));
738 			return;
739 		}
740 
741         dirList << dir;
742 		path = path.nextSiblingElement("path");
743 	}
744 
745     foreach (QDir dir, dirList) {
746         DebugDialog::debug("directory " + dir.absolutePath());
747 	    QStringList filepaths;
748         QStringList filters("*" + FritzingBundleExtension);
749         FolderUtils::collectFiles(dir, filters, filepaths, false);
750 	    foreach (QString filepath, filepaths) {
751 		    QFileInfo fileInfo(filepath);
752 
753 		    fzzFilePaths.insert(fileInfo.fileName(), filepath);
754 	    }
755     }
756 }
757 
checkBoards(QDomElement & board,QHash<QString,QString> & fzzFilePaths)758 bool Panelizer::checkBoards(QDomElement & board, QHash<QString, QString> & fzzFilePaths)
759 {
760 	while (!board.isNull()) {
761 		QString boardname = board.attribute("name");
762 		//DebugDialog::debug(QString("board %1").arg(boardname));
763 		bool ok;
764 		int optional = board.attribute("maxOptionalCount", "").toInt(&ok);
765 		if (!ok) {
766 			writePanelizerOutput(QString("maxOptionalCount for board '%1' not an integer: '%2'").arg(boardname).arg(board.attribute("maxOptionalCount")));
767 			return false;
768 		}
769 
770 		int optionalPriority = board.attribute("optionalPriority", "").toInt(&ok);
771         Q_UNUSED(optionalPriority);
772 		if (!ok) {
773 			writePanelizerOutput(QString("optionalPriority for board '%1' not an integer: '%2'").arg(boardname).arg(board.attribute("optionalPriority")));
774 			return false;
775 		}
776 
777 		int required = board.attribute("requiredCount", "").toInt(&ok);
778 		if (!ok) {
779 			writePanelizerOutput(QString("required for board '%1' not an integer: '%2'").arg(boardname).arg(board.attribute("maxOptionalCount")));
780 			return false;
781 		}
782 
783         if (optional > 0 || required> 0) {
784 		    QString path = fzzFilePaths.value(boardname, "");
785 		    if (path.isEmpty()) {
786 			    writePanelizerOutput(QString("File for board '%1' not found in search paths").arg(boardname));
787 			    return false;
788 		    }
789         }
790         else {
791             writePanelizerOutput(QString("skipping board '%1'").arg(boardname));
792         }
793 
794 		board = board.nextSiblingElement("board");
795 	}
796 
797 	return true;
798 }
799 
openWindows(QDomElement & boardElement,QHash<QString,QString> & fzzFilePaths,FApplication * app,PanelParams & panelParams,QDir & fzDir,QDir & svgDir,QList<PanelItem * > & refPanelItems,QList<LayerThing> & layerThingList,bool customPartsOnly,QDir & copyDir)800 bool Panelizer::openWindows(QDomElement & boardElement, QHash<QString, QString> & fzzFilePaths,
801                     FApplication * app, PanelParams & panelParams,
802                     QDir & fzDir, QDir & svgDir, QList<PanelItem *> & refPanelItems,
803                     QList<LayerThing> & layerThingList, bool customPartsOnly, QDir & copyDir)
804 {
805     QDir rotateDir(svgDir);
806     QDir norotateDir(svgDir);
807     rotateDir.mkdir("rotate");
808     rotateDir.cd("rotate");
809     norotateDir.mkdir("norotate");
810     norotateDir.cd("norotate");
811 
812     PanelType * bigPanelType = NULL;
813     foreach (PanelType * panelType, panelParams.panelTypes) {
814         if (panelType->name == "big") {
815             bigPanelType = panelType;
816             break;
817         }
818     }
819 
820     if (bigPanelType == NULL) {
821         writePanelizerOutput("No panel types defined");
822         return false;
823     }
824 
825 	while (!boardElement.isNull()) {
826 		int required = boardElement.attribute("requiredCount", "").toInt();
827 		int optional = boardElement.attribute("maxOptionalCount", "").toInt();
828 		int priority = boardElement.attribute("optionalPriority", "").toInt();
829         if (customPartsOnly) {
830             optional = 0;
831             if (required > 1) required = 1;
832         }
833 		if (required == 0 && optional == 0) {
834 			boardElement = boardElement.nextSiblingElement("board");
835 			continue;
836 		}
837 
838 		QString boardName = boardElement.attribute("name");
839 		QString originalPath = fzzFilePaths.value(boardName, "");
840         QFileInfo originalInfo(originalPath);
841         QString copyPath = copyDir.absoluteFilePath(boardName);
842         QFileInfo copyInfo(copyPath);
843 
844         if (!copyInfo.exists()) {
845             writePanelizerOutput(QString("failed to load copy'%1'").arg(copyPath));
846             return false;
847         }
848 
849         if (!originalInfo.exists()) {
850             writePanelizerOutput(QString("failed to find original'%1'").arg(originalPath));
851             return false;
852         }
853 
854         if (originalInfo.lastModified() > copyInfo.lastModified()) {
855             writePanelizerOutput(QString("copy %1 is not up to date--rerun the inscriber").arg(copyPath));
856             return false;
857         }
858 
859 		MainWindow * mainWindow = app->openWindowForService(false, 3);
860         mainWindow->setCloseSilently(true);
861 
862 		FolderUtils::setOpenSaveFolderAux(fzDir.absolutePath());
863 
864 		if (!mainWindow->loadWhich(copyPath, false, false, false, "")) {
865 			writePanelizerOutput(QString("failed to load '%1'").arg(copyPath));
866 			return false;
867 		}
868 
869         if (customPartsOnly &&
870             !mainWindow->hasAnyAlien() &&
871             !mainWindow->hasCustomBoardShape() &&
872             (mainWindow->pcbView()->checkLoadedTraces() == 0) &&
873             (checkDonuts(mainWindow, false) == 0) &&
874             (checkText(mainWindow, false) == 0)
875            )
876         {
877             mainWindow->close();
878             delete mainWindow,
879 			boardElement = boardElement.nextSiblingElement("board");
880 			continue;
881         }
882 
883 		foreach (QGraphicsItem * item, mainWindow->pcbView()->scene()->items()) {
884 			ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
885 			if (itemBase == NULL) continue;
886 
887 			itemBase->setMoveLock(false);
888 		}
889 
890 		QList<ItemBase *> boards = mainWindow->pcbView()->findBoard();
891         foreach (ItemBase * boardItem, boards) {
892 		    PanelItem * panelItem = new PanelItem;
893 		    panelItem->boardName = boardName;
894 		    panelItem->path = originalPath;
895 		    panelItem->required = required;
896 		    panelItem->maxOptional = optional;
897             panelItem->optionalPriority = priority;
898             panelItem->boardID = boardItem->id();
899 
900 		    QRectF sbr = boardItem->layerKinChief()->sceneBoundingRect();
901 		    panelItem->boardSizeInches = sbr.size() / GraphicsUtils::SVGDPI;
902 		    DebugDialog::debug(QString("board size inches c %1, %2, %3")
903 				    .arg(panelItem->boardSizeInches.width())
904 				    .arg(panelItem->boardSizeInches.height())
905 				    .arg(copyPath));
906 
907 		    /*
908 		    QSizeF boardSize = boardItem->size();
909 		    ResizableBoard * resizableBoard = qobject_cast<ResizableBoard *>(boardItem->layerKinChief());
910 		    if (resizableBoard != NULL) {
911 			    panelItem->boardSizeInches = resizableBoard->getSizeMM() / 25.4;
912 			    DebugDialog::debug(QString("board size inches a %1, %2, %3")
913 				    .arg(panelItem->boardSizeInches.width())
914 				    .arg(panelItem->boardSizeInches.height())
915 				    .arg(path), boardItem->sceneBoundingRect());
916 		    }
917 		    else {
918 			    panelItem->boardSizeInches = boardSize / GraphicsUtils::SVGDPI;
919 			    DebugDialog::debug(QString("board size inches b %1, %2, %3")
920 				    .arg(panelItem->boardSizeInches.width())
921 				    .arg(panelItem->boardSizeInches.height())
922 				    .arg(path), boardItem->sceneBoundingRect());
923 		    }
924 		    */
925 
926 		    bool tooBig = false;
927 		    if (panelItem->boardSizeInches.width() >= bigPanelType->width) {
928 			    tooBig = panelItem->boardSizeInches.width() >= bigPanelType->height;
929 			    if (!tooBig) {
930 				    tooBig = panelItem->boardSizeInches.height() >= bigPanelType->width;
931 			    }
932 		    }
933 
934 		    if (!tooBig) {
935 			    if (panelItem->boardSizeInches.height() >= bigPanelType->height) {
936 				    tooBig = panelItem->boardSizeInches.height() >= bigPanelType->width;
937 				    if (!tooBig) {
938 					    tooBig = panelItem->boardSizeInches.width() >= bigPanelType->height;
939 				    }
940 			    }
941 		    }
942 
943 		    if (tooBig) {
944 			    writePanelizerOutput(QString("board is too big for panel '%1'").arg(originalPath));
945 			    return false;
946 		    }
947 
948             makeSVGs(mainWindow, boardItem, boardName, layerThingList, norotateDir, copyInfo);
949 
950 		    refPanelItems << panelItem;
951         }
952 
953         // now save the rotated version
954 		mainWindow->pcbView()->selectAllItems(true, false);
955 	    QMatrix matrix;
956 		mainWindow->pcbView()->rotateX(90, false, NULL);
957 
958         foreach (ItemBase * boardItem, boards) {
959             makeSVGs(mainWindow, boardItem, boardName, layerThingList, rotateDir, copyInfo);
960         }
961 
962         mainWindow->close();
963         delete mainWindow,
964 
965 		boardElement = boardElement.nextSiblingElement("board");
966 	}
967 
968 	return true;
969 }
970 
initPanelParams(QDomElement & root,PanelParams & panelParams)971 bool Panelizer::initPanelParams(QDomElement & root, PanelParams & panelParams)
972 {
973 	panelParams.prefix = root.attribute("prefix");
974 	if (panelParams.prefix.isEmpty()) {
975 		writePanelizerOutput(QString("Output file prefix not specified"));
976 		return false;
977 	}
978 
979     QDomElement panels = root.firstChildElement("panels");
980     QDomElement panel = panels.firstChildElement("panel");
981     while (!panel.isNull()) {
982         PanelType * panelType = new PanelType;
983 
984         panelType->name = panel.attribute("name");
985 
986         bool ok;
987 	    panelType->width = TextUtils::convertToInches(panel.attribute("width"), &ok, false);
988 	    if (!ok) {
989 		    writePanelizerOutput(QString("Can't parse panel width '%1'").arg(panel.attribute("width")));
990 		    return false;
991 	    }
992 
993 	    panelType->height = TextUtils::convertToInches(panel.attribute("height"), &ok, false);
994 	    if (!ok) {
995 		    writePanelizerOutput(QString("Can't parse panel height '%1'").arg(panel.attribute("height")));
996 		    return false;
997 	    }
998 
999 	    panelType->c1 = panel.attribute("c1").toDouble(&ok);
1000 	    if (!ok) {
1001 		    writePanelizerOutput(QString("Can't parse panel c1 '%1'").arg(panel.attribute("c1")));
1002 		    return false;
1003 	    }
1004 
1005 	    panelType->c2 = panel.attribute("c2").toDouble(&ok);
1006 	    if (!ok) {
1007 		    writePanelizerOutput(QString("Can't parse panel c2 '%1'").arg(panel.attribute("c2")));
1008 		    return false;
1009 	    }
1010 
1011         panelParams.panelTypes << panelType;
1012 
1013         panel = panel.nextSiblingElement("panel");
1014     }
1015 
1016     if (panelParams.panelTypes.count() == 0) {
1017 		writePanelizerOutput(QString("No panel types defined."));
1018 		return false;
1019     }
1020 
1021     bool ok;
1022 	panelParams.panelSpacing = TextUtils::convertToInches(root.attribute("spacing"), &ok, false);
1023 	if (!ok) {
1024 		writePanelizerOutput(QString("Can't parse panel spacing '%1'").arg(root.attribute("spacing")));
1025 		return false;
1026 	}
1027 
1028 	panelParams.panelBorder = TextUtils::convertToInches(root.attribute("border"), &ok, false);
1029 	if (!ok) {
1030 		writePanelizerOutput(QString("Can't parse panel border '%1'").arg(root.attribute("border")));
1031 		return false;
1032 	}
1033 
1034 	return true;
1035 
1036 }
1037 
placeBestFit(Tile * tile,UserData userData)1038 int Panelizer::placeBestFit(Tile * tile, UserData userData) {
1039 	if (TiGetType(tile) != Tile::SPACE) return 0;
1040 
1041 	BestPlace * bestPlace = (BestPlace *) userData;
1042 	TileRect tileRect;
1043 	TiToRect(tile, &tileRect);
1044 	int w = tileRect.xmaxi - tileRect.xmini;
1045 	int h = tileRect.ymaxi - tileRect.ymini;
1046 	if (bestPlace->width > w && bestPlace->height > w) {
1047 		return 0;
1048 	}
1049 
1050 	int fitCount = 0;
1051 	bool fit[4];
1052 	double area[4];
1053 	for (int i = 0; i < 4; i++) {
1054 		fit[i] = false;
1055 		area[i] = Worst;
1056 	}
1057 
1058 	if (w >= bestPlace->width && h >= bestPlace->height) {
1059 		fit[0] = true;
1060 		area[0] = (w * h) - (bestPlace->width * bestPlace->height);
1061 		fitCount++;
1062 	}
1063 	if (h >= bestPlace->width && w >= bestPlace->height) {
1064 		fit[1] = true;
1065 		area[1] = (w * h) - (bestPlace->width * bestPlace->height);
1066 		fitCount++;
1067 	}
1068 
1069 	if (!fit[0] && w >= bestPlace->width) {
1070 		// see if adjacent tiles below are open
1071 		TileRect temp;
1072 		temp.xmini = tileRect.xmini;
1073 		temp.xmaxi = temp.xmini + bestPlace->width;
1074 		temp.ymini = tileRect.ymini;
1075 		temp.ymaxi = temp.ymini + bestPlace->height;
1076 		if (temp.ymaxi < bestPlace->maxRect.ymaxi) {
1077 			QList<Tile*> spaces;
1078 			TiSrArea(tile, bestPlace->plane, &temp, allSpaces, &spaces);
1079 			if (spaces.count()) {
1080 				int y = temp.ymaxi;
1081 				foreach (Tile * t, spaces) {
1082 					if (YMAX(t) > y) y = YMAX(t);			// find the bottom of the lowest open tile
1083 				}
1084 				fit[2] = true;
1085 				fitCount++;
1086 				area[2] = (w * (y - temp.ymini)) - (bestPlace->width * bestPlace->height);
1087 			}
1088 		}
1089 	}
1090 
1091 	if (!fit[1] && w >= bestPlace->height) {
1092 		// see if adjacent tiles below are open
1093 		TileRect temp;
1094 		temp.xmini = tileRect.xmini;
1095 		temp.xmaxi = temp.xmini + bestPlace->height;
1096 		temp.ymini = tileRect.ymini;
1097 		temp.ymaxi = temp.ymini + bestPlace->width;
1098 		if (temp.ymaxi < bestPlace->maxRect.ymaxi) {
1099 			QList<Tile*> spaces;
1100 			TiSrArea(tile, bestPlace->plane, &temp, allSpaces, &spaces);
1101 			if (spaces.count()) {
1102 				int y = temp.ymaxi;
1103 				foreach (Tile * t, spaces) {
1104 					if (YMAX(t) > y) y = YMAX(t);			// find the bottom of the lowest open tile
1105 				}
1106 				fit[3] = true;
1107 				fitCount++;
1108 				area[3] = (w * (y - temp.ymini)) - (bestPlace->width * bestPlace->height);
1109 			}
1110 		}
1111 	}
1112 
1113 	if (fitCount == 0) return 0;
1114 
1115 	// area is white space remaining after board has been inserteds
1116 
1117 	int result = -1;
1118 	for (int i = 0; i < 4; i++) {
1119 		if (area[i] < bestPlace->bestArea) {
1120 			result = i;
1121 			break;
1122 		}
1123 	}
1124 	if (result < 0) return 0;			// current bestArea is better
1125 
1126 	bestPlace->bestTile = tile;
1127 	bestPlace->bestTileRect = tileRect;
1128 	if (fitCount == 1 || (bestPlace->width == bestPlace->height)) {
1129 		if (fit[0] || fit[2]) {
1130 			bestPlace->rotate90 = false;
1131 		}
1132 		else {
1133 			bestPlace->rotate90 = true;
1134 		}
1135 		bestPlace->bestArea = area[result];
1136 		return 0;
1137 	}
1138 
1139 	if (TiGetType(BL(tile)) == Tile::BUFFER) {
1140 		// this is a leftmost tile
1141 		// select for creating the biggest area after putting in the tile;
1142 		double a1 = (w - bestPlace->width) * (bestPlace->height);
1143 		double a2 = (h - bestPlace->height) * w;
1144 		double a = qMax(a1, a2);
1145 		double b1 = (w - bestPlace->height) * (bestPlace->width);
1146 		double b2 = (h - bestPlace->width) * w;
1147 		double b = qMax(b1, b2);
1148 		bestPlace->rotate90 = (a < b);
1149 		if (bestPlace->rotate90) {
1150 			bestPlace->bestArea = fit[1] ? area[1] : area[3];
1151 		}
1152 		else {
1153 			bestPlace->bestArea = fit[0] ? area[0] : area[2];
1154 		}
1155 
1156 		return 0;
1157 	}
1158 
1159 	TileRect temp;
1160 	temp.xmini = bestPlace->maxRect.xmini;
1161 	temp.xmaxi = tileRect.xmini - 1;
1162 	temp.ymini = tileRect.ymini;
1163 	temp.ymaxi = tileRect.ymaxi;
1164 	QList<Tile*> obstacles;
1165 	TiSrArea(tile, bestPlace->plane, &temp, allObstacles, &obstacles);
1166 	int maxBottom = 0;
1167 	foreach (Tile * obstacle, obstacles) {
1168 		if (YMAX(obstacle) > maxBottom) maxBottom = YMAX(obstacle);
1169 	}
1170 
1171 	if (tileRect.ymini + bestPlace->width <= maxBottom && tileRect.ymini + bestPlace->height <= maxBottom) {
1172 		// use the max length
1173 		if (bestPlace->width >= bestPlace->height) {
1174 			bestPlace->rotate90 = true;
1175 			bestPlace->bestArea = fit[1] ? area[1] : area[3];
1176 		}
1177 		else {
1178 			bestPlace->rotate90 = false;
1179 			bestPlace->bestArea = fit[0] ? area[0] : area[2];
1180 		}
1181 
1182 		return 0;
1183 	}
1184 
1185 	if (tileRect.ymini + bestPlace->width > maxBottom && tileRect.ymini + bestPlace->height > maxBottom) {
1186 		// use the min length
1187 		if (bestPlace->width <= bestPlace->height) {
1188 			bestPlace->rotate90 = true;
1189 			bestPlace->bestArea = fit[1] ? area[1] : area[3];
1190 		}
1191 		else {
1192 			bestPlace->rotate90 = false;
1193 			bestPlace->bestArea = fit[0] ? area[0] : area[2];
1194 		}
1195 
1196 		return 0;
1197 	}
1198 
1199 	if (tileRect.ymini + bestPlace->width <= maxBottom) {
1200 		bestPlace->rotate90 = true;
1201 		bestPlace->bestArea = fit[1] ? area[1] : area[3];
1202 		return 0;
1203 	}
1204 
1205 	bestPlace->rotate90 = false;
1206 	bestPlace->bestArea = fit[0] ? area[0] : area[2];
1207 	return 0;
1208 }
1209 
addOptional(int optionalCount,QList<PanelItem * > & refPanelItems,QList<PanelItem * > & insertPanelItems,PanelParams & panelParams,QList<PlanePair * > & planePairs)1210 void Panelizer::addOptional(int optionalCount, QList<PanelItem *> & refPanelItems, QList<PanelItem *> & insertPanelItems, PanelParams & panelParams, QList<PlanePair *> & planePairs)
1211 {
1212     if (optionalCount == 0) return;
1213 
1214     QList<PanelItem *> copies(refPanelItems);
1215     qSort(copies.begin(), copies.end(), byOptionalPriority);
1216 	while (optionalCount > 0) {
1217         int pool = 0;
1218         int priority = -1;
1219         foreach(PanelItem * panelItem, copies) {
1220             if (panelItem->maxOptional > 0) {
1221                 if (priority == -1) priority = panelItem->optionalPriority;
1222                 if (panelItem->optionalPriority == priority) {
1223                     pool += panelItem->maxOptional;
1224                 }
1225                 else break;
1226             }
1227         }
1228         if (pool == 0) break;
1229 
1230 		int ix = qFloor(qrand() * pool / (double) RAND_MAX);
1231 		int soFar = 0;
1232 		foreach (PanelItem * panelItem, copies) {
1233 			if (panelItem->maxOptional == 0) continue;
1234 			if (panelItem->optionalPriority != priority) continue;
1235 
1236 			if (ix >= soFar && ix < soFar + panelItem->maxOptional) {
1237 				PanelItem * copy = new PanelItem(panelItem);
1238 				if (bestFitOne(copy, panelParams, planePairs, false, false)) {
1239 					// got one
1240 					panelItem->maxOptional--;
1241 					optionalCount--;
1242 					insertPanelItems.append(copy);
1243 				}
1244 				else {
1245 					// don't bother trying this one again
1246 					optionalCount -= panelItem->maxOptional;
1247 					panelItem->maxOptional = 0;
1248 				}
1249 				break;
1250 			}
1251 
1252 			soFar += panelItem->maxOptional;
1253 		}
1254 	}
1255 }
1256 
1257 /////////////////////////////////////////////////////////////////////////////////
1258 
inscribe(FApplication * app,const QString & panelFilename,bool drc,bool noMessages)1259 void Panelizer::inscribe(FApplication * app, const QString & panelFilename, bool drc, bool noMessages)
1260 {
1261     initPanelizerOutput(panelFilename, "inscribe");
1262 
1263 	QFile file(panelFilename);
1264 
1265     QFileInfo info(panelFilename);
1266     QDir copyDir = info.absoluteDir();
1267     copyDir.mkdir("copies");
1268     copyDir.cd("copies");
1269     if (!copyDir.exists()) {
1270 		DebugDialog::debug(QString("unable to create 'copies' folder in '%1'").arg(info.absoluteDir().absolutePath()));
1271 		return;
1272 	}
1273 
1274 	QString errorStr;
1275 	int errorLine;
1276 	int errorColumn;
1277 
1278 	DebugDialog::setEnabled(true);
1279 
1280 	QDomDocument domDocument;
1281 	if (!domDocument.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
1282         writePanelizerOutput(QString("Unable to parse '%1': '%2' line:%3 column:%4").arg(panelFilename).arg(errorStr).arg(errorLine).arg(errorColumn));
1283 		return;
1284 	}
1285 
1286 	QDomElement root = domDocument.documentElement();
1287 	if (root.isNull() || root.tagName() != "panelizer") {
1288 		writePanelizerOutput(QString("root element is not 'panelizer'"));
1289 		return;
1290 	}
1291 
1292 	PanelParams panelParams;
1293 	if (!initPanelParams(root, panelParams)) return;
1294 
1295 	QDir outputDir = QDir::temp();
1296 	QDir fzDir(outputDir);
1297 	fzDir.cd("fz");
1298 	if (!fzDir.exists()) {
1299 		writePanelizerOutput(QString("unable to create fz folder in '%1'").arg(outputDir.absolutePath()));
1300 		return;
1301 	}
1302 
1303 	DebugDialog::debug(QString("fz folder '%1'\n").arg(fzDir.absolutePath()));
1304 
1305 	QDomElement boards = root.firstChildElement("boards");
1306 	QDomElement board = boards.firstChildElement("board");
1307 	if (board.isNull()) {
1308 		writePanelizerOutput(QString("no <board> elements found"));
1309 		return;
1310 	}
1311 
1312 	QHash<QString, QString> fzzFilePaths;
1313 	QDomElement paths = root.firstChildElement("paths");
1314 	QDomElement path = paths.firstChildElement("path");
1315 	if (path.isNull()) {
1316 		writePanelizerOutput(QString("no <path> elements found"));
1317 		return;
1318 	}
1319 
1320     QFileInfo pinfo(panelFilename);
1321 	collectFiles(pinfo.absoluteDir(), path, fzzFilePaths);
1322 	if (fzzFilePaths.count() == 0) {
1323 		writePanelizerOutput(QString("no fzz files found in paths"));
1324 		return;
1325 	}
1326 
1327 	board = boards.firstChildElement("board");
1328 	if (!checkBoards(board, fzzFilePaths)) return;
1329 
1330 	app->createUserDataStoreFolderStructure();
1331 	app->registerFonts();
1332 	app->loadReferenceModel("", false);
1333 
1334 	board = boards.firstChildElement("board");
1335 	while (!board.isNull()) {
1336 		MainWindow * mainWindow = inscribeBoard(board, fzzFilePaths, app, fzDir, drc, noMessages, copyDir);
1337 		if (mainWindow) {
1338 			mainWindow->setCloseSilently(true);
1339 			mainWindow->close();
1340             delete mainWindow;
1341 		}
1342 		board = board.nextSiblingElement("board");
1343 	}
1344 
1345     writePanelizerFilenames(panelFilename);
1346 
1347 	// TODO: delete temp fz folder
1348 
1349 }
1350 
inscribeBoard(QDomElement & board,QHash<QString,QString> & fzzFilePaths,FApplication * app,QDir & fzDir,bool drc,bool noMessages,QDir & copyDir)1351 MainWindow * Panelizer::inscribeBoard(QDomElement & board, QHash<QString, QString> & fzzFilePaths, FApplication * app, QDir & fzDir, bool drc, bool noMessages, QDir & copyDir)
1352 {
1353 	QString boardName = board.attribute("name");
1354 	int optional = board.attribute("maxOptionalCount", "").toInt();
1355 	int required = board.attribute("requiredCount", "").toInt();
1356     if (optional <= 0 && required <= 0) return NULL;
1357 
1358 	QString originalPath = fzzFilePaths.value(boardName, "");
1359     QFileInfo originalInfo(originalPath);
1360     QString copyPath = copyDir.absoluteFilePath(originalInfo.fileName());
1361     QFileInfo copyInfo(copyPath);
1362 
1363 
1364     if (!copyInfo.exists()) {
1365     }
1366     else {
1367         DebugDialog::debug("");
1368         //DebugDialog::debug(oldPath);
1369         //DebugDialog::debug(copyPath);
1370         DebugDialog::debug(QString("%1 original=%2, copy=%3").arg(originalInfo.fileName()).arg(originalInfo.lastModified().toString()).arg(copyInfo.lastModified().toString()));
1371         if (originalInfo.lastModified() <= copyInfo.lastModified()) {
1372             DebugDialog::debug(QString("copy %1 is up to date").arg(copyPath));
1373             return NULL;
1374         }
1375     }
1376 
1377     QFile file(originalPath);
1378     bool ok = FolderUtils::slamCopy(file, copyPath);
1379     if (!ok) {
1380         QMessageBox::warning(NULL, QObject::tr("Fritzing"), QObject::tr("unable to copy file '%1' to '%2'.").arg(originalPath).arg(copyPath));
1381         return NULL;
1382     }
1383 
1384 	MainWindow * mainWindow = app->openWindowForService(false, 3);
1385 
1386 	FolderUtils::setOpenSaveFolderAux(fzDir.absolutePath());
1387 
1388 	if (!mainWindow->loadWhich(copyPath, false, false, false, "")) {
1389 		writePanelizerOutput(QString("failed to load '%1'").arg(copyPath));
1390 		return mainWindow;
1391 	}
1392 
1393     // performance optimization
1394     mainWindow->pcbView()->setGridSize("0.1in");
1395     mainWindow->pcbView()->showGrid(false);
1396 
1397     QList<ItemBase *> items = mainWindow->pcbView()->selectAllObsolete();
1398 	if (items.count() > 0) {
1399         QFileInfo info(copyPath);
1400         writePanelizerOutput(QString("%2 ... %1 obsolete items").arg(items.count()).arg(info.fileName()));
1401     }
1402 
1403     int moved = mainWindow->pcbView()->checkLoadedTraces();
1404     if (moved > 0) {
1405         QFileInfo info(originalPath);
1406         QString message = QObject::tr("%2 ... %1 wires moved from their saved position").arg(moved).arg(info.fileName());
1407         if (!noMessages) {
1408             QMessageBox::warning(NULL, QObject::tr("Fritzing"), message);
1409         }
1410         writePanelizerOutput(message);
1411         collectFilenames(info.fileName());
1412     }
1413 
1414 	foreach (QGraphicsItem * item, mainWindow->pcbView()->scene()->items()) {
1415 		ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
1416 		if (itemBase == NULL) continue;
1417 
1418 		itemBase->setMoveLock(false);
1419 	}
1420 
1421 	QString fritzingVersion = mainWindow->fritzingVersion();
1422 	VersionThing versionThing;
1423 	versionThing.majorVersion = 0;
1424 	versionThing.minorVersion = 7;
1425 	versionThing.minorSubVersion = 0;
1426 	versionThing.releaseModifier = "";
1427 
1428 	VersionThing versionThingFz;
1429 	Version::toVersionThing(fritzingVersion, versionThingFz);
1430 	bool oldGround = !Version::greaterThan(versionThing, versionThingFz);
1431 
1432     bool filled = false;
1433     QList<ItemBase *> boards = mainWindow->pcbView()->findBoard();
1434     bool wasOne = false;
1435 	foreach (ItemBase * boardItem, boards) {
1436         mainWindow->pcbView()->selectAllItems(false, false);
1437         boardItem->setSelected(true);
1438 	    if (boardItem->prop("layers").compare("1") == 0) {
1439             mainWindow->swapLayers(boardItem, 2, "", 0);
1440             ProcessEventBlocker::processEvents();
1441             wasOne = true;
1442         }
1443 
1444         LayerList groundLayers;
1445         groundLayers << ViewLayer::GroundPlane0 << ViewLayer::GroundPlane1;
1446         foreach (ViewLayer::ViewLayerID viewLayerID, groundLayers) {
1447             QString fillType = mainWindow->pcbView()->characterizeGroundFill(viewLayerID);
1448 	        if (fillType == GroundPlane::fillTypeNone) {
1449 		        mainWindow->copperFill(viewLayerID);
1450                 ProcessEventBlocker::processEvents();
1451 		        filled = true;
1452 	        }
1453 	        else if ((fillType == GroundPlane::fillTypeGround) && oldGround) {
1454 		        mainWindow->groundFill(viewLayerID);
1455                 ProcessEventBlocker::processEvents();
1456 		        filled = true;
1457 	        }
1458         }
1459     }
1460 
1461 	if (filled) {
1462 		mainWindow->saveAsShareable(copyPath, true);
1463 		DebugDialog::debug(QString("%1 filled:%2").arg(copyPath).arg(filled));
1464 	}
1465 
1466     checkDonuts(mainWindow, !noMessages);
1467     checkText(mainWindow, !noMessages);
1468 
1469     if (drc) {
1470 	    foreach (ItemBase * boardItem, boards) {
1471             mainWindow->pcbView()->resetKeepout();
1472             mainWindow->pcbView()->selectAllItems(false, false);
1473             boardItem->setSelected(true);
1474             QStringList messages = mainWindow->newDesignRulesCheck(false);
1475             if (messages.count() > 0) {
1476                 QFileInfo info(mainWindow->fileName());
1477                 writePanelizerOutput(QString("%2 ... %1 drc complaints").arg(messages.count()).arg(info.fileName()));
1478                 collectFilenames(info.fileName());
1479                 //foreach (QString message, messages) {
1480                 //    writePanelizerOutput("\t" + message);
1481                 //}
1482             }
1483         }
1484     }
1485 
1486 	return mainWindow;
1487 }
1488 
makeSVGs(MainWindow * mainWindow,ItemBase * board,const QString & boardName,QList<LayerThing> & layerThingList,QDir & saveDir,QFileInfo & copyInfo)1489 void Panelizer::makeSVGs(MainWindow * mainWindow, ItemBase * board, const QString & boardName, QList<LayerThing> & layerThingList, QDir & saveDir, QFileInfo & copyInfo) {
1490 	try {
1491 
1492 		QString maskTop;
1493 		QString maskBottom;
1494         QStringList texts;
1495         QMultiHash<long, ConnectorItem *> treatAsCircle;
1496 
1497         bool needsRedo = false;
1498         int missing = 0;
1499 		foreach (LayerThing layerThing, layerThingList) {
1500 			QString name = layerThing.name;
1501             QString filename = saveDir.absoluteFilePath(QString("%1_%2_%3.svg").arg(boardName).arg(board->id()).arg(name));
1502             QFileInfo info(filename);
1503             if (info.exists()) {
1504                 if (info.lastModified() <= copyInfo.lastModified()) {
1505                     // need to save these files again
1506                     needsRedo = true;
1507                     break;
1508                 }
1509             }
1510             else {
1511                 missing++;
1512             }
1513         }
1514 
1515         if (!needsRedo) {
1516             if (missing < layerThingList.count()) {
1517                 // assume some number of missing files wouldn't have been written out anyway
1518                 // but if all are missing, then they were never written in the first place
1519                 return;
1520             }
1521         }
1522 
1523 		foreach (LayerThing layerThing, layerThingList) {
1524 			QString name = layerThing.name;
1525             QString filename = saveDir.absoluteFilePath(QString("%1_%2_%3.svg").arg(boardName).arg(board->id()).arg(name));
1526 
1527 			SVG2gerber::ForWhy forWhy = layerThing.forWhy;
1528 			QList<ItemBase *> copperLogoItems, holes;
1529             switch (forWhy) {
1530                 case SVG2gerber::ForPasteMask:
1531                     mainWindow->pcbView()->hideHoles(holes);
1532                 case SVG2gerber::ForMask:
1533 				    mainWindow->pcbView()->hideCopperLogoItems(copperLogoItems);
1534                 default:
1535                     break;
1536 			}
1537 
1538 	        RenderThing renderThing;
1539             renderThing.printerScale = GraphicsUtils::SVGDPI;
1540             renderThing.blackOnly = true;
1541             renderThing.dpi = GraphicsUtils::StandardFritzingDPI;
1542             renderThing.hideTerminalPoints = true;
1543             renderThing.selectedItems = renderThing.renderBlocker = false;
1544 			QString one = mainWindow->pcbView()->renderToSVG(renderThing, board, layerThing.layerList);
1545 
1546 			QString clipString;
1547 		    bool wantText = false;
1548 			switch (forWhy) {
1549 				case SVG2gerber::ForOutline:
1550 					one = GerberGenerator::cleanOutline(one);
1551 					break;
1552 				case SVG2gerber::ForPasteMask:
1553 					mainWindow->pcbView()->restoreCopperLogoItems(copperLogoItems);
1554 					mainWindow->pcbView()->restoreCopperLogoItems(holes);
1555                     one = mainWindow->pcbView()->makePasteMask(one, board, GraphicsUtils::StandardFritzingDPI, layerThing.layerList);
1556                     if (one.isEmpty()) continue;
1557 
1558 					forWhy = SVG2gerber::ForCopper;
1559                     break;
1560 				case SVG2gerber::ForMask:
1561 					mainWindow->pcbView()->restoreCopperLogoItems(copperLogoItems);
1562 					one = TextUtils::expandAndFill(one, "black", GerberGenerator::MaskClearanceMils * 2);
1563 					forWhy = SVG2gerber::ForCopper;
1564 					if (name.contains("bottom")) {
1565 						maskBottom = one;
1566 					}
1567 					else {
1568 						maskTop = one;
1569 					}
1570 					break;
1571 				case SVG2gerber::ForSilk:
1572                     wantText = true;
1573 					if (name.contains("bottom")) {
1574 						clipString = maskBottom;
1575 					}
1576 					else {
1577 						clipString = maskTop;
1578 					}
1579 					break;
1580                 case SVG2gerber::ForCopper:
1581                 case SVG2gerber::ForDrill:
1582                     treatAsCircle.clear();
1583                     foreach (QGraphicsItem * item, mainWindow->pcbView()->scene()->collidingItems(board)) {
1584                         ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
1585                         if (connectorItem == NULL) continue;
1586                         if (!connectorItem->isPath()) continue;
1587                         if (connectorItem->radius() == 0) continue;
1588 
1589                         treatAsCircle.insert(connectorItem->attachedToID(), connectorItem);
1590                     }
1591                     wantText = true;
1592                     break;
1593 				default:
1594                     wantText = true;
1595 					break;
1596 			}
1597 
1598             if (wantText) {
1599                 collectTexts(one, texts);
1600                 //DebugDialog::debug("one " + one);
1601             }
1602 
1603             QString two = GerberGenerator::clipToBoard(one, board, name, forWhy, clipString, true, treatAsCircle);
1604             treatAsCircle.clear();
1605             if (two.isEmpty()) continue;
1606 
1607             TextUtils::writeUtf8(filename, two);
1608 		}
1609 
1610         if (texts.count() > 0) {
1611             QString filename = saveDir.absoluteFilePath(QString("%1_%2_%3.txt").arg(boardName).arg(board->id()).arg("texts"));
1612             TextUtils::writeUtf8(filename, texts.join("\n"));
1613         }
1614 	}
1615 	catch (const char * msg) {
1616 		DebugDialog::debug(QString("panelizer error 1 %1 %2").arg(boardName).arg(msg));
1617 	}
1618 	catch (const QString & msg) {
1619 		DebugDialog::debug(QString("panelizer error 2 %1 %2").arg(boardName).arg(msg));
1620 	}
1621 	catch (...) {
1622 		DebugDialog::debug(QString("panelizer error 3 %1").arg(boardName));
1623 	}
1624 }
1625 
doOnePanelItem(PlanePair * planePair,QList<LayerThing> & layerThingList,PanelItem * panelItem,QDir & svgDir)1626 void Panelizer::doOnePanelItem(PlanePair * planePair, QList<LayerThing> & layerThingList, PanelItem * panelItem, QDir & svgDir) {
1627 	try {
1628 
1629 		for (int i = 0; i < planePair->svgs.count(); i++) {
1630 			QString name = layerThingList.at(i).name;
1631 
1632             QString rot = panelItem->rotate90 ? "rotate" : "norotate";
1633             QString filename = svgDir.absoluteFilePath(QString("%1/%2_%3_%4.svg").arg(rot).arg(panelItem->boardName).arg(panelItem->boardID).arg(name));
1634             QFile file(filename);
1635             if (file.open(QFile::ReadOnly)) {
1636 			    QString one = file.readAll();
1637 			    if (one.isEmpty()) continue;
1638 
1639 			    int left = one.indexOf("<svg");
1640 			    left = one.indexOf(">", left + 1);
1641 			    int right = one.lastIndexOf("<");
1642 			    one = QString("<g transform='translate(%1,%2)'>\n").arg(panelItem->x * GraphicsUtils::StandardFritzingDPI).arg(panelItem->y * GraphicsUtils::StandardFritzingDPI) +
1643 							    one.mid(left + 1, right - left - 1) +
1644 							    "</g>\n";
1645 
1646 			    planePair->svgs.replace(i, planePair->svgs.at(i) + one);
1647             }
1648             else {
1649                 DebugDialog::debug(QString("panelizer error? 1 %1 %2").arg(filename).arg("one text not found"));
1650             }
1651 		}
1652 	}
1653 	catch (const char * msg) {
1654 		DebugDialog::debug(QString("panelizer error 1 %1 %2").arg(panelItem->boardName).arg(msg));
1655 	}
1656 	catch (const QString & msg) {
1657 		DebugDialog::debug(QString("panelizer error 2 %1 %2").arg(panelItem->boardName).arg(msg));
1658 	}
1659 	catch (...) {
1660 		DebugDialog::debug(QString("panelizer error 3 %1").arg(panelItem->boardName));
1661 	}
1662 }
1663 
shrinkLastPanel(QList<PlanePair * > & planePairs,QList<PanelItem * > & insertPanelItems,PanelParams & panelParams,bool customPartsOnly)1664 void Panelizer::shrinkLastPanel( QList<PlanePair *> & planePairs, QList<PanelItem *> & insertPanelItems, PanelParams & panelParams, bool customPartsOnly)
1665 {
1666     PlanePair * lastPlanePair = planePairs.last();
1667     PlanePair * smallPlanePair = makePlanePair(panelParams, false);
1668     smallPlanePair->index = lastPlanePair->index;
1669     QList<PlanePair *> smallPlanePairs;
1670     smallPlanePairs << smallPlanePair;
1671     bool canFitSmaller = true;
1672     QList<PanelItem *> smallPanelItems;
1673     foreach (PanelItem * panelItem, insertPanelItems) {
1674         if (panelItem->planePair != lastPlanePair) continue;
1675 
1676         PanelItem * copy = new PanelItem(panelItem);
1677         smallPanelItems << copy;
1678 
1679 		if (!bestFitOne(copy, panelParams, smallPlanePairs, false, customPartsOnly)) {
1680             canFitSmaller = false;
1681         }
1682 	}
1683 
1684     if (canFitSmaller) {
1685         foreach (PanelItem * panelItem, insertPanelItems) {
1686             if (panelItem->planePair != lastPlanePair) continue;
1687 
1688             insertPanelItems.removeOne(panelItem);
1689             delete panelItem;
1690         }
1691         planePairs.removeOne(lastPlanePair);
1692         delete lastPlanePair;
1693         insertPanelItems.append(smallPanelItems);
1694         planePairs.append(smallPlanePair);
1695     }
1696     else {
1697         foreach (PanelItem * copy, smallPanelItems) {
1698             delete copy;
1699         }
1700         delete smallPlanePair;
1701     }
1702 
1703 }
1704 
1705 
checkText(MainWindow * mainWindow,bool displayMessage)1706 int Panelizer::checkText(MainWindow * mainWindow, bool displayMessage) {
1707 	QHash<QString, QString> svgHash;
1708     QList<ItemBase *> missing;
1709 
1710     foreach (QGraphicsItem * item, mainWindow->pcbView()->scene()->items()) {
1711         ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
1712         if (itemBase == NULL) continue;
1713         if (!itemBase->isEverVisible()) continue;
1714 
1715         double factor;
1716         QString itemSvg = itemBase->retrieveSvg(itemBase->viewLayerID(), svgHash, false, GraphicsUtils::StandardFritzingDPI, factor);
1717 		if (itemSvg.isEmpty()) continue;
1718 
1719         QDomDocument doc;
1720         QString errorStr;
1721 	    int errorLine;
1722 	    int errorColumn;
1723 	    if (!doc.setContent(itemSvg, &errorStr, &errorLine, &errorColumn)) {
1724             DebugDialog::debug(QString("itembase svg failure %1").arg(itemBase->id()));
1725             continue;
1726         }
1727 
1728         QDomElement root = doc.documentElement();
1729         QDomNodeList domNodeList = root.elementsByTagName("path");
1730 	    for (int i = 0; i < domNodeList.count(); i++) {
1731             QDomElement textElement = domNodeList.at(i).toElement();
1732             if (textElement.attribute("fill").isEmpty() && textElement.attribute("stroke").isEmpty() && textElement.attribute("stroke-width").isEmpty()) {
1733                 missing.append(itemBase);
1734                 break;
1735             }
1736         }
1737     }
1738 
1739     if (displayMessage && missing.count() > 0) {
1740         mainWindow->pcbView()->selectAllItems(false, false);
1741         mainWindow->pcbView()->selectItems(missing);
1742         QMessageBox::warning(NULL, "Text", QString("There are %1 possible instances of parts with <path> elements missing stroke/fill/stroke-width attributes").arg(missing.count()));
1743     }
1744 
1745     if (missing.count() > 0) {
1746         QFileInfo info(mainWindow->fileName());
1747         writePanelizerOutput(QString("%2 ... There are %1 possible instances of parts with <path> elements missing stroke/fill/stroke-width attributes")
1748                 .arg(missing.count()).arg(info.fileName())
1749             );
1750         collectFilenames(info.fileName());
1751     }
1752 
1753     return  missing.count();
1754 }
1755 
checkDonuts(MainWindow * mainWindow,bool displayMessage)1756 int Panelizer::checkDonuts(MainWindow * mainWindow, bool displayMessage) {
1757     QList<ConnectorItem *> donuts;
1758     foreach (QGraphicsItem * item, mainWindow->pcbView()->scene()->items()) {
1759         ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
1760         if (connectorItem == NULL) continue;
1761         if (!connectorItem->attachedTo()->isEverVisible()) continue;
1762 
1763         if (connectorItem->isPath() && connectorItem->getCrossLayerConnectorItem() != NULL) {  // && connectorItem->radius() == 0
1764             connectorItem->debugInfo("possible donut");
1765             connectorItem->attachedTo()->debugInfo("\t");
1766             donuts << connectorItem;
1767         }
1768     }
1769 
1770     if (displayMessage && donuts.count() > 0) {
1771         mainWindow->pcbView()->selectAllItems(false, false);
1772         QSet<ItemBase *> itemBases;
1773         foreach (ConnectorItem * connectorItem, donuts) {
1774             itemBases.insert(connectorItem->attachedTo());
1775         }
1776         mainWindow->pcbView()->selectItems(itemBases.toList());
1777         QMessageBox::warning(NULL, "Donuts", QString("There are %1 possible donut connectors").arg(donuts.count() / 2));
1778     }
1779 
1780     if (donuts.count() > 0) {
1781         QFileInfo info(mainWindow->fileName());
1782         writePanelizerOutput(QString("%2 ... %1 possible donuts").arg(donuts.count() / 2).arg(info.fileName()));
1783         collectFilenames(mainWindow->fileName());
1784     }
1785 
1786     return donuts.count() / 2;
1787 }
1788 
1789 
bestFitLoop(QList<PanelItem * > & refPanelItems,PanelParams & panelParams,bool customPartsOnly,QList<PlanePair * > & returnPlanePairs,QList<PanelItem * > & returnInsertPanelItems,const QDir & svgDir)1790 int Panelizer::bestFitLoop(QList<PanelItem *> & refPanelItems, PanelParams & panelParams, bool customPartsOnly, QList<PlanePair *> & returnPlanePairs, QList<PanelItem *> & returnInsertPanelItems, const QDir & svgDir)
1791 {
1792 	int optionalCount = 0;
1793 	foreach (PanelItem * panelItem, refPanelItems) {
1794 		optionalCount += panelItem->maxOptional;
1795 	}
1796 
1797     QDir intermediates(svgDir);
1798     intermediates.mkdir("intermediates");
1799     intermediates.cd("intermediates");
1800 
1801     double bestCost = 0;
1802     double bestDivisor = 1;
1803     bool firstTime = true;
1804     for (int divisor = 1; true; divisor++) {
1805         QList<PlanePair *> planePairs;
1806         QList<PanelItem *> insertPanelItems;
1807 
1808         PlanePairIndex = 0;         // reset to zero for each new list of PlanePairs
1809 
1810         bool stillMoreThanOne = false;
1811 	    foreach (PanelItem * panelItem, refPanelItems) {
1812             int count = (panelItem->required + divisor - 1) / divisor;
1813             if (count > 1) stillMoreThanOne = true;
1814 		    for (int i = 0; i < count; i++) {
1815 			    PanelItem * copy = new PanelItem(panelItem);
1816 			    insertPanelItems.append(copy);
1817 		    }
1818 	    }
1819 
1820 	    planePairs << makePlanePair(panelParams, true);
1821 
1822 	    qSort(insertPanelItems.begin(), insertPanelItems.end(), areaGreaterThan);
1823 	    bestFit(insertPanelItems, panelParams, planePairs, customPartsOnly);
1824 
1825         shrinkLastPanel(planePairs, insertPanelItems, panelParams, customPartsOnly);
1826 
1827         double cost = calcCost(panelParams, planePairs, divisor);
1828         foreach (PlanePair * planePair, planePairs) {
1829 		    planePair->layoutSVG += "</svg>";
1830 		    QString fname = intermediates.absoluteFilePath(QString("%3.divisor_%4.cost_%1.panel_%2.layout_.svg")
1831                 .arg(panelParams.prefix).arg(planePair->index).arg(divisor).arg(cost)
1832             );
1833             TextUtils::writeUtf8(fname, planePair->layoutSVG);
1834 
1835             // make sure to leave layoutSVG without a trailing </svg> since optionals are added later
1836 		    planePair->layoutSVG.chop(6);
1837 	    }
1838 
1839 
1840         DebugDialog::debug("");
1841         DebugDialog::debug(QString("%1 panels, %2 additional copies of each: cost %3").arg(planePairs.count()).arg(divisor - 1).arg(cost));
1842         DebugDialog::debug("");
1843         if (firstTime) {
1844             bestCost = cost;
1845             bestDivisor = 1;
1846             returnPlanePairs = planePairs;
1847             returnInsertPanelItems = insertPanelItems;
1848             firstTime = false;
1849         }
1850         else {
1851             if (cost < bestCost) {
1852                 bestCost = cost;
1853                 bestDivisor = divisor;
1854                 foreach (PlanePair * planePair, returnPlanePairs) {
1855                     delete planePair;
1856                 }
1857                 foreach (PanelItem * panelItem, returnInsertPanelItems) {
1858                     delete panelItem;
1859                 }
1860                 returnPlanePairs.clear();
1861                 returnInsertPanelItems.clear();
1862 
1863                 returnPlanePairs = planePairs;
1864                 returnInsertPanelItems = insertPanelItems;
1865             }
1866         }
1867 
1868         if (customPartsOnly) break;
1869         if (planePairs.count() == 1) break;
1870         if (!stillMoreThanOne) break;
1871     }
1872 
1873 	addOptional(optionalCount, refPanelItems, returnInsertPanelItems, panelParams, returnPlanePairs);
1874     return bestDivisor;
1875 }
1876 
calcCost(PanelParams & panelParams,QList<PlanePair * > & planePairs,int divisor)1877 double Panelizer::calcCost(PanelParams & panelParams, QList<PlanePair *> & planePairs, int divisor) {
1878     double total = 0;
1879     foreach (PlanePair * planePair, planePairs) {
1880         foreach (PanelType * panelType, panelParams.panelTypes) {
1881             if (planePair->panelWidth == panelType->width && planePair->panelHeight == panelType->height) {
1882                 total += panelType->c1;
1883                 total += panelType->c2 * (divisor - 1);
1884                 break;
1885             }
1886         }
1887     }
1888 
1889     return total;
1890 }
1891 
writePanelizerOutput(const QString & message)1892 void Panelizer::writePanelizerOutput(const QString & message) {
1893     DebugDialog::debug(message);
1894     if (PanelizerOutputPath.length() > 0) {
1895         QFile file(PanelizerOutputPath);
1896         if (file.open(QFile::Append)) {
1897 		    QTextStream out(&file);
1898 		    out.setCodec("UTF-8");
1899 		    out << message << "\n";
1900 		    file.close();
1901         }
1902     }
1903 }
1904 
initPanelizerOutput(const QString & panelFilename,const QString & msg)1905 void Panelizer::initPanelizerOutput(const QString & panelFilename, const QString & msg) {
1906     PanelizerFileNames.clear();
1907     QFileInfo info(panelFilename);
1908     PanelizerOutputPath = info.absoluteDir().absoluteFilePath("panelizer_output.txt");
1909 
1910     QDateTime dt = QDateTime::currentDateTime();
1911     writePanelizerOutput(QString("\n--------- %1 --- %2 ---")
1912                             .arg(msg).arg(dt.toString())
1913                         );
1914 }
1915 
collectFilenames(const QString & filename)1916 void Panelizer::collectFilenames(const QString & filename) {
1917     if (filename.endsWith(".fz")) {
1918         PanelizerFileNames.insert(filename + "z");
1919     }
1920     else PanelizerFileNames.insert(filename);
1921 }
1922 
writePanelizerFilenames(const QString & panelFilename)1923 void Panelizer::writePanelizerFilenames(const QString & panelFilename) {
1924     if (PanelizerFileNames.count() == 0) return;
1925 
1926     QFileInfo info(panelFilename);
1927     QDateTime dt = QDateTime::currentDateTime();
1928     QString path = info.absoluteDir().absoluteFilePath("panelizer_files_%1.txt").arg(dt.toString().replace(":", "."));
1929     QFile file(path);
1930     if (file.open(QFile::WriteOnly)) {
1931         QTextStream out(&file);
1932 		out.setCodec("UTF-8");
1933         foreach (QString name, PanelizerFileNames) {
1934             out << name << "\n";
1935         }
1936         file.close();
1937     }
1938 }
1939