/* * Utility to convert overlapping Excellon drill hits into drill slots. * Copyright © 2018 Nick Bowler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !ENABLE_NLS # undef ENABLE_NLS # define ENABLE_NLS 0 #endif #define _(x) (gettext(x)) static unsigned verbose; static const char *progname = "slotifier"; static const char sopts[] = "o:vVH"; static const struct option lopts[] = { { "output", 1, NULL, 'o' }, { "verbose", 0, NULL, 'v' }, { "version", 0, NULL, 'V' }, { "help", 0, NULL, 'H' }, { 0 } }; static void print_version(void) { const char *copysign = "(C)"; void *convsign = NULL; puts(PACKAGE_STRING); if (ENABLE_NLS) { convsign = str_iconv("\xc2\xa9", "UTF-8", locale_charset()); if (convsign) copysign = convsign; } printf("Copyright %s 2018 Nick Bowler.\n", copysign); puts("License GPLv3+: GNU GPL version 3 or any later version."); puts("This is free software: you are free to change and redistribute it."); puts("There is NO WARRANTY, to the extent permitted by law."); free(convsign); } static void print_usage(FILE *f) { fprintf(f, _("Usage: %s [options] [-o filename] filename\n"), progname); if (f != stdout) fprintf(f, _("Try %s --help for more information.\n"), progname); } static void print_help(void) { const struct option *opt; print_usage(stdout); puts(_("This is \"slotifier\": a tool to convert overlapping drill hits in Excellon\n" "drill files to G85 drill slots.")); putchar('\n'); puts(_("Options:")); for (opt = lopts; opt->val; opt++) { const char *line, *text = "ARG"; int w = 0; if (opt->has_arg) { switch (opt->val) { case 'o': text = _("FILE"); } w += printf(_(" -%c, --%s=%s"), opt->val, opt->name, text); } else { w += printf(_(" -%c, --%s"), opt->val, opt->name); } if (w > 18) { putchar('\n'); w = 0; } switch (opt->val) { case 'o': text = _("Output to FILE, instead of standard output."); break; case 'v': text = _("Increase verbosity (can be specified more than once)."); break; case 'V': text = _("Print a version message and then exit."); break; case 'H': text = _("Print this message and then exit."); break; default: if (w) putchar('\n'); continue; } for (line = text; line[0];) { const char *nl = strchr(line, '\n'); if (!nl) { printf("%*s%s\n", 20-w, "", line); break; } printf("%*s%.*s\n", 20-w, "", (int)(nl-line), line); line = nl+1; } } putchar('\n'); puts(_("For more information, see the slotifier(1) man page.")); putchar('\n'); printf(_("Report bugs to <%s>."), PACKAGE_BUGREPORT); putchar('\n'); } static void init_i18n(void) { setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); } static CNearTreeHandle build_search_tree(gerbv_image_t *drill) { CNearTreeHandle t; gerbv_net_t *x; if (CNearTreeCreate(&t, 2, CNEARTREE_TYPE_DOUBLE|CNEARTREE_NORM_L2)) return 0; /* Build a search tree from all the holes. */ for (x = drill->netlist; x; x = x->next) { double xy[2] = { x->start_x, x->start_y }; assert(x->aperture >= 0 && x->aperture < APERTURE_MAX); /* Skip things that aren't drill hits */ if (!drill->aperture[x->aperture]) continue; /* Holes are marked as "flashing"; otherwise it's an existing * slot; skip it. */ if (x->aperture_state != GERBV_APERTURE_STATE_FLASH) continue; if (CNearTreeInsert(t, xy, x)) { CNearTreeFree(&t); return 0; } } if (CNearTreeCompleteDelayedInsert(t)) { CNearTreeFree(&t); return 0; } return t; } static int combine_holes(gerbv_image_t *drill, gerbv_net_t *hole, CNearTreeHandle t) { int biggest_tool = hole->aperture; CVectorHandle group, tmp; gerbv_aperture_t *tool; int ret = -1; size_t i, j; if (CVectorCreate(&group, sizeof (gerbv_net_t *), 10)) { fprintf(stderr, _("%s: failed to allocate memory\n"), progname); return -1; } if (CVectorCreate(&tmp, sizeof (void *), 10)) { fprintf(stderr, _("%s: failed to allocate memory\n"), progname); CVectorFree(&group); return -1; } /* * Breadth-first nearest neighbour search of holes. We negate the * aperture to indicate which holes have been previously visited. */ CVectorAddElement(group, &hole); hole->aperture = -hole->aperture; for (i = 0; i < CVectorSize(group); i++) { double xy[2], dia; CVectorGetElement(group, &hole, i); tool = drill->aperture[abs(hole->aperture)]; assert(tool->type == GERBV_APTYPE_CIRCLE); xy[0] = hole->start_x; xy[1] = hole->start_y; dia = tool->parameter[0]; if (drill->aperture[biggest_tool]->parameter[0] < dia) biggest_tool = abs(hole->aperture); if (CNearTreeFindInSphere(t, dia, 0, tmp, xy, 1) != 0) { /* We should always should find at least one hole! */ fprintf(stderr, _("%s: fatal error searching holes\n"), progname); goto err; } for (j = 0; j < CVectorSize(tmp); j++) { /* I don't know why, but CNearTree returns a list * of pointers to its internal copies of pointers * to the objects in the tree. So we need this * double indirection to get the actual hole. */ void *p; CVectorGetElement(tmp, &p, j); hole = *(void **)p; if (hole->aperture >= 0) { CVectorAddElement(group, &hole); hole->aperture = -hole->aperture; } } } /* Compare each pair of matched points to find the longest slot. */ if (CVectorSize(group) > 1) { int biggest_slot = 0; double bestlen = 0; for (i = 0; i < CVectorSize(group) - 1; i++) { CVectorGetElement(group, &hole, i); for (j = i+1; j < CVectorSize(group); j++) { gerbv_net_t *h2; double newlen; CVectorGetElement(group, &h2, j); newlen = hypot(hole->start_x - h2->start_x, hole->start_y - h2->start_y); if (newlen > bestlen) { hole->stop_x = h2->start_x; hole->stop_y = h2->stop_y; bestlen = newlen; biggest_slot = i; } } } CVectorGetElement(group, &hole, biggest_slot); hole->aperture = biggest_tool; hole->aperture_state = GERBV_APERTURE_STATE_ON; if (verbose) { const char *fmt = ngettext( "%s: merged %zu hole into slot (%.4f,%.4f)-(%.4f,%.4f)\n", "%s: merged %zu holes into slot (%.4f,%.4f)-(%.4f,%.4f)\n", CVectorSize(group)); fprintf(stderr, fmt, progname, CVectorSize(group), hole->start_x, hole->start_y, hole->stop_x, hole->stop_y); } } else { /* The only hole we found was our original one, restore * initial state. */ CVectorGetElement(group, &hole, 0); assert(hole->aperture < 0); hole->aperture = -hole->aperture; if (verbose > 1) { fprintf(stderr, _("%s: hole at (%.4f,%.4f) not merged\n"), progname, hole->start_x, hole->start_y); } } ret = 0; err: CVectorFree(&tmp); CVectorFree(&group); return ret; } static int slotify(gerbv_image_t *drill) { CNearTreeHandle t; CVectorHandle holes; int ret = 0; size_t i; t = build_search_tree(drill); if (!t) { fprintf(stderr, _("%s: failed to build search tree\n"), progname); return -1; } CNearTreeObjects(t, &holes); for (i = 0; i < CVectorSize(holes); i++) { gerbv_net_t *hole; CVectorGetElement(holes, &hole, i); /* Skip holes we've already looked at */ if (hole->aperture < 0) continue; if (hole->aperture_state == GERBV_APERTURE_STATE_ON) continue; if (combine_holes(drill, hole, t) < 0) { ret = -1; break; } } /* Clean out any holes we don't need anymore */ for (i = 0; i < CVectorSize(holes); i++) { gerbv_net_t *hole; CVectorGetElement(holes, &hole, i); if (hole->aperture < 0) gerbv_image_delete_net(hole); } CNearTreeFree(&t); return ret; } int main(int argc, char **argv) { const char *outfile = "/dev/stdout"; gerbv_project_t *gp; gerbv_image_t *drill; int opt, ret = 0; if (argc > 0) progname = argv[0]; init_i18n(); while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) { switch (opt) { case 'o': outfile = optarg; break; case 'v': verbose++; break; case 'V': print_version(); return EXIT_SUCCESS; case 'H': print_help(); return EXIT_SUCCESS; default: print_usage(stderr); return EXIT_FAILURE; } } if (!argv[optind]) { fprintf(stderr, _("%s: error: must specify a filename\n"), progname); print_usage(stderr); return EXIT_FAILURE; } if (optind + 1 < argc) { fprintf(stderr, _("%s: error: excess command-line arguments\n"), progname); print_usage(stderr); return EXIT_FAILURE; } gp = gerbv_create_project(); if (!gp) return EXIT_FAILURE; gerbv_open_layer_from_filename(gp, argv[optind]); if (!gp->file[0]) { ret = EXIT_FAILURE; goto out; } drill = gp->file[0]->image; if (drill->layertype != GERBV_LAYERTYPE_DRILL) { fprintf(stderr, _("%s: %s: error: not a drill file\n"), progname, argv[optind]); ret = EXIT_FAILURE; goto out; } if (slotify(drill) != 0) { ret = EXIT_FAILURE; goto out; } if (!gerbv_export_drill_file_from_image(outfile, drill, NULL)) ret = EXIT_FAILURE; out: gerbv_destroy_project(gp); return 0; }