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