/*
* 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)
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"
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)
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;
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);
* 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;
}
}
}
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;
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;
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;
}
}
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();
ret = EXIT_FAILURE;
out:
gerbv_destroy_project(gp);
- return 0;
+ return ret;
}