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