[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

gEDA-user: PCB LF patch: single "foward annotate" sch->pcb button



Well, here it is.  Yes, I know it's not in git's pretty format, but
it's a bunch of local commits that make no sense separately.  Discuss,
then I'll commit it all.

John: yes, you can still do things your way :-)

I've also attached my working notes, in case it adds insight.

DJ

------------------------------------------------------------------------------

The rule is, the .pcb file cannot name the command (or commands) it is
to execute, only select from a list of pre-determined commands.

Attribute Map
-------------

import::mode
	make
		make -s PCB="<filename>" SRCLIST="<sourcelist . . .>" OUT="<tmpfile>" pcb_import
	gnetlist (default)
		gnetlist -g pcblf -o <tmpfile> <sourcelist . . .>

import::src0 ... import::src99 (etc)
	schematic files.  Note that the list stops at the first
	missing number in sequence, even if there are higher numbered
	sources present.


Action Map
----------

Import()
	imports using defaults

Import(make|gnetlist)
	imports using the method given

Import(...,sourcelist)
	imports using the method given and the sources given

ImportGui()
	The GUI defines this, to do a user-interactive import editor
	or whatever.  (not implemented)


Import does...
   {"Import schematics"
    ExecCommand(gnetlist -g pcblf -o m32c-sdram.cmd m32c-sdram.sch m32c-io.sch)
    ExecuteFile(m32c-sdram.cmd)
    Atomic(Save)
    DeleteRats(AllRats)
    Atomic(Restore)
    AddRats(AllRats)
    Atomic(Block)    
   }

Netlist(Clear)
	Clears the netlist

Netlist(Add,name,pin,defer)
	Adds the given pin to the given name.  If defer, don't tell
	the GUI about it - must then call NetlistChanged() manually.

Netlist(Sort)
	Sorts the netlist once it's all built.

ElementAddStart()
	Notes beginning of add sequence.

ElementAddIf(refdes,footprint,value)
	Add the given element if it is missing, else update its
	footprint and value.

ElementSetAttr(refdes,attrname,attrval)
	Set the given attribute.  If attrval is missing, erase.
	attrval may be "unknown".

ChangePinName(refdes,pinnum,pinname)
	Change the name of the given pin.

ElementAddDone()
	Notes end of add sequence.  Unmentioned elements are assumed
	to be unwanted, and are selected so the user can easily delete
	them.

------------------------------------------------------------------------------


diff --git a/src/action.c b/src/action.c
index 9cf163b..4b660df 100644
--- a/src/action.c
+++ b/src/action.c
@@ -70,6 +70,7 @@
 #include "thermal.h"
 #include "undo.h"
 #include "rtree.h"
+#include "macro.h"
 
 #ifdef HAVE_LIBDMALLOC
 #include <dmalloc.h>
@@ -306,6 +307,9 @@ static struct
 }
 Note;
 
+static int defer_updates = 0;
+static int defer_needs_update = 0;
+
 static Cardinal polyIndex = 0;
 static Boolean IgnoreMotionEvents = False;
 static Boolean saved_mode = False;
@@ -4436,8 +4440,13 @@ ActionChangePinName (int argc, char **argv, int x, int y)
    */
   if (changed)
     {
-      IncrementUndoSerialNumber ();
-      gui->invalidate_all ();
+      if (defer_updates)
+	defer_needs_update = 1;
+      else
+	{
+	  IncrementUndoSerialNumber ();
+	  gui->invalidate_all ();
+	}
     }
 
   return 0;
@@ -6752,6 +6761,8 @@ ActionExecuteFile (int argc, char **argv, int x, int y)
       return 1;
     }
 
+  defer_updates = 1;
+  defer_needs_update = 0;
   while (fgets (line, sizeof (line), fp) != NULL)
     {
       n++;
@@ -6774,11 +6785,17 @@ ActionExecuteFile (int argc, char **argv, int x, int y)
 
       if (*sp && *sp != '#')
 	{
-	  Message ("%s : line %-3d : \"%s\"\n", fname, n, sp);
+	  /*Message ("%s : line %-3d : \"%s\"\n", fname, n, sp);*/
 	  hid_parse_actions (sp, 0);
 	}
     }
 
+  defer_updates = 0;
+  if (defer_needs_update)
+    {
+      IncrementUndoSerialNumber ();
+      gui->invalidate_all ();
+    }
   fclose (fp);
   return 0;
 }
@@ -6795,10 +6812,634 @@ ActionPSCalib (int argc, char **argv, int x, int y)
 
 /* --------------------------------------------------------------------------- */
 
+static ElementType *element_cache = NULL;
+
+static ElementType *
+find_element_by_refdes (char *refdes)
+{
+  if (element_cache
+      && strcmp (NAMEONPCB_NAME(element_cache), refdes) == 0)
+    return element_cache;
+
+  ELEMENT_LOOP (PCB->Data);
+  {
+    if (NAMEONPCB_NAME(element)
+	&& strcmp (NAMEONPCB_NAME(element), refdes) == 0)
+      {
+	element_cache = element;
+	return element_cache;
+      }
+  }
+  END_LOOP;
+  return NULL;
+}
+
+static AttributeType *
+lookup_attr (AttributeListTypePtr list, const char *name)
+{
+  int i;
+  for (i=0; i<list->Number; i++)
+    if (strcmp (list->List[i].name, name) == 0)
+      return & list->List[i];
+  return NULL;
+}
+
+static void
+delete_attr (AttributeListTypePtr list, AttributeType *attr)
+{
+  int idx = attr - list->List;
+  if (idx < 0 || idx >= list->Number)
+    return;
+  if (list->Number - idx > 1)
+    memmove (attr, attr+1, (list->Number - idx - 1) * sizeof(AttributeType));
+  list->Number --;
+}
+
+static const char elementaddstart_syntax[] = "ElementAddStart()";
+
+static const char elementaddstart_help[] = "Notes the start of an element set update.";
+
+/* %start-doc actions elementaddstart
+
+Used before conditionally adding elements, it clears the list of
+"remembered" elements, so that unlisted elements can be discovered
+later.
+
+%end-doc */
+
+static int
+ActionElementAddStart (int argc, char **argv, int x, int y)
+{
+  ELEMENT_LOOP (PCB->Data);
+  {
+    CLEAR_FLAG (FOUNDFLAG, element);
+  }
+  END_LOOP;
+  element_cache = NULL;
+  return 0;
+}
+
+static const char elementaddif_syntax[] = "ElementAddIf(<refdes>,<footprint>,<value>)";
+
+static const char elementaddif_help[] = "Adds the given element if it doesn't already exist.";
+
+/* %start-doc actions elementaddif
+
+Searches the board for an element with a matching refdes.
+
+If found, the value and footprint are updated.
+
+If not found, a new element is created with the given footprint and value.
+
+%end-doc */
+
+static int
+ActionElementAddIf (int argc, char **argv, int x, int y)
+{
+  ElementType *e = NULL;
+  char *refdes, *value, *footprint, *old;
+  char *args[3];
+
+  if (argc != 3)
+    AFAIL (elementaddif);
+
+  refdes = ARG(0);
+  footprint = ARG(1);
+  value = ARG(2);
+
+  args[0] = footprint;
+  args[1] = refdes;
+  args[2] = value;
+
+  e = find_element_by_refdes (refdes);
+
+  if (!e)
+    {
+      /* Not on board, need to add it. */
+      if (LoadFootprint(argc, args, x, y))
+	return 1;
+      if (CopyPastebufferToLayout (0, 0))
+	SetChangedFlag (True);
+    }
+
+  else if (e && strcmp (DESCRIPTION_NAME(e), footprint) != 0)
+    {
+      int er, pr, i;
+      LocationType mx, my;
+      ElementType *pe;
+
+      /* Different footprint, we need to swap them out.  */
+      if (LoadFootprint(argc, args, x, y))
+	return 1;
+
+      er = ElementOrientation (e);
+      pe = & PASTEBUFFER->Data->Element[0];
+      pr = ElementOrientation (pe);
+
+      mx = e->MarkX;
+      my = e->MarkY;
+
+      if (er != pr)
+	RotateElementLowLevel (PASTEBUFFER->Data, pe, pe->MarkX, pe->MarkY, (er-pr+4)%4);
+
+      for (i=0; i<MAX_ELEMENTNAMES; i++)
+	{
+	  pe->Name[i].X = e->Name[i].X - mx;
+	  pe->Name[i].Y = e->Name[i].Y - my;
+	  pe->Name[i].Direction = e->Name[i].Direction;
+	  pe->Name[i].Scale = e->Name[i].Scale;
+	}
+
+      RemoveElement (e);
+
+      if (CopyPastebufferToLayout (mx, my))
+	SetChangedFlag (True);
+    }
+
+  e = find_element_by_refdes (refdes);
+
+  old = ChangeElementText (PCB, PCB->Data, e, NAMEONPCB_INDEX, strdup (refdes));
+  if (old)
+    free(old);
+  old = ChangeElementText (PCB, PCB->Data, e, VALUE_INDEX, strdup (value));
+  if (old)
+    free(old);
+
+  SET_FLAG (FOUNDFLAG, e);
+
+  return 0;
+}
+
+static const char elementadddone_syntax[] = "ElementAddDone()";
+
+static const char elementadddone_help[] = "Notes the end of an element set update";
+
+/* %start-doc actions elementadddone
+
+Used after conditionally adding elements, it finds any unmentioned
+elements which were previously added (non-empty refdes) and deletes
+them.
+
+%end-doc */
+
+static int
+ActionElementAddDone (int argc, char **argv, int x, int y)
+{
+  ELEMENT_LOOP (PCB->Data);
+  {
+    if (TEST_FLAG (FOUNDFLAG, element))
+      {
+	CLEAR_FLAG (FOUNDFLAG, element);
+      }
+    else if (! EMPTY_STRING_P (NAMEONPCB_NAME (element)))
+      {
+	/* Unnamed elements should remain untouched */
+	SET_FLAG (SELECTEDFLAG, element);
+      }
+  }
+  END_LOOP;
+  return 0;
+}
+
+static const char elementsetattr_syntax[] = "ElementSetAttr(refdes,name[,value])";
+
+static const char elementsetattr_help[] = "Sets or clears an element-specific attribute";
+
+/* %start-doc actions elementsetattr
+
+If a value is specified, the named attribute is added (if not already
+present) or changed (if it is) to the given value.  If the value is
+not specified, the given attribute is removed if present.
+
+%end-doc */
+
+static int
+ActionElementSetAttr (int argc, char **argv, int x, int y)
+{
+  ElementType *e = NULL;
+  char *refdes, *name, *value;
+  AttributeType *attr;
+
+  if (argc < 2)
+    {
+      AFAIL (changepinname);
+    }
+
+  refdes = argv[0];
+  name = argv[1];
+  value = ARG(2);
+
+  ELEMENT_LOOP (PCB->Data);
+  {
+    if (NSTRCMP (refdes, NAMEONPCB_NAME (element)) == 0)
+      {
+	e = element;
+	break;
+      }
+  }
+  END_LOOP;
+
+  if (!e)
+    {
+      Message("Cannot change attribute of %s - element not found", refdes);
+      return 1;
+    }
+
+  attr = lookup_attr (&e->Attributes, name);
+
+  if (attr && value)
+    {
+      MYFREE (attr->value);
+      attr->value = MyStrdup (value, "ElementSetAttr");
+    }
+  if (attr && ! value)
+    {
+      delete_attr (& e->Attributes, attr);
+    }
+  if (!attr && value)
+    {
+      CreateNewAttribute (& e->Attributes, name, value);
+    }
+
+  return 0;
+}
+
+static const char execcommand_syntax[] = "ExecCommand(command)";
+
+static const char execcommand_help[] = "Runs a command";
+
+/* %start-doc actions execcommand
+
+Runs the given command, which is a system executable.
+
+%end-doc */
+
+static int
+ActionExecCommand (int argc, char **argv, int x, int y)
+{
+  char *command;
+
+  if (argc < 1)
+    {
+      AFAIL (execcommand);
+    }
+
+  command = ARG(0);
+
+  if (system (command))
+    return 1;
+  return 0;
+}
+
+static const char import_syntax[] =
+  "Import()\n"
+  "Import([gnetlist|make[,source,source,...]])\n";
+
+static const char import_help[] = "Import schematics";
+
+/* %start-doc actions import
+
+Imports element and netlist data from the schematics (or some other
+source).  The first parameter, which is optional, is the mode.  If not
+specified, the @code{import::mode} attribute in the PCB is used.
+@code{gnetlist} means gnetlist is used to obtain the information from
+the schematics.  @code{make} invokes @code{make}, assuming the user
+has a @code{Makefile} in the current directory.  The @code{Makefile}
+will be invoked with the following variables set:
+
+@table @code
+
+@item PCB
+The name of the .pcb file
+
+@item SRCLIST
+A space-separated list of source files
+
+@item OUT
+The name of the file in which to put the command script.
+
+@end table
+
+The target requested will be @code{pcb_import}.
+
+If you specify the mode, you may also specify the source files
+(schematics).  If you do not specify any, the list of schematics is
+obtained by reading the @code{import::src@var{N}} attributes (like
+@code{import::src0}, @code{import::src1}, etc).
+
+%end-doc */
+
+static int
+pcb_spawnvp (char **argv)
+{
+  int pid;
+  pid = fork ();
+  if (pid < 0)
+    {
+      /* error */
+      Message("Cannot fork!");
+      return 1;
+    }
+  else if (pid == 0)
+    {
+      /* Child */
+      execvp (argv[0], argv);
+      exit(1);
+    }
+  else
+    {
+      int rv;
+      /* Parent */
+      wait (&rv);
+    }
+  return 0;
+}
+
+static int
+ActionImport (int argc, char **argv, int x, int y)
+{
+  char *mode;
+  char **sources = NULL;
+  int nsources = 0;
+
+  mode = ARG (0);
+  if (! mode)
+    mode = AttributeGet (PCB, "import::mode");
+  if (! mode)
+    mode = "gnetlist";
+
+  if (argc > 1)
+    {
+      sources = argv + 1;
+      nsources = argc - 1;
+    }
+
+  if (! sources)
+    {
+      char sname[40];
+      char *src;
+
+      nsources = -1;
+      do {
+	nsources ++;
+	sprintf(sname, "import::src%d", nsources);
+	src = AttributeGet (PCB, sname);
+      } while (src);
+
+      if (nsources > 0)
+	{
+	  sources = (char **) malloc ((nsources + 1) * sizeof (char *));
+	  nsources = -1;
+	  do {
+	    nsources ++;
+	    sprintf(sname, "import::src%d", nsources);
+	    src = AttributeGet (PCB, sname);
+	    sources[nsources] = src;
+	  } while (src);
+	}
+    }
+
+  if (! sources)
+    {
+      /* Replace .pcb with .sch and hope for the best.  */
+      char *pcbname = PCB->Filename;
+      char *schname;
+      char *dot, *slash, *bslash;
+
+      schname = (char *) malloc (strlen(pcbname) + 5);
+      strcpy (schname, pcbname);
+      dot = strchr (schname, '.');
+      slash = strchr (schname, '/');
+      bslash = strchr (schname, '\\');
+      if (dot && slash && dot < slash)
+	dot = NULL;
+      if (dot && bslash && dot < bslash)
+	dot = NULL;
+      if (dot)
+	*dot = 0;
+      strcat (schname, ".sch");
+
+      sources = (char **) malloc (2 * sizeof (char *));
+      sources[0] = schname;
+      sources[1] = NULL;
+      nsources = 1;
+    }
+
+  if (strcasecmp (mode, "gnetlist") == 0)
+    {
+      char *tmpfile = tmpnam (NULL);
+      char **cmd;
+      int i, pid;
+      cmd = (char **) malloc ((6 + nsources) * sizeof (char *));
+      cmd[0] = "gnetlist";
+      cmd[1] = "-g";
+      cmd[2] = "pcblf";
+      cmd[3] = "-o";
+      cmd[4] = tmpfile;
+      for (i=0; i<nsources; i++)
+	cmd[5+i] = sources[i];
+      cmd[5+nsources] = NULL;
+
+      if (pcb_spawnvp (cmd))
+	{
+	  unlink (tmpfile);
+	  return 1;
+	}
+
+      cmd[0] = tmpfile;
+      cmd[1] = NULL;
+      ActionExecuteFile (1, cmd, 0, 0);
+
+      free (cmd);
+      unlink (tmpfile);
+    }
+  else if (strcasecmp (mode, "make") == 0)
+    {
+      char *tmpfile = tmpnam (NULL);
+      char **cmd;
+      int i, pid;
+      char *srclist;
+      int srclen;
+
+      srclen = sizeof("SRCLIB=") + 2;
+      for (i=0; i<nsources; i++)
+	srclen += strlen (sources[i]) + 2;
+      srclist = (char *) malloc (srclen);
+      strcpy (srclist, "SRCLIST=");
+      for (i=0; i<nsources; i++)
+	{
+	  if (i)
+	    strcat (srclist, " ");
+	  strcat (srclist, sources[i]);
+	}
+
+      printf("Makefile!\n");
+      
+      cmd = (char **) malloc (7 * sizeof (char *));
+      cmd[0] = "make";
+      cmd[1] = "-s";
+      cmd[2] = Concat ("PCB=", PCB->Filename, NULL);
+      cmd[3] = srclist;
+      cmd[4] = Concat ("OUT=", tmpfile, NULL);
+      cmd[5] = "pcb_import";
+      cmd[6] = NULL;
+
+      if (pcb_spawnvp (cmd))
+	{
+	  unlink (tmpfile);
+	  free (cmd[2]);
+	  free (cmd[3]);
+	  free (cmd[4]);
+	  free (cmd);
+	  return 1;
+	}
+
+      cmd[0] = tmpfile;
+      cmd[1] = NULL;
+      ActionExecuteFile (1, cmd, 0, 0);
+
+      free (cmd[2]);
+      free (cmd[3]);
+      free (cmd[4]);
+      free (cmd);
+      unlink (tmpfile);
+    }
+  else
+    {
+      Message ("Unknown import mode: %s\n", mode);
+      return 1;
+    }
+
+  DeleteRats (False);
+  AddAllRats (False, NULL);
+
+  return 0;
+}
+
+/* ------------------------------------------------------------ */
+
+static const char attributes_syntax[] =
+"Attributes(Layout|Layer|Element)\n"
+"Attributes(Layer,layername)";
+
+static const char attributes_help[] =
+"Let the user edit the attributes of the layout, current or given\n"
+"layer, or selected element.";
+
+/* %start-doc actions Attributes
+
+This just pops up a dialog letting the user edit the attributes of the
+pcb, an element, or a layer.
+
+%end-doc */
+
+
+static int
+ActionAttributes (int argc, char **argv, int x, int y)
+{
+  char *function = ARG (0);
+  char *layername = ARG (1);
+  char *buf;
+
+  if (!function)
+    AFAIL (attributes);
+
+  if (!gui->edit_attributes)
+    {
+      Message ("This GUI doesn't support Attribute Editing\n");
+      return 1;
+    }
+
+  switch (GetFunctionID (function))
+    {
+    case F_Layout:
+      {
+	gui->edit_attributes("Layout Attributes", &(PCB->Attributes));
+	return 0;
+      }
+
+    case F_Layer:
+      {
+	LayerType *layer = CURRENT;
+	if (layername)
+	  {
+	    int i;
+	    layer = NULL;
+	    for (i=0; i<max_layer; i++)
+	      if (strcmp (PCB->Data->Layer[i].Name, layername) == 0)
+		{
+		  layer = & (PCB->Data->Layer[i]);
+		  break;
+		}
+	    if (layer == NULL)
+	      {
+		Message ("No layer named %s\n", layername);
+		return 1;
+	      }
+	  }
+	buf = (char *) malloc (strlen (layer->Name) + strlen ("Layer X Attributes"));
+	sprintf (buf, "Layer %s Attributes", layer->Name);
+	gui->edit_attributes(buf, &(layer->Attributes));
+	free (buf);
+	return 0;
+      }
+
+    case F_Element:
+      {
+	int n_found = 0;
+	ElementType *e = NULL;
+	ELEMENT_LOOP (PCB->Data);
+	{
+	  if (TEST_FLAG (SELECTEDFLAG, element))
+	    {
+	      e = element;
+	      n_found ++;
+	    }
+	}
+	END_LOOP;
+	if (n_found > 1)
+	  {
+	    Message ("Too many elements selected\n");
+	    return 1;
+	  }
+	if (n_found == 0)
+	  {
+	    void *ptrtmp;
+	    gui->get_coords ("Click on an element", &x, &y);
+	    if ((SearchScreen
+		 (x, y, ELEMENT_TYPE, &ptrtmp,
+		  &ptrtmp, &ptrtmp)) != NO_TYPE)
+	      e = (ElementTypePtr) ptrtmp;
+	    else
+	      {
+		Message ("No element found there\n");
+		return 1;
+	      }
+	  }
+
+	buf = (char *) malloc (strlen (NAMEONPCB_NAME(e)) + strlen ("Element X Attributes"));
+	sprintf(buf, "Element %s Attributes", NAMEONPCB_NAME(e));
+	gui->edit_attributes(buf, &(e->Attributes));
+	free (buf);
+	break;
+      }
+
+    default:
+      AFAIL (attributes);
+    }
+
+  return 0;
+}
+
+/* --------------------------------------------------------------------------- */
+
 HID_Action action_action_list[] = {
   {"AddRats", 0, ActionAddRats,
    addrats_help, addrats_syntax}
   ,
+  {"Attributes", 0, ActionAttributes,
+   attributes_help, attributes_syntax}
+  ,
   {"Atomic", 0, ActionAtomic,
    atomic_help, atomic_syntax}
   ,
@@ -6969,6 +7610,25 @@ HID_Action action_action_list[] = {
   ,
   {"pscalib", 0, ActionPSCalib}
   ,
+  {"ElementAddStart", 0, ActionElementAddStart,
+   elementaddstart_help, elementaddstart_syntax}
+  ,
+  {"ElementAddIf", 0, ActionElementAddIf,
+   elementaddif_help, elementaddif_syntax}
+  ,
+  {"ElementAddDone", 0, ActionElementAddDone,
+   elementadddone_help, elementadddone_syntax}
+  ,
+  {"ElementSetAttr", 0, ActionElementSetAttr,
+   elementsetattr_help, elementsetattr_syntax}
+  ,
+  {"ExecCommand", 0, ActionExecCommand,
+   execcommand_help, execcommand_syntax}
+  ,
+  {"Import", 0, ActionImport,
+   import_help, import_syntax}
+  ,
 };
 
 REGISTER_ACTIONS (action_action_list)
+
diff --git a/src/buffer.c b/src/buffer.c
index be51a0f..cdb34dd 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -83,6 +83,8 @@ static void *MovePolygonToBuffer (LayerTypePtr, PolygonTypePtr);
 static void *MoveElementToBuffer (ElementTypePtr);
 static void SwapBuffer (BufferTypePtr);
 
+#define ARG(n) (argc > (n) ? argv[n] : 0)
+
 /* ---------------------------------------------------------------------------
  * some local identifiers
  */
@@ -603,6 +605,287 @@ LoadElementToBuffer (BufferTypePtr Buffer, char *Name, Boolean FromFile)
 
 
 /*---------------------------------------------------------------------------
+ * Searches for the given element by "footprint" name, and loads it
+ * into the buffer.
+ */
+
+/* Figuring out which library entry is the one we want is a little
+   tricky.  For file-based footprints, it's just a matter of finding
+   the first match in the search list.  For m4-based footprints you
+   need to know what magic to pass to the m4 functions.  Fortunately,
+   the footprint needed is determined when we build the m4 libraries
+   and stored as a comment in the description, so we can search for
+   that to find the magic we need.  We use a hash to store the
+   corresponding footprints and pointers to the library tree so we can
+   quickly find the various bits we need to load a given
+   footprint.  */
+
+typedef struct {
+  char *footprint;
+  int footprint_allocated;
+  int menu_idx;
+  int entry_idx;
+} FootprintHashEntry;
+
+static FootprintHashEntry *footprint_hash = 0;
+int footprint_hash_size = 0;
+
+void
+clear_footprint_hash ()
+{
+  int i;
+  if (!footprint_hash)
+    return;
+  for (i=0; i<footprint_hash_size; i++)
+    if (footprint_hash[i].footprint_allocated)
+      free (footprint_hash[i].footprint);
+  free (footprint_hash);
+  footprint_hash = NULL;
+  footprint_hash_size = 0;
+}
+
+/* Used to sort footprint pointer entries.  Note we include the index
+   numbers so that same-named footprints are sorted by the library
+   search order.  */
+static int
+footprint_hash_cmp (const void *va, const void *vb)
+{
+  int i;
+  FootprintHashEntry *a = (FootprintHashEntry *)va;
+  FootprintHashEntry *b = (FootprintHashEntry *)vb;
+
+  i = strcmp (a->footprint, b->footprint);
+  if (i == 0)
+    i = a->menu_idx - b->menu_idx;
+  if (i == 0)
+    i = a->entry_idx - b->entry_idx;
+  return i;
+}
+
+void
+make_footprint_hash ()
+{
+  int i, j;
+  char *fp;
+  int num_entries = 0;
+
+  clear_footprint_hash ();
+
+  for (i=0; i<Library.MenuN; i++)
+    for (j=0; j<Library.Menu[i].EntryN; j++)
+      num_entries ++;
+  footprint_hash = (FootprintHashEntry *)malloc (num_entries * sizeof(FootprintHashEntry));
+  num_entries = 0;
+
+  /* There are two types of library entries.  The file-based types
+     have a Template of (char *)-1 and the AllocatedMemory is the full
+     path to the footprint file.  The m4 ones have the footprint name
+     in brackets in the description.  */
+  for (i=0; i<Library.MenuN; i++)
+    {
+      for (j=0; j<Library.Menu[i].EntryN; j++)
+	{
+	  footprint_hash[num_entries].menu_idx = i;
+	  footprint_hash[num_entries].entry_idx = j;
+	  if (Library.Menu[i].Entry[j].Template == (char *) -1)
+	    {
+	      fp = strrchr (Library.Menu[i].Entry[j].AllocatedMemory, '/');
+	      if (!fp)
+		fp = strrchr (Library.Menu[i].Entry[j].AllocatedMemory, '\\');
+	      if (fp)
+		fp ++;
+	      else
+		fp = Library.Menu[i].Entry[j].AllocatedMemory;
+	      footprint_hash[num_entries].footprint = fp;
+	      footprint_hash[num_entries].footprint_allocated = 0;
+	    }
+	  else
+	    {
+	      fp = strrchr (Library.Menu[i].Entry[j].Description, '[');
+	      if (fp)
+		{
+		  footprint_hash[num_entries].footprint = strdup (fp+1);
+		  footprint_hash[num_entries].footprint_allocated = 1;
+		  fp = strchr (footprint_hash[num_entries].footprint, ']');
+		  if (fp)
+		    *fp = 0;
+		}
+	      else
+		{
+		  fp = Library.Menu[i].Entry[j].Description;
+		  footprint_hash[num_entries].footprint = fp;
+		  footprint_hash[num_entries].footprint_allocated = 0;
+		}
+	    }
+	  num_entries ++;
+	}
+    }
+
+  footprint_hash_size = num_entries;
+  qsort (footprint_hash, num_entries, sizeof(footprint_hash[0]), footprint_hash_cmp);
+#if 0
+  for (i=0; i<num_entries; i++)
+    printf("[%s]\n", footprint_hash[i].footprint);
+#endif
+}
+
+FootprintHashEntry *
+search_footprint_hash (const char *footprint)
+{
+  int i, min, max, c;
+
+  /* Standard binary search */
+
+  min = -1;
+  max = footprint_hash_size;
+
+  while (max - min > 1)
+    {
+      i = (min+max)/2;
+      c = strcmp (footprint, footprint_hash[i].footprint);
+      if (c < 0)
+	max = i;
+      else if (c > 0)
+	min = i;
+      else
+	{
+	  /* We want to return the first match, not just any match.  */
+	  while (i > 0
+		 && strcmp (footprint, footprint_hash[i-1].footprint) == 0)
+	    i--;
+	  return & footprint_hash[i];
+	}
+    }
+  return NULL;
+}
+
+/* Returns zero on success, non-zero on error.  */
+int
+LoadFootprintByName (BufferTypePtr Buffer, char *Footprint)
+{
+  int i;
+  FootprintHashEntry *fpe;
+  LibraryMenuType *menu;
+  LibraryEntryType *entry;
+
+  if (!footprint_hash)
+    make_footprint_hash ();
+
+  fpe = search_footprint_hash (Footprint);
+  if (!fpe)
+    {
+      Message("Unable to load footprint %s\n", Footprint);
+      return 1;
+    }
+
+  menu = & Library.Menu[fpe->menu_idx];
+  entry = & menu->Entry[fpe->entry_idx];
+
+  if (entry->Template == (char *) -1)
+    {
+      i = LoadElementToBuffer (Buffer, entry->AllocatedMemory, True);
+      return i ? 0 : 1;
+    }
+  else
+    {
+      char *args;
+
+      args = Concat("'", EMPTY (entry->Template), "' '",
+		    EMPTY (entry->Value), "' '", EMPTY (entry->Package), "'", NULL);
+      i = LoadElementToBuffer (Buffer, args, False);
+
+      free (args);
+      return i ? 0 : 1;
+    }
+
+#if 1
+  {
+    int j;
+    printf("Library path: %s\n", Settings.LibraryPath);
+    printf("Library tree: %s\n", Settings.LibraryTree);
+
+    printf("Library:\n");
+    for (i=0; i<Library.MenuN; i++)
+      {
+	printf("  [%02d] Name: %s\n", i, Library.Menu[i].Name);
+	printf("       Dir:  %s\n", Library.Menu[i].directory);
+	printf("       Sty:  %s\n", Library.Menu[i].Style);
+	for (j=0; j<Library.Menu[i].EntryN; j++)
+	  {
+	    printf("       [%02d] E: %s\n", j, Library.Menu[i].Entry[j].ListEntry);
+	    if (Library.Menu[i].Entry[j].Template == (char *) -1)
+	      printf("            A: %s\n", Library.Menu[i].Entry[j].AllocatedMemory);
+	    else
+	      {
+		printf("            T: %s\n", Library.Menu[i].Entry[j].Template);
+		printf("            P: %s\n", Library.Menu[i].Entry[j].Package);
+		printf("            V: %s\n", Library.Menu[i].Entry[j].Value);
+		printf("            D: %s\n", Library.Menu[i].Entry[j].Description);
+	      }
+	    if (j == 10)
+	      break;
+	  }
+      }
+  }
+#endif
+}
+
+
+static const char loadfootprint_syntax[] = "LoadFootprint(filename[,refdes,value])";
+
+static const char loadfootprint_help[] = "Loads a single footprint by name";
+
+/* %start-doc actions LoadFootprint
+
+Loads a single footprint by name, rather than by reference or through
+the library.  If a refdes and value are specified, those are inserted
+into the footprint as well.  The footprint remains in the paste buffer.
+
+%end-doc */
+
+int
+LoadFootprint (int argc, char **argv, int x, int y)
+{
+  char *name = ARG(0);
+  char *refdes = ARG(1);
+  char *value = ARG(2);
+  ElementTypePtr e;
+
+  if (!name)
+    AFAIL (loadfootprint);
+
+  if (LoadFootprintByName (PASTEBUFFER, name))
+    return 1;
+
+  if (PASTEBUFFER->Data->ElementN == 0)
+    {
+      Message("Footprint %s contains no elements", name);
+      return 1;
+    }
+  if (PASTEBUFFER->Data->ElementN > 1)
+    {
+      Message("Footprint %s contains multiple elements", name);
+      return 1;
+    }
+
+  e = & PASTEBUFFER->Data->Element[0];
+
+  if (e->Name[0].TextString)
+    free (e->Name[0].TextString);
+  e->Name[0].TextString = strdup (name);
+
+  if (e->Name[1].TextString)
+    free (e->Name[1].TextString);
+  e->Name[1].TextString = refdes ? strdup (refdes) : 0;
+
+  if (e->Name[2].TextString)
+    free (e->Name[2].TextString);
+  e->Name[2].TextString = value ? strdup (value) : 0;
+
+  return 0;
+}
+
+/*---------------------------------------------------------------------------
  *
  * break buffer element into pieces
  */
@@ -1369,7 +1652,9 @@ CopyObjectToBuffer (DataTypePtr Destination, DataTypePtr Src,
 
 HID_Action rotate_action_list[] = {
   {"FreeRotateBuffer", 0, ActionFreeRotateBuffer,
-   freerotatebuffer_syntax, freerotatebuffer_help}
+   freerotatebuffer_syntax, freerotatebuffer_help},
+  {"LoadFootprint", 0, LoadFootprint,
+   0,0}
 };
 
 REGISTER_ACTIONS (rotate_action_list)
diff --git a/src/buffer.h b/src/buffer.h
index 74c7399..07c21c1 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -52,4 +52,7 @@ void *MoveObjectToBuffer (DataTypePtr, DataTypePtr, int, void *, void *, void *)
 void *CopyObjectToBuffer (DataTypePtr, DataTypePtr, int,
 			  void *, void *, void *);
 
+/* This action is called from ActionElementAddIf() */
+int LoadFootprint (int argc, char **argv, int x, int y);
+
 #endif
diff --git a/src/change.c b/src/change.c
index 0599ee4..f67e682 100644
--- a/src/change.c
+++ b/src/change.c
@@ -982,6 +982,30 @@ ChangeLineName (LayerTypePtr Layer, LineTypePtr Line)
 /* ---------------------------------------------------------------------------
  * changes the layout-name of an element
  */
+
+char *
+ChangeElementText (PCBType *pcb, DataType *data, ElementTypePtr Element, int which, char *new_name)
+{
+  char *old = Element->Name[which].TextString;
+
+  if (pcb && which == NAME_INDEX (pcb))
+    EraseElementName (Element);
+
+  r_delete_entry (data->name_tree[which],
+		  (BoxType *) & Element->Name[which].TextString);
+
+  Element->Name[which].TextString = new_name;
+  SetTextBoundingBox (&PCB->Font, &Element->Name[which]);
+
+  r_insert_entry (data->name_tree[which],
+		  (BoxType *) & Element->Name[which].TextString, 0);
+
+  if (pcb && which == NAME_INDEX (pcb))
+    DrawElementName (Element, 0);
+
+  return old;
+}
+
 static void *
 ChangeElementName (ElementTypePtr Element)
 {
@@ -998,15 +1022,8 @@ ChangeElementName (ElementTypePtr Element)
 	  return ((char *) -1);
 	}
     }
-  EraseElementName (Element);
-  r_delete_entry (PCB->Data->name_tree[NAME_INDEX (PCB)],
-		  (BoxType *) & ELEMENT_TEXT (PCB, Element));
-  ELEMENT_NAME (PCB, Element) = NewName;
-  SetTextBoundingBox (&PCB->Font, &ELEMENT_TEXT (PCB, Element));
-  r_insert_entry (PCB->Data->name_tree[NAME_INDEX (PCB)],
-		  (BoxType *) & ELEMENT_TEXT (PCB, Element), 0);
-  DrawElementName (Element, 0);
-  return (old);
+
+  return ChangeElementText (PCB, PCB->Data, Element, NAME_INDEX (PCB), NewName);
 }
 
 /* ---------------------------------------------------------------------------
diff --git a/src/change.h b/src/change.h
index 7f43fba..bd7749a 100644
--- a/src/change.h
+++ b/src/change.h
@@ -107,4 +107,10 @@ void *ChangeObjectName (int, void *, void *, void *, char *);
 void *QueryInputAndChangeObjectName (int, void *, void *, void *);
 void ChangePCBSize (BDimension, BDimension);
 
+/* Change the specified text on an element, either on the board (give
+   PCB, PCB->Data) or in a buffer (give NULL, Buffer->Data).  The old
+   string is returned, and must be properly freed by the caller.  */
+char *ChangeElementText (PCBType *pcb, DataType *data, ElementTypePtr Element,
+			 int which, char *new_name);
+
 #endif
diff --git a/src/create.c b/src/create.c
index 500d0a0..518a35b 100644
--- a/src/create.c
+++ b/src/create.c
@@ -955,7 +955,7 @@ CreateNewNet (LibraryTypePtr lib, char *name, char *style)
   menu = GetLibraryMenuMemory (lib);
   menu->Name = MyStrdup (temp, "CreateNewNet()");
   menu->flag = 1;		/* net is enabled by default */
-  if (NSTRCMP ("(unknown)", style) == 0)
+  if (style == NULL || NSTRCMP ("(unknown)", style) == 0)
     menu->Style = NULL;
   else
     menu->Style = MyStrdup (style, "CreateNewNet()");
diff --git a/src/file.c b/src/file.c
index 4cef71f..fd18340 100644
--- a/src/file.c
+++ b/src/file.c
@@ -197,7 +197,7 @@ sort_library (LibraryTypePtr lib)
 	   lib->Menu[i].EntryN, sizeof (lib->Menu[i].Entry[0]), netnode_sort);
 }
 
-static void
+void
 sort_netlist ()
 {
   netlist_sort_offset = 2;
diff --git a/src/global.h b/src/global.h
index fa9f55e..1462e50 100644
--- a/src/global.h
+++ b/src/global.h
@@ -50,10 +50,12 @@
 #include <ctype.h>
 #include <sys/types.h>
 
+/* Forward declarations for structures the HIDs need.  */
 typedef struct BoxType BoxType, *BoxTypePtr;
 typedef struct polygon_st PolygonType, *PolygonTypePtr;
 typedef struct drc_violation_st DrcViolationType, *DrcViolationTypePtr;
 typedef struct rtree rtree_t;
+typedef struct AttributeListType AttributeListType, *AttributeListTypePtr;
 
 #include "hid.h"
 #include "polyarea.h"
@@ -192,11 +194,11 @@ typedef struct
   char *value;
 } AttributeType, *AttributeTypePtr;
 
-typedef struct
+struct AttributeListType
 {
   int Number, Max;
   AttributeType *List;
-} AttributeListType, *AttributeListTypePtr;
+};
 
 /* ---------------------------------------------------------------------------
  * the basic object types supported by PCB
diff --git a/src/gpcb-menu.res b/src/gpcb-menu.res
index b844f56..11251b2 100644
--- a/src/gpcb-menu.res
+++ b/src/gpcb-menu.res
@@ -58,6 +58,7 @@ MainMenu =
    -
    {"Revert" Load(Revert,none) tip="Revert to the layout stored on disk"}
    -
+   {"Import Schematics" Import() }
    {"Load layout" Load(Layout) tip="Load a layout from a file"}
    {"Load element data to paste-buffer" PasteBuffer(Clear) Load(ElementTobuffer)}
    {"Load layout data to paste-buffer" PasteBuffer(Clear) Load(LayoutTobuffer)}
@@ -106,6 +107,11 @@ MainMenu =
      {"layout" ChangeName(Layout)}
      {"active layer" ChangeName(Layer)}
    }
+   {"Edit attributes of"
+    {"Layout" Attributes(Layout)}
+    {"CurrentLayer" Attributes(Layer)}
+    {"Element" Attributes(Element)}
+   }
    -
    {"Move to current layer" MoveToCurrentLayer(Object) a={"M" "<Key>m"}}
    {"Move selected to current layer" MoveToCurrentLayer(Selected) a={"Shift-M" "Shift<Key>m"}}
diff --git a/src/hid.h b/src/hid.h
index 7966346..1e3fb86 100644
--- a/src/hid.h
+++ b/src/hid.h
@@ -554,6 +554,8 @@ typedef enum
 
     HID_DRC_GUI *drc_gui;
 
+    void (*edit_attributes) (char *owner, AttributeListType *attrlist_);
+
   } HID;
 
 /* Call this as soon as possible from main().  No other HID calls are
diff --git a/src/hid/batch/batch.c b/src/hid/batch/batch.c
index 407bc3a..c10b280 100644
--- a/src/hid/batch/batch.c
+++ b/src/hid/batch/batch.c
@@ -515,7 +515,8 @@ HID batch_gui = {
   batch_show_item,
   batch_beep,
   batch_progress,
-  0 /* batch_drc_gui */
+  0 /* batch_drc_gui */ ,
+  0 /* batch_edit_attributes */
 };
 
 #include "dolists.h"
diff --git a/src/hid/bom/bom.c b/src/hid/bom/bom.c
index 08d4328..e593bdd 100644
--- a/src/hid/bom/bom.c
+++ b/src/hid/bom/bom.c
@@ -589,6 +589,7 @@ HID bom_hid = {
   0,				/* bom_beep */
   0,				/* bom_progress */
   0,				/* bom_drc_gui */
+  0,				/* bom_edit_attributes */
 };
 
 void
diff --git a/src/hid/common/hidnogui.c b/src/hid/common/hidnogui.c
index 4d6baa0..51c95cb 100644
--- a/src/hid/common/hidnogui.c
+++ b/src/hid/common/hidnogui.c
@@ -403,7 +403,8 @@ HID hid_nogui = {
   nogui_show_item,
   nogui_beep,
   nogui_progress,
-  0 /* nogui_drc_gui */
+  0 /* nogui_drc_gui */ ,
+  0 /* edit_attributes */
 };
 
 #define AD(x) if (!d->x) d->x = s->x
@@ -458,4 +459,5 @@ apply_default_hid (HID * d, HID * s)
   AD (beep);
   AD (progress);
   AD (drc_gui);
+  AD (edit_attributes);
 }
diff --git a/src/hid/gtk/gtkhid-main.c b/src/hid/gtk/gtkhid-main.c
index 64d8622..fd399ce 100644
--- a/src/hid/gtk/gtkhid-main.c
+++ b/src/hid/gtk/gtkhid-main.c
@@ -890,6 +890,223 @@ ghid_progress (int so_far, int total, const char *message)
 }
 
 /* ---------------------------------------------------------------------- */
+
+
+typedef struct {
+  GtkWidget *del;
+  GtkWidget *w_name;
+  GtkWidget *w_value;
+} AttrRow;
+
+static AttrRow *attr_row = 0;
+static int attr_num_rows = 0;
+static int attr_max_rows = 0;
+static AttributeListType *attributes_list;
+static GtkWidget *attributes_dialog, *attr_table;
+
+static void attributes_delete_callback (GtkWidget *w, void *v);
+
+#define GA_RESPONSE_REVERT	1
+#define GA_RESPONSE_NEW		2
+
+static void
+ghid_attr_set_table_size ()
+{
+  gtk_table_resize (GTK_TABLE (attr_table), attr_num_rows > 0 ? attr_num_rows : 1, 3);
+}
+
+static void
+ghid_attributes_need_rows (int new_max)
+{
+  if (attr_max_rows < new_max)
+    {
+      if (attr_row)
+	attr_row = (AttrRow *) realloc (attr_row, new_max * sizeof(AttrRow));
+      else
+	attr_row = (AttrRow *) malloc (new_max * sizeof(AttrRow));
+    }
+  while (attr_max_rows < new_max)
+    {
+      /* add [attr_max_rows] */
+      attr_row[attr_max_rows].del = gtk_button_new_with_label ("del");
+      gtk_table_attach (GTK_TABLE (attr_table), attr_row[attr_max_rows].del,
+			0, 1,
+			attr_max_rows, attr_max_rows+1,
+			GTK_FILL | GTK_EXPAND,
+			GTK_FILL,
+			0, 0);
+      g_signal_connect (G_OBJECT (attr_row[attr_max_rows].del), "clicked",
+			G_CALLBACK (attributes_delete_callback), (void *)attr_max_rows);
+
+      attr_row[attr_max_rows].w_name = gtk_entry_new ();
+      gtk_table_attach (GTK_TABLE (attr_table), attr_row[attr_max_rows].w_name,
+			1, 2,
+			attr_max_rows, attr_max_rows+1,
+			GTK_FILL | GTK_EXPAND,
+			GTK_FILL,
+			0, 0);
+
+      attr_row[attr_max_rows].w_value = gtk_entry_new ();
+      gtk_table_attach (GTK_TABLE (attr_table), attr_row[attr_max_rows].w_value,
+			2, 3,
+			attr_max_rows, attr_max_rows+1,
+			GTK_FILL | GTK_EXPAND,
+			GTK_FILL,
+			0, 0);
+
+      attr_max_rows ++;
+    }
+
+  /* Manage any previously unused rows we now need to show.  */
+  while (attr_num_rows < new_max)
+    {
+      /* manage attr_num_rows */
+      gtk_widget_show (attr_row[attr_num_rows].del);
+      gtk_widget_show (attr_row[attr_num_rows].w_name);
+      gtk_widget_show (attr_row[attr_num_rows].w_value);
+      attr_num_rows ++;
+    }
+}
+
+static void
+ghid_attributes_revert ()
+{
+  int i;
+
+  ghid_attributes_need_rows (attributes_list->Number);
+
+  /* Unmanage any previously used rows we don't need.  */
+  while (attr_num_rows > attributes_list->Number)
+    {
+      attr_num_rows --;
+      gtk_widget_hide (attr_row[attr_num_rows].del);
+      gtk_widget_hide (attr_row[attr_num_rows].w_name);
+      gtk_widget_hide (attr_row[attr_num_rows].w_value);
+    }
+
+  /* Fill in values */
+  for (i=0; i<attributes_list->Number; i++)
+    {
+      /* create row [i] */
+      gtk_entry_set_text (GTK_ENTRY (attr_row[i].w_name), attributes_list->List[i].name);
+      gtk_entry_set_text (GTK_ENTRY (attr_row[i].w_value), attributes_list->List[i].value);
+#if 0
+#endif
+    }
+  ghid_attr_set_table_size ();
+}
+
+static void
+attributes_delete_callback (GtkWidget *w, void *v)
+{
+  int i, n;
+  GtkWidget *wn, *wv;
+
+  n = (int) v;
+
+  for (i=n; i<attr_num_rows-1; i++)
+    {
+      gtk_entry_set_text (GTK_ENTRY (attr_row[i].w_name),
+			  gtk_entry_get_text (GTK_ENTRY (attr_row[i+1].w_name)));
+      gtk_entry_set_text (GTK_ENTRY (attr_row[i].w_value),
+			  gtk_entry_get_text (GTK_ENTRY (attr_row[i+1].w_value)));
+    }
+  attr_num_rows --;
+
+  gtk_widget_hide (attr_row[attr_num_rows].del);
+  gtk_widget_hide (attr_row[attr_num_rows].w_name);
+  gtk_widget_hide (attr_row[attr_num_rows].w_value);
+
+  ghid_attr_set_table_size ();
+}
+
+static void
+ghid_attributes (char *owner, AttributeListType *attrs)
+{
+  int response;
+
+  attributes_list = attrs;
+
+  attr_max_rows = 0;
+  attr_num_rows = 0;
+
+  attributes_dialog = gtk_dialog_new_with_buttons (owner,
+						   GTK_WINDOW (ghid_port.top_window),
+						   GTK_DIALOG_MODAL,
+						   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+						   "Revert", GA_RESPONSE_REVERT,
+						   "New", GA_RESPONSE_NEW,
+						   GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
+
+  attr_table = gtk_table_new (attrs->Number, 3, 0);
+
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (attributes_dialog)->vbox), attr_table, FALSE, FALSE, 0);
+    
+  gtk_widget_show (attr_table);
+
+  ghid_attributes_revert ();
+
+  while (1)
+    {
+      response = gtk_dialog_run (GTK_DIALOG (attributes_dialog));
+
+      if (response == GTK_RESPONSE_CANCEL)
+	break;
+
+      if (response == GTK_RESPONSE_OK)
+	{
+	  int i;
+	  /* Copy the values back */
+	  for (i=0; i<attributes_list->Number; i++)
+	    {
+	      if (attributes_list->List[i].name)
+		free (attributes_list->List[i].name);
+	      if (attributes_list->List[i].value)
+		free (attributes_list->List[i].value);
+	    }
+	  if (attributes_list->Max < attr_num_rows)
+	    {
+	      int sz = attr_num_rows * sizeof (AttributeType);
+	      if (attributes_list->List == NULL)
+		attributes_list->List = (AttributeType *) malloc (sz);
+	      else
+		attributes_list->List = (AttributeType *) realloc (attributes_list->List, sz);
+	      attributes_list->Max = attr_num_rows;
+	    }
+	  for (i=0; i<attr_num_rows; i++)
+	    {
+	      attributes_list->List[i].name = strdup (gtk_entry_get_text (GTK_ENTRY (attr_row[i].w_name)));
+	      attributes_list->List[i].value = strdup (gtk_entry_get_text (GTK_ENTRY (attr_row[i].w_value)));
+	      attributes_list->Number = attr_num_rows;
+	    }
+
+	  break;
+	}
+
+      if (response == GA_RESPONSE_REVERT)
+	{
+	  /* Revert */
+	  ghid_attributes_revert ();
+	}
+
+      if (response == GA_RESPONSE_NEW)
+	{
+	  ghid_attributes_need_rows (attr_num_rows + 1); /* also bumps attr_num_rows */
+
+	  gtk_entry_set_text (attr_row[attr_num_rows-1].w_name, "");
+	  gtk_entry_set_text (attr_row[attr_num_rows-1].w_value, "");
+
+	  ghid_attr_set_table_size ();
+	}
+    }
+
+  gtk_widget_destroy (attributes_dialog);
+  free (attr_row);
+  attr_row = NULL;
+}
+
+/* ---------------------------------------------------------------------- */
+
 HID_DRC_GUI ghid_drc_gui = {
   1,				/* log_drc_overview */
   0,				/* log_drc_details */
@@ -959,7 +1176,8 @@ HID ghid_hid = {
   ghid_show_item,
   ghid_beep,
   ghid_progress,
-  &ghid_drc_gui
+  &ghid_drc_gui,
+  ghid_attributes
 };
 
 /* ------------------------------------------------------------ 
diff --git a/src/hid/lesstif/dialogs.c b/src/hid/lesstif/dialogs.c
index 61b8097..213c5f2 100644
--- a/src/hid/lesstif/dialogs.c
+++ b/src/hid/lesstif/dialogs.c
@@ -1664,6 +1664,352 @@ EditLayerGroups (int argc, char **argv, int x, int y)
 
 /* ------------------------------------------------------------ */
 
+typedef struct {
+  Widget del;
+  Widget w_name;
+  Widget w_value;
+} AttrRow;
+
+static AttrRow *attr_row = 0;
+static int attr_num_rows = 0;
+static int attr_max_rows = 0;
+static Widget attr_dialog = NULL, f_top;
+static AttributeListType *attributes_list;
+
+static void attributes_delete_callback (Widget w, void *v, void *cbs);
+
+static void
+fiddle_with_bb_layout ()
+{
+  int i;
+  int max_height = 0;
+  int max_del_width = 0;
+  int max_name_width = 0;
+  int max_value_width = 0;
+  short ncolumns = 20;
+  short vcolumns = 20;
+
+  for (i=0; i<attr_num_rows; i++)
+    {
+      String v;
+
+      n = 0;
+      stdarg (XmNvalue, &v);
+      XtGetValues (attr_row[i].w_name, args, n);
+      if (ncolumns < strlen (v))
+	ncolumns = strlen (v);
+
+      n = 0;
+      stdarg (XmNvalue, &v);
+      XtGetValues (attr_row[i].w_value, args, n);
+      if (vcolumns < strlen (v))
+	vcolumns = strlen (v);
+    }
+
+  for (i=0; i<attr_num_rows; i++)
+    {
+      n = 0;
+      stdarg (XmNcolumns, ncolumns);
+      XtSetValues (attr_row[i].w_name, args, n);
+
+      n = 0;
+      stdarg (XmNcolumns, vcolumns);
+      XtSetValues (attr_row[i].w_value, args, n);
+    }
+
+  for (i=0; i<attr_num_rows; i++)
+    {
+      Dimension w, h;
+      n = 0;
+      stdarg (XmNwidth, &w);
+      stdarg (XmNheight, &h);
+
+      XtGetValues (attr_row[i].del, args, n);
+      if (max_height < h)
+	max_height = h;
+      if (max_del_width < w)
+	max_del_width = w;
+
+      XtGetValues (attr_row[i].w_name, args, n);
+      if (max_height < h)
+	max_height = h;
+      if (max_name_width < w)
+	max_name_width = w;
+
+      XtGetValues (attr_row[i].w_value, args, n);
+      if (max_height < h)
+	max_height = h;
+      if (max_value_width < w)
+	max_value_width = w;
+    }
+
+  for (i=0; i<attr_num_rows; i++)
+    {
+      n = 0;
+      stdarg (XmNx, 0);
+      stdarg (XmNy, i * max_height);
+      stdarg (XmNwidth, max_del_width);
+      stdarg (XmNheight, max_height);
+      XtSetValues (attr_row[i].del, args, n);
+
+      n = 0;
+      stdarg (XmNx, max_del_width);
+      stdarg (XmNy, i * max_height);
+      stdarg (XmNwidth, max_name_width);
+      stdarg (XmNheight, max_height);
+      XtSetValues (attr_row[i].w_name, args, n);
+
+      n = 0;
+      stdarg (XmNx, max_del_width + max_name_width);
+      stdarg (XmNy, i * max_height);
+      stdarg (XmNwidth, max_value_width);
+      stdarg (XmNheight, max_height);
+      XtSetValues (attr_row[i].w_value, args, n);
+    }
+
+  n = 0;
+  stdarg (XmNwidth, max_del_width + max_name_width + max_value_width + 1);
+  stdarg (XmNheight, max_height * attr_num_rows + 1);
+  XtSetValues (f_top, args, n);
+}
+
+static void
+lesstif_attributes_need_rows (int new_max)
+{
+  if (attr_max_rows < new_max)
+    {
+      if (attr_row)
+	attr_row = (AttrRow *) realloc (attr_row, new_max * sizeof(AttrRow));
+      else
+	attr_row = (AttrRow *) malloc (new_max * sizeof(AttrRow));
+    }
+
+  while (attr_max_rows < new_max)
+    {
+      n = 0;
+      attr_row[attr_max_rows].del = XmCreatePushButton (f_top, "del", args, n);
+      XtManageChild (attr_row[attr_max_rows].del);
+      XtAddCallback (attr_row[attr_max_rows].del, XmNactivateCallback,
+		     (XtCallbackProc) attributes_delete_callback,
+		     (XtPointer) attr_max_rows);
+
+      n = 0;
+      stdarg (XmNresizeWidth, True);
+      attr_row[attr_max_rows].w_name = XmCreateTextField (f_top, "name", args, n);
+      XtManageChild (attr_row[attr_max_rows].w_name);
+      XtAddCallback (attr_row[attr_max_rows].w_name, XmNvalueChangedCallback,
+		     (XtCallbackProc) fiddle_with_bb_layout, NULL);
+
+      n = 0;
+      stdarg (XmNresizeWidth, True);
+      attr_row[attr_max_rows].w_value = XmCreateTextField (f_top, "value", args, n);
+      XtManageChild (attr_row[attr_max_rows].w_value);
+      XtAddCallback (attr_row[attr_max_rows].w_value, XmNvalueChangedCallback,
+		     (XtCallbackProc) fiddle_with_bb_layout, NULL);
+
+      attr_max_rows ++;
+    }
+
+  /* Manage any previously unused rows we now need to show.  */
+  while (attr_num_rows < new_max)
+    {
+      XtManageChild (attr_row[attr_num_rows].del);
+      XtManageChild (attr_row[attr_num_rows].w_name);
+      XtManageChild (attr_row[attr_num_rows].w_value);
+      attr_num_rows ++;
+    }
+}
+
+static void
+lesstif_attributes_revert ()
+{
+  int i;
+
+  lesstif_attributes_need_rows (attributes_list->Number);
+
+  /* Unmanage any previously used rows we don't need.  */
+  while (attr_num_rows > attributes_list->Number)
+    {
+      attr_num_rows --;
+      XtUnmanageChild (attr_row[attr_num_rows].del);
+      XtUnmanageChild (attr_row[attr_num_rows].w_name);
+      XtUnmanageChild (attr_row[attr_num_rows].w_value);
+    }
+
+  /* Fill in values */
+  for (i=0; i<attributes_list->Number; i++)
+    {
+      XmTextFieldSetString (attr_row[i].w_name, attributes_list->List[i].name);
+      XmTextFieldSetString (attr_row[i].w_value, attributes_list->List[i].value);
+    }
+
+  fiddle_with_bb_layout ();
+}
+
+static void
+attributes_new_callback (Widget w, void *v, void *cbs)
+{
+  lesstif_attributes_need_rows (attr_num_rows + 1); /* also bumps attr_num_rows */
+  XmTextFieldSetString (attr_row[attr_num_rows-1].w_name, "");
+  XmTextFieldSetString (attr_row[attr_num_rows-1].w_value, "");
+
+  fiddle_with_bb_layout ();
+}
+
+static void
+attributes_delete_callback (Widget w, void *v, void *cbs)
+{
+  int i, n;
+  Widget wn, wv;
+
+  n = (int) v;
+
+  wn = attr_row[n].w_name;
+  wv = attr_row[n].w_value;
+
+  for (i=n; i<attr_num_rows-1; i++)
+    {
+      attr_row[i].w_name = attr_row[i+1].w_name;
+      attr_row[i].w_value = attr_row[i+1].w_value;
+    }
+  attr_row[attr_num_rows-1].w_name = wn;
+  attr_row[attr_num_rows-1].w_value = wv;
+  attr_num_rows --;
+
+  XtUnmanageChild (wn);
+  XtUnmanageChild (wv);
+
+  fiddle_with_bb_layout ();
+}
+
+static void
+attributes_revert_callback (Widget w, void *v, void *cbs)
+{
+  lesstif_attributes_revert ();
+}
+
+void
+lesstif_attributes_dialog (char *owner, AttributeListType *attrs_list)
+{
+  Widget bform, sw, b_ok, b_cancel, b_revert, b_new;
+  Widget sep;
+
+  if (attr_dialog == NULL)
+    {
+      n = 0;
+      stdarg (XmNautoUnmanage, False);
+      stdarg (XmNtitle, owner);
+      stdarg (XmNwidth, 400);
+      stdarg (XmNheight, 300);
+      attr_dialog = XmCreateFormDialog (mainwind, "attributes", args, n);
+
+      n = 0;
+      stdarg (XmNrightAttachment, XmATTACH_FORM);
+      stdarg (XmNbottomAttachment, XmATTACH_FORM);
+      stdarg (XmNorientation, XmHORIZONTAL);
+      stdarg (XmNentryAlignment, XmALIGNMENT_CENTER);
+      stdarg (XmNpacking, XmPACK_COLUMN);
+      bform = XmCreateRowColumn (attr_dialog, "attributes", args, n);
+      XtManageChild (bform);
+
+      n = 0;
+      b_ok = XmCreatePushButton (bform, "OK", args, n);
+      XtManageChild (b_ok);
+      XtAddCallback (b_ok, XmNactivateCallback,
+		     (XtCallbackProc) dialog_callback,
+		     (XtPointer) 0);
+
+      n = 0;
+      b_new = XmCreatePushButton (bform, "New", args, n);
+      XtManageChild (b_new);
+      XtAddCallback (b_new, XmNactivateCallback,
+		     (XtCallbackProc) attributes_new_callback,
+		     NULL);
+
+      n = 0;
+      b_revert = XmCreatePushButton (bform, "Revert", args, n);
+      XtManageChild (b_revert);
+      XtAddCallback (b_revert, XmNactivateCallback,
+		     (XtCallbackProc) attributes_revert_callback,
+		     NULL);
+
+      n = 0;
+      b_cancel = XmCreatePushButton (bform, "Cancel", args, n);
+      XtManageChild (b_cancel);
+      XtAddCallback (b_cancel, XmNactivateCallback,
+		     (XtCallbackProc) dialog_callback,
+		     (XtPointer) 1);
+
+      n = 0;
+      stdarg (XmNleftAttachment, XmATTACH_FORM);
+      stdarg (XmNrightAttachment, XmATTACH_FORM);
+      stdarg (XmNbottomAttachment, XmATTACH_WIDGET);
+      stdarg (XmNbottomWidget, bform);
+      sep = XmCreateSeparator (attr_dialog, "attributes", args, n);
+      XtManageChild (sep);
+
+      n = 0;
+      stdarg (XmNtopAttachment, XmATTACH_FORM);
+      stdarg (XmNleftAttachment, XmATTACH_FORM);
+      stdarg (XmNrightAttachment, XmATTACH_FORM);
+      stdarg (XmNbottomAttachment, XmATTACH_WIDGET);
+      stdarg (XmNbottomWidget, sep);
+      stdarg (XmNscrollingPolicy, XmAUTOMATIC);
+      sw = XmCreateScrolledWindow (attr_dialog, "attributes", args, n);
+      XtManageChild (sw);
+
+      n = 0;
+      stdarg (XmNmarginHeight, 0);
+      stdarg (XmNmarginWidth, 0);
+      f_top = XmCreateBulletinBoard (sw, "f_top", args, n);
+      XtManageChild (f_top);
+    }
+  else
+    {
+      n = 0;
+      stdarg (XmNtitle, owner);
+      XtSetValues (XtParent (attr_dialog), args, n);
+    }
+
+  attributes_list = attrs_list;
+  lesstif_attributes_revert ();
+
+  fiddle_with_bb_layout ();
+
+  if (wait_for_dialog (attr_dialog) == 0)
+    {
+      int i;
+      /* Copy the values back */
+      for (i=0; i<attributes_list->Number; i++)
+	{
+	  if (attributes_list->List[i].name)
+	    free (attributes_list->List[i].name);
+	  if (attributes_list->List[i].value)
+	    free (attributes_list->List[i].value);
+	}
+      if (attributes_list->Max < attr_num_rows)
+	{
+	  int sz = attr_num_rows * sizeof (AttributeType);
+	  if (attributes_list->List == NULL)
+	    attributes_list->List = (AttributeType *) malloc (sz);
+	  else
+	    attributes_list->List = (AttributeType *) realloc (attributes_list->List, sz);
+	  attributes_list->Max = attr_num_rows;
+	}
+      for (i=0; i<attr_num_rows; i++)
+	{
+	  attributes_list->List[i].name = strdup (XmTextFieldGetString (attr_row[i].w_name));
+	  attributes_list->List[i].value = strdup (XmTextFieldGetString (attr_row[i].w_value));
+	  attributes_list->Number = attr_num_rows;
+	}
+    }
+
+  return;
+}
+
+
+/* ------------------------------------------------------------ */
+
 HID_Action lesstif_dialog_action_list[] = {
   {"Load", 0, Load,
    load_help, load_syntax},
diff --git a/src/hid/lesstif/lesstif.h b/src/hid/lesstif/lesstif.h
index fe31371..54e4ced 100644
--- a/src/hid/lesstif/lesstif.h
+++ b/src/hid/lesstif/lesstif.h
@@ -55,6 +55,7 @@ extern char *lesstif_fileselect (const char *, const char *,
 				 char *, char *,
 				 const char *, int);
 extern void lesstif_log (const char *fmt, ...);
+extern void lesstif_attributes_dialog (char *, AttributeListType *);
 
 #define need_idle_proc lesstif_need_idle_proc
 #define show_crosshair lesstif_show_crosshair
diff --git a/src/hid/lesstif/main.c b/src/hid/lesstif/main.c
index 6ff7e74..6dfc513 100644
--- a/src/hid/lesstif/main.c
+++ b/src/hid/lesstif/main.c
@@ -3116,6 +3116,8 @@ lesstif_set_line_cap (hidGC gc, EndCapStyle style)
 static void
 lesstif_set_line_width (hidGC gc, int width)
 {
+  if (width == 1)
+    width = 0;
   gc->width = width;
 }
 
@@ -3819,7 +3821,8 @@ HID lesstif_gui = {
   lesstif_show_item,
   lesstif_beep,
   lesstif_progress,
-  0 /* lesstif_drc_gui */
+  0, /* lesstif_drc_gui */
+  lesstif_attributes_dialog
 };
 
 #include "dolists.h"
diff --git a/src/hid/lesstif/xincludes.h b/src/hid/lesstif/xincludes.h
index 44c25e9..7245a77 100644
--- a/src/hid/lesstif/xincludes.h
+++ b/src/hid/lesstif/xincludes.h
@@ -30,6 +30,7 @@
 #include <Xm/RowColumn.h>
 #include <Xm/Scale.h>
 #include <Xm/ScrollBar.h>
+#include <Xm/ScrolledW.h>
 #include <Xm/Separator.h>
 #include <Xm/Text.h>
 #include <Xm/TextF.h>
diff --git a/src/hid/lpr/lpr.c b/src/hid/lpr/lpr.c
index cf35d8b..e47c40f 100644
--- a/src/hid/lpr/lpr.c
+++ b/src/hid/lpr/lpr.c
@@ -165,7 +165,8 @@ HID lpr_hid = {
   0 /* lpr_show_item */ ,
   0 /* lpr_beep */ ,
   0 /* lpr_progress */ ,
-  0 /* lpr_drc_gui */
+  0 /* lpr_drc_gui */ ,
+  0 /* lpr_edit_attributes */
 };
 
 void
diff --git a/src/hid/nelma/nelma.c b/src/hid/nelma/nelma.c
index ee3a896..dc1cee5 100644
--- a/src/hid/nelma/nelma.c
+++ b/src/hid/nelma/nelma.c
@@ -1100,7 +1100,8 @@ HID             nelma_hid = {
 	0 /* nelma_show_item */ ,
 	0 /* nelma_beep */ ,
 	0 /* nelma_progress */ ,
-	0 /* nelma_drc_gui */
+	0 /* nelma_drc_gui */ ,
+	0 /* nelma_edit_attributes */
 };
 
 #include "dolists.h"
diff --git a/src/hid/png/png.c b/src/hid/png/png.c
index 2b4968b..b819533 100644
--- a/src/hid/png/png.c
+++ b/src/hid/png/png.c
@@ -1512,7 +1512,8 @@ HID png_hid = {
   0 /* png_show_item */ ,
   0 /* png_beep */ ,
   0 /* png_progress */ ,
-  0 /* png_drc_gui */
+  0 /* png_drc_gui */ ,
+  0 /* png_edit_attributes */
 };
 
 #include "dolists.h"
diff --git a/src/hid/ps/eps.c b/src/hid/ps/eps.c
index 2115b47..b0e7a7d 100644
--- a/src/hid/ps/eps.c
+++ b/src/hid/ps/eps.c
@@ -662,7 +662,8 @@ static HID eps_hid = {
   0 /* eps_show_item */ ,
   0 /* eps_beep */ ,
   0 /* eps_progress */ ,
-  0 /* eps_drc_gui */
+  0 /* eps_drc_gui */ ,
+  0 /* eps_edit_attributes */
 };
 
 void
diff --git a/src/hid/ps/ps.c b/src/hid/ps/ps.c
index e648fa1..3a92dc3 100644
--- a/src/hid/ps/ps.c
+++ b/src/hid/ps/ps.c
@@ -1230,7 +1230,8 @@ HID ps_hid = {
   0 /* ps_show_item */ ,
   0 /* ps_beep */ ,
   0 /* ps_progress */ ,
-  0 /* ps_drc_gui */
+  0 /* ps_drc_gui */ ,
+  0 /* ps_edit_attributes */
 };
 
 #include "dolists.h"
diff --git a/src/misc.c b/src/misc.c
index 2ba104c..1938dea 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -1882,6 +1882,25 @@ pcb_author (void)
 }
 
 
+char *
+AttributeGetFromList (AttributeListType *list, char *name)
+{
+  int i;
+  for (i=0; i<list->Number; i++)
+    if (strcmp (name, list->List[i].name) == 0)
+      return list->List[i].value;
+  return NULL;
+}
+
+int
+AttributePutToList (AttributeListType *list, char *name, char *value, int replace)
+{
+  /* Not implemented yet.  */
+  abort ();
+}
+
+
+
 const char *
 c_dtostr (double d)
 {
@@ -2080,3 +2099,92 @@ GetInfoString (void)
 
   return info.Data;
 }
+
+/* ---------------------------------------------------------------------------
+ * Returns a best guess about the orientation of an element.  The
+ * value corresponds to the rotation; a difference is the right value
+ * to pass to RotateElementLowLevel.  However, the actual value is no
+ * indication of absolute rotation; only relative rotation is
+ * meaningful.
+ */
+
+int 
+ElementOrientation (ElementType *e)
+{
+  int pin1x, pin1y, pin2x, pin2y, dx, dy;
+  int found_pin1 = 0;
+  int found_pin2 = 0;
+
+  PIN_LOOP (e);
+  {
+    if (NSTRCMP (pin->Number, "1") == 0)
+      {
+	pin1x = pin->X;
+	pin1y = pin->Y;
+	found_pin1 = 1;
+      }
+    else if (NSTRCMP (pin->Number, "2") == 0)
+      {
+	pin2x = pin->X;
+	pin2y = pin->Y;
+	found_pin2 = 1;
+      }
+  }
+  END_LOOP;
+
+  PAD_LOOP (e);
+  {
+    if (NSTRCMP (pad->Number, "1") == 0)
+      {
+	pin1x = (pad->Point1.X + pad->Point2.X) / 2;
+	pin1y = (pad->Point1.Y + pad->Point2.Y) / 2;
+	found_pin1 = 1;
+      }
+    else if (NSTRCMP (pad->Number, "2") == 0)
+      {
+	pin2x = (pad->Point1.X + pad->Point2.X) / 2;
+	pin2y = (pad->Point1.Y + pad->Point2.Y) / 2;
+	found_pin2 = 1;
+      }
+  }
+  END_LOOP;
+
+  if (found_pin1 && found_pin2)
+    {
+      dx = pin2x - pin1x;
+      dy = pin2y - pin1y;
+    }
+  else if (found_pin1 && (pin1x || pin1y))
+    {
+      dx = pin1x;
+      dy = pin1y;
+    }
+  else if (found_pin2 && (pin2x || pin2y))
+    {
+      dx = pin2x;
+      dy = pin2y;
+    }
+  else
+    return 0;
+
+  if (abs(dx) > abs(dy))
+    return dx > 0 ? 0 : 2;
+  return dy > 0 ? 3 : 1;
+}
+
+int
+ActionListRotations(int argc, char **argv, int x, int y)
+{
+  ELEMENT_LOOP (PCB->Data);
+  {
+    printf("%d %s\n", ElementOrientation(element), NAMEONPCB_NAME(element));
+  }
+  END_LOOP;
+}
+
+HID_Action misc_action_list[] = {
+  {"ListRotations", 0, ActionListRotations,
+   0,0},
+};
+
+REGISTER_ACTIONS (misc_action_list)
diff --git a/src/misc.h b/src/misc.h
index 58e75dc..f814784 100644
--- a/src/misc.h
+++ b/src/misc.h
@@ -80,6 +80,18 @@ char *Concat (const char *, ...);	/* end with NULL */
 
 char *pcb_author ();
 
+/* Returns NULL if the name isn't found, else the value for that named
+   attribute.  */
+char *AttributeGetFromList (AttributeListType *list, char *name);
+/* Adds an attribute to the list.  If the attribute already exists,
+   whether it's replaced or a second copy added depends on
+   REPLACE.  */
+int AttributePutToList (AttributeListType *list, char *name, char *value, int replace);
+/* Simplistic version: Takes a pointer to an object, looks up attributes in it.  */
+#define AttributeGet(OBJ,name) AttributeGetFromList (&(OBJ->Attributes), name)
+/* Simplistic version: Takes a pointer to an object, sets attributes in it.  */
+#define AttributePut(OBJ,name,value) AttributePutToList (&(OBJ->Attributes), name, value, 1)
+
 /* For passing modified flags to other functions. */
 FlagType MakeFlags (unsigned int);
 FlagType OldFlags (unsigned int);
@@ -103,4 +115,8 @@ extern const char *c_dtostr(double d);
 /* Returns a string with info about this copy of pcb. */
 char * GetInfoString (void);
 
+/* Return a relative rotation for an element, useful only for
+   comparing two similar footprints.  */
+int ElementOrientation (ElementType *e);
+
 #endif
diff --git a/src/netlist.c b/src/netlist.c
index d9592b0..78c8c3d 100644
--- a/src/netlist.c
+++ b/src/netlist.c
@@ -57,6 +57,7 @@
 #include "rats.h"
 #include "set.h"
 #include "vendor.h"
+#include "create.h"
 
 #ifdef HAVE_REGCOMP
 #undef HAVE_RE_COMP
@@ -178,9 +179,98 @@ netlist_norats (LibraryMenuType * net, LibraryEntryType * pin)
   hid_action ("NetlistChanged");
 }
 
+/* The primary purpose of this action is to remove the netlist
+   completely so that a new one can be loaded, usually via a gsch2pcb
+   style script.  */
+static void
+netlist_clear (LibraryMenuType * net, LibraryEntryType * pin)
+{
+  LibraryType *netlist = &PCB->NetlistLib;
+  int ni, pi;
+
+  if (net == 0)
+    {
+      /* Clear the entire netlist. */
+      FreeLibraryMemory (&PCB->NetlistLib);
+    }
+  else if (pin == 0)
+    {
+      /* Remove a net from the netlist. */
+      ni = net - netlist->Menu;
+      if (ni >= 0 && ni < netlist->MenuN)
+	{
+	  /* if there is exactly one item, MenuN is 1 and ni is 0 */
+	  if (netlist->MenuN - ni > 1)
+	    memmove (net, net+1, (netlist->MenuN - ni - 1) * sizeof (*net));
+	  netlist->MenuN --;
+	}
+    }
+  else
+    {
+      /* Remove a pin from the given net.  Note that this may leave an
+	 empty net, which is different than removing the net
+	 (above).  */
+      pi = pin - net->Entry;
+      if (pi >= 0 && pi < net->EntryN)
+	{
+	  /* if there is exactly one item, MenuN is 1 and ni is 0 */
+	  if (net->EntryN - pi > 1)
+	    memmove (pin, pin+1, (net->EntryN - pi - 1) * sizeof (*pin));
+	  net->EntryN --;
+	}
+    }
+  hid_action ("NetlistChanged");
+}
+
+static void
+netlist_style (LibraryMenuType *net, const char *style)
+{
+  if (net->Style)
+    MYFREE (net->Style);
+  if (style)
+    net->Style = MyStrdup ((char *)style, "Netlist(Style)");
+}
+
+/* The primary purpose of this action is to rebuild a netlist from a
+   script, in conjunction with the clear action above.  */
+static int
+netlist_add (const char *netname, const char *pinname, const char *defer_update)
+{
+  int ni, pi;
+  LibraryType *netlist = &PCB->NetlistLib;
+  LibraryMenuType *net = NULL;
+  LibraryEntryType *pin = NULL;
+
+  for (ni=0; ni<netlist->MenuN; ni++)
+    if (strcmp (netlist->Menu[ni].Name+2, netname) == 0)
+      {
+	net = & (netlist->Menu[ni]);
+	break;
+      }
+  if (net == NULL)
+    {
+      net = CreateNewNet (netlist, (char *)netname, NULL);
+    }
+
+  for (pi=0; pi<net->EntryN; pi++)
+    if (strcmp (net->Entry[pi].ListEntry, pinname) == 0)
+      {
+	pin = & (net->Entry[pi]);
+	break;
+      }
+  if (pin == NULL)
+    {
+      pin = CreateNewConnection (net, (char *)pinname);
+    }
+
+  if (!defer_update)
+    hid_action ("NetlistChanged");
+  return 0;
+}
 
 static const char netlist_syntax[] =
-  "Net(find|select|rats|norats[,net[,pin]])";
+  "Net(find|select|rats|norats|clear[,net[,pin]])\n"
+  "Net(add,net,pin[,defer])";
 
 static const char netlist_help[] = "Perform various actions on netlists.";
 
@@ -212,10 +302,23 @@ Nets which apply are marked as available for the rats nest.
 @item norats
 Nets which apply are marked as not available for the rats nest.
 
+@item clear
+Clears the netlist.
+
+@item add
+Add the given pin to the given netlist, creating either if needed.  If
+defer is specified, the GUI is not informed of this change - after a
+list of such changes, call NetlistChanged() to update the GUI.
+
+@item sort
+Called after a list of add's, this sorts the netlist.
+
 @end table
 
 %end-doc */
 
+#define ARG(n) (argc > (n) ? argv[n] : 0)
+
 static int
 Netlist (int argc, char **argv, int x, int y)
 {
@@ -251,6 +354,27 @@ Netlist (int argc, char **argv, int x, int y)
     func = netlist_rats;
   else if (strcasecmp (argv[0], "norats") == 0)
     func = netlist_norats;
+  else if (strcasecmp (argv[0], "clear") == 0)
+    {
+      func = netlist_clear;
+      if (argc == 1)
+	{
+	  netlist_clear (NULL, NULL);
+	  hid_action ("NetlistChanged");
+	  return 0;
+	}
+    }
+  else if (strcasecmp (argv[0], "style") == 0)
+    func = (void *)netlist_style;
+  else if (strcasecmp (argv[0], "add") == 0)
+    {
+      /* Add is different, because the net/pin won't already exist.  */
+      return netlist_add (ARG(1), ARG(2), ARG(3));
+    }
+  else if (strcasecmp (argv[0], "sort") == 0)
+    {
+      return sort_netlist ();
+    }
   else
     {
       Message (netlist_syntax);
@@ -295,7 +419,7 @@ Netlist (int argc, char **argv, int x, int y)
     }
 #endif
 
-  for (i = 0; i < PCB->NetlistLib.MenuN; i++)
+  for (i = PCB->NetlistLib.MenuN-1; i >= 0; i--)
     {
       net = PCB->NetlistLib.Menu + i;
 
@@ -321,10 +445,14 @@ Netlist (int argc, char **argv, int x, int y)
       net_found = 1;
 
       pin = 0;
-      if (argc > 2)
+      if (func == (void *)netlist_style)
+	{
+	  netlist_style (net, ARG(2));
+	}
+      else if (argc > 2)
 	{
 	  int l = strlen (argv[2]);
-	  for (j = 0; j < net->EntryN; j++)
+	  for (j = net->EntryN-1; j >= 0 ; j--)
 	    if (strcasecmp (net->Entry[j].ListEntry, argv[2]) == 0
 		|| (strncasecmp (net->Entry[j].ListEntry, argv[2], l) == 0
 		    && net->Entry[j].ListEntry[l] == '-'))
diff --git a/src/pcb-menu.res b/src/pcb-menu.res
index 5186183..b326a04 100644
--- a/src/pcb-menu.res
+++ b/src/pcb-menu.res
@@ -32,6 +32,7 @@ MainMenu =
    {"Save layout" Save(Layout)}
    {"Save layout as..." Save(LayoutAs)}
 	{"Revert" Load(Revert,none)}
+   {"Import Schematics" Import() }
    {"Load layout" Load(Layout)}
    {"Load element data to paste-buffer" PasteBuffer(Clear) Load(ElementTobuffer)}
    {"Load layout data to paste-buffer" PasteBuffer(Clear) Load(LayoutTobuffer)}
@@ -153,6 +154,10 @@ MainMenu =
    {" Change text on layout" ChangeName(Object) a={"N" "<Key>n"}}
    {" Edit name of layout" ChangeName(Layout)}
    {" Edit name of active layer" ChangeName(Layer)}
+   {"Edit Attributes..." foreground=grey50 sensitive=false}
+   {" Layout" Attributes(Layout)}
+   {" CurrentLayer" Attributes(Layer)}
+   {" Element" Attributes(Element)}
    -
    {"Board Sizes" AdjustSizes()}
    {"Route Styles"
diff --git a/tools/Makefile.am b/tools/Makefile.am
index d0438d1..77a2d1a 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -7,12 +7,15 @@
 toolsdir=	$(pkgdatadir)/tools
 tools_DATA=	${TOOLS}
 
+scmdatadir = $(datadir)/gEDA/scheme
+scmdata_DATA = $(DIST_SCM)
+
 EXTRA_DIST= ${TOOLS}
 
 bin_SCRIPTS= \
 	MergePCBPS \
 	Merge_dimPCBPS 
-	
+
 TOOLS= \
 	apctools.zip \
 	gerbertotk.c \
@@ -22,3 +25,5 @@ TOOLS= \
 	Merge_dimPCBPS \
 	PCB2HPGL 
 
+DIST_SCM= \
+	gnet-pcblf.scm
diff --git a/tools/gnet-pcblf.scm b/tools/gnet-pcblf.scm
new file mode 100644
index 0000000..637dca8
--- /dev/null
+++ b/tools/gnet-pcblf.scm
@@ -0,0 +1,114 @@
+;;; gEDA - GPL Electronic Design Automation
+;;; gnetlist - gEDA Netlist
+;;; Copyright (C) 1998-2008 Ales Hvezda
+;;; Copyright (C) 1998-2008 gEDA Contributors (see ChangeLog for details)
+;;;
+;;; This program is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;; PCB forward annotation script
+
+(use-modules (ice-9 format))
+
+;; This is a list of attributes which are propogated to the pcb
+;; elements.  Note that refdes, value, and footprint need not be
+;; listed here.
+(define pcblf:element-attrs
+  '("device"
+    "manufacturer"
+    "manufacturer_part_number"
+    "vendor"
+    "vendor_part_number"
+    ))
+
+(define (pcblf:pinfmt pin)
+  (format #f "~a-~a" (car pin) (car (cdr pin)))
+  )
+
+(define (pcblf:each-pin net pins port)
+  (if (not (null? pins))
+      (let ((pin (car pins)))
+	(format port "Netlist(Add,~a,~a,1)~%" net (pcblf:pinfmt pin))
+	(pcblf:each-pin net (cdr pins) port))))
+
+(define (pcblf:each-net netnames port)
+  (if (not (null? netnames))
+      (let ((netname (car netnames)))
+	(pcblf:each-pin netname (gnetlist:get-all-connections netname) port)
+	(pcblf:each-net (cdr netnames) port))))
+
+(define (pcblf:each-attr refdes attrs port)
+  (if (not (null? attrs))
+      (let ((attr (car attrs)))
+	(format port "ElementSetAttr(~a,~a,~a)~%"
+		refdes
+		attr
+		(gnetlist:get-package-attribute refdes attr))
+	(pcblf:each-attr refdes (cdr attrs) port))))
+
+;; write out the pins for a particular component
+(define pcblf:component_pins
+  (lambda (port package pins)
+    (if (and (not (null? package)) (not (null? pins)))
+	(begin
+	  (let (
+		(pin (car pins))
+		(label #f)
+		(pinnum #f)
+		)
+	    (display "ChangePinName(" port)
+	    (display package port)
+	    (display ", " port)
+
+	    (set! pinnum (gnetlist:get-attribute-by-pinnumber package pin "pinnumber"))
+
+	    (display pinnum port)
+	    (display ", " port)
+
+	    (set! label (gnetlist:get-attribute-by-pinnumber package pin "pinlabel"))
+	    (if (string=? label "unknown") 
+		(set! label pinnum)
+		)
+	    (display label port)
+	    (display ")\n" port)
+	    )
+	  (pcblf:component_pins port package (cdr pins))
+	  )
+	)
+    )
+  )
+
+(define (pcblf:each-element elements port)
+  (if (not (null? elements))
+      (let* ((refdes (car elements))
+	     (value (gnetlist:get-package-attribute refdes "value"))
+	     (footprint (gnetlist:get-package-attribute refdes "footprint"))
+	     )
+
+	(format port "ElementAddIf(~a,~a,~a)~%" refdes footprint value)
+	(pcblf:each-attr refdes pcblf:element-attrs port)
+	(pcblf:component_pins port refdes (gnetlist:get-pins refdes))
+
+	(pcblf:each-element (cdr elements) port))))
+
+(define (pcblf output-filename)
+  (let ((port (open-output-file output-filename)))
+    (format port "Netlist(Clear)\n")
+    (pcblf:each-net (gnetlist:get-all-unique-nets "dummy") port)
+    (format port "Netlist(Sort)\n")
+    (format port "NetlistChanged()\n")
+    (format port "ElementAddStart()\n")
+    (pcblf:each-element packages port)
+    (format port "ElementAddDone()\n")
+    (close-output-port port)))


_______________________________________________
geda-user mailing list
geda-user@xxxxxxxxxxxxxx
http://www.seul.org/cgi-bin/mailman/listinfo/geda-user