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

pcb2gcode / pcb2gcode / 21021097492

15 Jan 2026 05:42AM UTC coverage: 70.124% (+10.1%) from 60.064%
21021097492

push

github

web-flow
Merge pull request #752 from eyal0/integration_tests_in_ci

ci: Include integration tests as part of coverage.

4430 of 7541 branches covered (58.75%)

Branch coverage included in aggregate %.

3710 of 4067 relevant lines covered (91.22%)

15236187.44 hits per line

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

64.34
/eulerian_paths.cpp
1
#include "geometry_int.hpp"
2
#include "bg_operators.hpp"
3
#include <vector>
4
#include <utility>
5
#include <unordered_set>
6

7
#include "segmentize.hpp"
8
#include "eulerian_paths.hpp"
9

10
namespace eulerian_paths {
11

12
using std::vector;
13
using std::pair;
14

15
template<typename p_t>
16
struct identity {
17
  typedef p_t type;
18
};
19

20
enum struct Side : bool {
21
  front,
22
  back,
23
};
24

25
static inline Side operator!(const Side& s) {
26
  switch(s) {
959,049!
27
   case Side::front: return Side::back;
28
   case Side::back: return Side::front;
3,938,014✔
29
  }
30
}
31

32
static inline Side operator^(const Side& s, bool flip) {
33
  if (flip) {
2,068,846!
34
    return !s;
35
  } else {
36
    return s;
1,109,797✔
37
  }
38
}
39

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

52
template <typename point_t, typename linestring_t>
53
class path_manager;
54

55
class path_and_direction {
56
 public:
57
  template <typename point_t, typename linestring_t> friend class path_manager;
58
  friend struct std::hash<path_and_direction>;
59
  path_and_direction() = default;
60
  path_and_direction(size_t path_index, Side side) : path_index(path_index), side(side) {}
2,217,395!
61
  path_and_direction operator!() const {
62
    return path_and_direction(path_index, !side);
6,394,996✔
63
  }
64
  bool operator==(const path_and_direction& other) const {
65
    return path_index == other.path_index && side == other.side;
392,319!
66
  }
67
 private:
68
  size_t path_index;
69
  Side side;
70
};
71

72
} // namespace eulerian_paths
73

74
// Hash specialization for path_and_direction
75
namespace std {
76
template <>
77
struct hash<eulerian_paths::path_and_direction> {
78
  size_t operator()(const eulerian_paths::path_and_direction& p) const noexcept {
79
    return hash<std::pair<size_t, eulerian_paths::Side>>()(std::make_pair(p.path_index, p.side));
14,922,853!
80
  }
81
};
82
} // namespace std
83

84
namespace eulerian_paths {
85

86
template <typename linestring_t>
87
class linestring_iterator {
2,147,483,647!
88
 public:
89
  using value_type = typename linestring_t::value_type;
90
  using difference_type = typename linestring_t::difference_type;
91
  using pointer = typename linestring_t::const_pointer;
92
  using reference = typename linestring_t::const_reference;
93
  using iterator_category = std::bidirectional_iterator_tag;
94

95
  linestring_iterator() = default;
96
  linestring_iterator(typename linestring_t::const_iterator it) : it(it) {}
97
  linestring_iterator(typename linestring_t::const_reverse_iterator it) : it(it) {}
98

99
  reference operator*() const {
100
    return boost::apply_visitor([](auto&& iter) -> reference { return *iter; }, it);
2,217,395✔
101
  }
102

103
  linestring_iterator& operator++() {
104
    boost::apply_visitor([](auto& iter) { ++iter; }, it);
2,219,418✔
105
    return *this;
2,219,418✔
106
  }
107

108
  linestring_iterator& operator--() {
109
    boost::apply_visitor([](auto& iter) { --iter; }, it);
×
110
    return *this;
×
111
  }
112

113
  linestring_iterator operator+(difference_type n) const {
114
    return boost::apply_visitor([n](auto iter) {
2,147,483,647✔
115
      return linestring_iterator(iter + n);
116
    }, it);
2,147,483,647!
117
  }
118

119
  bool operator!=(const linestring_iterator& other) const {
120
    return it != other.it;
12,806,512✔
121
  }
122

123
 private:
124
  boost::variant<typename linestring_t::const_iterator, typename linestring_t::const_reverse_iterator> it;
125
};
126

127
/* This class holds on to all the paths and uses std::multimap internally to
128
 * make it quick to look up which paths extend from a given vertex and in which
129
 * direction. */
130
template <typename point_t, typename linestring_t>
131
class path_manager {
132
 public:
133
  // The bool indicates if the path is reversible.
134
  path_manager(const std::vector<std::pair<linestring_t, bool>>& paths) : paths(paths) {
4,412✔
135
    for (size_t index = 0; index < paths.size(); index++) {
2,221,807✔
136
      auto const& path = paths[index].first;
137
      if (path.size() < 2) {
2,217,395!
138
        // Valid path must have a start and end.
139
        continue;
×
140
      }
141
      point_t start = path.front();
2,217,395✔
142
      point_t end = path.back();
2,217,395✔
143
      all_vertices.insert(start);
2,217,395!
144
      all_vertices.insert(end);
2,217,395!
145
      auto const reversible = paths[index].second;
2,217,395✔
146
      if (reversible) {
2,217,395✔
147
        bidi_vertex_to_unvisited_path_index.emplace(start, path_and_direction(index, Side::front));
2,068,846!
148
        bidi_vertex_to_unvisited_path_index.emplace(end, path_and_direction(index, Side::back));
2,068,846✔
149
      } else {
150
        start_vertex_to_unvisited_path_index.emplace(start, path_and_direction(index, Side::front));
148,549!
151
        end_vertex_to_unvisited_path_index.emplace(end, path_and_direction(index, Side::back));
148,549✔
152
      }
153
    }
154
  }
4,412✔
155

156
  void bidi_to_directional(path_and_direction index_and_side, bool flip) {
2,068,846✔
157
    // Remove it from the bidi_vertex_to_unvisited_path_index.
158
    remove_path(index_and_side);
2,068,846✔
159
    // We won't change the original reversability aspect, just make it directional.
160
    // Add it to the directional maps, in the correct direction.
161
    index_and_side.side = index_and_side.side ^ flip;
2,068,846✔
162
    auto const start_vertex = get_front(index_and_side);
2,068,846✔
163
    auto const end_vertex = get_back(index_and_side);
2,068,846✔
164
    start_vertex_to_unvisited_path_index.emplace(start_vertex, path_and_direction(index_and_side.path_index, index_and_side.side));
2,068,846✔
165
    end_vertex_to_unvisited_path_index.emplace(end_vertex, path_and_direction(index_and_side.path_index, !index_and_side.side));
2,068,846✔
166
  }
2,068,846✔
167
  auto& get_all_vertices() const {
168
    return all_vertices;
169
  }
170
  auto& get_start_vertex_to_unvisited_path_index() const {
171
    return start_vertex_to_unvisited_path_index;
172
  }
173
  auto& get_bidi_vertex_to_unvisited_path_index() const {
174
    return bidi_vertex_to_unvisited_path_index;
175
  }
176
  auto& get_end_vertex_to_unvisited_path_index() const {
177
    return end_vertex_to_unvisited_path_index;
178
  }
179
  auto& get_path(size_t index) const {
180
    return paths[index];
181
  }
182
  void remove_path(path_and_direction const index_and_side) {
4,286,241✔
183
    auto& path = paths[index_and_side.path_index].first;
4,286,241✔
184
    // We don't know if the path is being used as a bidi path or a directional path and
185
    // we don't know if it is used in a forward or reverse direction.  Just remove it from all
186
    // three maps.
187
    for (auto& map : {&start_vertex_to_unvisited_path_index,
17,144,964✔
188
                      &end_vertex_to_unvisited_path_index,
4,286,241✔
189
                      &bidi_vertex_to_unvisited_path_index}) {
4,286,241✔
190
      for (auto& vertex : {path.front(), path.back()}) {
38,576,169✔
191
        auto range = map->equal_range(vertex);
25,717,446✔
192
        for (auto it = range.first; it != range.second; it++) {
31,265,152✔
193
          if (it->second.path_index == index_and_side.path_index) {
14,120,188✔
194
            map->erase(it);
8,572,482✔
195
            break;
196
          }
197
        }
198
      }
199
    }
200
  }
4,286,241✔
201
  auto get_point(path_and_direction const& index_and_side, int index) const {
2,147,483,647✔
202
    if (index >= 0) {
2,147,483,647✔
203
      return *(get_cbegin(index_and_side) + index);
2,147,483,647✔
204
    } else {
205
      return *(get_cend(index_and_side) + index);
2,147,483,647✔
206
    }
207
  }
208
  auto get_front(path_and_direction const& index_and_side) const {
209
    return get_point(index_and_side, 0);
1,665,741,768✔
210
  }
211
  auto get_back(path_and_direction const& index_and_side) const {
212
    return get_point(index_and_side, -1);
2,068,846✔
213
  }
214
  auto get_path_reversible(path_and_direction const& index_and_side) const {
215
    return paths[index_and_side.path_index].second;
2,068,828!
216
  }
217
  auto get_path_size(path_and_direction const& index_and_side) const {
218
    return paths[index_and_side.path_index].first.size();
4,487,257!
219
  }
220
  auto get_cbegin(path_and_direction const& index_and_side) const {
2,147,483,647✔
221
    linestring_iterator<linestring_t> ret;
222
    if (index_and_side.side == Side::front) {
2,147,483,647✔
223
      ret = paths[index_and_side.path_index].first.cbegin();
2,147,483,647✔
224
    } else {
225
      ret = paths[index_and_side.path_index].first.crbegin();
2,147,483,647✔
226
    }
227
    return ret;
2,147,483,647✔
228
  }
229
  auto get_cend(path_and_direction const& index_and_side) const {
2,147,483,647✔
230
    linestring_iterator<linestring_t> ret;
231
    if (index_and_side.side == Side::front) {
2,147,483,647✔
232
      ret = paths[index_and_side.path_index].first.cend();
2,147,483,647✔
233
    } else {
234
      ret = paths[index_and_side.path_index].first.crend();
2,147,483,647✔
235
    }
236
    return ret;
2,147,483,647✔
237
  }
238
private:
239
  std::vector<std::pair<linestring_t, bool>> const& paths;
240
  // Create a map from vertex to each path that start at that vertex.
241
  // It's a map to an index into the input paths.  The bool tells us
242
  // if the point_t is at the front or back.  For start, it will
243
  // always be true.
244
  std::multimap<point_t, path_and_direction> start_vertex_to_unvisited_path_index;
245
  // Create a map from vertex to each bidi path that may start or end
246
  // at that vertex.  It's a map to an index into the input paths.
247
  // The bool tells us if the point_t is at the front or back.  For
248
  // bidi, it could be either.
249
  std::multimap<point_t, path_and_direction> bidi_vertex_to_unvisited_path_index;
250
  // Create a map from vertex to each path that may start or end at
251
  // that vertex.  It's a map to an index into the input paths.  The
252
  // bool tells us if the point_t is at the front or back.  For end,
253
  // it will always be false.
254
  std::multimap<point_t, path_and_direction> end_vertex_to_unvisited_path_index;
255
  // Only the ones that have at least one potential edge leading out.
256
  std::set<point_t> all_vertices;
257
};
258

259
/* This finds a minimal number of eulerian paths that cover the input.  The
260
 * number of paths returned is equal to the number of vertices with odd edge
261
 * count divided by 2 if all of them are bidirectional.
262
 *
263
 * To use, first get paths.  Each path is a vector of n points that
264
 * represents n-1 line segments.  Each path is considerd
265
 * bidirectional.
266
 *
267
 * After adding paths, build the Eulerian paths.  The resulting paths
268
 * cover all segments in the input paths with the minimum number of
269
 * paths as described above.
270
 */
271
template <typename point_t, typename linestring_t>
272
class eulerian_paths {
4,412✔
273
 public:
274
  eulerian_paths(const std::vector<std::pair<linestring_t, bool>>& paths) :
4,412✔
275
    paths(paths) {}
4,412✔
276
  std::vector<std::pair<linestring_t, bool>> get() {
4,412✔
277
    /* We use Hierholzer's algorithm to find the minimum cycles.  However, some
278
     * paths are bidirectional and some are not.  To deal with this, we first convert
279
     * all paths to directional.  Then we use Hierholzer's algorithm to find the
280
     * minimum cycles.
281
     */
282
    // Convert all paths to bidirectional paths.
283
    auto vertices_to_examine = paths.get_all_vertices();
284
    while (paths.get_bidi_vertex_to_unvisited_path_index().size() > 0) {
86,530✔
285
      while (vertices_to_examine.size() > 0) {
4,260,519✔
286
        auto const vertex = *vertices_to_examine.begin();
4,178,401✔
287
        auto const out_edges = paths.get_start_vertex_to_unvisited_path_index().count(vertex);
288
        auto const in_edges = paths.get_end_vertex_to_unvisited_path_index().count(vertex);
289
        std::unordered_set<path_and_direction> seen_bidi_edges;
290
        auto bidi_edge_range = paths.get_bidi_vertex_to_unvisited_path_index().equal_range(vertex);
291
        for (auto bidi_edge_ptr = bidi_edge_range.first; bidi_edge_ptr != bidi_edge_range.second; bidi_edge_ptr++) {
10,573,397✔
292
          // Any bidi edge for which is connected to this vertex in both directions must be ignored.
293
          path_and_direction bidi_edge = bidi_edge_ptr->second;
6,394,996✔
294
          if (seen_bidi_edges.count(!bidi_edge) == 0) {
6,394,996!
295
            seen_bidi_edges.insert(bidi_edge);
296
          } else {
297
            // If we have already seen this bidi edge in the other direction,
298
            // ignore it because we know that this is a self-loop.
299
            // Also, we have to account for ignoring the first time that we saw it.
300
            seen_bidi_edges.erase(!bidi_edge);
×
301
          }
302
        }
303
        auto const bidi_edges = seen_bidi_edges.size();
304
        bool convert_to_out = in_edges >= bidi_edges + out_edges;
4,178,401✔
305
        bool convert_to_in = out_edges >= bidi_edges + in_edges;
4,178,401✔
306
        if (bidi_edges && (convert_to_out ^ convert_to_in)) {
4,178,401✔
307
          // If either convert_to_out or convert_to_in, we can convert bidi edges.
308
          // If neither are true, we can't convert.
309
          // If both are true, there are no bidi edges to convert.
310
          for (const auto& bidi_edge_and_path : seen_bidi_edges) {
3,980,979✔
311
            // We need to flip if the edge is connected in the wrong direction.
312
            bool flip = convert_to_in;
313
            paths.bidi_to_directional(bidi_edge_and_path, flip);
1,990,521!
314
            // The endpoints of the edge need to be re-examined.
315
            vertices_to_examine.insert(paths.get_front(bidi_edge_and_path));
1,990,521!
316
            vertices_to_examine.insert(paths.get_back(bidi_edge_and_path));
1,990,521!
317
          }
318
        }
319
        // Remove the vertex from the set of vertices to examine.
320
        vertices_to_examine.erase(vertex);
321
      }
322
      if (paths.get_bidi_vertex_to_unvisited_path_index().size() > 0) {
82,118✔
323
        // We've examined all vertices and converted as many bidi edges to directional edges as possible
324
        // but there are still some bidi edges left.
325
        // Pick a bidi edge that has the best score from an edge already in there.
326
        // Score is how unbalanced this would cause the graph to be and then angle.  Lowest is best.
327
        std::pair<long, double> best_score(std::numeric_limits<long>::max(), std::numeric_limits<double>::max());
328
        path_and_direction best_path_index_and_side = paths.get_bidi_vertex_to_unvisited_path_index().begin()->second;
78,325✔
329
        for (auto const& bidi_edge_and_path : paths.get_bidi_vertex_to_unvisited_path_index()) {
1,661,682,401✔
330
          auto const& bidi_edge = bidi_edge_and_path.second;
1,661,604,076✔
331
          auto const out_edges_at_end = paths.get_start_vertex_to_unvisited_path_index().count(paths.get_back(bidi_edge));
1,661,604,076✔
332
          auto const in_edges_at_end = paths.get_end_vertex_to_unvisited_path_index().count(paths.get_back(bidi_edge));
1,661,604,076✔
333
          auto const in_edges_at_start = paths.get_end_vertex_to_unvisited_path_index().count(paths.get_front(bidi_edge));
1,661,604,076✔
334
          auto const out_edges_at_start = paths.get_start_vertex_to_unvisited_path_index().count(paths.get_front(bidi_edge));
1,661,604,076✔
335
          // Find everything that starts at the end of the bidi_edge.  We aim to keep the number of out edges equal to the number of in edges
336
          // at each vertex.
337
          auto const start_options = paths.get_start_vertex_to_unvisited_path_index().equal_range(paths.get_back(bidi_edge));
1,661,604,076✔
338
          for (auto option = start_options.first; option != start_options.second; option++) {
1,685,124,323✔
339
            auto const& option_edge = option->second;
23,520,247✔
340
            auto const &p0 = paths.get_point(bidi_edge, -2);
23,520,247✔
341
            auto const &p1 = paths.get_point(option_edge, 0);
23,520,247✔
342
            auto const &p2 = paths.get_point(option_edge, 1);
23,520,247✔
343
            auto const cosine_of_angle = get_cosine_of_angle<point_t>(p0, p1, p2);
344
            auto const imbalance = std::abs(static_cast<long>(out_edges_at_end) - static_cast<long>(in_edges_at_end + 1)) +
23,520,247✔
345
                                   std::abs(static_cast<long>(out_edges_at_start + 1) - static_cast<long>(in_edges_at_start));
23,520,247✔
346
            auto const score = std::make_pair(imbalance, cosine_of_angle);
347
            // Lowest is best.
348
            if (score < best_score) {
349
              best_score = score;
350
              best_path_index_and_side = bidi_edge;
169,049✔
351
            }
352
          }
353
          // Find everything that ends at the start of bidi_edge_path.
354
          auto const end_options = paths.get_end_vertex_to_unvisited_path_index().equal_range(paths.get_front(bidi_edge));
1,661,604,076✔
355
          for (auto option = end_options.first; option != end_options.second; option++) {
1,685,118,449✔
356
            auto const& option_edge = option->second;
23,514,373✔
357
            auto const &p0 = paths.get_point(option_edge, 1); // The point one away from vertex.
23,514,373✔
358
            auto const &p1 = paths.get_point(option_edge, 0); // The point at vertex.
23,514,373✔
359
            auto const &p2 = paths.get_point(bidi_edge, 1);
23,514,373✔
360
            auto const cosine_of_angle = get_cosine_of_angle<point_t>(p0, p1, p2);
361
            auto const imbalance = std::abs(static_cast<long>(out_edges_at_end) - static_cast<long>(in_edges_at_end + 1)) +
23,514,373✔
362
                                   std::abs(static_cast<long>(out_edges_at_start + 1) - static_cast<long>(in_edges_at_start));
23,514,373✔
363
            auto const score = std::make_pair(imbalance, cosine_of_angle);
364
            // Lowest is best.
365
            if (score < best_score) {
366
              best_score = score;
367
              best_path_index_and_side = bidi_edge;
190,756✔
368
            }
369
          }
370
        }
371
        paths.bidi_to_directional(best_path_index_and_side, false);
78,325!
372
        // The endpoints of the bidi edge need to be re-examined.
373
        vertices_to_examine.insert(paths.get_front(best_path_index_and_side));
78,325!
374
        vertices_to_examine.insert(paths.get_back(best_path_index_and_side));
78,325!
375
      }
376
    }
377

378
    // All edges are now directional.
379
    std::vector<std::pair<linestring_t, bool>> euler_paths;
380
    for (const auto& vertex : paths.get_all_vertices()) {
2,159,346✔
381
      while (must_start(vertex)) {
2,161,265✔
382
        // Make a path starting from vertex with odd count.
383
        std::pair<linestring_t, bool> new_path({vertex}, true);
12,662!
384
        while (insert_one_path(&new_path, new_path.first.size()-1) > 0) {
627,151!
385
          // Keep going.
386
        }
387
        euler_paths.push_back(new_path);
6,331!
388
      }
389
    }
390
    // All vertices have the number of out edges less than or equal to the number of in edges.
391
    // We know that the total number of out edges is equal to the total number of in edges.
392
    // If there existed a vertex with more in edges than out edges, that would mean that all the other
393
    // vertices would together have more out edges than in edges, in total.  But that would mean that there must
394
    // be some other vertex with more out edges than in edges, which we already said is not true.  So it
395
    // must be the case that all vertices have the same number of out edges as in edges.  Which means that only
396
    // loops remain.  So now we can stitch.
397
    for (auto& euler_path : euler_paths) {
10,743✔
398
      stitch_loops(&euler_path);
6,331!
399
    }
400

401
    // Anything remaining is loops on islands.  Make all those paths, too.
402
    for (const auto& vertex : paths.get_all_vertices()) {
2,159,346✔
403
      std::pair<linestring_t, bool> new_path({vertex}, true);
4,309,868!
404
      while (insert_one_path(&new_path, new_path.first.size()-1) > 0) {
3,306,882!
405
        // Keep going.
406
      }
407
      if (new_path.first.size() > 1) {
2,154,934✔
408
        // We can stitch right now because all vertices already have even number
409
        // of edges.
410
        stitch_loops(&new_path);
5,281!
411
        euler_paths.push_back(new_path);
5,281!
412
      }
413
    }
414

415
    return euler_paths;
4,412✔
416
  }
417

418
 private:
419
  bool must_start(const point_t& vertex) const {
2,161,265✔
420
    // A vertex must be a starting point if there are more out edges than in
421
    // edges, even after using the bidi edges.
422
    auto out_edges = paths.get_start_vertex_to_unvisited_path_index().count(vertex);
423
    auto in_edges = paths.get_end_vertex_to_unvisited_path_index().count(vertex);
424
    return out_edges > in_edges;
2,161,265✔
425
  }
426

427
  template <typename p_t>
428
  double get_cosine_of_angle(const point_t& p0, const point_t& p1, const point_t& p2, identity<p_t>) {
51,521,877✔
429
    auto delta_x10 = p0.x() - p1.x();
51,521,877✔
430
    auto delta_y10 = p0.y() - p1.y();
51,521,877✔
431
    auto delta_x12 = p2.x() - p1.x();
51,521,877✔
432
    auto delta_y12 = p2.y() - p1.y();
51,521,877✔
433
    auto length_product = sqrt((delta_x10*delta_x10 + delta_y10*delta_y10)) * sqrt((delta_x12*delta_x12 + delta_y12*delta_y12));
51,521,877✔
434
    auto dot_product = (delta_x10*delta_x12) + (delta_y10*delta_y12);
51,521,877✔
435
    return dot_product/length_product;
51,521,877✔
436
  }
437

438
  double get_cosine_of_angle(const int& p0, const int& p1, const int& p2, identity<int>) {
×
439
    double ret;
440
    if (p0 == p1 || p1 == p2) {
×
441
      ret = 0;  // Undefined.
442
    } else if (p0 < p1 && p1 < p2) {
×
443
      ret = -1; // Straight line.
444
    } else if (p0 > p1 && p1 > p2) {
×
445
      ret = -1; // Straight line.
446
    } else {
447
      ret = 1; // Angle 0
448
    }
449
    return ret;
×
450
  }
451

452
  template <typename p_t>
453
  double get_cosine_of_angle(const p_t& p0, const p_t& p1, const p_t& p2) {
454
    return get_cosine_of_angle(p0, p1, p2, identity<p_t>());
51,521,877!
455
  }
456

457
  // Higher score is better.
458
  template <typename p_t>
459
  double path_score(const linestring_t& path_so_far,
4,511,612✔
460
                    const size_t where_to_start,
461
                    const std::pair<point_t, path_and_direction>& option,
462
                    identity<p_t>) {
463
    if (where_to_start < 1 || paths.get_path_size(option.second) < 2) {
4,511,612!
464
      // Doesn't matter, pick any.
465
      return 0;
466
    }
467
    auto p0 = path_so_far[where_to_start-1];
4,487,257✔
468
    auto p1 = path_so_far[where_to_start];
4,487,257✔
469
    // If the back of the path is what is connected to this vertex then we want to attach in reverse.
470
    auto p2 = paths.get_point(option.second, 1);
4,487,257✔
471

472
    // cos(theta) = (a dot b)/(|a|*|b|)
473
    // We don't need to take the cosine because it is decreasing over
474
    // the range of theta that we care about, so they are comparable.
475
    // Lowest is best.
476
    return get_cosine_of_angle(p0, p1, p2);
4,487,257✔
477
  }
478

479
  double path_score(const linestring_t&,
480
                    const size_t,
481
                    const std::pair<point_t, path_and_direction>&,
482
                    identity<int>) {
483
    return 0;
484
  }
485

486
  template <typename p_t>
487
  double path_score(const linestring_t& path_so_far,
488
                    const size_t where_to_start,
489
                    const std::pair<point_t, path_and_direction>& option) {
490
    return path_score(path_so_far, where_to_start, option, identity<p_t>());
4,511,612✔
491
  }
492

493
  // Pick the best path to continue on given the path_so_far and a
494
  // range of options.  The range must have at least one element in
495
  // it.
496
  typename std::multimap<point_t, path_and_direction>::const_iterator select_path(
2,217,395✔
497
      const linestring_t& path_so_far,
498
      const size_t where_to_start,
499
      const std::pair<typename std::multimap<point_t, path_and_direction>::const_iterator,
500
                      typename std::multimap<point_t, path_and_direction>::const_iterator>& options) {
501
    auto best = options.first;
2,217,395✔
502
    double best_score = path_score<point_t>(path_so_far, where_to_start, *best);
2,217,395✔
503
    for (auto current = options.first; current != options.second; current++) {
4,511,612✔
504
      double current_score = path_score<point_t>(path_so_far, where_to_start, *current);
2,294,217✔
505
      if (current_score < best_score) {
2,294,217✔
506
        best = current;
507
        best_score = current_score;
508
      }
509
    }
510
    return best;
2,217,395✔
511
  }
512

513
  // Given a point, make a path from that point as long as possible
514
  // until a dead end.  Assume that point itself is already in the
515
  // list.  Return the number of elements inserted.  Only call this
516
  // when there are no bidi edges.
517
  size_t insert_one_path(std::pair<linestring_t, bool>* new_path, const size_t where_to_start) {
6,607,667✔
518
    // Find an unvisited path that leads from point.  Prefer out edges to bidi
519
    // because we may need to save the bidi edges to later be in edges.
520
    auto vertex_and_path_range = paths.get_start_vertex_to_unvisited_path_index().equal_range(new_path->first.at(where_to_start));
6,607,667✔
521
    if (vertex_and_path_range.first == vertex_and_path_range.second) {
6,607,667✔
522
      // No more paths to follow.
523
      return 0;
524
    }
525
    auto vertex_and_path_index = *select_path(new_path->first, where_to_start, vertex_and_path_range);
2,217,395✔
526
    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));
4,434,790!
527
    paths.remove_path(vertex_and_path_index.second);
2,217,395✔
528
    // Update the reversability of the new path.
529
    new_path->second = new_path->second && paths.get_path_reversible(vertex_and_path_index.second);
2,217,547✔
530
    return paths.get_path_size(vertex_and_path_index.second) - 1;
2,217,395✔
531
  }
532

533
  // Only call this when there are no vertices with uneven edge count.  That
534
  // means that all vertices must have as many edges leading in as edges leading
535
  // out.  This can be true if a vertex has no paths at all.  This is also true
536
  // if some edges are reversable and they could poentially be used to make the
537
  // number of in edges equal to the number of out edges.  This will traverse a
538
  // path and, if it finds an unvisited edge, will make a Euler circuit there
539
  // and stitch it into the current path.  Because all paths have the same
540
  // number of in and out, the stitch can only possibly end in a loop.  This
541
  // continues until the end of the path.
542
  void stitch_loops(std::pair<linestring_t, bool> *euler_path) {
11,612✔
543
    // Use a counter and not a pointer because the list will grow and pointers
544
    // may be invalidated.
545
    for (size_t i = 0; i < euler_path->first.size(); i++) {
2,240,619✔
546
      // Make a path from here, as long as possible.  Because we only have loops left in the
547
      // paths, the path will end where it started.
548
      auto insert_at = i;
549
      do {
550
        auto const insert_length = insert_one_path(euler_path, insert_at);
2,673,634✔
551
        if (insert_length == 0) {
2,673,634✔
552
          break;
553
        }
554
        // Keep going.  It's sure to end where we started because it is a loop.
555
        insert_at += insert_length;
444,627✔
556
      } while (true);
557
    }
558
  }
11,612✔
559
  path_manager<point_t, linestring_t> paths;
560
}; //class eulerian_paths
561

562
template <typename point_t, typename linestring_t>
563
bool check_eulerian_paths(const std::vector<std::pair<linestring_t, bool>>& before,
×
564
                          const std::vector<std::pair<linestring_t, bool>>& after) {
565
  std::unordered_multiset<std::pair<std::pair<point_t, point_t>, bool>> all_edges;
566
  // Cheeck that each edge in the input is also in the output.
567
  // Also check that the number of output paths is reasonable.
568
  // At worst, nothing connected and the number of lines and rings is unchanged.
569
  size_t num_lines_before = 0;
570
  size_t num_rings_before = 0;
571
  for (auto const& linestring : before) {
×
572
    auto const reversible = linestring.second;
×
573
    auto const path = linestring.first;
×
574
    if (path.size() < 2) {
×
575
      continue;
576
    }
577
    if (path.front() == path.back()) {
×
578
      num_rings_before++;
×
579
    } else {
580
      num_lines_before++;
×
581
    }
582
    for (size_t i = 0; i < path.size()-1; i++) {
×
583
      auto const p0 = path[i];
×
584
      auto const p1 = path[i+1];
×
585
      auto const edge = std::make_pair(p0, p1);
586
      all_edges.insert({edge, reversible});
×
587
    }
588
  }
589
  size_t num_lines_after = 0;
590
  size_t num_rings_after = 0;
591
  for (auto const& linestring : after) {
×
592
    auto const path = linestring.first;
×
593
    if (path.front() == path.back()) {
×
594
      num_rings_after++;
×
595
    } else {
596
      num_lines_after++;
×
597
    }
598
    for (size_t i = 0; i < path.size()-1; i++) {
×
599
      auto const p0 = path[i];
×
600
      auto const p1 = path[i+1];
×
601
      auto const edge = std::make_pair(p0, p1);
602
      auto const reversed_edge = std::make_pair(p1, p0);
603
      // 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
604
      // flexible for searching later.
605
      if (all_edges.find({edge, false}) != all_edges.end()) {
×
606
        all_edges.erase(all_edges.find({edge, false}));
×
607
      } else if (all_edges.find({edge, true}) != all_edges.end()) {
×
608
        all_edges.erase(all_edges.find({edge, true}));
×
609
      } else if (all_edges.find({reversed_edge, true}) != all_edges.end()) {
×
610
        all_edges.erase(all_edges.find({reversed_edge, true}));
×
611
      } else {
612
        return false;
613
      }
614
    }
615
  }
616
  if (all_edges.size() > 0) {
×
617
    return false;
618
  }
619
  auto const num_elements_after = num_lines_after + num_rings_after;
×
620
  auto const num_elements_before = num_lines_before + num_rings_before;
×
621
  // 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
622
  // lines.  So if we have fewer rings in the result, we should have fewer elements over all.
623
  if (num_elements_after > num_elements_before || // We should not see the number of paths increase.
×
624
       // If we didn't decrease the number of elements then we should at least not see fewer rings.
625
      (num_elements_after == num_elements_before && num_rings_after < num_rings_before)) {
×
626
    return false;
×
627
  }
628
  return true;
629
}
630

631
template
632
bool check_eulerian_paths<point_type_fp, linestring_type_fp>(
633
    const std::vector<std::pair<linestring_type_fp, bool>>& before,
634
    const std::vector<std::pair<linestring_type_fp, bool>>& after);
635

636
template
637
bool check_eulerian_paths<point_type, linestring_type>(
638
    const std::vector<std::pair<linestring_type, bool>>& before,
639
    const std::vector<std::pair<linestring_type, bool>>& after);
640

641
template
642
bool check_eulerian_paths<int, std::vector<int>>(
643
    const std::vector<std::pair<std::vector<int>, bool>>& before,
644
    const std::vector<std::pair<std::vector<int>, bool>>& after);
645

646
// Returns a minimal number of toolpaths that include all the milling in the
647
// oroginal toolpaths.  Each path is traversed once.  Each path has a bool
648
// indicating if the path is reversible.
649
template <typename point_t, typename linestring_t>
650
std::vector<std::pair<linestring_t, bool>> get_eulerian_paths(const std::vector<std::pair<linestring_t, bool>>& paths) {
4,412✔
651
  auto const ret = eulerian_paths<point_t, linestring_t>(
4,412!
652
      paths).get();
653
  assert((check_eulerian_paths<point_t, linestring_t>(paths, ret)));
654
  return ret;
4,412✔
655
}
656

657
template
658
std::vector<std::pair<std::vector<int>, bool>> get_eulerian_paths<int, std::vector<int>>(
659
    const std::vector<std::pair<std::vector<int>, bool>>& paths);
660

661
template
662
std::vector<std::pair<linestring_type_fp, bool>> get_eulerian_paths<point_type_fp, linestring_type_fp>(
663
    const std::vector<std::pair<linestring_type_fp, bool>>& paths);
664

665
template
666
std::vector<std::pair<linestring_type, bool>> get_eulerian_paths<point_type, linestring_type>(
667
    const std::vector<std::pair<linestring_type, bool>>& paths);
668

669
// This calls segmentize and then get_eulerian_paths.  If unique is
670
// true, remove repeated segments.
671
multi_linestring_type_fp make_eulerian_paths(const multi_linestring_type_fp& paths, bool reversible, bool unique) {
4,212✔
672
  vector<pair<linestring_type_fp, bool>> path_to_simplify;
4,212✔
673
  for (const auto& ls : paths) {
227,416✔
674
    path_to_simplify.push_back(std::make_pair(ls, reversible));
223,204✔
675
  }
676
  path_to_simplify = segmentize::segmentize_paths(path_to_simplify);
8,424!
677
  if (unique) {
4,212✔
678
    path_to_simplify = segmentize::unique(path_to_simplify);
978!
679
  }
680
  auto eulerian_paths = get_eulerian_paths<
681
      point_type_fp,
682
      linestring_type_fp>(path_to_simplify);
8,424!
683
  multi_linestring_type_fp ret;
684
  for (auto& eulerian_path : eulerian_paths) {
13,146✔
685
    ret.push_back(eulerian_path.first);
8,934!
686
  }
687
  return ret;
4,212✔
688
}
689

690
} // 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