]> git.draconx.ca Git - slotifier.git/blob - src/slotifier.c
Properly return failure status from main.
[slotifier.git] / src / slotifier.c
1 /*
2  * Utility to convert overlapping Excellon drill hits into drill slots.
3  * Copyright © 2018, 2021 Nick Bowler
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <stddef.h>
23 #include <string.h>
24 #include <locale.h>
25 #include <stdint.h>
26 #include <assert.h>
27 #include <getopt.h>
28 #include <gettext.h>
29 #include <CNearTree.h>
30 #include <gerbv.h>
31 #include <striconv.h>
32 #include <localcharset.h>
33
34 #if !ENABLE_NLS
35 #  undef ENABLE_NLS
36 #  define ENABLE_NLS 0
37 #endif
38
39 #define _(x) (gettext(x))
40
41 static unsigned verbose;
42
43 static const char *progname = "slotifier";
44 static const char sopts[] = "o:vVH";
45 static const struct option lopts[] = {
46         { "output",  1, NULL, 'o' },
47         { "verbose", 0, NULL, 'v' },
48         { "version", 0, NULL, 'V' },
49         { "help",    0, NULL, 'H' },
50         { 0 }
51 };
52
53 static void print_version(void)
54 {
55         const char *copysign = "(C)";
56         void *convsign = NULL;
57
58         puts(PACKAGE_STRING);
59
60         if (ENABLE_NLS) {
61                 convsign = str_iconv("\xc2\xa9", "UTF-8", locale_charset());
62                 if (convsign)
63                         copysign = convsign;
64         }
65
66         printf("Copyright %s 2021 Nick Bowler.\n", copysign);
67         puts("License GPLv3+: GNU GPL version 3 or any later version.");
68         puts("This is free software: you are free to change and redistribute it.");
69         puts("There is NO WARRANTY, to the extent permitted by law.");
70
71         free(convsign);
72 }
73
74 static void print_usage(FILE *f)
75 {
76         fprintf(f, _("Usage: %s [options] [-o filename] filename\n"), progname);
77         if (f != stdout)
78                 fprintf(f, _("Try %s --help for more information.\n"),
79                            progname);
80 }
81
82 static void print_help(void)
83 {
84         const struct option *opt;
85         print_usage(stdout);
86
87         puts(_("This is \"slotifier\": a tool to convert overlapping drill hits in Excellon\n"
88                "drill files to G85 drill slots."));
89         putchar('\n');
90
91         puts(_("Options:"));
92         for (opt = lopts; opt->val; opt++) {
93                 const char *line, *text = "ARG";
94                 int w = 0;
95
96                 if (opt->has_arg) {
97                         switch (opt->val) {
98                         case 'o': text = _("FILE");
99                         }
100
101                         w += printf(_("  -%c, --%s=%s"),
102                                     opt->val, opt->name, text);
103                 } else {
104                         w += printf(_("  -%c, --%s"), opt->val, opt->name);
105                 }
106
107                 if (w > 18) {
108                         putchar('\n');
109                         w = 0;
110                 }
111
112                 switch (opt->val) {
113                 case 'o':
114                         text = _("Output to FILE, instead of standard output.");
115                         break;
116                 case 'v':
117                         text = _("Increase verbosity (can be specified more than once).");
118                         break;
119                 case 'V':
120                         text = _("Print a version message and then exit.");
121                         break;
122                 case 'H':
123                         text = _("Print this message and then exit.");
124                         break;
125                 default:
126                         if (w)
127                                 putchar('\n');
128                         continue;
129                 }
130
131                 for (line = text; line[0];) {
132                         const char *nl = strchr(line, '\n');
133
134                         if (!nl) {
135                                 printf("%*s%s\n", 20-w, "", line);
136                                 break;
137                         }
138
139                         printf("%*s%.*s\n", 20-w, "", (int)(nl-line), line);
140                         line = nl+1;
141                 }
142         }
143         putchar('\n');
144
145         puts(_("For more information, see the slotifier(1) man page."));
146         putchar('\n');
147
148         printf(_("Report bugs to <%s>."), PACKAGE_BUGREPORT);
149         putchar('\n');
150 }
151
152 static void init_i18n(void)
153 {
154         setlocale(LC_ALL, "");
155         bindtextdomain(PACKAGE, LOCALEDIR);
156         textdomain(PACKAGE);
157 }
158
159 static CNearTreeHandle build_search_tree(gerbv_image_t *drill)
160 {
161         CNearTreeHandle t;
162         gerbv_net_t *x;
163
164         if (CNearTreeCreate(&t, 2, CNEARTREE_TYPE_DOUBLE|CNEARTREE_NORM_L2))
165                 return 0;
166
167         /* Build a search tree from all the holes. */
168         for (x = drill->netlist; x; x = x->next) {
169                 double xy[2] = { x->start_x, x->start_y };
170
171                 assert(x->aperture >= 0 && x->aperture < APERTURE_MAX);
172
173                 /* Skip things that aren't drill hits */
174                 if (!drill->aperture[x->aperture])
175                         continue;
176
177                 /* Holes are marked as "flashing"; otherwise it's an existing
178                  * slot; skip it. */
179                 if (x->aperture_state != GERBV_APERTURE_STATE_FLASH)
180                         continue;
181
182                 if (CNearTreeInsert(t, xy, x)) {
183                         CNearTreeFree(&t);
184                         return 0;
185                 }
186         }
187
188         if (CNearTreeCompleteDelayedInsert(t)) {
189                 CNearTreeFree(&t);
190                 return 0;
191         }
192
193         return t;
194 }
195
196 static int combine_holes(gerbv_image_t *drill, gerbv_net_t *hole,
197                          CNearTreeHandle t)
198 {
199         int biggest_tool = hole->aperture;
200         CVectorHandle group, tmp;
201         gerbv_aperture_t *tool;
202         int ret = -1;
203         size_t i, j;
204
205         if (CVectorCreate(&group, sizeof (gerbv_net_t *), 10)) {
206                 fprintf(stderr, _("%s: failed to allocate memory\n"), progname);
207                 return -1;
208         }
209
210         if (CVectorCreate(&tmp, sizeof (void *), 10)) {
211                 fprintf(stderr, _("%s: failed to allocate memory\n"), progname);
212                 CVectorFree(&group);
213                 return -1;
214         }
215
216         /*
217          * Breadth-first nearest neighbour search of holes.  We negate the
218          * aperture to indicate which holes have been previously visited.
219          */
220         CVectorAddElement(group, &hole);
221         hole->aperture = -hole->aperture;
222
223         for (i = 0; i < CVectorSize(group); i++) {
224                 double xy[2], dia, r;
225
226                 CVectorGetElement(group, &hole, i);
227                 tool = drill->aperture[abs(hole->aperture)];
228                 assert(tool->type == GERBV_APTYPE_CIRCLE);
229
230                 xy[0] = hole->start_x; xy[1] = hole->start_y;
231                 dia = tool->parameter[0];
232
233                 /* Half a mil slop to decisively include points on boundary. */
234                 r = dia/2 + 0.0005;
235
236                 if (drill->aperture[biggest_tool]->parameter[0] < dia)
237                         biggest_tool = abs(hole->aperture);
238
239                 if (CNearTreeFindInSphere(t, r, 0, tmp, xy, 1) != 0) {
240                         /* We should always should find at least one hole! */
241                         fprintf(stderr, _("%s: fatal error searching holes\n"),
242                                         progname);
243                         goto err;
244                 }
245
246                 for (j = 0; j < CVectorSize(tmp); j++) {
247                         /* I don't know why, but CNearTree returns a list
248                          * of pointers to its internal copies of pointers
249                          * to the objects in the tree.  So we need this
250                          * double indirection to get the actual hole. */
251                         void *p;
252                         CVectorGetElement(tmp, &p, j);
253                         hole = *(void **)p;
254
255                         if (hole->aperture >= 0) {
256                                 CVectorAddElement(group, &hole);
257                                 hole->aperture = -hole->aperture;
258                         }
259                 }
260         }
261
262         /* Compare each pair of matched points to find the longest slot. */
263         if (CVectorSize(group) > 1) {
264                 int biggest_slot = 0;
265                 double bestlen = 0;
266
267                 for (i = 0; i < CVectorSize(group) - 1; i++) {
268                         CVectorGetElement(group, &hole, i);
269                         for (j = i+1; j < CVectorSize(group); j++) {
270                                 gerbv_net_t *h2;
271                                 double newlen;
272
273                                 CVectorGetElement(group, &h2, j);
274                                 newlen = hypot(hole->start_x - h2->start_x,
275                                                hole->start_y - h2->start_y);
276
277                                 if (newlen > bestlen) {
278                                         hole->stop_x = h2->start_x;
279                                         hole->stop_y = h2->stop_y;
280                                         bestlen = newlen;
281                                         biggest_slot = i;
282                                 }
283                         }
284                 }
285
286                 CVectorGetElement(group, &hole, biggest_slot);
287                 hole->aperture = biggest_tool;
288                 hole->aperture_state = GERBV_APERTURE_STATE_ON;
289
290                 if (verbose) {
291                         const char *fmt = ngettext(
292                                 "%s: merged %zu hole into slot (%.4f,%.4f)-(%.4f,%.4f)\n",
293                                 "%s: merged %zu holes into slot (%.4f,%.4f)-(%.4f,%.4f)\n",
294                                 CVectorSize(group));
295
296                         fprintf(stderr, fmt, progname, CVectorSize(group),
297                                              hole->start_x, hole->start_y,
298                                              hole->stop_x, hole->stop_y);
299                 }
300         } else {
301                 /* The only hole we found was our original one, restore
302                  * initial state. */
303                 CVectorGetElement(group, &hole, 0);
304
305                 assert(hole->aperture < 0);
306                 hole->aperture = -hole->aperture;
307
308                 if (verbose > 1) {
309                         fprintf(stderr, _("%s: hole at (%.4f,%.4f) not merged\n"),
310                                         progname, hole->start_x, hole->start_y);
311                 }
312         }
313
314         ret = 0;
315 err:
316         CVectorFree(&tmp);
317         CVectorFree(&group);
318         return ret;
319 }
320
321 static int slotify(gerbv_image_t *drill)
322 {
323         CNearTreeHandle t;
324         CVectorHandle holes;
325         int ret = 0;
326         size_t i;
327
328         t = build_search_tree(drill);
329         if (!t) {
330                 fprintf(stderr, _("%s: failed to build search tree\n"),
331                                 progname);
332                 return -1;
333         }
334
335         CNearTreeObjects(t, &holes);
336         if (!holes)
337                 goto out;
338
339         for (i = 0; i < CVectorSize(holes); i++) {
340                 gerbv_net_t *hole;
341
342                 CVectorGetElement(holes, &hole, i);
343                 /* Skip holes we've already looked at */
344                 if (hole->aperture < 0)
345                         continue;
346                 if (hole->aperture_state == GERBV_APERTURE_STATE_ON)
347                         continue;
348
349                 if (combine_holes(drill, hole, t) < 0) {
350                         ret = -1;
351                         break;
352                 }
353         }
354
355         /* Clean out any holes we don't need anymore */
356         for (i = 0; i < CVectorSize(holes); i++) {
357                 gerbv_net_t *hole;
358
359                 CVectorGetElement(holes, &hole, i);
360                 if (hole->aperture < 0)
361                         gerbv_image_delete_net(hole);
362         }
363
364 out:
365         CNearTreeFree(&t);
366         return ret;
367 }
368
369 int main(int argc, char **argv)
370 {
371         const char *outfile = "/dev/stdout";
372         gerbv_project_t *gp;
373         gerbv_image_t *drill;
374         int opt, ret = 0;
375
376         if (argc > 0)
377                 progname = argv[0];
378
379         init_i18n();
380
381         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
382                 switch (opt) {
383                 case 'o':
384                         outfile = optarg;
385                         break;
386                 case 'v':
387                         verbose++;
388                         break;
389                 case 'V':
390                         print_version();
391                         return EXIT_SUCCESS;
392                 case 'H':
393                         print_help();
394                         return EXIT_SUCCESS;
395                 default:
396                         print_usage(stderr);
397                         return EXIT_FAILURE;
398                 }
399         }
400
401         if (!argv[optind]) {
402                 fprintf(stderr, _("%s: error: must specify a filename\n"),
403                                 progname);
404                 print_usage(stderr);
405                 return EXIT_FAILURE;
406         }
407
408         if (optind + 1 < argc) {
409                 fprintf(stderr, _("%s: error: excess command-line arguments\n"),
410                                 progname);
411                 print_usage(stderr);
412                 return EXIT_FAILURE;
413         }
414
415         gp = gerbv_create_project();
416         if (!gp)
417                 return EXIT_FAILURE;
418
419         gerbv_open_layer_from_filename(gp, argv[optind]);
420         if (!gp->file[0]) {
421                 ret = EXIT_FAILURE;
422                 goto out;
423         }
424
425         drill = gp->file[0]->image;
426         if (drill->layertype != GERBV_LAYERTYPE_DRILL) {
427                 fprintf(stderr, _("%s: %s: error: not a drill file\n"),
428                                 progname, argv[optind]);
429                 ret = EXIT_FAILURE;
430                 goto out;
431         }
432
433         if (slotify(drill) != 0) {
434                 ret = EXIT_FAILURE;
435                 goto out;
436         }
437
438         if (!gerbv_export_drill_file_from_image(outfile, drill, NULL))
439                 ret = EXIT_FAILURE;
440 out:
441         gerbv_destroy_project(gp);
442         return ret;
443 }