]> git.draconx.ca Git - slotifier.git/blobdiff - src/slotifier.c
Trivial manual fixes.
[slotifier.git] / src / slotifier.c
index 03485f5b88d37d76749560c181fca7c8faad2bbd..09e5aeecce281f894bca42b8c87630c37eb8c6ff 100644 (file)
@@ -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
 #include <config.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <stddef.h>
 #include <string.h>
-#include <locale.h>
 #include <stdint.h>
 #include <assert.h>
+#include <locale.h>
+
 #include <getopt.h>
 #include <gettext.h>
+#include <localcharset.h>
+#include <mbswidth.h>
+
 #include <CNearTree.h>
 #include <gerbv.h>
-#include <striconv.h>
-#include <localcharset.h>
+
+#include "options.h"
+#include "help.h"
+#include "xtra.h"
+#include "copysym.h"
 
 #if !ENABLE_NLS
 #  undef ENABLE_NLS
 
 #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,22 +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, r;
+               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];
 
-               /* Half a mil slop to decisively include points on boundary. */
-               r = dia/2 + 0.0005;
+               assert(tool_type(drill, hole->aperture) == GERBV_APTYPE_CIRCLE);
+               assert(tool_radius(drill, hole->aperture) <= biggest_r);
 
-               if (drill->aperture[biggest_tool]->parameter[0] < dia)
-                       biggest_tool = abs(hole->aperture);
-
-               if (CNearTreeFindInSphere(t, r, 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);
@@ -248,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;
                        }
                }
        }
@@ -318,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;
 
@@ -334,18 +336,32 @@ static int slotify(gerbv_image_t *drill)
 
        CNearTreeObjects(t, &holes);
        if (!holes)
-               goto out;
+               goto err_free_tree;
 
-       for (i = 0; i < CVectorSize(holes); i++) {
+       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;
@@ -361,40 +377,39 @@ static int slotify(gerbv_image_t *drill)
                        gerbv_image_delete_net(hole);
        }
 
-out:
+       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;
                }
        }
 
@@ -402,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();
@@ -439,5 +470,5 @@ int main(int argc, char **argv)
                ret = EXIT_FAILURE;
 out:
        gerbv_destroy_project(gp);
-       return 0;
+       return ret;
 }