1 #include "board.hpp"
2 #include <unordered_map>
3 #include "util/util.hpp"
4 #include "delaunator/delaunator.hpp"
5
6 namespace horizon {
7
8 // adapted from kicad's ratsnest_data.cpp
9
10 class disjoint_set {
11
12 public:
disjoint_set(size_t size)13 disjoint_set(size_t size)
14 {
15 m_data.resize(size);
16 m_depth.resize(size, 0);
17
18 for (size_t i = 0; i < size; i++)
19 m_data[i] = i;
20 }
21
find(int aVal)22 int find(int aVal)
23 {
24 int root = aVal;
25
26 while (m_data[root] != root)
27 root = m_data[root];
28
29 // Compress the path
30 while (m_data[aVal] != aVal) {
31 auto &tmp = m_data[aVal];
32 aVal = tmp;
33 tmp = root;
34 }
35
36 return root;
37 }
38
39
unite(int aVal1,int aVal2)40 bool unite(int aVal1, int aVal2)
41 {
42 aVal1 = find(aVal1);
43 aVal2 = find(aVal2);
44
45 if (aVal1 != aVal2) {
46 if (m_depth[aVal1] < m_depth[aVal2]) {
47 m_data[aVal1] = aVal2;
48 }
49 else {
50 m_data[aVal2] = aVal1;
51
52 if (m_depth[aVal1] == m_depth[aVal2])
53 m_depth[aVal1]++;
54 }
55
56 return true;
57 }
58
59 return false;
60 }
61
62 private:
63 std::vector<int> m_data;
64 std::vector<int> m_depth;
65 };
66
67 struct Edge {
68 size_t from;
69 size_t to;
70 double weight;
71 };
72
73
kruskalMST(size_t npts,const std::vector<Edge> & aEdges)74 static std::vector<Edge> kruskalMST(size_t npts, const std::vector<Edge> &aEdges)
75 {
76 disjoint_set dset(npts);
77 std::vector<Edge> edges_out;
78 for (const auto &edge : aEdges) {
79 int u = edge.from;
80 int v = edge.to;
81 if (dset.unite(u, v)) {
82 if (edge.weight > 0)
83 edges_out.push_back(edge);
84 }
85 }
86 return edges_out;
87 }
88
points_are_linear(const delaunator::Points & points)89 static bool points_are_linear(const delaunator::Points &points)
90 {
91 if (points.size() < 3)
92 return true;
93 auto it = points.begin();
94 const auto &pp0 = *it++;
95 const Coordd p0(pp0.x(), pp0.y());
96 Coordd p1 = p0;
97 while (((p1 - p0).mag_sq() < 1e6) && it != points.end()) {
98 const auto &pp1 = *it++;
99 p1 = Coordd(pp1.x(), pp1.y());
100 }
101 if (it == points.end()) {
102 return true; // all points are coincident
103 }
104
105 const Coordd v = p1 - p0;
106 for (const auto &pp : points) {
107 const Coordd p(pp.x(), pp.y());
108 const Coordd vp = p - p0;
109 if (vp.mag_sq() > 1e6) {
110 if (v.cross(vp) != 0)
111 return false;
112 }
113 }
114 return true;
115 }
116
nextHalfedge(size_t e)117 static size_t nextHalfedge(size_t e)
118 {
119 return (e % 3 == 2) ? e - 2 : e + 1;
120 }
121
update_all_airwires()122 void Board::update_all_airwires()
123 {
124 airwires.clear();
125 std::set<UUID> nets;
126 // collect nets on board
127 for (auto &it_pkg : packages) {
128 for (auto &it_pad : it_pkg.second.package.pads) {
129 if (it_pad.second.net != nullptr)
130 nets.insert(it_pad.second.net->uuid);
131 }
132 }
133 airwires.clear();
134
135 for (const auto &net : nets) {
136 update_airwire(false, net);
137 }
138 }
139
update_airwire(bool fast,const UUID & net)140 void Board::update_airwire(bool fast, const UUID &net)
141 {
142 std::vector<Track::Connection> points_ref;
143 std::map<Track::Connection, int> connmap;
144 std::vector<double> points_for_delaunator;
145 delaunator::Points pts(points_for_delaunator);
146 std::map<Coordi, std::vector<size_t>> pts_map;
147
148 // collect possible ratsnest points
149 for (auto &it_junc : junctions) {
150 if (it_junc.second.net && it_junc.second.net->uuid == net) {
151 auto pos = it_junc.second.position;
152 points_for_delaunator.emplace_back(pos.x);
153 points_for_delaunator.emplace_back(pos.y);
154 points_ref.emplace_back(&it_junc.second);
155 pts_map[pos].push_back(pts.size() - 1);
156 }
157 }
158 for (auto &it_pkg : packages) {
159 for (auto &it_pad : it_pkg.second.package.pads) {
160 if (it_pad.second.net && it_pad.second.net->uuid == net) {
161 Track::Connection conn(&it_pkg.second, &it_pad.second);
162 auto pos = conn.get_position();
163 points_for_delaunator.emplace_back(pos.x);
164 points_for_delaunator.emplace_back(pos.y);
165 points_ref.push_back(conn);
166 pts_map[pos].push_back(pts.size() - 1);
167 }
168 }
169 }
170 for (size_t i = 0; i < points_ref.size(); i++) {
171 connmap[points_ref[i]] = i;
172 }
173
174
175 std::set<std::pair<size_t, size_t>> edges_from_board;
176 std::vector<std::pair<size_t, size_t>> zero_length_airwires;
177
178 // collect edges formed by overlapping points
179 for (const auto &[pos, idxs] : pts_map) {
180 if (idxs.size() > 1) {
181 for (size_t i = 0; i < idxs.size(); i++) {
182 for (size_t j = i + 1; j < idxs.size(); j++) {
183 const auto p1 = idxs.at(i);
184 const auto p2 = idxs.at(j);
185 if (points_ref.at(p1).get_layer().overlaps(points_ref.at(p2).get_layer())) {
186 edges_from_board.emplace(p1, p2);
187 }
188 else {
189 zero_length_airwires.emplace_back(p1, p2);
190 }
191 }
192 }
193 }
194 }
195
196 // collect edges formed by tracks
197 for (auto &it : tracks) {
198 if (it.second.net && it.second.net->uuid == net) {
199 auto la = it.second.layer;
200 auto la_from = it.second.from.get_layer();
201 auto la_to = it.second.to.get_layer();
202
203 if (la_from.overlaps(la) && la_to.overlaps(la)) { // only add connection
204 // if layers match
205 if (connmap.count(it.second.from) && connmap.count(it.second.to)) {
206 auto i_from = connmap.at(it.second.from);
207 auto i_to = connmap.at(it.second.to);
208 if (i_from > i_to)
209 std::swap(i_to, i_from);
210 edges_from_board.emplace(i_from, i_to);
211 }
212 }
213 }
214 }
215
216 if (!fast) {
217 for (const auto &it : planes) {
218 auto plane = &it.second;
219 if (plane->net->uuid == net) {
220 for (const auto &frag : plane->fragments) {
221 int last_point_id = -1;
222 int point_id = 0;
223 for (const auto &pt : pts) {
224 Coordi x(pt.x(), pt.y());
225 auto la = points_ref.at(point_id).get_layer();
226 if ((la.overlaps(plane->polygon->layer)) && frag.contains(x)) {
227 if (last_point_id >= 0) {
228 edges_from_board.emplace(last_point_id, point_id);
229 }
230 last_point_id = point_id;
231 }
232 point_id++;
233 }
234 }
235 }
236 }
237 }
238
239 std::set<std::pair<size_t, size_t>> edges_from_tri;
240 if (pts.size() > 3) {
241 const bool is_linear = points_are_linear(pts);
242 if (is_linear) {
243 std::vector<std::pair<Coordd, size_t>> pts_sorted;
244 pts_sorted.reserve(pts.size());
245 size_t tag = 0;
246 for (const auto &it : pts) {
247 pts_sorted.emplace_back(Coordd(it.x(), it.y()), tag);
248 tag++;
249 }
250 std::sort(pts_sorted.begin(), pts_sorted.end(),
251 [](const auto &a, const auto &b) { return a.first.x + a.first.y < b.first.x + b.first.y; });
252 for (size_t i = 1; i < pts_sorted.size(); i++) {
253 edges_from_tri.emplace(pts_sorted[i - 1].second, pts_sorted[i].second);
254 }
255 }
256 else {
257 delaunator::Delaunator delaunator(points_for_delaunator);
258 for (size_t e = 0; e < delaunator.triangles.size(); e++) {
259 if (delaunator.halfedges[e] == delaunator::INVALID_INDEX // hull edge
260 || e > delaunator.halfedges[e]) {
261 auto p = delaunator.triangles[e];
262 auto q = delaunator.triangles[nextHalfedge(e)];
263 if (p > q)
264 std::swap(p, q);
265 edges_from_tri.emplace(p, q);
266 }
267 }
268 }
269 }
270 else if (pts.size() == 3) {
271 edges_from_tri.emplace(0, 1);
272 edges_from_tri.emplace(1, 2);
273 edges_from_tri.emplace(0, 2);
274 }
275 else if (pts.size() == 2) {
276 edges_from_tri.emplace(0, 1);
277 }
278
279 std::set<std::pair<int, int>> edges;
280
281 std::vector<Edge> edges_for_mst;
282 edges_for_mst.reserve(edges_from_board.size() + edges_from_tri.size());
283
284 // build list for MST algorithm, start with edges defined by board
285 for (const auto &[a, b] : edges_from_board) {
286 edges.emplace(a, b);
287 edges_for_mst.push_back({a, b, -1});
288 }
289
290 // now add edges from delaunay triangulation
291 for (const auto &[a, b] : edges_from_tri) {
292 if (edges.emplace(a, b).second) {
293 double dist = sqrt(delaunator::Point::dist2(pts[a], pts[b]));
294 edges_for_mst.push_back({a, b, dist});
295 }
296 }
297
298 // add zero length airwires
299 for (const auto &[a, b] : zero_length_airwires) {
300 edges_for_mst.push_back({a, b, 1e9});
301 }
302
303 std::stable_sort(edges_for_mst.begin(), edges_for_mst.end(),
304 [](const auto &a, const auto &b) { return a.weight < b.weight; });
305
306 // run MST algorithm for removing superflous edges
307 auto edges_from_mst = kruskalMST(pts.size(), edges_for_mst);
308
309
310 if (edges_from_mst.size()) {
311 auto &li = airwires[net];
312 for (const auto &e : edges_from_mst) {
313 li.emplace_back(points_ref.at(e.from), points_ref.at(e.to));
314 }
315 }
316 }
317
update_airwires(bool fast,const std::set<UUID> & nets_only)318 void Board::update_airwires(bool fast, const std::set<UUID> &nets_only)
319 {
320 for (const auto &net : nets_only) {
321 airwires.erase(net);
322 update_airwire(fast, net);
323 }
324 }
325 } // namespace horizon
326