footprintupdate.c

00001 /*
00002  * footprintupdate -- Replaces footprints in layout with updated
00003  *     footprints.
00004  *
00005  * Copyright 2008 Dean Ferreyra <dferreyra@igc.org>, All rights reserved
00006  *
00007  * This file is part of Footprint-Update.
00008  * 
00009  * Footprint-Update is free software: you can redistribute it and/or modify
00010  * it under the terms of the GNU General Public License as published by
00011  * the Free Software Foundation, either version 3 of the License, or
00012  * (at your option) any later version.
00013  * 
00014  * Footprint-Update is distributed in the hope that it will be useful,
00015  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00016  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017  * GNU General Public License for more details.
00018  * 
00019  * You should have received a copy of the GNU General Public License
00020  * along with Footprint-Update.  If not, see <http://www.gnu.org/licenses/>.
00021  *
00022  * $Id: footprintupdate.c,v 1.31 2008-05-22 07:57:03 dean Exp $
00023  */
00024 
00025 /*
00026  * This PCB plug-in adds the UpdateFootprintsFromBuffer action.  It
00027  * allows you to replace existing elements (i.e., footprints) in your
00028  * PCB layout with an updated element that you've loaded into the
00029  * buffer.
00030  *
00031  * Because PCB modifies the elements it places, the plug-in needs to
00032  * make an educated guess about which elements to replace, and how to
00033  * position and orient the replacement elements.  It might guess wrong
00034  * or not replace some elements.
00035  *
00036  * WARNING: Make a backup copy of your layout before using this
00037  * plug-in in case something goes wrong!
00038  *
00039  * Usage:
00040  *
00041  *   UpdateFootprintsFromBuffer() or UpdateFootprintsFromBuffer(auto) --
00042  *     Uses the description field of the buffer element and tries to
00043  *     replace any layout element with a matching description field.
00044  *     (In my workflow, the element description in the footprint files
00045  *     and in the layout elements is the original footprint file name,
00046  *     e.g., "PHOENIX-PT-4-R.fp".)
00047  *
00048  *   UpdateFootprintsFromBuffer(auto, selected) -- Like
00049  *     UpdateFootprintsFromBuffer(auto) but will only consider selected
00050  *     layout elements.
00051  *
00052  *   UpdateFootprintsFromBuffer(auto, named, <name1>[, <name2>...]) --
00053  *     Like UpdateFootprintsFromBuffer(auto) but will only consider
00054  *     elements that match the given element names.  For example,
00055  *     UpdateFootprintsFromBuffer(auto, named, U101, U103, U105).
00056  *
00057  *   UpdateFootprintsFromBuffer(manual) or
00058  *     UpdateFootprintsFromBuffer(manual, selected) -- Like the "auto"
00059  *     version, but tries to replace the selected layout elements
00060  *     regardless of their description field.
00061  *
00062  *   UpdateFootprintsFromBuffer(manual, named, <name1>[, <name2>...]) --
00063  *     Like the "auto" version, but tries to replace the named layout
00064  *     elements regardless of their description field.
00065  *
00066  * Current assumptions and limitations:
00067  *
00068  *   * The rotation of the new element is calculated using two
00069  *     non-coincident pads or pins from both the buffer element and
00070  *     the layout element.  For elements consisting entirely of
00071  *     coincident pads and pins, the plug-in will only translate the
00072  *     replacement element without regard to rotation.
00073  *
00074  *   * Element-specific attributes in the layout element, if it has
00075  *     any, are replaced with the buffer element's attributes; i.e.,
00076  *     attributes in the layout element, if it has any, will be lost.
00077  */
00078 
00079 #include "utilities.h"
00080 #include "matrix.h"
00081 #include "pad-pin-data.h"
00082 
00083 static int replace_footprints(ElementTypePtr new_element);
00084 static Boolean replace_one_footprint(ElementTypePtr old_element,
00085                                      ElementTypePtr new_element);
00086 static Boolean replace_one_footprint_quick(ElementTypePtr old_element,
00087                                            ElementTypePtr new_element);
00088 static Boolean replace_one_footprint_expensive(ElementTypePtr old_element,
00089                                                ElementTypePtr new_element);
00090 static Boolean replace_one_footprint_translate_only(ElementTypePtr old_element,
00091                                                     ElementTypePtr new_element);
00092 static void replace_one_footprint_aux(ElementTypePtr old_element,
00093                                       PadOrPinType* old1_pp,
00094                                       PadOrPinType* old2_pp,
00095                                       ElementTypePtr new_element,
00096                                       PadOrPinType* new1_pp,
00097                                       PadOrPinType* new2_pp);
00098 static BYTE calculate_rotation_steps(CheapPointType new1_pt,
00099                                      CheapPointType new2_pt,
00100                                      CheapPointType old1_pt,
00101                                      CheapPointType old2_pt);
00102 static void transfer_text(ElementTypePtr old_element,
00103                           ElementTypePtr new_element);
00104 static void transfer_names(ElementTypePtr old_element,
00105                            ElementTypePtr new_element);
00106 static void transfer_flags(ElementTypePtr old_element,
00107                            ElementTypePtr new_element);
00108 
00109 enum {
00110   MATCH_MODE_AUTO,              /* Use element description field. */
00111   MATCH_MODE_MANUAL
00112 } match_mode = MATCH_MODE_AUTO;
00113 
00114 enum {
00115   STYLE_ALL,                    /* Only valid in auto mode. */
00116   STYLE_SELECTED,               /* Only consider selected elements. */
00117   STYLE_NAMED                   /* Only consider elements named in the
00118                                    arguments. */
00119 } style = STYLE_ALL;
00120 
00121 /*
00122  * Plug-in housekeeping.
00123  */
00124 
00125 int footprint_update(int argc, char **argv, int x, int y);
00126 
00127 HID_Action footprint_update_action_list[] = {
00128   { ACTION_NAME, NULL, footprint_update, NULL, NULL }
00129 };
00130 
00131 REGISTER_ACTIONS(footprint_update_action_list)
00132 
00133 void
00134 pcb_plugin_init()
00135 {
00136   register_footprint_update_action_list();
00137 }
00138 
00139 int global_argc = 0;
00140 char **global_argv = NULL;
00141 
00142 int
00143 usage()
00144 {
00145   base_log("usage:\n");
00146   base_log("    UpdateFootprintsFromBuffer()\n");
00147   base_log("    UpdateFootprintsFromBuffer(auto|manual)\n");
00148   base_log("    UpdateFootprintsFromBuffer(auto|manual, selected)\n");
00149   base_log("    UpdateFootprintsFromBuffer(auto|manual, named, "
00150            "<name1>[, <name2>...])\n");
00151   base_log("version: %s\n", VERSION);
00152   return 1;
00153 }
00154 
00155 int
00156 footprint_update(int argc, char **argv, int x, int y)
00157 {
00158   global_argc = argc;
00159   global_argv = argv;
00160 
00161   debug_log("footprint_update\n");
00162   debug_log("  argc: %d\n", argc);
00163   if (argc) {
00164     int i;
00165     for (i = 0; i < argc; i++) {
00166       debug_log("  argv[%d]: %s\n", i, argv[i]);
00167     }
00168   }
00169   if (argc >= 1) {
00170     if (strcasecmp(argv[0], "auto") == 0) {
00171       match_mode = MATCH_MODE_AUTO;
00172       style = STYLE_ALL;
00173     } else if (strcasecmp(argv[0], "manual") == 0) {
00174       match_mode = MATCH_MODE_MANUAL;
00175       style = STYLE_SELECTED;
00176     } else {
00177       base_log("Error: If given, the first argument must be "
00178                "\"auto\" or \"manual\".\n");
00179       return usage();
00180     }
00181     if (argc >= 2) {
00182       if (strcasecmp(argv[1], "selected") == 0) {
00183         style = STYLE_SELECTED;
00184       } else if (strcasecmp(argv[1], "named") == 0) {
00185         style = STYLE_NAMED;
00186       } else {
00187         base_log("Error: If given, the second argument must be "
00188                  "\"selected\", or \"named\".\n");
00189         return usage();
00190       }
00191     }
00192     
00193   }
00194   debug_log("match_mode: %d\n", match_mode);
00195   debug_log("style: %d\n", style);
00196 
00197   if (PASTEBUFFER->Data->ElementN != 1) {
00198     base_log("Error: Paste buffer should contain one element.\n");
00199     return usage();
00200   }
00201 
00202   ELEMENT_LOOP(PASTEBUFFER->Data);
00203   {
00204     int replaced = replace_footprints(element);
00205     if (replaced) {
00206       base_log("Replaced %d elements.\n", replaced);
00207       IncrementUndoSerialNumber();
00208     }
00209   }
00210   END_LOOP;
00211 
00212   return 0;
00213 }
00214 
00215 static int
00216 replace_footprints(ElementTypePtr new_element)
00217 {
00218   int replaced = 0;
00219   int i = 0;
00220 
00221   ELEMENT_LOOP (PCB->Data);
00222   {
00223     if (match_mode != MATCH_MODE_AUTO
00224         || strcmp(DESCRIPTION_NAME(new_element),
00225                   DESCRIPTION_NAME(element)) == 0) {
00226       Boolean matched = False;
00227 
00228       switch (style) {
00229       case STYLE_ALL:
00230         matched = True;
00231         break;
00232       case STYLE_SELECTED:
00233         matched = TEST_FLAG(SELECTEDFLAG, element);
00234         break;
00235       case STYLE_NAMED:
00236         for (i = 0; i < global_argc; i++) {
00237           if (NAMEONPCB_NAME(element)
00238               && strcasecmp(NAMEONPCB_NAME(element), global_argv[i]) == 0) {
00239             matched = True;
00240             break;
00241           }
00242         }
00243         break;
00244       }
00245       if (matched) {
00246         if (TEST_FLAG (LOCKFLAG, element)) {
00247           base_log("Skipping \"%s\".  Element locked.\n",
00248                    NAMEONPCB_NAME(element));
00249         } else {
00250           debug_log("Considering \"%s\".\n", NAMEONPCB_NAME(element));
00251           if (replace_one_footprint(element, new_element)) {
00252             base_log("Replaced \"%s\".\n", NAMEONPCB_NAME(element));
00253             replaced++;
00254           }
00255         }
00256       }
00257     }
00258   }
00259   END_LOOP;
00260   return replaced;
00261 }
00262 
00263 static Boolean
00264 replace_one_footprint(ElementTypePtr old_element,
00265                       ElementTypePtr new_element)
00266 {
00267   /* Called in order from from most desirable to least desirable. */
00268   return (replace_one_footprint_quick(old_element, new_element)
00269           || replace_one_footprint_expensive(old_element, new_element)
00270           || replace_one_footprint_translate_only(old_element, new_element));
00271 }
00272 
00273 static Boolean
00274 replace_one_footprint_quick(ElementTypePtr old_element,
00275                             ElementTypePtr new_element)
00276 {
00277   /* Requires that there be two non-coincident, uniquely numbered
00278      pads/pins that correspond between the old and new element. */
00279   if (! have_two_corresponding_unique_non_coincident(old_element,
00280                                                      new_element,
00281                                                      NULL, NULL,
00282                                                      NULL, NULL)) {
00283     return False;
00284   }
00285 
00286   /* Create copy of new element. */
00287   ElementTypePtr copy_element =
00288     CopyElementLowLevel(PCB->Data, NULL, new_element, False, 0, 0);
00289 
00290   PadOrPinType old1_pp;
00291   PadOrPinType old2_pp;
00292   PadOrPinType copy1_pp;
00293   PadOrPinType copy2_pp;
00294   if (! have_two_corresponding_unique_non_coincident(old_element,
00295                                                      copy_element,
00296                                                      &old1_pp, &old2_pp,
00297                                                      &copy1_pp, &copy2_pp)) {
00298       base_log("Error: Couldn't find two corresponding, unique, "
00299                "non-coincident element pads/pins.");
00300       return False;
00301   }
00302   replace_one_footprint_aux(old_element, &old1_pp, &old2_pp,
00303                             copy_element, &copy1_pp, &copy2_pp);
00304   debug_log("Used quick replacement.\n");
00305   return True;
00306 }
00307 
00308 static Boolean
00309 replace_one_footprint_expensive(ElementTypePtr old_element,
00310                                 ElementTypePtr new_element)
00311 {
00312   /* Requires that there be two corresponding, non-coincident
00313      pads/pins; non-unique pad/pin numbers okay.
00314 
00315      It's expensive because it does an exhaustive comparison of the
00316      resulting transformations for the four possible 90 degree
00317      rotations plus combinations of same-numbered pads/pins.  Should
00318      only be necessary if the element *only* has pads and pins that
00319      share the same pad/pin number. */
00320   if (! have_two_corresponding_non_coincident(old_element, new_element,
00321                                               NULL, NULL, NULL, NULL)) {
00322     return False;
00323   }
00324 
00325   /* Create copy of new element. */
00326   ElementTypePtr copy_element =
00327     CopyElementLowLevel(PCB->Data, NULL, new_element, False, 0, 0);
00328 
00329   int old_ppd_len = 0;
00330   ElementPadPinData* old_ppd =
00331     alloc_pad_pin_data_array(old_element, &old_ppd_len);
00332 
00333   int copy_ppd_len = 0;
00334   ElementPadPinData* copy_ppd =
00335     alloc_pad_pin_data_array(copy_element, &copy_ppd_len);
00336 
00337   int copy_index1 = 0;
00338   int copy_index2 = 0;
00339   if (! find_non_coincident(copy_ppd, copy_ppd_len,
00340                             &copy_index1, &copy_index2)) {
00341     base_log("Error: Couldn't find non-coincident element pads/pins.");
00342     MYFREE(old_ppd);
00343     MYFREE(copy_ppd);
00344     return False;
00345   }
00346 
00347   Boolean reflect = IS_REFLECTED(new_element, old_element);
00348   int old_index1 = 0;
00349   int old_index2 = 0;
00350   if (! find_best_corresponding_pads_or_pins(copy_ppd, copy_ppd_len,
00351                                              copy_index1, copy_index2,
00352                                              reflect,
00353                                              old_ppd, old_ppd_len,
00354                                              &old_index1, &old_index2)) {
00355     MYFREE(old_ppd);
00356     MYFREE(copy_ppd);
00357     return False;
00358   }
00359 
00360   replace_one_footprint_aux(old_element,
00361                             &old_ppd[old_index1].pp,
00362                             &old_ppd[old_index2].pp,
00363                             copy_element,
00364                             &copy_ppd[copy_index1].pp,
00365                             &copy_ppd[copy_index2].pp);
00366   MYFREE(old_ppd);
00367   MYFREE(copy_ppd);
00368   debug_log("Used expensive replacement.\n");
00369   return True;
00370 }
00371 
00372 static Boolean
00373 replace_one_footprint_translate_only(ElementTypePtr old_element,
00374                                      ElementTypePtr new_element)
00375 {
00376   /* Just requires one corresponding pad/pin.  Does no rotations. */
00377   if (! have_any_corresponding_pad_or_pin(old_element, new_element,
00378                                           NULL, NULL)) {
00379     return False;
00380   }
00381 
00382   /* Create copy of new element. */
00383   ElementTypePtr copy_element =
00384     CopyElementLowLevel(PCB->Data, NULL, new_element, False, 0, 0);
00385 
00386   PadOrPinType old_pp = make_pad_or_pin(NULL, NULL);
00387   PadOrPinType copy_pp = make_pad_or_pin(NULL, NULL);
00388   if (! have_any_corresponding_pad_or_pin(old_element, copy_element,
00389                                           &old_pp, &copy_pp)) {
00390     base_log("Error: Couldn't find any corresponding pads or pins.");
00391     return False;
00392   }
00393   replace_one_footprint_aux(old_element, &old_pp, NULL,
00394                             copy_element, &copy_pp, NULL);
00395   debug_log("Used translation-only replacement.\n");
00396   return True;
00397 }
00398 
00399 static void
00400 replace_one_footprint_aux(ElementTypePtr old_element,
00401                           PadOrPinType* old1_pp, PadOrPinType* old2_pp,
00402                           ElementTypePtr copy_element,
00403                           PadOrPinType* copy1_pp, PadOrPinType* copy2_pp)
00404 {
00405   Boolean two_points = (old2_pp && copy2_pp);
00406   Boolean reflect = IS_REFLECTED(copy_element, old_element);
00407 
00408   debug_log("Reflect?: %s\n", (reflect ? "yes" : "no"));
00409   if (reflect) {
00410     /* Change side of board */
00411     ChangeElementSide(copy_element, 0);
00412   }
00413 
00414   CheapPointType copy1_pt = pad_or_pin_center(copy1_pp);
00415   CheapPointType old1_pt = pad_or_pin_center(old1_pp);
00416 
00417   BYTE rot_steps = 0;
00418   if (two_points) {
00419     /* Calculate nearest rotation steps */
00420     CheapPointType copy2_pt = pad_or_pin_center(copy2_pp);
00421     CheapPointType old2_pt = pad_or_pin_center(old2_pp);
00422     rot_steps =
00423       calculate_rotation_steps(copy1_pt, copy2_pt, old1_pt, old2_pt);
00424   }
00425   if (rot_steps) {
00426     /* Rotate copy */
00427     RotateElementLowLevel(PCB->Data, copy_element, 0, 0, rot_steps);
00428     /* Recalculate since copy_element has changed. */
00429     copy1_pt = pad_or_pin_center(copy1_pp);
00430   }
00431 
00432   /* Calculate translation */
00433   LocationType dx = old1_pt.X - copy1_pt.X;
00434   LocationType dy = old1_pt.Y - copy1_pt.Y;
00435   /* Move element */
00436   MoveElementLowLevel(PCB->Data, copy_element, dx, dy);
00437 
00438   /* Transfer pad/pin text and names. */
00439   transfer_text(old_element, copy_element);
00440   transfer_names(old_element, copy_element);
00441   transfer_flags(old_element, copy_element);
00442   SetElementBoundingBox(PCB->Data, copy_element, &PCB->Font);
00443 
00444   AddObjectToCreateUndoList(ELEMENT_TYPE,
00445                             copy_element, copy_element, copy_element);
00446   /* Remove old element. */
00447   MoveObjectToRemoveUndoList(ELEMENT_TYPE,
00448                              old_element, old_element, old_element);
00449 }
00450 
00451 static BYTE
00452 calculate_rotation_steps(CheapPointType new1_pt, CheapPointType new2_pt,
00453                          CheapPointType old1_pt, CheapPointType old2_pt)
00454 {
00455   /* Translation of new1_pt to origin. */
00456   LocationType new1_to_origin_dx = -new1_pt.X;
00457   LocationType new1_to_origin_dy = -new1_pt.Y;
00458   /* Use translation for new2_pt. */
00459   CheapPointType new2_translated_pt =
00460     make_point(new2_pt.X + new1_to_origin_dx,
00461                new2_pt.Y + new1_to_origin_dy);
00462   double new2_angle =
00463     (new2_translated_pt.Y || new2_translated_pt.X
00464      ? atan2(new2_translated_pt.Y, new2_translated_pt.X) : 0);
00465 
00466   /* Translation of old1_pt to origin. */
00467   LocationType old1_to_origin_dx = -old1_pt.X;
00468   LocationType old1_to_origin_dy = -old1_pt.Y;
00469   /* Use translation for old2_pt. */
00470   CheapPointType old2_translated_pt =
00471     make_point(old2_pt.X + old1_to_origin_dx,
00472                old2_pt.Y + old1_to_origin_dy);
00473   double old2_angle =
00474     (old2_translated_pt.X || old2_translated_pt.Y
00475      ? atan2(old2_translated_pt.Y, old2_translated_pt.X) : 0);
00476 
00477   /* Compute rotation, adjust to match atan2 range. */
00478   double angle = old2_angle - new2_angle;
00479   if (angle > M_PI) {
00480     angle -= 2 * M_PI;
00481   } else if (angle < -M_PI) {
00482     angle += 2 * M_PI;
00483   }
00484   debug_log("Rotation: %lf\n", RAD_TO_DEG * angle);
00485   /* Return a PCB rotation steps count. */
00486   return angle_to_rotation_steps(angle);
00487 }
00488 
00489 static void
00490 transfer_text(ElementTypePtr old_element, ElementTypePtr new_element)
00491 {
00492   int i;
00493   for (i = 0; i < MAX_ELEMENTNAMES; i++) {
00494     TextTypePtr old_text = &old_element->Name[i];
00495     TextTypePtr new_text = &new_element->Name[i];
00496     MYFREE(new_text->TextString);
00497     new_text->X = old_text->X;
00498     new_text->Y = old_text->Y;
00499     new_text->Direction = old_text->Direction;
00500     new_text->Flags = old_text->Flags;
00501     new_text->Scale = old_text->Scale;
00502     new_text->TextString =
00503       ((old_text->TextString && *old_text->TextString) ?
00504        MyStrdup(old_text->TextString, "transfer_text()") : NULL);
00505   }
00506 }
00507 
00508 static void
00509 transfer_names(ElementTypePtr old_element, ElementTypePtr new_element)
00510 {
00511   PAD_OR_PIN_LOOP_HYG(old_element, _old);
00512   {
00513     const char* old_name = pad_or_pin_name(&pp_old);
00514     PAD_OR_PIN_LOOP_HYG(new_element, _new);
00515     {
00516       if (pad_or_pin_number_cmp(&pp_old, &pp_new) == 0) {
00517         if (pp_new.pad) {
00518           MYFREE(pp_new.pad->Name);
00519           pp_new.pad->Name = MyStrdup((char*)old_name, "transfer_names()");
00520         } else if (pp_new.pin) {
00521           MYFREE(pp_new.pin->Name);
00522           pp_new.pin->Name = MyStrdup((char*)old_name, "transfer_names()");
00523         }
00524       }
00525     }
00526     END_LOOP;
00527   }
00528   END_LOOP;
00529 }
00530 
00531 static void
00532 transfer_flags(ElementTypePtr old_element, ElementTypePtr new_element)
00533 {
00534   PAD_OR_PIN_LOOP_HYG(old_element, _old);
00535   {
00536     if (pad_or_pin_test_flag(&pp_old, SELECTEDFLAG)) {
00537       PAD_OR_PIN_LOOP_HYG(new_element, _new);
00538       {
00539         if (pad_or_pin_number_cmp(&pp_old, &pp_new) == 0) {
00540           pad_or_pin_set_flag(&pp_new, SELECTEDFLAG);
00541         }
00542       }
00543       END_LOOP;
00544     }
00545   }
00546   END_LOOP;
00547   if (TEST_FLAG(SELECTEDFLAG, old_element)) {
00548     SET_FLAG(SELECTEDFLAG, new_element);
00549   }
00550 }

Generated on Tue Aug 17 15:28:04 2010 for pcb-plugins by  doxygen 1.4.6-NO