• 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

66.51
/gerberimporter.cpp
1
/*
2
 * This file is part of pcb2gcode.
3
 * 
4
 * Copyright (C) 2009, 2010 Patrick Birnzain <pbirnzain@users.sourceforge.net>
5
 *
6
 * pcb2gcode is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * 
11
 * pcb2gcode is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 * 
16
 * You should have received a copy of the GNU General Public License
17
 * along with pcb2gcode.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19

20
#include <algorithm>
21
#include <utility>
22
using std::pair;
23
using std::reverse;
24
using std::swap;
25

26
#include <iostream>
27
using std::cerr;
28
using std::endl;
29

30
#include <string>
31
using std::string;
32

33
#include <vector>
34
using std::vector;
35

36
#include <cstdint>
37
#include <list>
38
#include <iterator>
39
using std::list;
40
using std::next;
41
using std::make_move_iterator;
42

43
#include <memory>
44
using std::unique_ptr;
45

46
#include <forward_list>
47
using std::forward_list;
48

49
#include <map>
50
using std::map;
51

52
#include <boost/format.hpp>
53

54
#include "gerberimporter.hpp"
55
#include "eulerian_paths.hpp"
56
#include "bg_operators.hpp"
57
#include "bg_helpers.hpp"
58
#include "merge_near_points.hpp"
59

60
namespace bg = boost::geometry;
61

62
typedef bg::strategy::transform::rotate_transformer<bg::degree, double, 2, 2> rotate_deg;
63
typedef bg::strategy::transform::translate_transformer<coordinate_type_fp, 2, 2> translate;
64

65
GerberImporter::GerberImporter(coordinate_type_fp max_arc_segment_length)
166✔
66
  : max_arc_segment_length(max_arc_segment_length) {
166✔
67
  project = gerbv_create_project();
166✔
68
}
166✔
69

70
GerberImporter::~GerberImporter() {
332✔
71
  gerbv_destroy_project(project);
166✔
72
}
166✔
73

74
/* Returns true iff successful. */
75
bool GerberImporter::load_file(const string& path) {
166!
76
  gchar *filename = g_strdup(path.c_str());
77
  gerbv_open_layer_from_filename(project, filename);
166✔
78
  g_free(filename);
166✔
79
  return project->file[0] != NULL;
166✔
80
}
81

82
box_type_fp GerberImporter::get_bounding_box() const {
349✔
83
  return box_type_fp{
84
    {project->file[0]->image->info->min_x,  project->file[0]->image->info->min_y},
349✔
85
    {project->file[0]->image->info->max_x,  project->file[0]->image->info->max_y}
86
  };
349✔
87
}
88

89
// Draw a regular polygon with outer diameter as specified and center.  The
90
// number of vertices is provided.  offset is an angle in degrees to the
91
// starting vertex of the shape.
92
multi_polygon_type_fp make_regular_polygon(point_type_fp center, coordinate_type_fp diameter, unsigned int vertices,
624✔
93
                                           double offset) {
94
  double angle_step;
95

96
  angle_step = -2 * bg::math::pi<double>() / vertices;
624✔
97
  offset *= bg::math::pi<double>() / 180.0; // Convert to radians.
624✔
98

99
  ring_type_fp ring;
100
  for (unsigned int i = 0; i < vertices; i++) {
400,518✔
101
    ring.push_back(point_type_fp(cos(angle_step * i + offset) * diameter / 2 + center.x(),
399,894✔
102
                                 sin(angle_step * i + offset) * diameter / 2 + center.y()));
399,894!
103
  }
104
  ring.push_back(ring.front()); // Don't forget to close the ring.
624!
105
  multi_polygon_type_fp ret;
106
  bg::convert(ring, ret);
107
  return ret;
624✔
108
}
109

110
// Uses make_regular_polygon to draw a circle of line segments.
111
multi_polygon_type_fp GerberImporter::make_circle(point_type_fp center, coordinate_type_fp diameter, coordinate_type_fp offset) const {
605✔
112
  const auto points_per_circle = std::max(32., diameter * bg::math::pi<double>() / max_arc_segment_length);
605✔
113
  return ::make_regular_polygon(center, diameter, points_per_circle, offset);
605✔
114
}
115

116
// Same as above but potentially puts a hole in the center.
117
multi_polygon_type_fp GerberImporter::make_regular_polygon(point_type_fp center, coordinate_type_fp diameter, unsigned int vertices,
1!
118
                                                           coordinate_type_fp offset, coordinate_type_fp hole_diameter) const {
119
  multi_polygon_type_fp ret;
120
  ret = ::make_regular_polygon(center, diameter, vertices, offset);
1!
121

122
  if (hole_diameter > 0) {
1!
123
    ret = ret - make_circle(center, hole_diameter, offset);
×
124
  }
125
  return ret;
1✔
126
}
127

128
// Same as above but potentially puts a hole in the center.
129
multi_polygon_type_fp GerberImporter::make_circle(point_type_fp center, coordinate_type_fp diameter,
506!
130
                                                  coordinate_type_fp offset,
131
                                                  coordinate_type_fp hole_diameter) const {
132
  multi_polygon_type_fp ret;
133
  ret = make_circle(center, diameter, offset);
506!
134

135
  if (hole_diameter > 0) {
506✔
136
    ret = ret - make_circle(center, hole_diameter, offset);
36!
137
  }
138
  return ret;
506✔
139
}
140

141
multi_polygon_type_fp GerberImporter::make_rectangle(point_type_fp center, double width, double height,
200✔
142
                                                      coordinate_type_fp hole_diameter) const {
143
  const coordinate_type_fp x = center.x();
200✔
144
  const coordinate_type_fp y = center.y();
200!
145

146
  multi_polygon_type_fp ret;
147
  ret.resize(1);
200!
148
  auto& polygon = ret.front();
149
  polygon.outer().push_back(point_type_fp(x - width / 2, y - height / 2));
200!
150
  polygon.outer().push_back(point_type_fp(x - width / 2, y + height / 2));
200!
151
  polygon.outer().push_back(point_type_fp(x + width / 2, y + height / 2));
200!
152
  polygon.outer().push_back(point_type_fp(x + width / 2, y - height / 2));
200!
153
  polygon.outer().push_back(polygon.outer().front());
200!
154

155
  if (hole_diameter > 0) {
200!
156
    ret = ret - make_circle(center, hole_diameter, 0);
×
157
  }
158
  return ret;
200✔
159
}
160

161
multi_polygon_type_fp make_rectangle(point_type_fp point1, point_type_fp point2, double height) {
82!
162
  multi_polygon_type_fp ret;
163
  linestring_type_fp line;
164
  line.push_back(point1);
82!
165
  line.push_back(point2);
82!
166
  bg::buffer(line, ret,
82!
167
             bg::strategy::buffer::distance_symmetric<coordinate_type_fp>(height/2),
82!
168
             bg::strategy::buffer::side_straight(),
82✔
169
             bg::strategy::buffer::join_round(0),
82✔
170
             bg::strategy::buffer::end_flat(),
82✔
171
             bg::strategy::buffer::point_circle(0));
82✔
172
  return ret;
82✔
173
}
174

175
multi_polygon_type_fp GerberImporter::make_oval(point_type_fp center, coordinate_type_fp width, coordinate_type_fp height,
42✔
176
                                                coordinate_type_fp hole_diameter) const {
177
  point_type_fp start(center.x(), center.y());
178
  point_type_fp end(center.x(), center.y());
179
  if (width > height) {
42✔
180
    // The oval is more wide than tall.
181
    start.x(start.x() - (width - height)/2);
35✔
182
    end.x(end.x() + (width - height)/2);
35✔
183
  } else if (width < height) {
7!
184
    // The oval is more tall than wide.
185
    start.y(start.y() - (height - width)/2);
×
186
    end.y(end.y() + (height - width)/2);
×
187
  } else {
188
    // This is just a circle.  Older boost doesn't handle a line with no length
189
    // though new boost does.
190
    return make_circle(center, width, 0, hole_diameter);
7✔
191
  }
192

193
  multi_polygon_type_fp oval;
194
  linestring_type_fp line;
195
  line.push_back(start);
35!
196
  line.push_back(end);
35!
197
  const auto diameter = std::max(width, height); // Close enough.
35✔
198
  const auto circle_points = std::max(32., diameter * bg::math::pi<double>() / max_arc_segment_length);
35!
199
  bg::buffer(line, oval,
35!
200
             bg::strategy::buffer::distance_symmetric<coordinate_type_fp>(std::min(width, height)/2),
35!
201
             bg::strategy::buffer::side_straight(),
35✔
202
             bg::strategy::buffer::join_round(circle_points),
35✔
203
             bg::strategy::buffer::end_round(circle_points),
35✔
204
             bg::strategy::buffer::point_circle(circle_points));
35!
205

206
  if (hole_diameter > 0) {
35!
207
    multi_polygon_type_fp hole = make_circle(center, hole_diameter, 0);
×
208
    multi_polygon_type_fp hole_fp;
209
    bg::convert(hole, hole_fp);
210
    oval = oval - hole_fp;
×
211
  }
212
  multi_polygon_type_fp ret;
213
  bg::convert(oval, ret);
214
  return ret;
215
}
216

217
multi_polygon_type_fp linear_draw_rectangular_aperture(point_type_fp startpoint, point_type_fp endpoint, coordinate_type_fp width,
×
218
                                                       coordinate_type_fp height) {
219
  // It's the convex hull of all the corners of all the points.
220
  multi_point_type_fp all_points;
221
  for (const auto& p : {startpoint, endpoint}) {
×
222
    for (double w : {-1, 1}) {
×
223
      for (double h : {-1, 1}) {
×
224
        all_points.push_back(point_type_fp(p.x()+w*width/2, p.y()+h*height/2));
×
225
      }
226
    }
227
  }
228
  multi_polygon_type_fp hull;
229
  hull.resize(1);
×
230
  bg::convex_hull(all_points, hull[0]);
231
  return hull;
×
232
}
233

234
double get_angle(point_type_fp start, point_type_fp center, point_type_fp stop, bool clockwise) {
86✔
235
  double start_angle = atan2(start.y() - center.y(), start.x() - center.x());
86✔
236
  double  stop_angle = atan2( stop.y() - center.y(),  stop.x() - center.x());
86✔
237
  double delta_angle = stop_angle - start_angle;
86✔
238
  while (clockwise && delta_angle > 0) {
103✔
239
    delta_angle -= 2 * bg::math::pi<double>();
17✔
240
  }
241
  while (!clockwise && delta_angle < 0) {
100✔
242
    delta_angle += 2 * bg::math::pi<double>();
14✔
243
  }
244
  return delta_angle;
86✔
245
}
246

247
// delta_angle is in radians.  Positive signed is counterclockwise, like math.
248
linestring_type_fp GerberImporter::circular_arc(const point_type_fp& start, const point_type_fp& stop,
66✔
249
                                                point_type_fp center, const coordinate_type_fp& radius,
250
                                                const coordinate_type_fp& radius2, double delta_angle, const bool& clockwise) const {
251
  // We can't trust gerbv to calculate single-quadrant vs multi-quadrant
252
  // correctly so we must so it ourselves.
253
  bool definitely_sq = false;
254
  if (radius != radius2) {
66!
255
    definitely_sq = true; // Definiltely single-quadrant.
256
  }
257
  if (start.x() == stop.x() && start.y() == stop.y()) {
66!
258
    // Either 0 or 360, depending on mq/sq.
259
    if (definitely_sq) {
23!
260
      delta_angle = 0;
261
    } else {
262
      if (std::abs(delta_angle) < bg::math::pi<double>()) {
23!
263
        delta_angle = 0;
264
      } else {
265
        delta_angle = bg::math::pi<double>() * 2;
266
        if (clockwise) {
23!
267
          delta_angle = -delta_angle;
268
        }
269
      }
270
    }
271
  } else {
272
    const auto signs_to_try = definitely_sq ? vector<double>{-1, 1} : vector<double>{1};
43!
273
    const coordinate_type_fp i = std::abs(center.x() - start.x());
43✔
274
    const coordinate_type_fp j = std::abs(center.y() - start.y());
43✔
275
    delta_angle = get_angle(start, center, stop, clockwise);
43✔
276
    for (const double& i_sign : signs_to_try) {
86✔
277
      for (const double& j_sign : signs_to_try) {
86✔
278
        const point_type_fp current_center = point_type_fp(start.x() + i*i_sign, start.y() + j*j_sign);
43✔
279
        double new_angle = get_angle(start, current_center, stop, clockwise);
43✔
280
        if (std::abs(new_angle) > bg::math::pi<double>()) {
43✔
281
          continue; // Wrong side.
22✔
282
        }
283
        if (std::abs(bg::distance(start, current_center) - bg::distance(stop, current_center)) <
42!
284
            std::abs(bg::distance(start,         center) - bg::distance(stop,         center))) {
21!
285
          // This is closer to the center line so it's a better choice.
286
          delta_angle = new_angle;
287
          center = current_center;
×
288
        }
289
      }
290
    }
291
  }
292

293
  // Now delta_angle is between -2pi and 2pi and accurate and center is correct.
294
  const double start_angle = atan2(start.y() - center.y(), start.x() - center.x());
66✔
295
  const double stop_angle = start_angle + delta_angle;
66✔
296
  const coordinate_type_fp start_radius = bg::distance(start, center);
297
  const coordinate_type_fp stop_radius = bg::distance(stop, center);
298
  auto const average_radius = (start_radius + stop_radius) / 2;
66✔
299
  auto const radius_points = std::max(32. / 2 / bg::math::pi<double>(), average_radius / max_arc_segment_length);
132!
300
  const unsigned int steps = std::ceil(std::abs(delta_angle) * radius_points) + 1; // One more for the end point.
66!
301
  linestring_type_fp linestring;
302
  linestring.reserve(steps);
66!
303
  // First place the start;
304
  linestring.push_back(start);
66!
305
  for (unsigned int i = 1; i < steps - 1; i++) {
209,034✔
306
    const double stop_weight = double(i) / (steps - 1);
208,968✔
307
    const double start_weight = 1 - stop_weight;
208,968✔
308
    const double current_angle = start_angle*start_weight + stop_angle*stop_weight;
208,968✔
309
    const double current_radius = start_radius*start_weight + stop_radius*stop_weight;
208,968✔
310
    linestring.push_back(point_type_fp(cos(current_angle) * current_radius + center.x(),
208,968✔
311
                                       sin(current_angle) * current_radius + center.y()));
208,968!
312
  }
313
  linestring.push_back(stop);
66!
314
  return linestring;
66✔
315
}
316

317
inline static void unsupported_polarity_throw_exception() {
×
318
  cerr << ("Non-positive image polarity is deprecated by the Gerber "
319
           "standard and unsupported; re-run pcb2gcode without the "
320
           "--vectorial flag") << endl;
×
321
  throw gerber_exception();
×
322
}
323

324
// A pair of shapes, one from filling in closed loops, one from all the rest of
325
// the shapes.  If fill_closed_lines is false, all the outputs will be in
326
// shapes.  If fill_closed_lines is true, loops will be converted into shapes
327
// and put into filled_closed_lines.  The rest are put into shapes.  shapes are
328
// combined with addition but filled_closed_lines are combined with exclusive
329
// or.
330
struct mp_pair {
14,346✔
331
  mp_pair() {}
332
  mp_pair(multi_polygon_type_fp shapes) : shapes(shapes) {}
3,887!
333
  mp_pair(multi_polygon_type_fp shapes,
96✔
334
          multi_polygon_type_fp filled_closed_lines) :
96✔
335
    shapes(shapes),
336
    filled_closed_lines(filled_closed_lines) {}
96✔
337
  multi_polygon_type_fp shapes;
338
  multi_polygon_type_fp filled_closed_lines;
339
};
340

341
// To speed up the merging, we do them in pairs so that we're mostly merging
342
// equal-sized shapes.
343
mp_pair merge_multi_draws(const vector<mp_pair>& multi_draws) {
166✔
344
  if (multi_draws.size() == 0) {
166✔
345
    return multi_polygon_type_fp();
4✔
346
  } else if (multi_draws.size() == 1) {
162✔
347
    return multi_draws.front();
66✔
348
  }
349
  vector<multi_polygon_type_fp> shapes;
96✔
350
  vector<multi_polygon_type_fp> filled_closed_lines;
96✔
351
  shapes.reserve(multi_draws.size());
96!
352
  filled_closed_lines.reserve(multi_draws.size());
96!
353
  for (const auto& multi_draw : multi_draws) {
4,150✔
354
    shapes.push_back(multi_draw.shapes);
4,054!
355
    filled_closed_lines.push_back(multi_draw.filled_closed_lines);
4,054!
356
  }
357
  return mp_pair(sum(shapes), symdiff(filled_closed_lines));
192!
358
}
359

360
// layers is a vector of layers.  Each layer has a polarity, which can
361
// be dark meaning to draw, or clear, meaning to erase.  In the end,
362
// the output is regions that are drawn and regions that are undrawn.
363
// The layers also have two sets of elements to draw: shapes and
364
// filled_closed_regions.  The former are actual shapes.  The later
365
// are line drawings that maybe need to be treated as shapes, or not,
366
// depend on the options provided.  Finally, there is the xor.  xor
367
// overrides the layer polarity if set and causes each layer to be
368
// xored with the previous layer, instead of drawn or erased (dark or
369
// clear).
370
multi_polygon_type_fp generate_layers(vector<pair<const gerbv_layer_t *, mp_pair>>& layers,
304✔
371
                                      multi_polygon_type_fp mp_pair::* member, bool xor_layers) {
372
  multi_polygon_type_fp output;
373
  vector<ring_type_fp> rings;
304✔
374

375
  for (auto layer = layers.cbegin(); layer != layers.cend(); layer++) {
636✔
376
    const gerbv_polarity_t polarity = layer->first->polarity;
332✔
377
    const gerbv_step_and_repeat_t& stepAndRepeat = layer->first->stepAndRepeat;
378
    mp_pair draw_pair = layer->second;
332!
379
    multi_polygon_type_fp draws = draw_pair.*member;
332!
380
    if (stepAndRepeat.X > 0 || stepAndRepeat.Y > 0) {
332!
381
      vector<multi_polygon_type_fp> to_sum{draws};
664!
382

383
      to_sum.reserve(stepAndRepeat.X * stepAndRepeat.Y);
332!
384
      for (int sr_x = 0; sr_x < stepAndRepeat.X; sr_x++) {
664✔
385
        for (int sr_y = 0; sr_y < stepAndRepeat.Y; sr_y++) {
664✔
386
          if (sr_x == 0 && sr_y == 0) {
332!
387
            continue; // Already got this one.
332✔
388
          }
389
          multi_polygon_type_fp translated_draws;
390
          bg::transform(draws, translated_draws,
391
                        translate(stepAndRepeat.dist_X * sr_x,
×
392
                                  stepAndRepeat.dist_Y * sr_y));
×
393
          to_sum.push_back(translated_draws);
×
394
        }
395
      }
396
      draws = sum(to_sum);
664!
397
    }
398

399
    if (xor_layers) {
332✔
400
      output = output ^ draws;
124!
401
    } else if (polarity == GERBV_POLARITY_DARK) {
270✔
402
      output = output + draws;
514!
403
    } else if (polarity == GERBV_POLARITY_CLEAR) {
13!
404
      output = output - draws;
26!
405
    } else {
406
      unsupported_polarity_throw_exception();
×
407
    }
408
  }
409
  return output;
304✔
410
}
411

412
multi_polygon_type_fp GerberImporter::make_moire(const double * const parameters) const {
9!
413
  const point_type_fp center(parameters[0], parameters[1]);
414
  vector<multi_polygon_type_fp> moire_parts;
9✔
415

416
  double crosshair_thickness = parameters[6];
9✔
417
  double crosshair_length = parameters[7];
9✔
418
  moire_parts.push_back(make_rectangle(center, crosshair_thickness, crosshair_length, 0));
9!
419
  moire_parts.push_back(make_rectangle(center, crosshair_length, crosshair_thickness, 0));
9!
420
  const int max_number_of_rings = parameters[5];
9✔
421
  const double outer_ring_diameter = parameters[2];
9✔
422
  const double ring_thickness = parameters[3];
9✔
423
  const double gap_thickness = parameters[4];
9✔
424
  for (int i = 0; i < max_number_of_rings; i++) {
18!
425
    const double external_diameter = outer_ring_diameter - 2 * (ring_thickness + gap_thickness) * i;
18✔
426
    double internal_diameter = external_diameter - 2 * ring_thickness;
18✔
427
    if (external_diameter <= 0)
18✔
428
      break;
429
    if (internal_diameter < 0)
9!
430
      internal_diameter = 0;
431
    moire_parts.push_back(make_circle(center, external_diameter, 0,
18!
432
                                      internal_diameter));
433
  }
434
  return sum(moire_parts);
18!
435
}
436

437
multi_polygon_type_fp GerberImporter::make_thermal(point_type_fp center, coordinate_type_fp external_diameter, coordinate_type_fp internal_diameter,
9✔
438
                                                   coordinate_type_fp gap_width) const {
439
  multi_polygon_type_fp ring = make_circle(center, external_diameter, 0, internal_diameter);
9✔
440

441
  multi_polygon_type_fp rect1 = make_rectangle(center, gap_width, 2 * external_diameter, 0);
9!
442
  multi_polygon_type_fp rect2 = make_rectangle(center, 2 * external_diameter, gap_width, 0);
9!
443
  return ring - rect1 - rect2;
27!
444
}
445

446
// Look through ls for crossing points and snip them out of the input so that
447
// the return value is a series of rings such that no ring has the same point in
448
// it twice except for the front and back.  There is also one linestring that
449
// isn't a ring in the output if the input isn't a ring.
450
multi_linestring_type_fp get_all_ls(const linestring_type_fp& ls) {
2,076✔
451
  for (auto start = ls.cbegin(); start != ls.cend(); start++) {
50,905✔
452
    for (auto end = std::next(start); end != ls.cend(); end++) {
24,995,976✔
453
      if (bg::equals(*start, *end)) {
24,947,147✔
454
        if (start == ls.cbegin() && end == std::prev(ls.cend())) {
1,445✔
455
          continue; // This is just the entire ls, no need to try to recurse here.
628✔
456
        }
457
        linestring_type_fp inner(start, end); // Build the ring that we've found.
121!
458
        inner.push_back(inner.front()); // Close the ring.
121!
459

460
        // Connect up the rest.
461
        linestring_type_fp outer(ls.cbegin(), start);
121!
462
        outer.insert(outer.cend(), end, ls.cend());
121!
463
        // Recurse on outer and inner and put together.
464
        auto all = get_all_ls(outer);
121!
465
        auto all_inner = get_all_ls(inner);
121!
466
        all.insert(all.cend(), all_inner.cbegin(), all_inner.cend());
121!
467
        return all;
468
      }
469
    }
470
  }
471
  // No points repeated so just return the original without recursion.
472
  return multi_linestring_type_fp{ls};
3,910!
473
}
474

475
vector<ring_type_fp> get_all_rings(const ring_type_fp& ring) {
344✔
476
  auto mls = get_all_ls(linestring_type_fp(ring.cbegin(), ring.cend()));
688!
477
  vector<ring_type_fp> rings;
478
  for (const auto& ls : mls) {
692✔
479
    rings.push_back(ring_type_fp(ls.cbegin(), ls.cend()));
696!
480
  }
481
  return rings;
344✔
482
}
483

484
multi_polygon_type_fp simplify_cutins(const ring_type_fp& ring) {
983✔
485
  if (ring.size() < 4) {
983✔
486
    return {};
487
  }
488
  auto new_mls = eulerian_paths::make_eulerian_paths({linestring_type_fp(ring.cbegin(), ring.cend())}, true, false);
1,376!
489
  if (new_mls.size() == 0) {
344!
490
    return {};
491
  }
492
  ring_type_fp new_ring(new_mls[0].cbegin(), new_mls[0].cend());
344✔
493
  vector<ring_type_fp> all_rings = get_all_rings(new_ring);
688!
494
  multi_polygon_type_fp ret;
495
  for (auto r : all_rings) {
692✔
496
    const auto this_area = bg::area(r);
497
    if (r.size() < 4 || this_area == 0) {
348!
498
      continue; // No area so ignore it.
499
    }
500
    auto correct_r = r;
501
    bg::correct(correct_r);
502
    ret = ret ^ multi_polygon_type_fp{{correct_r}};
1,384!
503
  }
504
  return ret;
505
}
506

507
map<int, multi_polygon_type_fp> GerberImporter::generate_apertures_map(const gerbv_aperture_t * const apertures[]) const {
152✔
508
  const point_type_fp origin (0, 0);
152✔
509
  map<int, multi_polygon_type_fp> apertures_map;
510
  for (int i = 0; i < APERTURE_MAX; i++) {
1,520,000✔
511
    const gerbv_aperture_t * const aperture = apertures[i];
1,519,848✔
512

513
    if (aperture) {
1,519,848✔
514
      const double * const parameters = aperture->parameter;
515
      multi_polygon_type_fp input;
516

517
      switch (aperture->type) {
770!
518
        case GERBV_APTYPE_NONE:
×
519
          continue;
×
520

521
        case GERBV_APTYPE_CIRCLE:
481✔
522
          input = make_circle(origin,
481!
523
                              parameters[0],
524
                              parameters[1],
525
                              parameters[2]);
526
          break;
481✔
527
        case GERBV_APTYPE_RECTANGLE:
146✔
528
          input = make_rectangle(origin,
146!
529
                                 parameters[0],
530
                                 parameters[1],
531
                                 parameters[2]);
532
          break;
146✔
533
        case GERBV_APTYPE_OVAL:
42✔
534
          input = make_oval(origin,
42!
535
                            parameters[0],
536
                            parameters[1],
537
                            parameters[2]);
538
          break;
42✔
539
        case GERBV_APTYPE_POLYGON:
1✔
540
          input = make_regular_polygon(origin,
2✔
541
                                       parameters[0],
542
                                       parameters[1],
1!
543
                                       parameters[2],
544
                                       parameters[3]);
545
          break;
1✔
546
        case GERBV_APTYPE_MACRO:
100✔
547
          if (aperture->simplified) {
100!
548
            // I thikn that this means that the marco's variables are substitued.
549
            const gerbv_simplified_amacro_t *simplified_amacro = aperture->simplified;
550

551
            while (simplified_amacro) {
344✔
552
              const double * const parameters = simplified_amacro->parameter;
244!
553
              double rotation;
554
              int polarity;
555
              multi_polygon_type_fp mpoly;
556
              multi_polygon_type_fp mpoly_rotated;
557

558
              switch (simplified_amacro->type) {
244!
559
                case GERBV_APTYPE_NONE:
×
560
                case GERBV_APTYPE_CIRCLE:
561
                case GERBV_APTYPE_RECTANGLE:
562
                case GERBV_APTYPE_OVAL:
563
                case GERBV_APTYPE_POLYGON:
564
                  cerr << "Non-macro aperture during macro drawing: skipping" << endl;
×
565
                  simplified_amacro = simplified_amacro->next;
×
566
                  continue;
×
567
                case GERBV_APTYPE_MACRO:
×
568
                  cerr << "Macro start aperture during macro drawing: skipping" << endl;
×
569
                  simplified_amacro = simplified_amacro->next;
×
570
                  continue;
×
571
                case GERBV_APTYPE_MACRO_CIRCLE:
81✔
572
                  mpoly = make_circle(point_type_fp(parameters[2], parameters[3]),
81!
573
                                      parameters[1],
574
                                      0);
575
                  polarity = parameters[0];
81✔
576
                  rotation = parameters[4];
81✔
577
                  break;
81✔
578
              case GERBV_APTYPE_MACRO_OUTLINE: // 4.5.2.6 Outline, Code 4
579
                  {
580
                    ring_type_fp ring;
581
                    for (unsigned int i = 0; i < round(parameters[1]) + 1; i++){
153✔
582
                      ring.push_back(point_type_fp(parameters[i * 2 + 2],
126✔
583
                                                   parameters [i * 2 + 3]));
126!
584
                    }
585
                    bg::correct(ring);
586
                    mpoly = simplify_cutins(ring);
54!
587
                  }
588
                  polarity = parameters[0];
27✔
589
                  rotation = parameters[(2 * int(round(parameters[1])) + 4)];
27✔
590
                  break;
27✔
591
                case GERBV_APTYPE_MACRO_POLYGON: // 4.12.4.6 Polygon, Primitve Code 5
18✔
592
                  mpoly = ::make_regular_polygon(point_type_fp(parameters[2], parameters[3]),
36✔
593
                                                 parameters[4],
594
                                                 parameters[1],
18!
595
                                                 0);
596
                  polarity = parameters[0];
18✔
597
                  rotation = parameters[5];
18✔
598
                  break;
18✔
599
                case GERBV_APTYPE_MACRO_MOIRE: // 4.12.4.7 Moire, Primitive Code 6
9✔
600
                  mpoly = make_moire(parameters);
9!
601
                  polarity = 1;
602
                  rotation = parameters[8];
9✔
603
                  break;
9✔
604
                case GERBV_APTYPE_MACRO_THERMAL: // 4.12.4.8 Thermal, Primitive Code 7
9✔
605
                  mpoly = make_thermal(point_type_fp(parameters[0], parameters[1]),
9!
606
                                       parameters[2],
607
                                       parameters[3],
608
                                       parameters[4]);
609
                  polarity = 1;
610
                  rotation = parameters[5];
9✔
611
                  break;
9✔
612
                case GERBV_APTYPE_MACRO_LINE20: // 4.12.4.3 Vector Line, Primitive Code 20
82✔
613
                  mpoly = ::make_rectangle(point_type_fp(parameters[2], parameters[3]),
82!
614
                                           point_type_fp(parameters[4], parameters[5]),
615
                                           parameters[1]);
616
                  polarity = parameters[0];
82✔
617
                  rotation = parameters[6];
82✔
618
                  break;
82✔
619
                case GERBV_APTYPE_MACRO_LINE21: // 4.12.4.4 Center Line, Primitive Code 21
9✔
620
                  mpoly = make_rectangle(point_type_fp(parameters[3], parameters[4]),
9!
621
                                         parameters[1],
622
                                         parameters[2],
623
                                         0);
624
                  polarity = parameters[0];
9✔
625
                  rotation = parameters[5];
9✔
626
                  break;
9✔
627
                case GERBV_APTYPE_MACRO_LINE22:
9✔
628
                  mpoly = make_rectangle(point_type_fp((parameters[3] + parameters[1] / 2),
18✔
629
                                                       (parameters[4] + parameters[2] / 2)),
9!
630
                                         parameters[1],
631
                                         parameters[2],
632
                                         0);
633
                  polarity = parameters[0];
9✔
634
                  rotation = parameters[5];
9✔
635
                  break;
9✔
636
                default:
×
637
                  cerr << "Unrecognized aperture: skipping" << endl;
×
638
                  simplified_amacro = simplified_amacro->next;
×
639
                  continue;
×
640
              }
641
              // For Boost.Geometry a positive angle is considered
642
              // clockwise, for Gerber is the opposite
643
              bg::transform(mpoly, mpoly_rotated, rotate_deg(-rotation));
244✔
644

645
              if (polarity == 0) {
244!
646
                input = input - mpoly_rotated;
×
647
              } else {
648
                input = input + mpoly_rotated;
488!
649
              }
650
              simplified_amacro = simplified_amacro->next;
244✔
651
            }
652
          } else {
653
            cerr << "Macro aperture " << i << " is not simplified: skipping" << endl;
×
654
            continue;
×
655
          }
656
          break;
657
        case GERBV_APTYPE_MACRO_CIRCLE:
×
658
        case GERBV_APTYPE_MACRO_OUTLINE:
659
        case GERBV_APTYPE_MACRO_POLYGON:
660
        case GERBV_APTYPE_MACRO_MOIRE:
661
        case GERBV_APTYPE_MACRO_THERMAL:
662
        case GERBV_APTYPE_MACRO_LINE20:
663
        case GERBV_APTYPE_MACRO_LINE21:
664
        case GERBV_APTYPE_MACRO_LINE22:
665
          cerr << "Macro aperture during non-macro drawing: skipping" << endl;
×
666
          continue;
×
667
        default:
×
668
          cerr << "Unrecognized aperture: skipping" << endl;
×
669
          continue;
×
670
      }
671
      apertures_map[i] = input;
770!
672
    }
673
  }
674
  return apertures_map;
152✔
675
}
676

677
bool layers_equivalent(const gerbv_layer_t* const layer1, const gerbv_layer_t* const layer2) {
25,691✔
678
  const gerbv_step_and_repeat_t& sr1 = layer1->stepAndRepeat;
679
  const gerbv_step_and_repeat_t& sr2 = layer2->stepAndRepeat;
680

681
  return (layer1->polarity == layer2->polarity &&
25,691✔
682
          sr1.X == sr2.X &&
25,677!
683
          sr1.Y == sr2.Y &&
25,677✔
684
          sr1.dist_X == sr2.dist_X &&
51,368!
685
          sr1.dist_Y == sr2.dist_Y);
25,677!
686
}
687

688
/* Convert paths that all need to be drawn with the same diameter into shapes.
689
 *
690
 * If fill_closed_lines is true, we'll try to find closed loops among the paths
691
 * and treat those loops as a polygons with a filled surface.  We'll ignore the
692
 * direction of the paths.  Where loops overlap, we'll xor with the other loops.
693
 * If there are non-loops when fill_closed_lines is true, we'll report an
694
 * error.
695
 */
696
mp_pair paths_to_shapes(const coordinate_type_fp& diameter, const multi_linestring_type_fp& paths, bool fill_closed_lines) {
233✔
697
  multi_linestring_type_fp new_paths(paths);
698
  if (fill_closed_lines) {
233✔
699
    if (merge_near_points(new_paths, diameter) > 0) {
63!
700
      cerr << "Some nearly-connected lines in the gerber input have been adjusted to properly connect" << endl;
8!
701
    }
702
  }
703
  // This converts the many small line segments into the longest paths possible.
704
  multi_linestring_type_fp euler_paths_with_rings =
705
      eulerian_paths::make_eulerian_paths(new_paths, true, true);
233!
706
  multi_linestring_type_fp euler_paths;
707
  for (const auto& ls : euler_paths_with_rings) {
1,723✔
708
    auto all_ls = get_all_ls(ls);
1,490!
709
    euler_paths.insert(euler_paths.cend(), all_ls.cbegin(), all_ls.cend());
1,490!
710
  }
711
  mp_pair ovals;
712
  if (fill_closed_lines) {
233✔
713
    for (auto& euler_path : euler_paths) {
138✔
714
      if (bg::equals(euler_path.front(), euler_path.back())) {
75✔
715
        // This is a loop.
716
        polygon_type_fp loop_poly;
717
        loop_poly.outer().swap(euler_path);
718
        bg::correct(loop_poly);
719
        multi_polygon_type_fp loop_mpoly;
720
        loop_mpoly.push_back(loop_poly);
72!
721
        ovals.filled_closed_lines = ovals.filled_closed_lines ^ loop_mpoly;
144!
722
      }
723
    }
724
  }
725
  euler_paths.erase(std::remove_if(euler_paths.begin(), euler_paths.end(), [](const linestring_type_fp& l) { return l.size() == 0; }), euler_paths.end());
233✔
726
  if (euler_paths.size() > 0) {
233✔
727
    // This converts the long paths into a shape with thickness equal to the specified diameter.
728
    auto new_ovals = bg_helpers::buffer(euler_paths, diameter / 2);
173!
729
    if (fill_closed_lines) {
173✔
730
      // Assume that this are slots that were drawn as lines.
731
      cerr << "Found an unconnected loop while parsing a gerber file while expecting only loops"
3!
732
           << endl;
733
      ovals.shapes = ovals.shapes + new_ovals;
6!
734
    } else {
735
      ovals.shapes = ovals.shapes + new_ovals;
340!
736
    }
737
  }
738
  return ovals;
233✔
739
}
740

741

742
// Convert the gerber file into a pair of multi_polygon_type_fp and a list of
743
// linear_paths.  The linear paths are a map from diamter of the tool for the
744
// path to all the paths at that diameter.  If fill_closed_lines is true, return
745
// all closed shapes without holes in them.
746
pair<multi_polygon_type_fp, map<coordinate_type_fp, multi_linestring_type_fp>> GerberImporter::render(
152!
747
    bool fill_closed_lines,
748
    bool render_paths_to_shapes) const {
749
  ring_type_fp region;
750
  bool contour = false; // Are we in contour mode?
751

752
  vector<pair<const gerbv_layer_t *, vector<mp_pair>>> layers(1);
304!
753

754
  gerbv_image_t *gerber = project->file[0]->image;
152✔
755

756
  if (gerber->info->polarity != GERBV_POLARITY_POSITIVE) {
152!
757
    unsupported_polarity_throw_exception();
×
758
  }
759

760
  const map<int, multi_polygon_type_fp> apertures_map = generate_apertures_map(gerber->aperture);
152!
761
  layers.front().first = gerber->netlist->layer;
152✔
762

763

764
  map<coordinate_type_fp, multi_linestring_type_fp> linear_circular_paths;
765
  for (gerbv_net_t *currentNet = gerber->netlist; currentNet; currentNet = currentNet->next) {
25,843✔
766
    const point_type_fp start (currentNet->start_x, currentNet->start_y);
767
    const point_type_fp stop (currentNet->stop_x, currentNet->stop_y);
768
    const double * const parameters = gerber->aperture[currentNet->aperture]->parameter;
25,691✔
769
    multi_polygon_type_fp mpoly;
770

771
    if (!layers_equivalent(currentNet->layer, layers.back().first)) {
25,691✔
772
      if (render_paths_to_shapes) {
14!
773
        // About to start a new layer, render all the linear_circular_paths so far.
774
        for (const auto& diameter_and_path : linear_circular_paths) {
16✔
775
          layers.back().second.push_back(paths_to_shapes(diameter_and_path.first, diameter_and_path.second, fill_closed_lines));
4!
776
        }
777
        linear_circular_paths.clear();
778
      }
779
      layers.resize(layers.size() + 1);
14!
780
      layers.back().first = currentNet->layer;
14✔
781
    }
782

783
    vector<mp_pair>& draws = layers.back().second;
25,691✔
784

785
    if (currentNet->interpolation == GERBV_INTERPOLATION_LINEARx1) {
25,691✔
786
      if (currentNet->aperture_state == GERBV_APERTURE_STATE_ON) {
24,669✔
787
        if (contour) {
21,108✔
788
          if (region.empty()) {
13,753✔
789
            bg::append(region, start);
790
          }
791
          bg::append(region, stop);
792
        } else {
793
          if (gerber->aperture[currentNet->aperture]->type == GERBV_APTYPE_CIRCLE) {
7,355!
794
            // These are common and too slow to merge one by one so we put them
795
            // all together and then do one big union at the end.
796
            const double diameter = parameters[0];
7,355!
797
            linestring_type_fp segment;
798
            segment.push_back(start);
7,355!
799
            segment.push_back(stop);
7,355!
800
            linear_circular_paths[diameter].push_back(segment);
7,355!
801
          } else if (gerber->aperture[currentNet->aperture]->type == GERBV_APTYPE_RECTANGLE) {
×
802
            mpoly = linear_draw_rectangular_aperture(start, stop, parameters[0],
×
803
                                                     parameters[1]);
804
            draws.push_back(mpoly);
×
805
          } else {
806
            cerr << ("Drawing with an aperture different from a circle "
807
                     "or a rectangle is forbidden by the Gerber standard; skipping.")
×
808
                 << endl;
809
          }
810
        }
811
      } else if (currentNet->aperture_state == GERBV_APERTURE_STATE_FLASH) {
3,561✔
812
        if (contour) {
2,931!
813
          cerr << ("D03 during contour mode is forbidden by the Gerber "
814
                   "standard; skipping") << endl;
×
815
        } else {
816
          const auto aperture_mpoly = apertures_map.find(currentNet->aperture);
2,931!
817

818
          if (aperture_mpoly != apertures_map.end()) {
2,931!
819
            bg::transform(aperture_mpoly->second, mpoly, translate(stop.x(), stop.y()));
5,862!
820
          } else {
821
            cerr << "Macro aperture " << currentNet->aperture <<
×
822
                " not found in macros list; skipping" << endl;
×
823
          }
824
          draws.push_back(mpoly);
2,931✔
825
        }
826
      } else if (currentNet->aperture_state == GERBV_APERTURE_STATE_OFF) {
630!
827
        if (contour) {
630✔
828
          if (region.size() > 0 && region.front() != region.back()) {
478!
829
            cerr << "Repairing invalid contour (EasyEDA makes these sometimes): " << bg::wkt(region) << std::endl;
×
830
            bg::append(region, region.front());
831
          }
832
          draws.push_back(simplify_cutins(region));
956!
833
          region.clear();
834
        }
835
      } else {
836
        cerr << "Unrecognized aperture state: skipping" << endl;
×
837
      }
838
    } else if (currentNet->interpolation == GERBV_INTERPOLATION_PAREA_START) {
1,022✔
839
      contour = true;
840
    } else if (currentNet->interpolation == GERBV_INTERPOLATION_PAREA_END) {
544✔
841
      contour = false;
842
      if (region.size() > 0 && region.front() != region.back()) {
478!
843
        cerr << "Repairing invalid contour (EasyEDA makes these sometimes): " << bg::wkt(region) << std::endl;
×
844
        bg::append(region, region.front());
845
      }
846
      draws.push_back(simplify_cutins(region));
956!
847
      region.clear();
848
    } else if (currentNet->interpolation == GERBV_INTERPOLATION_CW_CIRCULAR ||
66!
849
               currentNet->interpolation == GERBV_INTERPOLATION_CCW_CIRCULAR) {
850
      if (currentNet->aperture_state == GERBV_APERTURE_STATE_ON) {
66!
851
        const gerbv_cirseg_t * const cirseg = currentNet->cirseg;
66✔
852
        if (cirseg != NULL) {
66!
853
          double delta_angle = (cirseg->angle1 - cirseg->angle2) * bg::math::pi<double>() / 180.0;
66✔
854
          if (currentNet->interpolation == GERBV_INTERPOLATION_CW_CIRCULAR) {
66✔
855
            delta_angle = -delta_angle;
14✔
856
          }
857
          point_type_fp center(cirseg->cp_x, cirseg->cp_y);
858
          linestring_type_fp path = circular_arc(start, stop, center,
859
                                                 cirseg->width / 2,
132✔
860
                                                 cirseg->height / 2,
132✔
861
                                                 delta_angle,
862
                                                 currentNet->interpolation == GERBV_INTERPOLATION_CW_CIRCULAR);
66!
863
          if (contour) {
66!
864
            if (region.empty()) {
×
865
              region.insert(region.end(), path.begin(), path.end());
×
866
            } else {
867
              region.insert(region.end(), path.begin() + 1, path.end());
×
868
            }
869
          } else {
870
            if (gerber->aperture[currentNet->aperture]->type == GERBV_APTYPE_CIRCLE) {
66!
871
              const double diameter = parameters[0];
66✔
872
              for (size_t i = 1; i < path.size(); i++) {
209,100✔
873
                linestring_type_fp segment;
874
                segment.push_back(path[i-1]);
209,034!
875
                segment.push_back(path[i]);
209,034!
876
                linear_circular_paths[diameter].push_back(segment);
209,034!
877
              }
878
            } else {
879
              cerr << ("Drawing an arc with an aperture different from a circle "
880
                       "is forbidden by the Gerber standard; skipping.")
×
881
                   << endl;
882
            }
883
          }
884
        } else {
885
          cerr << "Circular arc requested but cirseg == NULL" << endl;
×
886
        }
887
      } else if (currentNet->aperture_state == GERBV_APERTURE_STATE_FLASH) {
×
888
        cerr << "D03 during circular arc mode is forbidden by the Gerber "
889
            "standard; skipping" << endl;
×
890
      }
891
    } else if (currentNet->interpolation == GERBV_INTERPOLATION_LINEARx10 ||
×
892
               currentNet->interpolation == GERBV_INTERPOLATION_LINEARx01 || 
×
893
               currentNet->interpolation == GERBV_INTERPOLATION_LINEARx001 ) {
894
      cerr << ("Linear zoomed interpolation modes are not supported "
895
               "(are they in the RS274X standard?)") << endl;
×
896
    } else { //if (currentNet->interpolation != GERBV_INTERPOLATION_DELETED)
897
      cerr << "Unrecognized interpolation mode" << endl;
×
898
    }
899
  }
900
  if (render_paths_to_shapes) {
152✔
901
    // If there are any unrendered circular paths, add them to the last layer.
902
    for (const auto& diameter_and_path : linear_circular_paths) {
380✔
903
      layers.back().second.push_back(paths_to_shapes(diameter_and_path.first, diameter_and_path.second, fill_closed_lines));
462!
904
    }
905
    linear_circular_paths.clear();
906
  }
907
  vector<pair<const gerbv_layer_t *, mp_pair>> merged_layers;
152✔
908
  merged_layers.reserve(layers.size());
152!
909
  for (const auto& layer : layers) {
318✔
910
    merged_layers.emplace_back(layer.first, merge_multi_draws(layer.second));
332!
911
  }
912
  auto result = generate_layers(merged_layers, &mp_pair::filled_closed_lines, fill_closed_lines);
152!
913
  if (fill_closed_lines) {
152✔
914
    result = result - generate_layers(merged_layers, &mp_pair::shapes, false);
120!
915
  } else {
916
    result = result + generate_layers(merged_layers, &mp_pair::shapes, false);
184!
917
  }
918

919
  if (gerber->netlist->state->unit == GERBV_UNIT_MM) {
152!
920
    // I don't believe that this ever happens because I think that gerbv
921
    // internally converts everything to inches.
922
    multi_polygon_type_fp scaled_result;
923
    bg::transform(result, scaled_result,
924
                  bg::strategy::transform::scale_transformer<coordinate_type_fp, 2, 2>(
×
925
                      1/25.4, 1/25.4));
926
    result.swap(scaled_result);
927
  }
928
  for (auto& path : linear_circular_paths) {
157✔
929
    path.second = eulerian_paths::make_eulerian_paths(path.second, true, true);
10!
930
  }
931
  return make_pair(result, linear_circular_paths);
152✔
932
}
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