1 /*!
2 * \file src/hid/bom/bom.c
3 *
4 * \brief Prints a centroid file in a format which includes data needed
5 * by a pick and place machine.
6 *
7 * Further formatting for a particular factory setup can easily be
8 * generated with awk or perl.
9 * In addition, a bill of materials file is generated which can be used
10 * for checking stock and purchasing needed materials.
11 * returns != zero on error.
12 *
13 * <hr>
14 *
15 * <h1><b>Copyright.</b></h1>\n
16 *
17 * PCB, interactive printed circuit board design
18 *
19 * Copyright (C) 2006 DJ Delorie
20 *
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
25 *
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
30 *
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
34 *
35 * Contact addresses for paper mail and Email:
36 * Thomas Nau, Schlehenweg 15, 88471 Baustetten, Germany
37 * Thomas.Nau@rz.uni-ulm.de
38 *
39 * <hr>
40 */
41
42 #ifdef HAVE_CONFIG_H
43 #include "config.h"
44 #endif
45
46 #include <stdio.h>
47 #include <stdarg.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <time.h>
51
52 #include "global.h"
53 #include "data.h"
54 #include "error.h"
55 #include "misc.h"
56 #include "pcb-printf.h"
57
58 #include "hid.h"
59 #include "hid/common/hidnogui.h"
60 #include "../hidint.h"
61
62 #ifdef HAVE_LIBDMALLOC
63 #include <dmalloc.h>
64 #endif
65
66 static HID_Attribute bom_options[] = {
67 /* %start-doc options "80 BOM Creation"
68 @ftable @code
69 @item --bomfile <string>
70 Name of the BOM output file.
71 Parameter @code{<string>} can include a path.
72 @end ftable
73 %end-doc
74 */
75 {"bomfile", "Name of the BOM output file",
76 HID_String, 0, 0, {0, 0, 0}, 0, 0},
77 #define HA_bomfile 0
78
79 /* %start-doc options "80 BOM Creation"
80 @ftable @code
81 @item --xyfile <string>
82 Name of the XY output file.
83 Parameter @code{<string>} can include a path.
84 @end ftable
85 %end-doc
86 */
87 {"xyfile", "Name of the XY output file",
88 HID_String, 0, 0, {0, 0, 0}, 0, 0},
89 #define HA_xyfile 1
90
91 /* %start-doc options "80 BOM Creation"
92 @ftable @code
93 @item --attrs <string>
94 Name of the attributes input file.
95 Parameter @code{<string>} can include a path.
96 The input file can be any path, the format matches what gschem uses.
97 One attribute per line, and whitespace is ignored. Example:
98 @example
99 device
100 manufacturer
101 manufacturer_part_number
102 vendor
103 vendor_part_number
104 @end example
105 @end ftable
106 %end-doc
107 */
108 {"attrs", "Name of the attributes input file",
109 HID_String, 0, 0, {0, 0, 0}, 0, 0},
110 #define HA_attrs 2
111
112 /* %start-doc options "80 BOM Creation"
113 @ftable @code
114 @item --xy-unit <unit>
115 Unit of XY dimensions. Defaults to mil.
116 Parameter @code{<unit>} can be @samp{km}, @samp{m}, @samp{cm}, @samp{mm},
117 @samp{um}, @samp{nm}, @samp{px}, @samp{in}, @samp{mil}, @samp{dmil},
118 @samp{cmil}, or @samp{inch}.
119 @end ftable
120 %end-doc
121 */
122 {"xy-unit", "XY units",
123 HID_Unit, 0, 0, {-1, 0, 0}, NULL, 0},
124 #define HA_unit 3
125
126 {"xy-in-mm", ATTR_UNDOCUMENTED,
127 HID_Boolean, 0, 0, {0, 0, 0}, 0, 0},
128 #define HA_xymm 4
129 };
130
131 #define NUM_OPTIONS (sizeof(bom_options)/sizeof(bom_options[0]))
132
133 static HID_Attr_Val bom_values[NUM_OPTIONS];
134
135 static const char *bom_filename;
136 static const char *xy_filename;
137 static const Unit *xy_unit;
138
139 static char **attr_list = NULL;
140 static int attr_count = 0;
141 static int attr_max = 0;
142
143 typedef struct _StringList
144 {
145 char *str;
146 struct _StringList *next;
147 } StringList;
148
149 typedef struct _BomList
150 {
151 char *descr;
152 char *value;
153 int num;
154 StringList *refdes;
155 char **attrs;
156 struct _BomList *next;
157 } BomList;
158
159 static HID_Attribute *
bom_get_export_options(int * n)160 bom_get_export_options (int *n)
161 {
162 static char *last_bom_filename = 0;
163 static char *last_xy_filename = 0;
164 static int last_unit_value = -1;
165
166 if (bom_options[HA_unit].default_val.int_value == last_unit_value)
167 {
168 if (Settings.grid_unit)
169 bom_options[HA_unit].default_val.int_value = Settings.grid_unit->index;
170 else
171 bom_options[HA_unit].default_val.int_value = get_unit_struct ("mil")->index;
172 last_unit_value = bom_options[HA_unit].default_val.int_value;
173 }
174 if (PCB) {
175 derive_default_filename(PCB->Filename, &bom_options[HA_bomfile], ".bom", &last_bom_filename);
176 derive_default_filename(PCB->Filename, &bom_options[HA_xyfile ], ".xy" , &last_xy_filename );
177 }
178
179 if (!bom_options[HA_attrs].default_val.str_value)
180 bom_options[HA_attrs].default_val.str_value = strdup("attribs");
181
182 if (n)
183 *n = NUM_OPTIONS;
184 return bom_options;
185 }
186
187 static char *
CleanBOMString(char * in)188 CleanBOMString (char *in)
189 {
190 char *out;
191 int i;
192
193 if ((out = (char *)malloc ((strlen (in) + 1) * sizeof (char))) == NULL)
194 {
195 fprintf (stderr, "Error: CleanBOMString() malloc() failed\n");
196 exit (1);
197 }
198
199 /*
200 * copy over in to out with some character conversions.
201 * Go all the way to then end to get the terminating \0
202 */
203 for (i = 0; i <= strlen (in); i++)
204 {
205 switch (in[i])
206 {
207 case '"':
208 out[i] = '\'';
209 break;
210 default:
211 out[i] = in[i];
212 }
213 }
214
215 return out;
216 }
217
218
219 static double
xyToAngle(double x,double y,bool morethan2pins)220 xyToAngle (double x, double y, bool morethan2pins)
221 {
222 double d = atan2 (-y, x) * 180.0 / M_PI;
223
224 /* IPC 7351 defines different rules for 2 pin elements */
225 if (morethan2pins)
226 {
227 /* Multi pin case:
228 * Output 0 degrees if pin1 in is top left or top, i.e. between angles of
229 * 80 to 170 degrees.
230 * Pin #1 can be at dead top (e.g. certain PLCCs) or anywhere in the top
231 * left.
232 */
233 if (d < -100)
234 return 90; /* -180 to -100 */
235 else if (d < -10)
236 return 180; /* -100 to -10 */
237 else if (d < 80)
238 return 270; /* -10 to 80 */
239 else if (d < 170)
240 return 0; /* 80 to 170 */
241 else
242 return 90; /* 170 to 180 */
243 }
244 else
245 {
246 /* 2 pin element:
247 * Output 0 degrees if pin #1 is in top left or left, i.e. in sector
248 * between angles of 95 and 185 degrees.
249 */
250 if (d < -175)
251 return 0; /* -180 to -175 */
252 else if (d < -85)
253 return 90; /* -175 to -85 */
254 else if (d < 5)
255 return 180; /* -85 to 5 */
256 else if (d < 95)
257 return 270; /* 5 to 95 */
258 else
259 return 0; /* 95 to 180 */
260 }
261 }
262
263 static StringList *
string_insert(char * str,StringList * list)264 string_insert (char *str, StringList * list)
265 {
266 StringList *newlist, *cur;
267
268 if ((newlist = (StringList *) malloc (sizeof (StringList))) == NULL)
269 {
270 fprintf (stderr, "malloc() failed in string_insert()\n");
271 exit (1);
272 }
273
274 newlist->next = NULL;
275 newlist->str = strdup (str);
276
277 if (list == NULL)
278 return (newlist);
279
280 cur = list;
281 while (cur->next != NULL)
282 cur = cur->next;
283
284 cur->next = newlist;
285
286 return (list);
287 }
288
289 static BomList *
bom_insert(char * refdes,char * descr,char * value,ElementType * e,BomList * bom)290 bom_insert (char *refdes, char *descr, char *value, ElementType *e, BomList * bom)
291 {
292 BomList *newlist = NULL, *cur = NULL, *prev = NULL;
293 int i;
294 char *val;
295
296
297 if (bom != NULL)
298 {
299 /* search and see if we already have used one of these
300 components */
301 cur = bom;
302 while (cur != NULL)
303 {
304 int attr_mismatch = 0;
305
306 for (i=0; i<attr_count; i++)
307 {
308 val = AttributeGet (e, attr_list[i]);
309 val = val ? val : "";
310 if (strcmp (val, cur->attrs[i]) != 0)
311 attr_mismatch = 1;
312 }
313
314 if ((NSTRCMP (descr, cur->descr) == 0) &&
315 (NSTRCMP (value, cur->value) == 0) &&
316 ! attr_mismatch)
317 {
318 cur->num++;
319 cur->refdes = string_insert (refdes, cur->refdes);
320 return (bom);
321 }
322 prev = cur;
323 cur = cur->next;
324 }
325 }
326
327 if ((newlist = (BomList *) malloc (sizeof (BomList))) == NULL)
328 {
329 fprintf (stderr, "malloc() failed in bom_insert()\n");
330 exit (1);
331 }
332
333 if (prev)
334 prev->next = newlist;
335
336 newlist->next = NULL;
337 newlist->descr = strdup (descr);
338 newlist->value = strdup (value);
339 newlist->num = 1;
340 newlist->refdes = string_insert (refdes, NULL);
341
342 if ((newlist->attrs = (char **) malloc (attr_count * sizeof (char *))) == NULL)
343 {
344 fprintf (stderr, "malloc() failed in bom_insert()\n");
345 exit (1);
346 }
347
348 for (i=0; i<attr_count; i++)
349 {
350 val = AttributeGet (e, attr_list[i]);
351 newlist->attrs[i] = val ? val : "";
352 }
353
354 if (bom == NULL)
355 bom = newlist;
356
357 return (bom);
358
359 }
360
361 /*!
362 * \brief If \c fp is not NULL then print out the bill of materials
363 * contained in \c bom.
364 * Either way, free all memory which has been allocated for bom.
365 */
366 static void
print_and_free(FILE * fp,BomList * bom)367 print_and_free (FILE *fp, BomList *bom)
368 {
369 BomList *lastb;
370 StringList *lasts;
371 char *descr, *value;
372
373 while (bom != NULL)
374 {
375 if (fp)
376 {
377 descr = CleanBOMString (bom->descr);
378 value = CleanBOMString (bom->value);
379 fprintf (fp, "%d,\"%s\",\"%s\",", bom->num, descr, value);
380 free (descr);
381 free (value);
382 }
383
384 while (bom->refdes != NULL)
385 {
386 if (fp)
387 {
388 fprintf (fp, "%s ", bom->refdes->str);
389 }
390 free (bom->refdes->str);
391 lasts = bom->refdes;
392 bom->refdes = bom->refdes->next;
393 free (lasts);
394 }
395 if (fp)
396 {
397 int i;
398 for (i=0; i<attr_count; i++)
399 fprintf (fp, ",\"%s\"", bom->attrs[i]);
400 fprintf (fp, "\n");
401 }
402 free (bom->attrs);
403 lastb = bom;
404 bom = bom->next;
405 free (lastb);
406 }
407 }
408
409 static void
fetch_attr_list()410 fetch_attr_list ()
411 {
412 int i;
413 FILE *f;
414 char buf[1024];
415 char *fname;
416
417 if (attr_list != NULL)
418 {
419 for (i=0; i<attr_count; i++)
420 free (attr_list[i]);
421 attr_count = 0;
422 }
423
424 fname = (char *)bom_options[HA_attrs].value;
425 if (!fname)
426 fname = (char *)bom_options[HA_attrs].default_val.str_value;
427
428 printf("reading attrs from %s\n", fname);
429 f = fopen (fname, "r");
430 if (f == NULL)
431 return;
432
433 while (fgets (buf, 1023, f) != NULL)
434 {
435 char *c = buf + strlen (buf) - 1;
436 while (c >= buf && isspace (*c))
437 *c-- = 0;
438 c = buf;
439 while (*c && isspace (*c))
440 c++;
441
442 if (*c)
443 {
444 if (attr_count == attr_max)
445 {
446 attr_max += 10;
447 attr_list = (char **) realloc (attr_list, attr_max * sizeof (char *));
448 }
449 attr_list[attr_count++] = strdup (c);
450 }
451 }
452 fclose (f);
453 }
454
455 /*!
456 * Maximum length of following list.
457 */
458 #define MAXREFPINS 32
459
460 /*!
461 * \brief Includes numbered and BGA pins.
462 *
463 * In order of preference.
464 * Possibly BGA pins can be missing, so we add a few to try.
465 */
466 static char *reference_pin_names[] = {"1", "2", "A1", "A2", "B1", "B2", 0};
467
468 static int
PrintBOM(void)469 PrintBOM (void)
470 {
471 char utcTime[64];
472 Coord x, y;
473 double theta = 0.0;
474 double sumx, sumy;
475 int pinfound[MAXREFPINS];
476 double pinx[MAXREFPINS];
477 double piny[MAXREFPINS];
478 double pinangle[MAXREFPINS];
479 double padcentrex, padcentrey;
480 double centroidx, centroidy;
481 double pin1x, pin1y;
482 int pin_cnt;
483 int found_any_not_at_centroid;
484 int found_any;
485 time_t currenttime;
486 FILE *fp;
487 BomList *bom = NULL;
488 char *name, *descr, *value,*fixed_rotation;
489 int rpindex;
490 int i;
491 char fmt[256];
492
493 sprintf(fmt, "%%s,\"%%s\",\"%%s\",%%.2`m%c,%%.2`m%c,%%g,%%s\n",
494 xy_unit->printf_code, xy_unit->printf_code);
495
496 fp = fopen (xy_filename, "wb");
497 if (!fp)
498 {
499 gui->log ("Cannot open file %s for writing\n", xy_filename);
500 return 1;
501 }
502
503 fetch_attr_list ();
504
505 /* Create a portable timestamp. */
506 currenttime = time (NULL);
507 {
508 /* avoid gcc complaints */
509 const char *fmt = "%c UTC";
510 strftime (utcTime, sizeof (utcTime), fmt, gmtime (¤ttime));
511 }
512 fprintf (fp, "# PcbXY Version 1.0\n");
513 fprintf (fp, "# Date: %s\n", utcTime);
514 fprintf (fp, "# Author: %s\n", pcb_author ());
515 fprintf (fp, "# Title: %s - PCB X-Y\n", UNKNOWN (PCB->Name));
516 fprintf (fp, "# RefDes, Description, Value, X, Y, rotation, top/bottom\n");
517 /* don't use localized xy_unit->in_suffix here since */
518 /* the line itself is not localized and not for GUI */
519 fprintf (fp, "# X,Y in %s. rotation in degrees.\n", xy_unit->suffix);
520 fprintf (fp, "# --------------------------------------------\n");
521
522 /*
523 * For each element we calculate the centroid of the footprint.
524 * In addition, we need to extract some notion of rotation.
525 * While here generate the BOM list
526 */
527
528 ELEMENT_LOOP (PCB->Data);
529 {
530
531 /* Initialize our pin count and our totals for finding the centroid. */
532 pin_cnt = 0;
533 sumx = 0.0;
534 sumy = 0.0;
535 for (rpindex = 0; rpindex < MAXREFPINS; rpindex++)
536 pinfound[rpindex] = 0;
537
538 /* Insert this component into the bill of materials list. */
539 bom = bom_insert ((char *)UNKNOWN (NAMEONPCB_NAME (element)),
540 (char *)UNKNOWN (DESCRIPTION_NAME (element)),
541 (char *)UNKNOWN (VALUE_NAME (element)),
542 element,
543 bom);
544
545
546 /*
547 * Iterate over the pins and pads keeping a running count of how
548 * many pins/pads total and the sum of x and y coordinates
549 *
550 * While we're at it, store the location of pin/pad #1 and #2 if
551 * we can find them.
552 */
553
554 PIN_LOOP (element);
555 {
556 sumx += (double) pin->X;
557 sumy += (double) pin->Y;
558 pin_cnt++;
559
560 for (rpindex = 0; reference_pin_names[rpindex]; rpindex++)
561 {
562 if (NSTRCMP (pin->Number, reference_pin_names[rpindex]) == 0)
563 {
564 pinx[rpindex] = (double) pin->X;
565 piny[rpindex] = (double) pin->Y;
566 pinangle[rpindex] = 0.0; /* pins have no notion of angle */
567 pinfound[rpindex] = 1;
568 }
569 }
570 }
571 END_LOOP;
572
573 PAD_LOOP (element);
574 {
575 sumx += (pad->Point1.X + pad->Point2.X) / 2.0;
576 sumy += (pad->Point1.Y + pad->Point2.Y) / 2.0;
577 pin_cnt++;
578
579 for (rpindex = 0; reference_pin_names[rpindex]; rpindex++)
580 {
581 if (NSTRCMP (pad->Number, reference_pin_names[rpindex]) == 0)
582 {
583 padcentrex = (double) (pad->Point1.X + pad->Point2.X) / 2.0;
584 padcentrey = (double) (pad->Point1.Y + pad->Point2.Y) / 2.0;
585 pinx[rpindex] = padcentrex;
586 piny[rpindex] = padcentrey;
587 /*
588 * NOTE: We swap the Y points because in PCB, the Y-axis
589 * is inverted. Increasing Y moves down. We want to deal
590 * in the usual increasing Y moves up coordinates though.
591 */
592 pinangle[rpindex] = (180.0 / M_PI) * atan2 (pad->Point1.Y - pad->Point2.Y,
593 pad->Point2.X - pad->Point1.X);
594 pinfound[rpindex]=1;
595 }
596 }
597 }
598 END_LOOP;
599
600 if (pin_cnt > 0)
601 {
602 centroidx = sumx / (double) pin_cnt;
603 centroidy = sumy / (double) pin_cnt;
604
605 if (NSTRCMP( AttributeGetFromList (&element->Attributes,"xy-centre"), "origin") == 0 )
606 {
607 x = element->MarkX;
608 y = element->MarkY;
609 }
610 else
611 {
612 x = centroidx;
613 y = centroidy;
614 }
615
616 fixed_rotation = AttributeGetFromList (&element->Attributes, "xy-fixed-rotation");
617 if (fixed_rotation)
618 {
619 /* The user specified a fixed rotation */
620 theta = atof (fixed_rotation);
621 found_any_not_at_centroid = 1;
622 found_any = 1;
623 }
624 else
625 {
626 /* Find first reference pin not at the centroid */
627 found_any_not_at_centroid = 0;
628 found_any = 0;
629 theta = 0.0;
630 for (rpindex = 0;
631 reference_pin_names[rpindex] && !found_any_not_at_centroid;
632 rpindex++)
633 {
634 if (pinfound[rpindex])
635 {
636 found_any = 1;
637
638 /* Recenter pin "#1" onto the axis which cross at the part
639 centroid */
640 pin1x = pinx[rpindex] - x;
641 pin1y = piny[rpindex] - y;
642
643 /* flip x, to reverse rotation for elements on back */
644 if (FRONT (element) != 1)
645 pin1x = -pin1x;
646
647 /* if only 1 pin, use pin 1's angle */
648 if (pin_cnt == 1)
649 {
650 theta = pinangle[rpindex];
651 found_any_not_at_centroid = 1;
652 }
653 else if ((pin1x != 0.0) || (pin1y != 0.0))
654 {
655 theta = xyToAngle (pin1x, pin1y, pin_cnt > 2);
656 found_any_not_at_centroid = 1;
657 }
658 }
659 }
660
661 if (!found_any)
662 {
663 Message
664 ("PrintBOM(): unable to figure out angle because I could\n"
665 " not find a suitable reference pin of element %s\n"
666 " Setting to %g degrees\n",
667 UNKNOWN (NAMEONPCB_NAME (element)), theta);
668 }
669 else if (!found_any_not_at_centroid)
670 {
671 Message
672 ("PrintBOM(): unable to figure out angle of element\n"
673 " %s because the reference pin(s) are at the centroid of the part.\n"
674 " Setting to %g degrees\n",
675 UNKNOWN (NAMEONPCB_NAME (element)), theta);
676 }
677 }
678 name = CleanBOMString ((char *)UNKNOWN (NAMEONPCB_NAME (element)));
679 descr = CleanBOMString ((char *)UNKNOWN (DESCRIPTION_NAME (element)));
680 value = CleanBOMString ((char *)UNKNOWN (VALUE_NAME (element)));
681
682 y = PCB->MaxHeight - y;
683 //pcb_fprintf (fp, "%m+%s,\"%s\",\"%s\",%.2`mS,%.2`mS,%g,%s\n",
684 pcb_fprintf (fp, fmt, name, descr, value, x, y, theta,
685 FRONT (element) == 1 ? "top" : "bottom");
686 free (name);
687 free (descr);
688 free (value);
689 }
690 }
691 END_LOOP;
692
693 fclose (fp);
694
695 /* Now print out a Bill of Materials file */
696
697 fp = fopen (bom_filename, "wb");
698 if (!fp)
699 {
700 gui->log ("Cannot open file %s for writing\n", bom_filename);
701 print_and_free (NULL, bom);
702 return 1;
703 }
704
705 fprintf (fp, "# PcbBOM Version 1.0\n");
706 fprintf (fp, "# Date: %s\n", utcTime);
707 fprintf (fp, "# Author: %s\n", pcb_author ());
708 fprintf (fp, "# Title: %s - PCB BOM\n", UNKNOWN (PCB->Name));
709 fprintf (fp, "# Quantity, Description, Value, RefDes");
710 for (i=0; i<attr_count; i++)
711 fprintf (fp, ", %s", attr_list[i]);
712 fprintf (fp, "\n");
713 fprintf (fp, "# --------------------------------------------\n");
714
715 print_and_free (fp, bom);
716
717 fclose (fp);
718
719 return (0);
720 }
721
722 static void
bom_do_export(HID_Attr_Val * options)723 bom_do_export (HID_Attr_Val * options)
724 {
725 int i;
726
727 if (!options)
728 {
729 bom_get_export_options (0);
730 for (i = 0; i < NUM_OPTIONS; i++)
731 bom_values[i] = bom_options[i].default_val;
732 options = bom_values;
733 }
734
735 bom_filename = options[HA_bomfile].str_value;
736 if (!bom_filename)
737 bom_filename = "pcb-out.bom";
738
739 xy_filename = options[HA_xyfile].str_value;
740 if (!xy_filename)
741 xy_filename = "pcb-out.xy";
742
743 if (options[HA_xymm].int_value)
744 xy_unit = get_unit_struct ("mm");
745 else
746 /* options[HA_unit].int_value gets set to the index of the passed unit
747 * in the Units structure (pcb-printf.c) by hid_parse_command_line
748 * (hid/common/hidinit.c), so, this command gets a pointer to the desired
749 * unit structure from that list.
750 * */
751 xy_unit = &(get_unit_list ()[options[HA_unit].int_value]);
752 PrintBOM ();
753 }
754
755 static void
bom_parse_arguments(int * argc,char *** argv)756 bom_parse_arguments (int *argc, char ***argv)
757 {
758 hid_register_attributes (bom_options,
759 sizeof (bom_options) / sizeof (bom_options[0]));
760 hid_parse_command_line (argc, argv);
761 }
762
763 HID bom_hid;
764
765 void
hid_bom_init()766 hid_bom_init ()
767 {
768 memset (&bom_hid, 0, sizeof (HID));
769
770 common_nogui_init (&bom_hid);
771
772 bom_hid.struct_size = sizeof (HID);
773 bom_hid.name = "bom";
774 bom_hid.description = "Exports a Bill of Materials";
775 bom_hid.exporter = 1;
776
777 bom_hid.get_export_options = bom_get_export_options;
778 bom_hid.do_export = bom_do_export;
779 bom_hid.parse_arguments = bom_parse_arguments;
780
781 hid_register_hid (&bom_hid);
782 }
783