• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pcb2gcode / pcb2gcode / 23501427562

24 Mar 2026 04:47PM UTC coverage: 91.208% (-0.007%) from 91.215%
23501427562

push

github

web-flow
Merge pull request #861 from eyal0/test_labels

test: Add unit_test label to unit tests.

7600 of 8684 branches covered (87.52%)

Branch coverage included in aggregate %.

6643 of 6932 relevant lines covered (95.83%)

42820508.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.6
/src/eulerian_paths.cpp
1
#include "geometry_int.hpp"
2
#include "bg_operators.hpp"
3
#include <vector>
4
#include <utility>
5
#include <unordered_set>
6
#include <unordered_map>
7
#include <cmath>
8

9
#include "segmentize.hpp"
10
#include "eulerian_paths.hpp"
11
#include "options.hpp"
12

13
namespace eulerian_paths {
14

15
using std::vector;
16
using std::pair;
17

18
template<typename p_t>
19
struct identity {
20
  typedef p_t type;
21
};
22

23
enum struct Side : bool {
24
  front,
25
  back,
26
};
27

28
static inline Side operator!(const Side& s) {
357,836,380✔
29
  switch(s) {
357,836,380✔
30
   case Side::front: return Side::back;
185,948,079✔
31
   case Side::back: return Side::front;
162,102,302✔
32
  }
339,944,504✔
33
}
339,944,504✔
34

35
static inline Side operator^(const Side& s, bool flip) {
80,097,444✔
36
  if (flip) {
80,097,444✔
37
    return !s;
30,569,000!
38
  } else {
47,052,018✔
39
    return s;
49,528,444✔
40
  }
47,052,018✔
41
}
76,092,568✔
42

43
static inline std::ostream& operator<<(std::ostream& out, const Side& s) {
44
  switch(s) {
45
   case Side::front:
46
     out << "front";
47
     break;
48
   case Side::back:
49
     out << "back";
50
     break;
51
  }
52
  return out;
53
}
54

55
template <typename point_t, typename linestring_t>
56
class path_manager;
57

58
class path_and_direction {
59
 public:
60
  template <typename point_t, typename linestring_t> friend class path_manager;
61
  friend struct std::hash<path_and_direction>;
62
  path_and_direction() = default;
63
  path_and_direction(size_t path_index, Side side) : path_index(path_index), side(side) {}
579,579,789✔
64
  path_and_direction operator!() const {
247,171,028✔
65
    return path_and_direction(path_index, !side);
259,529,578✔
66
  }
234,812,450✔
67
  bool operator==(const path_and_direction& other) const {
702,624,530✔
68
    return path_index == other.path_index && side == other.side;
702,624,530!
69
  }
667,492,952✔
70
  bool operator<(const path_and_direction& other) const {
4,630,123,296✔
71
    return std::tie(path_index, side) < std::tie(other.path_index, other.side);
4,630,123,296✔
72
  }
4,294,967,294✔
73
  friend std::ostream& operator<<(std::ostream& out, const path_and_direction& p) {
74
    out << "path_index: " << p.path_index << ", side: " << p.side;
75
    return out;
76
  }
77

78
 private:
79
  size_t path_index;
80
  Side side;
81
};
82

83
} // namespace eulerian_paths
84

85
// Hash specialization for path_and_direction
86
namespace std {
87
template <>
88
struct hash<eulerian_paths::path_and_direction> {
89
  size_t operator()(const eulerian_paths::path_and_direction& p) const noexcept {
1,706,310,619✔
90
    return hash<std::pair<size_t, eulerian_paths::Side>>()(std::make_pair(p.path_index, p.side));
1,669,141,911✔
91
  }
1,620,992,809✔
92
};
93
} // namespace std
94

95
namespace eulerian_paths {
96

97
template <typename linestring_t>
98
class linestring_iterator {
234,077,036!
99
 public:
100
  using value_type = typename linestring_t::value_type;
101
  using difference_type = typename linestring_t::difference_type;
102
  using pointer = typename linestring_t::const_pointer;
103
  using reference = typename linestring_t::const_reference;
104
  using iterator_category = std::bidirectional_iterator_tag;
105

106
  linestring_iterator() = default;
2,364,708,629✔
107
  linestring_iterator(typename linestring_t::const_iterator it) : it(it) {}
2,443,527,392✔
108
  linestring_iterator(typename linestring_t::const_reverse_iterator it) : it(it) {}
2,085,905,672✔
109

110
  reference operator*() const {
2,278,646,834✔
111
    return boost::apply_visitor([](auto&& iter) -> reference { return *iter; }, it);
2,169,047,427✔
112
  }
2,164,712,066✔
113

114
  linestring_iterator& operator++() {
172,222,417✔
115
    boost::apply_visitor([](auto& iter) { ++iter; }, it);
172,222,930✔
116
    return *this;
167,916,981✔
117
  }
163,611,261✔
118

119
  linestring_iterator& operator--() {
×
120
    boost::apply_visitor([](auto& iter) { --iter; }, it);
×
121
    return *this;
×
122
  }
123

124
  linestring_iterator operator+(difference_type n) const {
2,278,648,423✔
125
    return boost::apply_visitor([n](auto iter) {
2,506,545,305✔
126
      return linestring_iterator(iter + n);
2,278,675,821✔
127
    }, it);
2,278,675,821✔
128
  }
2,164,713,681✔
129

130
  bool operator!=(const linestring_iterator& other) const {
499,139,471✔
131
    return it != other.it;
499,139,471!
132
  }
474,182,069✔
133

134
 private:
135
  boost::variant<typename linestring_t::const_iterator, typename linestring_t::const_reverse_iterator> it;
136
};
137

138
template <typename point_t>
139
double get_cosine_of_angle_impl(const point_t& p0, const point_t& p1, const point_t& p2) {
241,404,172✔
140
  auto delta_x10 = p0.x() - p1.x();
241,404,172✔
141
  auto delta_y10 = p0.y() - p1.y();
241,404,172✔
142
  auto delta_x12 = p2.x() - p1.x();
241,404,172✔
143
  auto delta_y12 = p2.y() - p1.y();
241,404,172✔
144
  auto length_product = std::sqrt((delta_x10*delta_x10 + delta_y10*delta_y10)) * std::sqrt((delta_x12*delta_x12 + delta_y12*delta_y12));
241,404,172✔
145
  auto dot_product = (delta_x10*delta_x12) + (delta_y10*delta_y12);
241,404,172✔
146
  return dot_product/length_product;
241,404,172✔
147
}
229,333,838✔
148

149
double get_cosine_of_angle_impl(const int& p0, const int& p1, const int& p2) {
5,240✔
150
  double ret;
5,240✔
151
  if (p0 == p1 || p1 == p2) {
5,240✔
152
    ret = 0;  // Undefined.
456✔
153
  } else if (p0 < p1 && p1 < p2) {
4,760✔
154
    ret = -1; // Straight line.
1,444✔
155
  } else if (p0 > p1 && p1 > p2) {
3,240✔
156
    ret = -1; // Straight line.
760✔
157
  } else {
2,318✔
158
    ret = 1; // Angle 0
2,580✔
159
  }
2,318✔
160
  return ret;
5,240✔
161
}
4,978✔
162

163
template <typename point_t>
164
double get_cosine_of_angle(const point_t& p0, const point_t& p1, const point_t& p2) {
241,409,621✔
165
  return get_cosine_of_angle_impl(p0, p1, p2);
241,409,621✔
166
}
229,339,025✔
167

168
/* This class holds on to all the paths and uses std::multimap internally to
169
 * make it quick to look up which paths extend from a given vertex and in which
170
 * direction. */
171
template <typename point_t, typename linestring_t>
172
class path_manager {
173
 public:
174
  // The bool indicates if the path is reversible.
175
  path_manager(const std::vector<std::pair<linestring_t, bool>>& paths) : paths(paths) {
176,800✔
176
    for (size_t index = 0; index < paths.size(); index++) {
86,285,042✔
177
      auto const& path = paths[index].first;
86,108,242✔
178
      if (path.size() < 2) {
86,108,242✔
179
        // Valid path must have a start and end.
180
        continue;
80✔
181
      }
76✔
182
      point_t start = path.front();
86,108,162✔
183
      point_t end = path.back();
86,108,162✔
184
      all_vertices.insert(start);
86,108,162✔
185
      all_vertices.insert(end);
86,108,162✔
186
      auto const reversible = paths[index].second;
86,108,162✔
187
      if (reversible) {
86,108,162✔
188
        bidi_vertex_to_unvisited_path_index.emplace(start, path_and_direction(index, Side::front));
80,097,425✔
189
        bidi_vertex_to_unvisited_path_index.emplace(end, path_and_direction(index, Side::back));
80,097,425✔
190
      } else {
76,092,549✔
191
        start_vertex_to_unvisited_path_index.emplace(start, path_and_direction(index, Side::front));
6,010,737✔
192
        end_vertex_to_unvisited_path_index.emplace(end, path_and_direction(index, Side::back));
6,010,737✔
193
      }
5,710,203✔
194
    }
81,802,752✔
195
    for (auto const& bidi_edge : bidi_vertex_to_unvisited_path_index) {
160,203,671✔
196
      auto const score = compute_bidi_conversion_score(bidi_edge.second);
160,194,831✔
197
      bidi_conversion_score_cache.emplace(bidi_edge.second, score);
160,194,831✔
198
      bidi_conversion_sorted_scores.emplace(score, bidi_edge.second);
160,194,831✔
199
    }
152,185,079✔
200
  }
176,800✔
201

202
  void bidi_to_directional(path_and_direction index_and_side, bool flip) {
80,097,444✔
203
    // Remove it from the bidi_vertex_to_unvisited_path_index.
204
    remove_path(index_and_side);
80,097,444✔
205
    // We won't change the original reversability aspect, just make it directional.
206
    // Add it to the directional maps, in the correct direction.
207
    index_and_side.side = index_and_side.side ^ flip;
80,097,444✔
208
    auto const start_vertex = get_front(index_and_side);
80,097,444✔
209
    auto const end_vertex = get_back(index_and_side);
80,097,444✔
210
    start_vertex_to_unvisited_path_index.emplace(start_vertex, path_and_direction(index_and_side.path_index, index_and_side.side));
80,097,444✔
211
    end_vertex_to_unvisited_path_index.emplace(end_vertex, path_and_direction(index_and_side.path_index, !index_and_side.side));
82,103,926✔
212
    // Converting an edge from bidi to directional invalidates cached scores for all bidi edges
213
    // that start or end at the endpoints of the converted edge.
214
    auto start_range = bidi_vertex_to_unvisited_path_index.equal_range(start_vertex);
80,097,444✔
215
    for (auto it = start_range.first; it != start_range.second; ++it) {
115,792,924✔
216
      auto const old_score = bidi_conversion_score_cache.at(it->second);
35,695,480✔
217
      auto const new_score = compute_bidi_conversion_score(it->second);
35,695,480✔
218
      if (old_score != new_score) {
35,914,926!
219
        bidi_conversion_score_cache[it->second] = new_score; // Replace the old score with the new one.
4,388,920✔
220
        bidi_conversion_sorted_scores.erase({old_score, it->second}); // Remove the old score from the sorted scores.
4,388,920✔
221
        bidi_conversion_sorted_scores.emplace(new_score, it->second); // Add the new score to the sorted scores.
4,388,920✔
222
      }
4,169,474✔
223
    }
33,910,706✔
224
    auto end_range = bidi_vertex_to_unvisited_path_index.equal_range(end_vertex);
80,097,444✔
225
    for (auto it = end_range.first; it != end_range.second; ++it) {
133,636,566✔
226
      auto const old_score = bidi_conversion_score_cache.at(it->second);
53,539,122✔
227
      auto const new_score = compute_bidi_conversion_score(it->second);
53,539,122✔
228
      if (old_score != new_score) {
56,212,688!
229
        bidi_conversion_score_cache[it->second] = new_score; // Replace the old score with the new one.
53,471,320✔
230
        bidi_conversion_sorted_scores.erase({old_score, it->second}); // Remove the old score from the sorted scores.
53,471,320✔
231
        bidi_conversion_sorted_scores.emplace(new_score, it->second); // Add the new score to the sorted scores.
53,471,320✔
232
      }
50,797,754✔
233
    }
50,862,164✔
234
  }
80,097,444✔
235
  auto& get_all_vertices() const {
503,880✔
236
    return all_vertices;
530,400✔
237
  }
503,880✔
238
  auto& get_start_vertex_to_unvisited_path_index() const {
477,119,165✔
239
    return start_vertex_to_unvisited_path_index;
502,230,855✔
240
  }
477,119,165✔
241
  auto& get_bidi_vertex_to_unvisited_path_index() const {
159,713,145✔
242
    return bidi_vertex_to_unvisited_path_index;
168,119,115✔
243
  }
159,713,145✔
244
  auto& get_end_vertex_to_unvisited_path_index() const {
233,363,396✔
245
    return end_vertex_to_unvisited_path_index;
245,645,722✔
246
  }
233,363,396✔
247
  auto& get_path(size_t index) const {
248
    return paths[index];
249
  }
250
  void remove_path(path_and_direction const index_and_side) {
166,204,979✔
251
    auto& path = paths[index_and_side.path_index].first;
166,204,979✔
252
    // We don't know if the path is being used as a bidi path or a directional path and
253
    // we don't know if it is used in a forward or reverse direction.  Just remove it from all
254
    // three maps.
255
    for (auto& map : {&start_vertex_to_unvisited_path_index,
191,135,837✔
256
                      &end_vertex_to_unvisited_path_index,
166,204,979✔
257
                      &bidi_vertex_to_unvisited_path_index}) {
481,990,337✔
258
      for (auto& vertex : {path.front(), path.back()}) {
1,022,140,573✔
259
        auto vertex_range = map->equal_range(vertex);
997,209,715✔
260
        for (auto vertex_it = vertex_range.first; vertex_it != vertex_range.second; vertex_it++) {
1,212,538,653✔
261
          if (vertex_it->second.path_index == index_and_side.path_index) {
547,736,198✔
262
            path_and_direction const to_erase = vertex_it->second; // Save value before iterator invalidation.
332,407,260✔
263
            map->erase(vertex_it);
332,407,260✔
264
            // If it happens to be a bidi edge, remove it from the cache.
265
            if (map == &bidi_vertex_to_unvisited_path_index) {
332,407,260✔
266
              auto const score = bidi_conversion_score_cache.at(to_erase);
160,194,812✔
267
              bidi_conversion_score_cache.erase(to_erase);
160,194,812✔
268
              bidi_conversion_sorted_scores.erase({score, to_erase});
160,194,812✔
269
            }
152,185,060✔
270
            break;
315,786,688✔
271
          }
315,786,688✔
272
        }
520,343,918✔
273
      }
947,347,999✔
274
    }
473,680,051✔
275
  }
166,204,979✔
276
  auto get_point(path_and_direction const& index_and_side, int index) const {
2,192,496,732✔
277
    if (index >= 0) {
2,192,496,732✔
278
      return *(get_cbegin(index_and_side) + index);
1,278,078,407✔
279
    } else {
1,214,173,093✔
280
      return *(get_cend(index_and_side) + index);
914,418,325✔
281
    }
868,694,307✔
282
  }
2,082,867,400✔
283
  auto get_front(path_and_direction const& index_and_side) const {
908,473,706✔
284
    return get_point(index_and_side, 0);
867,054,378✔
285
  }
863,049,502✔
286
  auto get_back(path_and_direction const& index_and_side) const {
908,471,198✔
287
    return get_point(index_and_side, -1);
867,051,870✔
288
  }
863,046,994✔
289
  auto get_path_reversible(path_and_direction const& index_and_side) const {
80,099,707✔
290
    return paths[index_and_side.path_index].second;
80,099,707✔
291
  }
76,094,715✔
292
  auto get_path_size(path_and_direction const& index_and_side) const {
260,413,059✔
293
    return paths[index_and_side.path_index].first.size();
260,413,059✔
294
  }
247,392,217✔
295
  auto get_cbegin(path_and_direction const& index_and_side) const {
1,364,181,306✔
296
    linestring_iterator<linestring_t> ret;
1,364,181,306✔
297
    if (index_and_side.side == Side::front) {
1,364,181,306✔
298
      ret = paths[index_and_side.path_index].first.cbegin();
728,097,557✔
299
    } else {
691,692,131✔
300
      ret = paths[index_and_side.path_index].first.crbegin();
636,083,749✔
301
    }
604,278,451✔
302
    return ret;
1,364,181,306✔
303
  }
1,295,970,582✔
304
  auto get_cend(path_and_direction const& index_and_side) const {
1,000,570,795✔
305
    linestring_iterator<linestring_t> ret;
1,000,570,795✔
306
    if (index_and_side.side == Side::front) {
1,000,570,795✔
307
      ret = paths[index_and_side.path_index].first.cend();
547,426,381✔
308
    } else {
520,054,795✔
309
      ret = paths[index_and_side.path_index].first.crend();
453,144,414✔
310
    }
430,486,572✔
311
    return ret;
1,000,570,795✔
312
  }
950,541,367✔
313

314
  // Returns the bidi edge with the lowest conversion score. Call only when there is at least one bidi edge.
315
  path_and_direction choose_best_bidi_edge_to_convert() const {
3,020,880✔
316
    return bidi_conversion_sorted_scores.begin()->second;
3,020,880✔
317
  }
2,869,836✔
318

319
private:
320
  // Score for choosing which bidi edge to convert to directional next. Lower is better.
321
  std::pair<int, double> compute_bidi_conversion_score(path_and_direction bidi_edge) const {
249,428,958✔
322
    auto const out_edges_at_end = start_vertex_to_unvisited_path_index.count(get_back(bidi_edge));
249,428,958✔
323
    auto const in_edges_at_end = end_vertex_to_unvisited_path_index.count(get_back(bidi_edge));
249,428,958✔
324
    auto const in_edges_at_start = end_vertex_to_unvisited_path_index.count(get_front(bidi_edge));
249,428,958✔
325
    auto const out_edges_at_start = start_vertex_to_unvisited_path_index.count(get_front(bidi_edge));
249,428,958✔
326
    auto const imbalance = (in_edges_at_end < out_edges_at_end ? 0 : 1) +
249,428,958✔
327
                           (out_edges_at_start < in_edges_at_start ? 0 : 1);
249,428,958✔
328
    double best_start_cosine = 1;
249,428,958✔
329
    auto start_range = start_vertex_to_unvisited_path_index.equal_range(get_back(bidi_edge));
249,428,958✔
330
    for (auto it = start_range.first; it != start_range.second; ++it) {
255,425,238✔
331
      auto const& option_edge = it->second;
5,996,280✔
332
      auto const& p0 = get_point(bidi_edge, -2);
5,996,280✔
333
      auto const& p1 = get_point(option_edge, 0);
5,996,280✔
334
      auto const& p2 = get_point(option_edge, 1);
5,996,280✔
335
      auto const cosine_of_angle = get_cosine_of_angle(p0, p1, p2);
5,996,280✔
336
      if (cosine_of_angle < best_start_cosine) {
5,996,280!
337
        best_start_cosine = cosine_of_angle;
2,800,040✔
338
      }
2,660,038✔
339
    }
5,696,466✔
340
    double best_end_cosine = 1;
249,428,958✔
341
    auto end_range = end_vertex_to_unvisited_path_index.equal_range(get_front(bidi_edge));
249,428,958✔
342
    for (auto it = end_range.first; it != end_range.second; ++it) {
310,535,939✔
343
      auto const& option_edge = it->second;
61,106,981✔
344
      auto const& p0 = get_point(option_edge, 1);
61,106,981✔
345
      auto const& p1 = get_point(option_edge, 0);
61,106,981✔
346
      auto const& p2 = get_point(bidi_edge, 1);
61,106,981✔
347
      auto const cosine_of_angle = get_cosine_of_angle(p0, p1, p2);
61,106,981✔
348
      if (cosine_of_angle < best_end_cosine) {
61,106,981✔
349
        best_end_cosine = cosine_of_angle;
57,211,981✔
350
      }
54,351,381✔
351
    }
58,051,631✔
352
    auto const result = std::make_pair(imbalance, best_start_cosine + best_end_cosine);
249,428,958✔
353
    return result;
249,428,958✔
354
  }
236,957,474✔
355

356
private:
357
  std::vector<std::pair<linestring_t, bool>> const& paths;
358
  // Create a map from vertex to each path that start at that vertex.
359
  // It's a map to an index into the input paths.  The bool tells us
360
  // if the point_t is at the front or back.  For start, it will
361
  // always be true.
362
  std::multimap<point_t, path_and_direction> start_vertex_to_unvisited_path_index;
363
  // Create a map from vertex to each bidi path that may start or end
364
  // at that vertex.  It's a map to an index into the input paths.
365
  // The bool tells us if the point_t is at the front or back.  For
366
  // bidi, it could be either.
367
  std::multimap<point_t, path_and_direction> bidi_vertex_to_unvisited_path_index;
368
  // Create a map from vertex to each path that may start or end at
369
  // that vertex.  It's a map to an index into the input paths.  The
370
  // bool tells us if the point_t is at the front or back.  For end,
371
  // it will always be false.
372
  std::multimap<point_t, path_and_direction> end_vertex_to_unvisited_path_index;
373
  // Only the ones that have at least one potential edge leading out.
374
  std::set<point_t> all_vertices;
375
  mutable std::unordered_map<path_and_direction, std::pair<int, double>> bidi_conversion_score_cache;
376
  mutable std::set<std::pair<std::pair<int, double>, path_and_direction>> bidi_conversion_sorted_scores;
377
};
378

379
/* This finds a minimal number of eulerian paths that cover the input.  The
380
 * number of paths returned is equal to the number of vertices with odd edge
381
 * count divided by 2 if all of them are bidirectional.
382
 *
383
 * To use, first get paths.  Each path is a vector of n points that
384
 * represents n-1 line segments.  Each path is considerd
385
 * bidirectional.
386
 *
387
 * After adding paths, build the Eulerian paths.  The resulting paths
388
 * cover all segments in the input paths with the minimum number of
389
 * paths as described above.
390
 */
391
template <typename point_t, typename linestring_t>
392
class eulerian_paths {
8,840✔
393
 public:
394
  eulerian_paths(const std::vector<std::pair<linestring_t, bool>>& paths) :
8,840✔
395
    paths(paths) {}
176,800✔
396
  std::vector<std::pair<linestring_t, bool>> get() {
176,800✔
397
    /* We use Hierholzer's algorithm to find the minimum cycles.  However, some
398
     * paths are bidirectional and some are not.  To deal with this, we first convert
399
     * all paths to directional.  Then we use Hierholzer's algorithm to find the
400
     * minimum cycles.
401
     */
402
    // Convert all paths to bidirectional paths.
403
    auto vertices_to_examine = paths.get_all_vertices();
176,800✔
404
    while (paths.get_bidi_vertex_to_unvisited_path_index().size() > 0) {
3,197,680✔
405
      while (vertices_to_examine.size() > 0) {
164,921,454✔
406
        auto const vertex = *vertices_to_examine.begin();
161,749,454✔
407
        auto const out_edges = paths.get_start_vertex_to_unvisited_path_index().count(vertex);
161,749,454✔
408
        auto const in_edges = paths.get_end_vertex_to_unvisited_path_index().count(vertex);
161,749,454✔
409
        std::unordered_set<path_and_direction> seen_bidi_edges;
161,749,454✔
410
        auto bidi_edge_range = paths.get_bidi_vertex_to_unvisited_path_index().equal_range(vertex);
161,749,454✔
411
        for (auto bidi_edge_ptr = bidi_edge_range.first; bidi_edge_ptr != bidi_edge_range.second; bidi_edge_ptr++) {
408,919,523✔
412
          // Any bidi edge for which is connected to this vertex in both directions must be ignored.
413
          path_and_direction bidi_edge = bidi_edge_ptr->second;
247,170,069✔
414
          if (seen_bidi_edges.count(!bidi_edge) == 0) {
259,528,667✔
415
            seen_bidi_edges.insert(bidi_edge);
247,169,993✔
416
          } else {
2,264,889,492✔
417
            // If we have already seen this bidi edge in the other direction,
418
            // ignore it because we know that this is a self-loop.
419
            // Also, we have to account for ignoring the first time that we saw it.
420
            seen_bidi_edges.erase(!bidi_edge);
2,147,483,827✔
421
          }
2,147,483,799✔
422
        }
234,811,519✔
423
        auto const bidi_edges = seen_bidi_edges.size();
161,749,454✔
424
        bool convert_to_out = in_edges >= bidi_edges + out_edges;
161,749,454✔
425
        bool convert_to_in = out_edges >= bidi_edges + in_edges;
161,749,454✔
426
        if (bidi_edges && (convert_to_out ^ convert_to_in)) {
161,749,454✔
427
          // If either convert_to_out or convert_to_in, we can convert bidi edges.
428
          // If neither are true, we can't convert.
429
          // If both are true, there are no bidi edges to convert.
430
          for (const auto& bidi_edge_and_path : seen_bidi_edges) {
80,929,988!
431
            // We need to flip if the edge is connected in the wrong direction.
432
            bool flip = convert_to_in;
77,076,602✔
433
            paths.bidi_to_directional(bidi_edge_and_path, flip);
77,076,602✔
434
            // The endpoints of the edge need to be re-examined.
435
            vertices_to_examine.insert(paths.get_front(bidi_edge_and_path));
77,076,602✔
436
            vertices_to_examine.insert(paths.get_back(bidi_edge_and_path));
77,076,602✔
437
          }
73,222,770✔
438
        }
73,214,315✔
439
        // Remove the vertex from the set of vertices to examine.
440
        vertices_to_examine.erase(vertex);
161,749,454✔
441
      }
153,661,968✔
442
      if (paths.get_bidi_vertex_to_unvisited_path_index().size() == 0) {
3,172,000✔
443
        // We've examined all vertices and converted as many bidi edges to
444
        // directional edges as possible and there are no bidi edges left.
445
        break;
143,564✔
446
      }
143,564✔
447
      // There are still some bidi edges left. Pick the one with the best conversion score.
448
      path_and_direction best_path_index_and_side = paths.choose_best_bidi_edge_to_convert();
3,020,880✔
449
      paths.bidi_to_directional(best_path_index_and_side, false);
3,020,880✔
450
      // The endpoints of the bidi edge need to be re-examined.
451
      vertices_to_examine.insert(paths.get_front(best_path_index_and_side));
3,020,880✔
452
      vertices_to_examine.insert(paths.get_back(best_path_index_and_side));
3,020,880✔
453
    }
2,869,836✔
454

455
    // All edges are now directional.
456
    std::vector<std::pair<linestring_t, bool>> euler_paths;
176,800✔
457
    for (const auto& vertex : paths.get_all_vertices()) {
83,642,865✔
458
      while (must_start(vertex)) {
83,896,705✔
459
        // Make a path starting from vertex with odd count.
460
        std::pair<linestring_t, bool> new_path({vertex}, true);
275,814✔
461
        while (insert_one_path(&new_path, new_path.first.size()-1) > 0) {
26,538,583✔
462
          // Keep going.
463
        }
24,962,105✔
464
        euler_paths.push_back(new_path);
262,680✔
465
      }
249,546✔
466
    }
79,452,319✔
467
    // All vertices have the number of out edges less than or equal to the number of in edges.
468
    // We know that the total number of out edges is equal to the total number of in edges.
469
    // If there existed a vertex with more in edges than out edges, that would mean that all the other
470
    // vertices would together have more out edges than in edges, in total.  But that would mean that there must
471
    // be some other vertex with more out edges than in edges, which we already said is not true.  So it
472
    // must be the case that all vertices have the same number of out edges as in edges.  Which means that only
473
    // loops remain.  So now we can stitch.
474
    for (auto& euler_path : euler_paths) {
271,520✔
475
      stitch_loops(&euler_path);
262,680✔
476
    }
249,546✔
477

478
    // Anything remaining is loops on islands.  Make all those paths, too.
479
    for (const auto& vertex : paths.get_all_vertices()) {
83,642,960✔
480
      std::pair<linestring_t, bool> new_path({vertex}, true);
87,815,826✔
481
      while (insert_one_path(&new_path, new_path.first.size()-1) > 0) {
128,171,667✔
482
        // Keep going.
483
      }
42,310,663✔
484
      if (new_path.first.size() > 1) {
83,634,120✔
485
        // We can stitch right now because all vertices already have even number
486
        // of edges.
487
        stitch_loops(&new_path);
210,880✔
488
        euler_paths.push_back(new_path);
210,880✔
489
      }
200,336✔
490
    }
79,452,414✔
491

492
    return euler_paths;
176,800✔
493
  }
176,800✔
494

495
 private:
496
  bool must_start(const point_t& vertex) const {
83,896,705✔
497
    // A vertex must be a starting point if there are more out edges than in
498
    // edges, even after using the bidi edges.
499
    auto out_edges = paths.get_start_vertex_to_unvisited_path_index().count(vertex);
83,896,705✔
500
    auto in_edges = paths.get_end_vertex_to_unvisited_path_index().count(vertex);
83,896,705✔
501
    return out_edges > in_edges;
83,896,705✔
502
  }
79,701,865✔
503

504
  // Higher score is better.
505
  template <typename p_t>
506
  double path_score(const linestring_t& path_so_far,
8,765,184✔
507
                    const size_t where_to_start,
508
                    const std::pair<point_t, path_and_direction>& option,
509
                    identity<p_t>) {
166,537,071✔
510
    if (where_to_start < 1 || paths.get_path_size(option.second) < 2) {
175,302,255!
511
      // Doesn't matter, pick any.
512
      return 0;
945,288✔
513
    }
945,288✔
514
    auto p0 = path_so_far[where_to_start-1];
174,307,215✔
515
    auto p1 = path_so_far[where_to_start];
174,307,215✔
516
    // If the back of the path is what is connected to this vertex then we want to attach in reverse.
517
    auto p2 = paths.get_point(option.second, 1);
174,307,215✔
518

519
    // cos(theta) = (a dot b)/(|a|*|b|)
520
    // We don't need to take the cosine because it is decreasing over
521
    // the range of theta that we care about, so they are comparable.
522
    // Lowest is best.
523
    return get_cosine_of_angle(p0, p1, p2);
174,307,215✔
524
  }
166,537,071✔
525

526
  double path_score(const linestring_t&,
527
                    const size_t,
528
                    const std::pair<point_t, path_and_direction>&,
529
                    identity<int>) {
6,460✔
530
    return 0;
6,460✔
531
  }
6,460✔
532

533
  template <typename p_t>
534
  double path_score(const linestring_t& path_so_far,
8,765,184✔
535
                    const size_t where_to_start,
536
                    const std::pair<point_t, path_and_direction>& option) {
166,543,474✔
537
    return path_score(path_so_far, where_to_start, option, identity<p_t>());
175,308,658✔
538
  }
166,543,474✔
539

540
  // Pick the best path to continue on given the path_so_far and a
541
  // range of options.  The range must have at least one element in
542
  // it.
543
  typename std::multimap<point_t, path_and_direction>::const_iterator select_path(
4,305,260✔
544
      const linestring_t& path_so_far,
545
      const size_t where_to_start,
546
      const std::pair<typename std::multimap<point_t, path_and_direction>::const_iterator,
547
                      typename std::multimap<point_t, path_and_direction>::const_iterator>& options) {
81,802,524✔
548
    auto best = options.first;
86,107,784✔
549
    double best_score = path_score<point_t>(path_so_far, where_to_start, *best);
86,107,784✔
550
    for (auto current = options.first; current != options.second; current++) {
175,309,663✔
551
      double current_score = path_score<point_t>(path_so_far, where_to_start, *current);
89,201,729✔
552
      if (current_score < best_score) {
89,201,729✔
553
        best = current;
746,920✔
554
        best_score = current_score;
746,920✔
555
      }
709,574✔
556
    }
84,741,805✔
557
    return best;
86,107,784✔
558
  }
81,802,524✔
559

560
  // Given a point, make a path from that point as long as possible
561
  // until a dead end.  Assume that point itself is already in the
562
  // list.  Return the number of elements inserted.  Only call this
563
  // when there are no bidi edges.
564
  size_t insert_one_path(std::pair<linestring_t, bool>* new_path, const size_t where_to_start) {
256,586,425✔
565
    // Find an unvisited path that leads from point.  Prefer out edges to bidi
566
    // because we may need to save the bidi edges to later be in edges.
567
    auto vertex_and_path_range = paths.get_start_vertex_to_unvisited_path_index().equal_range(new_path->first.at(where_to_start));
256,586,425✔
568
    if (vertex_and_path_range.first == vertex_and_path_range.second) {
256,586,425✔
569
      // No more paths to follow.
570
      return 0;
161,955,050✔
571
    }
161,955,050✔
572
    auto vertex_and_path_index = *select_path(new_path->first, where_to_start, vertex_and_path_range);
86,107,421✔
573
    new_path->first.insert(new_path->first.begin() + where_to_start + 1, paths.get_cbegin(vertex_and_path_index.second) + 1, paths.get_cend(vertex_and_path_index.second));
86,107,421✔
574
    paths.remove_path(vertex_and_path_index.second);
86,107,421✔
575
    // Update the reversability of the new path.
576
    new_path->second = new_path->second && paths.get_path_reversible(vertex_and_path_index.second);
86,107,421✔
577
    return paths.get_path_size(vertex_and_path_index.second) - 1;
86,107,421✔
578
  }
243,757,061✔
579

580
  // Only call this when there are no vertices with uneven edge count.  That
581
  // means that all vertices must have as many edges leading in as edges leading
582
  // out.  This can be true if a vertex has no paths at all.  This is also true
583
  // if some edges are reversable and they could poentially be used to make the
584
  // number of in edges equal to the number of out edges.  This will traverse a
585
  // path and, if it finds an unvisited edge, will make a Euler circuit there
586
  // and stitch it into the current path.  Because all paths have the same
587
  // number of in and out, the stitch can only possibly end in a loop.  This
588
  // continues until the end of the path.
589
  void stitch_loops(std::pair<linestring_t, bool> *euler_path) {
473,560✔
590
    // Use a counter and not a pointer because the list will grow and pointers
591
    // may be invalidated.
592
    for (size_t i = 0; i < euler_path->first.size(); i++) {
87,055,840✔
593
      // Make a path from here, as long as possible.  Because we only have loops left in the
594
      // paths, the path will end where it started.
595
      auto insert_at = i;
82,253,166✔
596
      do {
97,547,707✔
597
        auto const insert_length = insert_one_path(euler_path, insert_at);
101,876,821✔
598
        if (insert_length == 0) {
101,876,821✔
599
          break;
82,253,166✔
600
        }
82,253,166✔
601
        // Keep going.  It's sure to end where we started because it is a loop.
602
        insert_at += insert_length;
15,294,541✔
603
      } while (true);
15,294,541✔
604
    }
82,253,166✔
605
  }
473,560✔
606
  path_manager<point_t, linestring_t> paths;
607
}; //class eulerian_paths
608

609
template <typename point_t, typename linestring_t>
610
bool check_eulerian_paths(const std::vector<std::pair<linestring_t, bool>>& before,
8,696✔
611
                          const std::vector<std::pair<linestring_t, bool>>& after) {
165,224✔
612
  std::unordered_multiset<std::pair<std::pair<point_t, point_t>, bool>> all_edges;
173,920✔
613
  // Cheeck that each edge in the input is also in the output.
614
  // Also check that the number of output paths is reasonable.
615
  // At worst, nothing connected and the number of lines and rings is unchanged.
616
  size_t num_lines_before = 0;
173,920✔
617
  size_t num_rings_before = 0;
173,920✔
618
  for (auto const& linestring : before) {
85,940,326✔
619
    auto const reversible = linestring.second;
85,931,480✔
620
    auto const path = linestring.first;
85,931,480!
621
    if (path.size() < 2) {
85,931,480✔
622
      continue;
76✔
623
    }
76✔
624
    if (path.front() == path.back()) {
85,931,400✔
625
      num_rings_before++;
120✔
626
    } else {
81,634,716✔
627
      num_lines_before++;
85,931,280✔
628
    }
81,634,716✔
629
    for (size_t i = 0; i < path.size()-1; i++) {
171,863,000✔
630
      auto const p0 = path[i];
85,931,600✔
631
      auto const p1 = path[i+1];
85,931,600✔
632
      auto const edge = std::make_pair(p0, p1);
85,931,600✔
633
      all_edges.insert({edge, reversible});
85,931,600✔
634
    }
81,635,020✔
635
  }
81,634,830✔
636
  size_t num_lines_after = 0;
173,920✔
637
  size_t num_rings_after = 0;
173,920✔
638
  for (auto const& linestring : after) {
473,776✔
639
    auto const path = linestring.first;
465,080✔
640
    if (path.front() == path.back()) {
465,080✔
641
      num_rings_after++;
208,600✔
642
    } else {
243,656✔
643
      num_lines_after++;
256,480✔
644
    }
243,656✔
645
    for (size_t i = 0; i < path.size()-1; i++) {
86,396,680✔
646
      auto const p0 = path[i];
85,931,600✔
647
      auto const p1 = path[i+1];
85,931,600✔
648
      auto const edge = std::make_pair(p0, p1);
85,931,600✔
649
      auto const reversed_edge = std::make_pair(p1, p0);
85,931,600✔
650
      // If the edge is in all_edges twice, reversible and not, we prefer to delete the non-reversible one first because the other one can be more
651
      // flexible for searching later.
652
      if (all_edges.find({edge, false}) != all_edges.end()) {
85,931,600✔
653
        all_edges.erase(all_edges.find({edge, false}));
6,010,680✔
654
      } else if (all_edges.find({edge, true}) != all_edges.end()) {
79,920,920✔
655
        all_edges.erase(all_edges.find({edge, true}));
40,126,240✔
656
      } else if (all_edges.find({reversed_edge, true}) != all_edges.end()) {
40,109,662!
657
        all_edges.erase(all_edges.find({reversed_edge, true}));
39,794,680✔
658
      } else {
37,804,946✔
659
        return false;
×
660
      }
661
    }
81,635,020✔
662
  }
441,826✔
663
  if (all_edges.size() > 0) {
173,920✔
664
    return false;
665
  }
666
  auto const num_elements_after = num_lines_after + num_rings_after;
173,920✔
667
  auto const num_elements_before = num_lines_before + num_rings_before;
173,920✔
668
  // We expect to always do at least as well as the input.  Also, the only way to have more rings than before is to combine
669
  // lines.  So if we have fewer rings in the result, we should have fewer elements over all.
670
  if (num_elements_after > num_elements_before || // We should not see the number of paths increase.
173,920✔
671
       // If we didn't decrease the number of elements then we should at least not see fewer rings.
672
      (num_elements_after == num_elements_before && num_rings_after < num_rings_before)) {
173,920!
673
    return false;
674
  }
675
  return true;
165,224✔
676
}
173,920✔
677

678
template
679
bool check_eulerian_paths<point_type_fp, linestring_type_fp>(
680
    const std::vector<std::pair<linestring_type_fp, bool>>& before,
681
    const std::vector<std::pair<linestring_type_fp, bool>>& after);
682

683
template
684
bool check_eulerian_paths<point_type, linestring_type>(
685
    const std::vector<std::pair<linestring_type, bool>>& before,
686
    const std::vector<std::pair<linestring_type, bool>>& after);
687

688
template
689
bool check_eulerian_paths<int, std::vector<int>>(
690
    const std::vector<std::pair<std::vector<int>, bool>>& before,
691
    const std::vector<std::pair<std::vector<int>, bool>>& after);
692

693
// Returns a minimal number of toolpaths that include all the milling in the
694
// oroginal toolpaths.  Each path is traversed once.  Each path has a bool
695
// indicating if the path is reversible.
696
template <typename point_t, typename linestring_t>
697
std::vector<std::pair<linestring_t, bool>> get_eulerian_paths(const std::vector<std::pair<linestring_t, bool>>& paths) {
176,800✔
698
  auto const ret = eulerian_paths<point_t, linestring_t>(
185,640✔
699
      paths).get();
167,960✔
700
  if (options::get_vm().count("sanity-checks")) {
185,640✔
701
    if (!check_eulerian_paths<point_t, linestring_t>(paths, ret)) {
173,400!
702
      options::maybe_throw("Sanity checks failed", ERR_SANITY_CHECKS_FAILED);
×
703
    }
704
  }
164,730✔
705
  return ret;
176,800✔
706
}
167,960✔
707

708
template
709
std::vector<std::pair<std::vector<int>, bool>> get_eulerian_paths<int, std::vector<int>>(
710
    const std::vector<std::pair<std::vector<int>, bool>>& paths);
711

712
template
713
std::vector<std::pair<linestring_type_fp, bool>> get_eulerian_paths<point_type_fp, linestring_type_fp>(
714
    const std::vector<std::pair<linestring_type_fp, bool>>& paths);
715

716
template
717
std::vector<std::pair<linestring_type, bool>> get_eulerian_paths<point_type, linestring_type>(
718
    const std::vector<std::pair<linestring_type, bool>>& paths);
719

720
// This calls segmentize and then get_eulerian_paths.  If unique is
721
// true, remove repeated segments.
722
multi_linestring_type_fp make_eulerian_paths(const multi_linestring_type_fp& paths, bool reversible, bool unique) {
168,160✔
723
  vector<pair<linestring_type_fp, bool>> path_to_simplify;
168,160✔
724
  for (const auto& ls : paths) {
671,528✔
725
    path_to_simplify.push_back(std::make_pair(ls, reversible));
663,120✔
726
  }
629,964✔
727
  path_to_simplify = segmentize::segmentize_paths(path_to_simplify);
176,568✔
728
  if (unique) {
168,160✔
729
    path_to_simplify = segmentize::unique(path_to_simplify);
22,260✔
730
  }
20,140✔
731
  auto eulerian_paths = get_eulerian_paths<
168,160✔
732
      point_type_fp,
159,752✔
733
      linestring_type_fp>(path_to_simplify);
168,160✔
734
  multi_linestring_type_fp ret;
168,160✔
735
  for (auto& eulerian_path : eulerian_paths) {
372,208✔
736
    ret.push_back(eulerian_path.first);
363,800✔
737
  }
345,610✔
738
  return ret;
168,160✔
739
}
168,160✔
740

741
} // namespace eulerian_paths
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc