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: 6998 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-28 13:51:10 +0200 (So, 28. Apr 2013) $
24
25 ********************************************************************/
26
27 #include <qmath.h>
28
29 #include "pcbsketchwidget.h"
30 #include "../debugdialog.h"
31 #include "../items/tracewire.h"
32 #include "../items/virtualwire.h"
33 #include "../items/resizableboard.h"
34 #include "../items/pad.h"
35 #include "../waitpushundostack.h"
36 #include "../connectors/connectoritem.h"
37 #include "../items/moduleidnames.h"
38 #include "../items/partlabel.h"
39 #include "../fsvgrenderer.h"
40 #include "../autoroute/autorouteprogressdialog.h"
41 #include "../autoroute/drc.h"
42 #include "../items/groundplane.h"
43 #include "../items/jumperitem.h"
44 #include "../utils/autoclosemessagebox.h"
45 #include "../utils/graphicsutils.h"
46 #include "../utils/textutils.h"
47 #include "../utils/folderutils.h"
48 #include "../processeventblocker.h"
49 #include "../autoroute/cmrouter/tileutils.h"
50 #include "../autoroute/cmrouter/cmrouter.h"
51 #include "../autoroute/panelizer.h"
52 #include "../autoroute/autoroutersettingsdialog.h"
53 #include "../svg/groundplanegenerator.h"
54 #include "../items/logoitem.h"
55 #include "../dialogs/groundfillseeddialog.h"
56 #include "../version/version.h"
57
58 #include <limits>
59 #include <QApplication>
60 #include <QScrollBar>
61 #include <QDialog>
62 #include <QRadioButton>
63 #include <QVBoxLayout>
64 #include <QGroupBox>
65 #include <QDialogButtonBox>
66 #include <QSettings>
67 #include <QPushButton>
68 #include <QMessageBox>
69 #include <QNetworkAccessManager>
70 #include <QInputDialog>
71
72
73 /////////////////////////////////////////////////////
74
75 static const int MAX_INT = std::numeric_limits<int>::max();
76 static const double StrokeWidthIncrement = 50;
77
78 static QString PCBTraceColor1 = "trace1";
79 static QString PCBTraceColor = "trace";
80
81 QSizeF PCBSketchWidget::m_jumperItemSize = QSizeF(0, 0);
82
83 struct DistanceThing {
84 int distance;
85 bool fromConnector0;
86 };
87
88 QHash <ConnectorItem *, DistanceThing *> distances;
89
bySize(QList<ConnectorItem * > * l1,QList<ConnectorItem * > * l2)90 bool bySize(QList<ConnectorItem *> * l1, QList<ConnectorItem *> * l2) {
91 return l1->count() >= l2->count();
92 }
93
distanceLessThan(ConnectorItem * end0,ConnectorItem * end1)94 bool distanceLessThan(ConnectorItem * end0, ConnectorItem * end1) {
95 if (end0->connectorType() == Connector::Male && end1->connectorType() == Connector::Female) {
96 return true;
97 }
98 if (end1->connectorType() == Connector::Male && end0->connectorType() == Connector::Female) {
99 return false;
100 }
101
102 DistanceThing * dt0 = distances.value(end0, NULL);
103 DistanceThing * dt1 = distances.value(end1, NULL);
104 if (dt0 && dt1) {
105 return dt0->distance <= dt1->distance;
106 }
107
108 if (dt0) {
109 return true;
110 }
111
112 if (dt1) {
113 return false;
114 }
115
116 return true;
117 }
118
119 //////////////////////////////////////////////////////
120
121 const char * PCBSketchWidget::FakeTraceProperty = "FakeTrace";
122
PCBSketchWidget(ViewLayer::ViewID viewID,QWidget * parent)123 PCBSketchWidget::PCBSketchWidget(ViewLayer::ViewID viewID, QWidget *parent)
124 : SketchWidget(viewID, parent)
125 {
126 m_rolloverQuoteDialog = NULL;
127 m_requestQuoteTimer.setSingleShot(true);
128 m_requestQuoteTimer.setInterval(100);
129 connect(&m_requestQuoteTimer, SIGNAL(timeout()), this, SLOT(requestQuoteNow()));
130 m_resizingBoard = NULL;
131 m_resizingJumperItem = NULL;
132 m_viewName = QObject::tr("PCB View");
133 m_shortName = QObject::tr("pcb");
134 initBackgroundColor();
135
136 m_routingStatus.zero();
137 m_cleanType = noClean;
138 }
139
setWireVisible(Wire * wire)140 void PCBSketchWidget::setWireVisible(Wire * wire)
141 {
142 bool visible = wire->getRatsnest() || (wire->isTraceType(this->getTraceFlag()));
143 wire->setVisible(visible);
144 wire->setEverVisible(visible);
145 }
146
addViewLayers()147 void PCBSketchWidget::addViewLayers() {
148 setViewLayerIDs(ViewLayer::Silkscreen1, ViewLayer::Copper0Trace, ViewLayer::Copper0, ViewLayer::PcbRuler, ViewLayer::PcbNote);
149 addViewLayersAux(ViewLayer::layersForView(ViewLayer::PCBView), ViewLayer::layersForViewFromBelow(ViewLayer::PCBView));
150
151 ViewLayer * silkscreen1 = m_viewLayers.value(ViewLayer::Silkscreen1);
152 ViewLayer * silkscreen1Label = m_viewLayers.value(ViewLayer::Silkscreen1Label);
153 if (silkscreen1 && silkscreen1Label) {
154 silkscreen1Label->setParentLayer(silkscreen1);
155 }
156 ViewLayer * silkscreen0 = m_viewLayers.value(ViewLayer::Silkscreen0);
157 ViewLayer * silkscreen0Label = m_viewLayers.value(ViewLayer::Silkscreen0Label);
158 if (silkscreen0 && silkscreen0Label) {
159 silkscreen0Label->setParentLayer(silkscreen0);
160 }
161
162 ViewLayer * copper0 = m_viewLayers.value(ViewLayer::Copper0);
163 ViewLayer * copper0Trace = m_viewLayers.value(ViewLayer::Copper0Trace);
164 ViewLayer * copper1 = m_viewLayers.value(ViewLayer::Copper1);
165 ViewLayer * copper1Trace = m_viewLayers.value(ViewLayer::Copper1Trace);
166 if (copper0 && copper0Trace) {
167 copper0Trace->setParentLayer(copper0);
168 }
169 ViewLayer * groundPlane0 = m_viewLayers.value(ViewLayer::GroundPlane0);
170 if (copper0 && groundPlane0) {
171 groundPlane0->setParentLayer(copper0);
172 }
173 if (copper1 && copper1Trace) {
174 copper1Trace->setParentLayer(copper1);
175 }
176 ViewLayer * groundPlane1 = m_viewLayers.value(ViewLayer::GroundPlane1);
177 if (copper1 && groundPlane1) {
178 groundPlane1->setParentLayer(copper1);
179 }
180
181 // disable these for now
182 //viewLayer = m_viewLayers.value(ViewLayer::Keepout);
183 //viewLayer->action()->setEnabled(false);
184
185 setBoardLayers(1, false);
186 }
187
188
189
multiLayerGetViewLayerID(ModelPart * modelPart,ViewLayer::ViewID viewID,ViewLayer::ViewLayerPlacement viewLayerPlacement,LayerList & layerList)190 ViewLayer::ViewLayerID PCBSketchWidget::multiLayerGetViewLayerID(ModelPart * modelPart, ViewLayer::ViewID viewID, ViewLayer::ViewLayerPlacement viewLayerPlacement, LayerList & layerList)
191 {
192 if (modelPart->itemType() == ModelPart::CopperFill) {
193 if (viewLayerPlacement == ViewLayer::NewBottom) return ViewLayer::GroundPlane0;
194 else if (viewLayerPlacement == ViewLayer::NewTop) return ViewLayer::GroundPlane1;
195 }
196
197 // privilege Copper if it's available
198 ViewLayer::ViewLayerID wantLayer = (modelPart->flippedSMD() && viewLayerPlacement == ViewLayer::NewTop) ? ViewLayer::Copper1 : ViewLayer::Copper0;
199 if (layerList.contains(wantLayer)) return wantLayer;
200
201 return SketchWidget::multiLayerGetViewLayerID(modelPart, viewID, viewLayerPlacement, layerList);
202 }
203
canDeleteItem(QGraphicsItem * item,int count)204 bool PCBSketchWidget::canDeleteItem(QGraphicsItem * item, int count)
205 {
206 VirtualWire * wire = dynamic_cast<VirtualWire *>(item);
207 if (wire != NULL && count > 1) return false;
208
209 return SketchWidget::canDeleteItem(item, count);
210 }
211
canCopyItem(QGraphicsItem * item,int count)212 bool PCBSketchWidget::canCopyItem(QGraphicsItem * item, int count)
213 {
214 VirtualWire * wire = dynamic_cast<VirtualWire *>(item);
215 if (wire != NULL) {
216 if (wire->getRatsnest()) return false;
217 }
218
219 return SketchWidget::canCopyItem(item, count);
220 }
221
canChainWire(Wire * wire)222 bool PCBSketchWidget::canChainWire(Wire * wire) {
223 bool result = SketchWidget::canChainWire(wire);
224 if (!result) return result;
225
226 if (wire->getRatsnest()) {
227 ConnectorItem * c0 = wire->connector0()->firstConnectedToIsh();
228 if (c0 == NULL) return false;
229
230 ConnectorItem * c1 = wire->connector1()->firstConnectedToIsh();
231 if (c1 == NULL) return false;
232
233 return !c0->wiredTo(c1, (ViewGeometry::NormalFlag | ViewGeometry::PCBTraceFlag | ViewGeometry::RatsnestFlag | ViewGeometry::SchematicTraceFlag) ^ getTraceFlag());
234 }
235
236 return result;
237 }
238
239
createTrace(Wire * wire,bool useLastWireColor)240 void PCBSketchWidget::createTrace(Wire * wire, bool useLastWireColor) {
241 QString commandString = tr("Create Trace from Ratsnest");
242 SketchWidget::createTrace(wire, commandString, getTraceFlag(), useLastWireColor);
243 ensureTraceLayerVisible();
244 }
245
excludeFromAutoroute(bool exclude)246 void PCBSketchWidget::excludeFromAutoroute(bool exclude)
247 {
248 foreach (QGraphicsItem * item, scene()->selectedItems()) {
249 TraceWire * wire = dynamic_cast<TraceWire *>(item);
250
251 if (wire) {
252 if (!wire->isTraceType(getTraceFlag())) continue;
253
254 QList<Wire *> wires;
255 QList<ConnectorItem *> ends;
256 wire->collectChained(wires, ends);
257 foreach (Wire * w, wires) {
258 w->setAutoroutable(!exclude);
259 }
260 continue;
261 }
262
263 JumperItem * jumperItem = dynamic_cast<JumperItem *>(item);
264 if (jumperItem) {
265 jumperItem->setAutoroutable(!exclude);
266 continue;
267 }
268
269 Via * via = dynamic_cast<Via *>(item);
270 if (via) {
271 via->setAutoroutable(!exclude);
272 continue;
273 }
274 }
275 }
276
selectAllExcludedTraces()277 void PCBSketchWidget::selectAllExcludedTraces()
278 {
279 selectAllXTraces(false, QObject::tr("Select all 'Don't autoroute' traces"), autorouteTypePCB());
280 }
281
selectAllIncludedTraces()282 void PCBSketchWidget::selectAllIncludedTraces()
283 {
284 selectAllXTraces(true, QObject::tr("Select all autorouteable traces"), autorouteTypePCB());
285 }
286
selectAllXTraces(bool autoroutable,const QString & cmdText,bool forPCB)287 void PCBSketchWidget::selectAllXTraces(bool autoroutable, const QString & cmdText, bool forPCB)
288 {
289 QList<Wire *> wires;
290 QList<QGraphicsItem *> items;
291 if (forPCB) {
292 int boardCount;
293 ItemBase * board = findSelectedBoard(boardCount);
294 if (boardCount == 0 && autorouteTypePCB()) {
295 QMessageBox::critical(this, tr("Fritzing"),
296 tr("Your sketch does not have a board yet! Please add a PCB in order to use this selection operation."));
297 return;
298 }
299 if (board == NULL) {
300 QMessageBox::critical(this, tr("Fritzing"),
301 tr("Please click on a PCB first--this selection operation only works for one board at a time."));
302 return;
303 }
304
305 items = scene()->collidingItems(board);
306 }
307 else {
308 items = scene()->items();
309 }
310 foreach (QGraphicsItem * item, items) {
311 TraceWire * wire = dynamic_cast<TraceWire *>(item);
312 if (wire == NULL) continue;
313
314 if (!wire->isTraceType(getTraceFlag())) continue;
315
316 if (wire->getAutoroutable() == autoroutable) {
317 wires.append(wire);
318 }
319 }
320
321 QUndoCommand * parentCommand = new QUndoCommand(cmdText);
322
323 stackSelectionState(false, parentCommand);
324 SelectItemCommand * selectItemCommand = new SelectItemCommand(this, SelectItemCommand::NormalSelect, parentCommand);
325 foreach (Wire * wire, wires) {
326 selectItemCommand->addRedo(wire->id());
327 }
328
329 scene()->clearSelection();
330 m_undoStack->push(parentCommand);
331 }
332
333
334
hoverEnterPartConnectorMessage(QGraphicsSceneHoverEvent * event,ConnectorItem * item)335 const QString & PCBSketchWidget::hoverEnterPartConnectorMessage(QGraphicsSceneHoverEvent * event, ConnectorItem * item)
336 {
337 Q_UNUSED(event);
338 Q_UNUSED(item);
339
340 static QString message = tr("Click this connector to drag out a new trace.");
341
342 return message;
343 }
344
addDefaultParts()345 void PCBSketchWidget::addDefaultParts() {
346
347 long newID = ItemBase::getNextID();
348 ViewGeometry viewGeometry;
349 viewGeometry.setLoc(QPointF(0, 0));
350
351 // have to put this off until later, because positioning the item doesn't work correctly until the view is visible
352 m_addedDefaultPart = addItem(referenceModel()->retrieveModelPart(ModuleIDNames::TwoSidedRectanglePCBModuleIDName), ViewLayer::NewTop, BaseCommand::CrossView, viewGeometry, newID, -1, NULL);
353 m_addDefaultParts = true;
354
355 changeBoardLayers(2, true);
356 }
357
showEvent(QShowEvent * event)358 void PCBSketchWidget::showEvent(QShowEvent * event) {
359 SketchWidget::showEvent(event);
360 dealWithDefaultParts();
361 }
362
dealWithDefaultParts()363 void PCBSketchWidget::dealWithDefaultParts() {
364 if (!m_addDefaultParts) return;
365 if (m_addedDefaultPart == NULL) return;
366
367 m_addDefaultParts = false;
368
369 QSizeF vpSize = this->viewport()->size();
370 QSizeF partSize(300, 200);
371
372 //if (vpSize.height() < helpSize.height() + 50 + partSize.height()) {
373 //vpSize.setWidth(vpSize.width() - verticalScrollBar()->width());
374 //}
375
376 QPointF p;
377 p.setX((int) ((vpSize.width() - partSize.width()) / 2.0));
378 p.setY((int) ((vpSize.height() - partSize.height()) / 2.0));
379
380 // place it
381 QPointF q = mapToScene(p.toPoint());
382 m_addedDefaultPart->setPos(q);
383 alignOneToGrid(m_addedDefaultPart);
384 ResizableBoard * rb = qobject_cast<ResizableBoard *>(m_addedDefaultPart);
385 if (rb) rb->resizePixels(partSize.width(), partSize.height(), m_viewLayers);
386 QTimer::singleShot(10, this, SLOT(vScrollToZero()));
387
388 // set both layers active by default
389 setLayerActive(ViewLayer::Copper1, true);
390 setLayerActive(ViewLayer::Copper0, true);
391 setLayerActive(ViewLayer::Silkscreen1, true);
392 setLayerActive(ViewLayer::Silkscreen0, true);
393 }
394
395
setClipEnds(ClipableWire * vw,bool clipEnds)396 void PCBSketchWidget::setClipEnds(ClipableWire * vw, bool clipEnds) {
397 vw->setClipEnds(clipEnds);
398 }
399
getDragWireViewLayerID(ConnectorItem * connectorItem)400 ViewLayer::ViewLayerID PCBSketchWidget::getDragWireViewLayerID(ConnectorItem * connectorItem) {
401 switch (connectorItem->attachedToViewLayerID()) {
402 case ViewLayer::Copper1:
403 case ViewLayer::Copper1Trace:
404 case ViewLayer::GroundPlane1:
405 return ViewLayer::Copper1Trace;
406 default:
407 return ViewLayer::Copper0Trace;
408 }
409 }
410
getWireViewLayerID(const ViewGeometry & viewGeometry,ViewLayer::ViewLayerPlacement viewLayerPlacement)411 ViewLayer::ViewLayerID PCBSketchWidget::getWireViewLayerID(const ViewGeometry & viewGeometry, ViewLayer::ViewLayerPlacement viewLayerPlacement) {
412 if (viewGeometry.getRatsnest()) {
413 return ViewLayer::PcbRatsnest;
414 }
415
416 if (viewGeometry.getAnyTrace()) {
417 switch (viewLayerPlacement) {
418 case ViewLayer::NewTop:
419 return ViewLayer::Copper1Trace;
420 default:
421 return ViewLayer::Copper0Trace;
422 }
423 }
424
425 switch (viewLayerPlacement) {
426 case ViewLayer::NewTop:
427 return ViewLayer::Copper1Trace;
428 default:
429 return m_wireViewLayerID;
430 }
431 }
432
initWire(Wire * wire,int penWidth)433 void PCBSketchWidget::initWire(Wire * wire, int penWidth) {
434 Q_UNUSED(penWidth);
435 if (wire->getRatsnest()) return;
436
437 wire->setColorString(traceColor(wire->connector0()), 1.0, false);
438 wire->setPenWidth(1, this, 2);
439 }
440
autorouteTypePCB()441 bool PCBSketchWidget::autorouteTypePCB() {
442 return true;
443 }
444
traceColor(ConnectorItem * forColor)445 const QString & PCBSketchWidget::traceColor(ConnectorItem * forColor) {
446 switch(forColor->attachedToViewLayerID()) {
447 case ViewLayer::Copper1:
448 case ViewLayer::Copper1Trace:
449 case ViewLayer::GroundPlane1:
450 return PCBTraceColor1;
451 default:
452 return PCBTraceColor;
453 }
454 }
455
traceColor(ViewLayer::ViewLayerPlacement viewLayerPlacement)456 const QString & PCBSketchWidget::traceColor(ViewLayer::ViewLayerPlacement viewLayerPlacement) {
457 if (viewLayerPlacement == ViewLayer::NewTop) {
458 return PCBTraceColor1;
459 }
460
461 return PCBTraceColor;
462 }
463
cleanType()464 PCBSketchWidget::CleanType PCBSketchWidget::cleanType() {
465 return m_cleanType;
466 }
467
ensureTraceLayersVisible()468 void PCBSketchWidget::ensureTraceLayersVisible() {
469 ensureLayerVisible(ViewLayer::Copper0);
470 ensureLayerVisible(ViewLayer::Copper0Trace);
471 ensureLayerVisible(ViewLayer::GroundPlane0);
472 if (m_boardLayers == 2) {
473 ensureLayerVisible(ViewLayer::Copper1);
474 ensureLayerVisible(ViewLayer::Copper1Trace);
475 ensureLayerVisible(ViewLayer::GroundPlane1);
476 }
477 }
478
ensureTraceLayerVisible()479 void PCBSketchWidget::ensureTraceLayerVisible() {
480 ensureLayerVisible(ViewLayer::Copper0);
481 ensureLayerVisible(ViewLayer::Copper0Trace);
482 }
483
canChainMultiple()484 bool PCBSketchWidget::canChainMultiple() {
485 return false;
486 }
487
setNewPartVisible(ItemBase * itemBase)488 void PCBSketchWidget::setNewPartVisible(ItemBase * itemBase) {
489 if (itemBase->itemType() == ModelPart::Breadboard ||
490 itemBase->itemType() == ModelPart::Symbol ||
491 itemBase->itemType() == ModelPart::SchematicSubpart ||
492 itemBase->moduleID().endsWith(ModuleIDNames::SchematicFrameModuleIDName) ||
493 itemBase->moduleID().endsWith(ModuleIDNames::PowerModuleIDName))
494 {
495 // don't need to see the breadboard in the other views
496 // but it's there so connections can be more easily synched between views
497 itemBase->setVisible(false);
498 itemBase->setEverVisible(false);
499 }
500 }
501
canDropModelPart(ModelPart * modelPart)502 bool PCBSketchWidget::canDropModelPart(ModelPart * modelPart) {
503 if (!SketchWidget::canDropModelPart(modelPart)) return false;
504
505 if (Board::isBoard(modelPart)) {
506 return matchesLayer(modelPart);
507 }
508
509 switch (modelPart->itemType()) {
510 case ModelPart::Logo:
511 if (modelPart->moduleID().contains("schematic", Qt::CaseInsensitive)) return false;
512 if (modelPart->moduleID().contains("breadboard", Qt::CaseInsensitive)) return false;
513 case ModelPart::Jumper:
514 case ModelPart::Ruler:
515 case ModelPart::CopperFill:
516 return true;
517 case ModelPart::Wire:
518 case ModelPart::Breadboard:
519 case ModelPart::Symbol:
520 case ModelPart::SchematicSubpart:
521 // can't drag and drop these parts in this view
522 return false;
523 default:
524 return !modelPart->moduleID().endsWith(ModuleIDNames::SchematicFrameModuleIDName);
525 }
526
527 return true;
528 }
529
bothEndsConnected(Wire * wire,ViewGeometry::WireFlags flag,ConnectorItem * oneEnd,QList<Wire * > & wires,QList<ConnectorItem * > & partConnectorItems)530 bool PCBSketchWidget::bothEndsConnected(Wire * wire, ViewGeometry::WireFlags flag, ConnectorItem * oneEnd, QList<Wire *> & wires, QList<ConnectorItem *> & partConnectorItems)
531 {
532 QList<Wire *> visited;
533 return bothEndsConnectedAux(wire, flag, oneEnd, wires, partConnectorItems, visited);
534 }
535
536
bothEndsConnectedAux(Wire * wire,ViewGeometry::WireFlags flag,ConnectorItem * oneEnd,QList<Wire * > & wires,QList<ConnectorItem * > & partConnectorItems,QList<Wire * > & visited)537 bool PCBSketchWidget::bothEndsConnectedAux(Wire * wire, ViewGeometry::WireFlags flag, ConnectorItem * oneEnd, QList<Wire *> & wires, QList<ConnectorItem *> & partConnectorItems, QList<Wire *> & visited)
538 {
539 if (visited.contains(wire)) return false;
540 visited.append(wire);
541
542 bool result = false;
543 ConnectorItem * otherEnd = wire->otherConnector(oneEnd);
544 foreach (ConnectorItem * toConnectorItem, otherEnd->connectedToItems()) {
545 if (partConnectorItems.contains(toConnectorItem)) {
546 result = true;
547 continue;
548 }
549
550 if (toConnectorItem->attachedToItemType() != ModelPart::Wire) continue;
551
552 Wire * w = qobject_cast<Wire *>(toConnectorItem->attachedTo());
553 ViewGeometry::WireFlags wflag = w->wireFlags() & (ViewGeometry::RatsnestFlag | getTraceFlag());
554 if (wflag != flag) continue;
555
556 result = bothEndsConnectedAux(w, flag, toConnectorItem, wires, partConnectorItems, visited) || result; // let it recurse
557 }
558
559 if (result) {
560 wires.removeOne(wire);
561 }
562
563 return result;
564 }
565
canCreateWire(Wire * dragWire,ConnectorItem * from,ConnectorItem * to)566 bool PCBSketchWidget::canCreateWire(Wire * dragWire, ConnectorItem * from, ConnectorItem * to)
567 {
568 Q_UNUSED(dragWire);
569 return ((from != NULL) && (to != NULL));
570 }
571
findNearestPartConnectorItem(ConnectorItem * fromConnectorItem)572 ConnectorItem * PCBSketchWidget::findNearestPartConnectorItem(ConnectorItem * fromConnectorItem) {
573 // find the nearest part to fromConnectorItem
574 Wire * wire = qobject_cast<Wire *>(fromConnectorItem->attachedTo());
575 if (wire == NULL) return NULL;
576
577 QList<ConnectorItem *> ends;
578 calcDistances(wire, ends);
579 clearDistances();
580 if (ends.count() < 1) return NULL;
581
582 return ends[0];
583 }
584
calcDistances(Wire * wire,QList<ConnectorItem * > & ends)585 void PCBSketchWidget::calcDistances(Wire * wire, QList<ConnectorItem *> & ends) {
586 QList<Wire *> chained;
587 wire->collectChained(chained, ends);
588 if (ends.count() < 2) return;
589
590 clearDistances();
591 foreach (ConnectorItem * end, ends) {
592 bool fromConnector0;
593 QList<Wire *> distanceWires;
594 int distance = calcDistance(wire, end, 0, distanceWires, fromConnector0);
595 DistanceThing * dt = new DistanceThing;
596 dt->distance = distance;
597 dt->fromConnector0 = fromConnector0;
598 DebugDialog::debug(QString("distance %1 %2 %3, %4 %5")
599 .arg(end->attachedToID()).arg(end->attachedToTitle()).arg(end->connectorSharedID())
600 .arg(distance).arg(fromConnector0 ? "connector0" : "connector1"));
601 distances.insert(end, dt);
602 }
603 qSort(ends.begin(), ends.end(), distanceLessThan);
604
605 }
606
clearDistances()607 void PCBSketchWidget::clearDistances() {
608 foreach (ConnectorItem * c, distances.keys()) {
609 DistanceThing * dt = distances.value(c, NULL);
610 if (dt) delete dt;
611 }
612 distances.clear();
613 }
614
calcDistanceAux(ConnectorItem * from,ConnectorItem * to,int distance,QList<Wire * > & distanceWires)615 int PCBSketchWidget::calcDistanceAux(ConnectorItem * from, ConnectorItem * to, int distance, QList<Wire *> & distanceWires) {
616 //DebugDialog::debug(QString("calc distance aux: %1 %2, %3 %4, %5").arg(from->attachedToID()).arg(from->connectorSharedID())
617 //.arg(to->attachedToTitle()).arg(to->connectorSharedID()).arg(distance));
618
619 foreach (ConnectorItem * toConnectorItem, from->connectedToItems()) {
620 if (toConnectorItem == to) {
621 return distance;
622 }
623 }
624
625 int result = MAX_INT;
626 foreach (ConnectorItem * toConnectorItem, from->connectedToItems()) {
627 if (toConnectorItem->attachedToItemType() != ModelPart::Wire) continue;
628
629 Wire * w = qobject_cast<Wire *>(toConnectorItem->attachedTo());
630 if (distanceWires.contains(w)) continue;
631
632 bool fromConnector0;
633 int temp = calcDistance(w, to, distance + 1, distanceWires, fromConnector0);
634 if (temp < result) {
635 result = temp;
636 }
637 }
638
639 return result;
640 }
641
calcDistance(Wire * wire,ConnectorItem * end,int distance,QList<Wire * > & distanceWires,bool & fromConnector0)642 int PCBSketchWidget::calcDistance(Wire * wire, ConnectorItem * end, int distance, QList<Wire *> & distanceWires, bool & fromConnector0) {
643 //DebugDialog::debug(QString("calc distance wire: %1 rat:%2 to %3 %4, %5").arg(wire->id()).arg(wire->getRatsnest())
644 //.arg(end->attachedToTitle()).arg(end->connectorSharedID()).arg(distance));
645
646 distanceWires.append(wire);
647 int d0 = calcDistanceAux(wire->connector0(), end, distance, distanceWires);
648 if (d0 == distance) {
649 fromConnector0 = true;
650 return d0;
651 }
652
653 int d1 = calcDistanceAux(wire->connector1(), end, distance, distanceWires);
654 if (d0 <= d1) {
655 fromConnector0 = true;
656 return d0;
657 }
658
659 fromConnector0 = false;
660 return d1;
661 }
662
showGroundTraces(QList<ConnectorItem * > & connectorItems,bool show)663 void PCBSketchWidget::showGroundTraces(QList<ConnectorItem *> & connectorItems, bool show) {
664
665 foreach (ConnectorItem * connectorItem, connectorItems) {
666 TraceWire * trace = dynamic_cast<TraceWire *>(connectorItem->attachedTo());
667 if (trace == NULL) continue;
668
669 if (!trace->isTraceType(getTraceFlag())) continue;
670
671 trace->setVisible(show);
672 }
673 }
674
getLabelFont(QFont & font,QColor & color,ItemBase * itemBase)675 void PCBSketchWidget::getLabelFont(QFont & font, QColor & color, ItemBase * itemBase) {
676 font.setFamily(OCRAFontName);
677 font.setPointSize(getLabelFontSizeSmall());
678 font.setBold(false);
679 font.setItalic(false);
680 color.setAlpha(255);
681
682 QString name = ViewLayer::Silkscreen1Color;
683
684 if (boardLayers() == 2) {
685 if (itemBase->viewLayerPlacement() == ViewLayer::NewBottom) name = ViewLayer::Silkscreen0Color;
686 }
687
688 color.setNamedColor(name);
689
690 }
691
getLabelViewLayerID(ItemBase * itemBase)692 ViewLayer::ViewLayerID PCBSketchWidget::getLabelViewLayerID(ItemBase * itemBase) {
693
694 if (boardLayers() == 2) {
695 if (itemBase->viewLayerPlacement() == ViewLayer::NewBottom) return ViewLayer::Silkscreen0Label;
696 }
697
698 return ViewLayer::Silkscreen1Label;
699 }
700
getLabelFontSizeTiny()701 double PCBSketchWidget::getLabelFontSizeTiny() {
702 return 3;
703 }
704
getLabelFontSizeSmall()705 double PCBSketchWidget::getLabelFontSizeSmall() {
706 return 5;
707 }
708
getLabelFontSizeMedium()709 double PCBSketchWidget::getLabelFontSizeMedium() {
710 return 7;
711 }
712
getLabelFontSizeLarge()713 double PCBSketchWidget::getLabelFontSizeLarge() {
714 return 12;
715 }
716
resizeBoard(double mmW,double mmH,bool doEmit)717 void PCBSketchWidget::resizeBoard(double mmW, double mmH, bool doEmit)
718 {
719 Q_UNUSED(doEmit);
720
721 PaletteItem * item = getSelectedPart();
722 if (item == NULL) return;
723
724 bool handle = false;
725 switch (item->itemType()) {
726 case ModelPart::ResizableBoard:
727 case ModelPart::Logo:
728 handle = true;
729 break;
730 case ModelPart::Part:
731 handle = item->moduleID().endsWith(ModuleIDNames::PadModuleIDName) ||
732 item->moduleID().endsWith(ModuleIDNames::CopperBlockerModuleIDName) ||
733 item->moduleID().endsWith(ModuleIDNames::SchematicFrameModuleIDName);
734 break;
735 default:
736 break;
737 }
738
739 if (!handle) return SketchWidget::resizeBoard(mmW, mmH, doEmit);
740
741 resizeWithHandle(item, mmW, mmH);
742 }
743
showLabelFirstTime(long itemID,bool show,bool doEmit)744 void PCBSketchWidget::showLabelFirstTime(long itemID, bool show, bool doEmit) {
745 // called when new item is added, to decide whether to show part label
746 SketchWidget::showLabelFirstTime(itemID, show, doEmit);
747 ItemBase * itemBase = findItem(itemID);
748 if (itemBase == NULL) return;
749 if (!canDropModelPart(itemBase->modelPart())) return;
750
751 switch (itemBase->itemType()) {
752 case ModelPart::Part:
753 case ModelPart::Jumper:
754 {
755 if (itemBase->hasPartLabel()) {
756 ViewLayer * viewLayer = m_viewLayers.value(getLabelViewLayerID(itemBase));
757 itemBase->showPartLabel(itemBase->isVisible(), viewLayer);
758 itemBase->partLabelSetHidden(!viewLayer->visible());
759 }
760 }
761 break;
762 default:
763 break;
764 }
765
766 }
767
findBoardBeneath(ItemBase * itemBase)768 ItemBase * PCBSketchWidget::findBoardBeneath(ItemBase * itemBase) {
769 foreach (QGraphicsItem * item, scene()->collidingItems(itemBase)) {
770 Board * board = dynamic_cast<Board *>(item);
771 if (board == NULL) continue;
772
773 if (Board::isBoard(board)) return board;
774 }
775
776 return NULL;
777 }
778
findSelectedBoard(int & boardCount)779 ItemBase * PCBSketchWidget::findSelectedBoard(int & boardCount) {
780 QList<ItemBase *> boards = findBoard();
781 boardCount = boards.count();
782 if (boards.count() == 0) return NULL;
783 if (boards.count() == 1) return boards.at(0);
784
785 int selectedCount = 0;
786 ItemBase * selectedBoard = NULL;
787 foreach (ItemBase * board, boards) {
788 if (board->isSelected()) {
789 selectedCount++;
790 selectedBoard = board;
791 }
792 }
793
794 if (selectedCount == 1) return selectedBoard;
795 return NULL;
796 }
797
findBoard()798 QList<ItemBase *> PCBSketchWidget::findBoard() {
799 QSet<ItemBase *> boards;
800 foreach (QGraphicsItem * childItem, items()) {
801 Board * board = dynamic_cast<Board *>(childItem);
802 if (board == NULL) continue;
803
804 if (Board::isBoard(board)) {
805 boards.insert(board->layerKinChief());
806 }
807 }
808
809 return boards.toList();
810 }
811
forwardRoutingStatus(const RoutingStatus & routingStatus)812 void PCBSketchWidget::forwardRoutingStatus(const RoutingStatus & routingStatus)
813 {
814 m_routingStatus = routingStatus;
815 SketchWidget::forwardRoutingStatus(routingStatus);
816 }
817
818
defaultGridSizeInches()819 double PCBSketchWidget::defaultGridSizeInches() {
820 return 0.1;
821 }
822
wireViewLayerPlacement(ConnectorItem * connectorItem)823 ViewLayer::ViewLayerPlacement PCBSketchWidget::wireViewLayerPlacement(ConnectorItem * connectorItem) {
824 switch (connectorItem->attachedToViewLayerID()) {
825 case ViewLayer::Copper1:
826 case ViewLayer::Copper1Trace:
827 case ViewLayer::GroundPlane1:
828 return ViewLayer::NewTop;
829 default:
830 return ViewLayer::NewBottom;
831 }
832 }
833
setBoardLayers(int layers,bool redraw)834 void PCBSketchWidget::setBoardLayers(int layers, bool redraw) {
835 SketchWidget::setBoardLayers(layers, redraw);
836
837 QList <ViewLayer::ViewLayerID> viewLayerIDs;
838 viewLayerIDs << ViewLayer::Copper1 << ViewLayer::Copper1Trace;
839 foreach (ViewLayer::ViewLayerID viewLayerID, viewLayerIDs) {
840 ViewLayer * layer = m_viewLayers.value(viewLayerID, NULL);
841 if (layer) {
842 layer->action()->setEnabled(layers == 2);
843 layer->setVisible(layers == 2);
844 if (redraw) {
845 setLayerVisible(layer, layers == 2, true);
846 if (layers == 2) {
847 layer->action()->setChecked(true);
848 }
849 }
850 }
851 }
852 }
853
swapLayers(ItemBase *,int newLayers,QUndoCommand * parentCommand)854 void PCBSketchWidget::swapLayers(ItemBase *, int newLayers, QUndoCommand * parentCommand)
855 {
856 QList<ItemBase *> smds;
857 QList<ItemBase *> pads;
858 QList<Wire *> already;
859
860 ChangeBoardLayersCommand * changeBoardCommand = new ChangeBoardLayersCommand(this, m_boardLayers, newLayers, parentCommand);
861 QList<ItemBase *> boards = findBoard();
862 foreach (ItemBase * board, boards) {
863 new SetPropCommand(this, board->id(), "layers", QString::number(m_boardLayers), QString::number(newLayers), true, parentCommand);
864 }
865
866 if (newLayers == 2) {
867 new CleanUpRatsnestsCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
868 new CleanUpWiresCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
869 return;
870 }
871
872 // disconnect and flip smds
873 foreach (QGraphicsItem * item, scene()->items()) {
874 ItemBase * smd = dynamic_cast<ItemBase *>(item);
875 if (smd == NULL) continue;
876 if (smd->moduleID().endsWith(ModuleIDNames::PadModuleIDName)) {
877 pads << smd;
878 continue;
879 }
880
881 if (!smd->modelPart()->flippedSMD()) continue;
882
883 smd = smd->layerKinChief();
884 if (smds.contains(smd)) continue;
885
886 smds.append(smd);
887 }
888
889 changeTraceLayer(NULL, true, changeBoardCommand);
890
891 foreach (ItemBase * smd, smds) {
892 long newID;
893 emit subSwapSignal(this, smd, smd->moduleID(), (newLayers == 1) ? ViewLayer::NewBottom : ViewLayer::NewTop, newID, changeBoardCommand);
894 }
895
896 foreach (ItemBase * itemBase, pads) {
897 Pad * pad = qobject_cast<Pad *>(itemBase);
898 if (pad == NULL) continue;
899
900 long newID;
901 emit subSwapSignal(this, pad,
902 (newLayers == 1) ? ModuleIDNames::Copper0PadModuleIDName : ModuleIDNames::PadModuleIDName,
903 (newLayers == 1) ? ViewLayer::NewBottom : ViewLayer::NewTop,
904 newID, changeBoardCommand);
905
906 double w = pad->modelPart()->localProp("width").toDouble();
907 double h = pad->modelPart()->localProp("height").toDouble();
908 new ResizeBoardCommand(this, newID, w, h, w, h, parentCommand);
909 }
910
911 }
912
isBoardLayerChange(ItemBase * itemBase,const QString & newModuleID,int & newLayers)913 bool PCBSketchWidget::isBoardLayerChange(ItemBase * itemBase, const QString & newModuleID, int & newLayers)
914 {
915 newLayers = m_boardLayers;
916 if (!Board::isBoard(itemBase)) {
917 // no change
918 return false;
919 }
920
921 ModelPart * modelPart = referenceModel()->retrieveModelPart(newModuleID);
922 if (modelPart == NULL) {
923 // shouldn't happen
924 return false;
925 }
926
927 QString slayers = modelPart->properties().value("layers", "");
928 if (slayers.isEmpty()) {
929 // shouldn't happen
930 return false;
931 }
932
933 bool ok;
934 int layers = slayers.toInt(&ok);
935 if (!ok) {
936 // shouldn't happen
937 return false;
938 }
939
940 newLayers = layers;
941 return (m_boardLayers != layers);
942 }
943
changeBoardLayers(int layers,bool doEmit)944 void PCBSketchWidget::changeBoardLayers(int layers, bool doEmit) {
945 setBoardLayers(layers, true);
946 SketchWidget::changeBoardLayers(layers, doEmit);
947 if (layers == 1) {
948 this->setLayerActive(ViewLayer::Copper0, true);
949 this->setLayerActive(ViewLayer::Silkscreen0, true);
950 }
951 emit updateLayerMenuSignal();
952 }
953
loadFromModelParts(QList<ModelPart * > & modelParts,BaseCommand::CrossViewType crossViewType,QUndoCommand * parentCommand,bool offsetPaste,const QRectF * boundingRect,bool seekOutsideConnections,QList<long> & newIDs)954 void PCBSketchWidget::loadFromModelParts(QList<ModelPart *> & modelParts, BaseCommand::CrossViewType crossViewType, QUndoCommand * parentCommand,
955 bool offsetPaste, const QRectF * boundingRect, bool seekOutsideConnections, QList<long> & newIDs) {
956
957 int layers = 1;
958 if (parentCommand == NULL) {
959 foreach (ModelPart * modelPart, modelParts) {
960 if (Board::isBoard(modelPart)) {
961 QString slayers = modelPart->localProp("layers").toString();
962 if (slayers.isEmpty()) {
963 slayers = modelPart->properties().value("layers", "");
964 }
965 if (slayers.isEmpty()) {
966 // shouldn't happen
967 continue;
968 }
969 bool ok;
970 int localLayers = slayers.toInt(&ok);
971 if (!ok) {
972 // shouldn't happen
973 continue;
974 }
975 if (localLayers == 2) {
976 layers = 2;
977 break;
978 }
979 }
980 else if (modelPart->itemType() == ModelPart::Wire) {
981 QDomElement instance = modelPart->instanceDomElement();
982 QDomElement views = instance.firstChildElement("views");
983 QDomElement view = views.firstChildElement("pcbView");
984 if (view.attribute("layer").compare("copper1trace") == 0) {
985 layers = 2;
986 break;
987 }
988 }
989 else if (modelPart->itemType() == ModelPart::CopperFill) {
990 QDomElement instance = modelPart->instanceDomElement();
991 QDomElement views = instance.firstChildElement("views");
992 QDomElement view = views.firstChildElement("pcbView");
993 if (view.attribute("layer").compare("groundplane1") == 0) {
994 layers = 2;
995 break;
996 }
997 }
998 }
999 changeBoardLayers(layers, true);
1000 }
1001
1002 SketchWidget::loadFromModelParts(modelParts, crossViewType, parentCommand, offsetPaste, boundingRect, seekOutsideConnections, newIDs);
1003
1004 if (parentCommand == NULL) {
1005 changeBoardLayers(layers, true);
1006 shiftHoles();
1007 }
1008 }
1009
isInLayers(ConnectorItem * connectorItem,ViewLayer::ViewLayerPlacement viewLayerPlacement)1010 bool PCBSketchWidget::isInLayers(ConnectorItem * connectorItem, ViewLayer::ViewLayerPlacement viewLayerPlacement) {
1011 return connectorItem->isInLayers(viewLayerPlacement);
1012 }
1013
routeBothSides()1014 bool PCBSketchWidget::routeBothSides() {
1015 return m_boardLayers > 1;
1016 }
1017
sameElectricalLayer2(ViewLayer::ViewLayerID id1,ViewLayer::ViewLayerID id2)1018 bool PCBSketchWidget::sameElectricalLayer2(ViewLayer::ViewLayerID id1, ViewLayer::ViewLayerID id2) {
1019 switch (id1) {
1020 case ViewLayer::Copper0Trace:
1021 if (id1 == id2) return true;
1022 return (id2 == ViewLayer::Copper0 || id2 == ViewLayer::GroundPlane0);
1023 case ViewLayer::Copper0:
1024 case ViewLayer::GroundPlane0:
1025 if (id1 == id2) return true;
1026 return (id2 == ViewLayer::Copper0Trace);
1027 case ViewLayer::Copper1Trace:
1028 if (id1 == id2) return true;
1029 return (id2 == ViewLayer::Copper1 || id2 == ViewLayer::GroundPlane1);
1030 case ViewLayer::Copper1:
1031 case ViewLayer::GroundPlane1:
1032 if (id1 == id2) return true;
1033 return (id2 == ViewLayer::Copper1Trace);
1034 default:
1035 break;
1036 }
1037
1038 return false;
1039 }
1040
changeTraceLayer(ItemBase * itemBase,bool force,QUndoCommand * parentCommand)1041 void PCBSketchWidget::changeTraceLayer(ItemBase * itemBase, bool force, QUndoCommand * parentCommand) {
1042 QList<Wire *> visitedWires;
1043 QSet<Wire *> changeWires;
1044 TraceWire * sample = NULL;
1045 QList<QGraphicsItem *> items;
1046 if (itemBase != NULL) items << itemBase;
1047 else if (force) items = scene()->items();
1048 else items = scene()->selectedItems();
1049 foreach (QGraphicsItem * item, items) {
1050 TraceWire * tw = dynamic_cast<TraceWire *>(item);
1051 if (tw == NULL) continue;
1052
1053 if (!tw->isTraceType(getTraceFlag())) continue;
1054 if (visitedWires.contains(tw)) continue;
1055
1056 sample = tw;
1057
1058 QList<Wire *> wires;
1059 QList<ConnectorItem *> ends;
1060 tw->collectChained(wires, ends);
1061 visitedWires.append(wires);
1062
1063 if (!force) {
1064 bool canChange = true;
1065 foreach(ConnectorItem * end, ends) {
1066 if (end->getCrossLayerConnectorItem() == NULL) {
1067 canChange = false;
1068 break;
1069 }
1070 }
1071 if (!canChange) continue;
1072 }
1073
1074 changeWires.insert(tw);
1075 }
1076
1077 if (changeWires.count() == 0 || sample == NULL) return;
1078
1079 bool createNew = false;
1080 if (parentCommand == NULL) {
1081 parentCommand = new QUndoCommand(tr("Change trace layer"));
1082 createNew = true;
1083 }
1084
1085 ViewLayer::ViewLayerID newViewLayerID = (sample->viewLayerID() == ViewLayer::Copper0Trace) ? ViewLayer::Copper1Trace : ViewLayer::Copper0Trace;
1086 if (force) {
1087 // move all traces to bottom layer (force == true when switching from 2 layers to 1)
1088 newViewLayerID = ViewLayer::Copper0Trace;
1089 }
1090 foreach (Wire * wire, changeWires) {
1091 QList<Wire *> wires;
1092 QList<ConnectorItem *> ends;
1093 wire->collectChained(wires, ends);
1094
1095 // probably safest to disconnect change the layers and reconnect, so that's why the redundant looping
1096
1097 foreach (ConnectorItem * end, ends) {
1098 ConnectorItem * targetConnectorItem = NULL;
1099 foreach (ConnectorItem * toConnectorItem, end->connectedToItems()) {
1100 Wire * w = qobject_cast<Wire *>(toConnectorItem->attachedTo());
1101 if (w == NULL) continue;
1102
1103 if (wires.contains(w)) {
1104 targetConnectorItem = toConnectorItem;
1105 break;
1106 }
1107 }
1108
1109 extendChangeConnectionCommand(BaseCommand::SingleView,
1110 targetConnectorItem, end,
1111 ViewLayer::specFromID(end->attachedToViewLayerID()),
1112 false, parentCommand);
1113 }
1114
1115 foreach (Wire * w, wires) {
1116 new ChangeLayerCommand(this, w->id(), w->zValue(), m_viewLayers.value(newViewLayerID)->nextZ(), w->viewLayerID(), newViewLayerID, parentCommand);
1117 }
1118
1119 foreach (ConnectorItem * end, ends) {
1120 ConnectorItem * targetConnectorItem = NULL;
1121 foreach (ConnectorItem * toConnectorItem, end->connectedToItems()) {
1122 Wire * w = qobject_cast<Wire *>(toConnectorItem->attachedTo());
1123 if (w == NULL) continue;
1124
1125 if (wires.contains(w)) {
1126 targetConnectorItem = toConnectorItem;
1127 break;
1128 }
1129 }
1130
1131 new ChangeConnectionCommand(this, BaseCommand::SingleView,
1132 targetConnectorItem->attachedToID(), targetConnectorItem->connectorSharedID(),
1133 end->attachedToID(), end->connectorSharedID(),
1134 ViewLayer::specFromID(newViewLayerID),
1135 true, parentCommand);
1136 }
1137 }
1138
1139 if (createNew) {
1140 m_undoStack->waitPush(parentCommand, PropChangeDelay);
1141 }
1142 }
1143
changeLayer(long id,double z,ViewLayer::ViewLayerID viewLayerID)1144 void PCBSketchWidget::changeLayer(long id, double z, ViewLayer::ViewLayerID viewLayerID) {
1145 ItemBase * itemBase = findItem(id);
1146 if (itemBase == NULL) return;
1147
1148 itemBase->setViewLayerID(viewLayerID, m_viewLayers);
1149 itemBase->setZValue(z);
1150 itemBase->saveGeometry();
1151
1152 TraceWire * tw = qobject_cast<TraceWire *>(itemBase);
1153 if (tw != NULL) {
1154 ViewLayer::ViewLayerPlacement viewLayerPlacement = ViewLayer::specFromID(viewLayerID);
1155 tw->setViewLayerPlacement(viewLayerPlacement);
1156 tw->setColorString(traceColor(viewLayerPlacement), 1.0, true);
1157 ViewLayer * viewLayer = m_viewLayers.value(viewLayerID);
1158 tw->setInactive(!viewLayer->isActive());
1159 tw->setHidden(!viewLayer->visible());
1160 tw->update();
1161 }
1162
1163 updateInfoView();
1164 }
1165
resizingJumperItemPress(ItemBase * itemBase)1166 bool PCBSketchWidget::resizingJumperItemPress(ItemBase * itemBase) {
1167 if (itemBase == NULL) return false;
1168
1169 JumperItem * jumperItem = qobject_cast<JumperItem *>(itemBase->layerKinChief());
1170 if (jumperItem == NULL) return false;
1171 if (!jumperItem->inDrag()) return false;
1172
1173 m_resizingJumperItem = jumperItem;
1174 m_resizingJumperItem->saveParams();
1175 if (m_alignToGrid) {
1176 m_alignmentStartPoint = QPointF(0,0);
1177 ItemBase * board = findBoardBeneath(m_resizingJumperItem);
1178 QHash<long, ItemBase *> savedItems;
1179 QHash<Wire *, ConnectorItem *> savedWires;
1180 if (board == NULL) {
1181 foreach (QGraphicsItem * item, scene()->items()) {
1182 PaletteItemBase * itemBase = dynamic_cast<PaletteItemBase *>(item);
1183 if (itemBase == NULL) continue;
1184 if (itemBase->itemType() == ModelPart::Jumper) continue;
1185
1186 savedItems.insert(itemBase->layerKinChief()->id(), itemBase);
1187 }
1188 }
1189 findAlignmentAnchor(board, savedItems, savedWires);
1190 m_jumperDragOffset = jumperItem->dragOffset();
1191 connect(m_resizingJumperItem, SIGNAL(alignMe(JumperItem *, QPointF &)), this, SLOT(alignJumperItem(JumperItem *, QPointF &)), Qt::DirectConnection);
1192 }
1193 return true;
1194 }
1195
alignJumperItem(JumperItem * jumperItem,QPointF & loc)1196 void PCBSketchWidget::alignJumperItem(JumperItem * jumperItem, QPointF & loc) {
1197 Q_UNUSED(jumperItem);
1198 if (!m_alignToGrid) return;
1199
1200 QPointF newPos = loc - m_jumperDragOffset - m_alignmentStartPoint;
1201 double ny = GraphicsUtils::getNearestOrdinate(newPos.y(), gridSizeInches() * GraphicsUtils::SVGDPI);
1202 double nx = GraphicsUtils::getNearestOrdinate(newPos.x(), gridSizeInches() * GraphicsUtils::SVGDPI);
1203 loc.setX(loc.x() + nx - newPos.x());
1204 loc.setY(loc.y() + ny - newPos.y());
1205 }
1206
resizingJumperItemRelease()1207 bool PCBSketchWidget::resizingJumperItemRelease() {
1208 if (m_resizingJumperItem == NULL) return false;
1209
1210 if (m_alignToGrid) {
1211 disconnect(m_resizingJumperItem, SIGNAL(alignMe(JumperItem *, QPointF &)), this, SLOT(alignJumperItem(JumperItem *, QPointF &)));
1212 }
1213 resizeJumperItem();
1214 return true;
1215 }
1216
resizeJumperItem()1217 void PCBSketchWidget::resizeJumperItem() {
1218 QPointF oldC0, oldC1;
1219 QPointF oldPos;
1220 m_resizingJumperItem->getParams(oldPos, oldC0, oldC1);
1221 QPointF newC0, newC1;
1222 QPointF newPos;
1223 m_resizingJumperItem->saveParams();
1224 m_resizingJumperItem->getParams(newPos, newC0, newC1);
1225 QUndoCommand * cmd = new ResizeJumperItemCommand(this, m_resizingJumperItem->id(), oldPos, oldC0, oldC1, newPos, newC0, newC1, NULL);
1226 cmd->setText("Resize Jumper");
1227 m_undoStack->waitPush(cmd, 10);
1228 m_resizingJumperItem = NULL;
1229 }
1230
canDragWire(Wire * wire)1231 bool PCBSketchWidget::canDragWire(Wire * wire) {
1232 if (wire == NULL) return false;
1233
1234 if (wire->getRatsnest()) return false;
1235
1236 return true;
1237 }
1238
wireSplitSlot(Wire * wire,QPointF newPos,QPointF oldPos,const QLineF & oldLine)1239 void PCBSketchWidget::wireSplitSlot(Wire* wire, QPointF newPos, QPointF oldPos, const QLineF & oldLine) {
1240 if (!wire->getRatsnest()) {
1241 SketchWidget::wireSplitSlot(wire, newPos, oldPos, oldLine);
1242 }
1243
1244 createTrace(wire, false);
1245 }
1246
1247
addCopperLogoItem(ViewLayer::ViewLayerPlacement viewLayerPlacement)1248 ItemBase * PCBSketchWidget::addCopperLogoItem(ViewLayer::ViewLayerPlacement viewLayerPlacement)
1249 {
1250 long newID = ItemBase::getNextID();
1251 ViewGeometry viewGeometry;
1252 viewGeometry.setLoc(QPointF(0, 0));
1253 QString moduleID = (viewLayerPlacement == ViewLayer::NewBottom) ? ModuleIDNames::Copper0LogoTextModuleIDName : ModuleIDNames::Copper1LogoTextModuleIDName;
1254 return addItem(referenceModel()->retrieveModelPart(moduleID), viewLayerPlacement, BaseCommand::SingleView, viewGeometry, newID, -1, NULL);
1255 }
1256
hasAnyNets()1257 bool PCBSketchWidget::hasAnyNets() {
1258 return m_routingStatus.m_netCount > 0;
1259 }
1260
jumperItemSize()1261 QSizeF PCBSketchWidget::jumperItemSize() {
1262 if (m_jumperItemSize.width() == 0) {
1263 long newID = ItemBase::getNextID();
1264 ViewGeometry viewGeometry;
1265 viewGeometry.setLoc(QPointF(0, 0));
1266 ItemBase * itemBase = addItem(referenceModel()->retrieveModelPart(ModuleIDNames::JumperModuleIDName), ViewLayer::NewTop, BaseCommand::SingleView, viewGeometry, newID, -1, NULL);
1267 if (itemBase) {
1268 JumperItem * jumperItem = qobject_cast<JumperItem *>(itemBase);
1269 m_jumperItemSize = jumperItem->connector0()->rect().size();
1270 deleteItem(itemBase, true, false, false);
1271 }
1272 }
1273
1274 return m_jumperItemSize;
1275 }
1276
getKeepout()1277 double PCBSketchWidget::getKeepout() {
1278 QString keepoutString = m_autorouterSettings.value(DRC::KeepoutSettingName);
1279 if (keepoutString.isEmpty()) {
1280 QSettings settings;
1281 keepoutString = settings.value(DRC::KeepoutSettingName, "").toString();
1282 }
1283 bool ok;
1284 double inches = TextUtils::convertToInches(keepoutString, &ok, false);
1285 if (!ok) {
1286 keepoutString = QString("%1in").arg(DRC::KeepoutDefaultMils / 1000);
1287 inches = DRC::KeepoutDefaultMils / 1000;
1288 }
1289
1290 m_autorouterSettings.insert(DRC::KeepoutSettingName, keepoutString);
1291
1292 return inches * GraphicsUtils::SVGDPI; // inches converted to pixels
1293 }
1294
setKeepout(double mils)1295 void PCBSketchWidget::setKeepout(double mils) {
1296 QString keepoutString = QString("%1in").arg(mils / 1000);
1297 m_autorouterSettings.insert(DRC::KeepoutSettingName, keepoutString);
1298 }
1299
resetKeepout()1300 void PCBSketchWidget::resetKeepout() {
1301 setKeepout(DRC::KeepoutDefaultMils);
1302 }
1303
acceptsTrace(const ViewGeometry & viewGeometry)1304 bool PCBSketchWidget::acceptsTrace(const ViewGeometry & viewGeometry) {
1305 return !viewGeometry.getSchematicTrace();
1306 }
1307
placePartDroppedInOtherView(ModelPart * modelPart,ViewLayer::ViewLayerPlacement viewLayerPlacement,const ViewGeometry & viewGeometry,long id,SketchWidget * dropOrigin)1308 ItemBase * PCBSketchWidget::placePartDroppedInOtherView(ModelPart * modelPart, ViewLayer::ViewLayerPlacement viewLayerPlacement, const ViewGeometry & viewGeometry, long id, SketchWidget * dropOrigin)
1309 {
1310 ItemBase * newItem = SketchWidget::placePartDroppedInOtherView(modelPart, viewLayerPlacement, viewGeometry, id, dropOrigin);
1311 if (newItem == NULL) return newItem;
1312 if (!newItem->isEverVisible()) return newItem;
1313
1314 dealWithDefaultParts();
1315
1316 QList<ItemBase *> boards;
1317 if (autorouteTypePCB()) {
1318 boards = findBoard();
1319 }
1320 else {
1321 boards << NULL;
1322 }
1323
1324 foreach (ItemBase * board, boards) {
1325
1326 // This is a 2d bin-packing problem. We can use our tile datastructure for this.
1327 // Use a simple best-fit approach for now. No idea how optimal a solution it is.
1328
1329 CMRouter router(this, board, false);
1330 int keepout = 10;
1331 router.setKeepout(keepout);
1332 Plane * plane = router.initPlane(false);
1333 QList<Tile *> alreadyTiled;
1334
1335 foreach (QGraphicsItem * item, (board) ? scene()->collidingItems(board) : scene()->items()) {
1336 ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
1337 if (itemBase == NULL) continue;
1338 if (!itemBase->isEverVisible()) continue;
1339 if (itemBase->layerKinChief() != itemBase) continue;
1340
1341 if (itemBase->layerKinChief() == board) continue;
1342 if (itemBase->layerKinChief() == newItem) continue;
1343
1344 Wire * wire = qobject_cast<Wire *>(itemBase);
1345 if (wire != NULL) {
1346 if (!wire->getTrace()) continue;
1347 if (!wire->isTraceType(getTraceFlag())) continue;
1348 }
1349 else if (ResizableBoard::isBoard(itemBase)) continue;
1350
1351 // itemBase->debugInfo("tiling");
1352 QRectF r = itemBase->sceneBoundingRect().adjusted(-keepout, -keepout, keepout, keepout);
1353 router.insertTile(plane, r, alreadyTiled, NULL, Tile::OBSTACLE, CMRouter::IgnoreAllOverlaps);
1354 }
1355
1356 BestPlace bestPlace;
1357 bestPlace.maxRect = router.boardRect();
1358 bestPlace.rotate90 = false;
1359 bestPlace.width = realToTile(newItem->boundingRect().width());
1360 bestPlace.height = realToTile(newItem->boundingRect().height());
1361 bestPlace.plane = plane;
1362
1363 TiSrArea(NULL, plane, &bestPlace.maxRect, Panelizer::placeBestFit, &bestPlace);
1364 if (bestPlace.bestTile != NULL) {
1365 QRectF r;
1366 tileToQRect(bestPlace.bestTile, r);
1367 ItemBase * chief = newItem->layerKinChief();
1368 chief->setPos(r.topLeft());
1369 DebugDialog::debug(QString("placing part with rotation:%1").arg(bestPlace.rotate90), r);
1370 if (bestPlace.rotate90) {
1371 chief->rotateItem(90, false);
1372 }
1373 alignOneToGrid(newItem);
1374 }
1375 router.drcClean();
1376 if (bestPlace.bestTile != NULL) {
1377 break;
1378 }
1379 }
1380
1381 return newItem;
1382 }
1383
autorouterSettings()1384 void PCBSketchWidget::autorouterSettings() {
1385 // initialize settings values if they haven't already been initialized
1386 getKeepout();
1387 QString ringThickness, holeSize;
1388 getDefaultViaSize(ringThickness, holeSize);
1389 getAutorouterTraceWidth();
1390
1391 AutorouterSettingsDialog dialog(m_autorouterSettings);
1392 if (QDialog::Accepted == dialog.exec()) {
1393 m_autorouterSettings = dialog.getSettings();
1394 QSettings settings;
1395 foreach (QString key, m_autorouterSettings.keys()) {
1396 settings.setValue(key, m_autorouterSettings.value(key));
1397 }
1398 }
1399 }
1400
getViaSize(double & ringThickness,double & holeSize)1401 void PCBSketchWidget::getViaSize(double & ringThickness, double & holeSize) {
1402 QString ringThicknessStr, holeSizeStr;
1403 getDefaultViaSize(ringThicknessStr, holeSizeStr);
1404 double rt = TextUtils::convertToInches(ringThicknessStr);
1405 double hs = TextUtils::convertToInches(holeSizeStr);
1406 ringThickness = rt * GraphicsUtils::SVGDPI;
1407 holeSize = hs * GraphicsUtils::SVGDPI;
1408 }
1409
getDefaultViaSize(QString & ringThickness,QString & holeSize)1410 void PCBSketchWidget::getDefaultViaSize(QString & ringThickness, QString & holeSize) {
1411 // these settings are initialized in via.cpp
1412 ringThickness = m_autorouterSettings.value(Via::AutorouteViaRingThickness, "");
1413 holeSize = m_autorouterSettings.value(Via::AutorouteViaHoleSize, "");
1414
1415 QSettings settings;
1416 if (ringThickness.isEmpty()) {
1417 ringThickness = settings.value(Via::AutorouteViaRingThickness, Via::DefaultAutorouteViaRingThickness).toString();
1418 }
1419 if (holeSize.isEmpty()) {
1420 holeSize = settings.value(Via::AutorouteViaHoleSize, Via::DefaultAutorouteViaHoleSize).toString();
1421 }
1422
1423 m_autorouterSettings.insert(Via::AutorouteViaRingThickness, ringThickness);
1424 m_autorouterSettings.insert(Via::AutorouteViaHoleSize, holeSize);
1425 }
1426
deleteItem(ItemBase * itemBase,bool deleteModelPart,bool doEmit,bool later)1427 void PCBSketchWidget::deleteItem(ItemBase * itemBase, bool deleteModelPart, bool doEmit, bool later)
1428 {
1429 bool boardDeleted = Board::isBoard(itemBase);
1430 SketchWidget::deleteItem(itemBase, deleteModelPart, doEmit, later);
1431 if (boardDeleted) {
1432 if (findBoard().count() == 0) {
1433 emit boardDeletedSignal();
1434 }
1435 requestQuoteSoon();
1436 }
1437 }
1438
getTraceWidth()1439 double PCBSketchWidget::getTraceWidth() {
1440 return Wire::STANDARD_TRACE_WIDTH;
1441 }
1442
getAutorouterTraceWidth()1443 double PCBSketchWidget::getAutorouterTraceWidth() {
1444 QString traceWidthString = m_autorouterSettings.value(AutorouterSettingsDialog::AutorouteTraceWidth, "");
1445 if (traceWidthString.isEmpty()) {
1446 QSettings settings;
1447 QString def = QString::number(GraphicsUtils::pixels2mils(getTraceWidth(), GraphicsUtils::SVGDPI));
1448 traceWidthString = settings.value(AutorouterSettingsDialog::AutorouteTraceWidth, def).toString();
1449 }
1450
1451 m_autorouterSettings.insert(AutorouterSettingsDialog::AutorouteTraceWidth, traceWidthString);
1452
1453 return GraphicsUtils::SVGDPI * traceWidthString.toInt() / 1000.0; // traceWidthString is in mils
1454 }
1455
getBendpointWidths(Wire * wire,double width,double & bendpointWidth,double & bendpoint2Width,bool & negativeOffsetRect)1456 void PCBSketchWidget::getBendpointWidths(Wire * wire, double width, double & bendpointWidth, double & bendpoint2Width, bool & negativeOffsetRect)
1457 {
1458 Q_UNUSED(wire);
1459 bendpointWidth = bendpoint2Width = (width / -2);
1460 negativeOffsetRect = false;
1461 }
1462
getSmallerTraceWidth(double minDim)1463 double PCBSketchWidget::getSmallerTraceWidth(double minDim) {
1464 int mils = qMax((int) GraphicsUtils::pixels2mils(minDim, GraphicsUtils::SVGDPI) - 1, TraceWire::MinTraceWidthMils);
1465 return GraphicsUtils::mils2pixels(mils, GraphicsUtils::SVGDPI);
1466 }
1467
groundFill(bool fillGroundTraces,ViewLayer::ViewLayerID viewLayerID,QUndoCommand * parentCommand)1468 bool PCBSketchWidget::groundFill(bool fillGroundTraces, ViewLayer::ViewLayerID viewLayerID, QUndoCommand * parentCommand)
1469 {
1470 m_groundFillSeeds = NULL;
1471 int boardCount;
1472 ItemBase * board = findSelectedBoard(boardCount);
1473 // barf an error if there's no board
1474 if (boardCount == 0) {
1475 QMessageBox::critical(this, tr("Fritzing"),
1476 tr("Your sketch does not have a board yet! Please add a PCB in order to use copper fill."));
1477 return false;
1478 }
1479 if (board == NULL) {
1480 QMessageBox::critical(this, tr("Fritzing"),
1481 tr("%1 Fill: please select the board you want to apply fill to.").arg(fillGroundTraces ? tr("Ground") : tr("Copper")));
1482 return false;
1483 }
1484
1485
1486 QList<ConnectorItem *> seeds;
1487 if (fillGroundTraces) {
1488 bool gotTrueSeeds = collectGroundFillSeeds(seeds, false);
1489
1490 if (!gotTrueSeeds && (seeds.count() != 1)) {
1491 QString message = tr("Please designate one or more ground fill seeds before doing a ground fill.\n\n");
1492 setGroundFillSeeds(message);
1493 return false;
1494 }
1495
1496 ConnectorItem::collectEqualPotential(seeds, true, ViewGeometry::NoFlag);
1497 //foreach (ConnectorItem * seed, seeds) {
1498 // seed->debugInfo("seed");
1499 //}
1500 m_groundFillSeeds = &seeds;
1501 }
1502
1503 LayerList viewLayerIDs;
1504 viewLayerIDs << ViewLayer::Board;
1505
1506 QRectF boardImageRect, copperImageRect;
1507 RenderThing renderThing;
1508 renderThing.printerScale = GraphicsUtils::SVGDPI;
1509 renderThing.blackOnly = true;
1510 renderThing.dpi = GraphicsUtils::StandardFritzingDPI;
1511 renderThing.hideTerminalPoints = true;
1512 renderThing.selectedItems = renderThing.renderBlocker = false;
1513 QString boardSvg = renderToSVG(renderThing, board, viewLayerIDs);
1514 if (boardSvg.isEmpty()) {
1515 QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to render board svg (1)."));
1516 return false;
1517 }
1518
1519 boardImageRect = renderThing.imageRect;
1520 renderThing.renderBlocker = true;
1521
1522 QString svg0;
1523 if (viewLayerID == ViewLayer::UnknownLayer || viewLayerID == ViewLayer::GroundPlane0) {
1524 viewLayerIDs.clear();
1525 viewLayerIDs << ViewLayer::Copper0 << ViewLayer::Copper0Trace << ViewLayer::GroundPlane0;
1526
1527 // hide ground traces so the ground plane will intersect them
1528 if (fillGroundTraces) showGroundTraces(seeds, false);
1529 svg0 = renderToSVG(renderThing, board, viewLayerIDs);
1530 if (fillGroundTraces) showGroundTraces(seeds, true);
1531 if (svg0.isEmpty()) {
1532 QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to render copper svg (1)."));
1533 return false;
1534 }
1535 copperImageRect = renderThing.imageRect;
1536 }
1537
1538 QString svg1;
1539 if (boardLayers() > 1 && (viewLayerID == ViewLayer::UnknownLayer || viewLayerID == ViewLayer::GroundPlane1)) {
1540 viewLayerIDs.clear();
1541 viewLayerIDs << ViewLayer::Copper1 << ViewLayer::Copper1Trace << ViewLayer::GroundPlane1;
1542
1543 if (fillGroundTraces) showGroundTraces(seeds, false);
1544 svg1 = renderToSVG(renderThing, board, viewLayerIDs);
1545 if (fillGroundTraces) showGroundTraces(seeds, true);
1546 if (svg1.isEmpty()) {
1547 QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to render copper svg (1)."));
1548 return false;
1549 }
1550 copperImageRect = renderThing.imageRect;
1551 }
1552
1553 QStringList exceptions;
1554 exceptions << "none" << "" << background().name(); // the color of holes in the board
1555
1556 GroundPlaneGenerator gpg0;
1557 if (!svg0.isEmpty()) {
1558 gpg0.setLayerName("groundplane");
1559 gpg0.setStrokeWidthIncrement(StrokeWidthIncrement);
1560 gpg0.setMinRunSize(10, 10);
1561 if (fillGroundTraces) {
1562 connect(&gpg0, SIGNAL(postImageSignal(GroundPlaneGenerator *, QImage *, QImage *, QGraphicsItem *, QList<QRectF> *)),
1563 this, SLOT(postImageSlot(GroundPlaneGenerator *, QImage *, QImage *, QGraphicsItem *, QList<QRectF> *)),
1564 Qt::DirectConnection);
1565 }
1566
1567 bool result = gpg0.generateGroundPlane(boardSvg, boardImageRect.size(), svg0, copperImageRect.size(), exceptions, board, GraphicsUtils::StandardFritzingDPI / 2.0 /* 2 MIL */,
1568 ViewLayer::Copper0Color, getKeepoutMils());
1569 if (result == false) {
1570 QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to write copper fill (1)."));
1571 return false;
1572 }
1573 }
1574
1575 GroundPlaneGenerator gpg1;
1576 if (boardLayers() > 1 && !svg1.isEmpty()) {
1577 gpg1.setLayerName("groundplane1");
1578 gpg1.setStrokeWidthIncrement(StrokeWidthIncrement);
1579 gpg1.setMinRunSize(10, 10);
1580 if (fillGroundTraces) {
1581 connect(&gpg1, SIGNAL(postImageSignal(GroundPlaneGenerator *, QImage *, QImage *, QGraphicsItem *, QList<QRectF> *)),
1582 this, SLOT(postImageSlot(GroundPlaneGenerator *, QImage *, QImage *, QGraphicsItem *, QList<QRectF> *)),
1583 Qt::DirectConnection);
1584 }
1585 bool result = gpg1.generateGroundPlane(boardSvg, boardImageRect.size(), svg1, copperImageRect.size(), exceptions, board, GraphicsUtils::StandardFritzingDPI / 2.0 /* 2 MIL */,
1586 ViewLayer::Copper1Color, getKeepoutMils());
1587 if (result == false) {
1588 QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to write copper fill (2)."));
1589 return false;
1590 }
1591 }
1592
1593
1594 QString fillType = (fillGroundTraces) ? GroundPlane::fillTypeGround : GroundPlane::fillTypePlain;
1595 QRectF bsbr = board->sceneBoundingRect();
1596
1597 int ix = 0;
1598 foreach (QString svg, gpg0.newSVGs()) {
1599 ViewGeometry vg;
1600 vg.setLoc(bsbr.topLeft() + gpg0.newOffsets()[ix++]);
1601 long newID = ItemBase::getNextID();
1602 new AddItemCommand(this, BaseCommand::CrossView, ModuleIDNames::GroundPlaneModuleIDName, ViewLayer::NewBottom, vg, newID, false, -1, parentCommand);
1603 new SetPropCommand(this, newID, "svg", svg, svg, true, parentCommand);
1604 new SetPropCommand(this, newID, "fillType", fillType, fillType, false, parentCommand);
1605 }
1606
1607 ix = 0;
1608 foreach (QString svg, gpg1.newSVGs()) {
1609 ViewGeometry vg;
1610 vg.setLoc(bsbr.topLeft() + gpg1.newOffsets()[ix++]);
1611 long newID = ItemBase::getNextID();
1612 new AddItemCommand(this, BaseCommand::CrossView, ModuleIDNames::GroundPlaneModuleIDName, ViewLayer::NewTop, vg, newID, false, -1, parentCommand);
1613 new SetPropCommand(this, newID, "svg", svg, svg, true, parentCommand);
1614 new SetPropCommand(this, newID, "fillType", fillType, fillType, false, parentCommand);
1615 }
1616
1617 return true;
1618
1619 }
1620
generateCopperFillUnit(ItemBase * itemBase,QPointF whereToStart)1621 QString PCBSketchWidget::generateCopperFillUnit(ItemBase * itemBase, QPointF whereToStart)
1622 {
1623 int boardCount;
1624 ItemBase * board = findSelectedBoard(boardCount);
1625 // barf an error if there's no board
1626 if (boardCount == 0) {
1627 QMessageBox::critical(this, tr("Fritzing"),
1628 tr("Your sketch does not have a board yet! Please add a PCB in order to use copper fill."));
1629 return "";
1630 }
1631 if (board == NULL) {
1632 QMessageBox::critical(this, tr("Fritzing"),
1633 tr("Copper fill: please select only the board you want to fill."));
1634 return "";
1635 }
1636
1637 QRectF bsbr = board->sceneBoundingRect();
1638 if (!bsbr.contains(whereToStart)) {
1639 QMessageBox::critical(this, tr("Fritzing"), tr("Unable to create copper fill--probably the part wasn't dropped onto the PCB."));
1640 return "";
1641 }
1642
1643 LayerList viewLayerIDs;
1644 viewLayerIDs << ViewLayer::Board;
1645
1646 QRectF boardImageRect, copperImageRect;
1647
1648 RenderThing renderThing;
1649 renderThing.printerScale = GraphicsUtils::SVGDPI;
1650 renderThing.blackOnly = true;
1651 renderThing.dpi = GraphicsUtils::StandardFritzingDPI;
1652 renderThing.hideTerminalPoints = true;
1653 renderThing.selectedItems = renderThing.renderBlocker = false;
1654 QString boardSvg = renderToSVG(renderThing, board, viewLayerIDs);
1655 if (boardSvg.isEmpty()) {
1656 QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to render board svg (1)."));
1657 return "";
1658 }
1659
1660 boardImageRect = renderThing.imageRect;
1661 ViewLayer::ViewLayerPlacement viewLayerPlacement = ViewLayer::NewBottom;
1662 QString color = ViewLayer::Copper0Color;
1663 QString gpLayerName = "groundplane";
1664
1665 if (m_boardLayers == 2 && !dropOnBottom()) {
1666 gpLayerName += "1";
1667 color = ViewLayer::Copper1Color;
1668 viewLayerPlacement = ViewLayer::NewTop;
1669 }
1670
1671 viewLayerIDs = ViewLayer::copperLayers(viewLayerPlacement);
1672
1673 bool vis = itemBase->isVisible();
1674 itemBase->setVisible(false);
1675 renderThing.renderBlocker = true;
1676 QString svg = renderToSVG(renderThing, board, viewLayerIDs);
1677 itemBase->setVisible(vis);
1678 if (svg.isEmpty()) {
1679 QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to render copper svg (1)."));
1680 return "";
1681 }
1682
1683 copperImageRect = renderThing.imageRect;
1684
1685 QStringList exceptions;
1686 exceptions << "none" << "" << background().name(); // the color of holes in the board
1687
1688 GroundPlaneGenerator gpg;
1689 gpg.setStrokeWidthIncrement(StrokeWidthIncrement);
1690 gpg.setLayerName(gpLayerName);
1691 gpg.setMinRunSize(10, 10);
1692 bool result = gpg.generateGroundPlaneUnit(boardSvg, boardImageRect.size(), svg, copperImageRect.size(), exceptions, board, GraphicsUtils::StandardFritzingDPI / 2.0 /* 2 MIL */,
1693 color, whereToStart, getKeepoutMils());
1694
1695 if (result == false || gpg.newSVGs().count() < 1) {
1696 QMessageBox::critical(this, tr("Fritzing"), tr("Unable to create copper fill--possibly the part was dropped onto another part or wire rather than the actual PCB."));
1697 return "";
1698 }
1699
1700 itemBase->setPos(bsbr.topLeft() + gpg.newOffsets()[0]);
1701 itemBase->setViewLayerID(gpLayerName, m_viewLayers);
1702
1703 return gpg.newSVGs()[0];
1704 }
1705
1706
connectorItemHasSpec(ConnectorItem * connectorItem,ViewLayer::ViewLayerPlacement spec)1707 bool PCBSketchWidget::connectorItemHasSpec(ConnectorItem * connectorItem, ViewLayer::ViewLayerPlacement spec) {
1708 if (ViewLayer::specFromID(connectorItem->attachedToViewLayerID()) == spec) return true;
1709
1710 connectorItem = connectorItem->getCrossLayerConnectorItem();
1711 if (connectorItem == NULL) return false;
1712
1713 return (ViewLayer::specFromID(connectorItem->attachedToViewLayerID()) == spec);
1714 }
1715
createWireViewLayerPlacement(ConnectorItem * from,ConnectorItem * to)1716 ViewLayer::ViewLayerPlacement PCBSketchWidget::createWireViewLayerPlacement(ConnectorItem * from, ConnectorItem * to) {
1717 QList<ViewLayer::ViewLayerPlacement> guesses;
1718 guesses.append(layerIsActive(ViewLayer::Copper0) ? ViewLayer::NewBottom : ViewLayer::NewTop);
1719 guesses.append(layerIsActive(ViewLayer::Copper0) ? ViewLayer::NewTop : ViewLayer::NewBottom);
1720 foreach (ViewLayer::ViewLayerPlacement guess, guesses) {
1721 if (connectorItemHasSpec(from, guess) && connectorItemHasSpec(to, guess)) {
1722 return guess;
1723 }
1724 }
1725
1726 return ViewLayer::UnknownPlacement;
1727 }
1728
getWireStrokeWidth(Wire * wire,double wireWidth)1729 double PCBSketchWidget::getWireStrokeWidth(Wire * wire, double wireWidth)
1730 {
1731 double w, h;
1732 wire->originalConnectorDimensions(w, h);
1733 if (wireWidth < Wire::THIN_TRACE_WIDTH) {
1734 wire->setConnectorDimensions(qMin(w, wireWidth + 1), qMin(w, wireWidth + 1));
1735 }
1736 if (wireWidth < Wire::STANDARD_TRACE_WIDTH) {
1737 wire->setConnectorDimensions(qMin(w, wireWidth + 1.5), qMin(w, wireWidth + 1.5));
1738 }
1739 else {
1740 wire->setConnectorDimensions(w, h);
1741 }
1742
1743 return wireWidth + 6;
1744 }
1745
createTempWireForDragging(Wire * fromWire,ModelPart * wireModel,ConnectorItem * connectorItem,ViewGeometry & viewGeometry,ViewLayer::ViewLayerPlacement spec)1746 Wire * PCBSketchWidget::createTempWireForDragging(Wire * fromWire, ModelPart * wireModel, ConnectorItem * connectorItem, ViewGeometry & viewGeometry, ViewLayer::ViewLayerPlacement spec)
1747 {
1748 if (spec == ViewLayer::UnknownPlacement) {
1749 spec = wireViewLayerPlacement(connectorItem);
1750 }
1751 viewGeometry.setPCBTrace(true);
1752 Wire * wire = SketchWidget::createTempWireForDragging(fromWire, wireModel, connectorItem, viewGeometry, spec);
1753 if (fromWire == NULL) {
1754 wire->setColorString(traceColor(connectorItem), 1.0, false);
1755 double traceWidth = getTraceWidth();
1756 double minDim = connectorItem->minDimension();
1757 if (minDim < traceWidth) {
1758 traceWidth = getSmallerTraceWidth(minDim);
1759 }
1760 wire->setWireWidth(traceWidth, this, getWireStrokeWidth(wire, traceWidth));
1761 wire->setProperty(FakeTraceProperty, true);
1762 }
1763 else {
1764 wire->setColorString(fromWire->colorString(), fromWire->opacity(), false);
1765 wire->setWireWidth(fromWire->width(), this, fromWire->hoverStrokeWidth());
1766 }
1767
1768 return wire;
1769 }
1770
prereleaseTempWireForDragging(Wire * wire)1771 void PCBSketchWidget::prereleaseTempWireForDragging(Wire* wire)
1772 {
1773 if (wire->property(PCBSketchWidget::FakeTraceProperty).toBool()) {
1774 // make it not look like a trace, or modifyNewWireConnections will create the wrong kind of wire
1775 wire->setWireFlags(0);
1776 }
1777 }
1778
rotatePartLabels(double degrees,QTransform & transform,QPointF center,QUndoCommand * parentCommand)1779 void PCBSketchWidget::rotatePartLabels(double degrees, QTransform & transform, QPointF center, QUndoCommand * parentCommand)
1780 {
1781 QList<ItemBase *> savedValues = m_savedItems.values();
1782 /*
1783 QSet<ItemBase *> boards;
1784 foreach (ItemBase * itemBase, savedValues) {
1785 if (Board::isBoard(itemBase)) {
1786 boards.insert(itemBase);
1787 }
1788 }
1789
1790
1791 if (boards.count() == 0) return;
1792
1793 QRectF bbr;
1794 foreach (ItemBase * board, boards.values()) {
1795 bbr |= board->sceneBoundingRect();
1796 }
1797 */
1798
1799 foreach (QGraphicsItem * item, scene()->items()) {
1800 PartLabel * partLabel = dynamic_cast<PartLabel *>(item);
1801 if (partLabel == NULL) continue;
1802 if (!partLabel->isVisible()) continue;
1803 //if (!bbr.intersects(partLabel->sceneBoundingRect())) continue; // if the part is on the board and the label is off the board, this does not rotate
1804 if (!savedValues.contains(partLabel->owner()->layerKinChief())) continue;
1805
1806 QPointF offset = partLabel->pos() - partLabel->owner()->pos();
1807 new MoveLabelCommand(this, partLabel->owner()->id(), partLabel->pos(), offset, partLabel->pos(), offset, parentCommand);
1808 new RotateFlipLabelCommand(this, partLabel->owner()->id(), degrees, 0, parentCommand);
1809 QPointF p = GraphicsUtils::calcRotation(transform, center, partLabel->pos(), partLabel->boundingRect().center());
1810 ViewGeometry vg;
1811 partLabel->owner()->calcRotation(transform, center, vg);
1812 new MoveLabelCommand(this, partLabel->owner()->id(), p, p - vg.loc(), p, p - vg.loc(), parentCommand);
1813 }
1814 }
1815
characterizeGroundFill(ViewLayer::ViewLayerID whichGroundPlane)1816 QString PCBSketchWidget::characterizeGroundFill(ViewLayer::ViewLayerID whichGroundPlane) {
1817 QString result = GroundPlane::fillTypeNone;
1818 bool gotOne = false;
1819
1820 int boardCount;
1821 ItemBase * board = findSelectedBoard(boardCount);
1822 foreach (QGraphicsItem * item, scene()->collidingItems(board)) {
1823 GroundPlane * gp = dynamic_cast<GroundPlane *>(item);
1824 if (gp == NULL) continue;
1825
1826 if (gp->viewLayerID() == whichGroundPlane) {
1827 gotOne = true;
1828 break;
1829 }
1830
1831 }
1832
1833 if (!gotOne) return result;
1834
1835 foreach (QGraphicsItem * item, scene()->items()) {
1836 GroundPlane * gp = dynamic_cast<GroundPlane *>(item);
1837 if (gp == NULL) continue;
1838 if (gp->viewLayerID() != whichGroundPlane) continue;
1839
1840 QString fillType = gp->prop("fillType");
1841 if (fillType.isEmpty()) {
1842 // old style fill with no property
1843 return GroundPlane::fillTypeGround;
1844 }
1845
1846 if (fillType == GroundPlane::fillTypeGround) {
1847 // assumes multiple fill types are not possible
1848 return fillType;
1849 }
1850
1851 if (fillType == GroundPlane::fillTypePlain) {
1852 // assumes multiple fill types are not possible
1853 return fillType;
1854 }
1855
1856 result = fillType;
1857 }
1858
1859 return result;
1860 }
1861
setUpColor(ConnectorItem * fromConnectorItem,ConnectorItem * toConnectorItem,Wire * wire,QUndoCommand * parentCommand)1862 void PCBSketchWidget::setUpColor(ConnectorItem * fromConnectorItem, ConnectorItem * toConnectorItem, Wire * wire, QUndoCommand * parentCommand) {
1863
1864 QString tc = traceColor(fromConnectorItem);
1865 new WireColorChangeCommand(this, wire->id(), tc, tc, 1.0, 1.0, parentCommand);
1866 double traceWidth = getTraceWidth();
1867 if (autorouteTypePCB()) {
1868 double minDim = qMin(fromConnectorItem->minDimension(), toConnectorItem->minDimension());
1869 if (minDim < traceWidth) {
1870 traceWidth = getSmallerTraceWidth(minDim);
1871 }
1872 }
1873 new WireWidthChangeCommand(this, wire->id(), traceWidth, traceWidth, parentCommand);
1874
1875 }
1876
getTraceFlag()1877 ViewGeometry::WireFlag PCBSketchWidget::getTraceFlag() {
1878 return ViewGeometry::PCBTraceFlag;
1879 }
1880
postImageSlot(GroundPlaneGenerator * gpg,QImage * copperImage,QImage * boardImage,QGraphicsItem * board,QList<QRectF> * rects)1881 void PCBSketchWidget::postImageSlot(GroundPlaneGenerator * gpg, QImage * copperImage, QImage * boardImage, QGraphicsItem * board, QList<QRectF> * rects) {
1882
1883 if (m_groundFillSeeds == NULL) return;
1884
1885 ViewLayer::ViewLayerID viewLayerID = (gpg->layerName() == "groundplane") ? ViewLayer::Copper0 : ViewLayer::Copper1;
1886
1887 QRectF boardRect = board->sceneBoundingRect();
1888
1889 foreach (ConnectorItem * connectorItem, *m_groundFillSeeds) {
1890 if (connectorItem->attachedToViewLayerID() != viewLayerID) continue;
1891 if (connectorItem->attachedToItemType() == ModelPart::Wire) continue;
1892 if (!connectorItem->attachedTo()->isEverVisible()) continue;
1893
1894 //connectorItem->debugInfo("post image b");
1895 QRectF r = connectorItem->sceneBoundingRect();
1896 //DebugDialog::debug("pb", r);
1897 QRectF check = r;
1898 check.setLeft(r.right());
1899 check.setRight(r.right() + r.width());
1900 bool checkRight = !hasNeighbor(connectorItem, viewLayerID, check);
1901
1902 check = r;
1903 check.setLeft(r.left() - r.width());
1904 check.setRight(r.left());
1905 bool checkLeft = !hasNeighbor(connectorItem, viewLayerID, check);
1906
1907 check = r;
1908 check.setTop(r.bottom());
1909 check.setBottom(r.bottom() + r.height());
1910 bool checkDown = !hasNeighbor(connectorItem, viewLayerID, check);
1911
1912 check = r;
1913 check.setTop(r.top() - r.width());
1914 check.setBottom(r.top());
1915 bool checkUp = !hasNeighbor(connectorItem, viewLayerID, check);
1916
1917 double x1 = (r.left() - boardRect.left()) * copperImage->width() / boardRect.width();
1918 double x2 = (r.right() - boardRect.left()) * copperImage->width() / boardRect.width();
1919 double y1 = (r.top() - boardRect.top()) * copperImage->height() / boardRect.height();
1920 double y2 = (r.bottom() - boardRect.top()) * copperImage->height() / boardRect.height();
1921 double w = x2 - x1;
1922 double h = y2 - y1;
1923
1924 double cw = w / 4;
1925 double ch = h / 4;
1926 int cx = (x1 + x2) /2;
1927 int cy = (y1 + y2) /2;
1928
1929 int rad = qFloor(connectorItem->calcClipRadius() * copperImage->width() / boardRect.width());
1930
1931 double borderl = qMax(0.0, x1 - w);
1932 double borderr = qMin(x2 + w, copperImage->width());
1933 double bordert = qMax(0.0, y1 - h);
1934 double borderb = qMin(y2 + h, copperImage->height());
1935
1936 // check left, up, right, down for groundplane, and if it's there draw to it from the connector
1937
1938 if (checkUp){
1939 for (int y = y1; y > bordert; y--) {
1940 if ((copperImage->pixel(cx, y) & 0xffffff) || (boardImage->pixel(cx, y) == 0xff000000)) {
1941 QRectF s(cx - cw, y - 1, cw + cw, cy - y - rad);
1942 rects->append(s);
1943 break;
1944 }
1945 }
1946 }
1947 if (checkDown) {
1948 for (int y = y2; y < borderb; y++) {
1949 if ((copperImage->pixel(cx, y) & 0xffffff) || (boardImage->pixel(cx, y) == 0xff000000)) {
1950 QRectF s(cx - cw, cy + rad, cw + cw, y - cy - rad);
1951 rects->append(s);
1952 break;
1953 }
1954 }
1955 }
1956 if (checkLeft) {
1957 for (int x = x1; x > borderl; x--) {
1958 if ((copperImage->pixel(x, cy) & 0xffffff) || (boardImage->pixel(x, cy) == 0xff000000)) {
1959 QRectF s(x - 1, cy - ch, cx - x - rad, ch + ch);
1960 rects->append(s);
1961 break;
1962 }
1963 }
1964 }
1965 if (checkRight) {
1966 for (int x = x2; x < borderr; x++) {
1967 if ((copperImage->pixel(x, cy) & 0xffffff) || (boardImage->pixel(x, cy) == 0xff000000)) {
1968 QRectF s(cx + rad, cy - ch, x - cx - rad, ch + ch);
1969 rects->append(s);
1970 break;
1971 }
1972 }
1973 }
1974
1975 DebugDialog::debug(QString("x1:%1 y1:%2 x2:%3 y2:%4").arg(x1).arg(y1).arg(x2).arg(y2));
1976 }
1977 }
1978
hasNeighbor(ConnectorItem * connectorItem,ViewLayer::ViewLayerID viewLayerID,const QRectF & r)1979 bool PCBSketchWidget::hasNeighbor(ConnectorItem * connectorItem, ViewLayer::ViewLayerID viewLayerID, const QRectF & r)
1980 {
1981 foreach (QGraphicsItem * item, scene()->items(r)) {
1982 ConnectorItem * ci = dynamic_cast<ConnectorItem *>(item);
1983 if (ci != NULL) {
1984 if (ci->attachedToViewLayerID() != viewLayerID) continue;
1985 if (!ci->attachedTo()->isEverVisible()) continue;
1986 if (ci == connectorItem) continue;
1987
1988 return true;
1989 }
1990
1991 TraceWire * traceWire = dynamic_cast<TraceWire *>(item);
1992 if (traceWire != NULL) {
1993 if (!sameElectricalLayer2(traceWire->viewLayerID(), viewLayerID)) continue;
1994 if (!traceWire->isTraceType(getTraceFlag())) continue;
1995
1996 return true;
1997 }
1998 }
1999
2000 return false;
2001 }
2002
collectThroughHole(QList<ConnectorItem * > & th,QList<ConnectorItem * > & pads,const LayerList & layerList)2003 void PCBSketchWidget::collectThroughHole(QList<ConnectorItem *> & th, QList<ConnectorItem *> & pads, const LayerList & layerList)
2004 {
2005 foreach (QGraphicsItem * item, scene()->items()) {
2006 ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
2007 if (connectorItem == NULL) continue;
2008 if (!connectorItem->attachedTo()->isVisible()) continue;
2009 if (!layerList.contains(connectorItem->attachedToViewLayerID())) continue;
2010 if (connectorItem->attachedTo()->moduleID().endsWith(ModuleIDNames::PadModuleIDName)) {
2011 pads.append(connectorItem);
2012 continue;
2013 }
2014
2015 if (connectorItem->attachedTo()->modelPart()->flippedSMD()) {
2016 pads.append(connectorItem);
2017 continue;
2018 }
2019
2020 th << connectorItem;
2021 }
2022 }
2023
hideCopperLogoItems(QList<ItemBase * > & copperLogoItems)2024 void PCBSketchWidget::hideCopperLogoItems(QList<ItemBase *> & copperLogoItems)
2025 {
2026 foreach (QGraphicsItem * item, this->items()) {
2027 CopperLogoItem * logoItem = dynamic_cast<CopperLogoItem *>(item);
2028 if (logoItem && logoItem->isVisible()) {
2029 copperLogoItems.append(logoItem);
2030 logoItem->setVisible(false);
2031 }
2032 }
2033 }
2034
hideHoles(QList<ItemBase * > & holes)2035 void PCBSketchWidget::hideHoles(QList<ItemBase *> & holes)
2036 {
2037 foreach (QGraphicsItem * item, this->items()) {
2038 ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
2039 // for some reason the layerkin of the hole doesn't have a modelPart->itemType() == ModelPart::Hole
2040 if (itemBase && itemBase->isVisible() && itemBase->layerKinChief()->modelPart()->itemType() == ModelPart::Hole) {
2041 holes.append(itemBase);
2042 itemBase->setVisible(false);
2043 }
2044 }
2045 }
2046
restoreCopperLogoItems(QList<ItemBase * > & copperLogoItems)2047 void PCBSketchWidget::restoreCopperLogoItems(QList<ItemBase *> & copperLogoItems)
2048 {
2049 foreach (ItemBase * logoItem, copperLogoItems) {
2050 logoItem->setVisible(true);
2051 }
2052 }
2053
clearGroundFillSeeds()2054 void PCBSketchWidget::clearGroundFillSeeds()
2055 {
2056 QList<ConnectorItem *> trueSeeds;
2057
2058 int boardCount;
2059 ItemBase * board = findSelectedBoard(boardCount);
2060 if (board == NULL) return;
2061
2062 foreach (QGraphicsItem * item, scene()->collidingItems(board)) {
2063 ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
2064 if (connectorItem == NULL) continue;
2065 if (connectorItem->attachedToItemType() == ModelPart::CopperFill) continue;
2066
2067 if (connectorItem->isGroundFillSeed()) {
2068 trueSeeds.append(connectorItem);
2069 continue;
2070 }
2071 }
2072
2073 if (trueSeeds.count() == 0) return;
2074
2075 GroundFillSeedCommand * command = new GroundFillSeedCommand(this, NULL);
2076 command->setText(tr("Clear ground fill seeds"));
2077 foreach (ConnectorItem * connectorItem, trueSeeds) {
2078 command->addItem(connectorItem->attachedToID(), connectorItem->connectorSharedID(), false);
2079 }
2080
2081 m_undoStack->waitPush(command, PropChangeDelay);
2082 }
2083
2084
setGroundFillSeeds()2085 void PCBSketchWidget::setGroundFillSeeds()
2086 {
2087 setGroundFillSeeds("");
2088 }
2089
setGroundFillSeeds(const QString & intro)2090 void PCBSketchWidget::setGroundFillSeeds(const QString & intro)
2091 {
2092 QList<ConnectorItem *> seeds;
2093 collectGroundFillSeeds(seeds, true);
2094 GroundFillSeedDialog gfsd(this, seeds, intro, NULL);
2095 int result = gfsd.exec();
2096 if (result == QDialog::Accepted) {
2097 GroundFillSeedCommand * command = NULL;
2098 QList<bool> results;
2099 gfsd.getResults(results);
2100 bool checked = false;
2101 for (int i = 0; i < seeds.count(); i++) {
2102 ConnectorItem * ci = seeds.at(i);
2103 bool isSeed = results.at(i);
2104 checked |= isSeed;
2105 if (isSeed != ci->isGroundFillSeed()) {
2106 if (command == NULL) {
2107 command = new GroundFillSeedCommand(this, NULL);
2108 }
2109 command->addItem(ci->attachedToID(), ci->connectorSharedID(), isSeed);
2110 }
2111 }
2112 if (command) {
2113 m_undoStack->push(command);
2114 }
2115
2116 if (gfsd.getFill()) {
2117 if (checked) emit groundFillSignal();
2118 else emit copperFillSignal();
2119 }
2120 }
2121 }
2122
collectGroundFillSeeds(QList<ConnectorItem * > & seeds,bool includePotential)2123 bool PCBSketchWidget::collectGroundFillSeeds(QList<ConnectorItem *> & seeds, bool includePotential) {
2124 QList<ConnectorItem *> trueSeeds;
2125 QList<ConnectorItem *> potentialSeeds;
2126
2127 int boardCount;
2128 ItemBase * board = findSelectedBoard(boardCount);
2129 if (board == NULL) return false;
2130
2131 foreach (QGraphicsItem * item, scene()->collidingItems(board)) {
2132 ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
2133 if (connectorItem == NULL) continue;
2134 if (connectorItem->attachedToItemType() == ModelPart::CopperFill) continue;
2135
2136 if (connectorItem->isGroundFillSeed()) {
2137 trueSeeds.append(connectorItem);
2138 continue;
2139 }
2140
2141 if (connectorItem->isGrounded()) {
2142 potentialSeeds.append(connectorItem);
2143 }
2144 }
2145
2146 for (int ix = 0; ix < trueSeeds.count(); ix++) {
2147 ConnectorItem * ci = trueSeeds.at(ix);
2148 QList<ConnectorItem *> cis;
2149 cis.append(ci);
2150 ConnectorItem::collectEqualPotential(cis, true, ViewGeometry::NoFlag);
2151 foreach (ConnectorItem * eq, cis) {
2152 if (eq != ci) trueSeeds.removeAll(eq);
2153 potentialSeeds.removeAll(eq);
2154 }
2155 }
2156
2157 for (int ix = 0; ix < potentialSeeds.count(); ix++) {
2158 ConnectorItem * ci = potentialSeeds.at(ix);
2159 QList<ConnectorItem *> cis;
2160 cis.append(ci);
2161 ConnectorItem::collectEqualPotential(cis, true, ViewGeometry::NoFlag);
2162 foreach (ConnectorItem * eq, cis) {
2163 if (eq != ci) potentialSeeds.removeAll(eq);
2164 }
2165 }
2166
2167 seeds.append(trueSeeds);
2168 if (trueSeeds.count() == 0 || includePotential) {
2169 seeds.append(potentialSeeds);
2170 }
2171
2172 return trueSeeds.count() > 0;
2173 }
2174
shiftHoles()2175 void PCBSketchWidget::shiftHoles() {
2176 // vias and holes before version 0.7.3 did not have offset
2177
2178 VersionThing versionThingOffset;
2179 versionThingOffset.majorVersion = 0;
2180 versionThingOffset.minorVersion = 7;
2181 versionThingOffset.minorSubVersion = 2;
2182 versionThingOffset.releaseModifier = "b";
2183 VersionThing versionThingFz;
2184 Version::toVersionThing(m_sketchModel->fritzingVersion(), versionThingFz);
2185 bool doShift = !Version::greaterThan(versionThingOffset, versionThingFz);
2186 if (!doShift) return;
2187
2188 foreach (QGraphicsItem * item, scene()->items()) {
2189 ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
2190 if (itemBase == NULL) continue;
2191
2192 switch (itemBase->itemType()) {
2193 case ModelPart::Via:
2194 case ModelPart::Hole:
2195 itemBase->setPos(itemBase->pos().x() - (Hole::OffsetPixels / 2), itemBase->pos().y() - (Hole::OffsetPixels / 2));
2196 break;
2197
2198 default:
2199 continue;
2200 }
2201 }
2202 }
2203
canAlignToCenter(ItemBase * itemBase)2204 bool PCBSketchWidget::canAlignToCenter(ItemBase * itemBase)
2205 {
2206 return qobject_cast<Hole *>(itemBase) != NULL;
2207 }
2208
selectAllItemType(ModelPart::ItemType itemType,const QString & typeName)2209 int PCBSketchWidget::selectAllItemType(ModelPart::ItemType itemType, const QString & typeName)
2210 {
2211 int boardCount;
2212 ItemBase * board = findSelectedBoard(boardCount);
2213 if (boardCount == 0 && autorouteTypePCB()) {
2214 QMessageBox::critical(this, tr("Fritzing"),
2215 tr("Your sketch does not have a board yet! Please add a PCB in order to use this selection operation."));
2216 return 0;
2217 }
2218 if (board == NULL) {
2219 QMessageBox::critical(this, tr("Fritzing"),
2220 tr("Please click on a PCB first--this selection operation only works for one board at a time."));
2221 return 0;
2222 }
2223
2224 QSet<ItemBase *> itemBases;
2225 foreach (QGraphicsItem * item, (board == NULL ? scene()->items() : scene()->collidingItems(board))) {
2226 ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
2227 if (itemBase == NULL) continue;
2228 if (itemBase->itemType() != itemType) continue;
2229
2230 itemBases.insert(itemBase->layerKinChief());
2231 }
2232
2233 return selectAllItems(itemBases, QObject::tr("Select all %1").arg(typeName));
2234
2235 }
2236
selectAllWires(ViewGeometry::WireFlag flag)2237 void PCBSketchWidget::selectAllWires(ViewGeometry::WireFlag flag)
2238 {
2239 int boardCount;
2240 ItemBase * board = findSelectedBoard(boardCount);
2241 if (boardCount == 0 && autorouteTypePCB()) {
2242 QMessageBox::critical(this, tr("Fritzing"),
2243 tr("Your sketch does not have a board yet! Please add a PCB in order to use this selection operation."));
2244 return;
2245 }
2246 if (board == NULL) {
2247 QMessageBox::critical(this, tr("Fritzing"),
2248 tr("Please click on a PCB first--this selection operation only works for one board at a time."));
2249 return;
2250 }
2251
2252 QList<QGraphicsItem *> items = scene()->collidingItems(board);
2253 selectAllWiresFrom(flag, items);
2254 }
2255
defaultViewLayerPlacement(ModelPart * modelPart)2256 ViewLayer::ViewLayerPlacement PCBSketchWidget::defaultViewLayerPlacement(ModelPart * modelPart) {
2257 if (modelPart == NULL || boardLayers() == 2) return SketchWidget::defaultViewLayerPlacement(modelPart);
2258
2259 if (modelPart->flippedSMD()) return ViewLayer::NewBottom;
2260 if (modelPart->moduleID() == ModuleIDNames::GroundPlaneModuleIDName) return ViewLayer::NewBottom;
2261
2262 return SketchWidget::defaultViewLayerPlacement(modelPart);
2263 }
2264
checkDroppedModuleID(const QString & moduleID)2265 QString PCBSketchWidget::checkDroppedModuleID(const QString & moduleID) {
2266 if (moduleID.endsWith(ModuleIDNames::CopperBlockerModuleIDName)) {
2267 if (dropOnBottom()) return ModuleIDNames::Copper0BlockerModuleIDName;
2268
2269 return ModuleIDNames::Copper1BlockerModuleIDName;
2270 }
2271
2272 if (moduleID.endsWith(ModuleIDNames::PadModuleIDName)) {
2273 if (dropOnBottom()) return ModuleIDNames::Copper0PadModuleIDName;
2274
2275 return ModuleIDNames::PadModuleIDName;
2276 }
2277
2278 if (moduleID.endsWith(ModuleIDNames::RectanglePCBModuleIDName)) {
2279 if (boardLayers() == 2) return ModuleIDNames::TwoSidedRectanglePCBModuleIDName;
2280 return ModuleIDNames::RectanglePCBModuleIDName;
2281 }
2282
2283 if (moduleID.endsWith(ModuleIDNames::EllipsePCBModuleIDName)) {
2284 if (boardLayers() == 2) return ModuleIDNames::TwoSidedEllipsePCBModuleIDName;
2285 return ModuleIDNames::EllipsePCBModuleIDName;
2286 }
2287
2288 return moduleID;
2289 }
2290
convertToVia(ConnectorItem * lastHoverEnterConnectorItem)2291 void PCBSketchWidget::convertToVia(ConnectorItem * lastHoverEnterConnectorItem) {
2292 Wire * wire = qobject_cast<Wire *>(lastHoverEnterConnectorItem->attachedTo());
2293 if (wire == NULL) return;
2294
2295 this->clearHoldingSelectItem();
2296 this->m_moveEventCount = 0; // clear this so an extra MoveItemCommand isn't posted
2297
2298 QUndoCommand * parentCommand = new QUndoCommand(QObject::tr("Convert to Via"));
2299
2300 new CleanUpWiresCommand(this, CleanUpWiresCommand::UndoOnly, parentCommand);
2301 new CleanUpRatsnestsCommand(this, CleanUpWiresCommand::UndoOnly, parentCommand);
2302
2303 double ringThickness, holeSize;
2304 getViaSize(ringThickness, holeSize);
2305 QPointF p = lastHoverEnterConnectorItem->sceneAdjustedTerminalPoint(NULL);
2306 double d = ringThickness + (holeSize / 2) + Via::OffsetPixels;
2307 QPointF loc(p.x() - d, p.y() - d);
2308 long newID = ItemBase::getNextID();
2309 ViewGeometry viewGeometry;
2310 viewGeometry.setLoc(loc);
2311 new AddItemCommand(this, BaseCommand::CrossView, ModuleIDNames::ViaModuleIDName, wire->viewLayerPlacement(), viewGeometry, newID, true, -1, parentCommand);
2312
2313 QList<ConnectorItem *> connectorItems;
2314 connectorItems.append(lastHoverEnterConnectorItem);
2315 for (int i = 0; i < connectorItems.count(); i++) {
2316 ConnectorItem * from = connectorItems.at(i);
2317 foreach (ConnectorItem * to, from->connectedToItems()) {
2318 Wire * w = qobject_cast<Wire *>(to->attachedTo());
2319 if (w != NULL && w->isTraceType(getTraceFlag())) {
2320 if (!connectorItems.contains(to)) {
2321 connectorItems.append(to);
2322 }
2323 }
2324 }
2325 }
2326
2327
2328 foreach (ConnectorItem * from, connectorItems) {
2329 foreach (ConnectorItem * to, from->connectedToItems()) {
2330 Wire * w = qobject_cast<Wire *>(to->attachedTo());
2331 if (w != NULL && w->isTraceType(getTraceFlag())) {
2332 new ChangeConnectionCommand(this, BaseCommand::CrossView, from->attachedToID(), from->connectorSharedID(),
2333 to->attachedToID(), to->connectorSharedID(),
2334 ViewLayer::specFromID(w->viewLayerID()),
2335 false, parentCommand);
2336 }
2337 }
2338 }
2339
2340 foreach (ConnectorItem * from, connectorItems) {
2341 new ChangeConnectionCommand(this, BaseCommand::CrossView, from->attachedToID(), from->connectorSharedID(),
2342 newID, "connector0",
2343 ViewLayer::specFromID(wire->viewLayerID()),
2344 true, parentCommand);
2345
2346 }
2347
2348 SelectItemCommand * selectItemCommand = new SelectItemCommand(this, SelectItemCommand::NormalSelect, parentCommand);
2349 selectItemCommand->addRedo(newID);
2350
2351 new CleanUpRatsnestsCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
2352 new CleanUpWiresCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
2353
2354 m_undoStack->push(parentCommand);
2355
2356 }
2357
convertToBendpoint()2358 void PCBSketchWidget::convertToBendpoint() {
2359
2360 ItemBase * itemBase = NULL;
2361 foreach (QGraphicsItem * item, scene()->selectedItems()) {
2362 ItemBase * candidate = dynamic_cast<ItemBase *>(item);
2363 if (candidate == NULL) continue;
2364
2365 if (itemBase == NULL) itemBase = candidate->layerKinChief();
2366 else if (candidate->layerKinChief() != itemBase) return;
2367 }
2368
2369 Via * via = dynamic_cast<Via *>(itemBase);
2370 if (via == NULL) return;
2371
2372 QList<ConnectorItem *> viaConnectorItems;
2373 viaConnectorItems << via->connectorItem();
2374 if (via->connectorItem()->getCrossLayerConnectorItem()) {
2375 viaConnectorItems << via->connectorItem()->getCrossLayerConnectorItem();
2376 }
2377
2378 QList<ConnectorItem *> targets;
2379 int copper0 = 0;
2380 int copper1 = 0;
2381 bool copper0Only = false;
2382 bool copper1Only = false;
2383
2384 foreach (ConnectorItem * viaConnectorItem, viaConnectorItems) {
2385 foreach (ConnectorItem * connectorItem, viaConnectorItem->connectedToItems()) {
2386 Wire * wire = qobject_cast<Wire *>(connectorItem->attachedTo());
2387 if (wire == NULL) continue;
2388 if (wire->getRatsnest()) continue;
2389 if (!wire->isTraceType(getTraceFlag())) continue;
2390
2391 bool gotOne = false;;
2392 if (wire->viewLayerID() == ViewLayer::Copper0Trace) {
2393 copper0++;
2394 gotOne = true;
2395 }
2396 else if (wire->viewLayerID() == ViewLayer::Copper1Trace) {
2397 copper1++;
2398 gotOne = true;
2399 }
2400
2401 if (!gotOne) continue;
2402
2403 targets.append(connectorItem);
2404 QList<Wire *> wires;
2405 QList<ConnectorItem *> ends;
2406 wire->collectChained(wires, ends);
2407 foreach (ConnectorItem * end, ends) {
2408 if (end->getCrossLayerConnectorItem() == NULL) {
2409 if (ViewLayer::copperLayers(ViewLayer::NewTop).contains(end->attachedToViewLayerID())) {
2410 copper1Only = true;
2411 }
2412 else {
2413 copper0Only = true;
2414 }
2415 }
2416 }
2417 }
2418 }
2419
2420 if (copper0Only && copper1Only) {
2421 QMessageBox::warning(this, tr("Fritzing"),
2422 tr("Unable to convert this via to a bendpoint because it is connected to a part that is only on the bottom layer and another part that is only on the top layer."));
2423 return;
2424 }
2425
2426 this->clearHoldingSelectItem();
2427 this->m_moveEventCount = 0; // clear this so an extra MoveItemCommand isn't posted
2428
2429 QUndoCommand * parentCommand = new QUndoCommand(QObject::tr("Convert Via to Bendpoint"));
2430
2431 new CleanUpWiresCommand(this, CleanUpWiresCommand::UndoOnly, parentCommand);
2432 new CleanUpRatsnestsCommand(this, CleanUpWiresCommand::UndoOnly, parentCommand);
2433
2434 foreach (ConnectorItem * target, targets) {
2435 new ChangeConnectionCommand(this, BaseCommand::CrossView, target->attachedToID(), target->connectorSharedID(),
2436 via->id(), via->connectorItem()->connectorSharedID(),
2437 ViewLayer::specFromID(target->attachedToViewLayerID()),
2438 false, parentCommand);
2439 }
2440
2441 ViewLayer::ViewLayerID dest = ViewLayer::Copper0Trace;
2442 if (copper1Only) {
2443 dest = ViewLayer::Copper1Trace;
2444 }
2445 else if (!copper0Only) {
2446 if (copper1 > copper0) {
2447 dest = ViewLayer::Copper1Trace;
2448 }
2449 }
2450
2451 if (copper0 > 0 && copper1 > 0) {
2452 foreach (ConnectorItem * target, targets) {
2453 if (target->attachedToViewLayerID() == dest) continue;
2454
2455 Wire * wire = qobject_cast<Wire *>(target->attachedTo());
2456 QList<Wire *> wires;
2457 QList<ConnectorItem *> ends;
2458 wire->collectChained(wires, ends);
2459 foreach (Wire * w, wires) {
2460 new ChangeLayerCommand(this, w->id(), w->zValue(), m_viewLayers.value(dest)->nextZ(), w->viewLayerID(), dest, parentCommand);
2461 }
2462 }
2463 }
2464
2465
2466 ConnectorItem * from = targets.at(0);
2467 for (int j = 1; j < targets.count(); j++) {
2468 ConnectorItem * to = targets.at(j);
2469 new ChangeConnectionCommand(this, BaseCommand::CrossView, from->attachedToID(), from->connectorSharedID(),
2470 to->attachedToID(), to->connectorSharedID(),
2471 ViewLayer::specFromID(dest),
2472 true, parentCommand);
2473 }
2474
2475
2476 makeDeleteItemCommand(via, BaseCommand::CrossView, parentCommand);
2477
2478 new CleanUpRatsnestsCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
2479 new CleanUpWiresCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
2480
2481 m_undoStack->push(parentCommand);
2482 }
2483
2484
2485
canConnect(Wire * from,ItemBase * to)2486 bool PCBSketchWidget::canConnect(Wire * from, ItemBase * to) {
2487 to = to->layerKinChief();
2488 QList<ItemBase *> kin;
2489 kin.append(to);
2490 kin.append(to->layerKin());
2491
2492 foreach (ItemBase * itemBase, kin) {
2493 if (!ViewLayer::isCopperLayer(itemBase->viewLayerID())) continue;
2494
2495 if (ViewLayer::canConnect(from->viewLayerID(), itemBase->viewLayerID())) return true;
2496 }
2497
2498 return false;
2499 }
2500
makePasteMask(const QString & svgMask,ItemBase * board,double dpi,const LayerList & maskLayerIDs)2501 QString PCBSketchWidget::makePasteMask(const QString & svgMask, ItemBase * board, double dpi, const LayerList & maskLayerIDs)
2502 {
2503 QList<ConnectorItem *> throughHoles;
2504 QList<ConnectorItem *> pads;
2505 collectThroughHole(throughHoles, pads, maskLayerIDs);
2506 if (pads.count() == 0) return "";
2507
2508 QRectF boardRect = board->sceneBoundingRect();
2509 QList<QRectF> connectorRects;
2510 foreach (ConnectorItem * connectorItem, throughHoles) {
2511 QRectF r = connectorItem->sceneBoundingRect();
2512 QRectF s((r.left() - boardRect.left()) * dpi / GraphicsUtils::SVGDPI,
2513 (r.top() - boardRect.top()) * dpi / GraphicsUtils::SVGDPI,
2514 r.width() * dpi / GraphicsUtils::SVGDPI,
2515 r.height() * dpi / GraphicsUtils::SVGDPI);
2516 connectorRects << s;
2517 }
2518
2519 QDomDocument doc;
2520 doc.setContent(svgMask);
2521 QList<QDomElement> leaves;
2522 QDomElement root = doc.documentElement();
2523 TextUtils::collectLeaves(root, leaves);
2524 int ix = 0;
2525 foreach (QDomElement element, leaves) {
2526 element.setAttribute("id", ix++);
2527 }
2528
2529 QSvgRenderer renderer;
2530 renderer.load(doc.toByteArray());
2531
2532 foreach (QDomElement element, leaves) {
2533 QString id = element.attribute("id");
2534 QRectF bounds = renderer.boundsOnElement(id);
2535 QRectF leafRect = renderer.matrixForElement(id).mapRect(bounds);
2536 QPointF leafCenter = leafRect.center();
2537 foreach (QRectF r, connectorRects) {
2538 if (!leafRect.intersects(r)) continue;
2539
2540 if (!r.contains(leafCenter)) continue;
2541
2542 QPointF rCenter = r.center();
2543 if (!leafRect.contains(rCenter)) continue;
2544
2545 element.setTagName("g");
2546 break;
2547 }
2548 }
2549
2550 return doc.toString();
2551 }
2552
checkLoadedTraces()2553 int PCBSketchWidget::checkLoadedTraces() {
2554
2555 ProcessEventBlocker::processEvents();
2556 scene()->clearSelection();
2557 QList<Wire *> wires;
2558 QHash<Wire *, QLineF> lines;
2559 foreach (QGraphicsItem * item, scene()->items()) {
2560 Wire * wire = dynamic_cast<Wire *>(item);
2561 if (wire == NULL) continue;
2562 if (!wire->isTraceType(getTraceFlag())) continue;
2563
2564 ConnectorItem * c0 = wire->connector0();
2565 ConnectorItem * c1 = wire->connector1();
2566 QPointF p0 = c0->sceneBoundingRect().center();
2567 QPointF p1 = c1->sceneBoundingRect().center();
2568 QLineF line(p0, p1);
2569 lines.insert(wire, line);
2570 }
2571
2572 foreach (Wire * wire, lines.keys()) {
2573 QList<ConnectorItem *> already;
2574 wire->updateConnections(wire->connector0(), false, already);
2575 wire->updateConnections(wire->connector1(), false, already);
2576 }
2577
2578 foreach (Wire * wire, lines.keys()) {
2579 QLineF line = wire->line();
2580 QPointF l0 = wire->pos() + line.p1();
2581 QPointF l1 = wire->pos() + line.p2();
2582 QLineF oldLine = lines.value(wire);
2583
2584 double d = 0.1;
2585
2586 if (qAbs(oldLine.p1().x() - l0.x()) > d ||
2587 qAbs(oldLine.p1().y() - l0.y()) > d ||
2588 qAbs(oldLine.p2().x() - l1.x()) > d ||
2589 qAbs(oldLine.p2().y() - l1.y()) > d)
2590 {
2591 wires.append(wire);
2592 wire->debugInfo2(QString("wire moved from:\n%1,%2 %3,%4\n%5,%6 %7,%8")
2593 .arg(oldLine.p1().x()).arg(oldLine.p1().y()).arg(oldLine.p2().x()).arg(oldLine.p2().y())
2594 .arg(l0.x()).arg(l0.y()).arg(l1.x()).arg(l1.y())
2595 );
2596 }
2597 }
2598
2599 foreach (Wire * wire, wires) {
2600 wire->setSelected(true);
2601 }
2602
2603 return wires.count();
2604 }
2605
hasCustomBoardShape()2606 bool PCBSketchWidget::hasCustomBoardShape() {
2607 QList<ItemBase *> boards = findBoard();
2608 foreach (ItemBase * board, boards) {
2609 if (qobject_cast<BoardLogoItem *>(board)) return true;
2610 }
2611
2612 return false;
2613 }
2614
getViewLayerPlacement(ModelPart * modelPart,QDomElement & instance,QDomElement & view,ViewGeometry & viewGeometry)2615 ViewLayer::ViewLayerPlacement PCBSketchWidget::getViewLayerPlacement(ModelPart * modelPart, QDomElement & instance, QDomElement & view, ViewGeometry & viewGeometry)
2616 {
2617 if (modelPart->flippedSMD()) {
2618 ViewLayer::ViewLayerID viewLayerID = ViewLayer::viewLayerIDFromXmlString(view.attribute("layer"));
2619 if (ViewLayer::bottomLayers().contains(viewLayerID)) return ViewLayer::NewBottom;
2620 return ViewLayer::NewTop;
2621 }
2622
2623 if (modelPart->itemType() == ModelPart::Part) {
2624 QDomElement views = view.parentNode().toElement();
2625 QDomElement pcbview = views.firstChildElement("pcbView");
2626 bool bottom = pcbview.attribute("bottom", "").compare("true") == 0;
2627 if (bottom) return ViewLayer::NewBottom;
2628 }
2629
2630 return SketchWidget::getViewLayerPlacement(modelPart, instance, view, viewGeometry);
2631 }
2632
routingLayers(ViewLayer::ViewLayerPlacement spec)2633 LayerList PCBSketchWidget::routingLayers(ViewLayer::ViewLayerPlacement spec) {
2634 LayerList layerList = ViewLayer::copperLayers(spec);
2635 layerList.removeOne(ViewLayer::GroundPlane0);
2636 layerList.removeOne(ViewLayer::GroundPlane1);
2637 return layerList;
2638 }
2639
attachedToBottomLayer(ConnectorItem * connectorItem)2640 bool PCBSketchWidget::attachedToBottomLayer(ConnectorItem * connectorItem) {
2641 return (connectorItem->attachedToViewLayerID() == ViewLayer::Copper0) ||
2642 (connectorItem->attachedToViewLayerID() == ViewLayer::Copper0Trace);
2643 }
2644
attachedToTopLayer(ConnectorItem * connectorItem)2645 bool PCBSketchWidget::attachedToTopLayer(ConnectorItem * connectorItem) {
2646 return (connectorItem->attachedToViewLayerID() == ViewLayer::Copper1) ||
2647 (connectorItem->attachedToViewLayerID() == ViewLayer::Copper1Trace);
2648 }
2649
getAutorouterSettings()2650 QHash<QString, QString> PCBSketchWidget::getAutorouterSettings() {
2651 return m_autorouterSettings;
2652 }
2653
setAutorouterSettings(QHash<QString,QString> & autorouterSettings)2654 void PCBSketchWidget::setAutorouterSettings(QHash<QString, QString> & autorouterSettings) {
2655 QList<QString> keys;
2656 keys << DRC::KeepoutSettingName << AutorouterSettingsDialog::AutorouteTraceWidth << Via::AutorouteViaHoleSize << Via::AutorouteViaRingThickness << GroundPlaneGenerator::KeepoutSettingName;
2657 foreach (QString key, keys) {
2658 m_autorouterSettings.insert(key, autorouterSettings.value(key, ""));
2659 }
2660 }
2661
hidePartSilkscreen()2662 void PCBSketchWidget::hidePartSilkscreen() {
2663
2664 ItemBase * itemBase = NULL;
2665 foreach (QGraphicsItem * item, scene()->selectedItems()) {
2666 ItemBase * candidate = dynamic_cast<ItemBase *>(item);
2667 if (candidate == NULL) continue;
2668
2669 itemBase = candidate->layerKinChief();
2670 break;
2671 }
2672
2673 if (itemBase == NULL) return;
2674
2675 QList<ItemBase *> itemBases;
2676 itemBases.append(itemBase);
2677 itemBases.append(itemBase->layerKin());
2678 foreach (ItemBase * lkpi, itemBases) {
2679 if (lkpi->viewLayerID() == ViewLayer::Silkscreen1 || lkpi->viewLayerID() == ViewLayer::Silkscreen0) {
2680 bool layerHidden = lkpi->layerHidden();
2681 QUndoCommand * parentCommand = new QUndoCommand(layerHidden ? tr("Show part silkscreen") : tr("Hide part silkscreen"));
2682 new HidePartLayerCommand(this, itemBase->id(), ViewLayer::Silkscreen0, layerHidden, !layerHidden, parentCommand);
2683 new HidePartLayerCommand(this, itemBase->id(), ViewLayer::Silkscreen1, layerHidden, !layerHidden, parentCommand);
2684 m_undoStack->push(parentCommand);
2685 break;
2686 }
2687 }
2688 }
2689
fabQuote()2690 void PCBSketchWidget::fabQuote() {
2691 int boardCount = 0;
2692 double area = calcBoardArea(boardCount);
2693 QuoteDialog::setArea(area, boardCount);
2694 if (boardCount == 0) {
2695 QMessageBox::information(this, tr("Fritzing Fab Quote"),
2696 tr("Your sketch does not have a board yet. You cannot fabricate this sketch without a PCB part."));
2697 return;
2698 }
2699
2700 if (!QuoteDialog::quoteSucceeded()) {
2701 QMessageBox::information(this, tr("Fritzing Fab Quote"),
2702 tr("Sorry, http://fab.fritzing.org is not responding to the quote request. Please check your network connection and/or try again later."));
2703 requestQuote(true);
2704 return;
2705 }
2706
2707 m_quoteDialog = new QuoteDialog(true, this);
2708 requestQuote(true);
2709
2710 m_quoteDialog->exec();
2711 delete m_quoteDialog;
2712 m_quoteDialog = NULL;
2713 }
2714
gotFabQuote(QNetworkReply * networkReply)2715 void PCBSketchWidget::gotFabQuote(QNetworkReply * networkReply) {
2716 QNetworkAccessManager * manager = networkReply->manager();
2717 QString count = manager->property("count").toString();
2718 QStringList countArgs = count.split(",");
2719 int responseCode = networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
2720 if (responseCode == 200) {
2721 QString data(networkReply->readAll());
2722 QStringList values = data.split(",");
2723 if (values.count() == countArgs.count()) {
2724 for (int ix = 0; ix < countArgs.count(); ix++) {
2725 QString value = values.at(ix);
2726 QString c = countArgs.at(ix);
2727 bool ok;
2728 int count = c.toInt(&ok);
2729 if (!ok) continue;
2730
2731 double cost = value.toDouble(&ok);
2732 if (!ok) continue;
2733
2734 QuoteDialog::setCountCost(ix, count, cost);
2735 }
2736 QuoteDialog::setQuoteSucceeded(true);
2737 }
2738
2739 if (m_quoteDialog) m_quoteDialog->setText();
2740 if (m_rolloverQuoteDialog) m_rolloverQuoteDialog->setText();
2741 }
2742 else {
2743 }
2744
2745 manager->deleteLater();
2746 networkReply->deleteLater();
2747 }
2748
requestQuote(bool byUser)2749 void PCBSketchWidget::requestQuote(bool byUser) {
2750 int boardCount;
2751 double area = calcBoardArea(boardCount);
2752 QuoteDialog::setArea(area, boardCount);
2753
2754 QString paramString = Version::makeRequestParamsString(false);
2755 QNetworkAccessManager * manager = new QNetworkAccessManager(this);
2756
2757
2758 QString countArgs = QuoteDialog::countArgs();
2759 manager->setProperty("count", countArgs);
2760 QString filename = QUrl::toPercentEncoding(filenameIf());
2761 connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(gotFabQuote(QNetworkReply *)));
2762 QString string = QString("http://fab.fritzing.org/fritzing-fab/quote%1&area=%2&count=%3&filename=%4&byuser=%5")
2763 .arg(paramString)
2764 .arg(area)
2765 .arg(countArgs)
2766 .arg(filename)
2767 .arg(byUser)
2768 ;
2769 QuoteDialog::setQuoteSucceeded(false);
2770 manager->get(QNetworkRequest(QUrl(string)));
2771 }
2772
calcBoardArea(int & boardCount)2773 double PCBSketchWidget::calcBoardArea(int & boardCount) {
2774 QList<ItemBase *> boards = findBoard();
2775 boardCount = boards.count();
2776 if (boardCount == 0) {
2777 return 0;
2778 }
2779
2780 double area = 0;
2781 foreach (ItemBase * board, boards) {
2782 area += GraphicsUtils::pixels2mm(board->boundingRect().width(), GraphicsUtils::SVGDPI) *
2783 GraphicsUtils::pixels2mm(board->boundingRect().height(), GraphicsUtils::SVGDPI) /
2784 100;
2785 }
2786
2787 return area;
2788 }
2789
addPartItem(ModelPart * modelPart,ViewLayer::ViewLayerPlacement viewLayerPlacement,PaletteItem * paletteItem,bool doConnectors,bool & ok,ViewLayer::ViewID viewID,bool temporary)2790 PaletteItem* PCBSketchWidget::addPartItem(ModelPart * modelPart, ViewLayer::ViewLayerPlacement viewLayerPlacement, PaletteItem * paletteItem, bool doConnectors, bool & ok, ViewLayer::ViewID viewID, bool temporary) {
2791 if (viewID == ViewLayer::PCBView && Board::isBoard(modelPart)) {
2792 requestQuoteSoon();
2793 }
2794 return SketchWidget::addPartItem(modelPart, viewLayerPlacement, paletteItem, doConnectors, ok, viewID, temporary);
2795 }
2796
requestQuoteSoon()2797 void PCBSketchWidget::requestQuoteSoon() {
2798 m_requestQuoteTimer.stop();
2799 m_requestQuoteTimer.start();
2800 }
2801
requestQuoteNow()2802 void PCBSketchWidget::requestQuoteNow() {
2803 m_requestQuoteTimer.stop();
2804 requestQuote(false);
2805 }
2806
resizeBoard(long itemID,double mmW,double mmH)2807 ItemBase * PCBSketchWidget::resizeBoard(long itemID, double mmW, double mmH) {
2808 ItemBase * itemBase = SketchWidget::resizeBoard(itemID, mmW, mmH);
2809 if (itemBase != NULL && Board::isBoard(itemBase)) requestQuoteSoon();
2810 return itemBase;
2811 }
2812
quoteDialog(QWidget * parent)2813 QDialog * PCBSketchWidget::quoteDialog(QWidget * parent) {
2814 if (m_rolloverQuoteDialog == NULL) {
2815 m_rolloverQuoteDialog = new QuoteDialog(false, parent);
2816 requestQuote(false);
2817 }
2818 m_rolloverQuoteDialog->setText();
2819 return m_rolloverQuoteDialog;
2820 }
2821
getKeepoutMils()2822 double PCBSketchWidget::getKeepoutMils() {
2823 QSettings settings;
2824 QString keepoutString = m_autorouterSettings.value(GroundPlaneGenerator::KeepoutSettingName);
2825 if (keepoutString.isEmpty()) {
2826 keepoutString = settings.value(GroundPlaneGenerator::KeepoutSettingName, "").toString();
2827 }
2828
2829 bool ok;
2830 double mils = TextUtils::convertToInches(keepoutString, &ok, false);
2831 if (ok) {
2832 mils *= 1000; // convert from inches
2833 }
2834 else {
2835 mils = GroundPlaneGenerator::KeepoutDefaultMils;
2836 }
2837
2838 return mils;
2839 }
2840
setGroundFillKeepout()2841 void PCBSketchWidget::setGroundFillKeepout() {
2842
2843 bool ok;
2844 double mils = QInputDialog::getInt(this, tr("Enter Keepout"),
2845 tr("Keepout is in mils (.001 inches).\n\n") +
2846 tr("Note that due to aliasing, distances may be too short by up to 2 mils\n") +
2847 tr("so you may want to increase the keepout value by that much.\n\n") +
2848 tr("10 mils is a good default choice.\n\n") +
2849 tr("Enter keepout value:"),
2850 qRound(getKeepoutMils()), 0, 10 * 1000, 1, &ok);
2851
2852
2853 if (!ok) return;
2854
2855 QString keepoutString = QString("%1in").arg(mils / 1000);
2856 m_autorouterSettings.insert(GroundPlaneGenerator::KeepoutSettingName, keepoutString);
2857
2858 QSettings settings;
2859 settings.setValue(GroundPlaneGenerator::KeepoutSettingName, keepoutString);
2860 }
2861
setViewFromBelow(bool viewFromBelow)2862 void PCBSketchWidget::setViewFromBelow(bool viewFromBelow) {
2863 if (m_viewFromBelow == viewFromBelow) return;
2864
2865 QSet<ItemBase *> chiefs;
2866 foreach (QGraphicsItem * item, scene()->items()) {
2867 ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
2868 if (itemBase == NULL) continue;
2869
2870 ViewLayer * viewLayer = m_viewLayers.value(itemBase->viewLayerID(), NULL);
2871 if (viewLayer == NULL) continue;
2872
2873 double newZ = viewLayer->getZFromBelow(itemBase->z(), viewFromBelow);
2874 itemBase->setZValue(newZ);
2875 itemBase->getViewGeometry().setZ(newZ);
2876 chiefs.insert(itemBase->layerKinChief());
2877 }
2878
2879 foreach (ItemBase * chief, chiefs) {
2880 chief->figureHover();
2881 }
2882
2883 foreach (ViewLayer * viewLayer, m_viewLayers.values()) {
2884 viewLayer->setFromBelow(viewFromBelow);
2885 }
2886
2887 // enable only with multiple layers?
2888 // active layer should be secondary mechanism?
2889
2890 SketchWidget::setViewFromBelow(viewFromBelow);
2891 }
2892
getDroppedItemViewLayerPlacement(ModelPart * modelPart,ViewLayer::ViewLayerPlacement & viewLayerPlacement)2893 void PCBSketchWidget::getDroppedItemViewLayerPlacement(ModelPart * modelPart, ViewLayer::ViewLayerPlacement & viewLayerPlacement) {
2894 if (ResizableBoard::isBoard(modelPart)) {
2895 viewLayerPlacement = ViewLayer::NewTop;
2896 return;
2897 }
2898
2899 viewLayerPlacement = defaultViewLayerPlacement(modelPart); // top for a two layer board
2900 if (boardLayers() == 2) {
2901 if (dropOnBottom()) {
2902 viewLayerPlacement = ViewLayer::NewBottom;
2903 }
2904 return;
2905 }
2906 }
2907
dropOnBottom()2908 bool PCBSketchWidget::dropOnBottom() {
2909 if (boardLayers() == 1) return true;
2910
2911 if (!layerIsActive(ViewLayer::Copper0)) return false;
2912 if (!layerIsActive(ViewLayer::Copper1)) return true;
2913
2914 return viewFromBelow();
2915 }
2916
updateOK(ConnectorItem * c1,ConnectorItem * c2)2917 bool PCBSketchWidget::updateOK(ConnectorItem * c1, ConnectorItem * c2) {
2918 // don't update if both connectors belong to parts--this isn't legit in schematic or pcb view
2919 if (c1->attachedTo()->wireFlags()) return true;
2920 return c2->attachedTo()->wireFlags() != 0;
2921 }
2922