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 "airwiresbuilder.h"
24
25 #include <unordered_map>
26
27 #include <QtCore>
28
29 /*******************************************************************************
30 * Namespace
31 ******************************************************************************/
32 namespace librepcb {
33
34 /*******************************************************************************
35 * Constructors / Destructor
36 ******************************************************************************/
37
AirWiresBuilder()38 AirWiresBuilder::AirWiresBuilder() noexcept {
39 }
40
~AirWiresBuilder()41 AirWiresBuilder::~AirWiresBuilder() noexcept {
42 }
43
44 /*******************************************************************************
45 * Public Methods
46 ******************************************************************************/
47
addPoint(const Point & p)48 int AirWiresBuilder::addPoint(const Point& p) noexcept {
49 int id = mPoints.size();
50 mPoints.emplace_back(p.getX().toNm(), p.getY().toNm(), id);
51 return id;
52 }
53
addEdge(int p1,int p2)54 void AirWiresBuilder::addEdge(int p1, int p2) noexcept {
55 mEdges.emplace_back(mPoints[p1], mPoints[p2], -1);
56 }
57
buildAirWires()58 AirWiresBuilder::AirWires AirWiresBuilder::buildAirWires() noexcept {
59 // remember how many edges are already known as connected
60 uint connectedEdges = mEdges.size();
61
62 // determine additional edges between found points (candidates for airwires)
63 if (mPoints.size() == 2) {
64 mEdges.emplace_back(mPoints[0], mPoints[1], -1);
65 } else if (mPoints.size() == 3) {
66 // manually triangulate since it is easy and more stable than the
67 // delaunay-triangulation library
68 mEdges.emplace_back(mPoints[0], mPoints[1], -1);
69 mEdges.emplace_back(mPoints[1], mPoints[2], -1);
70 mEdges.emplace_back(mPoints[2], mPoints[0], -1);
71 } else if (mPoints.size() >= 3) {
72 // since delaunay-triangulation sometimes doesn't work well, add fallback
73 // edges to make sure at least all points are connected somehow
74 for (std::size_t i = 1; i < mPoints.size(); ++i) {
75 mEdges.emplace_back(mPoints[i - 1], mPoints[i], -1);
76 }
77
78 // now run delaunay triangulation to add additional edges
79 delaunay::Delaunay<qreal> del;
80 del.triangulate(mPoints);
81 mEdges.insert(mEdges.end(), del.getEdges().begin(), del.getEdges().end());
82 }
83
84 // determine weights of these new edges
85 for (uint i = connectedEdges; i < mEdges.size(); ++i) {
86 mEdges[i].weight = mEdges[i].p1.dist2(mEdges[i].p2);
87 }
88
89 // find airwires in list of edges
90 return AirWiresBuilder::kruskalMst();
91 }
92
93 /*******************************************************************************
94 * Private Methods
95 ******************************************************************************/
96
97 // adapted from horizon/kicad
kruskalMst()98 AirWiresBuilder::AirWires AirWiresBuilder::kruskalMst() noexcept {
99 unsigned int nodeNumber = mPoints.size();
100 unsigned int mstExpectedSize = nodeNumber - 1;
101 unsigned int mstSize = 0;
102 bool ratsnestLines = false;
103
104 // printf("mst nodes : %d edges : %d\n", mPoints.size(), mEdges.size () );
105 // The output
106 AirWires mst;
107
108 // Set tags for marking cycles
109 std::unordered_map<int, int> tags;
110 unsigned int tag = 0;
111
112 for (auto& node : mPoints) {
113 node.tag = tag;
114 tags[node.id] = tag++;
115 }
116
117 // Lists of nodes connected together (subtrees) to detect cycles in the
118 // graph
119 std::vector<std::list<int>> cycles(nodeNumber);
120
121 for (unsigned int i = 0; i < nodeNumber; ++i) cycles[i].push_back(i);
122
123 // Kruskal algorithm requires edges to be sorted by their weight
124 std::sort(mEdges.begin(), mEdges.end(),
125 [](const delaunay::Edge<qreal>& a, const delaunay::Edge<qreal>& b) {
126 return a.weight > b.weight;
127 });
128
129 while (mstSize < mstExpectedSize && !mEdges.empty()) {
130 auto& dt = mEdges.back();
131
132 int srcTag = tags[dt.p1.id];
133 int trgTag = tags[dt.p2.id];
134 // printf("mstSize %d %d %f %d<>%d\n", mstSize, mstExpectedSize,
135 // dt.weight, srcTag, trgTag);
136
137 // Check if by adding this edge we are going to join two different
138 // forests
139 if (srcTag != trgTag) {
140 // Because edges are sorted by their weight, first we always process
141 // connected
142 // items (weight == 0). Once we stumble upon an edge with non-zero
143 // weight,
144 // it means that the rest of the lines are ratsnest.
145 if (!ratsnestLines && dt.weight >= 0) ratsnestLines = true;
146
147 // Update tags
148 if (ratsnestLines) {
149 for (auto it = cycles[trgTag].begin(); it != cycles[trgTag].end();
150 ++it) {
151 tags[mPoints[*it].id] = srcTag;
152 }
153
154 // Do a copy of edge, but make it RN_EDGE_MST. In contrary to
155 // RN_EDGE,
156 // RN_EDGE_MST saves both source and target node and does not
157 // require any other
158 // edges to exist for getting source/target nodes
159 // CN_EDGE newEdge ( dt.GetSourceNode(), dt.GetTargetNode(),
160 // dt.GetWeight() );
161
162 // assert( newEdge.GetSourceNode()->GetTag() !=
163 // newEdge.GetTargetNode()->GetTag() );
164 // assert(dt.p1.tag != dt.p2.tag);
165 mst.append(qMakePair(Point(dt.p1.x, dt.p1.y), Point(dt.p2.x, dt.p2.y)));
166 ++mstSize;
167 } else {
168 // for( it = cycles[trgTag].begin(), itEnd =
169 // cycles[trgTag].end(); it != itEnd; ++it )
170 // for( auto it : cycles[trgTag] )
171 for (auto it = cycles[trgTag].begin(); it != cycles[trgTag].end();
172 ++it) {
173 tags[mPoints[*it].id] = srcTag;
174 mPoints[*it].tag = srcTag;
175 }
176
177 // Processing a connection, decrease the expected size of the
178 // ratsnest MST
179 --mstExpectedSize;
180 }
181
182 // Move nodes that were marked with old tag to the list marked with
183 // the new tag
184 cycles[srcTag].splice(cycles[srcTag].end(), cycles[trgTag]);
185 }
186
187 // Remove the edge that was just processed
188 mEdges.pop_back();
189 }
190
191 return mst;
192 }
193
194 /*******************************************************************************
195 * End of File
196 ******************************************************************************/
197
198 } // namespace librepcb
199