1 /*
2 * LibrePCB - Professional EDA for everyone!
3 * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4 * https://librepcb.org/
5 *
6 * This program 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 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /*******************************************************************************
21 * Includes
22 ******************************************************************************/
23 #include "bgi_plane.h"
24
25 #include "../../project.h"
26 #include "../board.h"
27 #include "../boardlayerstack.h"
28 #include "../items/bi_plane.h"
29
30 #include <librepcb/common/geometry/polygon.h>
31 #include <librepcb/common/graphics/primitivepathgraphicsitem.h>
32 #include <librepcb/common/toolbox.h>
33
34 #include <QPrinter>
35 #include <QtCore>
36 #include <QtWidgets>
37
38 /*******************************************************************************
39 * Namespace
40 ******************************************************************************/
41 namespace librepcb {
42 namespace project {
43
44 /*******************************************************************************
45 * Constructors / Destructor
46 ******************************************************************************/
47
BGI_Plane(BI_Plane & plane)48 BGI_Plane::BGI_Plane(BI_Plane& plane) noexcept
49 : BGI_Base(),
50 mPlane(plane),
51 mLayer(nullptr),
52 mLineWidthPx(0),
53 mVertexRadiusPx(0) {
54 updateCacheAndRepaint();
55 }
56
~BGI_Plane()57 BGI_Plane::~BGI_Plane() noexcept {
58 }
59
60 /*******************************************************************************
61 * Getters
62 ******************************************************************************/
63
isSelectable() const64 bool BGI_Plane::isSelectable() const noexcept {
65 return mLayer && mLayer->isVisible();
66 }
67
getLineIndexAtPosition(const Point & pos) const68 int BGI_Plane::getLineIndexAtPosition(const Point& pos) const noexcept {
69 // We build temporary PrimitivePathGraphicsItem objects for each segment of
70 // the plane and check if the specified position is located within the shape
71 // of one of these graphics items. This is quite ugly, but was easy to
72 // implement and seems to work nicely... ;-)
73 for (int i = 1; i < mPlane.getOutline().getVertices().count(); ++i) {
74 Path path;
75 path.addVertex(mPlane.getOutline().getVertices()[i - 1]);
76 path.addVertex(mPlane.getOutline().getVertices()[i]);
77
78 PrimitivePathGraphicsItem item(const_cast<BGI_Plane*>(this));
79 item.setPath(path.toQPainterPathPx());
80 item.setLineWidth(UnsignedLength(Length::fromPx(mLineWidthPx)));
81 item.setLineLayer(mLayer);
82
83 if (item.shape().contains(item.mapFromScene(pos.toPxQPointF()))) {
84 return i;
85 }
86 }
87
88 return -1;
89 }
90
getVertexIndicesAtPosition(const Point & pos) const91 QVector<int> BGI_Plane::getVertexIndicesAtPosition(const Point& pos) const
92 noexcept {
93 QVector<int> indices;
94 for (int i = 0; i < mPlane.getOutline().getVertices().count(); ++i) {
95 Point diff = (mPlane.getOutline().getVertices()[i].getPos() - pos);
96 if (diff.getLength()->toPx() < mVertexRadiusPx) {
97 indices.append(i);
98 }
99 }
100 return indices;
101 }
102
103 /*******************************************************************************
104 * General Methods
105 ******************************************************************************/
106
updateCacheAndRepaint()107 void BGI_Plane::updateCacheAndRepaint() noexcept {
108 prepareGeometryChange();
109
110 setZValue(getZValueOfCopperLayer(*mPlane.getLayerName()));
111
112 mLayer = getLayer(*mPlane.getLayerName());
113
114 // set shape and bounding rect
115 mOutline = mPlane.getOutline().toClosedPath().toQPainterPathPx();
116 mShape = mShape = Toolbox::shapeFromPath(
117 mOutline, QPen(Length::fromMm(0.3).toPx()), QBrush());
118 mBoundingRect = mShape.boundingRect();
119
120 // get areas
121 mAreas.clear();
122 for (const Path& r : mPlane.getFragments()) {
123 mAreas.append(r.toQPainterPathPx());
124 mBoundingRect = mBoundingRect.united(mAreas.last().boundingRect());
125 }
126
127 update();
128 }
129
130 /*******************************************************************************
131 * Inherited from QGraphicsItem
132 ******************************************************************************/
133
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)134 void BGI_Plane::paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
135 QWidget* widget) {
136 Q_UNUSED(widget);
137
138 const bool selected = mPlane.isSelected();
139 const bool deviceIsPrinter =
140 (dynamic_cast<QPrinter*>(painter->device()) != nullptr);
141 const qreal lod =
142 option->levelOfDetailFromTransform(painter->worldTransform());
143
144 if (mLayer && mLayer->isVisible()) {
145 // draw outline only on screen, not for print or PDF export
146 if (!deviceIsPrinter) {
147 mLineWidthPx = 3 / lod;
148 painter->setPen(QPen(mLayer->getColor(selected), mLineWidthPx,
149 Qt::DashLine, Qt::RoundCap));
150 painter->setBrush(Qt::NoBrush);
151 painter->drawPath(mOutline);
152
153 // if the plane is selected, draw vertex handles
154 if (selected) {
155 mVertexRadiusPx = (mLineWidthPx / 2) + Length::fromMm(0.2).toPx();
156 painter->setPen(
157 QPen(mLayer->getColor(selected), 0, Qt::SolidLine, Qt::RoundCap));
158 foreach (const Vertex& vertex, mPlane.getOutline().getVertices()) {
159 painter->drawEllipse(vertex.getPos().toPxQPointF(), mVertexRadiusPx,
160 mVertexRadiusPx);
161 }
162 }
163 }
164
165 // draw plane only if plane should be visible
166 if (mPlane.isVisible()) {
167 painter->setPen(Qt::NoPen);
168 painter->setBrush(mLayer->getColor(selected));
169 foreach (const QPainterPath& area, mAreas) { painter->drawPath(area); }
170 }
171 }
172
173 #ifdef QT_DEBUG
174 // draw bounding rect
175 const GraphicsLayer* layer = mPlane.getBoard().getLayerStack().getLayer(
176 GraphicsLayer::sDebugGraphicsItemsBoundingRects);
177 if (layer) {
178 if (layer->isVisible()) {
179 painter->setPen(QPen(layer->getColor(selected), 0));
180 painter->setBrush(Qt::NoBrush);
181 painter->drawRect(mBoundingRect);
182 }
183 }
184 #endif
185 }
186
187 /*******************************************************************************
188 * Private Methods
189 ******************************************************************************/
190
getLayer(QString name) const191 GraphicsLayer* BGI_Plane::getLayer(QString name) const noexcept {
192 if (mPlane.getIsMirrored()) name = GraphicsLayer::getMirroredLayerName(name);
193 return mPlane.getBoard().getLayerStack().getLayer(name);
194 }
195
196 /*******************************************************************************
197 * End of File
198 ******************************************************************************/
199
200 } // namespace project
201 } // namespace librepcb
202