X-Git-Url: https://git.draconx.ca/gitweb/slotifier.git/blobdiff_plain/0c9d7fe949b77df0c02dace419eea9e097caf69d..HEAD:/src/slotifier.c diff --git a/src/slotifier.c b/src/slotifier.c index 0165931..09e5aee 100644 --- a/src/slotifier.c +++ b/src/slotifier.c @@ -1,6 +1,6 @@ /* * Utility to convert overlapping Excellon drill hits into drill slots. - * Copyright © 2018, 2021 Nick Bowler + * Copyright © 2018, 2021, 2023 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 @@ -19,17 +19,23 @@ #include #include #include -#include #include -#include #include #include +#include + #include #include +#include +#include + #include #include -#include -#include + +#include "options.h" +#include "help.h" +#include "xtra.h" +#include "copysym.h" #if !ENABLE_NLS # undef ENABLE_NLS @@ -38,37 +44,18 @@ #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 unsigned verbose; static void print_version(void) { - const char *copysign = "(C)"; - void *convsign = NULL; + const char *copysign = copyright_symbol(locale_charset()); puts(PACKAGE_STRING); - - if (ENABLE_NLS) { - convsign = str_iconv("\xc2\xa9", "UTF-8", locale_charset()); - if (convsign) - copysign = convsign; - } - - printf("Copyright %s 2021 Nick Bowler.\n", copysign); + printf("Copyright %s 2023 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) @@ -79,9 +66,10 @@ static void print_usage(FILE *f) progname); } -static void print_help(void) +static void print_help(const struct option *lopts) { const struct option *opt; + print_usage(stdout); puts(_("This is \"slotifier\": a tool to convert overlapping drill hits in Excellon\n" @@ -90,63 +78,23 @@ static void print_help(void) 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; - } + struct lopt_help help; - 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'); + if (!lopt_get_help(opt, &help)) 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; - } + help_print_option(opt, help.arg, help.desc, 20); } putchar('\n'); puts(_("For more information, see the slotifier(1) man page.")); putchar('\n'); - printf(_("Report bugs to <%s>."), PACKAGE_BUGREPORT); - putchar('\n'); + /* + * TRANSLATORS: Please add *another line* indicating where users should + * report translation bugs. + */ + printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT); } static void init_i18n(void) @@ -193,15 +141,43 @@ static CNearTreeHandle build_search_tree(gerbv_image_t *drill) return t; } +static gerbv_aperture_type_t tool_type(gerbv_image_t *drill, int aperture) +{ + gerbv_aperture_t *tool = drill->aperture[abs(aperture)]; + + return tool->type; +} + +static double tool_radius(gerbv_image_t *drill, int aperture) +{ + gerbv_aperture_t *tool = drill->aperture[abs(aperture)]; + + /* Half a mil slop to decisively include points on boundary. */ + return tool->parameter[0] / 2.0 + 0.0005; +} + +static int holes_overlap(gerbv_image_t *drill, gerbv_net_t *a, gerbv_net_t *b) +{ + double d = hypot(a->start_x - b->start_x, a->start_y - b->start_y); + + return tool_radius(drill, a->aperture) >= d + || tool_radius(drill, b->aperture) >= d; +} + 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; + int biggest_tool, ret = -1; + double biggest_r; size_t i, j; + /* + * Since we consider holes in order of decreasing size, the initial hole + * considered is by definition the biggest one we will find in a group. + */ + biggest_r = tool_radius(drill, (biggest_tool = hole->aperture)); + if (CVectorCreate(&group, sizeof (gerbv_net_t *), 10)) { fprintf(stderr, _("%s: failed to allocate memory\n"), progname); return -1; @@ -221,19 +197,15 @@ static int combine_holes(gerbv_image_t *drill, gerbv_net_t *hole, hole->aperture = -hole->aperture; for (i = 0; i < CVectorSize(group); i++) { - double xy[2], dia; + double xy[2]; 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); + assert(tool_type(drill, hole->aperture) == GERBV_APTYPE_CIRCLE); + assert(tool_radius(drill, hole->aperture) <= biggest_r); - if (CNearTreeFindInSphere(t, dia, 0, tmp, xy, 1) != 0) { + xy[0] = hole->start_x; xy[1] = hole->start_y; + if (CNearTreeFindInSphere(t, biggest_r, 0, tmp, xy, 1) != 0) { /* We should always should find at least one hole! */ fprintf(stderr, _("%s: fatal error searching holes\n"), progname); @@ -245,13 +217,18 @@ static int combine_holes(gerbv_image_t *drill, gerbv_net_t *hole, * 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. */ + gerbv_net_t *newhole; void *p; + CVectorGetElement(tmp, &p, j); - hole = *(void **)p; + newhole = *(void **)p; + + if (newhole->aperture < 0) + continue; /* already visited */ - if (hole->aperture >= 0) { - CVectorAddElement(group, &hole); - hole->aperture = -hole->aperture; + if (holes_overlap(drill, hole, newhole)) { + CVectorAddElement(group, &newhole); + newhole->aperture = -newhole->aperture; } } } @@ -315,10 +292,38 @@ err: return ret; } +/* + * Order two holes by hole diameter. + */ +static gerbv_image_t *hsc_drill_data; +static int hole_size_cmp(const void *a_, const void *b_) +{ + gerbv_net_t * const *a = a_, * const *b = b_; + gerbv_aperture_t *ta, *tb; + + ta = hsc_drill_data->aperture[abs(a[0]->aperture)]; + assert(ta->type == GERBV_APTYPE_CIRCLE); + + tb = hsc_drill_data->aperture[abs(b[0]->aperture)]; + assert(tb->type == GERBV_APTYPE_CIRCLE); + + if (ta->parameter[0] > tb->parameter[0]) + return -1; + if (ta->parameter[0] < tb->parameter[0]) + return 1; + return 0; +} + +static void sort_holes_by_size(gerbv_image_t *drill, CVectorHandle work) +{ + hsc_drill_data = drill; + qsort(work->array, work->size, work->elementsize, hole_size_cmp); +} + static int slotify(gerbv_image_t *drill) { + CVectorHandle holes, work; CNearTreeHandle t; - CVectorHandle holes; int ret = 0; size_t i; @@ -330,16 +335,33 @@ static int slotify(gerbv_image_t *drill) } CNearTreeObjects(t, &holes); - for (i = 0; i < CVectorSize(holes); i++) { + if (!holes) + goto err_free_tree; + + if (CVectorCreate(&work, sizeof (gerbv_net_t *), CVectorSize(holes))) { + fprintf(stderr, _("%s: failed to allocate memory\n"), progname); + goto err_free_tree; + } + + memcpy(work->array, holes->array, holes->size * holes->elementsize); + work->size = holes->size; + sort_holes_by_size(drill, work); + + for (i = 0; i < CVectorSize(work); i++) { gerbv_net_t *hole; - CVectorGetElement(holes, &hole, i); + CVectorGetElement(work, &hole, i); /* Skip holes we've already looked at */ if (hole->aperture < 0) continue; if (hole->aperture_state == GERBV_APERTURE_STATE_ON) continue; + if (verbose > 1) { + fprintf(stderr, _("%s: checking hole at (%.4f,%.4f) for overlaps\n"), + progname, hole->start_x, hole->start_y); + } + if (combine_holes(drill, hole, t) < 0) { ret = -1; break; @@ -355,39 +377,39 @@ static int slotify(gerbv_image_t *drill) gerbv_image_delete_net(hole); } + CVectorFree(&work); +err_free_tree: CNearTreeFree(&t); return ret; } -int main(int argc, char **argv) +static int do_cmdline(int argc, char **argv, const char **outfile) { - const char *outfile = "/dev/stdout"; - gerbv_project_t *gp; - gerbv_image_t *drill; - int opt, ret = 0; + const char *sopts = SOPT_STRING; + int opt; + + XTRA_PACKED_LOPTS(lopts); if (argc > 0) progname = argv[0]; - init_i18n(); - while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) { switch (opt) { case 'o': - outfile = optarg; + *outfile = optarg; break; case 'v': verbose++; break; case 'V': print_version(); - return EXIT_SUCCESS; + return 1; case 'H': - print_help(); - return EXIT_SUCCESS; + print_help(lopts); + return 1; default: print_usage(stderr); - return EXIT_FAILURE; + return -1; } } @@ -395,14 +417,30 @@ int main(int argc, char **argv) fprintf(stderr, _("%s: error: must specify a filename\n"), progname); print_usage(stderr); - return EXIT_FAILURE; + return -1; } if (optind + 1 < argc) { fprintf(stderr, _("%s: error: excess command-line arguments\n"), progname); print_usage(stderr); - return EXIT_FAILURE; + return -1; + } + + return 0; +} + +int main(int argc, char **argv) +{ + const char *outfile = "/dev/stdout"; + gerbv_project_t *gp; + gerbv_image_t *drill; + int ret = 0; + + init_i18n(); + switch (do_cmdline(argc, argv, &outfile)) { + case -1: return EXIT_FAILURE; + case 1: return EXIT_SUCCESS; } gp = gerbv_create_project(); @@ -432,5 +470,5 @@ int main(int argc, char **argv) ret = EXIT_FAILURE; out: gerbv_destroy_project(gp); - return 0; + return ret; }