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/>.
29 #include <CNearTree.h>
32 #include <localcharset.h>
39 #define _(x) (gettext(x))
41 static unsigned verbose;
43 static const char *progname = "slotifier";
44 static const char sopts[] = "o:vVH";
45 static const struct option lopts[] = {
46 { "output", 1, NULL, 'o' },
47 { "verbose", 0, NULL, 'v' },
48 { "version", 0, NULL, 'V' },
49 { "help", 0, NULL, 'H' },
53 static void print_version(void)
55 const char *copysign = "(C)";
56 void *convsign = NULL;
61 convsign = str_iconv("\xc2\xa9", "UTF-8", locale_charset());
66 printf("Copyright %s 2021 Nick Bowler.\n", copysign);
67 puts("License GPLv3+: GNU GPL version 3 or any later version.");
68 puts("This is free software: you are free to change and redistribute it.");
69 puts("There is NO WARRANTY, to the extent permitted by law.");
74 static void print_usage(FILE *f)
76 fprintf(f, _("Usage: %s [options] [-o filename] filename\n"), progname);
78 fprintf(f, _("Try %s --help for more information.\n"),
82 static void print_help(void)
84 const struct option *opt;
87 puts(_("This is \"slotifier\": a tool to convert overlapping drill hits in Excellon\n"
88 "drill files to G85 drill slots."));
92 for (opt = lopts; opt->val; opt++) {
93 const char *line, *text = "ARG";
98 case 'o': text = _("FILE");
101 w += printf(_(" -%c, --%s=%s"),
102 opt->val, opt->name, text);
104 w += printf(_(" -%c, --%s"), opt->val, opt->name);
114 text = _("Output to FILE, instead of standard output.");
117 text = _("Increase verbosity (can be specified more than once).");
120 text = _("Print a version message and then exit.");
123 text = _("Print this message and then exit.");
131 for (line = text; line[0];) {
132 const char *nl = strchr(line, '\n');
135 printf("%*s%s\n", 20-w, "", line);
139 printf("%*s%.*s\n", 20-w, "", (int)(nl-line), line);
145 puts(_("For more information, see the slotifier(1) man page."));
148 printf(_("Report bugs to <%s>."), PACKAGE_BUGREPORT);
152 static void init_i18n(void)
154 setlocale(LC_ALL, "");
155 bindtextdomain(PACKAGE, LOCALEDIR);
159 static CNearTreeHandle build_search_tree(gerbv_image_t *drill)
164 if (CNearTreeCreate(&t, 2, CNEARTREE_TYPE_DOUBLE|CNEARTREE_NORM_L2))
167 /* Build a search tree from all the holes. */
168 for (x = drill->netlist; x; x = x->next) {
169 double xy[2] = { x->start_x, x->start_y };
171 assert(x->aperture >= 0 && x->aperture < APERTURE_MAX);
173 /* Skip things that aren't drill hits */
174 if (!drill->aperture[x->aperture])
177 /* Holes are marked as "flashing"; otherwise it's an existing
179 if (x->aperture_state != GERBV_APERTURE_STATE_FLASH)
182 if (CNearTreeInsert(t, xy, x)) {
188 if (CNearTreeCompleteDelayedInsert(t)) {
196 static int combine_holes(gerbv_image_t *drill, gerbv_net_t *hole,
199 int biggest_tool = hole->aperture;
200 CVectorHandle group, tmp;
201 gerbv_aperture_t *tool;
205 if (CVectorCreate(&group, sizeof (gerbv_net_t *), 10)) {
206 fprintf(stderr, _("%s: failed to allocate memory\n"), progname);
210 if (CVectorCreate(&tmp, sizeof (void *), 10)) {
211 fprintf(stderr, _("%s: failed to allocate memory\n"), progname);
217 * Breadth-first nearest neighbour search of holes. We negate the
218 * aperture to indicate which holes have been previously visited.
220 CVectorAddElement(group, &hole);
221 hole->aperture = -hole->aperture;
223 for (i = 0; i < CVectorSize(group); i++) {
226 CVectorGetElement(group, &hole, i);
227 tool = drill->aperture[abs(hole->aperture)];
228 assert(tool->type == GERBV_APTYPE_CIRCLE);
230 xy[0] = hole->start_x; xy[1] = hole->start_y;
231 dia = tool->parameter[0];
233 if (drill->aperture[biggest_tool]->parameter[0] < dia)
234 biggest_tool = abs(hole->aperture);
236 if (CNearTreeFindInSphere(t, dia, 0, tmp, xy, 1) != 0) {
237 /* We should always should find at least one hole! */
238 fprintf(stderr, _("%s: fatal error searching holes\n"),
243 for (j = 0; j < CVectorSize(tmp); j++) {
244 /* I don't know why, but CNearTree returns a list
245 * of pointers to its internal copies of pointers
246 * to the objects in the tree. So we need this
247 * double indirection to get the actual hole. */
249 CVectorGetElement(tmp, &p, j);
252 if (hole->aperture >= 0) {
253 CVectorAddElement(group, &hole);
254 hole->aperture = -hole->aperture;
259 /* Compare each pair of matched points to find the longest slot. */
260 if (CVectorSize(group) > 1) {
261 int biggest_slot = 0;
264 for (i = 0; i < CVectorSize(group) - 1; i++) {
265 CVectorGetElement(group, &hole, i);
266 for (j = i+1; j < CVectorSize(group); j++) {
270 CVectorGetElement(group, &h2, j);
271 newlen = hypot(hole->start_x - h2->start_x,
272 hole->start_y - h2->start_y);
274 if (newlen > bestlen) {
275 hole->stop_x = h2->start_x;
276 hole->stop_y = h2->stop_y;
283 CVectorGetElement(group, &hole, biggest_slot);
284 hole->aperture = biggest_tool;
285 hole->aperture_state = GERBV_APERTURE_STATE_ON;
288 const char *fmt = ngettext(
289 "%s: merged %zu hole into slot (%.4f,%.4f)-(%.4f,%.4f)\n",
290 "%s: merged %zu holes into slot (%.4f,%.4f)-(%.4f,%.4f)\n",
293 fprintf(stderr, fmt, progname, CVectorSize(group),
294 hole->start_x, hole->start_y,
295 hole->stop_x, hole->stop_y);
298 /* The only hole we found was our original one, restore
300 CVectorGetElement(group, &hole, 0);
302 assert(hole->aperture < 0);
303 hole->aperture = -hole->aperture;
306 fprintf(stderr, _("%s: hole at (%.4f,%.4f) not merged\n"),
307 progname, hole->start_x, hole->start_y);
318 static int slotify(gerbv_image_t *drill)
325 t = build_search_tree(drill);
327 fprintf(stderr, _("%s: failed to build search tree\n"),
332 CNearTreeObjects(t, &holes);
333 for (i = 0; i < CVectorSize(holes); i++) {
336 CVectorGetElement(holes, &hole, i);
337 /* Skip holes we've already looked at */
338 if (hole->aperture < 0)
340 if (hole->aperture_state == GERBV_APERTURE_STATE_ON)
343 if (combine_holes(drill, hole, t) < 0) {
349 /* Clean out any holes we don't need anymore */
350 for (i = 0; i < CVectorSize(holes); i++) {
353 CVectorGetElement(holes, &hole, i);
354 if (hole->aperture < 0)
355 gerbv_image_delete_net(hole);
362 int main(int argc, char **argv)
364 const char *outfile = "/dev/stdout";
366 gerbv_image_t *drill;
374 while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
395 fprintf(stderr, _("%s: error: must specify a filename\n"),
401 if (optind + 1 < argc) {
402 fprintf(stderr, _("%s: error: excess command-line arguments\n"),
408 gp = gerbv_create_project();
412 gerbv_open_layer_from_filename(gp, argv[optind]);
418 drill = gp->file[0]->image;
419 if (drill->layertype != GERBV_LAYERTYPE_DRILL) {
420 fprintf(stderr, _("%s: %s: error: not a drill file\n"),
421 progname, argv[optind]);
426 if (slotify(drill) != 0) {
431 if (!gerbv_export_drill_file_from_image(outfile, drill, NULL))
434 gerbv_destroy_project(gp);