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.a
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: 6991 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-26 15:24:41 +0200 (Fr, 26. Apr 2013) $
24 
25 ********************************************************************/
26 
27 #include "drc.h"
28 #include "../connectors/svgidlayer.h"
29 #include "../sketch/pcbsketchwidget.h"
30 #include "../debugdialog.h"
31 #include "../items/virtualwire.h"
32 #include "../items/tracewire.h"
33 #include "../items/via.h"
34 #include "../utils/graphicsutils.h"
35 #include "../utils/folderutils.h"
36 #include "../utils/textutils.h"
37 #include "../connectors/connectoritem.h"
38 #include "../items/moduleidnames.h"
39 #include "../processeventblocker.h"
40 #include "../fsvgrenderer.h"
41 #include "../viewlayer.h"
42 #include "../processeventblocker.h"
43 
44 #include <qmath.h>
45 #include <QApplication>
46 #include <QMessageBox>
47 #include <QPixmap>
48 #include <QSet>
49 #include <QSettings>
50 #include <QDialogButtonBox>
51 #include <QVBoxLayout>
52 #include <QHBoxLayout>
53 #include <QLabel>
54 #include <QListWidget>
55 #include <QRadioButton>
56 
57 ///////////////////////////////////////////
58 //
59 //
60 //  deal with invisible connectors
61 //
62 //  if a part is already overlapping, leave it out of future checking?
63 //
64 //
65 ///////////////////////////////////////////
66 
67 const QString DRC::AlsoNet = "alsoNet";
68 const QString DRC::NotNet = "notnet";
69 const QString DRC::Net = "net";
70 
71 const uchar DRC::BitTable[] = { 128, 64, 32, 16, 8, 4, 2, 1 };
72 
pixelsCollide(QImage * image1,QImage * image2,QImage * image3,int x1,int y1,int x2,int y2,uint clr,QList<QPointF> & points)73 bool pixelsCollide(QImage * image1, QImage * image2, QImage * image3, int x1, int y1, int x2, int y2, uint clr, QList<QPointF> & points) {
74     bool result = false;
75     const uchar * bits1 = image1->constScanLine(0);
76     const uchar * bits2 = image2->constScanLine(0);
77     int bytesPerLine = image1->bytesPerLine();
78 	for (int y = y1; y < y2; y++) {
79         int offset = y * bytesPerLine;
80 		for (int x = x1; x < x2; x++) {
81 			//QRgb p1 = image1->pixel(x, y);
82 			//if (p1 == 0xffffffff) continue;
83 
84 			//QRgb p2 = image2->pixel(x, y);
85 			//if (p2 == 0xffffffff) continue;
86 
87             int byteOffset = (x >> 3) + offset;
88             uchar mask = DRC::BitTable[x & 7];
89 
90             if (*(bits1 + byteOffset) & mask) continue;
91             if (*(bits2 + byteOffset) & mask) continue;
92 
93             image3->setPixel(x, y, clr);
94 			//DebugDialog::debug(QString("p1:%1 p2:%2").arg(p1, 0, 16).arg(p2, 0, 16));
95 			result = true;
96             if (points.count() < 1000) {
97                 points.append(QPointF(x, y));
98             }
99 		}
100 	}
101 
102 	return result;
103 }
104 
getNames(CollidingThing * collidingThing)105 QStringList getNames(CollidingThing * collidingThing) {
106     QStringList names;
107     QList<ItemBase *> itemBases;
108     if (collidingThing->nonConnectorItem) {
109         itemBases << collidingThing->nonConnectorItem->attachedTo();
110     }
111     foreach (ItemBase * itemBase, itemBases) {
112         if (itemBase) {
113             itemBase = itemBase->layerKinChief();
114             QString name = QObject::tr("Part %1 '%2'").arg(itemBase->title()) .arg(itemBase->instanceTitle());
115             names << name;
116         }
117     }
118 
119     return names;
120 }
121 
allGs(QDomElement & element)122 void allGs(QDomElement & element) {
123     element.setTagName("g");
124     QDomElement child = element.firstChildElement();
125     while (!child.isNull()) {
126         allGs(child);
127         child = child.nextSiblingElement();
128     }
129 }
130 
131 ///////////////////////////////////////////////
132 
DRCResultsDialog(const QString & message,const QStringList & messages,const QList<CollidingThing * > & collidingThings,QGraphicsPixmapItem * displayItem,QImage * displayImage,PCBSketchWidget * sketchWidget,QWidget * parent)133 DRCResultsDialog::DRCResultsDialog(const QString & message, const QStringList & messages, const QList<CollidingThing *> & collidingThings,
134                                     QGraphicsPixmapItem * displayItem, QImage * displayImage, PCBSketchWidget * sketchWidget, QWidget *parent)
135                  : QDialog(parent)
136 {
137     setAttribute(Qt::WA_DeleteOnClose, true);
138     m_messages = messages;
139     m_sketchWidget = sketchWidget;
140     m_displayItem = displayItem;
141     if (m_displayItem) {
142         m_displayItem->setFlags(0);
143     }
144     m_displayImage = displayImage;
145     m_collidingThings = collidingThings;
146 
147 	this->setWindowTitle(tr("DRC Results"));
148 
149 	QVBoxLayout * vLayout = new QVBoxLayout(this);
150 
151     QLabel * label = new QLabel(message);
152     label->setWordWrap(true);
153 	vLayout->addWidget(label);
154 
155     label = new QLabel(tr("Click on an item in the list to highlight of overlap it refers to."));
156     label->setWordWrap(true);
157 	vLayout->addWidget(label);
158 
159     label = new QLabel(tr("Note: the list items and the red highlighting will not update as you edit your sketch--you must rerun the DRC. The highlighting will disappear when you close this dialog."));
160     label->setWordWrap(true);
161 	vLayout->addWidget(label);
162 
163     QListWidget *listWidget = new QListWidget();
164     for (int ix = 0; ix < messages.count(); ix++) {
165         QListWidgetItem * item = new QListWidgetItem(messages.at(ix));
166         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
167         item->setData(Qt::UserRole, ix);
168         listWidget->addItem(item);
169     }
170     vLayout->addWidget(listWidget);
171 	connect(listWidget, SIGNAL(itemPressed(QListWidgetItem *)), this, SLOT(pressedSlot(QListWidgetItem *)));
172 	connect(listWidget, SIGNAL(itemClicked(QListWidgetItem *)), this, SLOT(releasedSlot(QListWidgetItem *)));
173 
174 	QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
175 	connect(buttonBox, SIGNAL(accepted()), this, SLOT(close()));
176 
177 	vLayout->addWidget(buttonBox);
178 	this->setLayout(vLayout);
179 }
180 
~DRCResultsDialog()181 DRCResultsDialog::~DRCResultsDialog() {
182     if (m_displayItem != NULL && m_sketchWidget != NULL) {
183         delete m_displayItem;
184     }
185     if (m_displayImage != NULL) {
186         delete m_displayImage;
187     }
188     foreach (CollidingThing * collidingThing, m_collidingThings) {
189         delete collidingThing;
190     }
191     m_collidingThings.clear();
192 }
193 
pressedSlot(QListWidgetItem * item)194 void DRCResultsDialog::pressedSlot(QListWidgetItem * item) {
195     if (item == NULL) return;
196 
197     int ix = item->data(Qt::UserRole).toInt();
198     CollidingThing * collidingThing = m_collidingThings.at(ix);
199     foreach (QPointF p, collidingThing->atPixels) {
200         m_displayImage->setPixel(p.x(), p.y(), 2 /* 0xffffff00 */);
201     }
202     QPixmap pixmap = QPixmap::fromImage(*m_displayImage);
203     m_displayItem->setPixmap(pixmap);
204     if (collidingThing->nonConnectorItem) {
205         m_sketchWidget->selectItem(collidingThing->nonConnectorItem->attachedTo());
206     }
207 }
208 
releasedSlot(QListWidgetItem * item)209 void DRCResultsDialog::releasedSlot(QListWidgetItem * item) {
210     if (item == NULL) return;
211 
212     int ix = item->data(Qt::UserRole).toInt();
213     CollidingThing * collidingThing = m_collidingThings.at(ix);
214     foreach (QPointF p, collidingThing->atPixels) {
215         m_displayImage->setPixel(p.x(), p.y(), 1 /* 0x80ff0000 */);
216     }
217     QPixmap pixmap = QPixmap::fromImage(*m_displayImage);
218     m_displayItem->setPixmap(pixmap);
219 }
220 
221 ///////////////////////////////////////////
222 
223 const QString DRC::KeepoutSettingName("DRC_Keepout");
224 const double DRC::KeepoutDefaultMils = 10;
225 
226 ///////////////////////////////////////////////
227 
228 static QString CancelledMessage;
229 
230 ///////////////////////////////////////////
231 
DRC(PCBSketchWidget * sketchWidget,ItemBase * board)232 DRC::DRC(PCBSketchWidget * sketchWidget, ItemBase * board)
233 {
234     CancelledMessage = tr("DRC was cancelled.");
235 
236     m_cancelled = false;
237 	m_sketchWidget = sketchWidget;
238     m_board = board;
239     m_displayItem = NULL;
240     m_displayImage = NULL;
241     m_plusImage = NULL;
242     m_minusImage = NULL;
243 }
244 
~DRC(void)245 DRC::~DRC(void)
246 {
247     if (m_displayItem) {
248         delete m_displayItem;
249     }
250     if (m_plusImage) {
251         delete m_plusImage;
252     }
253     if (m_minusImage) {
254         delete m_minusImage;
255     }
256     if (m_displayImage) {
257         delete m_displayImage;
258     }
259     foreach (QDomDocument * doc, m_masterDocs) {
260         delete doc;
261     }
262 }
263 
start(bool showOkMessage,double keepoutMils)264 QStringList DRC::start(bool showOkMessage, double keepoutMils) {
265 	QString message;
266     QStringList messages;
267     QList<CollidingThing *> collidingThings;
268 
269     bool result = startAux(message, messages, collidingThings, keepoutMils);
270     if (result) {
271         if (messages.count() == 0) {
272             message = tr("Your sketch is ready for production: there are no connectors or traces that overlap or are too close together.");
273         }
274         else {
275             message = tr("The areas on your board highlighted in red are connectors and traces which may overlap or be too close together. ") +
276 					 tr("Reposition them and run the DRC again to find more problems");
277         }
278     }
279 
280 #ifndef QT_NO_DEBUG
281 	m_displayImage->save(FolderUtils::getUserDataStorePath("") + "/testDRCDisplay.png");
282 #endif
283 
284     emit wantBothVisible();
285     emit setProgressValue(m_maxProgress);
286     emit hideProgress();
287 
288 	if (messages.count() == 0) {
289         if (showOkMessage) {
290 		    QMessageBox::information(m_sketchWidget->window(), tr("Fritzing"), message);
291         }
292 	}
293 	else {
294 		DRCResultsDialog * dialog = new DRCResultsDialog(message, messages, collidingThings, m_displayItem, m_displayImage, m_sketchWidget, m_sketchWidget->window());
295         if (showOkMessage) {
296             dialog->show();
297         }
298         else {
299             dialog->exec();
300         }
301         m_displayItem = NULL;
302         m_displayImage = NULL;
303 	}
304 
305     return messages;
306 }
307 
startAux(QString & message,QStringList & messages,QList<CollidingThing * > & collidingThings,double keepoutMils)308 bool DRC::startAux(QString & message, QStringList & messages, QList<CollidingThing *> & collidingThings, double keepoutMils) {
309     bool bothSidesNow = m_sketchWidget->boardLayers() == 2;
310 
311     QList<ConnectorItem *> visited;
312     QList< QList<ConnectorItem *> > equis;
313     QList< QList<ConnectorItem *> > singletons;
314     foreach (QGraphicsItem * item, m_sketchWidget->scene()->items()) {
315         ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
316         if (connectorItem == NULL) continue;
317         if (!connectorItem->attachedTo()->isEverVisible()) continue;
318         if (connectorItem->attachedTo()->getRatsnest()) continue;
319         if (visited.contains(connectorItem)) continue;
320 
321         QList<ConnectorItem *> equi;
322         equi.append(connectorItem);
323         ConnectorItem::collectEqualPotential(equi, bothSidesNow, (ViewGeometry::RatsnestFlag | ViewGeometry::NormalFlag | ViewGeometry::PCBTraceFlag | ViewGeometry::SchematicTraceFlag) ^ m_sketchWidget->getTraceFlag());
324         visited.append(equi);
325 
326         if (equi.count() == 1) {
327             singletons.append(equi);
328             continue;
329         }
330 
331         ItemBase * firstPart = connectorItem->attachedTo()->layerKinChief();
332         bool gotTwo = false;
333         foreach (ConnectorItem * equ, equi) {
334             if (equ->attachedTo()->layerKinChief() != firstPart) {
335                 gotTwo = true;
336                 break;
337             }
338         }
339         if (!gotTwo) {
340             singletons.append(equi);
341             continue;
342         }
343 
344         equis.append(equi);
345     }
346 
347     m_maxProgress = equis.count() + singletons.count() + 1;
348     if (bothSidesNow) m_maxProgress *= 2;
349     emit setMaximumProgress(m_maxProgress);
350     int progress = 1;
351     emit setProgressValue(progress);
352 
353 	ProcessEventBlocker::processEvents();
354 
355     double dpi = qMax((double) 250, 1000 / keepoutMils);  // turns out making a variable dpi doesn't work due to vector-to-raster issues
356     QRectF boardRect = m_board->sceneBoundingRect();
357     QRectF sourceRes(0, 0,
358 					 boardRect.width() * dpi / GraphicsUtils::SVGDPI,
359                      boardRect.height() * dpi / GraphicsUtils::SVGDPI);
360 
361     QSize imgSize(qCeil(sourceRes.width()), qCeil(sourceRes.height()));
362 
363     m_plusImage = new QImage(imgSize, QImage::Format_Mono);
364     m_plusImage->fill(0xffffffff);
365 
366     m_minusImage = new QImage(imgSize, QImage::Format_Mono);
367     m_minusImage->fill(0);
368 
369     m_displayImage = new QImage(imgSize, QImage::Format_Indexed8);
370     m_displayImage->setColor(0, 0);
371     m_displayImage->setColor(1, 0x80ff0000);
372     m_displayImage->setColor(2, 0xffffff00);
373     m_displayImage->fill(0);
374 
375     if (!makeBoard(m_minusImage, sourceRes)) {
376         message = tr("Fritzing error: unable to render board svg.");
377         return false;
378     }
379 
380     extendBorder(1, m_minusImage);   // since the resolution = keepout, extend by 1
381 
382     QList<ViewLayer::ViewLayerPlacement> layerSpecs;
383     layerSpecs << ViewLayer::NewBottom;
384     if (bothSidesNow) layerSpecs << ViewLayer::NewTop;
385 
386     int emptyMasterCount = 0;
387     foreach (ViewLayer::ViewLayerPlacement viewLayerPlacement, layerSpecs) {
388         if (viewLayerPlacement == ViewLayer::NewTop) {
389             emit wantTopVisible();
390             m_plusImage->fill(0xffffffff);
391         }
392         else emit wantBottomVisible();
393 
394 	    LayerList viewLayerIDs = ViewLayer::copperLayers(viewLayerPlacement);
395         viewLayerIDs.removeOne(ViewLayer::GroundPlane0);
396         viewLayerIDs.removeOne(ViewLayer::GroundPlane1);
397         RenderThing renderThing;
398         renderThing.printerScale = GraphicsUtils::SVGDPI;
399         renderThing.blackOnly = true;
400         renderThing.dpi = GraphicsUtils::StandardFritzingDPI;
401         renderThing.hideTerminalPoints = renderThing.selectedItems = renderThing.renderBlocker = false;
402 	    QString master = m_sketchWidget->renderToSVG(renderThing, m_board, viewLayerIDs);
403         if (master.isEmpty()) {
404             if (++emptyMasterCount == layerSpecs.count()) {
405                 message = tr("No traces or connectors to check");
406                 return false;
407             }
408 
409             progress++;
410             continue;
411 	    }
412 
413 	    QDomDocument * masterDoc = new QDomDocument();
414         m_masterDocs.insert(viewLayerPlacement, masterDoc);
415 
416 	    QString errorStr;
417 	    int errorLine;
418 	    int errorColumn;
419 	    if (!masterDoc->setContent(master, &errorStr, &errorLine, &errorColumn)) {
420             message = tr("Unexpected SVG rendering failure--contact fritzing.org");
421 		    return false;
422 	    }
423 
424 	    ProcessEventBlocker::processEvents();
425         if (m_cancelled) {
426             message = CancelledMessage;
427             return false;
428         }
429 
430         QDomElement root = masterDoc->documentElement();
431         SvgFileSplitter::forceStrokeWidth(root, 2 * keepoutMils, "#000000", true, false);
432 
433         ItemBase::renderOne(masterDoc, m_plusImage, sourceRes);
434 
435 	    ProcessEventBlocker::processEvents();
436         if (m_cancelled) {
437             message = CancelledMessage;
438             return false;
439         }
440 
441         QList<QPointF> atPixels;
442         if (pixelsCollide(m_plusImage, m_minusImage, m_displayImage, 0, 0, imgSize.width(), imgSize.height(), 1 /* 0x80ff0000 */, atPixels)) {
443             CollidingThing * collidingThing = findItemsAt(atPixels, m_board, viewLayerIDs, keepoutMils, dpi, true, NULL);
444             QString msg = tr("Too close to a border (%1 layer)")
445                 .arg(viewLayerPlacement == ViewLayer::NewTop ? ItemBase::TranslatedPropertyNames.value("top") : ItemBase::TranslatedPropertyNames.value("bottom"))
446                 ;
447             emit setProgressMessage(msg);
448             messages << msg;
449             collidingThings << collidingThing;
450             updateDisplay();
451         }
452 
453         emit setProgressValue(progress++);
454 
455 	    ProcessEventBlocker::processEvents();
456         if (m_cancelled) {
457             message = CancelledMessage;
458             return false;
459         }
460 
461     }
462 
463     // we are checking all the singletons at once
464     // but the DRC will miss it if any of them overlap each other
465 
466     while (singletons.count() > 0) {
467         QList<ConnectorItem *> combined;
468         QList<ConnectorItem *> singleton = singletons.takeFirst();
469         ItemBase * chief = singleton.at(0)->attachedTo()->layerKinChief();
470         combined.append(singleton);
471         for (int ix = singletons.count() - 1; ix >= 0; ix--) {
472             QList<ConnectorItem *> candidate = singletons.at(ix);
473             if (candidate.at(0)->attachedTo()->layerKinChief() == chief) {
474                 combined.append(candidate);
475                 singletons.removeAt(ix);
476             }
477         }
478 
479         equis.append(combined);
480     }
481 
482     int index = 0;
483     foreach (ViewLayer::ViewLayerPlacement viewLayerPlacement, layerSpecs) {
484         if (viewLayerPlacement == ViewLayer::NewTop) emit wantTopVisible();
485         else emit wantBottomVisible();
486 
487         QDomDocument * masterDoc = m_masterDocs.value(viewLayerPlacement, NULL);
488         if (masterDoc == NULL) continue;
489 
490 	    LayerList viewLayerIDs = ViewLayer::copperLayers(viewLayerPlacement);
491         viewLayerIDs.removeOne(ViewLayer::GroundPlane0);
492         viewLayerIDs.removeOne(ViewLayer::GroundPlane1);
493 
494         foreach (QList<ConnectorItem *> equi, equis) {
495             bool inLayer = false;
496             foreach (ConnectorItem * equ, equi) {
497                 if (viewLayerIDs.contains(equ->attachedToViewLayerID())) {
498                     inLayer = true;
499                     break;
500                 }
501             }
502             if (!inLayer) {
503                 progress++;
504                 continue;
505             }
506 
507             // we have a net;
508             m_plusImage->fill(0xffffffff);
509             m_minusImage->fill(0xffffffff);
510             splitNet(masterDoc, equi, m_minusImage, m_plusImage, sourceRes, viewLayerPlacement, index++, keepoutMils);
511 
512             QHash<ConnectorItem *, QRectF> rects;
513             QList<Wire *> wires;
514             foreach (ConnectorItem * equ, equi) {
515                 if (viewLayerIDs.contains(equ->attachedToViewLayerID())) {
516                     if (equ->attachedToItemType() == ModelPart::Wire) {
517                         Wire * wire = qobject_cast<Wire *>(equ->attachedTo());
518                         if (!wires.contains(wire)) {
519                             wires.append(wire);
520                             // could break diagonal wires into a series of rects
521                             rects.insert(equ, wire->sceneBoundingRect());
522                         }
523                     }
524                     else {
525                         rects.insert(equ, equ->sceneBoundingRect());
526                     }
527                 }
528             }
529 
530 	        ProcessEventBlocker::processEvents();
531             if (m_cancelled) {
532                 message = CancelledMessage;
533                 return false;
534             }
535 
536             foreach (ConnectorItem * equ, rects.keys()) {
537                 QRectF rect = rects.value(equ).intersected(boardRect);
538                 double l = (rect.left() - boardRect.left()) * dpi / GraphicsUtils::SVGDPI;
539                 double t = (rect.top() - boardRect.top()) * dpi / GraphicsUtils::SVGDPI;
540                 double r = (rect.right() - boardRect.left()) * dpi / GraphicsUtils::SVGDPI;
541                 double b = (rect.bottom() - boardRect.top()) * dpi / GraphicsUtils::SVGDPI;
542                 //DebugDialog::debug(QString("l:%1 t:%2 r:%3 b:%4").arg(l).arg(t).arg(r).arg(b));
543                 QList<QPointF> atPixels;
544                 if (pixelsCollide(m_plusImage, m_minusImage, m_displayImage, l, t, r, b, 1 /* 0x80ff0000 */, atPixels)) {
545 
546                     #ifndef QT_NO_DEBUG
547 	                    m_plusImage->save(FolderUtils::getUserDataStorePath("") + QString("/collidePlus%1_%2.png").arg(viewLayerPlacement).arg(index));
548 	                    m_minusImage->save(FolderUtils::getUserDataStorePath("") + QString("/collideMinus%1_%2.png").arg(viewLayerPlacement).arg(index));
549                     #endif
550 
551                     CollidingThing * collidingThing = findItemsAt(atPixels, m_board, viewLayerIDs, keepoutMils, dpi, false, equ);
552                     QStringList names = getNames(collidingThing);
553                     QString name0 = names.at(0);
554                     QString msg = tr("%1 is overlapping (%2 layer)")
555                         .arg(name0)
556                         .arg(viewLayerPlacement == ViewLayer::NewTop ? ItemBase::TranslatedPropertyNames.value("top") : ItemBase::TranslatedPropertyNames.value("bottom"))
557                         ;
558                     messages << msg;
559                     collidingThings << collidingThing;
560                     emit setProgressMessage(msg);
561                     updateDisplay();
562                 }
563             }
564 
565             emit setProgressValue(progress++);
566 
567 	        ProcessEventBlocker::processEvents();
568             if (m_cancelled) {
569                 message = CancelledMessage;
570                 return false;
571             }
572         }
573     }
574     checkHoles(messages, collidingThings,  dpi);
575     checkCopperBoth(messages, collidingThings, dpi);
576 
577     return true;
578 }
579 
makeBoard(QImage * image,QRectF & sourceRes)580 bool DRC::makeBoard(QImage * image, QRectF & sourceRes) {
581 	LayerList viewLayerIDs;
582 	viewLayerIDs << ViewLayer::Board;
583     RenderThing renderThing;
584     renderThing.printerScale = GraphicsUtils::SVGDPI;
585     renderThing.blackOnly = true;
586     renderThing.dpi = GraphicsUtils::StandardFritzingDPI;
587     renderThing.hideTerminalPoints = renderThing.selectedItems = renderThing.renderBlocker = false;
588 	QString boardSvg = m_sketchWidget->renderToSVG(renderThing, m_board, viewLayerIDs);
589 	if (boardSvg.isEmpty()) {
590 		return false;
591 	}
592 
593     QByteArray boardByteArray;
594     QString tempColor("#ffffff");
595     QStringList exceptions;
596 	exceptions << "none" << "";
597     if (!SvgFileSplitter::changeColors(boardSvg, tempColor, exceptions, boardByteArray)) {
598 		return false;
599 	}
600 
601 	QSvgRenderer renderer(boardByteArray);
602 	QPainter painter;
603 	painter.begin(image);
604 	painter.setRenderHint(QPainter::Antialiasing, false);
605 	DebugDialog::debug("boardbounds", sourceRes);
606 	renderer.render(&painter  /*, sourceRes */);
607 	painter.end();
608 
609     // board should be white, borders should be black
610 
611 #ifndef QT_NO_DEBUG
612 	image->save(FolderUtils::getUserDataStorePath("") + "/testDRCBoard.png");
613 #endif
614 
615     return true;
616 }
617 
splitNet(QDomDocument * masterDoc,QList<ConnectorItem * > & equi,QImage * minusImage,QImage * plusImage,QRectF & sourceRes,ViewLayer::ViewLayerPlacement viewLayerPlacement,int index,double keepoutMils)618 void DRC::splitNet(QDomDocument * masterDoc, QList<ConnectorItem *> & equi, QImage * minusImage, QImage * plusImage, QRectF & sourceRes, ViewLayer::ViewLayerPlacement viewLayerPlacement, int index, double keepoutMils) {
619     // deal with connectors on the same part, even though they are not on the same net
620     // in other words, make sure there are no overlaps of connectors on the same part
621     QList<QDomElement> net;
622     QList<QDomElement> alsoNet;
623     QList<QDomElement> notNet;
624     Markers markers;
625     markers.outID = AlsoNet;
626     markers.inTerminalID = markers.inSvgID = markers.inSvgAndID = markers.inNoID = Net;
627     splitNetPrep(masterDoc, equi, markers, net, alsoNet, notNet, true);
628     foreach (QDomElement element, notNet) element.setTagName("g");
629     foreach (QDomElement element, alsoNet) element.setTagName("g");
630     foreach (QDomElement element, net) {
631         // want the normal size
632         SvgFileSplitter::forceStrokeWidth(element, -2 * keepoutMils, "#000000", false, false);
633     }
634 
635     ItemBase::renderOne(masterDoc, plusImage, sourceRes);
636 
637     foreach (QDomElement element, net) {
638         // restore to keepout size
639         SvgFileSplitter::forceStrokeWidth(element, 2 * keepoutMils, "#000000", false, false);
640     }
641 
642     #ifndef QT_NO_DEBUG
643 	    plusImage->save(FolderUtils::getUserDataStorePath("") + QString("/splitNetPlus%1_%2.png").arg(viewLayerPlacement).arg(index));
644     #else
645         Q_UNUSED(viewLayerPlacement);
646         Q_UNUSED(index);
647     #endif
648 
649     // now want notnet
650     foreach (QDomElement element, net) {
651         element.removeAttribute("net");
652         element.setTagName("g");
653     }
654     foreach (QDomElement element, alsoNet) {
655         element.setTagName(element.attribute("former"));
656         element.removeAttribute("net");
657     }
658     foreach (QDomElement element, notNet) {
659         element.setTagName(element.attribute("former"));
660         element.removeAttribute("net");
661     }
662 
663     ItemBase::renderOne(masterDoc, minusImage, sourceRes);
664     #ifndef QT_NO_DEBUG
665 	    minusImage->save(FolderUtils::getUserDataStorePath("") + QString("/splitNetMinus%1_%2.png").arg(viewLayerPlacement).arg(index));
666     #endif
667 
668      // master doc restored to original state
669     foreach (QDomElement element, net) {
670         element.setTagName(element.attribute("former"));
671     }
672 
673 }
674 
splitNetPrep(QDomDocument * masterDoc,QList<ConnectorItem * > & equi,const Markers & markers,QList<QDomElement> & net,QList<QDomElement> & alsoNet,QList<QDomElement> & notNet,bool checkIntersection)675 void DRC::splitNetPrep(QDomDocument * masterDoc, QList<ConnectorItem *> & equi, const Markers & markers, QList<QDomElement> & net, QList<QDomElement> & alsoNet, QList<QDomElement> & notNet, bool checkIntersection)
676 {
677     QMultiHash<QString, QString> partSvgIDs;
678     QMultiHash<QString, QString> partTerminalIDs;
679     QHash<QString, QString> bothIDs;
680     QMultiHash<QString, ItemBase *> itemBases;
681     QSet<QString> wireIDs;
682     foreach (ConnectorItem * equ, equi) {
683         ItemBase * itemBase = equ->attachedTo();
684         if (itemBase == NULL) continue;
685 
686         if (itemBase->itemType() == ModelPart::Wire) {
687             wireIDs.insert(QString::number(itemBase->id()));
688         }
689 
690         if (equ->connector() == NULL) {
691             // this shouldn't happen
692             itemBase->debugInfo("!!!!!!!!!!!!!!!!!!!!!!!!!!!!! missing connector !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
693             equ->debugInfo("missing connector");
694             continue;
695         }
696 
697         QString sid = QString::number(itemBase->id());
698         SvgIdLayer * svgIdLayer = equ->connector()->fullPinInfo(itemBase->viewID(), itemBase->viewLayerID());
699         partSvgIDs.insert(sid, svgIdLayer->m_svgId);
700         if (!svgIdLayer->m_terminalId.isEmpty()) {
701             partTerminalIDs.insert(sid, svgIdLayer->m_terminalId);
702             bothIDs.insert(sid + svgIdLayer->m_svgId, svgIdLayer->m_terminalId);
703         }
704         itemBases.insert(sid, itemBase);
705     }
706 
707     QList<QDomElement> todo;
708     todo << masterDoc->documentElement();
709     bool firstTime = true;
710     while (!todo.isEmpty()) {
711         QDomElement element = todo.takeFirst();
712         //QString string;
713         //QTextStream stream(&string);
714         //element.save(stream, 0);
715 
716         QDomElement child = element.firstChildElement();
717         while (!child.isNull()) {
718             todo << child;
719             child = child.nextSiblingElement();
720         }
721         if (firstTime) {
722             // don't include the root <svg> element
723             firstTime = false;
724             continue;
725         }
726 
727         QString partID = element.attribute("partID");
728         if (!partID.isEmpty()) {
729             QStringList svgIDs = partSvgIDs.values(partID);
730             QStringList terminalIDs = partTerminalIDs.values(partID);
731             if (svgIDs.count() == 0) {
732                 markSubs(element, NotNet);
733             }
734             else if (wireIDs.contains(partID)) {
735                 markSubs(element, Net);
736             }
737             else {
738                 splitSubs(masterDoc, element, partID, markers, svgIDs, terminalIDs, itemBases.values(partID), bothIDs, checkIntersection);
739             }
740         }
741 
742         if (element.tagName() == "g") {
743             element.removeAttribute("net");
744             continue;
745         }
746 
747         element.setAttribute("former", element.tagName());
748         if (element.attribute("net") == Net) {
749             net.append(element);
750         }
751         else if (element.attribute("net") == AlsoNet) {
752             alsoNet.append(element);
753         }
754         else {
755             // assume the element belongs to notNet
756             notNet.append(element);
757         }
758     }
759 }
760 
markSubs(QDomElement & root,const QString & mark)761 void DRC::markSubs(QDomElement & root, const QString & mark) {
762     QList<QDomElement> todo;
763     todo << root;
764     while (!todo.isEmpty()) {
765         QDomElement element = todo.takeFirst();
766         element.setAttribute("net", mark);
767         QDomElement child = element.firstChildElement();
768         while (!child.isNull()) {
769             todo << child;
770             child = child.nextSiblingElement();
771         }
772     }
773 }
774 
splitSubs(QDomDocument * doc,QDomElement & root,const QString & partID,const Markers & markers,const QStringList & svgIDs,const QStringList & terminalIDs,const QList<ItemBase * > & itemBases,QHash<QString,QString> & bothIDs,bool checkIntersection)775 void DRC::splitSubs(QDomDocument * doc, QDomElement & root, const QString & partID, const Markers & markers, const QStringList & svgIDs, const QStringList & terminalIDs, const QList<ItemBase *> & itemBases, QHash<QString, QString> & bothIDs, bool checkIntersection)
776 {
777     //QString string;
778     //QTextStream stream(&string);
779     //root.save(stream, 0);
780 
781     QStringList notSvgIDs;
782     QStringList notTerminalIDs;
783     if (checkIntersection && itemBases.count() > 0) {
784         ItemBase * itemBase = itemBases.at(0);
785         foreach (ConnectorItem * connectorItem, itemBase->cachedConnectorItems()) {
786             SvgIdLayer * svgIdLayer = connectorItem->connector()->fullPinInfo(itemBase->viewID(), itemBase->viewLayerID());
787             if (!svgIDs.contains(svgIdLayer->m_svgId)) {
788                 notSvgIDs.append(svgIdLayer->m_svgId);
789             }
790             if (!svgIdLayer->m_terminalId.isEmpty()) {
791                 if (!terminalIDs.contains(svgIdLayer->m_terminalId)) {
792                     notTerminalIDs.append(svgIdLayer->m_terminalId);
793                 }
794             }
795         }
796     }
797 
798     // split subelements of a part into separate nets
799     QList<QDomElement> todo;
800     QList<QDomElement> netElements;
801     todo << root;
802     while (!todo.isEmpty()) {
803         QDomElement element = todo.takeFirst();
804         QString svgID = element.attribute("id");
805         if (!svgID.isEmpty()) {
806             if (svgIDs.contains(svgID)) {
807                 if (bothIDs.value(partID + svgID).isEmpty()) {
808                     // no terminal point
809                     markSubs(element, markers.inSvgID);
810                 }
811                 else {
812                     // privilege the terminal point over the pin/pad
813                     markSubs(element, markers.inSvgAndID);
814                 }
815                 netElements << element;         // save these for intersection checks
816                 // all children are marked so don't add these to todo
817                 continue;
818             }
819             else if (notSvgIDs.contains(svgID)) {
820                 markSubs(element, markers.outID);
821                 // all children are marked so don't add these to todo
822                 continue;
823             }
824             else if (terminalIDs.contains(svgID)) {
825                 markSubs(element, markers.inTerminalID);
826                 continue;
827             }
828             else if (notTerminalIDs.contains(svgID)) {
829                 markSubs(element, markers.outID);
830                 continue;
831             }
832         }
833 
834         QDomElement child = element.firstChildElement();
835         while (!child.isNull()) {
836             todo << child;
837             child = child.nextSiblingElement();
838         }
839     }
840 
841     QList<QDomElement> toCheck;
842 
843     todo << root;
844     while (!todo.isEmpty()) {
845         QDomElement element = todo.takeFirst();
846         QString net = element.attribute("net");
847         if (net == AlsoNet) continue;
848         else if (net == Net) continue;
849         else if (net == NotNet) continue;
850         else if (!checkIntersection) {
851             element.setAttribute("net", markers.outID);
852         }
853         else if (element.tagName() != "g") {
854             element.setAttribute("oldid", element.attribute("id"));
855             element.setAttribute("id", QString("_____toCheck_____%1").arg(toCheck.count()));
856             toCheck << element;
857         }
858 
859         QDomElement child = element.firstChildElement();
860         while (!child.isNull()) {
861             todo << child;
862             child = child.nextSiblingElement();
863         }
864     }
865 
866     if (toCheck.count() > 0) {
867         // deal with elements that are effectively part of a connector, but aren't separately labeled
868         // such as a rect which overlaps a circle
869         QDomElement masterRoot = doc->documentElement();
870         QString svg = QString("<svg width='%1' height='%2' viewBox='%3'>\n")
871             .arg(masterRoot.attribute("width"))
872             .arg(masterRoot.attribute("height"))
873             .arg(masterRoot.attribute("viewBox"));
874         QString string;
875         QTextStream stream(&string);
876         root.save(stream, 0);
877         svg += string;
878         svg += "</svg>";
879         QSvgRenderer renderer;
880         renderer.load(svg.toUtf8());
881         QList<QRectF> netRects;
882         foreach (QDomElement element, netElements) {
883             QString id = element.attribute("id");
884             QRectF r = renderer.matrixForElement(id).mapRect(renderer.boundsOnElement(id));
885             netRects << r;
886         }
887         QList<QRectF> checkRects;
888         foreach (QDomElement element, toCheck) {
889             QString id = element.attribute("id");
890             QRectF r = renderer.matrixForElement(id).mapRect(renderer.boundsOnElement(id));
891             checkRects << r;
892             element.setAttribute("id", element.attribute("oldid"));
893         }
894         for (int i = 0; i < checkRects.count(); i++) {
895             QRectF checkr = checkRects.at(i);
896             QDomElement checkElement = toCheck.at(i);
897             bool gotOne = false;
898             double carea = checkr.width() * checkr.height();
899             foreach (QRectF netr, netRects) {
900                 QRectF sect = netr.intersected(checkr);
901                 if (sect.isEmpty()) continue;
902 
903                 double area = sect.width() * sect.height();
904                 double netArea = netr.width() * netr.height();
905                 if ((area > netArea * .5) && (netArea * 2 > carea)) {
906                     markSubs(checkElement, markers.inNoID);
907                     gotOne = true;
908                     break;
909                 }
910             }
911             if (!gotOne) {
912                 markSubs(checkElement, markers.outID);
913             }
914         }
915     }
916 }
917 
updateDisplay()918 void DRC::updateDisplay() {
919     QPixmap pixmap = QPixmap::fromImage(*m_displayImage);
920     if (m_displayItem == NULL) {
921         m_displayItem = new QGraphicsPixmapItem(pixmap);
922         m_displayItem->setPos(m_board->sceneBoundingRect().topLeft());
923         m_sketchWidget->scene()->addItem(m_displayItem);
924         m_displayItem->setZValue(5000);
925         m_displayItem->setScale(m_board->sceneBoundingRect().width() / m_displayImage->width());   // GraphicsUtils::SVGDPI / dpi
926         m_displayItem->setVisible(true);
927     }
928     else {
929         m_displayItem->setPixmap(pixmap);
930     }
931     ProcessEventBlocker::processEvents();
932 }
933 
cancel()934 void DRC::cancel() {
935 	m_cancelled = true;
936 }
937 
findItemsAt(QList<QPointF> & atPixels,ItemBase * board,const LayerList & viewLayerIDs,double keepoutMils,double dpi,bool skipHoles,ConnectorItem * already)938 CollidingThing * DRC::findItemsAt(QList<QPointF> & atPixels, ItemBase * board, const LayerList & viewLayerIDs, double keepoutMils, double dpi, bool skipHoles, ConnectorItem * already) {
939     Q_UNUSED(board);
940     Q_UNUSED(viewLayerIDs);
941     Q_UNUSED(keepoutMils);
942     Q_UNUSED(dpi);
943     Q_UNUSED(skipHoles);
944 
945     CollidingThing * collidingThing = new CollidingThing;
946     collidingThing->nonConnectorItem = already;
947     collidingThing->atPixels = atPixels;
948 
949     return collidingThing;
950 }
951 
extendBorder(double keepout,QImage * image)952 void DRC::extendBorder(double keepout, QImage * image) {
953     // TODO: scanlines
954 
955     // keepout in terms of the board grid size
956 
957     QImage copy = image->copy();
958 
959     int h = image->height();
960     int w = image->width();
961     int ikeepout = qCeil(keepout);
962     for (int y = 0; y < h; y++) {
963         for (int x = 0; x < w; x++) {
964             if (copy.pixel(x, y) != 0xff000000) {
965                 continue;
966             }
967 
968             for (int dy = y - ikeepout; dy <= y + ikeepout; dy++) {
969                 if (dy < 0) continue;
970                 if (dy >= h) continue;
971                 for (int dx = x - ikeepout; dx <= x + ikeepout; dx++) {
972                     if (dx < 0) continue;
973                     if (dx >= w) continue;
974 
975                     // extend border by keepout
976                     image->setPixel(dx, dy, 0);
977                 }
978             }
979         }
980     }
981 }
982 
checkHoles(QStringList & messages,QList<CollidingThing * > & collidingThings,double dpi)983 void DRC::checkHoles(QStringList & messages, QList<CollidingThing *> & collidingThings, double dpi) {
984     QRectF boardRect = m_board->sceneBoundingRect();
985     foreach (QGraphicsItem * item, m_sketchWidget->scene()->collidingItems(m_board)) {
986         NonConnectorItem * nci = dynamic_cast<NonConnectorItem *>(item);
987         if (nci == NULL) continue;
988 
989         QRectF ncibr = nci->sceneBoundingRect();
990         if (boardRect.contains(ncibr)) continue;
991 
992         if (nci->attachedToItemType() == ModelPart::Wire) continue;
993         if (nci->attachedToItemType() == ModelPart::CopperFill) continue;
994 
995         // TODO: skip pad part, logo item, smds
996 
997         QRectF ir = boardRect.intersected(ncibr);
998         int x = qFloor((ir.left() - boardRect.left()) * dpi / GraphicsUtils::SVGDPI);
999         if (x < 0) x = 0;
1000         int y = qFloor((ir.top() - boardRect.top()) * dpi / GraphicsUtils::SVGDPI);
1001         if (y < 0) y = 0;
1002         int w = qCeil(ir.width() * dpi / GraphicsUtils::SVGDPI);
1003         if (x + w > m_displayImage->width()) w = m_displayImage->width() - x;
1004         int h = qCeil(ir.height() * dpi / GraphicsUtils::SVGDPI);
1005         if (y + h > m_displayImage->height()) h = m_displayImage->height() - y;
1006 
1007         CollidingThing * collidingThing = new CollidingThing;
1008         collidingThing->nonConnectorItem = nci;
1009         for (int iy = 0; iy < h; iy++) {
1010             for (int ix = 0; ix < w; ix++) {
1011                 QPoint p(ix + x, iy + y);
1012                 m_displayImage->setPixel(p, 1);
1013                 collidingThing->atPixels << p;
1014             }
1015         }
1016         QStringList names = getNames(collidingThing);
1017         QString name0 = names.at(0);
1018         QString msg = tr("A hole in %1 may lie outside the border of the board and would be clipped.")
1019             .arg(name0)
1020             ;
1021         messages << msg;
1022         collidingThings << collidingThing;
1023         emit setProgressMessage(msg);
1024         updateDisplay();
1025     }
1026 }
1027 
checkCopperBoth(QStringList & messages,QList<CollidingThing * > & collidingThings,double dpi)1028 void DRC::checkCopperBoth(QStringList & messages, QList<CollidingThing *> & collidingThings, double dpi) {
1029     QRectF boardRect = m_board->sceneBoundingRect();
1030     QList<ItemBase *> visited;
1031     foreach (QGraphicsItem * item, m_sketchWidget->scene()->items()) {
1032         ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
1033         if (itemBase == NULL) continue;
1034         if (!itemBase->isEverVisible()) continue;
1035         if (itemBase->modelPart()->isCore()) continue;
1036 
1037         itemBase = itemBase->layerKinChief();
1038         if (visited.contains(itemBase)) continue;
1039 
1040         visited << itemBase;
1041 
1042         QRectF ibr = item->sceneBoundingRect();
1043         if (!boardRect.intersects(ibr)) continue;
1044 
1045         QString fzpPath = itemBase->modelPart()->path();
1046         QFile file(fzpPath);
1047         if (!file.open(QFile::ReadOnly)) {
1048             DebugDialog::debug(QString("unable to open %1").arg(fzpPath));
1049             continue;
1050         }
1051 
1052         QString fzp = file.readAll();
1053         file.close();
1054 
1055         bool copper0 = fzp.contains("copper0");
1056         bool copper1 = fzp.contains("copper1");
1057         if (!copper1 && !copper0) continue;
1058 
1059         QString svgPath = itemBase->fsvgRenderer()->filename();
1060         QFile file2(svgPath);
1061         if (!file2.open(QFile::ReadOnly)) {
1062             DebugDialog::debug(QString("itembase svg file open failure %1").arg(itemBase->id()));
1063             continue;
1064         }
1065 
1066         QString svg = file2.readAll();
1067         file2.close();
1068 
1069         QDomDocument doc;
1070         QString errorStr;
1071 	    int errorLine;
1072 	    int errorColumn;
1073 	    if (!doc.setContent(svg, &errorStr, &errorLine, &errorColumn)) {
1074             DebugDialog::debug(QString("itembase svg xml failure %1 %2 %3 %4").arg(itemBase->id()).arg(errorStr).arg(errorLine).arg(errorColumn));
1075             continue;
1076         }
1077 
1078         QSet<ConnectorItem *> missing;
1079         QDomElement root = doc.documentElement();
1080 
1081         if (copper1) {
1082             foreach(ConnectorItem * ci, missingCopper("copper1", ViewLayer::Copper1, itemBase, root)) {
1083                 missing << ci;
1084             }
1085         }
1086 
1087         if (copper0) {
1088             foreach (ConnectorItem * ci, missingCopper("copper0", ViewLayer::Copper0, itemBase, root)) {
1089                 missing << ci;
1090             }
1091         }
1092 
1093         foreach (ConnectorItem * ci, missing) {
1094             QRectF ir = boardRect.intersected(ci->sceneBoundingRect());
1095             int x = qFloor((ir.left() - boardRect.left()) * dpi / GraphicsUtils::SVGDPI);
1096             if (x < 0) x = 0;
1097             int y = qFloor((ir.top() - boardRect.top()) * dpi / GraphicsUtils::SVGDPI);
1098             if (y < 0) y = 0;
1099             int w = qCeil(ir.width() * dpi / GraphicsUtils::SVGDPI);
1100             if (x + w > m_displayImage->width()) w = m_displayImage->width() - x;
1101             int h = qCeil(ir.height() * dpi / GraphicsUtils::SVGDPI);
1102             if (y + h > m_displayImage->height()) h = m_displayImage->height() - y;
1103 
1104             CollidingThing * collidingThing = new CollidingThing;
1105             collidingThing->nonConnectorItem = ci;
1106             for (int iy = 0; iy < h; iy++) {
1107                 for (int ix = 0; ix < w; ix++) {
1108                     QPoint p(ix + x, iy + y);
1109                     m_displayImage->setPixel(p, 1);
1110                     collidingThing->atPixels << p;
1111                 }
1112             }
1113             QStringList names = getNames(collidingThing);
1114             QString name0 = names.at(0);
1115             QString msg = tr("Connector %1 on %2 should have both copper top and bottom layers, but the svg only specifies one layer.")
1116                 .arg(ci->connectorSharedName())
1117                 .arg(name0)
1118                 ;
1119             messages << msg;
1120             collidingThings << collidingThing;
1121             emit setProgressMessage(msg);
1122             updateDisplay();
1123         }
1124     }
1125 
1126 }
1127 
missingCopper(const QString & layerName,ViewLayer::ViewLayerID viewLayerID,ItemBase * itemBase,const QDomElement & root)1128 QList<ConnectorItem *> DRC::missingCopper(const QString & layerName, ViewLayer::ViewLayerID viewLayerID, ItemBase * itemBase, const QDomElement & root)
1129 {
1130     QDomElement copperElement = TextUtils::findElementWithAttribute(root, "id", layerName);
1131     QList<ConnectorItem *> missing;
1132 
1133     foreach (ConnectorItem * connectorItem, itemBase->cachedConnectorItems()) {
1134         SvgIdLayer * svgIdLayer = connectorItem->connector()->fullPinInfo(itemBase->viewID(), viewLayerID);
1135         if (svgIdLayer == NULL) {
1136             DebugDialog::debug(QString("missing pin info for %1").arg(itemBase->id()));
1137             missing << connectorItem;
1138             continue;
1139         }
1140 
1141         QDomElement element = TextUtils::findElementWithAttribute(copperElement, "id", svgIdLayer->m_svgId);
1142         if (element.isNull()) {
1143             missing << connectorItem;
1144         }
1145     }
1146 
1147     return missing;
1148 }
1149