]> git.draconx.ca Git - slotifier.git/blob - src/slotifier.c
6ca3213ff14ed91937605e5f38f0f07d02cf6ac1
[slotifier.git] / src / slotifier.c
1 /*
2  * Utility to convert overlapping Excellon drill hits into drill slots.
3  * Copyright © 2018 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 2018 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;
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                 if (drill->aperture[biggest_tool]->parameter[0] < dia)
234                         biggest_tool = abs(hole->aperture);
235
236                 if (CNearTreeFindInSphere(t, dia, 0, tmp, xy, 1) != 0) {
237                         /* We should always should find at least one hole! */
238                         fprintf(stderr, _("%s: fatal error searching holes\n"),
239                                         progname);
240                         goto err;
241                 }
242
243                 for (j = 0; j < CVectorSize(tmp); j++) {
244                         /* I don't know why, but CNearTree returns a list
245                          * of pointers to its internal copies of pointers
246                          * to the objects in the tree.  So we need this
247                          * double indirection to get the actual hole. */
248                         void *p;
249                         CVectorGetElement(tmp, &p, j);
250                         hole = *(void **)p;
251
252                         if (hole->aperture >= 0) {
253                                 CVectorAddElement(group, &hole);
254                                 hole->aperture = -hole->aperture;
255                         }
256                 }
257         }
258
259         /* Compare each pair of matched points to find the longest slot. */
260         if (CVectorSize(group) > 1) {
261                 int biggest_slot = 0;
262                 double bestlen = 0;
263
264                 for (i = 0; i < CVectorSize(group) - 1; i++) {
265                         CVectorGetElement(group, &hole, i);
266                         for (j = i+1; j < CVectorSize(group); j++) {
267                                 gerbv_net_t *h2;
268                                 double newlen;
269
270                                 CVectorGetElement(group, &h2, j);
271                                 newlen = hypot(hole->start_x - h2->start_x,
272                                                hole->start_y - h2->start_y);
273
274                                 if (newlen > bestlen) {
275                                         hole->stop_x = h2->start_x;
276                                         hole->stop_y = h2->stop_y;
277                                         bestlen = newlen;
278                                         biggest_slot = i;
279                                 }
280                         }
281                 }
282
283                 CVectorGetElement(group, &hole, biggest_slot);
284                 hole->aperture = biggest_tool;
285                 hole->aperture_state = GERBV_APERTURE_STATE_ON;
286
287                 if (verbose) {
288                         const char *fmt = ngettext(
289                                 "%s: merged %zu hole into slot (%.4f,%.4f)-(%.4f,%.4f)\n",
290                                 "%s: merged %zu holes into slot (%.4f,%.4f)-(%.4f,%.4f)\n",
291                                 CVectorSize(group));
292
293                         fprintf(stderr, fmt, progname, CVectorSize(group),
294                                              hole->start_x, hole->start_y,
295                                              hole->stop_x, hole->stop_y);
296                 }
297         } else {
298                 /* The only hole we found was our original one, restore
299                  * initial state. */
300                 CVectorGetElement(group, &hole, 0);
301
302                 assert(hole->aperture < 0);
303                 hole->aperture = -hole->aperture;
304
305                 if (verbose > 1) {
306                         fprintf(stderr, _("%s: hole at (%.4f,%.4f) not merged\n"),
307                                         progname, hole->start_x, hole->start_y);
308                 }
309         }
310
311         ret = 0;
312 err:
313         CVectorFree(&tmp);
314         CVectorFree(&group);
315         return ret;
316 }
317
318 static int slotify(gerbv_image_t *drill)
319 {
320         CNearTreeHandle t;
321         CVectorHandle holes;
322         int ret = 0;
323         size_t i;
324
325         t = build_search_tree(drill);
326         if (!t) {
327                 fprintf(stderr, _("%s: failed to build search tree\n"),
328                                 progname);
329                 return -1;
330         }
331
332         CNearTreeObjects(t, &holes);
333         for (i = 0; i < CVectorSize(holes); i++) {
334                 gerbv_net_t *hole;
335
336                 CVectorGetElement(holes, &hole, i);
337                 /* Skip holes we've already looked at */
338                 if (hole->aperture < 0)
339                         continue;
340                 if (hole->aperture_state == GERBV_APERTURE_STATE_ON)
341                         continue;
342
343                 if (combine_holes(drill, hole, t) < 0) {
344                         ret = -1;
345                         break;
346                 }
347         }
348
349         /* Clean out any holes we don't need anymore */
350         for (i = 0; i < CVectorSize(holes); i++) {
351                 gerbv_net_t *hole;
352
353                 CVectorGetElement(holes, &hole, i);
354                 if (hole->aperture < 0)
355                         gerbv_image_delete_net(hole);
356         }
357
358         CNearTreeFree(&t);
359         return ret;
360 }
361
362 int main(int argc, char **argv)
363 {
364         const char *outfile = "/dev/stdout";
365         gerbv_project_t *gp;
366         gerbv_image_t *drill;
367         int opt, ret = 0;
368
369         if (argc > 0)
370                 progname = argv[0];
371
372         init_i18n();
373
374         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
375                 switch (opt) {
376                 case 'o':
377                         outfile = optarg;
378                         break;
379                 case 'v':
380                         verbose++;
381                         break;
382                 case 'V':
383                         print_version();
384                         return EXIT_SUCCESS;
385                 case 'H':
386                         print_help();
387                         return EXIT_SUCCESS;
388                 default:
389                         print_usage(stderr);
390                         return EXIT_FAILURE;
391                 }
392         }
393
394         if (!argv[optind]) {
395                 fprintf(stderr, _("%s: error: must specify a filename\n"),
396                                 progname);
397                 print_usage(stderr);
398                 return EXIT_FAILURE;
399         }
400
401         if (optind + 1 < argc) {
402                 fprintf(stderr, _("%s: error: excess command-line arguments\n"),
403                                 progname);
404                 print_usage(stderr);
405                 return EXIT_FAILURE;
406         }
407
408         gp = gerbv_create_project();
409         if (!gp)
410                 return EXIT_FAILURE;
411
412         gerbv_open_layer_from_filename(gp, argv[optind]);
413         if (!gp->file[0]) {
414                 ret = EXIT_FAILURE;
415                 goto out;
416         }
417
418         drill = gp->file[0]->image;
419         if (drill->layertype != GERBV_LAYERTYPE_DRILL) {
420                 fprintf(stderr, _("%s: %s: error: not a drill file\n"),
421                                 progname, argv[optind]);
422                 ret = EXIT_FAILURE;
423                 goto out;
424         }
425
426         if (slotify(drill) != 0) {
427                 ret = EXIT_FAILURE;
428                 goto out;
429         }
430
431         if (!gerbv_export_drill_file_from_image(outfile, drill, NULL))
432                 ret = EXIT_FAILURE;
433 out:
434         gerbv_destroy_project(gp);
435         return 0;
436 }