gerbv
drill.c
Go to the documentation of this file.
1 /*
2  * gEDA - GNU Electronic Design Automation
3  * drill.c
4  * Copyright (C) 2000-2006 Andreas Andersson
5  *
6  * $Id$
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
28 /*
29  * 21 Feb 2007 patch for metric drill files:
30  * 1) METRIC/INCH commands (partly) parsed to define units of the header
31  * 2) units of the header and the program body are independent
32  * 3) ICI command parsed in the header
33  */
34 
35 #include "gerbv.h"
36 
37 #include <stdlib.h>
38 
39 #ifdef HAVE_STRING_H
40 #include <string.h>
41 #endif
42 
43 #include <math.h> /* pow(), atan2(), hypot(), cos(), sin(), fabs(), floor() */
44 #include <float.h> /* DBL_EPSILON */
45 #include <ctype.h>
46 
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 
50 #ifdef HAVE_UNISTD_H
51 #include <unistd.h>
52 #endif
53 
54 #include "attribute.h"
55 #include "common.h"
56 #include "drill.h"
57 #include "drill_stats.h"
58 #include "gerber.h"
59 
60 /* DEBUG printing. #define DEBUG 1 in config.h to use this fcn. */
61 #undef DPRINTF
62 #define DPRINTF(...) do { if (DEBUG) printf(__VA_ARGS__); } while (0)
63 
64 #define MAXL 200
65 #define DRILL_READ_DOUBLE_SIZE 32
66 
67 typedef enum {
68  DRILL_NONE, DRILL_HEADER, DRILL_DATA
69 } drill_file_section_t;
70 
71 typedef enum {
72  DRILL_MODE_ABSOLUTE, DRILL_MODE_INCREMENTAL
73 } drill_coordinate_mode_t;
74 
75 typedef enum {
76  FMT_00_0000 /* INCH */,
77  FMT_000_000 /* METRIC 6-digit, 1 um */,
78  FMT_000_00 /* METRIC 5-digit, 10 um */,
79  FMT_0000_00 /* METRIC 6-digit, 10 um */,
80  FMT_USER /* User defined format */
81 } number_fmt_t;
82 
83 typedef struct drill_pattern_entry {
84  double x;
85  double y;
86  double prev_x; /* start point for route segments */
87  double prev_y;
88  int tool;
89  gboolean is_route; /* FALSE = drill hole, TRUE = route segment */
90 } drill_pattern_entry_t;
91 
92 typedef struct drill_state {
93  double curr_x;
94  double curr_y;
95  int current_tool;
96  drill_file_section_t curr_section;
97  drill_coordinate_mode_t coordinate_mode;
98  double origin_x;
99  double origin_y;
100  gerbv_unit_t unit;
101  /* number_format is used throughout the file itself.
102 
103  header_number_format is used to parse the tool definition C
104  codes within the header. It is fixed to FMT_00_0000 for INCH
105  measures, and FMT_000_000 (1 um resolution) for metric
106  measures. */
107  number_fmt_t number_format, header_number_format;
108  /* Used as a backup when temporarily switching to INCH. */
109  number_fmt_t backup_number_format;
110 
111  /* 0 means we don't try to autodetect any of the other values */
112  int autod;
113 
114  /* in FMT_USER this specifies the number of digits before the
115  * decimal point when doing trailing zero suppression. Otherwise
116  * it is the number of digits *after* the decimal
117  * place in the file
118  */
119  int decimals;
120 
121  /* When FILE_FORMAT=N:M is parsed, stores N (digits before decimal).
122  * Used to correctly set decimals when trailing zero suppression is
123  * later specified via INCH,LZ or METRIC,LZ, because decimals must be
124  * N (not M) in FMT_USER + trailing suppression mode. */
125  int digits_before;
126 
127  /* Routing mode state (G00/G01 + M15/M16/M17) */
128  drill_g_code_t route_mode; /* DRILL_G_DRILL (default), DRILL_G_ROUT, or DRILL_G_LINEARMOVE */
129  gboolean tool_down; /* TRUE after M15, FALSE after M16/M17 */
130 
131  /* Arc center offsets for G02/G03 (I/J values, relative to start) */
132  double delta_cp_x;
133  double delta_cp_y;
134 
135  /* Arc radius for G02/G03 (A value) — alternative to I/J */
136  double arc_radius;
137  gboolean found_arc_radius;
138 
139  /* Pattern recording state (M25/M01/M02) */
140  gboolean in_pattern;
141  GArray *pattern_buffer; /* Array of drill_pattern_entry_t */
142 
143  /* Axis transform state (M70/M80/M90) — toggles */
144  gboolean swap_axis; /* M70 */
145  gboolean mirror_x; /* M80 */
146  gboolean mirror_y; /* M90 */
147 
148 } drill_state_t;
149 
150 /* Local function prototypes */
151 static drill_g_code_t drill_parse_G_code(gerb_file_t *fd,
152  gerbv_image_t *image, unsigned int file_line);
153 static drill_m_code_t drill_parse_M_code(gerb_file_t *fd, drill_state_t *state,
154  gerbv_image_t *image, unsigned int file_line);
155 static int drill_parse_T_code(gerb_file_t *fd, drill_state_t *state,
156  gerbv_image_t *image, unsigned int file_line);
157 static int drill_parse_header_is_metric(gerb_file_t *fd, drill_state_t *state,
158  gerbv_image_t *image, unsigned int file_line);
159 static int drill_parse_header_is_metric_comment(gerb_file_t *fd, drill_state_t *state,
160  gerbv_image_t *image, unsigned int file_line);
161 static int drill_parse_header_is_inch(gerb_file_t *fd, drill_state_t *state,
162  gerbv_image_t *image, unsigned int file_line);
163 static int drill_parse_header_is_ici(gerb_file_t *fd, drill_state_t *state,
164  gerbv_image_t *image, unsigned int file_line);
165 static void drill_parse_coordinate(gerb_file_t *fd, char firstchar,
166  gerbv_image_t *image, drill_state_t *state,
167  unsigned int file_line);
168 static drill_state_t *new_state(void);
169 static gerbv_net_t *drill_add_route_segment(gerbv_image_t *image,
170  drill_state_t *state,
171  gerbv_net_t *curr_net,
172  double prev_x, double prev_y);
173 static gerbv_net_t *drill_add_arc_segment(gerbv_image_t *image,
174  drill_state_t *state,
175  gerbv_drill_stats_t *stats,
176  gerbv_net_t *curr_net,
177  double prev_x, double prev_y);
178 static gerbv_net_t *drill_add_circle_segment(gerbv_image_t *image,
179  drill_state_t *state,
180  gerbv_drill_stats_t *stats,
181  gerbv_net_t *curr_net,
182  double start_x, double start_y,
183  double center_x, double center_y,
184  gboolean cw);
185 static double read_double(gerb_file_t *fd, number_fmt_t fmt,
186  gerbv_omit_zeros_t omit_zeros, int decimals);
187 static void eat_line(gerb_file_t *fd);
188 static void eat_whitespace(gerb_file_t *fd);
189 static char *get_line(gerb_file_t *fd);
190 static int file_check_str(gerb_file_t *fd, const char *str);
191 
192 /* -------------------------------------------------------------- */
193 /* This is the list of specific attributes a drill file may have from
194  * the point of view of parsing it.
195  */
196 
197 enum {
198  SUP_NONE = 0,
199  SUP_LEAD,
200  SUP_TRAIL
201 };
202 
203 static const char *suppression_list[] = {
204  N_("None"),
205  N_("Leading"),
206  N_("Trailing"),
207  0
208 };
209 
210 static const char *units_list[] = {
211  N_("inch"),
212  N_("mm"),
213  0
214 };
215 
216 enum {
217  HA_auto = 0,
218  HA_suppression,
219  HA_xy_units,
220  HA_digits,
221 #if 0
222  HA_tool_units,
223 #endif
224 };
225 
226 static gerbv_HID_Attribute drill_attribute_list[] = {
227  /* This should be first */
228  {N_("autodetect"), N_("Try to autodetect the file format"),
229  HID_Boolean, 0, 0, {1, 0, 0}, 0, 0, 0},
230 
231  {N_("zero_suppression"), N_("Zero suppression"),
232  HID_Enum, 0, 0, {0, 0, 0}, suppression_list, 0, 0},
233 
234  {N_("units"), N_("Length units"),
235  HID_Enum, 0, 0, {0, 0, 0}, units_list, 0, 0},
236 
237  {N_("digits"), N_("Number of digits. For trailing zero suppression,"
238  " this is the number of digits before the decimal point. "
239  "Otherwise this is the number of digits after the decimal point."),
240  HID_Integer, 0, 20, {5, 0, 0}, 0, 0, 0},
241 
242 #if 0
243  {"tool_units", "Tool size units",
244  HID_Enum, 0, 0, {0, 0, 0}, units_list, 0, 0},
245 #endif
246 };
247 
248 void
249 drill_attribute_merge (gerbv_HID_Attribute *dest, int ndest, gerbv_HID_Attribute *src, int nsrc)
250 {
251  int i, j;
252 
253  /* Here is a brain dead merge algorithm which should make anyone cringe.
254  * Still, it is simple and we won't merge many attributes and not
255  * many times either.
256  */
257 
258  for (i = 0 ; i < nsrc ; i++) {
259  /* see if our destination wants this attribute */
260  j = 0;
261  while (j < ndest && strcmp (src[i].name, dest[j].name) != 0)
262  j++;
263 
264  /* if we wanted it and it is the same type, copy it over */
265  if (j < ndest && src[i].type == dest[j].type) {
266  dest[j].default_val = src[i].default_val;
267  } else {
268  GERB_MESSAGE("Ignoring \"%s\" attribute for drill file", src[i].name);
269  }
270  }
271 }
272 
273 static void
274 drill_update_image_info_min_max_from_bbox(gerbv_image_info_t *info,
275  const gerbv_render_size_t *bbox)
276 {
277  info->min_x = MIN(info->min_x, bbox->left);
278  info->min_y = MIN(info->min_y, bbox->bottom);
279  info->max_x = MAX(info->max_x, bbox->right);
280  info->max_y = MAX(info->max_y, bbox->top);
281 }
282 
283 /*
284  * Adds the actual drill hole to the drawing
285  */
286 static gerbv_net_t *
287 drill_add_drill_hole (gerbv_image_t *image, drill_state_t *state,
288  gerbv_net_t *curr_net)
289 {
290  gerbv_render_size_t *bbox;
291  double r;
292 
293  /* Add one to drill stats for the current tool */
294  drill_stats_increment_drill_counter(image->drill_stats->drill_list,
295  state->current_tool);
296 
297  curr_net->next = g_new0(gerbv_net_t, 1);
298  if (curr_net->next == NULL) {
299  GERB_FATAL_ERROR("malloc curr_net->next failed in %s()",
300  __FUNCTION__);
301  }
302 
303  curr_net = curr_net->next;
304  curr_net->layer = image->layers;
305  curr_net->state = image->states;
306  curr_net->start_x = state->curr_x;
307  curr_net->start_y = state->curr_y;
308  /* KLUDGE. This function isn't allowed to return anything
309  but inches */
310  if(state->unit == GERBV_UNIT_MM) {
311  curr_net->start_x /= 25.4;
312  curr_net->start_y /= 25.4;
313  /* KLUDGE. All images, regardless of input format,
314  are returned in INCH format */
315  curr_net->state->unit = GERBV_UNIT_INCH;
316  }
317 
318  curr_net->stop_x = curr_net->start_x - state->origin_x;
319  curr_net->stop_y = curr_net->start_y - state->origin_y;
320  curr_net->aperture = state->current_tool;
322 
323  /* Check if aperture is set. Ignore the below instead of
324  causing SEGV... */
325  if(image->aperture[state->current_tool] == NULL) {
326  return curr_net;
327  }
328 
329  bbox = &curr_net->boundingBox;
330  r = image->aperture[state->current_tool]->parameter[0] / 2;
331 
332  /* Set boundingBox */
333  bbox->left = curr_net->start_x - r;
334  bbox->right = curr_net->start_x + r;
335  bbox->bottom = curr_net->start_y - r;
336  bbox->top = curr_net->start_y + r;
337 
338  drill_update_image_info_min_max_from_bbox(image->info, bbox);
339 
340  return curr_net;
341 }
342 
343 /*
344  * Adds a routed line segment (G00/G01 with tool down) to the drawing.
345  * Similar to drill_add_drill_hole but creates a line (APERTURE_STATE_ON)
346  * instead of a flash, with start and stop coordinates.
347  */
348 static gerbv_net_t *
349 drill_add_route_segment(gerbv_image_t *image, drill_state_t *state,
350  gerbv_net_t *curr_net, double prev_x, double prev_y)
351 {
352  gerbv_render_size_t *bbox;
353  double r;
354  double start_x, start_y, stop_x, stop_y;
355 
356  curr_net->next = g_new0(gerbv_net_t, 1);
357  if (curr_net->next == NULL) {
358  GERB_FATAL_ERROR("malloc curr_net->next failed in %s()",
359  __FUNCTION__);
360  }
361 
362  curr_net = curr_net->next;
363  curr_net->layer = image->layers;
364  curr_net->state = image->states;
365 
366  start_x = prev_x;
367  start_y = prev_y;
368  stop_x = state->curr_x;
369  stop_y = state->curr_y;
370 
371  if (state->unit == GERBV_UNIT_MM) {
372  /* Convert to inches -- internal units */
373  start_x /= 25.4;
374  start_y /= 25.4;
375  stop_x /= 25.4;
376  stop_y /= 25.4;
377  curr_net->state->unit = GERBV_UNIT_INCH;
378  }
379 
380  curr_net->start_x = start_x;
381  curr_net->start_y = start_y;
382  curr_net->stop_x = stop_x;
383  curr_net->stop_y = stop_y;
384  curr_net->aperture = state->current_tool;
387 
388  /* Check if aperture is set. Ignore the below instead of
389  causing SEGV... */
390  if (image->aperture[state->current_tool] == NULL) {
391  return curr_net;
392  }
393 
394  bbox = &curr_net->boundingBox;
395  r = image->aperture[state->current_tool]->parameter[0] / 2;
396 
397  /* Set boundingBox covering both endpoints + tool radius */
398  bbox->left = MIN(start_x, stop_x) - r;
399  bbox->right = MAX(start_x, stop_x) + r;
400  bbox->bottom = MIN(start_y, stop_y) - r;
401  bbox->top = MAX(start_y, stop_y) + r;
402 
403  drill_update_image_info_min_max_from_bbox(image->info, bbox);
404 
405  return curr_net;
406 }
407 
408 /*
409  * Adds a routed arc segment (G02/G03 with tool down) to the drawing.
410  */
411 static gerbv_net_t *
412 drill_add_arc_segment(gerbv_image_t *image, drill_state_t *state,
413  gerbv_drill_stats_t *stats, gerbv_net_t *curr_net,
414  double prev_x, double prev_y)
415 {
416  gerbv_render_size_t *bbox;
417  double r;
418  double start_x, start_y, stop_x, stop_y;
419  double delta_cp_x, delta_cp_y;
420  int cw;
421 
422  curr_net->next = g_new0(gerbv_net_t, 1);
423  if (curr_net->next == NULL)
424  GERB_FATAL_ERROR("malloc curr_net->next failed in %s()",
425  __FUNCTION__);
426 
427  curr_net = curr_net->next;
428  curr_net->layer = image->layers;
429  curr_net->state = image->states;
430 
431  start_x = prev_x;
432  start_y = prev_y;
433  stop_x = state->curr_x;
434  stop_y = state->curr_y;
435  delta_cp_x = state->delta_cp_x;
436  delta_cp_y = state->delta_cp_y;
437 
438  if (state->unit == GERBV_UNIT_MM) {
439  /* Convert to inches -- internal units */
440  start_x /= 25.4;
441  start_y /= 25.4;
442  stop_x /= 25.4;
443  stop_y /= 25.4;
444  delta_cp_x /= 25.4;
445  delta_cp_y /= 25.4;
446  curr_net->state->unit = GERBV_UNIT_INCH;
447  }
448 
449  cw = (state->route_mode == DRILL_G_CWMOVE);
450 
451  if (state->found_arc_radius) {
452  /* A-parameter arc: convert radius to center offsets.
453  * Given start (sx,sy), end (ex,ey), radius r, direction CW/CCW,
454  * solve for the arc center and derive delta_cp from it. */
455  double radius = state->arc_radius;
456  double dx, dy, d, h, mx, my, px, py, sign, cp_x, cp_y;
457 
458  if (state->unit == GERBV_UNIT_MM)
459  radius /= 25.4;
460 
461  dx = stop_x - start_x;
462  dy = stop_y - start_y;
463  d = hypot(dx, dy);
464 
465  if (d < DBL_EPSILON) {
466  /* Start == end: full circle. Center is offset by radius
467  * perpendicular to the X axis (arbitrary choice). */
468  delta_cp_x = 0;
469  delta_cp_y = fabs(radius);
470  } else if (d > 2.0 * fabs(radius)) {
471  /* Chord longer than diameter — clamp radius to minimum.
472  * This avoids sqrt of a negative number while still
473  * producing a reasonable (semicircle) arc. */
474  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_WARNING, -1,
475  _("Arc radius %.4f too small for chord %.4f; "
476  "clamping to semicircle"),
477  fabs(radius), d);
478  delta_cp_x = dx / 2.0;
479  delta_cp_y = dy / 2.0;
480  } else {
481  h = sqrt(radius * radius - (d / 2.0) * (d / 2.0));
482  mx = (start_x + stop_x) / 2.0;
483  my = (start_y + stop_y) / 2.0;
484 
485  /* Perpendicular unit vector to the chord */
486  px = -dy / d;
487  py = dx / d;
488 
489  /* CW arc: center is to the right of start->end vector.
490  * CCW arc: center is to the left. */
491  sign = cw ? -1.0 : 1.0;
492 
493  cp_x = mx + sign * h * px;
494  cp_y = my + sign * h * py;
495 
496  delta_cp_x = cp_x - start_x;
497  delta_cp_y = cp_y - start_y;
498  }
499 
500  state->found_arc_radius = FALSE;
501  }
502 
503  curr_net->start_x = start_x;
504  curr_net->start_y = start_y;
505  curr_net->stop_x = stop_x;
506  curr_net->stop_y = stop_y;
507  curr_net->aperture = state->current_tool;
509 
512 
513  curr_net->cirseg = g_new0(gerbv_cirseg_t, 1);
514  if (curr_net->cirseg == NULL)
515  GERB_FATAL_ERROR("malloc cirseg failed in %s()", __FUNCTION__);
516 
517  calc_cirseg_mq(curr_net, cw, delta_cp_x, delta_cp_y);
518 
519  /* Check if aperture is set. Skip bbox computation if not. */
520  if (image->aperture[state->current_tool] == NULL)
521  return curr_net;
522 
523  /* Compute bounding box from arc geometry */
524  bbox = &curr_net->boundingBox;
525  r = image->aperture[state->current_tool]->parameter[0] / 2;
526 
527  {
528  double ang1, ang2, step_pi_2, x, y;
529 
530  ang1 = DEG2RAD(MIN(curr_net->cirseg->angle1,
531  curr_net->cirseg->angle2));
532  ang2 = DEG2RAD(MAX(curr_net->cirseg->angle1,
533  curr_net->cirseg->angle2));
534 
535  /* Start arc point */
536  x = curr_net->cirseg->cp_x +
537  curr_net->cirseg->width * cos(ang1) / 2;
538  y = curr_net->cirseg->cp_y +
539  curr_net->cirseg->width * sin(ang1) / 2;
540  bbox->left = x - r;
541  bbox->right = x + r;
542  bbox->bottom = y - r;
543  bbox->top = y + r;
544 
545  /* Middle arc points at each 90-degree axis crossing */
546  for (step_pi_2 = (floor(ang1 / M_PI_2) + 1) * M_PI_2;
547  step_pi_2 < MIN(ang2, ang1 + 2 * M_PI);
548  step_pi_2 += M_PI_2) {
549  x = curr_net->cirseg->cp_x +
550  curr_net->cirseg->width * cos(step_pi_2) / 2;
551  y = curr_net->cirseg->cp_y +
552  curr_net->cirseg->width * sin(step_pi_2) / 2;
553  bbox->left = MIN(bbox->left, x - r);
554  bbox->right = MAX(bbox->right, x + r);
555  bbox->bottom = MIN(bbox->bottom, y - r);
556  bbox->top = MAX(bbox->top, y + r);
557  }
558 
559  /* Stop arc point */
560  x = curr_net->cirseg->cp_x +
561  curr_net->cirseg->width * cos(ang2) / 2;
562  y = curr_net->cirseg->cp_y +
563  curr_net->cirseg->width * sin(ang2) / 2;
564  bbox->left = MIN(bbox->left, x - r);
565  bbox->right = MAX(bbox->right, x + r);
566  bbox->bottom = MIN(bbox->bottom, y - r);
567  bbox->top = MAX(bbox->top, y + r);
568  }
569 
570  drill_update_image_info_min_max_from_bbox(image->info, bbox);
571 
572  return curr_net;
573 }
574 
575 /* -------------------------------------------------------------- */
576 /* Full-circle arc net for G32/G33 canned cycles.
577  * start_x/y is the point on the circle (tool position before G-code),
578  * center_x/y is the circle center. Both in file units. */
579 static gerbv_net_t *
580 drill_add_circle_segment(gerbv_image_t *image, drill_state_t *state,
581  gerbv_drill_stats_t *stats, gerbv_net_t *curr_net,
582  double start_x, double start_y,
583  double center_x, double center_y, gboolean cw)
584 {
585  gerbv_render_size_t *bbox;
586  double r, tool_r;
587  double cx, cy, sx, sy;
588  double start_angle;
589 
590  curr_net->next = g_new0(gerbv_net_t, 1);
591  if (curr_net->next == NULL)
592  GERB_FATAL_ERROR("malloc curr_net->next failed in %s()",
593  __FUNCTION__);
594 
595  curr_net = curr_net->next;
596  curr_net->layer = image->layers;
597  curr_net->state = image->states;
598 
599  sx = start_x;
600  sy = start_y;
601  cx = center_x;
602  cy = center_y;
603 
604  if (state->unit == GERBV_UNIT_MM) {
605  sx /= 25.4;
606  sy /= 25.4;
607  cx /= 25.4;
608  cy /= 25.4;
609  curr_net->state->unit = GERBV_UNIT_INCH;
610  }
611 
612  /* Full circle: start == stop (returns to same point) */
613  curr_net->start_x = sx;
614  curr_net->start_y = sy;
615  curr_net->stop_x = sx;
616  curr_net->stop_y = sy;
617  curr_net->aperture = state->current_tool;
621 
622  /* Populate cirseg for full 360 degree circle */
623  curr_net->cirseg = g_new0(gerbv_cirseg_t, 1);
624  if (curr_net->cirseg == NULL)
625  GERB_FATAL_ERROR("malloc cirseg failed in %s()", __FUNCTION__);
626 
627  r = hypot(sx - cx, sy - cy);
628  curr_net->cirseg->cp_x = cx;
629  curr_net->cirseg->cp_y = cy;
630  curr_net->cirseg->width = r * 2.0;
631  curr_net->cirseg->height = r * 2.0;
632 
633  /* Angle from center to start point */
634  start_angle = RAD2DEG(atan2(sy - cy, sx - cx));
635  if (start_angle < 0)
636  start_angle += 360.0;
637 
638  if (cw) {
639  curr_net->cirseg->angle1 = start_angle;
640  curr_net->cirseg->angle2 = start_angle - 360.0;
641  } else {
642  curr_net->cirseg->angle1 = start_angle;
643  curr_net->cirseg->angle2 = start_angle + 360.0;
644  }
645 
646  /* Bounding box: full circle centered at (cx, cy) with tool radius */
647  if (image->aperture[state->current_tool] == NULL)
648  return curr_net;
649 
650  bbox = &curr_net->boundingBox;
651  tool_r = image->aperture[state->current_tool]->parameter[0] / 2;
652 
653  bbox->left = cx - r - tool_r;
654  bbox->right = cx + r + tool_r;
655  bbox->bottom = cy - r - tool_r;
656  bbox->top = cy + r + tool_r;
657 
658  drill_update_image_info_min_max_from_bbox(image->info, bbox);
659 
660  return curr_net;
661 }
662 
663 /* -------------------------------------------------------------- */
665 parse_drillfile(gerb_file_t *fd, gerbv_HID_Attribute *attr_list, int n_attr, int reload)
666 {
667  drill_state_t *state = NULL;
668  gerbv_image_t *image = NULL;
669  gerbv_net_t *curr_net = NULL;
670  gerbv_HID_Attribute *hid_attrs;
671  int read;
672  gboolean parsing_done = FALSE;
673  gerbv_drill_stats_t *stats;
674  gchar *tmps;
675  unsigned int file_line = 1;
676 
677  /*
678  * many locales redefine "." as "," and so on, so sscanf and strtod
679  * has problems when reading files using %f format.
680  * Fixes bug #1963618 reported by Lorenzo Marcantonio.
681  */
682  setlocale(LC_NUMERIC, "C" );
683 
684  /* Create new image for this layer */
685  DPRINTF("In parse_drillfile, about to create image for this layer\n");
686 
687  image = gerbv_create_image(image, "Excellon Drill File");
688  if (image == NULL) {
689  GERB_FATAL_ERROR("malloc image failed in %s()", __FUNCTION__);
690  }
691 
692  if (reload && attr_list != NULL) {
693  /* FIXME there should probably just be a function to copy an
694  attribute list including using strdup as needed */
695 
696  image->info->n_attr = n_attr;
697  image->info->attr_list = gerbv_attribute_dup(attr_list, n_attr);
698 
699  } else {
700  /* Copy in the default attribute list for drill files. We make a
701  * copy here because we will allow per-layer editing of the
702  * attributes.
703  */
704  image->info->n_attr = sizeof (drill_attribute_list) / sizeof (drill_attribute_list[0]);
705  image->info->attr_list = gerbv_attribute_dup (drill_attribute_list, image->info->n_attr);
706 
707  /* now merge any project attributes */
708  drill_attribute_merge (image->info->attr_list, image->info->n_attr,
709  attr_list, n_attr);
710  }
711 
712  curr_net = image->netlist;
713  curr_net->layer = image->layers;
714  curr_net->state = image->states;
716  stats = gerbv_drill_stats_new();
717  if (stats == NULL) {
718  GERB_FATAL_ERROR("malloc stats failed in %s()", __FUNCTION__);
719  }
720  image->drill_stats = stats;
721 
722  /* Create local state variable to track photoplotter state */
723  state = new_state();
724  if (state == NULL) {
725  GERB_FATAL_ERROR("malloc state failed in %s()", __FUNCTION__);
726  }
727 
728  image->format = g_new0(gerbv_format_t, 1);
729  if (image->format == NULL) {
730  GERB_FATAL_ERROR("malloc format failed in %s()", __FUNCTION__);
731  }
732 
733  image->format->omit_zeros = GERBV_OMIT_ZEROS_UNSPECIFIED;
734 
735  hid_attrs = image->info->attr_list;
736 
737  if (!hid_attrs[HA_auto].default_val.int_value) {
738  state->autod = 0;
739  state->number_format = FMT_USER;
740  state->decimals = hid_attrs[HA_digits].default_val.int_value;
741 
742  if (GERBV_UNIT_MM == hid_attrs[HA_xy_units].default_val.int_value) {
743  state->unit = GERBV_UNIT_MM;
744  }
745 
746  switch (hid_attrs[HA_suppression].default_val.int_value) {
747  case SUP_LEAD:
748  image->format->omit_zeros = GERBV_OMIT_ZEROS_LEADING;
749  break;
750 
751  case SUP_TRAIL:
752  image->format->omit_zeros = GERBV_OMIT_ZEROS_TRAILING;
753  break;
754 
755  default:
756  image->format->omit_zeros = GERBV_OMIT_ZEROS_EXPLICIT;
757  break;
758  }
759  }
760 
761  DPRINTF("%s(): Starting parsing of drill file \"%s\"\n",
762  __FUNCTION__, fd->filename);
763 
764  while (!parsing_done && (read = gerb_fgetc(fd)) != EOF) {
765 
766  switch ((char) read) {
767 
768  case ';' :
769  /* Comment found. Eat rest of line */
770  if (drill_parse_header_is_metric_comment(fd, state, image, file_line)) {
771  break;
772  }
773  tmps = get_line(fd);
774  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
775  _("Comment \"%s\" at line %u in file \"%s\""),
776  tmps, file_line, fd->filename);
777  DPRINTF(" Comment with ';' \"%s\" at line %u\n",
778  tmps, file_line);
779  g_free(tmps);
780  break;
781 
782  case 'A' :
783  /* ATC,ON/OFF — automatic tool change. Machine-only command
784  * found in Zuken CR-8000 Excellon output. Silently ignored,
785  * similar to DETECT,ON/OFF. See issue #93. */
786  gerb_ungetc(fd);
787  tmps = get_line(fd);
788  if (strcmp(tmps, "ATC,ON") == 0 ||
789  strcmp(tmps, "ATC,OFF") == 0) {
790  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
791  _("Ignoring ATC command \"%s\" "
792  "at line %u in file \"%s\""),
793  tmps, file_line, fd->filename);
794  } else {
795  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
796  _("Undefined code \"%s\" "
797  "at line %u in file \"%s\""),
798  tmps, file_line, fd->filename);
799  }
800  g_free(tmps);
801  break;
802 
803  case 'D' :
804  gerb_ungetc (fd);
805  tmps = get_line (fd);
806  if (strcmp (tmps, "DETECT,ON") == 0 ||
807  strcmp (tmps, "DETECT,OFF") == 0) {
808  gchar *tmps2;
809  gchar *tmps3;
810  if (strcmp (tmps, "DETECT,ON") == 0) {
811  tmps3 = "ON";
812  } else {
813  tmps3 = "OFF";
814  }
815 
816  /* broken tool detect on/off. Silently ignored. */
817  if (stats->detect) {
818  tmps2 = g_strdup_printf ("%s\n%s", stats->detect, tmps3);
819  g_free (stats->detect);
820  } else {
821  tmps2 = g_strdup_printf ("%s", tmps3);
822  }
823  stats->detect = tmps2;
824  } else {
825  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
826  _("Unrecognised string \"%s\" in header "
827  "at line %u in file \"%s\""),
828  tmps, file_line, fd->filename);
829  }
830  g_free (tmps);
831  break;
832 
833  case 'F' :
834  gerb_ungetc (fd);
835  tmps = get_line (fd);
836 
837  /* Silently ignore FMAT,2. Not sure what others are allowed */
838  if (0 == strcmp (tmps, "FMAT,2")) {
839  g_free (tmps);
840  break;
841  }
842 
843  if (0 == strcmp (tmps, "FMAT,1")) {
844  gerbv_stats_printf(stats->error_list,
846  _("File in unsupported format 1 "
847  "at line %u in file \"%s\""),
848  file_line, fd->filename);
849 
850  gerbv_destroy_image(image);
851  g_free (tmps);
852 
853  return NULL;
854  }
855 
856  gerbv_stats_printf(stats->error_list,
858  _("Unrecognised string \"%s\" in header "
859  "at line %u in file \"%s\""),
860  tmps, file_line, fd->filename);
861 
862  g_free (tmps);
863  break;
864 
865  case 'G': {
866  /* Most G codes aren't used, for now */
867  drill_g_code_t g_code;
868 
869  switch (g_code = drill_parse_G_code(fd, image, file_line)) {
870 
871  case DRILL_G_DRILL :
872  /* Drill mode - reset routing state */
873  state->route_mode = DRILL_G_DRILL;
874  state->tool_down = FALSE;
875  break;
876 
877  case DRILL_G_ROUT : /* G00 - rapid positioning */
878  state->route_mode = DRILL_G_ROUT;
879  break;
880  case DRILL_G_LINEARMOVE : /* G01 - linear routing */
881  state->route_mode = DRILL_G_LINEARMOVE;
882  break;
883  case DRILL_G_CWMOVE : /* G02 - CW arc routing */
884  state->route_mode = DRILL_G_CWMOVE;
885  break;
886  case DRILL_G_CCWMOVE : /* G03 - CCW arc routing */
887  state->route_mode = DRILL_G_CCWMOVE;
888  break;
889 
890  case DRILL_G_SLOT : {
891  /* Parse drilled slot end coords */
892  gerbv_render_size_t *bbox = &curr_net->boundingBox;
893  double r;
894 
895  if (EOF == (read = gerb_fgetc(fd))) {
896  gerbv_stats_printf(stats->error_list,
898  _("Unexpected EOF found in file \"%s\""),
899  fd->filename);
900  break;
901  }
902 
903  drill_parse_coordinate(fd, read, image, state, file_line);
904 
905  /* Modify last curr_net as drilled slot */
906  curr_net->stop_x = state->curr_x;
907  curr_net->stop_y = state->curr_y;
908 
909  if (state->unit == GERBV_UNIT_MM) {
910  /* Convert to inches -- internal units */
911  curr_net->stop_x /= 25.4;
912  curr_net->stop_y /= 25.4;
913  }
914 
915  r = image->aperture[state->current_tool]->parameter[0]/2;
916 
917  /* Update boundingBox with drilled slot stop_x,y coords */
918  bbox->left = MIN(bbox->left, curr_net->stop_x - r);
919  bbox->right = MAX(bbox->right, curr_net->stop_x + r);
920  bbox->bottom = MIN(bbox->bottom, curr_net->stop_y - r);
921  bbox->top = MAX(bbox->top, curr_net->stop_y + r);
922 
923  drill_update_image_info_min_max_from_bbox(image->info, bbox);
924 
926 
927  break;
928  }
929 
930  case DRILL_G_ROUTSLOT : {
931  /* G87 routed slot: read end XY coordinate, then create
932  * a routed line segment from current position to end */
933  double prev_x = state->curr_x;
934  double prev_y = state->curr_y;
935 
936  if (EOF == (read = gerb_fgetc(fd))) {
937  gerbv_stats_printf(stats->error_list,
939  _("Unexpected EOF found in file \"%s\""),
940  fd->filename);
941  break;
942  }
943 
944  drill_parse_coordinate(fd, read, image, state, file_line);
945 
946  curr_net = drill_add_route_segment(image, state, curr_net,
947  prev_x, prev_y);
948  break;
949  }
950 
951  case DRILL_G_ROUTCIRCLE: /* G32 -- routed CW circle */
952  case DRILL_G_ROUTCIRCLECCW: { /* G33 -- routed CCW circle */
953  double circ_start_x = state->curr_x;
954  double circ_start_y = state->curr_y;
955 
956  if (EOF == (read = gerb_fgetc(fd))) {
957  gerbv_stats_printf(stats->error_list,
959  _("Unexpected EOF found in file \"%s\""),
960  fd->filename);
961  break;
962  }
963 
964  /* Parse center coordinates */
965  drill_parse_coordinate(fd, read, image, state, file_line);
966 
967  /* curr_x/y now holds the center; start is on the circle */
968  curr_net = drill_add_circle_segment(image, state, stats,
969  curr_net, circ_start_x, circ_start_y,
970  state->curr_x, state->curr_y,
971  g_code == DRILL_G_ROUTCIRCLE);
972 
973  /* Restore position (full circle returns to origin) */
974  state->curr_x = circ_start_x;
975  state->curr_y = circ_start_y;
976 
977  break;
978  }
979 
980  case DRILL_G_ABSOLUTE :
981  state->coordinate_mode = DRILL_MODE_ABSOLUTE;
982  break;
983 
984  case DRILL_G_INCREMENTAL :
985  state->coordinate_mode = DRILL_MODE_INCREMENTAL;
986  break;
987 
988  case DRILL_G_ZEROSET :
989  if (EOF == (read = gerb_fgetc(fd))) {
990  gerbv_stats_printf(stats->error_list,
992  _("Unexpected EOF found in file \"%s\""),
993  fd->filename);
994  break;
995  }
996 
997  drill_parse_coordinate(fd, (char)read, image,
998  state, file_line);
999  state->origin_x = state->curr_x;
1000  state->origin_y = state->curr_y;
1001  break;
1002 
1003  case DRILL_G_UNKNOWN:
1004  tmps = get_line(fd);
1005  gerbv_stats_printf(stats->error_list,
1006  GERBV_MESSAGE_ERROR, -1,
1007  _("Unrecognized string \"%s\" found "
1008  "at line %u in file \"%s\""),
1009  tmps, file_line, fd->filename);
1010  g_free(tmps);
1011  break;
1012 
1013  case DRILL_G_OVERRIDETOOLSPEED: /* G07 */
1014  case DRILL_G_VISTOOL: /* G34 */
1015  case DRILL_G_VISSINGLEPOINTOFFSET: /* G35 */
1016  case DRILL_G_VISMULTIPOINTTRANS: /* G36 */
1017  case DRILL_G_VISCANCEL: /* G37 */
1018  case DRILL_G_VISCORRHOLEDRILL: /* G38 */
1019  case DRILL_G_VISAUTOCALIBRATION: /* G39 */
1020  case DRILL_G_CUTTERCOMPOFF: /* G40 */
1021  case DRILL_G_CUTTERCOMPLEFT: /* G41 */
1022  case DRILL_G_CUTTERCOMPRIGHT: /* G42 */
1023  case DRILL_G_VISSINGLEPOINTOFFSETREL: /* G45 */
1024  case DRILL_G_VISMULTIPOINTTRANSREL: /* G46 */
1025  case DRILL_G_VISCANCELREL: /* G47 */
1026  case DRILL_G_VISCORRHOLEDRILLREL: /* G48 */
1027  case DRILL_G_PACKDIP2: /* G81 */
1028  case DRILL_G_PACKDIP: /* G82 */
1029  case DRILL_G_PACK8PINL: /* G83 */
1030  case DRILL_G_CIRLE: /* G84 */
1031  eat_line(fd);
1032  gerbv_stats_printf(stats->error_list,
1033  GERBV_MESSAGE_NOTE, -1,
1034  _("Ignoring machine-only G%02d (%s) "
1035  "at line %u in file \"%s\""),
1036  g_code, _(drill_g_code_name(g_code)),
1037  file_line, fd->filename);
1038  break;
1039 
1040  default:
1041  eat_line(fd);
1042  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
1043  _("Unsupported G%02d (%s) code "
1044  "at line %u in file \"%s\""),
1045  g_code, drill_g_code_name(g_code),
1046  file_line, fd->filename);
1047  break;
1048  }
1049 
1050  break;
1051  }
1052 
1053  case 'I':
1054  gerb_ungetc(fd); /* To compare full string in function or
1055  report full string */
1056  if (drill_parse_header_is_inch(fd, state, image, file_line)) {
1057  break;
1058  }
1059 
1060  if (drill_parse_header_is_ici(fd, state, image, file_line)) {
1061  break;
1062  }
1063 
1064  tmps = get_line(fd);
1065  gerbv_stats_printf(stats->error_list,
1066  GERBV_MESSAGE_ERROR, -1,
1067  _("Unrecognized string \"%s\" found "
1068  "at line %u in file \"%s\""),
1069  tmps, file_line, fd->filename);
1070  g_free(tmps);
1071 
1072  break;
1073 
1074  case 'M': {
1075  int m_code;
1076 
1077  switch (m_code = drill_parse_M_code(fd, state, image, file_line)) {
1078  case DRILL_M_HEADER :
1079  state->curr_section = DRILL_HEADER;
1080  break;
1081  case DRILL_M_HEADEREND :
1082  state->curr_section = DRILL_DATA;
1083 
1084  if (image->format->omit_zeros == GERBV_OMIT_ZEROS_UNSPECIFIED) {
1085  /* Excellon says they default to specify leading
1086  zeros, i.e. omit trailing zeros. The Excellon
1087  files floating around that don't specify the
1088  leading/trailing zeros in the header seem to
1089  contradict to this though.
1090 
1091  XXX We should probably ask the user. */
1092 
1093  gerbv_stats_printf(stats->error_list,
1094  GERBV_MESSAGE_ERROR, -1,
1095  _("End of Excellon header reached "
1096  "but no leading/trailing zero "
1097  "handling specified "
1098  "at line %u in file \"%s\""),
1099  file_line, fd->filename);
1100  gerbv_stats_printf(stats->error_list,
1102  _("Assuming leading zeros in file \"%s\""),
1103  fd->filename);
1104  image->format->omit_zeros = GERBV_OMIT_ZEROS_LEADING;
1105  }
1106  break;
1107  case DRILL_M_METRIC :
1108  if (state->unit == GERBV_UNIT_UNSPECIFIED
1109  && state->curr_section != DRILL_HEADER) {
1110  double size;
1111 
1112  gerbv_stats_printf(stats->error_list,
1114  _("M71 code found but no METRIC "
1115  "specification in header "
1116  "at line %u in file \"%s\""),
1117  file_line, fd->filename);
1118  gerbv_stats_printf(stats->error_list,
1120  _("Assuming all tool sizes are MM in file \"%s\""),
1121  fd->filename);
1122 
1123  stats = image->drill_stats;
1124  for (int tool_num = TOOL_MIN; tool_num < TOOL_MAX;
1125  tool_num++) {
1126  if (image->aperture[tool_num]) {
1127  /* First update stats. Do this before changing drill dias.
1128  * Maybe also put error into stats? */
1129  size = image->aperture[tool_num]->parameter[0];
1130  drill_stats_modify_drill_list(stats->drill_list,
1131  tool_num,
1132  size,
1133  "MM");
1134  /* Now go back and update all tool dias, since
1135  * tools are displayed in inch units
1136  */
1137  image->aperture[tool_num]->parameter[0] /= 25.4;
1138  }
1139  }
1140  }
1141  if (state->autod) {
1142  state->number_format = state->backup_number_format;
1143  state->unit = GERBV_UNIT_MM;
1144  }
1145  break;
1146  case DRILL_M_IMPERIAL :
1147  if (state->autod) {
1148  if (state->number_format != FMT_00_0000) {
1149  /* save metric format definition for later */
1150  state->backup_number_format = state->number_format;
1151  }
1152  state->number_format = FMT_00_0000;
1153  state->decimals = 4;
1154  state->unit = GERBV_UNIT_INCH;
1155  }
1156 
1157  break;
1158  case DRILL_M_CANNEDTEXTX :
1159  case DRILL_M_CANNEDTEXTY :
1160  tmps = get_line(fd);
1161  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
1162  _("Canned text \"%s\" "
1163  "at line %u in drill file \"%s\""),
1164  tmps, file_line, fd->filename);
1165  g_free(tmps);
1166  break;
1167  case DRILL_M_MESSAGELONG :
1168  case DRILL_M_MESSAGE :
1169  tmps = get_line(fd);
1170  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
1171  _("Message \"%s\" embedded "
1172  "at line %u in drill file \"%s\""),
1173  tmps, file_line, fd->filename);
1174  g_free(tmps);
1175  break;
1176  case DRILL_M_PATTERN: /* M25 — begin pattern recording */
1177  state->in_pattern = TRUE;
1178  if (state->pattern_buffer) {
1179  g_array_set_size(state->pattern_buffer, 0);
1180  } else {
1181  state->pattern_buffer = g_array_new(FALSE, FALSE,
1182  sizeof(drill_pattern_entry_t));
1183  }
1184  break;
1185 
1186  case DRILL_M_PATTERNEND: /* M01 — end pattern recording */
1187  state->in_pattern = FALSE;
1188  break;
1189 
1190  case DRILL_M_REPEATPATTERNOFFSET: { /* M02 — repeat pattern at offset */
1191  double offset_x = 0.0, offset_y = 0.0;
1192  int c, saved_tool;
1193 
1194  /* Parse X/Y offsets (same format as R-code step) */
1195  c = gerb_fgetc(fd);
1196  if (c == 'X') {
1197  offset_x = read_double(fd, state->number_format,
1198  image->format->omit_zeros, state->decimals);
1199  c = gerb_fgetc(fd);
1200  }
1201  if (c == 'Y') {
1202  offset_y = read_double(fd, state->number_format,
1203  image->format->omit_zeros, state->decimals);
1204  } else {
1205  gerb_ungetc(fd);
1206  }
1207 
1208  /* Replay buffered drills at offset */
1209  if (state->pattern_buffer && state->pattern_buffer->len > 0) {
1210  double save_x = state->curr_x;
1211  double save_y = state->curr_y;
1212  saved_tool = state->current_tool;
1213 
1214  for (guint i = 0; i < state->pattern_buffer->len; i++) {
1215  drill_pattern_entry_t *e = &g_array_index(
1216  state->pattern_buffer,
1217  drill_pattern_entry_t, i);
1218  state->current_tool = e->tool;
1219  state->curr_x = e->x + offset_x;
1220  state->curr_y = e->y + offset_y;
1221 
1222  if (e->is_route) {
1223  curr_net = drill_add_route_segment(image, state,
1224  curr_net,
1225  e->prev_x + offset_x,
1226  e->prev_y + offset_y);
1227  } else {
1228  curr_net = drill_add_drill_hole(image, state,
1229  curr_net);
1230  }
1231  }
1232 
1233  state->curr_x = save_x;
1234  state->curr_y = save_y;
1235  state->current_tool = saved_tool;
1236  }
1237  break;
1238  }
1239 
1240  case DRILL_M_TOOLTIPCHECK:
1241  break;
1242 
1243  case DRILL_M_ZAXISROUTEPOSITIONDEPTHCTRL: /* M14 */
1244  case DRILL_M_ZAXISROUTEPOSITION: /* M15 */
1245  state->tool_down = TRUE;
1246  break;
1247  case DRILL_M_RETRACTCLAMPING: /* M16 */
1248  case DRILL_M_RETRACTNOCLAMPING: /* M17 */
1249  state->tool_down = FALSE;
1250  break;
1251 
1252  case DRILL_M_SWAPAXIS: /* M70 */
1253  state->swap_axis = !state->swap_axis;
1254  break;
1255  case DRILL_M_MIRRORX: /* M80 */
1256  state->mirror_x = !state->mirror_x;
1257  break;
1258  case DRILL_M_MIRRORY: /* M90 */
1259  state->mirror_y = !state->mirror_y;
1260  break;
1261 
1262  case DRILL_M_END :
1263  /* M00 has optional arguments */
1264  eat_line(fd);
1265  /* M00 ends the program the same way as M30 */
1266  [[fallthrough]];
1267 
1268  case DRILL_M_ENDREWIND :
1269  parsing_done = TRUE;
1270  break;
1271 
1272  case DRILL_M_UNKNOWN:
1273  gerb_ungetc(fd); /* To compare full string in function or
1274  report full string */
1275  if (drill_parse_header_is_metric(fd, state, image, file_line)) {
1276  break;
1277  }
1278 
1279  stats->M_unknown++;
1280  tmps = get_line(fd);
1281  gerbv_stats_printf(stats->error_list,
1282  GERBV_MESSAGE_ERROR, -1,
1283  _("Unrecognized string \"%s\" found "
1284  "at line %u in file \"%s\""),
1285  tmps, file_line, fd->filename);
1286  g_free(tmps);
1287 
1288  break;
1289 
1290  case DRILL_M_STOPOPTIONAL: /* M06 */
1291  case DRILL_M_SANDREND: /* M08 */
1292  case DRILL_M_STOPINSPECTION: /* M09 */
1293  case DRILL_M_VISANDRPATTERN: /* M50 */
1294  case DRILL_M_VISANDRPATTERNREWIND: /* M51 */
1295  case DRILL_M_VISANDRPATTERNOFFSETCOUNTERCTRL: /* M52 */
1296  case DRILL_M_REFSCALING: /* M60 */
1297  case DRILL_M_REFSCALINGEND: /* M61 */
1298  case DRILL_M_PECKDRILLING: /* M62 */
1299  case DRILL_M_PECKDRILLINGEND: /* M63 */
1300  eat_line(fd);
1301  gerbv_stats_printf(stats->error_list,
1302  GERBV_MESSAGE_NOTE, -1,
1303  _("Ignoring machine-only M%02d (%s) "
1304  "at line %u in file \"%s\""),
1305  m_code, _(drill_m_code_name(m_code)),
1306  file_line, fd->filename);
1307  break;
1308 
1309  default:
1310  stats->M_unknown++;
1311  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
1312  _("Unsupported M%02d (%s) code found "
1313  "at line %u in file \"%s\""),
1314  m_code, _(drill_m_code_name(m_code)),
1315  file_line, fd->filename);
1316  break;
1317  } /* switch(m_code) */
1318 
1319  break;
1320  } /* case 'M' */
1321 
1322  case 'R':
1323  if (state->curr_section == DRILL_HEADER) {
1324  stats->unknown++;
1325  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
1326  _("Not allowed 'R' code in the header "
1327  "at line %u in file \"%s\""),
1328  file_line, fd->filename);
1329  } else {
1330  double start_x, start_y;
1331  double step_x, step_y;
1332  int c;
1333  int rcnt;
1334  /*
1335  * This is the "Repeat hole" command. Format is:
1336  * R##[X##][Y##]
1337  * This repeats the previous hole stepping in the X and
1338  * Y increments give. Example:
1339  * R04X0.1 -- repeats the drill hole 4 times, stepping
1340  * 0.1" in the X direction. Note that the X and Y step
1341  * sizes default to zero if not given and that they use
1342  * the same format and units as the normal X,Y
1343  * coordinates.
1344  */
1345  stats->R++;
1346 
1347  start_x = state->curr_x;
1348  start_y = state->curr_y;
1349 
1350  /* figure out how many repeats there are */
1351  c = gerb_fgetc (fd);
1352  rcnt = 0;
1353  while ( '0' <= c && c <= '9') {
1354  rcnt = 10*rcnt + (c - '0');
1355  c = gerb_fgetc (fd);
1356  }
1357  DPRINTF("working on R code (repeat) with a number of reps equal to %d\n", rcnt);
1358 
1359  step_x = 0.0;
1360  if (c == 'X') {
1361  step_x = read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
1362  c = gerb_fgetc (fd);
1363  }
1364 
1365  step_y = 0.0;
1366  if( c == 'Y') {
1367  step_y = read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
1368  } else {
1369  gerb_ungetc (fd);
1370  }
1371 
1372  DPRINTF("Getting ready to repeat the drill %d times with delta_x = %g, delta_y = %g\n", rcnt, step_x, step_y);
1373 
1374  /* spit out the drills */
1375  for (c = 1 ; c <= rcnt ; c++) {
1376  state->curr_x = start_x + c*step_x;
1377  state->curr_y = start_y + c*step_y;
1378  DPRINTF(" Repeat #%d - new location is (%g, %g)\n", c, state->curr_x, state->curr_y);
1379  curr_net = drill_add_drill_hole (image, state, curr_net);
1380  if (state->in_pattern) {
1381  drill_pattern_entry_t entry = {
1382  state->curr_x, state->curr_y,
1383  0, 0, state->current_tool, FALSE
1384  };
1385  g_array_append_val(state->pattern_buffer, entry);
1386  }
1387  }
1388 
1389  }
1390  break;
1391 
1392  case 'S':
1393  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
1394  _("Ignoring setting spindle speed "
1395  "at line %u in drill file \"%s\""),
1396  file_line, fd->filename);
1397  eat_line(fd);
1398  break;
1399  case 'T':
1400  drill_parse_T_code(fd, state, image, file_line);
1401  state->route_mode = DRILL_G_DRILL;
1402  state->tool_down = FALSE;
1403  state->delta_cp_x = 0;
1404  state->delta_cp_y = 0;
1405  break;
1406  case 'V' :
1407  gerb_ungetc (fd);
1408  tmps = get_line (fd);
1409  /* Silently ignore VER,1. Not sure what others are allowed */
1410  if (0 != strcmp (tmps, "VER,1")) {
1411  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
1412  _("Undefined string \"%s\" in header "
1413  "at line %u in file \"%s\""),
1414  tmps, file_line, fd->filename);
1415  }
1416  g_free (tmps);
1417  break;
1418 
1419  case 'X':
1420  case 'Y': {
1421  double prev_x = state->curr_x;
1422  double prev_y = state->curr_y;
1423 
1424  /* Parse the coordinate(s) */
1425  drill_parse_coordinate(fd, read, image, state, file_line);
1426 
1427  if ((state->route_mode == DRILL_G_ROUT ||
1428  state->route_mode == DRILL_G_LINEARMOVE ||
1429  state->route_mode == DRILL_G_CWMOVE ||
1430  state->route_mode == DRILL_G_CCWMOVE) && !state->tool_down) {
1431  /* Routing mode, tool up: reposition only, no geometry */
1432  state->delta_cp_x = 0;
1433  state->delta_cp_y = 0;
1434  state->found_arc_radius = FALSE;
1435  break;
1436  }
1437 
1438  if ((state->route_mode == DRILL_G_CWMOVE ||
1439  state->route_mode == DRILL_G_CCWMOVE) && state->tool_down) {
1440  /* Arc routing mode, tool down: create arc segment */
1441  curr_net = drill_add_arc_segment(image, state, stats,
1442  curr_net, prev_x, prev_y);
1443  state->delta_cp_x = 0;
1444  state->delta_cp_y = 0;
1445  state->found_arc_radius = FALSE;
1446  } else if ((state->route_mode == DRILL_G_LINEARMOVE ||
1447  state->route_mode == DRILL_G_ROUT) && state->tool_down) {
1448  /* Routing mode, tool down: create line segment */
1449  curr_net = drill_add_route_segment(image, state,
1450  curr_net, prev_x, prev_y);
1451  if (state->in_pattern) {
1452  drill_pattern_entry_t entry = {
1453  state->curr_x, state->curr_y,
1454  prev_x, prev_y, state->current_tool, TRUE
1455  };
1456  g_array_append_val(state->pattern_buffer, entry);
1457  }
1458  } else {
1459  /* Drill mode (default): create flash hole */
1460  curr_net = drill_add_drill_hole(image, state, curr_net);
1461  if (state->in_pattern) {
1462  drill_pattern_entry_t entry = {
1463  state->curr_x, state->curr_y,
1464  0, 0, state->current_tool, FALSE
1465  };
1466  g_array_append_val(state->pattern_buffer, entry);
1467  }
1468  }
1469  break;
1470  }
1471 
1472  case '%':
1473  state->curr_section = DRILL_DATA;
1474  break;
1475 
1476  case '\n' :
1477  file_line++;
1478 
1479  /* Get <CR> char, if any, from <LF><CR> pair */
1480  read = gerb_fgetc(fd);
1481  if (read != '\r' && read != EOF) {
1482  gerb_ungetc(fd);
1483  }
1484  break;
1485 
1486  case '\r' :
1487  file_line++;
1488 
1489  /* Get <LF> char, if any, from <CR><LF> pair */
1490  read = gerb_fgetc(fd);
1491  if (read != '\n' && read != EOF) {
1492  gerb_ungetc(fd);
1493  }
1494  break;
1495 
1496  case ' ' : /* White space */
1497  case '\t' :
1498  break;
1499 
1500  default:
1501  stats->unknown++;
1502 
1503  if (DRILL_HEADER == state->curr_section) {
1504  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
1505  _("Undefined code '%s' (0x%x) found in header "
1506  "at line %u in file \"%s\""),
1507  gerbv_escape_char(read), read,
1508  file_line, fd->filename);
1509  gerb_ungetc(fd);
1510 
1511  /* Unrecognised crap in the header is thrown away */
1512  tmps = get_line(fd);
1513  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
1514  _("Unrecognised string \"%s\" in header "
1515  "at line %u in file \"%s\""),
1516  tmps, file_line, fd->filename);
1517  g_free (tmps);
1518  } else {
1519  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
1520  _("Ignoring undefined character '%s' (0x%x) "
1521  "found inside data at line %u in file \"%s\""),
1522  gerbv_escape_char(read), read, file_line, fd->filename);
1523  }
1524  }
1525  }
1526 
1527  if (!parsing_done) {
1528  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
1529  _("No EOF found in drill file \"%s\""), fd->filename);
1530  }
1531 
1532  DPRINTF("%s(): Populating file attributes\n", __FUNCTION__);
1533 
1534  hid_attrs = image->info->attr_list;
1535 
1536  switch (state->unit) {
1537  case GERBV_UNIT_MM:
1538  hid_attrs[HA_xy_units].default_val.int_value = GERBV_UNIT_MM;
1539  break;
1540 
1541  default:
1542  hid_attrs[HA_xy_units].default_val.int_value = GERBV_UNIT_INCH;
1543  break;
1544  }
1545 
1546  switch (state->number_format) {
1547  case FMT_000_00:
1548  case FMT_0000_00:
1549  hid_attrs[HA_digits].default_val.int_value = 2;
1550  break;
1551 
1552  case FMT_000_000:
1553  hid_attrs[HA_digits].default_val.int_value = 3;
1554  break;
1555 
1556  case FMT_00_0000:
1557  hid_attrs[HA_digits].default_val.int_value = 4;
1558  break;
1559 
1560  case FMT_USER:
1561  DPRINTF("%s(): Keeping user specified number of decimal places (%d)\n",
1562  __FUNCTION__,
1563  hid_attrs[HA_digits].default_val.int_value);
1564  break;
1565 
1566  default:
1567  break;
1568  }
1569 
1570  switch (image->format->omit_zeros) {
1572  hid_attrs[HA_suppression].default_val.int_value = SUP_LEAD;
1573  break;
1574 
1576  hid_attrs[HA_suppression].default_val.int_value = SUP_TRAIL;
1577  break;
1578 
1579  default:
1580  hid_attrs[HA_suppression].default_val.int_value = SUP_NONE;
1581  break;
1582  }
1583 
1584  if (state->pattern_buffer)
1585  g_array_free(state->pattern_buffer, TRUE);
1586  g_free(state);
1587 
1588  return image;
1589 } /* parse_drillfile */
1590 
1591 
1592 /* -------------------------------------------------------------- */
1593 /*
1594  * Checks for signs that this is a drill file
1595  * Returns TRUE if it is, FALSE if not.
1596  */
1597 gboolean
1598 drill_file_p(gerb_file_t *fd, gboolean *returnFoundBinary)
1599 {
1600  char *buf=NULL, *tbuf;
1601  int len = 0;
1602  char *letter;
1603  int ascii;
1604  int zero = 48; /* ascii 0 */
1605  int nine = 57; /* ascii 9 */
1606  int i;
1607  gboolean found_binary = FALSE;
1608  gboolean found_M48 = FALSE;
1609  gboolean found_M30 = FALSE;
1610  gboolean found_percent = FALSE;
1611  gboolean found_T = FALSE;
1612  gboolean found_X = FALSE;
1613  gboolean found_Y = FALSE;
1614  gboolean end_comments=FALSE;
1615 
1616  tbuf = g_malloc(MAXL);
1617  if (tbuf == NULL) {
1618  GERB_FATAL_ERROR(
1619  "malloc buf failed while checking for drill file in %s()",
1620  __FUNCTION__);
1621  }
1622 
1623  while (fgets(tbuf, MAXL, fd->fd) != NULL) {
1624  len = strlen(tbuf);
1625  buf = tbuf;
1626  /* check for comments at top of file. */
1627  if(!end_comments){
1628  if(g_strstr_len(buf, len, ";")!=NULL){/* comments at top of file */
1629  for (i = 0; i < len-1; ++i) {
1630  if (buf[i] == '\n'
1631  && buf[i+1] != ';'
1632  && buf[i+1] != '\r'
1633  && buf[i+1] != '\n') {
1634  end_comments = TRUE;
1635  /* Set rest of parser to end of
1636  * comments */
1637  buf = &tbuf[i+1];
1638 
1639  }
1640  }
1641  if(!end_comments) {
1642  continue;
1643  }
1644  } else {
1645  end_comments=TRUE;
1646  }
1647  }
1648 
1649  /* First look through the file for indications of its type */
1650  len = strlen(buf);
1651  /* check that file is not binary (non-printing chars) */
1652  for (i = 0; i < len; i++) {
1653  ascii = (int) buf[i];
1654  if ((ascii > 128) || (ascii < 0)) {
1655  found_binary = TRUE;
1656  }
1657  }
1658 
1659  /* Check for M48 = start of drill header */
1660  if (g_strstr_len(buf, len, "M48")) {
1661  found_M48 = TRUE;
1662  }
1663 
1664  /* Check for M30 = end of drill program */
1665  if (g_strstr_len(buf, len, "M30")) {
1666  if (found_percent) {
1667  found_M30 = TRUE; /* Found M30 after % = good */
1668  }
1669  }
1670 
1671  /* Check for % on its own line at end of header */
1672  if ((letter = g_strstr_len(buf, len, "%")) != NULL) {
1673  if ((letter[1] == '\r') || (letter[1] == '\n')) {
1674  found_percent = TRUE;
1675  }
1676  }
1677 
1678  /* Check for T<number> */
1679  if ((letter = g_strstr_len(buf, len, "T")) != NULL) {
1680  if (!found_T && (found_X || found_Y)) {
1681  found_T = FALSE; /* Found first T after X or Y */
1682  } else {
1683  if (isdigit( (int) letter[1])) { /* verify next char is digit */
1684  found_T = TRUE;
1685  }
1686  }
1687  }
1688 
1689  /* look for X<number> or Y<number> */
1690  if ((letter = g_strstr_len(buf, len, "X")) != NULL) {
1691  ascii = (int) letter[1]; /* grab char after X */
1692  if ((ascii >= zero) && (ascii <= nine)) {
1693  found_X = TRUE;
1694  }
1695  }
1696  if ((letter = g_strstr_len(buf, len, "Y")) != NULL) {
1697  ascii = (int) letter[1]; /* grab char after Y */
1698  if ((ascii >= zero) && (ascii <= nine)) {
1699  found_Y = TRUE;
1700  }
1701  }
1702  } /* while (fgets(buf, MAXL, fd->fd) */
1703 
1704  rewind(fd->fd);
1705  g_free(tbuf);
1706  *returnFoundBinary = found_binary;
1707 
1708  /* Now form logical expression determining if this is a drill file */
1709  if ( ((found_X || found_Y) && found_T) &&
1710  (found_M48 || (found_percent && found_M30)) ) {
1711  return TRUE;
1712  } else if (found_M48 && found_percent && found_M30) {
1713  /* Pathological case of drill file with valid header
1714  and EOF but no drill XY locations. */
1715  return TRUE;
1716  } else {
1717  return FALSE;
1718  }
1719 } /* drill_file_p */
1720 
1721 
1722 /* -------------------------------------------------------------- */
1723 /* Parse tool definition. This can get a bit tricky since it can
1724  appear in the header and/or data section.
1725  Returns tool number on success, -1 on error */
1726 static int
1727 drill_parse_T_code(gerb_file_t *fd, drill_state_t *state,
1728  gerbv_image_t *image, unsigned int file_line)
1729 {
1730  int tool_num;
1731  gboolean done = FALSE;
1732  int temp;
1733  double size;
1734  gerbv_drill_stats_t *stats = image->drill_stats;
1735  gerbv_aperture_t *apert;
1736  gchar *tmps;
1737  gchar *string;
1738 
1739  DPRINTF("---> entering %s()...\n", __FUNCTION__);
1740 
1741  /* Sneak a peek at what's hiding after the 'T'. Ugly fix for
1742  broken headers from Orcad, which is crap */
1743  temp = gerb_fgetc(fd);
1744  DPRINTF(" Found a char '%s' (0x%02x) after the T\n",
1745  gerbv_escape_char(temp), temp);
1746 
1747  /* might be a tool tool change stop switch on/off*/
1748  if((temp == 'C') && ((fd->ptr + 2) < fd->datalen)){
1749  if(gerb_fgetc(fd) == 'S'){
1750  if (gerb_fgetc(fd) == 'T' ){
1751  fd->ptr -= 4;
1752  tmps = get_line(fd++);
1753  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
1754  _("Tool change stop switch found \"%s\" "
1755  "at line %u in file \"%s\""),
1756  tmps, file_line, fd->filename);
1757  g_free (tmps);
1758 
1759  return -1;
1760  }
1761  gerb_ungetc(fd);
1762  }
1763  gerb_ungetc(fd);
1764  }
1765 
1766  if( !(isdigit(temp) != 0 || temp == '+' || temp =='-') ) {
1767  if(temp != EOF) {
1768  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
1769  _("OrCAD bug: Junk text found in place of tool definition"));
1770  tmps = get_line(fd);
1771  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_WARNING, -1,
1772  _("Junk text \"%s\" "
1773  "at line %u in file \"%s\""),
1774  tmps, file_line, fd->filename);
1775  g_free (tmps);
1776  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_WARNING, -1,
1777  _("Ignoring junk text"));
1778  }
1779  return -1;
1780  }
1781  gerb_ungetc(fd);
1782 
1783  tool_num = (int) gerb_fgetint(fd, NULL);
1784  DPRINTF(" Handling tool T%d at line %u\n", tool_num, file_line);
1785 
1786  if (tool_num == 0) {
1787  /* T0 is nominally an unload-tool command, but some CAD tools
1788  (e.g. ekf2) define T0 with a C diameter parameter in the
1789  header. Consume any trailing parameters so they don't get
1790  mis-parsed as unknown header codes, then return. */
1791  temp = gerb_fgetc(fd);
1792  while (temp != EOF && temp != '\n' && temp != '\r') {
1793  if (temp == 'C') {
1794  read_double(fd, state->header_number_format,
1795  GERBV_OMIT_ZEROS_TRAILING, state->decimals);
1796  } else if (temp == 'F' || temp == 'S') {
1797  gerb_fgetint(fd, NULL);
1798  } else {
1799  gerb_ungetc(fd);
1800  break;
1801  }
1802  temp = gerb_fgetc(fd);
1803  }
1804  if (temp == '\n' || temp == '\r')
1805  gerb_ungetc(fd);
1806  return tool_num;
1807  }
1808 
1809  if (tool_num < TOOL_MIN || tool_num >= TOOL_MAX) {
1810  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
1811  _("Out of bounds drill number %d "
1812  "at line %u in file \"%s\""),
1813  tool_num, file_line, fd->filename);
1814  return -1;
1815  }
1816 
1817  /* Set the current tool to the correct one */
1818  state->current_tool = tool_num;
1819  apert = image->aperture[tool_num];
1820 
1821  /* Check for a size definition */
1822  temp = gerb_fgetc(fd);
1823 
1824  /* This bit of code looks for a tool definition by scanning for strings
1825  * of form TxxC, TxxF, TxxS. */
1826  while (!done) {
1827  switch((char)temp) {
1828  case 'C':
1829  size = read_double(fd, state->header_number_format, GERBV_OMIT_ZEROS_TRAILING, state->decimals);
1830  DPRINTF(" Read a size of %g\n", size);
1831 
1832  if (state->unit == GERBV_UNIT_MM) {
1833  size /= 25.4;
1834  } else if(size >= 4.0) {
1835  /* If the drill size is >= 4 inches, assume that this
1836  must be wrong and that the units are mils.
1837  The limit being 4 inches is because the smallest drill
1838  I've ever seen used is 0,3mm(about 12mil). Half of that
1839  seemed a bit too small a margin, so a third it is */
1840 
1841  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
1842  _("Read a drill of diameter %g inches "
1843  "at line %u in file \"%s\""),
1844  size, file_line, fd->filename);
1845  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_WARNING, -1,
1846  _("Assuming units are mils"));
1847  size /= 1000.0;
1848  }
1849 
1850  if (size <= 0. || size >= 10000.) {
1851  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
1852  _("Unreasonable drill size %g found for drill %d "
1853  "at line %u in file \"%s\""),
1854  size, tool_num, file_line, fd->filename);
1855  } else {
1856  if (apert != NULL) {
1857  /* allow a redefine of a tool only if the new definition is exactly the same.
1858  * This avoid lots of spurious complaints with the output of some cad
1859  * tools while keeping complaints if there is a true problem
1860  */
1861  if (apert->parameter[0] != size
1862  || apert->type != GERBV_APTYPE_CIRCLE
1863  || apert->nuf_parameters != 1
1864  || apert->unit != GERBV_UNIT_INCH) {
1865 
1866  gerbv_stats_printf(stats->error_list,
1867  GERBV_MESSAGE_ERROR, -1,
1868  _("Found redefinition of drill %d "
1869  "at line %u in file \"%s\""),
1870  tool_num, file_line, fd->filename);
1871  }
1872  } else {
1873  apert = image->aperture[tool_num] =
1874  g_new0(gerbv_aperture_t, 1);
1875  if (apert == NULL) {
1876  GERB_FATAL_ERROR("malloc tool failed in %s()",
1877  __FUNCTION__);
1878  }
1879 
1880  /* There's really no way of knowing what unit the tools
1881  are defined in without sneaking a peek in the rest of
1882  the file first. That's done in drill_guess_format() */
1883  apert->parameter[0] = size;
1884  apert->type = GERBV_APTYPE_CIRCLE;
1885  apert->nuf_parameters = 1;
1886  apert->unit = GERBV_UNIT_INCH;
1887  }
1888  }
1889 
1890  /* Add the tool whose definition we just found into the list
1891  * of tools for this layer used to generate statistics. */
1892  stats = image->drill_stats;
1893  string = g_strdup_printf("%s", (state->unit == GERBV_UNIT_MM ? _("mm") : _("inch")));
1894  drill_stats_add_to_drill_list(stats->drill_list,
1895  tool_num,
1896  state->unit == GERBV_UNIT_MM ? size*25.4 : size,
1897  string);
1898  g_free(string);
1899  break;
1900 
1901  case 'F':
1902  case 'S' :
1903  /* Silently ignored. They're not important. */
1904  gerb_fgetint(fd, NULL);
1905  break;
1906 
1907  default:
1908  /* Stop when finding anything but what's expected
1909  (and put it back) */
1910  gerb_ungetc(fd);
1911  done = TRUE;
1912  break;
1913  } /* switch((char)temp) */
1914 
1915  temp = gerb_fgetc(fd);
1916  if (EOF == temp) {
1917  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
1918  _("Unexpected EOF encountered in header of "
1919  "drill file \"%s\""), fd->filename);
1920 
1921  /* Restore new line character for processing */
1922  if ('\n' == temp || '\r' == temp) {
1923  gerb_ungetc(fd);
1924  }
1925  }
1926  } /* while(!done) */ /* Done looking at tool definitions */
1927 
1928  /* Catch the tools that aren't defined.
1929  This isn't strictly a good thing, but at least something is shown */
1930  if (apert == NULL) {
1931  double dia;
1932 
1933  apert = image->aperture[tool_num] = g_new0(gerbv_aperture_t, 1);
1934  if (apert == NULL) {
1935  GERB_FATAL_ERROR("malloc tool failed in %s()", __FUNCTION__);
1936  }
1937 
1938  /* See if we have the tool table */
1939  dia = gerbv_get_tool_diameter(tool_num);
1940  if (dia <= 0) {
1941  /*
1942  * There is no tool. So go out and make some.
1943  * This size calculation is, of course, totally bogus.
1944  */
1945  dia = (double)(16 + 8 * tool_num) / 1000;
1946  /*
1947  * Oooh, this is sooo ugly. But some CAD systems seem to always
1948  * use T00 at the end of the file while others that don't have
1949  * tool definitions inside the file never seem to use T00 at all.
1950  */
1951  if (tool_num != 0) {
1952  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
1953  _("Tool %02d used without being defined "
1954  "at line %u in file \"%s\""),
1955  tool_num, file_line, fd->filename);
1956  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_WARNING, -1,
1957  _("Setting a default size of %g\""), dia);
1958  }
1959  }
1960 
1961  apert->type = GERBV_APTYPE_CIRCLE;
1962  apert->nuf_parameters = 1;
1963  apert->parameter[0] = dia;
1964 
1965  /* Add the tool whose definition we just found into the list
1966  * of tools for this layer used to generate statistics. */
1967  if (tool_num != 0) { /* Only add non-zero tool nums.
1968  * Zero = unload command. */
1969  stats = image->drill_stats;
1970  string = g_strdup_printf("%s",
1971  (state->unit == GERBV_UNIT_MM ? _("mm") : _("inch")));
1972  drill_stats_add_to_drill_list(stats->drill_list,
1973  tool_num,
1974  state->unit == GERBV_UNIT_MM ? dia*25.4 : dia,
1975  string);
1976  g_free(string);
1977  }
1978  } /* if(image->aperture[tool_num] == NULL) */
1979 
1980  DPRINTF("<---- ...leaving %s()\n", __FUNCTION__);
1981 
1982  return tool_num;
1983 } /* drill_parse_T_code() */
1984 
1985 
1986 /* -------------------------------------------------------------- */
1987 static drill_m_code_t
1988 drill_parse_M_code(gerb_file_t *fd, drill_state_t *state,
1989  gerbv_image_t *image, unsigned int file_line)
1990 {
1991  gerbv_drill_stats_t *stats = image->drill_stats;
1992  drill_m_code_t m_code;
1993  char op[3];
1994 
1995  DPRINTF("---> entering %s() ...\n", __FUNCTION__);
1996 
1997  op[0] = gerb_fgetc(fd);
1998  op[1] = gerb_fgetc(fd);
1999  op[2] = '\0';
2000 
2001  if (op[0] == EOF
2002  || op[1] == EOF) {
2003  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
2004  _("Unexpected EOF found while parsing M-code in file \"%s\""),
2005  fd->filename);
2006 
2007  return DRILL_M_UNKNOWN;
2008  }
2009 
2010  DPRINTF(" Compare M-code \"%s\" at line %u\n", op, file_line);
2011 
2012  switch (m_code = atoi(op)) {
2013  case 0:
2014  /* atoi() return 0 in case of error, recheck string */
2015  if (0 != strncmp(op, "00", 2)) {
2016  m_code = DRILL_M_UNKNOWN;
2017  gerb_ungetc(fd);
2018  gerb_ungetc(fd);
2019  break;
2020  }
2021  stats->M00++;
2022  break;
2023  case 1:
2024  stats->M01++;
2025  break;
2026  case 2:
2027  stats->M02++;
2028  break;
2029  case 14: break; /* M14 - Z-axis route position with depth control */
2030  case 15: break; /* M15 - Z-axis route position (tool down) */
2031  case 16: break; /* M16 - Retract with clamping */
2032  case 17: break; /* M17 - Retract without clamping */
2033  case 18:
2034  stats->M18++;
2035  break;
2036  case 25:
2037  stats->M25++;
2038  break;
2039  case 30:
2040  stats->M30++;
2041  break;
2042  case 45:
2043  stats->M45++;
2044  break;
2045  case 47:
2046  stats->M47++;
2047  break;
2048  case 48:
2049  stats->M48++;
2050  break;
2051  case 70:
2052  stats->M70++;
2053  break;
2054  case 71:
2055  stats->M71++;
2056  eat_line(fd);
2057  break;
2058  case 72:
2059  stats->M72++;
2060  eat_line(fd);
2061  break;
2062  case 80:
2063  stats->M80++;
2064  break;
2065  case 90:
2066  stats->M90++;
2067  break;
2068  case 95:
2069  stats->M95++;
2070  break;
2071  case 97:
2072  stats->M97++;
2073  break;
2074  case 98:
2075  stats->M98++;
2076  break;
2077 
2078  case 6:
2079  case 8: case 9:
2080  case 50: case 51: case 52:
2081  case 60: case 61: case 62: case 63:
2082  stats->M_machine_only++;
2083  break;
2084 
2085  default:
2086  case DRILL_M_UNKNOWN:
2087  break;
2088  }
2089 
2090 
2091  DPRINTF("<---- ...leaving %s()\n", __FUNCTION__);
2092 
2093  return m_code;
2094 } /* drill_parse_M_code() */
2095 
2096 /* -------------------------------------------------------------- */
2097 static int
2098 drill_parse_header_is_metric(gerb_file_t *fd, drill_state_t *state,
2099  gerbv_image_t *image, unsigned int file_line)
2100 {
2101  gerbv_drill_stats_t *stats = image->drill_stats;
2102  char c, op[3];
2103 
2104  DPRINTF(" %s(): entering\n", __FUNCTION__);
2105 
2106  /* METRIC is not an actual M code but a command that is only
2107  * acceptable within the header.
2108  *
2109  * The syntax is
2110  * METRIC[,{TZ|LZ}][,{000.000|000.00|0000.00}]
2111  */
2112 
2113  if (DRILL_HEADER != state->curr_section) {
2114  return 0;
2115  }
2116 
2117  switch (file_check_str(fd, "METRIC")) {
2118  case -1:
2119  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
2120  _("Unexpected EOF found while parsing \"%s\" string "
2121  "in file \"%s\""), "METRIC", fd->filename);
2122  return 0;
2123 
2124  case 0:
2125  return 0;
2126  }
2127 
2128  for (;;) {
2129  gboolean found_junk = FALSE;
2130 
2131  if (',' != gerb_fgetc(fd)) {
2132  gerb_ungetc(fd);
2133  eat_line(fd);
2134  break;
2135  }
2136 
2137  /* Is it TZ, LZ, or zerofmt? */
2138  switch (c = gerb_fgetc(fd)) {
2139  case 'T':
2140  case 'L':
2141  if ('Z' != gerb_fgetc(fd)) {
2142  found_junk = TRUE;
2143  break;
2144  }
2145 
2146  if (c == 'L') {
2147  DPRINTF(" %s(): Detected a file that probably has "
2148  "trailing zero suppression\n", __FUNCTION__);
2149  image->format->omit_zeros = GERBV_OMIT_ZEROS_TRAILING;
2150  } else {
2151  DPRINTF(" %s(): Detected a file that probably has "
2152  "leading zero suppression\n", __FUNCTION__);
2153  image->format->omit_zeros = GERBV_OMIT_ZEROS_LEADING;
2154  }
2155 
2156  if (!state->autod && state->number_format == FMT_USER
2157  && state->digits_before > 0 && c == 'L') {
2158  /* FILE_FORMAT=N:M was seen; for trailing suppression,
2159  * decimals must be N (digits before decimal point). */
2160  state->decimals = state->digits_before;
2161  }
2162 
2163  /* Tool definitions always use standard metric format
2164  * (1 um resolution), regardless of FILE_FORMAT= settings. */
2165  state->header_number_format = FMT_000_000;
2166 
2167  if (state->autod && state->number_format != FMT_USER) {
2168  /* Default metric number format is 6-digit, 1 um
2169  * resolution. If the number_format is already
2170  * FMT_USER, that means that we have set the number
2171  * format in another way, maybe with one of the altium
2172  * FILE_FORMAT= style comments, so don't do this
2173  * default. */
2174  state->number_format = FMT_000_000;
2175  state->decimals = 3;
2176  }
2177 
2178  if (',' == gerb_fgetc(fd)) {
2179  /* Another option follows */
2180  continue;
2181  }
2182 
2183  gerb_ungetc(fd);
2184  break;
2185 
2186  case '0':
2187  if ('0' != gerb_fgetc(fd)
2188  || '0' != gerb_fgetc(fd)) {
2189  found_junk = TRUE;
2190  break;
2191  }
2192 
2193  /* We just parsed three 0s, the remainder options
2194  so far are: .000 | .00 | 0.00 */
2195  op[0] = gerb_fgetc(fd);
2196  op[1] = gerb_fgetc(fd);
2197  op[2] = '\0';
2198  if (EOF == op[0]
2199  || EOF == op[1]) {
2200  found_junk = TRUE;
2201  break;
2202  }
2203 
2204  if (0 == strcmp(op, "0.")) {
2205  /* expecting FMT_0000_00,
2206  two trailing 0s must follow */
2207  if ('0' != gerb_fgetc(fd)
2208  || '0' != gerb_fgetc(fd)) {
2209  found_junk = TRUE;
2210  break;
2211  }
2212 
2213  eat_line(fd);
2214 
2215  if (state->autod) {
2216  state->number_format = FMT_0000_00;
2217  state->decimals = 2;
2218  }
2219  break;
2220  }
2221 
2222  if (0 != strcmp(op, ".0")) {
2223  found_junk = TRUE;
2224  break;
2225  }
2226 
2227  /* Must be either FMT_000_000 or FMT_000_00, depending
2228  * on whether one or two 0s are following */
2229  if ('0' != gerb_fgetc(fd)) {
2230  found_junk = TRUE;
2231  break;
2232  }
2233 
2234  /* Either FMT_000_000 or FMT_000_00.
2235  * No longer rewind to beginning, as either option
2236  * results in an accepted value. */
2237  {
2238  int last_char = gerb_fgetc(fd);
2239  if (last_char == '0') {
2240  if (state->autod) {
2241  state->number_format = FMT_000_000;
2242  state->decimals = 3;
2243  }
2244  } else {
2245  if (last_char != EOF) {
2246  gerb_ungetc(fd);
2247  }
2248  if (state->autod) {
2249  state->number_format = FMT_000_00;
2250  state->decimals = 2;
2251  }
2252  }
2253  }
2254 
2255  eat_line(fd);
2256  break;
2257 
2258  default:
2259  found_junk = TRUE;
2260  break;
2261  }
2262 
2263  if (found_junk) {
2264  gerb_ungetc(fd);
2265  eat_line(fd);
2266 
2267  gerbv_stats_printf(stats->error_list,
2269  _("Found junk after METRIC command "
2270  "at line %u in file \"%s\""),
2271  file_line, fd->filename);
2272  }
2273 
2274  break;
2275  }
2276 
2277  if (state->autod) {
2278  state->unit = GERBV_UNIT_MM;
2279  }
2280 
2281  return 1;
2282 } /* drill_parse_header_is_metric() */
2283 
2284 /* -------------------------------------------------------------- */
2285 /* Look for a comment like FILE_FORMAT=4:4 and interpret it as if the
2286  * format will be 4 digits before the decimal point and 4 after.
2287  * Return non-zero if we find a FILE_FORMAT header, otherwise return
2288  * 0. */
2289 static int
2290 drill_parse_header_is_metric_comment(gerb_file_t *fd, drill_state_t *state,
2291  gerbv_image_t *image, unsigned int file_line) {
2292  gerbv_drill_stats_t *stats = image->drill_stats;
2293 
2294  DPRINTF(" %s(): entering\n", __FUNCTION__);
2295  /* The leading semicolon is already gone. */
2296  if (DRILL_HEADER != state->curr_section) {
2297  return 0;
2298  }
2299 
2300  switch (file_check_str(fd, "FILE_FORMAT")) {
2301  case -1:
2302  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
2303  _("Unexpected EOF found while parsing \"%s\" string "
2304  "in file \"%s\" on line %u"),
2305  "FILE_FORMAT", fd->filename, file_line);
2306  return 0;
2307  case 0:
2308  return 0;
2309  }
2310 
2311  eat_whitespace(fd);
2312  if (file_check_str(fd, "=") != 1) {
2313  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
2314  _("Expected '=' while parsing \"%s\" string "
2315  "in file \"%s\" on line %u"),
2316  "FILE_FORMAT", fd->filename, file_line);
2317  return 0;
2318  }
2319  eat_whitespace(fd);
2320  int len = -1;
2321  int digits_before = gerb_fgetint(fd, &len);
2322  if (len < 1) {
2323  /* We've failed to read a number. */
2324  gerbv_stats_printf(
2325  stats->error_list, GERBV_MESSAGE_ERROR, -1,
2326  _("Expected integer after '=' while parsing \"%s\" string "
2327  "in file \"%s\" on line %u"),
2328  "FILE_FORMAT", fd->filename, file_line);
2329  return 0;
2330  }
2331  eat_whitespace(fd);
2332  if (file_check_str(fd, ":") != 1) {
2333  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
2334  _("Expected ':' while parsing \"%s\" string "
2335  "in file \"%s\" on line %u"),
2336  "FILE_FORMAT", fd->filename, file_line);
2337  return 0;
2338  }
2339  eat_whitespace(fd);
2340  len = -1;
2341  int digits_after = gerb_fgetint(fd, &len);
2342  if (len < 1) {
2343  gerbv_stats_printf(
2344  stats->error_list, GERBV_MESSAGE_ERROR, -1,
2345  _("Expected integer after ':' while parsing \"%s\" string "
2346  "in file \"%s\" on line %u"),
2347  "FILE_FORMAT", fd->filename, file_line);
2348  /* We've failed to read a number. */
2349  return 0;
2350  }
2351  state->number_format = FMT_USER;
2352  /* header_number_format is intentionally NOT set here — tool definitions
2353  * use standard formats (FMT_00_0000 for inch, FMT_000_000 for metric),
2354  * never FMT_USER. See issue #214. */
2355  state->decimals = digits_after;
2356  state->digits_before = digits_before;
2357  state->autod = 0;
2358  return 1;
2359 } /* drill_parse_header_is_metric_comment() */
2360 
2361 /* -------------------------------------------------------------- */
2362 static int
2363 drill_parse_header_is_inch(gerb_file_t *fd, drill_state_t *state,
2364  gerbv_image_t *image, unsigned int file_line)
2365 {
2366  gerbv_drill_stats_t *stats = image->drill_stats;
2367  char c;
2368 
2369  DPRINTF(" %s(): entering\n", __FUNCTION__);
2370 
2371  if (DRILL_HEADER != state->curr_section) {
2372  return 0;
2373  }
2374 
2375  switch (file_check_str(fd, "INCH")) {
2376  case -1:
2377  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
2378  _("Unexpected EOF found while parsing \"%s\" string "
2379  "in file \"%s\""), "INCH", fd->filename);
2380  return 0;
2381 
2382  case 0:
2383  return 0;
2384  }
2385 
2386  /* Look for TZ/LZ */
2387  if (',' != gerb_fgetc(fd)) {
2388  /* Unget the char in case we just advanced past a new line char */
2389  gerb_ungetc (fd);
2390  } else {
2391  c = gerb_fgetc(fd);
2392  if (c != EOF && 'Z' == gerb_fgetc(fd)) {
2393  switch (c) {
2394  case 'L':
2395  image->format->omit_zeros = GERBV_OMIT_ZEROS_TRAILING;
2396  state->header_number_format = FMT_00_0000;
2397  if (state->autod) {
2398  state->number_format = FMT_00_0000;
2399  state->decimals = 4;
2400  } else if (state->number_format == FMT_USER
2401  && state->digits_before > 0) {
2402  /* FILE_FORMAT=N:M was seen; for trailing suppression,
2403  * decimals must be N (digits before decimal point). */
2404  state->decimals = state->digits_before;
2405  }
2406  break;
2407 
2408  case 'T':
2409  image->format->omit_zeros = GERBV_OMIT_ZEROS_LEADING;
2410  state->header_number_format = FMT_00_0000;
2411  if (state->autod) {
2412  state->number_format = FMT_00_0000;
2413  state->decimals = 4;
2414  }
2415  /* For TZ (leading suppression), decimals stays as M (digits
2416  * after decimal, already set by FILE_FORMAT parser). */
2417  break;
2418 
2419  default:
2420  gerbv_stats_printf(stats->error_list,
2422  _("Found junk '%s' after "
2423  "INCH command "
2424  "at line %u in file \"%s\""),
2425  gerbv_escape_char(c),
2426  file_line, fd->filename);
2427  break;
2428  }
2429  } else {
2430  gerbv_stats_printf(stats->error_list,
2432  _("Found junk '%s' after INCH command "
2433  "at line %u in file \"%s\""),
2434  gerbv_escape_char(c),
2435  file_line, fd->filename);
2436  }
2437  }
2438 
2439  if (state->autod) {
2440  state->unit = GERBV_UNIT_INCH;
2441  }
2442 
2443  return 1;
2444 } /* drill_parse_header_is_inch() */
2445 
2446 /* -------------------------------------------------------------- */
2447 /* Check "ICI" incremental input of coordinates */
2448 static int
2449 drill_parse_header_is_ici(gerb_file_t *fd, drill_state_t *state,
2450  gerbv_image_t *image, unsigned int file_line)
2451 {
2452  gerbv_drill_stats_t *stats = image->drill_stats;
2453 
2454  switch (file_check_str(fd, "ICI,ON")) {
2455  case -1:
2456  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
2457  _("Unexpected EOF found while parsing \"%s\" string "
2458  "in file \"%s\""), "ICI,ON", fd->filename);
2459  return 0;
2460 
2461  case 1:
2462  state->coordinate_mode = DRILL_MODE_INCREMENTAL;
2463  return 1;
2464  }
2465 
2466  switch (file_check_str(fd, "ICI,OFF")) {
2467  case -1:
2468  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
2469  _("Unexpected EOF found while parsing \"%s\" string "
2470  "in file \"%s\""), "ICI,OFF", fd->filename);
2471  return 0;
2472 
2473  case 1:
2474  state->coordinate_mode = DRILL_MODE_ABSOLUTE;
2475  return 1;
2476  }
2477 
2478  return 0;
2479 } /* drill_parse_header_is_ici() */
2480 
2481 /* -------------------------------------------------------------- */
2482 static drill_g_code_t
2483 drill_parse_G_code(gerb_file_t *fd, gerbv_image_t *image, unsigned int file_line)
2484 {
2485  char op[3];
2486  drill_g_code_t g_code;
2487  gerbv_drill_stats_t *stats = image->drill_stats;
2488 
2489  DPRINTF("---> entering %s()...\n", __FUNCTION__);
2490 
2491  op[0] = gerb_fgetc(fd);
2492  op[1] = gerb_fgetc(fd);
2493  op[2] = '\0';
2494 
2495  if (op[0] == EOF
2496  || op[1] == EOF) {
2497  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
2498  _("Unexpected EOF found while parsing G-code in file \"%s\""),
2499  fd->filename);
2500  return DRILL_G_UNKNOWN;
2501  }
2502 
2503  DPRINTF(" Compare G-code \"%s\" at line %u\n", op, file_line);
2504 
2505  switch (g_code = atoi(op)) {
2506  case 0:
2507  /* atoi() return 0 in case of error, recheck string */
2508  if (0 != strncmp(op, "00", 2)) {
2509  g_code = DRILL_G_UNKNOWN;
2510  gerb_ungetc(fd);
2511  gerb_ungetc(fd);
2512  break;
2513  }
2514  stats->G00++;
2515  break;
2516  case 1:
2517  stats->G01++;
2518  break;
2519  case 2:
2520  stats->G02++;
2521  break;
2522  case 3:
2523  stats->G03++;
2524  break;
2525  case 5:
2526  stats->G05++;
2527  break;
2528  case 32:
2529  stats->G32++;
2530  break;
2531  case 33:
2532  stats->G33++;
2533  break;
2534  case 85:
2535  stats->G85++;
2536  break;
2537  case 87:
2538  stats->G87++;
2539  break;
2540  case 90:
2541  stats->G90++;
2542  break;
2543  case 91:
2544  stats->G91++;
2545  break;
2546  case 93:
2547  stats->G93++;
2548  break;
2549 
2550  case 7:
2551  case 34: case 35: case 36: case 37: case 38: case 39:
2552  case 40: case 41: case 42:
2553  case 45: case 46: case 47: case 48:
2554  case 81: case 82: case 83: case 84:
2555  stats->G_machine_only++;
2556  break;
2557 
2558  case DRILL_G_UNKNOWN:
2559  default:
2560  stats->G_unknown++;
2561  break;
2562  }
2563 
2564  DPRINTF("<---- ...leaving %s()\n", __FUNCTION__);
2565 
2566  return g_code;
2567 } /* drill_parse_G_code() */
2568 
2569 
2570 /* -------------------------------------------------------------- */
2571 /* Parse on drill file coordinate.
2572  Returns nothing, but modifies state */
2573 static void
2574 drill_parse_coordinate(gerb_file_t *fd, char firstchar,
2575  gerbv_image_t *image, drill_state_t *state,
2576  unsigned int file_line)
2577 {
2578  gerbv_drill_stats_t *stats = image->drill_stats;
2579 
2580  double x = 0;
2581  gboolean found_x = FALSE;
2582  double y = 0;
2583  gboolean found_y = FALSE;
2584  double i_val = 0;
2585  gboolean found_i = FALSE;
2586  double j_val = 0;
2587  gboolean found_j = FALSE;
2588 
2589 
2590  while (TRUE) {
2591  if (firstchar == 'X') {
2592  x = read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
2593  found_x = TRUE;
2594  } else if (firstchar == 'Y') {
2595  y = read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
2596  found_y = TRUE;
2597  } else if (firstchar == 'I') {
2598  i_val = read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
2599  found_i = TRUE;
2600  } else if (firstchar == 'J') {
2601  j_val = read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
2602  found_j = TRUE;
2603  } else if (firstchar == 'A') {
2604  state->arc_radius = read_double(fd, state->number_format,
2605  image->format->omit_zeros, state->decimals);
2606  state->found_arc_radius = TRUE;
2607  } else {
2608  gerb_ungetc(fd);
2609  break;
2610  }
2611  eat_whitespace(fd);
2612  firstchar = gerb_fgetc(fd);
2613  }
2614  /* Apply axis transforms (M70/M80/M90) to raw parsed values */
2615  if (state->swap_axis) {
2616  double tmp = x; x = y; y = tmp;
2617  gboolean ftmp = found_x; found_x = found_y; found_y = ftmp;
2618  }
2619  if (state->mirror_x && found_x)
2620  x = -x;
2621  if (state->mirror_y && found_y)
2622  y = -y;
2623 
2624  if(state->coordinate_mode == DRILL_MODE_ABSOLUTE) {
2625  if (found_x) {
2626  state->curr_x = x;
2627  }
2628  if (found_y) {
2629  state->curr_y = y;
2630  }
2631  } else if(state->coordinate_mode == DRILL_MODE_INCREMENTAL) {
2632  if (found_x) {
2633  state->curr_x += x;
2634  }
2635  if (found_y) {
2636  state->curr_y += y;
2637  }
2638  } else {
2639  gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
2640  _("Coordinate mode is not absolute and not incremental "
2641  "at line %u in file \"%s\""),
2642  file_line, fd->filename);
2643  }
2644 
2645  /* Store arc center offsets for G02/G03 */
2646  if (found_i)
2647  state->delta_cp_x = i_val;
2648  if (found_j)
2649  state->delta_cp_y = j_val;
2650 } /* drill_parse_coordinate */
2651 
2652 
2653 /* Allocates and returns a new drill_state structure
2654  Returns state pointer on success, NULL on ERROR */
2655 static drill_state_t *
2656 new_state(void)
2657 {
2658  drill_state_t *state = g_new0(drill_state_t, 1);
2659  if (state != NULL) {
2660  /* Init structure */
2661  state->curr_section = DRILL_NONE;
2662  state->coordinate_mode = DRILL_MODE_ABSOLUTE;
2663  state->origin_x = 0.0;
2664  state->origin_y = 0.0;
2665  state->unit = GERBV_UNIT_UNSPECIFIED;
2666  state->backup_number_format = FMT_000_000; /* only used for METRIC */
2667  state->header_number_format = state->number_format = FMT_00_0000; /* i. e. INCH */
2668  state->autod = 1;
2669  state->decimals = 4;
2670  state->route_mode = DRILL_G_DRILL;
2671  state->tool_down = FALSE;
2672  }
2673 
2674  return state;
2675 } /* new_state */
2676 
2677 
2678 /* -------------------------------------------------------------- */
2679 /* Reads one double from fd and returns it.
2680  If a decimal point is found, fmt is not used. */
2681 static double
2682 read_double(gerb_file_t *fd, number_fmt_t fmt, gerbv_omit_zeros_t omit_zeros, int decimals)
2683 {
2684  int read;
2685  char temp[DRILL_READ_DOUBLE_SIZE];
2686  unsigned int i = 0;
2687  double result;
2688  gboolean decimal_point = FALSE;
2689  gboolean sign_prepend = FALSE;
2690 
2691  memset(temp, 0, sizeof(temp));
2692 
2693  read = gerb_fgetc(fd);
2694  while(read != EOF && i < (DRILL_READ_DOUBLE_SIZE -1) &&
2695  (isdigit(read) || read == '.' || read == ',' || read == '+' || read == '-')) {
2696  if(read == ',' || read == '.') decimal_point = TRUE;
2697 
2698  /*
2699  * FIXME -- if we are going to do this, don't we need a
2700  * locale-independent strtod()? I think pcb has one.
2701  */
2702  if(read == ',') {
2703  read = '.'; /* adjust for strtod() */
2704  }
2705 
2706  if(read == '-' || read == '+') {
2707  sign_prepend = TRUE;
2708  }
2709 
2710  temp[i++] = (char)read;
2711  read = gerb_fgetc(fd);
2712  }
2713 
2714  temp[i] = 0;
2715  gerb_ungetc(fd);
2716 
2717  if (decimal_point) {
2718  result = strtod(temp, NULL);
2719  } else {
2720  unsigned int wantdigits;
2721  double scale;
2722  char tmp2[DRILL_READ_DOUBLE_SIZE];
2723 
2724  memset(tmp2, 0, sizeof(tmp2));
2725 
2726  /* Nothing to take care for when leading zeros are
2727  omitted. */
2728  if (omit_zeros == GERBV_OMIT_ZEROS_TRAILING) {
2729  switch (fmt) {
2730  case FMT_00_0000:
2731  wantdigits = 2;
2732  break;
2733 
2734  case FMT_000_000:
2735  wantdigits = 3;
2736  break;
2737 
2738  case FMT_0000_00:
2739  wantdigits = 4;
2740  break;
2741 
2742  case FMT_000_00:
2743  wantdigits = 3;
2744  break;
2745 
2746  case FMT_USER:
2747  wantdigits = decimals;
2748  break;
2749 
2750  default:
2751  /* cannot happen, just plugs a compiler warning */
2752  fprintf(stderr, _("%s(): omit_zeros == GERBV_OMIT_ZEROS_TRAILING but fmt = %d.\n"
2753  "This should never have happened\n"), __FUNCTION__, fmt);
2754  return 0;
2755  }
2756 
2757  /* need to add an extra char for '+' or '-' */
2758  if (sign_prepend) {
2759  wantdigits++;
2760  }
2761 
2762 
2763  /*
2764  * we need at least wantdigits + one for the decimal place
2765  * + one for the terminating null character
2766  */
2767  if (wantdigits > sizeof(tmp2) - 2) {
2768  fprintf(stderr, _("%s(): wantdigits = %d which exceeds the maximum allowed size\n"),
2769  __FUNCTION__, wantdigits);
2770  return 0;
2771  }
2772 
2773  /*
2774  * After we have read the correct number of digits
2775  * preceeding the decimal point, insert a decimal point
2776  * and append the rest of the digits.
2777  */
2778  DPRINTF("%s(): wantdigits = %d, strlen(\"%s\") = %ld\n",
2779  __FUNCTION__, wantdigits, temp, (long) strlen(temp));
2780  for (i = 0 ; i < wantdigits && i < strlen(temp) ; i++) {
2781  tmp2[i] = temp[i];
2782  }
2783  for ( ; i < wantdigits ; i++) {
2784  tmp2[i] = '0';
2785  }
2786  tmp2[i++] = '.';
2787  for ( ; i <= strlen(temp) ; i++) {
2788  tmp2[i] = temp[i-1];
2789  }
2790  DPRINTF("%s(): After dealing with trailing zero suppression, convert \"%s\"\n", __FUNCTION__, tmp2);
2791  scale = 1.0;
2792 
2793  for (i = 0 ; i <= strlen(tmp2) && i < sizeof (temp) ; i++) {
2794  temp[i] = tmp2[i];
2795  }
2796 
2797  } else {
2798 
2799  /*
2800  * figure out the scale factor when we are not suppressing
2801  * trailing zeros.
2802  */
2803  switch (fmt) {
2804  case FMT_00_0000:
2805  scale = 1E-4;
2806  break;
2807 
2808  case FMT_000_000:
2809  scale = 1E-3;
2810  break;
2811 
2812  case FMT_000_00:
2813  case FMT_0000_00:
2814  scale = 1E-2;
2815  break;
2816 
2817  case FMT_USER:
2818  scale = pow (10.0, -1.0*decimals);
2819  break;
2820 
2821  default:
2822  /* cannot happen, just plugs a compiler warning */
2823  fprintf (stderr, _("%s(): Unhandled fmt ` %d\n"), __FUNCTION__, fmt);
2824  exit (1);
2825  }
2826  }
2827 
2828  result = strtod(temp, NULL) * scale;
2829  }
2830 
2831  if (!isfinite(result)) {
2832  result = 0.0;
2833  }
2834 
2835  DPRINTF(" %s()=%f: fmt=%d, omit_zeros=%d, decimals=%d \n",
2836  __FUNCTION__, result, fmt, omit_zeros, decimals);
2837 
2838  return result;
2839 } /* read_double */
2840 
2841 /* -------------------------------------------------------------- */
2842 /* Eats all characters up to and including
2843  the first one of CR or LF */
2844 static void
2845 eat_line(gerb_file_t *fd)
2846 {
2847  int read;
2848 
2849  do {
2850  read = gerb_fgetc(fd);
2851  } while (read != '\n' && read != '\r' && read != EOF);
2852 
2853  /* Restore new line character for processing */
2854  if (read != EOF) {
2855  gerb_ungetc(fd);
2856  }
2857 } /* eat_line */
2858 
2859 /* -------------------------------------------------------------- */
2860 /* Eats all tabs and spaces. */
2861 static void
2862 eat_whitespace(gerb_file_t *fd)
2863 {
2864  int read;
2865 
2866  do {
2867  read = gerb_fgetc(fd);
2868  } while ((read == ' ' || read == '\t') && read != EOF);
2869 
2870  /* Restore the non-whitespace character for processing */
2871  if (read != EOF) {
2872  gerb_ungetc(fd);
2873  }
2874 } /* eat_whitespace */
2875 
2876 /* -------------------------------------------------------------- */
2877 static char *
2878 get_line(gerb_file_t *fd)
2879 {
2880  int read;
2881  gchar *retstring;
2882  gchar *tmps=g_strdup("");
2883 
2884  read = gerb_fgetc(fd);
2885  while (read != '\n' && read != '\r' && read != EOF) {
2886  retstring = g_strdup_printf("%s%c", tmps, read);
2887 
2888  /* since g_strdup_printf allocates memory, we need to free it */
2889  if (tmps) {
2890  g_free (tmps);
2891  tmps = NULL;
2892  }
2893  tmps = retstring;
2894  read = gerb_fgetc(fd);
2895  }
2896 
2897  /* Restore new line character for processing */
2898  if (read != EOF) {
2899  gerb_ungetc(fd);
2900  }
2901 
2902  return tmps;
2903 } /* get_line */
2904 
2905 /* -------------------------------------------------------------- */
2906 /* Look for str in the file fd. If found, return 1. If not, return
2907  * 0. If EOF is reached while searching, return -1. If the find
2908  * fails, rewinds the file descriptor. Otherwise, it doesn't and the
2909  * string is consumed.
2910  */
2911 static int
2912 file_check_str(gerb_file_t *fd, const char *str)
2913 {
2914  char c;
2915 
2916  for (int i = 0; str[i] != '\0'; i++) {
2917 
2918  c = gerb_fgetc(fd);
2919 
2920  if (c == EOF) {
2921  return -1;
2922  }
2923 
2924  if (c != str[i]) {
2925  do {
2926  /* Restore checked string */
2927  gerb_ungetc(fd);
2928  } while (i--);
2929 
2930  return 0;
2931  }
2932  }
2933 
2934  return 1;
2935 }
2936 
2937 /* -------------------------------------------------------------- */
2939 const char *drill_g_code_name(drill_g_code_t g_code)
2940 {
2941  switch (g_code) {
2942  case DRILL_G_ROUT:
2943  return N_("rout mode");
2944  case DRILL_G_LINEARMOVE:
2945  return N_("linear mode");
2946  case DRILL_G_CWMOVE:
2947  return N_("circular CW mode");
2948  case DRILL_G_CCWMOVE:
2949  return N_("circular CCW mode");
2950  case DRILL_G_VARIABLEDWELL:
2951  return N_("variable dwell");
2952  case DRILL_G_DRILL:
2953  return N_("drill mode");
2954  case DRILL_G_OVERRIDETOOLSPEED:
2955  return N_("override tool feed or speed");
2956  case DRILL_G_ROUTCIRCLE:
2957  return N_("routed CW circle");
2958  case DRILL_G_ROUTCIRCLECCW:
2959  return N_("routed CCW circle");
2960  case DRILL_G_VISTOOL:
2961  return N_("select vision tool");
2962  case DRILL_G_VISSINGLEPOINTOFFSET:
2963  return N_("single point vision offset");
2964  case DRILL_G_VISMULTIPOINTTRANS:
2965  return N_("multipoint vision translation");
2966  case DRILL_G_VISCANCEL:
2967  return N_("cancel vision translation or offset");
2968  case DRILL_G_VISCORRHOLEDRILL:
2969  return N_("vision corrected single hole drilling");
2970  case DRILL_G_VISAUTOCALIBRATION:
2971  return N_("vision system autocalibration");
2972  case DRILL_G_CUTTERCOMPOFF:
2973  return N_("cutter compensation off");
2974  case DRILL_G_CUTTERCOMPLEFT:
2975  return N_("cutter compensation left");
2976  case DRILL_G_CUTTERCOMPRIGHT:
2977  return N_("cutter compensation right");
2978  case DRILL_G_VISSINGLEPOINTOFFSETREL:
2979  return N_("single point vision relative offset");
2980  case DRILL_G_VISMULTIPOINTTRANSREL:
2981  return N_("multipoint vision relative translation");
2982  case DRILL_G_VISCANCELREL:
2983  return N_("cancel vision relative translation or offset");
2984  case DRILL_G_VISCORRHOLEDRILLREL:
2985  return N_("vision corrected single hole relative drilling");
2986  case DRILL_G_PACKDIP2:
2987  return N_("dual in line package");
2988  case DRILL_G_PACKDIP:
2989  return N_("dual in line package");
2990  case DRILL_G_PACK8PINL:
2991  return N_("eight pin L package");
2992  case DRILL_G_CIRLE:
2993  return N_("canned circle");
2994  case DRILL_G_SLOT:
2995  return N_("canned slot");
2996  case DRILL_G_ROUTSLOT:
2997  return N_("routed step slot");
2998  case DRILL_G_ABSOLUTE:
2999  return N_("absolute input mode");
3000  case DRILL_G_INCREMENTAL:
3001  return N_("incremental input mode");
3002  case DRILL_G_ZEROSET:
3003  return N_("zero set");
3004 
3005  case DRILL_G_UNKNOWN:
3006  default:
3007  return N_("unknown G-code");
3008  }
3009 } /* drill_g_code_name() */
3010 
3011 /* -------------------------------------------------------------- */
3013 const char *drill_m_code_name(drill_m_code_t m_code)
3014 {
3015  switch (m_code) {
3016  case DRILL_M_END:
3017  return N_("end of program");
3018  case DRILL_M_PATTERNEND:
3019  return N_("pattern end");
3020  case DRILL_M_REPEATPATTERNOFFSET:
3021  return N_("repeat pattern offset");
3022  case DRILL_M_STOPOPTIONAL:
3023  return N_("stop optional");
3024  case DRILL_M_SANDREND:
3025  return N_("step and repeat end");
3026  case DRILL_M_STOPINSPECTION:
3027  return N_("stop for inspection");
3028  case DRILL_M_ZAXISROUTEPOSITIONDEPTHCTRL:
3029  return N_("Z-axis rout position with depth control");
3030  case DRILL_M_ZAXISROUTEPOSITION:
3031  return N_("Z-axis rout position");
3032  case DRILL_M_RETRACTCLAMPING:
3033  return N_("retract with clamping");
3034  case DRILL_M_RETRACTNOCLAMPING:
3035  return N_("retract without clamping");
3036  case DRILL_M_TOOLTIPCHECK:
3037  return N_("tool tip check");
3038  case DRILL_M_PATTERN:
3039  return N_("pattern start");
3040  case DRILL_M_ENDREWIND:
3041  return N_("end of program with rewind");
3042  case DRILL_M_MESSAGELONG:
3043  return N_("long operator message");
3044  case DRILL_M_MESSAGE:
3045  return N_("operator message");
3046  case DRILL_M_HEADER:
3047  return N_("header start");
3048  case DRILL_M_VISANDRPATTERN:
3049  return N_("vision step and repeat pattern start");
3050  case DRILL_M_VISANDRPATTERNREWIND:
3051  return N_("vision step and repeat rewind");
3052  case DRILL_M_VISANDRPATTERNOFFSETCOUNTERCTRL:
3053  return N_("vision step and repeat offset counter control");
3054  case DRILL_M_REFSCALING:
3055  return N_("reference scaling on");
3056  case DRILL_M_REFSCALINGEND:
3057  return N_("reference scaling off");
3058  case DRILL_M_PECKDRILLING:
3059  return N_("peck drilling on");
3060  case DRILL_M_PECKDRILLINGEND:
3061  return N_("peck drilling off");
3062  case DRILL_M_SWAPAXIS:
3063  return N_("swap axes");
3064  case DRILL_M_METRIC:
3065  return N_("metric measuring mode");
3066  case DRILL_M_IMPERIAL:
3067  return N_("inch measuring mode");
3068  case DRILL_M_MIRRORX:
3069  return N_("mirror image X-axis");
3070  case DRILL_M_MIRRORY:
3071  return N_("mirror image Y-axis");
3072  case DRILL_M_HEADEREND:
3073  return N_("header end");
3074  case DRILL_M_CANNEDTEXTX:
3075  return N_("canned text along X-axis");
3076  case DRILL_M_CANNEDTEXTY:
3077  return N_("canned text along Y-axis");
3078  case DRILL_M_USERDEFPATTERN:
3079  return N_("user defined stored pattern");
3080 
3081  case DRILL_M_UNKNOWN:
3082  default:
3083  return N_("unknown M-code");
3084  }
3085 } /* drill_m_code_name() */
Dynamic GUI window creation header info.
const char * drill_g_code_name(drill_g_code_t g_code)
Return drill G-code name by code number.
Definition: drill.c:2939
const char * drill_m_code_name(drill_m_code_t m_code)
Return drill M-code name by code number.
Definition: drill.c:3013
Header info for the Excellon drill parsing functions.
gerbv_drill_stats_t * gerbv_drill_stats_new(void)
Allocates a new drill_stats structure.
Definition: drill_stats.c:48
Header info to the statistics generating functions for Excellon drill files.
void gerbv_destroy_image(gerbv_image_t *image)
Free an image structure.
Definition: gerb_image.c:106
gerbv_image_t * gerbv_create_image(gerbv_image_t *image, const gchar *type)
Allocate a new gerbv_image structure.
Definition: gerb_image.c:46
Header info for the RS274X parsing functions.
The main header file for the libgerbv library.
@ GERBV_APERTURE_STATE_ON
Definition: gerbv.h:180
@ GERBV_APERTURE_STATE_FLASH
Definition: gerbv.h:181
gerbv_omit_zeros_t
Definition: gerbv.h:289
@ GERBV_OMIT_ZEROS_TRAILING
Definition: gerbv.h:290
@ GERBV_OMIT_ZEROS_EXPLICIT
Definition: gerbv.h:291
@ GERBV_OMIT_ZEROS_LEADING
Definition: gerbv.h:289
@ GERBV_OMIT_ZEROS_UNSPECIFIED
Definition: gerbv.h:292
@ GERBV_MESSAGE_ERROR
Definition: gerbv.h:150
@ GERBV_MESSAGE_NOTE
Definition: gerbv.h:152
@ GERBV_MESSAGE_WARNING
Definition: gerbv.h:151
@ GERBV_APTYPE_CIRCLE
Definition: gerbv.h:160
gerbv_unit_t
Definition: gerbv.h:276
@ GERBV_UNIT_INCH
Definition: gerbv.h:276
@ GERBV_UNIT_MM
Definition: gerbv.h:277
@ GERBV_UNIT_UNSPECIFIED
Definition: gerbv.h:278
@ GERBV_INTERPOLATION_CW_CIRCULAR
Definition: gerbv.h:307
@ GERBV_INTERPOLATION_CCW_CIRCULAR
Definition: gerbv.h:308
@ GERBV_INTERPOLATION_LINEARx1
Definition: gerbv.h:303
@ GERBV_LAYERTYPE_DRILL
Definition: gerbv.h:330
gerbv_format_t * format
Definition: gerbv.h:739
gerbv_layer_t * layers
Definition: gerbv.h:736
gerbv_layertype_t layertype
Definition: gerbv.h:734
gerbv_net_t * netlist
Definition: gerbv.h:741
gerbv_drill_stats_t * drill_stats
Definition: gerbv.h:743
gerbv_aperture_t * aperture[APERTURE_MAX]
Definition: gerbv.h:735
gerbv_netstate_t * states
Definition: gerbv.h:737
gerbv_image_info_t * info
Definition: gerbv.h:740
gerbv_render_size_t boundingBox
Definition: gerbv.h:673
gerbv_layer_t * layer
Definition: gerbv.h:680
double stop_y
Definition: gerbv.h:672
gerbv_aperture_state_t aperture_state
Definition: gerbv.h:675
double stop_x
Definition: gerbv.h:671
double start_x
Definition: gerbv.h:669
gerbv_netstate_t * state
Definition: gerbv.h:681
struct gerbv_net * next
Definition: gerbv.h:678
double start_y
Definition: gerbv.h:670
gerbv_interpolation_t interpolation
Definition: gerbv.h:676
gerbv_cirseg_t * cirseg
Definition: gerbv.h:677
int aperture
Definition: gerbv.h:674
gerbv_unit_t unit
Definition: gerbv.h:658