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