pcb 4.1.1
An interactive printed circuit board layout editor.

bom.c

Go to the documentation of this file.
00001 
00042 #ifdef HAVE_CONFIG_H
00043 #include "config.h"
00044 #endif
00045 
00046 #include <stdio.h>
00047 #include <stdarg.h>
00048 #include <stdlib.h>
00049 #include <string.h>
00050 #include <time.h>
00051 
00052 #include "global.h"
00053 #include "data.h"
00054 #include "error.h"
00055 #include "misc.h"
00056 #include "pcb-printf.h"
00057 
00058 #include "hid.h"
00059 #include "hid/common/hidnogui.h"
00060 #include "../hidint.h"
00061 
00062 #ifdef HAVE_LIBDMALLOC
00063 #include <dmalloc.h>
00064 #endif
00065 
00066 static HID_Attribute bom_options[] = {
00067 /* %start-doc options "80 BOM Creation"
00068 @ftable @code
00069 @item --bomfile <string>
00070 Name of the BOM output file.
00071 Parameter @code{<string>} can include a path.
00072 @end ftable
00073 %end-doc
00074 */
00075   {"bomfile", "Name of the BOM output file",
00076    HID_String, 0, 0, {0, 0, 0}, 0, 0},
00077 #define HA_bomfile 0
00078 /* %start-doc options "80 BOM Creation"
00079 @ftable @code
00080 @item --xyfile <string>
00081 Name of the XY output file.
00082 Parameter @code{<string>} can include a path.
00083 @end ftable
00084 %end-doc
00085 */
00086   {"xyfile", "Name of the XY output file",
00087    HID_String, 0, 0, {0, 0, 0}, 0, 0},
00088 #define HA_xyfile 1
00089 
00090 /* %start-doc options "80 BOM Creation"
00091 @ftable @code
00092 @item --xy-unit <unit>
00093 Unit of XY dimensions. Defaults to mil.
00094 Parameter @code{<unit>} can be @samp{km}, @samp{m}, @samp{cm}, @samp{mm},
00095 @samp{um}, @samp{nm}, @samp{px}, @samp{in}, @samp{mil}, @samp{dmil},
00096 @samp{cmil}, or @samp{inch}.
00097 @end ftable
00098 %end-doc
00099 */
00100   {"xy-unit", "XY units",
00101    HID_Unit, 0, 0, {-1, 0, 0}, NULL, 0},
00102 #define HA_unit 2
00103   {"xy-in-mm", ATTR_UNDOCUMENTED,
00104    HID_Boolean, 0, 0, {0, 0, 0}, 0, 0},
00105 #define HA_xymm 3
00106 };
00107 
00108 #define NUM_OPTIONS (sizeof(bom_options)/sizeof(bom_options[0]))
00109 
00110 static HID_Attr_Val bom_values[NUM_OPTIONS];
00111 
00112 static const char *bom_filename;
00113 static const char *xy_filename;
00114 static const Unit *xy_unit;
00115 
00116 typedef struct _StringList
00117 {
00118   char *str;
00119   struct _StringList *next;
00120 } StringList;
00121 
00122 typedef struct _BomList
00123 {
00124   char *descr;
00125   char *value;
00126   int num;
00127   StringList *refdes;
00128   struct _BomList *next;
00129 } BomList;
00130 
00131 static HID_Attribute *
00132 bom_get_export_options (int *n)
00133 {
00134   static char *last_bom_filename = 0;
00135   static char *last_xy_filename = 0;
00136   static int last_unit_value = -1;
00137 
00138   if (bom_options[HA_unit].default_val.int_value == last_unit_value)
00139     {
00140       if (Settings.grid_unit)
00141         bom_options[HA_unit].default_val.int_value = Settings.grid_unit->index;
00142       else
00143         bom_options[HA_unit].default_val.int_value = get_unit_struct ("mil")->index;
00144       last_unit_value = bom_options[HA_unit].default_val.int_value;
00145     }
00146   if (PCB) {
00147     derive_default_filename(PCB->Filename, &bom_options[HA_bomfile], ".bom", &last_bom_filename);
00148     derive_default_filename(PCB->Filename, &bom_options[HA_xyfile ], ".xy" , &last_xy_filename );
00149   }
00150 
00151   if (n)
00152     *n = NUM_OPTIONS;
00153   return bom_options;
00154 }
00155 
00156 static char *
00157 CleanBOMString (char *in)
00158 {
00159   char *out;
00160   int i;
00161 
00162   if ((out = (char *)malloc ((strlen (in) + 1) * sizeof (char))) == NULL)
00163     {
00164       fprintf (stderr, "Error:  CleanBOMString() malloc() failed\n");
00165       exit (1);
00166     }
00167 
00168   /* 
00169    * copy over in to out with some character conversions.
00170    * Go all the way to then end to get the terminating \0
00171    */
00172   for (i = 0; i <= strlen (in); i++)
00173     {
00174       switch (in[i])
00175         {
00176         case '"':
00177           out[i] = '\'';
00178           break;
00179         default:
00180           out[i] = in[i];
00181         }
00182     }
00183 
00184   return out;
00185 }
00186 
00187 
00188 static double
00189 xyToAngle (double x, double y, bool morethan2pins)
00190 {
00191   double d = atan2 (-y, x) * 180.0 / M_PI;
00192 
00193   /* IPC 7351 defines different rules for 2 pin elements */
00194   if (morethan2pins)
00195     {
00196       /* Multi pin case:
00197        * Output 0 degrees if pin1 in is top left or top, i.e. between angles of
00198        * 80 to 170 degrees.
00199        * Pin #1 can be at dead top (e.g. certain PLCCs) or anywhere in the top
00200        * left.
00201        */           
00202       if (d < -100)
00203         return 90; /* -180 to -100 */
00204       else if (d < -10)
00205         return 180; /* -100 to -10 */
00206       else if (d < 80)
00207         return 270; /* -10 to 80 */
00208       else if (d < 170)
00209         return 0; /* 80 to 170 */
00210       else
00211         return 90; /* 170 to 180 */
00212     }
00213   else
00214     {
00215       /* 2 pin element:
00216        * Output 0 degrees if pin #1 is in top left or left, i.e. in sector
00217        * between angles of 95 and 185 degrees.
00218        */
00219       if (d < -175)
00220         return 0; /* -180 to -175 */
00221       else if (d < -85)
00222         return 90; /* -175 to -85 */
00223       else if (d < 5)
00224         return 180; /* -85 to 5 */
00225       else if (d < 95)
00226         return 270; /* 5 to 95 */
00227       else
00228         return 0; /* 95 to 180 */
00229     }
00230 }
00231 
00232 static StringList *
00233 string_insert (char *str, StringList * list)
00234 {
00235   StringList *newlist, *cur;
00236 
00237   if ((newlist = (StringList *) malloc (sizeof (StringList))) == NULL)
00238     {
00239       fprintf (stderr, "malloc() failed in string_insert()\n");
00240       exit (1);
00241     }
00242 
00243   newlist->next = NULL;
00244   newlist->str = strdup (str);
00245 
00246   if (list == NULL)
00247     return (newlist);
00248 
00249   cur = list;
00250   while (cur->next != NULL)
00251     cur = cur->next;
00252 
00253   cur->next = newlist;
00254 
00255   return (list);
00256 }
00257 
00258 static BomList *
00259 bom_insert (char *refdes, char *descr, char *value, BomList * bom)
00260 {
00261   BomList *newlist, *cur, *prev = NULL;
00262 
00263   if (bom == NULL)
00264     {
00265       /* this is the first element so automatically create an entry */
00266       if ((newlist = (BomList *) malloc (sizeof (BomList))) == NULL)
00267         {
00268           fprintf (stderr, "malloc() failed in bom_insert()\n");
00269           exit (1);
00270         }
00271 
00272       newlist->next = NULL;
00273       newlist->descr = strdup (descr);
00274       newlist->value = strdup (value);
00275       newlist->num = 1;
00276       newlist->refdes = string_insert (refdes, NULL);
00277       return (newlist);
00278     }
00279 
00280   /* search and see if we already have used one of these
00281      components */
00282   cur = bom;
00283   while (cur != NULL)
00284     {
00285       if ((NSTRCMP (descr, cur->descr) == 0) &&
00286           (NSTRCMP (value, cur->value) == 0))
00287         {
00288           cur->num++;
00289           cur->refdes = string_insert (refdes, cur->refdes);
00290           break;
00291         }
00292       prev = cur;
00293       cur = cur->next;
00294     }
00295 
00296   if (cur == NULL)
00297     {
00298       if ((newlist = (BomList *) malloc (sizeof (BomList))) == NULL)
00299         {
00300           fprintf (stderr, "malloc() failed in bom_insert()\n");
00301           exit (1);
00302         }
00303 
00304       prev->next = newlist;
00305 
00306       newlist->next = NULL;
00307       newlist->descr = strdup (descr);
00308       newlist->value = strdup (value);
00309       newlist->num = 1;
00310       newlist->refdes = string_insert (refdes, NULL);
00311     }
00312 
00313   return (bom);
00314 
00315 }
00316 
00322 static void
00323 print_and_free (FILE *fp, BomList *bom)
00324 {
00325   BomList *lastb;
00326   StringList *lasts;
00327   char *descr, *value;
00328 
00329   while (bom != NULL)
00330     {
00331       if (fp)
00332         {
00333           descr = CleanBOMString (bom->descr);
00334           value = CleanBOMString (bom->value);
00335           fprintf (fp, "%d,\"%s\",\"%s\",", bom->num, descr, value);
00336           free (descr);
00337           free (value);
00338         }
00339       
00340       while (bom->refdes != NULL)
00341         {
00342           if (fp)
00343             {
00344               fprintf (fp, "%s ", bom->refdes->str);
00345             }
00346           free (bom->refdes->str);
00347           lasts = bom->refdes;
00348           bom->refdes = bom->refdes->next;
00349           free (lasts);
00350         }
00351       if (fp)
00352         {
00353           fprintf (fp, "\n");
00354         }
00355       lastb = bom;
00356       bom = bom->next;
00357       free (lastb);
00358     }
00359 }
00360 
00364 #define MAXREFPINS 32
00365 
00372 static char *reference_pin_names[] = {"1", "2", "A1", "A2", "B1", "B2", 0};
00373 
00374 static int
00375 PrintBOM (void)
00376 {
00377   char utcTime[64];
00378   Coord x, y;
00379   double theta = 0.0;
00380   double sumx, sumy;
00381   int pinfound[MAXREFPINS];
00382   double pinx[MAXREFPINS];
00383   double piny[MAXREFPINS];
00384   double pinangle[MAXREFPINS];
00385   double padcentrex, padcentrey;
00386   double centroidx, centroidy;
00387   double pin1x, pin1y;
00388   int pin_cnt;
00389   int found_any_not_at_centroid;
00390   int found_any;
00391   time_t currenttime;
00392   FILE *fp;
00393   BomList *bom = NULL;
00394   char *name, *descr, *value,*fixed_rotation;
00395   int rpindex;
00396 
00397   fp = fopen (xy_filename, "w");
00398   if (!fp)
00399     {
00400       gui->log ("Cannot open file %s for writing\n", xy_filename);
00401       return 1;
00402     }
00403 
00404   /* Create a portable timestamp. */
00405   currenttime = time (NULL);
00406   {
00407     /* avoid gcc complaints */
00408     const char *fmt = "%c UTC";
00409     strftime (utcTime, sizeof (utcTime), fmt, gmtime (&currenttime));
00410   }
00411   fprintf (fp, "# PcbXY Version 1.0\n");
00412   fprintf (fp, "# Date: %s\n", utcTime);
00413   fprintf (fp, "# Author: %s\n", pcb_author ());
00414   fprintf (fp, "# Title: %s - PCB X-Y\n", UNKNOWN (PCB->Name));
00415   fprintf (fp, "# RefDes, Description, Value, X, Y, rotation, top/bottom\n");
00416   /* don't use localized xy_unit->in_suffix here since */
00417   /* the line itself is not localized and not for GUI  */
00418   fprintf (fp, "# X,Y in %s.  rotation in degrees.\n", xy_unit->suffix);
00419   fprintf (fp, "# --------------------------------------------\n");
00420 
00421   /*
00422    * For each element we calculate the centroid of the footprint.
00423    * In addition, we need to extract some notion of rotation.
00424    * While here generate the BOM list
00425    */
00426 
00427   ELEMENT_LOOP (PCB->Data);
00428   {
00429 
00430     /* Initialize our pin count and our totals for finding the centroid. */
00431     pin_cnt = 0;
00432     sumx = 0.0;
00433     sumy = 0.0;
00434     for (rpindex = 0; rpindex < MAXREFPINS; rpindex++)
00435       pinfound[rpindex] = 0;
00436 
00437     /* Insert this component into the bill of materials list. */
00438     bom = bom_insert ((char *)UNKNOWN (NAMEONPCB_NAME (element)),
00439                       (char *)UNKNOWN (DESCRIPTION_NAME (element)),
00440                       (char *)UNKNOWN (VALUE_NAME (element)), bom);
00441 
00442 
00443     /*
00444      * Iterate over the pins and pads keeping a running count of how
00445      * many pins/pads total and the sum of x and y coordinates
00446      *
00447      * While we're at it, store the location of pin/pad #1 and #2 if
00448      * we can find them.
00449      */
00450 
00451     PIN_LOOP (element);
00452     {
00453       sumx += (double) pin->X;
00454       sumy += (double) pin->Y;
00455       pin_cnt++;
00456 
00457       for (rpindex = 0; reference_pin_names[rpindex]; rpindex++)
00458         {
00459           if (NSTRCMP (pin->Number, reference_pin_names[rpindex]) == 0)
00460             {
00461                 pinx[rpindex] = (double) pin->X;
00462                 piny[rpindex] = (double) pin->Y;
00463                 pinangle[rpindex] = 0.0; /* pins have no notion of angle */
00464                 pinfound[rpindex] = 1;
00465             }
00466         }
00467     }
00468     END_LOOP;
00469 
00470     PAD_LOOP (element);
00471     {
00472       sumx += (pad->Point1.X + pad->Point2.X) / 2.0;
00473       sumy += (pad->Point1.Y + pad->Point2.Y) / 2.0;
00474       pin_cnt++;
00475 
00476       for (rpindex = 0; reference_pin_names[rpindex]; rpindex++)
00477         {
00478           if (NSTRCMP (pad->Number, reference_pin_names[rpindex]) == 0)
00479             {
00480               padcentrex = (double) (pad->Point1.X + pad->Point2.X) / 2.0;
00481               padcentrey = (double) (pad->Point1.Y + pad->Point2.Y) / 2.0;
00482               pinx[rpindex] = padcentrex;
00483               piny[rpindex] = padcentrey;
00484               /*
00485                * NOTE: We swap the Y points because in PCB, the Y-axis
00486                * is inverted.  Increasing Y moves down.  We want to deal
00487                * in the usual increasing Y moves up coordinates though.
00488                */
00489               pinangle[rpindex] = (180.0 / M_PI) * atan2 (pad->Point1.Y - pad->Point2.Y,
00490                 pad->Point2.X - pad->Point1.X);
00491               pinfound[rpindex]=1;
00492             }
00493         }
00494     }
00495     END_LOOP;
00496 
00497     if (pin_cnt > 0)
00498       {
00499         centroidx = sumx / (double) pin_cnt;
00500         centroidy = sumy / (double) pin_cnt;
00501               
00502         if (NSTRCMP( AttributeGetFromList (&element->Attributes,"xy-centre"), "origin") == 0 )
00503           {
00504             x = element->MarkX;
00505             y = element->MarkY;
00506           }
00507         else
00508           {
00509             x = centroidx;
00510             y = centroidy;
00511           }
00512         
00513         fixed_rotation = AttributeGetFromList (&element->Attributes, "xy-fixed-rotation");
00514         if (fixed_rotation)
00515           {     
00516             /* The user specified a fixed rotation */
00517             theta = atof (fixed_rotation);
00518             found_any_not_at_centroid = 1;
00519             found_any = 1;
00520           } 
00521         else
00522           {
00523             /* Find first reference pin not at the  centroid  */
00524             found_any_not_at_centroid = 0;
00525             found_any = 0;
00526             theta = 0.0;
00527             for (rpindex = 0;
00528                  reference_pin_names[rpindex] && !found_any_not_at_centroid;
00529                  rpindex++)
00530               {
00531                 if (pinfound[rpindex])
00532                   {
00533                     found_any = 1;
00534 
00535                     /* Recenter pin "#1" onto the axis which cross at the part
00536                        centroid */
00537                     pin1x = pinx[rpindex] - x;
00538                     pin1y = piny[rpindex] - y;
00539 
00540                     /* flip x, to reverse rotation for elements on back */
00541                     if (FRONT (element) != 1)
00542                         pin1x = -pin1x;
00543 
00544                     /* if only 1 pin, use pin 1's angle */
00545                     if (pin_cnt == 1)
00546                       {
00547                         theta = pinangle[rpindex];
00548                         found_any_not_at_centroid = 1;
00549                       }
00550                     else if ((pin1x != 0.0) || (pin1y != 0.0))
00551                       {
00552                         theta = xyToAngle (pin1x, pin1y, pin_cnt > 2);
00553                         found_any_not_at_centroid = 1;
00554                       }
00555                   }
00556               }
00557 
00558             if (!found_any)
00559               {
00560                 Message
00561                   ("PrintBOM(): unable to figure out angle because I could\n"
00562                    "     not find a suitable reference pin of element %s\n"
00563                    "     Setting to %g degrees\n",
00564                    UNKNOWN (NAMEONPCB_NAME (element)), theta);
00565               }
00566             else if (!found_any_not_at_centroid)
00567               {
00568                 Message
00569                       ("PrintBOM(): unable to figure out angle of element\n"
00570                        "     %s because the reference pin(s) are at the centroid of the part.\n"
00571                        "     Setting to %g degrees\n",
00572                        UNKNOWN (NAMEONPCB_NAME (element)), theta);
00573               }
00574           }
00575         name = CleanBOMString ((char *)UNKNOWN (NAMEONPCB_NAME (element)));
00576         descr = CleanBOMString ((char *)UNKNOWN (DESCRIPTION_NAME (element)));
00577         value = CleanBOMString ((char *)UNKNOWN (VALUE_NAME (element)));
00578 
00579         y = PCB->MaxHeight - y;
00580         pcb_fprintf (fp, "%m+%s,\"%s\",\"%s\",%.2`mS,%.2`mS,%g,%s\n",
00581                      xy_unit->allow, name, descr, value, x, y,
00582                      theta, FRONT (element) == 1 ? "top" : "bottom");
00583         free (name);
00584         free (descr);
00585         free (value);
00586       }
00587   }
00588   END_LOOP;
00589 
00590   fclose (fp);
00591 
00592   /* Now print out a Bill of Materials file */
00593 
00594   fp = fopen (bom_filename, "w");
00595   if (!fp)
00596     {
00597       gui->log ("Cannot open file %s for writing\n", bom_filename);
00598       print_and_free (NULL, bom);
00599       return 1;
00600     }
00601 
00602   fprintf (fp, "# PcbBOM Version 1.0\n");
00603   fprintf (fp, "# Date: %s\n", utcTime);
00604   fprintf (fp, "# Author: %s\n", pcb_author ());
00605   fprintf (fp, "# Title: %s - PCB BOM\n", UNKNOWN (PCB->Name));
00606   fprintf (fp, "# Quantity, Description, Value, RefDes\n");
00607   fprintf (fp, "# --------------------------------------------\n");
00608 
00609   print_and_free (fp, bom);
00610 
00611   fclose (fp);
00612 
00613   return (0);
00614 }
00615 
00616 static void
00617 bom_do_export (HID_Attr_Val * options)
00618 {
00619   int i;
00620 
00621   if (!options)
00622     {
00623       bom_get_export_options (0);
00624       for (i = 0; i < NUM_OPTIONS; i++)
00625         bom_values[i] = bom_options[i].default_val;
00626       options = bom_values;
00627     }
00628 
00629   bom_filename = options[HA_bomfile].str_value;
00630   if (!bom_filename)
00631     bom_filename = "pcb-out.bom";
00632 
00633   xy_filename = options[HA_xyfile].str_value;
00634   if (!xy_filename)
00635     xy_filename = "pcb-out.xy";
00636 
00637   if (options[HA_xymm].int_value)
00638     xy_unit = get_unit_struct ("mm");
00639   else
00640     xy_unit = &get_unit_list ()[options[HA_unit].int_value];
00641   PrintBOM ();
00642 }
00643 
00644 static void
00645 bom_parse_arguments (int *argc, char ***argv)
00646 {
00647   hid_register_attributes (bom_options,
00648                            sizeof (bom_options) / sizeof (bom_options[0]));
00649   hid_parse_command_line (argc, argv);
00650 }
00651 
00652 HID bom_hid;
00653 
00654 void
00655 hid_bom_init ()
00656 {
00657   memset (&bom_hid, 0, sizeof (HID));
00658 
00659   common_nogui_init (&bom_hid);
00660 
00661   bom_hid.struct_size         = sizeof (HID);
00662   bom_hid.name                = "bom";
00663   bom_hid.description         = "Exports a Bill of Materials";
00664   bom_hid.exporter            = 1;
00665 
00666   bom_hid.get_export_options  = bom_get_export_options;
00667   bom_hid.do_export           = bom_do_export;
00668   bom_hid.parse_arguments     = bom_parse_arguments;
00669 
00670   hid_register_hid (&bom_hid);
00671 }