2 * Utility to convert overlapping Excellon drill hits into drill slots.
3 * Copyright © 2018, 2021 Nick Bowler
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.
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.
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/>.
28 #include <localcharset.h>
32 #include <CNearTree.h>
40 #define _(x) (gettext(x))
42 static const char *progname = "slotifier";
43 static unsigned verbose;
46 static const char sopts[] = SOPT_STRING;
47 static const struct option lopts[] = {
52 static void print_version(void)
54 const char *copysign = "(C)";
55 void *convsign = NULL;
60 convsign = str_iconv("\xc2\xa9", "UTF-8", locale_charset());
65 printf("Copyright %s 2021 Nick Bowler.\n", copysign);
66 puts("License GPLv3+: GNU GPL version 3 or any later version.");
67 puts("This is free software: you are free to change and redistribute it.");
68 puts("There is NO WARRANTY, to the extent permitted by law.");
73 static void print_usage(FILE *f)
75 fprintf(f, _("Usage: %s [options] [-o filename] filename\n"), progname);
77 fprintf(f, _("Try %s --help for more information.\n"),
82 print_optstring(const struct option *opt, const struct lopt_help *help)
91 w = snprintf(optstring, sizeof optstring,
92 _(" -%c, --%s=%s"), opt->val, opt->name,
93 pgettext_expr(opt->name, help->arg));
95 w = snprintf(optstring, sizeof optstring,
96 _(" -%c, --%s"), opt->val, opt->name);
102 w = mbsnwidth(optstring, w, 0);
103 printf("%s", optstring);
108 w = printf(" -%c, --%s=%s", opt->val, opt->name, help->arg);
110 w = printf(" -%c, --%s", opt->val, opt->name);
113 if (w < 0 || w > 18) {
122 * Print a string, with each line indented by i spaces. The first line
123 * will be indented by w fewer spaces (to account for the cursor being in
124 * some other column).
126 static void print_block(const char *s, int i, int w)
129 const char *nl = strchr(s, '\n');
130 int n = (nl ? nl-s : -1);
132 printf("%*s%.*s\n", i-w, "", n, s);
140 static void print_help(void)
142 const struct option *opt;
146 puts(_("This is \"slotifier\": a tool to convert overlapping drill hits in Excellon\n"
147 "drill files to G85 drill slots."));
151 for (opt = lopts; opt->val; opt++) {
152 struct lopt_help help;
155 if (!lopt_get_help(opt, &help))
158 w = print_optstring(opt, &help);
161 help.desc = pgettext_expr(opt->name, help.desc);
163 print_block(help.desc, 20, w);
167 puts(_("For more information, see the slotifier(1) man page."));
171 * TRANSLATORS: Please add *another line* indicating where users should
172 * report translation bugs.
174 printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
177 static void init_i18n(void)
179 setlocale(LC_ALL, "");
180 bindtextdomain(PACKAGE, LOCALEDIR);
184 static CNearTreeHandle build_search_tree(gerbv_image_t *drill)
189 if (CNearTreeCreate(&t, 2, CNEARTREE_TYPE_DOUBLE|CNEARTREE_NORM_L2))
192 /* Build a search tree from all the holes. */
193 for (x = drill->netlist; x; x = x->next) {
194 double xy[2] = { x->start_x, x->start_y };
196 assert(x->aperture >= 0 && x->aperture < APERTURE_MAX);
198 /* Skip things that aren't drill hits */
199 if (!drill->aperture[x->aperture])
202 /* Holes are marked as "flashing"; otherwise it's an existing
204 if (x->aperture_state != GERBV_APERTURE_STATE_FLASH)
207 if (CNearTreeInsert(t, xy, x)) {
213 if (CNearTreeCompleteDelayedInsert(t)) {
221 static gerbv_aperture_type_t tool_type(gerbv_image_t *drill, int aperture)
223 gerbv_aperture_t *tool = drill->aperture[abs(aperture)];
228 static double tool_radius(gerbv_image_t *drill, int aperture)
230 gerbv_aperture_t *tool = drill->aperture[abs(aperture)];
232 /* Half a mil slop to decisively include points on boundary. */
233 return tool->parameter[0] / 2.0 + 0.0005;
236 static int holes_overlap(gerbv_image_t *drill, gerbv_net_t *a, gerbv_net_t *b)
238 double d = hypot(a->start_x - b->start_x, a->start_y - b->start_y);
240 return tool_radius(drill, a->aperture) >= d
241 || tool_radius(drill, b->aperture) >= d;
244 static int combine_holes(gerbv_image_t *drill, gerbv_net_t *hole,
247 CVectorHandle group, tmp;
248 int biggest_tool, ret = -1;
253 * Since we consider holes in order of decreasing size, the initial hole
254 * considered is by definition the biggest one we will find in a group.
256 biggest_r = tool_radius(drill, (biggest_tool = hole->aperture));
258 if (CVectorCreate(&group, sizeof (gerbv_net_t *), 10)) {
259 fprintf(stderr, _("%s: failed to allocate memory\n"), progname);
263 if (CVectorCreate(&tmp, sizeof (void *), 10)) {
264 fprintf(stderr, _("%s: failed to allocate memory\n"), progname);
270 * Breadth-first nearest neighbour search of holes. We negate the
271 * aperture to indicate which holes have been previously visited.
273 CVectorAddElement(group, &hole);
274 hole->aperture = -hole->aperture;
276 for (i = 0; i < CVectorSize(group); i++) {
279 CVectorGetElement(group, &hole, i);
281 assert(tool_type(drill, hole->aperture) == GERBV_APTYPE_CIRCLE);
282 assert(tool_radius(drill, hole->aperture) <= biggest_r);
284 xy[0] = hole->start_x; xy[1] = hole->start_y;
285 if (CNearTreeFindInSphere(t, biggest_r, 0, tmp, xy, 1) != 0) {
286 /* We should always should find at least one hole! */
287 fprintf(stderr, _("%s: fatal error searching holes\n"),
292 for (j = 0; j < CVectorSize(tmp); j++) {
293 /* I don't know why, but CNearTree returns a list
294 * of pointers to its internal copies of pointers
295 * to the objects in the tree. So we need this
296 * double indirection to get the actual hole. */
297 gerbv_net_t *newhole;
300 CVectorGetElement(tmp, &p, j);
301 newhole = *(void **)p;
303 if (newhole->aperture < 0)
304 continue; /* already visited */
306 if (holes_overlap(drill, hole, newhole)) {
307 CVectorAddElement(group, &newhole);
308 newhole->aperture = -newhole->aperture;
313 /* Compare each pair of matched points to find the longest slot. */
314 if (CVectorSize(group) > 1) {
315 int biggest_slot = 0;
318 for (i = 0; i < CVectorSize(group) - 1; i++) {
319 CVectorGetElement(group, &hole, i);
320 for (j = i+1; j < CVectorSize(group); j++) {
324 CVectorGetElement(group, &h2, j);
325 newlen = hypot(hole->start_x - h2->start_x,
326 hole->start_y - h2->start_y);
328 if (newlen > bestlen) {
329 hole->stop_x = h2->start_x;
330 hole->stop_y = h2->stop_y;
337 CVectorGetElement(group, &hole, biggest_slot);
338 hole->aperture = biggest_tool;
339 hole->aperture_state = GERBV_APERTURE_STATE_ON;
342 const char *fmt = ngettext(
343 "%s: merged %zu hole into slot (%.4f,%.4f)-(%.4f,%.4f)\n",
344 "%s: merged %zu holes into slot (%.4f,%.4f)-(%.4f,%.4f)\n",
347 fprintf(stderr, fmt, progname, CVectorSize(group),
348 hole->start_x, hole->start_y,
349 hole->stop_x, hole->stop_y);
352 /* The only hole we found was our original one, restore
354 CVectorGetElement(group, &hole, 0);
356 assert(hole->aperture < 0);
357 hole->aperture = -hole->aperture;
360 fprintf(stderr, _("%s: hole at (%.4f,%.4f) not merged\n"),
361 progname, hole->start_x, hole->start_y);
373 * Order two holes by hole diameter.
375 static gerbv_image_t *hsc_drill_data;
376 static int hole_size_cmp(const void *a_, const void *b_)
378 gerbv_net_t * const *a = a_, * const *b = b_;
379 gerbv_aperture_t *ta, *tb;
381 ta = hsc_drill_data->aperture[abs(a[0]->aperture)];
382 assert(ta->type == GERBV_APTYPE_CIRCLE);
384 tb = hsc_drill_data->aperture[abs(b[0]->aperture)];
385 assert(tb->type == GERBV_APTYPE_CIRCLE);
387 if (ta->parameter[0] > tb->parameter[0])
389 if (ta->parameter[0] < tb->parameter[0])
394 static void sort_holes_by_size(gerbv_image_t *drill, CVectorHandle work)
396 hsc_drill_data = drill;
397 qsort(work->array, work->size, work->elementsize, hole_size_cmp);
400 static int slotify(gerbv_image_t *drill)
402 CVectorHandle holes, work;
407 t = build_search_tree(drill);
409 fprintf(stderr, _("%s: failed to build search tree\n"),
414 CNearTreeObjects(t, &holes);
418 if (CVectorCreate(&work, sizeof (gerbv_net_t *), CVectorSize(holes))) {
419 fprintf(stderr, _("%s: failed to allocate memory\n"), progname);
423 memcpy(work->array, holes->array, holes->size * holes->elementsize);
424 work->size = holes->size;
425 sort_holes_by_size(drill, work);
427 for (i = 0; i < CVectorSize(work); i++) {
430 CVectorGetElement(work, &hole, i);
431 /* Skip holes we've already looked at */
432 if (hole->aperture < 0)
434 if (hole->aperture_state == GERBV_APERTURE_STATE_ON)
438 fprintf(stderr, _("%s: checking hole at (%.4f,%.4f) for overlaps\n"),
439 progname, hole->start_x, hole->start_y);
442 if (combine_holes(drill, hole, t) < 0) {
448 /* Clean out any holes we don't need anymore */
449 for (i = 0; i < CVectorSize(holes); i++) {
452 CVectorGetElement(holes, &hole, i);
453 if (hole->aperture < 0)
454 gerbv_image_delete_net(hole);
463 int main(int argc, char **argv)
465 const char *outfile = "/dev/stdout";
467 gerbv_image_t *drill;
475 while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
496 fprintf(stderr, _("%s: error: must specify a filename\n"),
502 if (optind + 1 < argc) {
503 fprintf(stderr, _("%s: error: excess command-line arguments\n"),
509 gp = gerbv_create_project();
513 gerbv_open_layer_from_filename(gp, argv[optind]);
519 drill = gp->file[0]->image;
520 if (drill->layertype != GERBV_LAYERTYPE_DRILL) {
521 fprintf(stderr, _("%s: %s: error: not a drill file\n"),
522 progname, argv[optind]);
527 if (slotify(drill) != 0) {
532 if (!gerbv_export_drill_file_from_image(outfile, drill, NULL))
535 gerbv_destroy_project(gp);