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

pcb2gcode / pcb2gcode / 19811714133

24 Nov 2025 02:34PM UTC coverage: 59.007% (-15.0%) from 74.006%
19811714133

push

github

web-flow
Merge pull request #730 from mar0x/master

Enable windows build in CI

2199 of 4409 branches covered (49.88%)

Branch coverage included in aggregate %.

1902 of 2541 relevant lines covered (74.85%)

117318.67 hits per line

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

70.21
/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)
24✔
66
  : max_arc_segment_length(max_arc_segment_length) {
24✔
67
  project = gerbv_create_project();
24✔
68
}
24✔
69

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

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

82
box_type_fp GerberImporter::get_bounding_box() const {
21✔
83
  return box_type_fp{
84
    {project->file[0]->image->info->min_x,  project->file[0]->image->info->min_y},
21✔
85
    {project->file[0]->image->info->max_x,  project->file[0]->image->info->max_y}
86
  };
21✔
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,
114✔
93
                                           double offset) {
94
  double angle_step;
95

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

99
  ring_type_fp ring;
100
  for (unsigned int i = 0; i < vertices; i++) {
312,236✔
101
    ring.push_back(point_type_fp(cos(angle_step * i + offset) * diameter / 2 + center.x(),
312,122✔
102
                                 sin(angle_step * i + offset) * diameter / 2 + center.y()));
312,122!
103
  }
104
  ring.push_back(ring.front()); // Don't forget to close the ring.
114!
105
  multi_polygon_type_fp ret;
106
  bg::convert(ring, ret);
107
  return ret;
114✔
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 {
112✔
112
  const auto points_per_circle = std::max(32., diameter * bg::math::pi<double>() / max_arc_segment_length);
112✔
113
  return ::make_regular_polygon(center, diameter, points_per_circle, offset);
112✔
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);
2!
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,
40!
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);
40!
134

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

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

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

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

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

175
multi_polygon_type_fp GerberImporter::make_oval(point_type_fp center, coordinate_type_fp width, coordinate_type_fp height,
3✔
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) {
3✔
180
    // The oval is more wide than tall.
181
    start.x(start.x() - (width - height)/2);
1✔
182
    end.x(end.x() + (width - height)/2);
1✔
183
  } else if (width < height) {
2✔
184
    // The oval is more tall than wide.
185
    start.y(start.y() - (height - width)/2);
1✔
186
    end.y(end.y() + (height - width)/2);
1✔
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);
1✔
191
  }
192

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

206
  if (hole_diameter > 0) {
2!
207
    multi_polygon_type_fp hole = make_circle(center, hole_diameter, 0);
2!
208
    multi_polygon_type_fp hole_fp;
209
    bg::convert(hole, hole_fp);
210
    oval = oval - hole_fp;
4!
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,
8✔
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}) {
24✔
222
    for (double w : {-1, 1}) {
48✔
223
      for (double h : {-1, 1}) {
96✔
224
        all_points.push_back(point_type_fp(p.x()+w*width/2, p.y()+h*height/2));
64!
225
      }
226
    }
227
  }
228
  multi_polygon_type_fp hull;
229
  hull.resize(1);
8!
230
  bg::convex_hull(all_points, hull[0]);
231
  return hull;
8✔
232
}
233

234
double get_angle(point_type_fp start, point_type_fp center, point_type_fp stop, bool clockwise) {
35✔
235
  double start_angle = atan2(start.y() - center.y(), start.x() - center.x());
35✔
236
  double  stop_angle = atan2( stop.y() - center.y(),  stop.x() - center.x());
35✔
237
  double delta_angle = stop_angle - start_angle;
35✔
238
  while (clockwise && delta_angle > 0) {
38✔
239
    delta_angle -= 2 * bg::math::pi<double>();
3✔
240
  }
241
  while (!clockwise && delta_angle < 0) {
50✔
242
    delta_angle += 2 * bg::math::pi<double>();
15✔
243
  }
244
  return delta_angle;
35✔
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,
19✔
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) {
19✔
255
    definitely_sq = true; // Definiltely single-quadrant.
256
  }
257
  if (start.x() == stop.x() && start.y() == stop.y()) {
19✔
258
    // Either 0 or 360, depending on mq/sq.
259
    if (definitely_sq) {
9✔
260
      delta_angle = 0;
261
    } else {
262
      if (std::abs(delta_angle) < bg::math::pi<double>()) {
7!
263
        delta_angle = 0;
264
      } else {
265
        delta_angle = bg::math::pi<double>() * 2;
266
        if (clockwise) {
7✔
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};
10✔
273
    const coordinate_type_fp i = std::abs(center.x() - start.x());
10✔
274
    const coordinate_type_fp j = std::abs(center.y() - start.y());
10✔
275
    delta_angle = get_angle(start, center, stop, clockwise);
10✔
276
    for (const double& i_sign : signs_to_try) {
25✔
277
      for (const double& j_sign : signs_to_try) {
40✔
278
        const point_type_fp current_center = point_type_fp(start.x() + i*i_sign, start.y() + j*j_sign);
25✔
279
        double new_angle = get_angle(start, current_center, stop, clockwise);
25✔
280
        if (std::abs(new_angle) > bg::math::pi<double>()) {
25✔
281
          continue; // Wrong side.
11✔
282
        }
283
        if (std::abs(bg::distance(start, current_center) - bg::distance(stop, current_center)) <
28✔
284
            std::abs(bg::distance(start,         center) - bg::distance(stop,         center))) {
14✔
285
          // This is closer to the center line so it's a better choice.
286
          delta_angle = new_angle;
287
          center = current_center;
1✔
288
        }
289
      }
290
    }
291
  }
10✔
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());
19✔
295
  const double stop_angle = start_angle + delta_angle;
19✔
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;
19✔
299
  auto const radius_points = std::max(32. / 2 / bg::math::pi<double>(), average_radius / max_arc_segment_length);
38!
300
  const unsigned int steps = std::ceil(std::abs(delta_angle) * radius_points) + 1; // One more for the end point.
19!
301
  linestring_type_fp linestring;
302
  linestring.reserve(steps);
19!
303
  // First place the start;
304
  linestring.push_back(start);
19!
305
  for (unsigned int i = 1; i < steps - 1; i++) {
13,020✔
306
    const double stop_weight = double(i) / (steps - 1);
13,001✔
307
    const double start_weight = 1 - stop_weight;
13,001✔
308
    const double current_angle = start_angle*start_weight + stop_angle*stop_weight;
13,001✔
309
    const double current_radius = start_radius*start_weight + stop_radius*stop_weight;
13,001✔
310
    linestring.push_back(point_type_fp(cos(current_angle) * current_radius + center.x(),
13,001✔
311
                                       sin(current_angle) * current_radius + center.y()));
13,001!
312
  }
313
  linestring.push_back(stop);
19!
314
  return linestring;
19✔
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 {
1,082✔
331
  mp_pair() {}
332
  mp_pair(multi_polygon_type_fp shapes) : shapes(shapes) {}
221!
333
  mp_pair(multi_polygon_type_fp shapes,
21✔
334
          multi_polygon_type_fp filled_closed_lines) :
21✔
335
    shapes(shapes),
336
    filled_closed_lines(filled_closed_lines) {}
21✔
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) {
30✔
344
  if (multi_draws.size() == 0) {
30✔
345
    return multi_polygon_type_fp();
1✔
346
  } else if (multi_draws.size() == 1) {
29✔
347
    return multi_draws.front();
8✔
348
  }
349
  vector<multi_polygon_type_fp> shapes;
350
  vector<multi_polygon_type_fp> filled_closed_lines;
351
  shapes.reserve(multi_draws.size());
21!
352
  filled_closed_lines.reserve(multi_draws.size());
21!
353
  for (const auto& multi_draw : multi_draws) {
252✔
354
    shapes.push_back(multi_draw.shapes);
231!
355
    filled_closed_lines.push_back(multi_draw.filled_closed_lines);
231!
356
  }
357
  return mp_pair(sum(shapes), symdiff(filled_closed_lines));
42!
358
}
21✔
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,
46✔
371
                                      multi_polygon_type_fp mp_pair::* member, bool xor_layers) {
372
  multi_polygon_type_fp output;
373
  vector<ring_type_fp> rings;
374

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

383
      to_sum.reserve(stepAndRepeat.X * stepAndRepeat.Y);
60!
384
      for (int sr_x = 0; sr_x < stepAndRepeat.X; sr_x++) {
128✔
385
        for (int sr_y = 0; sr_y < stepAndRepeat.Y; sr_y++) {
168✔
386
          if (sr_x == 0 && sr_y == 0) {
100✔
387
            continue; // Already got this one.
60✔
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));
40!
393
          to_sum.push_back(translated_draws);
40!
394
        }
395
      }
396
      draws = sum(to_sum);
60!
397
    }
60✔
398

399
    if (xor_layers) {
60✔
400
      output = output ^ draws;
2!
401
    } else if (polarity == GERBV_POLARITY_DARK) {
59✔
402
      output = output + draws;
102!
403
    } else if (polarity == GERBV_POLARITY_CLEAR) {
8!
404
      output = output - draws;
16!
405
    } else {
406
      unsupported_polarity_throw_exception();
×
407
    }
408
  }
409
  return output;
46✔
410
}
46✔
411

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

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

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

441
  multi_polygon_type_fp rect1 = make_rectangle(center, gap_width, 2 * external_diameter, 0);
2!
442
  multi_polygon_type_fp rect2 = make_rectangle(center, 2 * external_diameter, gap_width, 0);
2!
443
  return ring - rect1 - rect2;
6!
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) {
207✔
451
  for (auto start = ls.cbegin(); start != ls.cend(); start++) {
20,063✔
452
    for (auto end = std::next(start); end != ls.cend(); end++) {
17,416,204✔
453
      if (bg::equals(*start, *end)) {
17,396,348✔
454
        if (start == ls.cbegin() && end == std::prev(ls.cend())) {
295✔
455
          continue; // This is just the entire ls, no need to try to recurse here.
126✔
456
        }
457
        linestring_type_fp inner(start, end); // Build the ring that we've found.
42!
458
        inner.push_back(inner.front()); // Close the ring.
42!
459

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

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

484
multi_polygon_type_fp simplify_cutins(const ring_type_fp& ring) {
56✔
485
  if (ring.size() < 4) {
56✔
486
    return {};
487
  }
488
  auto new_mls = eulerian_paths::make_eulerian_paths({linestring_type_fp(ring.cbegin(), ring.cend())}, true, false);
108!
489
  if (new_mls.size() == 0) {
36✔
490
    return {};
491
  }
492
  if (new_mls.size() != 1 || new_mls[0].front() != new_mls[0].back()) {
35!
493
    cerr << "Internal error in gerberimporter" << endl;
×
494
    cerr << bg::wkt(ring) << std::endl;
×
495
    cerr << bg::wkt(new_mls) << std::endl;
×
496
    throw gerber_exception();
×
497
  }
498
  ring_type_fp new_ring(new_mls[0].cbegin(), new_mls[0].cend());
35✔
499
  vector<ring_type_fp> all_rings = get_all_rings(new_ring);
35!
500
  multi_polygon_type_fp ret;
501
  for (auto r : all_rings) {
107✔
502
    const auto this_area = bg::area(r);
503
    if (r.size() < 4 || this_area == 0) {
72!
504
      continue; // No area so ignore it.
505
    }
506
    auto correct_r = r;
507
    bg::correct(correct_r);
508
    ret = ret ^ multi_polygon_type_fp{{correct_r}};
212!
509
  }
510
  return ret;
511
}
230!
512

513
map<int, multi_polygon_type_fp> GerberImporter::generate_apertures_map(const gerbv_aperture_t * const apertures[]) const {
23✔
514
  const point_type_fp origin (0, 0);
23✔
515
  map<int, multi_polygon_type_fp> apertures_map;
516
  for (int i = 0; i < APERTURE_MAX; i++) {
230,000✔
517
    const gerbv_aperture_t * const aperture = apertures[i];
229,977✔
518

519
    if (aperture) {
229,977✔
520
      const double * const parameters = aperture->parameter;
521
      multi_polygon_type_fp input;
522

523
      switch (aperture->type) {
70!
524
        case GERBV_APTYPE_NONE:
×
525
          continue;
×
526

527
        case GERBV_APTYPE_CIRCLE:
29✔
528
          input = make_circle(origin,
29!
529
                              parameters[0],
530
                              parameters[1],
531
                              parameters[2]);
532
          break;
29✔
533
        case GERBV_APTYPE_RECTANGLE:
6✔
534
          input = make_rectangle(origin,
6!
535
                                 parameters[0],
536
                                 parameters[1],
537
                                 parameters[2]);
538
          break;
6✔
539
        case GERBV_APTYPE_OVAL:
3✔
540
          input = make_oval(origin,
3!
541
                            parameters[0],
542
                            parameters[1],
543
                            parameters[2]);
544
          break;
3✔
545
        case GERBV_APTYPE_POLYGON:
1✔
546
          input = make_regular_polygon(origin,
×
547
                                       parameters[0],
548
                                       parameters[1],
1!
549
                                       parameters[2],
550
                                       parameters[3]);
551
          break;
1✔
552
        case GERBV_APTYPE_MACRO:
31✔
553
          if (aperture->simplified) {
31!
554
            // I thikn that this means that the marco's variables are substitued.
555
            const gerbv_simplified_amacro_t *simplified_amacro = aperture->simplified;
556

557
            while (simplified_amacro) {
174✔
558
              const double * const parameters = simplified_amacro->parameter;
143!
559
              double rotation;
560
              int polarity;
561
              multi_polygon_type_fp mpoly;
562
              multi_polygon_type_fp mpoly_rotated;
563

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

651
              if (polarity == 0) {
143!
652
                input = input - mpoly_rotated;
×
653
              } else {
654
                input = input + mpoly_rotated;
286!
655
              }
656
              simplified_amacro = simplified_amacro->next;
143✔
657
            }
658
          } else {
659
            cerr << "Macro aperture " << i << " is not simplified: skipping" << endl;
×
660
            continue;
×
661
          }
662
          break;
663
        case GERBV_APTYPE_MACRO_CIRCLE:
×
664
        case GERBV_APTYPE_MACRO_OUTLINE:
665
        case GERBV_APTYPE_MACRO_POLYGON:
666
        case GERBV_APTYPE_MACRO_MOIRE:
667
        case GERBV_APTYPE_MACRO_THERMAL:
668
        case GERBV_APTYPE_MACRO_LINE20:
669
        case GERBV_APTYPE_MACRO_LINE21:
670
        case GERBV_APTYPE_MACRO_LINE22:
671
          cerr << "Macro aperture during non-macro drawing: skipping" << endl;
×
672
          continue;
×
673
        default:
×
674
          cerr << "Unrecognized aperture: skipping" << endl;
×
675
          continue;
×
676
      }
×
677
      apertures_map[i] = input;
70!
678
    }
679
  }
680
  return apertures_map;
23✔
681
}
682

683
bool layers_equivalent(const gerbv_layer_t* const layer1, const gerbv_layer_t* const layer2) {
4,851✔
684
  const gerbv_step_and_repeat_t& sr1 = layer1->stepAndRepeat;
685
  const gerbv_step_and_repeat_t& sr2 = layer2->stepAndRepeat;
686

687
  return (layer1->polarity == layer2->polarity &&
4,851✔
688
          sr1.X == sr2.X &&
4,845✔
689
          sr1.Y == sr2.Y &&
4,844✔
690
          sr1.dist_X == sr2.dist_X &&
9,695!
691
          sr1.dist_Y == sr2.dist_Y);
4,844!
692
}
693

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

747

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

758
  vector<pair<const gerbv_layer_t *, vector<mp_pair>>> layers(1);
23!
759

760
  gerbv_image_t *gerber = project->file[0]->image;
23✔
761

762
  if (gerber->info->polarity != GERBV_POLARITY_POSITIVE) {
23!
763
    unsupported_polarity_throw_exception();
×
764
  }
765

766
  const map<int, multi_polygon_type_fp> apertures_map = generate_apertures_map(gerber->aperture);
23!
767
  layers.front().first = gerber->netlist->layer;
23✔
768

769

770
  map<coordinate_type_fp, multi_linestring_type_fp> linear_circular_paths;
771
  for (gerbv_net_t *currentNet = gerber->netlist; currentNet; currentNet = currentNet->next) {
4,874✔
772
    const point_type_fp start (currentNet->start_x, currentNet->start_y);
773
    const point_type_fp stop (currentNet->stop_x, currentNet->stop_y);
774
    const double * const parameters = gerber->aperture[currentNet->aperture]->parameter;
4,851✔
775
    multi_polygon_type_fp mpoly;
776

777
    if (!layers_equivalent(currentNet->layer, layers.back().first)) {
4,851✔
778
      if (render_paths_to_shapes) {
7!
779
        // About to start a new layer, render all the linear_circular_paths so far.
780
        for (const auto& diameter_and_path : linear_circular_paths) {
7!
781
          layers.back().second.push_back(paths_to_shapes(diameter_and_path.first, diameter_and_path.second, fill_closed_lines));
×
782
        }
783
        linear_circular_paths.clear();
784
      }
785
      layers.resize(layers.size() + 1);
7!
786
      layers.back().first = currentNet->layer;
7✔
787
    }
788

789
    vector<mp_pair>& draws = layers.back().second;
4,851✔
790

791
    if (currentNet->interpolation == GERBV_INTERPOLATION_LINEARx1) {
4,851!
792
      if (currentNet->aperture_state == GERBV_APERTURE_STATE_ON) {
4,792✔
793
        if (contour) {
4,576✔
794
          if (region.empty()) {
4,254✔
795
            bg::append(region, start);
796
          }
797
          bg::append(region, stop);
798
        } else {
799
          if (gerber->aperture[currentNet->aperture]->type == GERBV_APTYPE_CIRCLE) {
322✔
800
            // These are common and too slow to merge one by one so we put them
801
            // all together and then do one big union at the end.
802
            const double diameter = parameters[0];
314!
803
            linestring_type_fp segment;
804
            segment.push_back(start);
314!
805
            segment.push_back(stop);
314!
806
            linear_circular_paths[diameter].push_back(segment);
314!
807
          } else if (gerber->aperture[currentNet->aperture]->type == GERBV_APTYPE_RECTANGLE) {
8!
808
            mpoly = linear_draw_rectangular_aperture(start, stop, parameters[0],
16!
809
                                                     parameters[1]);
810
            draws.push_back(mpoly);
8✔
811
          } else {
812
            cerr << ("Drawing with an aperture different from a circle "
813
                     "or a rectangle is forbidden by the Gerber standard; skipping.")
×
814
                 << endl;
815
          }
816
        }
817
      } else if (currentNet->aperture_state == GERBV_APERTURE_STATE_FLASH) {
216✔
818
        if (contour) {
173!
819
          cerr << ("D03 during contour mode is forbidden by the Gerber "
820
                   "standard; skipping") << endl;
×
821
        } else {
822
          const auto aperture_mpoly = apertures_map.find(currentNet->aperture);
173!
823

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

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