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 (&currenttime));
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