/*
 * Electric(tm) VLSI Design System
 *
 * File: usrnet.c
 * User interface aid: network manipulation routines
 * Written by: Steven M. Rubin, Static Free Software
 *
 * Copyright (c) 2000 Static Free Software.
 *
 * Electric(tm) 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.
 *
 * Electric(tm) 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 Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

#include "global.h"
#include "egraphics.h"
#include "usr.h"
#include "usrtrack.h"
#include "database.h"
#include "conlay.h"
#include "efunction.h"
#include "edialogs.h"
#include "tech.h"
#include "tecgen.h"
#include "tecart.h"
#include "tecschem.h"
#include <math.h>

#define EXACTSELECTDISTANCE 5		/* number of pixels for selection */

/* working memory for "us_pickhigherinstance()" */
static INTSML      us_pickinstlistsize = 0;
static NODEPROTO **us_pickinstfacetlist;
static INTSML     *us_pickinstinstcount;
static NODEINST  **us_pickinstinstlist;

/* working memory for "us_makenewportproto()" */
static INTBIG us_netportlimit = 0, *us_netportlocation;

/* for constructing implant coverage polygons */
#define NOCOVERRECT ((COVERRECT *)-1)

typedef struct Icoverrect
{
	INTBIG             lx, hx, ly, hy;
	INTBIG             contributions;
	INTBIG             layer;
	TECHNOLOGY        *tech;
	struct Icoverrect *nextcoverrect;
} COVERRECT;

/* for "explorer" window */
#define EXPLORERBLOCKSIZE 10

#define NOEXPLORERNODE ((EXPLORERNODE *)-1)

#define EXNODEOPEN    1
#define EXNODESHOWN   2

typedef struct Iexplorernode
{
	NODEPROTO            *facet;
	LIBRARY              *lib;
	INTBIG                flags;
	INTBIG                count;
	INTBIG                x, y;
	INTBIG                textwidth;
	struct Iexplorernode *subexplorernode;
	struct Iexplorernode *nextsubexplorernode;
	struct Iexplorernode *nextexplorernode;
} EXPLORERNODE;

EXPLORERNODE  *us_firstexplorernode = NOEXPLORERNODE;
EXPLORERNODE  *us_explorernodeused = NOEXPLORERNODE;
EXPLORERNODE  *us_explorernodefree = NOEXPLORERNODE;
EXPLORERNODE  *us_explorernodeselected;				/* the selected explorer node */
EXPLORERNODE  *us_explorernodenowselected;			/* the newly selected explorer node */

INTSML         us_exploretextsize;
INTBIG         us_exploretextheight;
INTBIG         us_explorefirstline;					/* first visible line in explorer window */
INTBIG         us_exploretotallines;
INTBIG         us_explorerhittopline;				/* nonzero if first line has been drawn */
INTBIG         us_explorerdepth;					/* size of explorer depth */
INTBIG         us_explorertoplinedepth;				/* size of explorer depth for first line */
INTBIG         us_explorerstacksize = 0;			/* space allocated for node stacks */
EXPLORERNODE **us_explorerstack;					/* stack of explorer nodes */
EXPLORERNODE **us_explorertoplinestack;				/* stack of explorer nodes for first line */
WINDOWPART    *us_explorerwindow;					/* current window when arrows are clicked */
INTBIG         us_explorersliderpart;				/* 0:down arrow 1:below thumb 2:thumb 3:above thumb 4:up arrow */

/* prototypes for local routines */
void us_modnodebits(NODEINST*, INTSML, INTSML, INTBIG, INTBIG);
INTSML us_copyvariablefacets(INTBIG, INTBIG, LIBRARY*, INTSML);
void us_recursivelysearch(RTNODE*, INTSML, INTSML, INTSML, INTSML, INTSML, HIGHLIGHT*, HIGHLIGHT*, HIGHLIGHT*, HIGHLIGHT*, HIGHLIGHT*, INTSML*, INTBIG*, INTBIG, INTBIG, WINDOWPART*, INTSML);
void us_checkoutobject(GEOM*, INTSML, INTSML, INTSML, INTSML, HIGHLIGHT*, HIGHLIGHT*, HIGHLIGHT*, HIGHLIGHT*, HIGHLIGHT*, INTSML*, INTBIG*, INTBIG, INTBIG, WINDOWPART*);
void us_initnodetext(NODEINST*, INTSML);
INTSML us_getnodetext(NODEINST*, WINDOWPART*, POLYGON*, VARIABLE**, PORTPROTO**);
void us_initarctext(ARCINST*, INTSML);
INTSML us_getarctext(ARCINST*, WINDOWPART*, POLYGON*, VARIABLE**);
INTBIG us_findnewplace(INTBIG*, INTBIG, INTBIG, INTBIG);
void us_setbestsnappoint(HIGHLIGHT *best, INTBIG wantx, INTBIG wanty, INTBIG newx, INTBIG newy, INTSML tan, INTSML perp);
void us_selectsnappoly(HIGHLIGHT *best, POLYGON *poly, INTBIG wantx, INTBIG wanty);
INTSML us_pointonarc(INTBIG x, INTBIG y, POLYGON *poly);
void us_intersectsnapline(HIGHLIGHT *best, INTBIG x1, INTBIG y1, INTBIG x2, INTBIG y2,
	POLYGON *interpoly, INTBIG wantx, INTBIG wanty);
void us_intersectsnappoly(HIGHLIGHT *best, POLYGON *poly, POLYGON *interpoly, INTBIG wantx, INTBIG wanty);
void us_adjustonetangent(HIGHLIGHT *high, POLYGON *poly, INTBIG x, INTBIG y);
void us_adjustoneperpendicular(HIGHLIGHT *high, POLYGON *poly, INTBIG x, INTBIG y);
void us_xformpointtonode(INTBIG x, INTBIG y, NODEINST *ni, INTBIG *xo, INTBIG *yo);
void us_addleadtoicon(NODEPROTO *facet, ARCPROTO *wire, NODEINST *pin, NODEINST *box,
	PORTPROTO *boxport, INTBIG pinx, INTBIG piny, INTBIG boxx, INTBIG boxy);
INTBIG us_alignvalueround(INTBIG value);
INTBIG us_disttoobject(INTBIG x, INTBIG y, GEOM *geom);
void us_addtocoverlist(POLYGON *poly, COVERRECT **crlist);
void us_choparc(ARCINST *ai, INTBIG keepend, INTBIG ix, INTBIG iy);
void us_createexplorertree(EXPLORERNODE *en);
EXPLORERNODE *us_allocexplorernode(void);
void us_freeexplorernode(EXPLORERNODE *en);
INTBIG us_showexplorerstruct(WINDOWPART *w, EXPLORERNODE *firsten, INTBIG indent, INTBIG line);
void us_addexplorernode(EXPLORERNODE *en, EXPLORERNODE **head);
INTSML us_expandexplorerdepth(void);
void us_buildexplorerstruct(void);
INTBIG us_scannewexplorerstruct(EXPLORERNODE *firsten, EXPLORERNODE *oldfirsten, INTBIG line);
void us_highlightexplorernode(WINDOWPART *w);
INTBIG us_iconposition(PORTPROTO *pp, INTBIG style);
INTSML us_explorerarrowdown(INTBIG x, INTBIG y);
void us_filltextpoly(char *str, WINDOWPART *win, INTBIG xc, INTBIG yc, XARRAY trans,
	TECHNOLOGY *tech, INTBIG descript, POLYGON *poly);

/*
 * Routine to free all memory associated with this module.
 */
void us_freenetmemory(void)
{
	REGISTER EXPLORERNODE *en;

	if (us_pickinstlistsize != 0)
	{
		efree((char *)us_pickinstfacetlist);
		efree((char *)us_pickinstinstlist);
		efree((char *)us_pickinstinstcount);
	}
	if (us_netportlimit > 0) efree((char *)us_netportlocation);

	/* free "explorer" memory */
	while (us_explorernodeused != NOEXPLORERNODE)
	{
		en = us_explorernodeused;
		us_explorernodeused = en->nextexplorernode;
		us_freeexplorernode(en);
	}
	while (us_explorernodefree != NOEXPLORERNODE)
	{
		en = us_explorernodefree;
		us_explorernodefree = en->nextexplorernode;
		efree((char *)en);
	}
}

/*********************************** FACET SUPPORT ***********************************/

/*
 * routine to recursively expand the facet "ni" by "amount" levels.
 * "sofar" is the number of levels that this has already been expanded.
 */
void us_doexpand(NODEINST *ni, INTSML amount, INTSML sofar)
{
	REGISTER NODEPROTO *np;
	REGISTER NODEINST *ono;

	if ((ni->userbits & NEXPAND) == 0)
	{
		/* turn the nodeinst display off */
		startobjectchange((INTBIG)ni, VNODEINST);

		/* expanded the facet */
		ni->userbits |= NEXPAND;

		/* redisplay node */
		endobjectchange((INTBIG)ni, VNODEINST);

		/* if depth limit reached, quit */
		if (++sofar >= amount) return;
	}

	/* explore insides of this one */
	np = ni->proto;
	for(ono = np->firstnodeinst; ono != NONODEINST; ono = ono->nextnodeinst)
	{
		if (ono->proto->primindex != 0) continue;
		us_doexpand(ono, amount, sofar);
	}
}

void us_dounexpand(NODEINST *ni)
{
	REGISTER NODEPROTO *np;
	REGISTER NODEINST *ono;

	if ((ni->userbits & NEXPAND) == 0) return;

	np = ni->proto;
	for(ono = np->firstnodeinst; ono != NONODEINST; ono = ono->nextnodeinst)
	{
		if (ono->proto->primindex != 0) continue;
		if ((ono->userbits & NEXPAND) != 0) us_dounexpand(ono);
	}

	if ((ni->userbits & WANTEXP) != 0)
	{
		/* turn the nodeinst display off */
		startobjectchange((INTBIG)ni, VNODEINST);

		/* expanded the facet */
		ni->userbits &= ~NEXPAND;

		/* redisplay node */
		endobjectchange((INTBIG)ni, VNODEINST);
	}
}

INTSML us_setunexpand(NODEINST *ni, INTSML amount)
{
	REGISTER INTSML depth;
	REGISTER NODEINST *ono;
	REGISTER NODEPROTO *np;

	ni->userbits &= ~WANTEXP;
	if ((ni->userbits & NEXPAND) == 0) return(0);
	np = ni->proto;
	depth = 0;
	for(ono = np->firstnodeinst; ono != NONODEINST; ono = ono->nextnodeinst)
	{
		if (ono->proto->primindex != 0) continue;
		if ((ono->userbits & NEXPAND) != 0)
			depth = (INTSML)maxi(depth, us_setunexpand(ono, amount));
	}
	if (depth < amount) ni->userbits |= WANTEXP;
	return(depth+1);
}

/*
 * routine to recursively modify the bits in the user aid
 * word of nodes below nodeinst "nodeinst".  This goes only to a depth of
 * "depth" levels below "curdepth", so initially, "curdepth" should be 0 and
 * "depth" should be the desired recursion depth.  The bits in "onbits"
 * of each nodeinst are turned on and the bits in "offbits" are turned off.
 */
void us_modnodebits(NODEINST *nodeinst, INTSML depth, INTSML curdepth, INTBIG onbits,
	INTBIG offbits)
{
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *np;

	nodeinst->userbits = (nodeinst->userbits | onbits) & ~offbits;
	if (++curdepth >= depth) return;

	np = nodeinst->proto;
	if (np->primindex != 0) return;
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		us_modnodebits(ni, depth, curdepth, onbits,offbits);
}

/*
 * Routine to adjust everything in the facet to even grid units.
 */
void us_regridfacet(void)
{
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER PORTPROTO *pp;
	REGISTER NODEPROTO *facet;
	REGISTER INTBIG bodyxoffset, bodyyoffset, portxoffset, portyoffset, pxo, pyo,
		adjustednodes, end1xoff, end1yoff, end2xoff, end2yoff;
	REGISTER INTSML mixedportpos;
	INTBIG px, py;

	if (us_alignment <= 0)
	{
		us_abortcommand("No alignment given: set Alignment Options first");
		return;
	}
	facet = us_needfacet();
	if (facet == NONODEPROTO) return;

	/* save and remove highlighting */
	us_pushhighlight();
	us_clearhighlightcount();

	/* remove constraints on arcs that would make things bad */
	for(ai = facet->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		end1xoff = us_alignvalueround(ai->end[0].xpos) - ai->end[0].xpos;
		end1yoff = us_alignvalueround(ai->end[0].ypos) - ai->end[0].ypos;
		end2xoff = us_alignvalueround(ai->end[1].xpos) - ai->end[1].xpos;
		end2yoff = us_alignvalueround(ai->end[1].ypos) - ai->end[1].ypos;
		if (end1xoff == end2xoff && end1yoff == end2yoff) continue;

		if (ai->end[0].xpos == ai->end[1].xpos)
		{
			/* vertical wire, just make nonrigid if there are Y offsets */
			if (end1yoff != 0 || end2yoff != 0)
			{
				/* make sure the wire isn't rigid */
				if ((ai->userbits&FIXED) != 0)
					(*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPEUNRIGID, 0);
				continue;
			}
		}
		if (ai->end[0].ypos == ai->end[1].ypos)
		{
			/* horizontal wire, just make nonrigid if there are X offsets */
			if (end1xoff != 0 || end2xoff != 0)
			{
				/* make sure the wire isn't rigid */
				if ((ai->userbits&FIXED) != 0)
					(*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPEUNRIGID, 0);
				continue;
			}
		}

		/* nonmanhattan wire: make non-fixed angle if there are any offsets */
		if (end1xoff != 0 || end2xoff != 0 || end1yoff != 0 || end2yoff != 0)
		{
			if ((ai->userbits&FIXANG) != 0)
				(*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPENOTFIXEDANGLE, 0);
		}
	}

	/* adjust the nodes */
	adjustednodes = 0;
	for(ni = facet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		bodyxoffset = us_alignvalueround(ni->lowx) - ni->lowx;
		bodyyoffset = us_alignvalueround(ni->lowy) - ni->lowy;

		portxoffset = bodyxoffset;
		portyoffset = bodyyoffset;
		mixedportpos = 0;
		for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			portposition(ni, pp, &px, &py);
			pxo = us_alignvalueround(px) - px;
			pyo = us_alignvalueround(py) - py;
			if (pp == ni->proto->firstportproto)
			{
				portxoffset = pxo;   portyoffset = pyo;
			} else
			{
				if (portxoffset != pxo || portyoffset != pyo) mixedportpos = 1;
			}
		}
		if (mixedportpos == 0)
		{
			bodyxoffset = portxoffset;   bodyyoffset = portyoffset;
		}

		/* move the node */
		if (bodyxoffset != 0 || bodyyoffset != 0)
		{
			startobjectchange((INTBIG)ni, VNODEINST);
			modifynodeinst(ni, bodyxoffset, bodyyoffset, bodyxoffset, bodyyoffset, 0, 0);
			endobjectchange((INTBIG)ni, VNODEINST);
			adjustednodes++;

			/*
			 * since many changes are being made at once, must totally reset
			 * the change control system between each one
			 * in order to get them to work right
			 */
			db_endbatch();
		}
	}

	/* restore highlighting and show results */
	(void)us_pophighlight(1);
	if (adjustednodes == 0) ttyputmsg("No adjustments necessary"); else
		ttyputmsg("Adjusted %d %s", adjustednodes, makeplural("node", adjustednodes));
}

/*
 * Routine to create or modify implant (well/substrate) nodes that cover their components
 * cleanly.
 */
void us_coverimplant(void)
{
	COVERRECT *crlist;
	REGISTER COVERRECT *cr, *ocr, *lastcr, *nextcr;
	REGISTER NODEPROTO *np, *facet;
	REGISTER INTSML i, total, domerge;
	REGISTER INTBIG fun, count;
	REGISTER NODEINST *ni, *nextni;
	REGISTER ARCINST *ai;
	HIGHLIGHT high;
	XARRAY trans;
	char *extra;
	static POLYGON *poly = NOPOLYGON;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);

	/* generate an initial coverage list from all nodes and arcs in the facet */
	crlist = NOCOVERRECT;
	facet = us_needfacet();
	if (facet == NONODEPROTO) return;
	for(ni = facet->firstnodeinst; ni != NONODEINST; ni = nextni)
	{
		nextni = ni->nextnodeinst;
		if (ni->proto->primindex == 0) continue;
		fun = nodefunction(ni, &extra);
		if (fun == NPNODE)
		{
			startobjectchange((INTBIG)ni, VNODEINST);
			killnodeinst(ni);
			continue;
		}
		makerot(ni, trans);
		total = nodepolys(ni);
		for(i=0; i<total; i++)
		{
			shapenodepoly(ni, i, poly);
			fun = layerfunction(ni->proto->tech, poly->layer) & LFTYPE;
			if (fun != LFIMPLANT && fun != LFSUBSTRATE && fun != LFWELL) continue;
			xformpoly(poly, trans);
			us_addtocoverlist(poly, &crlist);
		}
	}
	for(ai = facet->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		total = arcpolys(ai);
		for(i=0; i<total; i++)
		{
			shapearcpoly(ai, i, poly);
			fun = layerfunction(ai->proto->tech, poly->layer) & LFTYPE;
			if (fun != LFIMPLANT && fun != LFSUBSTRATE && fun != LFWELL) continue;
			us_addtocoverlist(poly, &crlist);
		}
	}

	/* merge the coverage rectangles that touch */
	for(cr = crlist; cr != NOCOVERRECT; cr = cr->nextcoverrect)
	{
		lastcr = NOCOVERRECT;
		for(ocr = cr->nextcoverrect; ocr != NOCOVERRECT; ocr = nextcr)
		{
			nextcr = ocr->nextcoverrect;
			domerge = 0;
			if (cr->layer == ocr->layer && cr->tech == ocr->tech)
			{
				if (cr->hx >= ocr->lx && cr->lx <= ocr->hx && 
					cr->hy >= ocr->ly && cr->ly <= ocr->hy) domerge = 1;
			}
			if (domerge != 0)
			{
				/* merge them */
				if (ocr->lx < cr->lx) cr->lx = ocr->lx;
				if (ocr->hx > cr->hx) cr->hx = ocr->hx;
				if (ocr->ly < cr->ly) cr->ly = ocr->ly;
				if (ocr->hy > cr->hy) cr->hy = ocr->hy;
				cr->contributions += ocr->contributions;

				if (lastcr == NOCOVERRECT) cr->nextcoverrect = ocr->nextcoverrect; else
					lastcr->nextcoverrect = ocr->nextcoverrect;
				efree((char *)ocr);
				continue;
			}
			lastcr = ocr;
		}
	}
#if 0
	/* merge coverage rectangles across open space */
	for(cr = crlist; cr != NOCOVERRECT; cr = cr->nextcoverrect)
	{
		lastcr = NOCOVERRECT;
		for(ocr = cr->nextcoverrect; ocr != NOCOVERRECT; ocr = nextcr)
		{
			nextcr = ocr->nextcoverrect;
			domerge = 0;
			if (cr->layer == ocr->layer && cr->tech == ocr->tech)
			{
				REGISTER COVERRECT *icr;
				REGISTER INTBIG mlx, mhx, mly, mhy;

				mlx = mini(cr->lx, ocr->lx);
				mhx = maxi(cr->hx, ocr->hx);
				mly = mini(cr->ly, ocr->ly);
				mhy = maxi(cr->hy, ocr->hy);
				for(icr = crlist; icr != NOCOVERRECT; icr = icr->nextcoverrect)
				{
					if (icr == cr || icr == ocr) continue;
					if (icr->layer == cr->layer && icr->tech == cr->tech) continue;
					if (icr->lx < mhx && icr->hx > mlx && icr->ly < mhy && icr->hy > mly)
						break;
				}
				if (icr == NOCOVERRECT) domerge = 1;
			}
			if (domerge != 0)
			{
				/* merge them */
				if (ocr->lx < cr->lx) cr->lx = ocr->lx;
				if (ocr->hx > cr->hx) cr->hx = ocr->hx;
				if (ocr->ly < cr->ly) cr->ly = ocr->ly;
				if (ocr->hy > cr->hy) cr->hy = ocr->hy;
				cr->contributions += ocr->contributions;

				if (lastcr == NOCOVERRECT) cr->nextcoverrect = ocr->nextcoverrect; else
					lastcr->nextcoverrect = ocr->nextcoverrect;
				efree((char *)ocr);
				continue;
			}
			lastcr = ocr;
		}
	}
#endif

	count = 0;
	while (crlist != NOCOVERRECT)
	{
		cr = crlist;
		crlist = crlist->nextcoverrect;

		if (cr->contributions > 1)
		{
			np = getpurelayernode(cr->tech, cr->layer, 0);
			ni = newnodeinst(np, cr->lx, cr->hx, cr->ly, cr->hy, 0, 0, facet);
			if (ni == NONODEINST) continue;
			ni->userbits |= HARDSELECTN;
			endobjectchange((INTBIG)ni, VNODEINST);
			if (count == 0) us_clearhighlightcount();
			high.status = HIGHFROM;
			high.fromgeom = ni->geom;
			high.fromport = NOPORTPROTO;
			high.facet = facet;
			(void)us_addhighlight(&high);
			count++;
		}
		efree((char *)cr);
	}
	if (count == 0) ttyputmsg("No implant areas added"); else
		ttyputmsg("Added %d implant %s", count, makeplural("area", count));
}

void us_addtocoverlist(POLYGON *poly, COVERRECT **crlist)
{
	REGISTER COVERRECT *cr;
	INTBIG lx, hx, ly, hy;

	getbbox(poly, &lx, &hx, &ly, &hy);
	for(cr = *crlist; cr != NOCOVERRECT; cr = cr->nextcoverrect)
	{
		if (cr->layer != poly->layer) continue;
		if (cr->tech != poly->tech) continue;
		if (hx < cr->lx || lx > cr->hx || hy < cr->ly || ly > cr->hy) continue;

		/* this polygon touches another: merge into it */
		if (lx < cr->lx) cr->lx = lx;
		if (hx > cr->hx) cr->hx = hx;
		if (ly < cr->ly) cr->ly = ly;
		if (hy > cr->hy) cr->hy = hy;
		cr->contributions++;
		return;
	}

	/* nothing in this area: create a new one */
	cr = (COVERRECT *)emalloc(sizeof (COVERRECT), el_tempcluster);
	if (cr == 0) return;
	cr->lx = lx;   cr->hx = hx;
	cr->ly = ly;   cr->hy = hy;
	cr->layer = poly->layer;
	cr->tech = poly->tech;
	cr->contributions = 1;
	cr->nextcoverrect = *crlist;
	*crlist = cr;
}

/*
 * Routine to round "value" to the nearest "us_alignment" units.
 */
INTBIG us_alignvalueround(INTBIG value)
{
	if (value > 0)
		return((value + us_alignment/2) / us_alignment * us_alignment);
	return((value - us_alignment/2) / us_alignment * us_alignment);
}

/*
 * Routine to determine whether facet "np" has a facet center in it.  If so,
 * an error is displayed and the routine returns nonzero.
 */
INTSML us_alreadyfacetcenter(NODEPROTO *np)
{
	REGISTER NODEINST *ni;

	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		if (ni->proto == gen_facetcenterprim)
	{
		us_abortcommand("This facet already has a facet-center");
		return(1);
	}
	return(0);
}

/*
 * recursive helper routine for "us_copyfacet" which copies facet "fromnp"
 * to a new facet called "toname" in library "tolib" with the new view type
 * "toview".  All needed subfacets, shared view facets, and facets
 * referenced by variables are copied too.  If "subdescript" is empty, the
 * operation is a top-level request.  Otherwise, this is for a subfacet, so
 * only create a new facet if one with the same name and date doesn't already
 * exists.
 */
NODEPROTO *us_copyrecursively(NODEPROTO *fromnp, char *toname,
	LIBRARY *tolib, VIEW *toview, INTSML verbose, char *subdescript)
{
	REGISTER NODEPROTO *np, *onp, *newfromnp;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER PORTPROTO *pp;
	REGISTER LIBRARY *savelib;
	REGISTER char *newname;

	/* see if the facet is already there */
	for(newfromnp = tolib->firstnodeproto; newfromnp != NONODEPROTO;
		newfromnp = newfromnp->nextnodeproto)
	{
		if (namesame(newfromnp->cell->cellname, toname) != 0) continue;
		if (newfromnp->cellview != toview) continue;
		if (newfromnp->creationdate != fromnp->creationdate) continue;
		if (newfromnp->revisiondate != fromnp->revisiondate) continue;
		break;
	}
	if (*subdescript != 0 && newfromnp != NONODEPROTO) return(newfromnp);

	/* check variables on the nodeproto that reference other facets */
	if (us_copyvariablefacets((INTBIG)fromnp, VNODEPROTO, tolib, verbose) != 0)
		return(NONODEPROTO);

	/* must copy subfacets */
	for(ni = fromnp->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (us_copyvariablefacets((INTBIG)ni, VNODEINST, tolib, verbose) != 0)
			return(NONODEPROTO);
		np = ni->proto;
		if (np->primindex != 0) continue;

		/* allow cross-library references to stay */
		if (ni->proto->cell->lib != fromnp->cell->lib) continue;

		/* copy subfacet if not already there */
		onp = us_copyrecursively(np, np->cell->cellname, tolib, np->cellview, verbose, "subfacet ");
		if (onp == NONODEPROTO)
		{
			newname = describenodeproto(np);
			ttyputerr("Copy of subfacet %s failed", newname);
			return(NONODEPROTO);
		}
	}

	/* copy facet variables on the arcs */
	for(ai = fromnp->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		if (us_copyvariablefacets((INTBIG)ai, VARCINST, tolib, verbose) != 0)
			return(NONODEPROTO);
		if (us_copyvariablefacets((INTBIG)ai->end[0].portarcinst,
			VPORTARCINST, tolib, verbose) != 0) return(NONODEPROTO);
		if (us_copyvariablefacets((INTBIG)ai->end[1].portarcinst,
			VPORTARCINST, tolib, verbose) != 0) return(NONODEPROTO);
	}

	/* copy facet variables on the ports */
	for(pp = fromnp->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		if (us_copyvariablefacets((INTBIG)pp, VPORTPROTO, tolib, verbose) != 0)
			return(NONODEPROTO);
		if (us_copyvariablefacets((INTBIG)pp->subportexpinst,
			VPORTEXPINST, tolib, verbose) != 0) return(NONODEPROTO);
	}

	/* copy the facet */
	if (*toview->sviewname != 0)
	{
		(void)initinfstr();
		(void)addstringtoinfstr(toname);
		(void)addtoinfstr('{');
		(void)addstringtoinfstr(toview->sviewname);
		(void)addtoinfstr('}');
		newname = returninfstr();
	} else newname = toname;
	newfromnp = copynodeproto(fromnp, tolib, newname);
	if (newfromnp == NONODEPROTO)
	{
		ttyputerr("Copy of %s%s failed", subdescript, describenodeproto(fromnp));
		return(NONODEPROTO);
	}
	if (verbose != 0)
	{
		if (fromnp->cell->lib != tolib)
		{
			(void)initinfstr();
			(void)addstringtoinfstr("Copied ");
			(void)addstringtoinfstr(subdescript);
			savelib = el_curlib;
			el_curlib = tolib;
			(void)addstringtoinfstr(describenodeproto(fromnp));
			(void)addstringtoinfstr(" to ");
			el_curlib = fromnp->cell->lib;
			(void)addstringtoinfstr(describenodeproto(newfromnp));
			el_curlib = savelib;
			ttyputmsg("%s", returninfstr());
		} else
		{
			ttyputmsg("Copied %s%s", subdescript, describenodeproto(fromnp));
		}
	}

	/* ensure that the copied facet is the right size */
	(*el_curconstraint->solve)(newfromnp);

	/* also copy equivalent views */
	for(np = fromnp->cell->firstincell; np != NONODEPROTO; np = np->nextincell)
		if (np != fromnp)
	{
		/* copy equivalent view if not already there */
		onp = us_copyrecursively(np, np->cell->cellname, tolib, np->cellview,
			verbose, "alternate view ");
		if (onp == NONODEPROTO)
		{
			ttyputerr("Copy of alternate view %s failed", describenodeproto(np));
			return(NONODEPROTO);
		}
	}
	return(newfromnp);
}

/*
 * helper routine of "us_copyrecursively" which examines the variables on object
 * "fromaddr" of type "fromtype" and copies any references to facets.
 */
INTSML us_copyvariablefacets(INTBIG fromaddr, INTBIG fromtype, LIBRARY *lib, INTSML verbose)
{
	REGISTER INTSML i, j;
	REGISTER INTBIG addr, type, len;
	INTSML *numvar;
	REGISTER NODEPROTO *np, *onp;
	VARIABLE **firstvar;

	/* get the list of variables on this object */
	if (db_getvarptr(fromaddr, fromtype, &firstvar, &numvar) != 0)
		return(0);

	/* look at each variable */
	for(i=0; i<(*numvar); i++)
	{
		type = (*firstvar)[i].type;
		if ((type&VTYPE) != VNODEPROTO) continue;

		/* NODEPROTO variable found: see if a facet must be copied */
		if ((type&VISARRAY) == 0)
		{
			addr = (*firstvar)[i].addr;
			np = (NODEPROTO *)addr;
			if (np == NONODEPROTO) continue;
			if (np->primindex != 0) continue;

			/* copy associated facet if not already there */
			onp = us_copyrecursively(np, np->cell->cellname, lib,
				np->cellview, verbose, "associated facet ");
			if (onp == NONODEPROTO)
			{
				ttyputerr("Copy of facet %s failed", describenodeproto(np));
				return(1);
			}
		} else
		{
			len = (type&VLENGTH) >> VLENGTHSH;
			if (len != 0)
			{
				for(j=0; j<len; j++)
				{
					np = ((NODEPROTO **)addr)[j];
					if (np == NONODEPROTO) continue;
					if (np->primindex != 0) continue;

					/* copy associated facet if not already there */
					onp = us_copyrecursively(np, np->cell->cellname,
						lib, np->cellview, verbose, "associated facet ");
					if (onp == NONODEPROTO)
					{
						ttyputerr("Copy of facet %s failed", describenodeproto(np));
						return(1);
					}
				}
			}
		}
	}
	return(0);
}

/*
 * Routine to remove geometry in the selected area, shortening arcs that enter
 * from outside.
 */
void us_erasegeometry(NODEPROTO *np)
{
	INTBIG lx, hx, ly, hy, lx1, hx1, ly1, hy1, ix, iy;
	REGISTER INTBIG i, in0, in1, keepend, killend, keepx, keepy, killx, killy,
		clx, chx, cly, chy, cx, cy;
	REGISTER INTSML ang, didin;
	REGISTER ARCINST *ai, *nextai;
	REGISTER NODEINST *ni, *nextni;
	static POLYGON *poly = NOPOLYGON;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);

	/* disallow erasing if lock is on */
	if (us_canedit(np, NONODEPROTO, 1) != 0) return;

	np = us_getareabounds(&lx, &hx, &ly, &hy);
	if (np == NONODEPROTO)
	{
		us_abortcommand("Outline an area first");
		return;
	}

	/* grid the area */
	gridalign(&lx, &ly, us_alignment);
	gridalign(&hx, &hy, us_alignment);

	/* truncate all arcs that cross the area */
	for(ai = np->firstarcinst; ai != NOARCINST; ai = nextai)
	{
		nextai = ai->nextarcinst;

		/* get the extent of this arc */
		i = ai->width - arcwidthoffset(ai);
		if (curvedarcoutline(ai, poly, CLOSED, i) != 0)
			makearcpoly(ai->length, i, ai, poly, FILLED);
		getbbox(poly, &lx1, &hx1, &ly1, &hy1);

		/* if the arc is outside of the area, ignore it */
		if (lx1 >= hx || hx1 <= lx || ly1 >= hy || hy1 <= ly) continue;

		/* if the arc is inside of the area, delete it */
		if (lx1 >= lx && hx1 <= hx && ly1 >= ly && hy1 <= hy)
		{
			/* delete the arc */
			startobjectchange((INTBIG)ai, VARCINST);
			if (killarcinst(ai)) ttyputerr("Error killing arc");
			continue;
		}

		/* partially inside the area: truncate the arc */
		if (ai->end[0].xpos > lx && ai->end[0].xpos < hx &&
			ai->end[0].ypos > ly && ai->end[0].ypos < hy) in0 = 1; else
				in0 = 0;
		if (ai->end[1].xpos > lx && ai->end[1].xpos < hx &&
			ai->end[1].ypos > ly && ai->end[1].ypos < hy) in1 = 1; else
				in1 = 0;
		if (in0 == in1) continue;
		if (in0 == 0) keepend = 0; else keepend = 1;
		killend = 1 - keepend;
		keepx = ai->end[keepend].xpos;   keepy = ai->end[keepend].ypos;
		killx = ai->end[killend].xpos;   killy = ai->end[killend].ypos;
		clx = lx - i/2;
		chx = hx + i/2;
		cly = ly - i/2;
		chy = hy + i/2;
		ang = figureangle(keepx, keepy, killx, killy);

		/* see which side has the intersection point */
		didin = intersect(keepx, keepy, ang, lx, hy, 0, &ix, &iy);
		if (didin >= 0)
		{
			if (ix >= lx && ix <= hx &&
				ix >= mini(keepx, killx) &&
				ix <= maxi(keepx, killx) &&
				iy >= mini(keepy, killy) &&
				iy <= maxi(keepy, killy))
			{
				/* intersects the top edge */
				(void)intersect(keepx, keepy, ang, clx, chy, 0, &ix, &iy);
				us_choparc(ai, keepend, ix, iy);
				continue;
			}
		}
		didin = intersect(keepx, keepy, ang, lx, ly, 0, &ix, &iy);
		if (didin >= 0)
		{
			if (ix >= lx && ix <= hx &&
				ix >= mini(keepx, killx) &&
				ix <= maxi(keepx, killx) &&
				iy >= mini(keepy, killy) &&
				iy <= maxi(keepy, killy))
			{
				/* intersects the bottom edge */
				(void)intersect(keepx, keepy, ang, clx, cly, 0, &ix, &iy);
				us_choparc(ai, keepend, ix, iy);
				continue;
			}
		}
		didin = intersect(keepx, keepy, ang, lx, ly, 900, &ix, &iy);
		if (didin >= 0)
		{
			if (iy >= ly && iy <= hy &&
				ix >= mini(keepx, killx) &&
				ix <= maxi(keepx, killx) &&
				iy >= mini(keepy, killy) &&
				iy <= maxi(keepy, killy))
			{
				/* intersects the bottom edge */
				(void)intersect(keepx, keepy, ang, clx, cly, 900, &ix, &iy);
				us_choparc(ai, keepend, ix, iy);
				continue;
			}
		}
		didin = intersect(keepx, keepy, ang, hx, ly, 900, &ix, &iy);
		if (didin >= 0)
		{
			if (iy >= ly && iy <= hy &&
				ix >= mini(keepx, killx) &&
				ix <= maxi(keepx, killx) &&
				iy >= mini(keepy, killy) &&
				iy <= maxi(keepy, killy))
			{
				/* intersects the bottom edge */
				(void)intersect(keepx, keepy, ang, chx, cly, 900, &ix, &iy);
				us_choparc(ai, keepend, ix, iy);
				continue;
			}
		}
	}

	/* now remove nodes in the area */
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = nextni)
	{
		nextni = ni->nextnodeinst;

		/* if the node is outside of the area, ignore it */
		cx = (ni->lowx + ni->highx) / 2;
		cy = (ni->lowy + ni->highy) / 2;
		if (cx > hx || cx < lx || cy > hy || cy < ly) continue;

		/* delete the node */
		while(ni->firstportexpinst != NOPORTEXPINST)
			killportproto(np, ni->firstportexpinst->exportproto);
		startobjectchange((INTBIG)ni, VNODEINST);
		if (killnodeinst(ni)) ttyputerr("Error killing node");
	}
}

void us_choparc(ARCINST *ai, INTBIG keepend, INTBIG ix, INTBIG iy)
{
	REGISTER NODEPROTO *pin;
	REGISTER NODEINST *ni, *oldni;
	REGISTER PORTPROTO *oldpp;
	REGISTER ARCPROTO *ap;
	REGISTER INTBIG size, lx, hx, ly, hy, wid, bits, oldx, oldy;

	ap = ai->proto;
	pin = getpinproto(ap);
	if (pin == NONODEPROTO) return;
	wid = ai->width;
	bits = ai->userbits;
	size = wid - arcwidthoffset(ai);
	lx = ix - size/2;   hx = lx + size;
	ly = iy - size/2;   hy = ly + size;
	ni = newnodeinst(pin, lx, hx, ly, hy, 0, 0, ai->parent);
	if (ni == NONODEINST) return;
	endobjectchange((INTBIG)ni, VNODEINST);
	oldni = ai->end[keepend].nodeinst;
	oldpp = ai->end[keepend].portarcinst->proto;
	oldx = ai->end[keepend].xpos;
	oldy = ai->end[keepend].ypos;
	startobjectchange((INTBIG)ai, VARCINST);
	if (killarcinst(ai)) ttyputerr("Error killing arc");
	ai = newarcinst(ap, wid, bits, oldni, oldpp, oldx, oldy, ni, ni->proto->firstportproto,
		ix, iy, ai->parent);
	if (ai == NOARCINST) return;
	endobjectchange((INTBIG)ai, VARCINST);
}

/*
 * routine to clean-up facet "np" as follows:
 *   remove stranded pins
 *   collapsing redundant arcs
 *   highlight zero-size nodes
 *   resize oversized pins that don't have oversized arcs on them
 */
void us_cleanupfacet(NODEPROTO *np, INTSML justthis)
{
	REGISTER NODEINST *ni, *nextni;
	REGISTER PORTARCINST *pi;
	REGISTER INTBIG i, j, pinsremoved, pinsscaled, zerosize, sx, sy, oversizearc,
		oversize, oversizex, oversizey, dlx, dhx, dly, dhy;
	REGISTER ARCINST *ai;
	REGISTER VARIABLE *var;
	char line[30];
	ARCINST *reconar[2];
	INTBIG dx[2], dy[2], lx, hx, ly, hy;
	HIGHLIGHT newhigh;

	/* look for unused pins that can be deleted */
	pinsremoved = 0;
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = nextni)
	{
		nextni = ni->nextnodeinst;
		if ((ni->proto->userbits&NFUNCTION) >> NFUNCTIONSH != NPPIN) continue;

		/* if the pin is exported, save it */
		if (ni->firstportexpinst != NOPORTEXPINST) continue;

		/* if the pin is not connected or displayed, delete it */
		if (ni->firstportarcinst == NOPORTARCINST)
		{
			/* see if the pin has displayable variables on it */
			for(i=0; i<ni->numvar; i++)
			{
				var = &ni->firstvar[i];
				if ((var->type&VDISPLAY) != 0) break;
			}
			if (i >= ni->numvar)
			{
				/* disallow erasing if lock is on */
				if (us_canedit(np, ni->proto, 1) != 0) continue;

				/* no displayable variables: delete it */
				startobjectchange((INTBIG)ni, VNODEINST);
				if (killnodeinst(ni)) ttyputerr("Error from killnodeinst");
				pinsremoved++;
			}
			continue;
		}

		/* if the pin is connected to two arcs along the same slope, delete it */
		j = 0;
		for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
		{
			if (j >= 2) { j = 0;   break; }
			reconar[j] = ai = pi->conarcinst;
			for(i=0; i<2; i++) if (ai->end[i].nodeinst != ni)
			{
				dx[j] = ai->end[i].xpos - ai->end[1-i].xpos;
				dy[j] = ai->end[i].ypos - ai->end[1-i].ypos;
			}
			j++;
		}

		/* must connect to two arcs of the same type and width */
		if (j != 2) continue;
		if (reconar[0]->proto != reconar[1]->proto) continue;
		if (reconar[0]->width != reconar[1]->width) continue;

		/* arcs must be along the same angle, and not be curved */
		if (dx[0] != 0 || dy[0] != 0 || dx[1] != 0 || dy[1] != 0)
		{
			if ((dx[0] != 0 || dy[0] != 0) && (dx[1] != 0 || dy[1] != 0) &&
				figureangle(0, 0, dx[0], dy[0]) !=
				figureangle(dx[1], dy[1], 0, 0)) continue;
		}
		if (getvalkey((INTBIG)reconar[0], VARCINST, VINTEGER, el_arc_radius) != NOVARIABLE)
			continue;
		if (getvalkey((INTBIG)reconar[1], VARCINST, VINTEGER, el_arc_radius) != NOVARIABLE)
			continue;

		/* remove the pin and reconnect the arcs */
		us_erasepassthru(ni, 0);
		pinsremoved++;
	}

	/* look for oversized pins */
	pinsscaled = 0;
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if ((ni->proto->userbits&NFUNCTION) >> NFUNCTIONSH != NPPIN) continue;

		/* if the pin is standard size, leave it alone */
		oversizex = (ni->highx - ni->lowx) - (ni->proto->highx - ni->proto->lowx);
		if (oversizex < 0) oversizex = 0;
		oversizey = (ni->highy - ni->lowy) - (ni->proto->highy - ni->proto->lowy);
		if (oversizey < 0) oversizey = 0;
		if (oversizex == 0 && oversizey == 0) continue;

		/* look for arcs that are oversized */
		oversizearc = 0;
		for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
		{
			ai = pi->conarcinst;
			oversize = ai->width - ai->proto->nominalwidth;
			if (oversize < 0) oversize = 0;
			if (oversize > oversizearc) oversizearc = oversize;
		}

		/* if an arc covers the pin, leave the pin */
		if (oversizearc >= oversizex && oversizearc >= oversizey) continue;

		dlx = dhx = dly = dhy = 0;
		if (oversizearc < oversizex)
		{
			dlx =  (oversizex - oversizearc) / 2;
			dhx = -(oversizex - oversizearc) / 2;
		}
		if (oversizearc < oversizey)
		{
			dly =  (oversizey - oversizearc) / 2;
			dhy = -(oversizey - oversizearc) / 2;
		}
		startobjectchange((INTBIG)ni, VNODEINST);
		modifynodeinst(ni, dlx, dly, dhx, dhy, 0, 0);
		endobjectchange((INTBIG)ni, VNODEINST);

		pinsscaled++;
	}

	/* now highlight zero-size nodes */
	zerosize = 0;
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto == gen_facetcenterprim) continue;
		nodesizeoffset(ni, &lx, &ly, &hx, &hy);
		sx = ni->highx - ni->lowx - lx - hx;
		sy = ni->highy - ni->lowy - ly - hy;
		if (sx > 0 && sy > 0) continue;
		if (justthis != 0)
		{
			newhigh.status = HIGHFROM;
			newhigh.facet = np;
			newhigh.fromgeom = ni->geom;
			newhigh.fromport = NOPORTPROTO;
			newhigh.frompoint = 0;
			(void)us_addhighlight(&newhigh);
		}
		zerosize++;
	}

	if (pinsremoved == 0 && pinsscaled == 0 && zerosize == 0)
	{
		if (justthis != 0) ttyputmsg("Nothing to clean");
	} else
	{
		(void)initinfstr();
		if (justthis == 0)
		{
			(void)addstringtoinfstr("Facet ");
			(void)addstringtoinfstr(describenodeproto(np));
			(void)addstringtoinfstr(": ");
		}
		if (pinsremoved != 0)
		{
			(void)addstringtoinfstr("Removed ");
			sprintf(line, "%ld", pinsremoved);
			(void)addstringtoinfstr(line);
			(void)addstringtoinfstr(" ");
			(void)addstringtoinfstr(makeplural("pin", pinsremoved));
		}
		if (pinsscaled != 0)
		{
			if (pinsremoved != 0) (void)addstringtoinfstr("; ");
			(void)addstringtoinfstr("Shrunk ");
			sprintf(line, "%ld", pinsscaled);
			(void)addstringtoinfstr(line);
			(void)addstringtoinfstr(" ");
			(void)addstringtoinfstr(makeplural("pin", pinsscaled));
		}
		if (zerosize != 0)
		{
			if (pinsremoved != 0 || pinsscaled != 0) (void)addstringtoinfstr("; ");
			if (justthis != 0) (void)addstringtoinfstr("Highlighted "); else
				(void)addstringtoinfstr("Found ");
			sprintf(line, "%ld", zerosize);
			(void)addstringtoinfstr(line);
			(void)addstringtoinfstr(" zero-size ");
			(void)addstringtoinfstr(makeplural("node", zerosize));
		}
		ttyputmsg("%s", returninfstr());
	}
}

/*
 * routine to generate an icon in library "lib" with name "iconname" from the
 * port list in "fpp".  The icon facet is called "pt" and it is constructed
 * from technology "tech".  The icon facet is returned (NONODEPROTO on error).
 */
NODEPROTO *us_makeiconfacet(PORTPROTO *fpp, char *iconname, char *pt,
	TECHNOLOGY *tech, LIBRARY *lib)
{
	REGISTER NODEPROTO *np, *pintype;
	REGISTER NODEINST *bbni, *pinni, *outni, *inni;
	REGISTER PORTPROTO *pp, *port, *leftport, *rightport, *bottomport,
		*topport, *whichport, *bpp, *inport;
	REGISTER ARCPROTO *wiretype;
	REGISTER INTSML leftside, rightside, bottomside, topside;
	REGISTER INTBIG xsize, ysize, xpos, ypos, xbbpos, ybbpos, spacing,
		style, index, descript, xoffset, yoffset, lambda;
	REGISTER VARIABLE *var;

	/* get the necessary symbols */
	rightport = sch_bboxprim->firstportproto;
	topport = rightport->nextportproto;
	leftport = topport->nextportproto;
	bottomport = leftport->nextportproto;
	lambda = tech->deflambda;

	/* get icon style controls */
	var = getval((INTBIG)us_aid, VAID, VINTEGER, "USER_icon_style");
	if (var != NOVARIABLE) style = var->addr; else style = ICONSTYLEEFAULT;

	/* create the new icon facet */
	np = newnodeproto(pt, lib);
	if (np == NONODEPROTO)
	{
		us_abortcommand("Cannot create icon %s", pt);
		return(NONODEPROTO);
	}
	np->userbits |= WANTNEXPAND;

	/* determine number of inputs and outputs */
	leftside = rightside = bottomside = topside = 0;
	for(pp = fpp; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		if ((pp->userbits&BODYONLY) != 0) continue;
		index = us_iconposition(pp, style);
		switch (index)
		{
			case 0: pp->temp1 = leftside++;    break;
			case 1: pp->temp1 = rightside++;   break;
			case 2: pp->temp1 = topside++;     break;
			case 3: pp->temp1 = bottomside++;  break;
		}
	}

	/* create the Black Box with the correct size */
	ysize = maxi(maxi(leftside, rightside), 5) * 2 * lambda;
	xsize = maxi(maxi(topside, bottomside), 3) * 2 * lambda;

	/* create the Black Box instance */
	if ((style&ICONSTYLEDRAWNOBODY) != 0) bbni = NONODEINST; else
	{
		bbni = newnodeinst(sch_bboxprim, 0, xsize, 0, ysize, 0, 0, np);
		if (bbni == NONODEINST) return(NONODEPROTO);

		/* put the original cell name on the Black Box */
		var = setvalkey((INTBIG)bbni, VNODEINST, sch_functionkey, (INTBIG)iconname, VSTRING|VDISPLAY);
		if (var != NOVARIABLE) var->textdescript = defaulttextdescript(NOGEOM);
	}

	/* create the Facet Center instance */
	if ((us_useroptions&PUTFACETCENTERINICON) != 0)
	{
		pinni = newnodeinst(gen_facetcenterprim, 0, 0, 0, 0, 0, 0, np);
		if (pinni == NONODEINST) return(NONODEPROTO);
	}

	/* place pins around the Black Box */
	for(pp = fpp; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		if ((pp->userbits&BODYONLY) != 0) continue;

		/* determine location of the port */
		index = us_iconposition(pp, style);
		switch (index)
		{
			case 0:		/* left */
				xpos = -2 * lambda;
				xbbpos = 0;
				spacing = 2 * lambda;
				if (leftside*2 < rightside) spacing = 4 * lambda;
				ybbpos = ypos = ysize - ((ysize - (leftside-1)*spacing) / 2 + pp->temp1 * spacing);
				whichport = leftport;
				break;
			case 1:		/* right side */
				xpos = xsize + 2 * lambda;
				xbbpos = xsize;
				spacing = 2 * lambda;
				if (rightside*2 < leftside) spacing = 4 * lambda;
				ybbpos = ypos = ysize - ((ysize - (rightside-1)*spacing) / 2 + pp->temp1 * spacing);
				whichport = rightport;
				break;
			case 2:		/* top */
				spacing = 2 * lambda;
				if (topside*2 < bottomside) spacing = 4 * lambda;
				xbbpos = xpos = xsize - ((xsize - (topside-1)*spacing) / 2 + pp->temp1 * spacing);
				ypos = ysize + 2 * lambda;
				ybbpos = ysize;
				whichport = topport;
				break;
			case 3:		/* bottom */
				spacing = 2 * lambda;
				if (bottomside*2 < topside) spacing = 4 * lambda;
				xbbpos = xpos = xsize - ((xsize - (bottomside-1)*spacing) / 2 + pp->temp1 * spacing);
				ypos = -2 * lambda;
				ybbpos = 0;
				whichport = bottomport;
				break;
		}

		/* determine type of pin */
		pintype = gen_invispinprim;
		wiretype = sch_wirearc;
		if (pp->subnodeinst != NONODEINST)
		{
			bpp = pp;
			while (bpp->subnodeinst->proto->primindex == 0) bpp = bpp->subportproto;
			if (bpp->subnodeinst->proto == sch_buspinprim)
			{
				pintype = sch_buspinprim;
				wiretype = sch_busarc;
			}
		}

		/* create the pin */
		if ((style&ICONSTYLEDRAWNOLEADS) != 0)
		{
			xpos = xbbpos;   ypos = ybbpos;
			style &= ~ICONSTYLEPORTLOC;
		}

		/* make the pin with the port */
		pinni = newnodeinst(pintype, xpos-(pintype->highx-pintype->lowx)/2,
			xpos+(pintype->highx-pintype->lowx)/2, ypos-(pintype->highy-pintype->lowy)/2,
				ypos+(pintype->highy-pintype->lowy)/2, 0, 0, np);
		endobjectchange((INTBIG)pinni, VNODEINST);
		if (pinni == NONODEINST) return(NONODEPROTO);

		/* export the port that should be on this pin */
		port = newportproto(np, pinni, pintype->firstportproto, pp->protoname);
		if (port != NOPORTPROTO)
		{
			port->userbits = (port->userbits & ~STATEBITS) | (pp->userbits & STATEBITS) | PORTDRAWN;
			switch ((style&ICONSTYLEPORTSTYLE) >> ICONSTYLEPORTSTYLESH)
			{
				case 0: /* Centered */
					descript = VTPOSCENT;
					break;
				case 1: /* Inward */
					switch (index)
					{
						case 0: descript = VTPOSRIGHT;  break;	/* left */
						case 1: descript = VTPOSLEFT;   break;	/* right */
						case 2: descript = VTPOSDOWN;   break;	/* top */
						case 3: descript = VTPOSUP;     break;	/* bottom */
					}
					break;
				case 2: /* Outward */
					switch (index)
					{
						case 0: descript = VTPOSLEFT;   break;	/* left */
						case 1: descript = VTPOSRIGHT;  break;	/* right */
						case 2: descript = VTPOSUP;     break;	/* top */
						case 3: descript = VTPOSDOWN;   break;	/* bottom */
					}
					break;
			}
			switch ((style&ICONSTYLEPORTLOC) >> ICONSTYLEPORTLOCSH)
			{
				case 0:		/* port on body */
					xoffset = xbbpos - xpos;   yoffset = ybbpos - ypos;
					break;
				case 1:		/* port on lead end */
					xoffset = yoffset = 0;
					break;
				case 2:		/* port on lead middle */
					xoffset = (xpos+xbbpos) / 2 - xpos;
					yoffset = (ypos+ybbpos) / 2 - ypos;
					break;
			}
			if (xoffset < 0)
			{
				descript |= VTXOFFNEG;
				xoffset = -xoffset;
			}
			if (yoffset < 0)
			{
				descript |= VTYOFFNEG;
				yoffset = -yoffset;
			}
			descript |= (xoffset * 4 / pintype->tech->deflambda) << VTXOFFSH;
			descript |= (yoffset * 4 / pintype->tech->deflambda) << VTYOFFSH;
			port->textdescript = (pp->textdescript &
				~(VTPOSITION|VTXOFF|VTXOFFNEG|VTYOFF|VTYOFFNEG)) | descript;
			if (copyvars((INTBIG)pp, VPORTPROTO, (INTBIG)port, VPORTPROTO) != 0)
				return(NONODEPROTO);
		}

		/* add lead is requested */
		if ((style&ICONSTYLEDRAWNOLEADS) == 0)
		{
			/* if port location isn't at end of lead, use port for drawing */
			outni = NONODEINST;
			outni = pinni;

			/* if there isn't a body, find the other end for connection */
			inni = bbni;   inport = whichport;
			if (inni == NONODEINST)
			{
				if ((style&ICONSTYLEPORTLOC) >> ICONSTYLEPORTLOCSH == 0) inni = pinni; else
				{
					if (wiretype != sch_wirearc)
					{
						/* not a simple wire: must actually run it (probably a bus) */
						inni = newnodeinst(pintype, xbbpos-(pintype->highx-pintype->lowx)/2,
							xbbpos+(pintype->highx-pintype->lowx)/2, ybbpos-(pintype->highy-pintype->lowy)/2,
								ybbpos+(pintype->highy-pintype->lowy)/2, 0, 0, np);
						if (inni == NONODEINST) return(NONODEPROTO);
						inport = inni->proto->firstportproto;
					}
				}
			}

			/* wire this pin to the black box */
			us_addleadtoicon(np, wiretype, outni, inni, inport, xpos, ypos,
				xbbpos, ybbpos);
		}
	}
	return(np);
}

/*
 * Routine to determine the side of the icon that port "pp" belongs on, given that
 * the icon style is "style".
 */
INTBIG us_iconposition(PORTPROTO *pp, INTBIG style)
{
	REGISTER INTBIG index, character;

	character = pp->userbits & STATEBITS;

	/* special detection for power and ground ports */
	if (portispower(pp) != 0) character = PWRPORT;
	if (portisground(pp) != 0) character = GNDPORT;

	/* see which side this type of port sits on */
	switch (character)
	{
		case INPORT:    index = (style & ICONSTYLESIDEIN) >> ICONSTYLESIDEINSH;         break;
		case OUTPORT:   index = (style & ICONSTYLESIDEOUT) >> ICONSTYLESIDEOUTSH;       break;
		case BIDIRPORT: index = (style & ICONSTYLESIDEBIDIR) >> ICONSTYLESIDEBIDIRSH;   break;
		case PWRPORT:   index = (style & ICONSTYLESIDEPOWER) >> ICONSTYLESIDEPOWERSH;   break;
		case GNDPORT:   index = (style & ICONSTYLESIDEGROUND) >> ICONSTYLESIDEGROUNDSH; break;
		case CLKPORT:
		case C1PORT:
		case C2PORT:
		case C3PORT:
		case C4PORT:
		case C5PORT:
		case C6PORT:
			index = (style & ICONSTYLESIDECLOCK) >> ICONSTYLESIDECLOCKSH;
			break;
		default:
			index = (style & ICONSTYLESIDEIN) >> ICONSTYLESIDEINSH;
			break;
	}
	return(index);
}

void us_addleadtoicon(NODEPROTO *facet, ARCPROTO *wire, NODEINST *pin, NODEINST *box,
	PORTPROTO *boxport, INTBIG pinx, INTBIG piny, INTBIG boxx, INTBIG boxy)
{
	NODEINST *ni;
	INTBIG lx, hx, ly, hy, cx, cy, data[4];

	if (wire != sch_wirearc)
	{
		/* not a simple wire: connect this pin to the black box */
		(void)newarcinst(wire, defaultarcwidth(wire), us_makearcuserbits(wire),
			pin, pin->proto->firstportproto, pinx, piny, box, boxport, boxx, boxy, facet);
	} else
	{
		/* simple wire: draw a line from this pin to the black box */
		lx = mini(pinx, boxx);   hx = maxi(pinx, boxx);
		ly = mini(piny, boxy);   hy = maxi(piny, boxy);
		cx = (pinx + boxx) / 2;  cy = (piny + boxy) / 2;
		ni = newnodeinst(art_openedpolygonprim, lx, hx, ly, hy, 0, 0, facet);
		if (ni == NONODEINST) return;
		data[0] = pinx - cx;   data[1] = piny - cy;
		data[2] = boxx - cx;   data[3] = boxy - cy;
		(void)setvalkey((INTBIG)ni, VNODEINST, el_trace, (INTBIG)data,
			VINTEGER|VISARRAY|(4<<VLENGTHSH));
	}
}

/*
 * Routine to return the name of the technology that is used in facet "np".
 * Distinction is made between analog and digital schematics.
 */
char *us_techname(NODEPROTO *np)
{
	REGISTER TECHNOLOGY *tech;
	REGISTER NODEINST *ni;
	REGISTER char *techname;

	tech = whattech(np);
	if (tech == NOTECHNOLOGY) return("");
	techname = tech->techname;
	if (tech == sch_tech)
	{
		/* see if it is analog or digital */
		techname = "schematic, analog";
		for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		{
			if (ni->proto == sch_bufprim || ni->proto == sch_andprim ||
				ni->proto == sch_orprim || ni->proto == sch_xorprim ||
				ni->proto == sch_ffprim || ni->proto == sch_muxprim)
			{
				techname = "schematic, digital";
				break;
			}
		}
	}
	return(techname);
}

/*
 * Routine to delete facet "np".  Validity checks are assumed to be made (i.e. the
 * facet is not used and is not locked).
 */
void us_dokillfacet(NODEPROTO *np)
{
	REGISTER NODEPROTO *onp, *curfacet;
	REGISTER NODEINST *ni;
	REGISTER VIEW *oldview;
	REGISTER CELL *oldcell;
	REGISTER INTSML oldversion, iscurrent;
	REGISTER WINDOWPART *w, *nextw, *neww;

	/* delete random references to this facet */
	curfacet = getcurfacet();
	if (np == curfacet)
	{
		(void)setval((INTBIG)el_curlib, VLIBRARY, "curnodeproto", (INTBIG)NONODEPROTO, VNODEPROTO);
		us_clearhighlightcount();
	}

	/* close windows that reference this facet */
	for(w = el_topwindowpart; w != NOWINDOWPART; w = nextw)
	{
		nextw = w->nextwindowpart;
		if (w->curnodeproto != np) continue;
		if (w == el_curwindowpart) iscurrent = 1; else iscurrent = 0;
		if (iscurrent != 0)
			(void)setvalkey((INTBIG)us_aid, VAID, us_current_window, (INTBIG)NOWINDOWPART,
				VWINDOWPART|VDONTSAVE);
		startobjectchange((INTBIG)us_aid, VAID);
		neww = newwindowpart(w->location, w);
		if (neww == NOWINDOWPART) return;

		/* adjust the new window to account for borders in the old one */
		if ((w->state&WINDOWTYPE) != DISPWINDOW)
		{
			neww->usehx -= DISPLAYSLIDERSIZE;
			neww->usely += DISPLAYSLIDERSIZE;
		}
		if ((w->state&WINDOWTYPE) == WAVEFORMWINDOW)
		{
			neww->uselx -= DISPLAYSLIDERSIZE;
			neww->usely -= DISPLAYSLIDERSIZE;
		}
		if ((w->state&WINDOWSIMULATING) != 0)
		{
			neww->uselx -= SIMULATINGBORDERSIZE;   neww->usehx += SIMULATINGBORDERSIZE;
			neww->usely -= SIMULATINGBORDERSIZE;   neww->usehy += SIMULATINGBORDERSIZE;
		}

		neww->curnodeproto = NONODEPROTO;
		neww->buttonhandler = DEFAULTBUTTONHANDLER;
		neww->charhandler = DEFAULTCHARHANDLER;
		neww->changehandler = DEFAULTCHANGEHANDLER;
		neww->termhandler = DEFAULTTERMHANDLER;
		neww->redisphandler = DEFAULTREDISPHANDLER;
		neww->state = (neww->state & ~(WINDOWTYPE|WINDOWSIMULATING)) | DISPWINDOW;
		killwindowpart(w);
		endobjectchange((INTBIG)us_aid, VAID);
		if (iscurrent != 0)
			(void)setvalkey((INTBIG)us_aid, VAID, us_current_window, (INTBIG)neww,
				VWINDOWPART|VDONTSAVE);
	}

	oldcell = np->cell;
	oldview = np->cellview;
	oldversion = np->version;
	if (killnodeproto(np))
	{
		ttyputerr("Error killing facet");
		return;
	}

	/* see if this was the latest version of a cell */
	for(onp = oldcell->firstincell; onp != NONODEPROTO; onp = onp->nextincell)
		if (onp->cellview == oldview && onp->version < oldversion) break;
	if (onp != NONODEPROTO)
	{
		/* newest version was deleted: rename next older version */
		for(ni = onp->firstinst; ni != NONODEINST; ni = ni->nextinst)
		{
			if ((ni->userbits&NEXPAND) != 0) continue;
			startobjectchange((INTBIG)ni, VNODEINST);
			endobjectchange((INTBIG)ni, VNODEINST);
		}
	}

	/* update status display if necessary */
	if (us_curnodeproto != NONODEPROTO && us_curnodeproto->primindex == 0)
	{
		if (np == us_curnodeproto)
		{
			if ((us_state&NONPERSISTENTCURNODE) != 0) us_setnodeproto(NONODEPROTO); else
				us_setnodeproto(el_curtech->firstnodeproto);
		} else if (np->cell == us_curnodeproto->cell)
		{
			onp = us_curnodeproto;
			us_curnodeproto = NONODEPROTO;
			us_setnodeproto(onp);
		}
	}
}

/*
 * Routine to compare the contents of two facets and return nonzero if they are the same.
 * If "explain" is positive, tell why they differ.
 */
INTSML us_samecontents(NODEPROTO *np1, NODEPROTO *np2, INTBIG explain)
{
	REGISTER NODEINST *ni1, *ni2;
	REGISTER GEOM *geom;
	REGISTER ARCINST *ai1, *ai2;
	REGISTER PORTPROTO *pp1, *pp2;
	REGISTER PORTARCINST *pi;
	REGISTER PORTEXPINST *pe;
	REGISTER INTBIG sea, cx, cy, i;

	/* make sure the nodes are the same */
	for(ni2 = np2->firstnodeinst; ni2 != NONODEINST; ni2 = ni2->nextnodeinst)
		ni2->temp1 = 0;
	for(ni1 = np1->firstnodeinst; ni1 != NONODEINST; ni1 = ni1->nextnodeinst)
	{
		/* find the node in the other facet */
		ni1->temp1 = 0;
		cx = (ni1->lowx + ni1->highx) / 2;
		cy = (ni1->lowy + ni1->highy) / 2;
		sea = initsearch(cx, cx, cy, cy, np2);
		for(;;)
		{
			geom = nextobject(sea);
			if (geom == NOGEOM) break;
			if (geom->entrytype == OBJARCINST) continue;
			ni2 = geom->entryaddr.ni;
			if (ni1->lowx != ni2->lowx || ni1->highx != ni2->highx || 
				ni1->lowy != ni2->lowy || ni1->highy != ni2->highy) continue;
			if (ni1->rotation != ni2->rotation || ni1->transpose != ni2->transpose) continue;
			if (ni1->proto->primindex != ni2->proto->primindex) continue;
			if (ni1->proto->primindex != 0)
			{
				/* make sure the two primitives are the same */
				if (ni1->proto != ni2->proto) continue;
			} else
			{
				/* make sure the two facets are the same */
				if (namesame(ni1->proto->cell->cellname, ni2->proto->cell->cellname) != 0)
					continue;
				if (ni1->proto->cellview != ni2->proto->cellview) continue;
			}

			/* the nodes match */
			ni1->temp1 = (INTBIG)ni2;
			ni2->temp1 = (INTBIG)ni1;
			termsearch(sea);
			break;
		}
		if (ni1->temp1 == 0)
		{
			if (explain > 0)
				ttyputmsg("No equivalent to node %s at (%s,%s) in facet %s",
					describenodeinst(ni1), latoa((ni1->lowx+ni1->highx)/2),
						latoa((ni1->lowy+ni1->highy)/2), describenodeproto(np1));
			return(0);
		}
	}
	for(ni2 = np2->firstnodeinst; ni2 != NONODEINST; ni2 = ni2->nextnodeinst)
	{
		if (ni2->temp1 != 0) continue;
		if (explain > 0)
			ttyputmsg("No equivalent to node %s at (%s,%s) in facet %s",
				describenodeinst(ni2), latoa((ni2->lowx+ni2->highx)/2),
					latoa((ni2->lowy+ni2->highy)/2), describenodeproto(np2));
		return(0);
	}

	/* all nodes match up, now check the arcs */
	for(ai2 = np2->firstarcinst; ai2 != NOARCINST; ai2 = ai2->nextarcinst)
		ai2->temp1 = 0;
	for(ai1 = np1->firstarcinst; ai1 != NOARCINST; ai1 = ai1->nextarcinst)
	{
		ai1->temp1 = 0;
		ni2 = (NODEINST *)ai1->end[0].nodeinst->temp1;
		for(pi = ni2->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
		{
			ai2 = pi->conarcinst;
			if (ai2->proto != ai1->proto) continue;
			if (ai2->width != ai1->width) continue;
			for(i=0; i<2; i++)
			{
				if (ai2->end[i].xpos != ai1->end[i].xpos) break;
				if (ai2->end[i].ypos != ai1->end[i].ypos) break;
			}
			if (i >= 2) break;
		}
		if (pi == NOPORTARCINST)
		{
			if (explain > 0)
				ttyputmsg("No equivalent to arc %s from (%s,%s) to (%s,%s) in facet %s",
					describearcinst(ai1), latoa(ai1->end[0].xpos), latoa(ai1->end[0].ypos),
						latoa(ai1->end[1].xpos), latoa(ai1->end[1].ypos), describenodeproto(np1));
			return(0);
		}
		ai1->temp1 = (INTBIG)ai2;
		ai2->temp1 = (INTBIG)ai1;
	}
	for(ai2 = np2->firstarcinst; ai2 != NOARCINST; ai2 = ai2->nextarcinst)
	{
		if (ai2->temp1 != 0) continue;
		if (explain > 0)
			ttyputmsg("No equivalent to arc %s from (%s,%s) to (%s,%s) in facet %s",
				describearcinst(ai2), latoa(ai2->end[0].xpos), latoa(ai2->end[0].ypos),
					latoa(ai2->end[1].xpos), latoa(ai2->end[1].ypos), describenodeproto(np2));
		return(0);
	}

	/* now match the ports */
	for(pp2 = np2->firstportproto; pp2 != NOPORTPROTO; pp2 = pp2->nextportproto)
		pp2->temp1 = 0;
	for(pp1 = np1->firstportproto; pp1 != NOPORTPROTO; pp1 = pp1->nextportproto)
	{
		pp1->temp1 = 0;
		ni2 = (NODEINST *)pp1->subnodeinst->temp1;
		for(pe = ni2->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
		{
			pp2 = pe->exportproto;
			if (namesame(pp1->protoname, pp2->protoname) != 0) continue;
			break;
		}
		if (pe == NOPORTEXPINST)
		{
			if (explain > 0)
				ttyputmsg("No equivalent to port %s in facet %s", pp1->protoname,
					describenodeproto(np1));
			return(0);
		}
		pp1->temp1 = (INTBIG)pp2;
		pp2->temp1 = (INTBIG)pp1;
	}
	for(pp2 = np2->firstportproto; pp2 != NOPORTPROTO; pp2 = pp2->nextportproto)
	{
		if (pp2->temp1 != 0) continue;
		if (explain > 0)
			ttyputmsg("No equivalent to port %s in facet %s", pp2->protoname,
				describenodeproto(np2));
		return(0);
	}

	/* facets match! */
	return(1);
}

/*********************************** EXPLORER WINDOWS ***********************************/

/*
 * Routine to free the former explorer structure and build a new one.
 */
void us_createexplorerstruct(void)
{
	REGISTER EXPLORERNODE *en;

	/* free all existing explorer nodes */
	while (us_explorernodeused != NOEXPLORERNODE)
	{
		en = us_explorernodeused;
		us_explorernodeused = en->nextexplorernode;
		us_freeexplorernode(en);
	}
	us_firstexplorernode = NOEXPLORERNODE;
	us_explorernodeselected = NOEXPLORERNODE;

	/* build a new explorer structure */
	us_buildexplorerstruct();
}

/*
 * Routine to build an explorer structure from the current database.
 */
void us_buildexplorerstruct(void)
{
	REGISTER EXPLORERNODE *en, *sen;
	REGISTER LIBRARY *lib;
	REGISTER NODEPROTO *np, *inp, *cnp;

	/* scan each library */
	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
	{
		if ((lib->userbits&HIDDENLIBRARY) != 0) continue;

		/* create an explorer node for this library */
		en = us_allocexplorernode();
		if (en == NOEXPLORERNODE) break;
		en->flags = EXNODEOPEN;
		en->lib = lib;
		us_addexplorernode(en, &us_firstexplorernode);

		/* find top-level facets in the library */
		for(np = lib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
		{
			if (np->firstinst != NONODEINST) continue;

			/* if any view or version is in use, this facet isn't top-level */
			for(cnp = np->cell->firstincell; cnp != NONODEPROTO; cnp = cnp->nextincell)
			{
				for(inp = cnp; inp != NONODEPROTO; inp = inp->lastversion)
					if (inp->firstinst != NONODEINST) break;
				if (inp != NONODEPROTO) break;
			}
			if (cnp != NONODEPROTO) continue;

			/* create an explorer node for this facet */
			sen = us_allocexplorernode();
			if (sen == NOEXPLORERNODE) break;
			sen->facet = np;
			us_addexplorernode(sen, &en->subexplorernode);

			/* add explorer nodes for everything under this facet */
			us_createexplorertree(sen);
		}
	}
}

/*
 * Routine to build an explorer structure starting at node "en".
 */
void us_createexplorertree(EXPLORERNODE *en)
{
	REGISTER NODEPROTO *np, *subnp, *cnp, *onp;
	REGISTER NODEINST *ni;
	REGISTER EXPLORERNODE *sen;

	np = en->facet;
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		subnp = ni->proto;
		if (subnp->primindex != 0) continue;
		for(sen = en->subexplorernode; sen != NOEXPLORERNODE; sen = sen->nextsubexplorernode)
			if (sen->facet == subnp) break;
		if (sen == NOEXPLORERNODE)
		{
			sen = us_allocexplorernode();
			if (sen == NOEXPLORERNODE) break;
			sen->facet = subnp;
			us_addexplorernode(sen, &en->subexplorernode);
			us_createexplorertree(sen);
		}
		sen->count++;

		/* include associated facets here */
		for(cnp = subnp->cell->firstincell; cnp != NONODEPROTO; cnp = cnp->nextincell)
		{
			for(onp = cnp; onp != NONODEPROTO; onp = onp->lastversion)
			{
				if (onp == subnp) continue;
				for(sen = en->subexplorernode; sen != NOEXPLORERNODE; sen = sen->nextsubexplorernode)
					if (sen->facet == onp) break;
				if (sen == NOEXPLORERNODE)
				{
					sen = us_allocexplorernode();
					if (sen == NOEXPLORERNODE) break;
					sen->facet = onp;
					us_addexplorernode(sen, &en->subexplorernode);
					us_createexplorertree(sen);
				}
				if (onp == contentsview(subnp)) sen->count++;
			}
		}
	}
}

/*
 * Routine to add explorer node "en" to the list headed by "head".
 * The node is inserted alphabetically.
 */
void us_addexplorernode(EXPLORERNODE *en, EXPLORERNODE **head)
{
	EXPLORERNODE *aen, *lasten;
	char *name, *aname;

	lasten = NOEXPLORERNODE;
	for(aen = *head; aen != NOEXPLORERNODE; aen = aen->nextsubexplorernode)
	{
		if (en->lib != NOLIBRARY) name = en->lib->libname; else
			name = describenodeproto(en->facet);
		if (aen->lib != NOLIBRARY) aname = aen->lib->libname; else
			aname = describenodeproto(aen->facet);
		if (namesame(name, aname) < 0) break;
		lasten = aen;
	}
	if (lasten == NOEXPLORERNODE)
	{
		en->nextsubexplorernode = *head;
		*head = en;
	} else
	{
		en->nextsubexplorernode = lasten->nextsubexplorernode;
		lasten->nextsubexplorernode = en;
	}
}

/*
 * Routine to show the explorer tree starting at "firsten" in window "w".  The indentation
 * of this level is "indent", and the line number in the window is "line".  Returns the
 * new line number after adding the necessary structure.
 */
#define EXPLORERINDENT 15

INTBIG us_showexplorerstruct(WINDOWPART *w, EXPLORERNODE *firsten, INTBIG indent, INTBIG line)
{
	REGISTER INTBIG xpos, ypos, i, firstline, topypos;
	REGISTER char *name;
	INTSML wid, hei;
	char num[30];
	REGISTER LIBRARY *savelib;
	extern GRAPHICS us_facetgra;
	REGISTER EXPLORERNODE *en;

	us_facetgra.col = BLACK;

	/* push the explorer stack */
	if (us_expandexplorerdepth() != 0) return(line);

	firstline = line;
	for(en = firsten; en != NOEXPLORERNODE; en = en->nextsubexplorernode)
	{
		/* record the position in the stack */
		us_explorerstack[us_explorerdepth-1] = en;

		/* determine the location of this entry */
		xpos = w->uselx + indent * EXPLORERINDENT + EXPLORERBLOCKSIZE + 2;
		ypos = w->usehy - (line+1) * us_exploretextheight;
		if (ypos+EXPLORERBLOCKSIZE <= w->usehy && ypos >= w->usely)
		{
			/* remember the stack if this is the first line */
			if (us_explorerhittopline == 0)
			{
				us_explorerhittopline = 1;
				for(i=0; i<us_explorerdepth; i++) us_explorertoplinestack[i] = us_explorerstack[i];
				us_explorertoplinedepth = us_explorerdepth;
			}

			/* get the name of this entry */
			(void)initinfstr();
			if (en->facet != NONODEPROTO)
			{
				savelib = el_curlib;
				el_curlib = us_explorerstack[0]->lib;
				(void)addstringtoinfstr(describenodeproto(en->facet));
				el_curlib = savelib;
				if (en->count > 0)
				{
					sprintf(num, " (%ld)", en->count);
					(void)addstringtoinfstr(num);
				}
			} else
			{
				if (en->lib == el_curlib) (void)addstringtoinfstr("CURRENT ");
				(void)addstringtoinfstr("LIBRARY: ");
				(void)addstringtoinfstr(en->lib->libname);
			}
			name = returninfstr();

			/* draw the box */
			screendrawline(w, (INTSML)xpos, (INTSML)ypos, (INTSML)xpos,
				(INTSML)(ypos+EXPLORERBLOCKSIZE), &us_facetgra, 0);
			screendrawline(w, (INTSML)xpos, (INTSML)(ypos+EXPLORERBLOCKSIZE),
				(INTSML)(xpos-EXPLORERBLOCKSIZE), (INTSML)(ypos+EXPLORERBLOCKSIZE),
					&us_facetgra, 0);
			screendrawline(w, (INTSML)(xpos-EXPLORERBLOCKSIZE),
				(INTSML)(ypos+EXPLORERBLOCKSIZE), (INTSML)(xpos-EXPLORERBLOCKSIZE),
					(INTSML)ypos, &us_facetgra, 0);
			screendrawline(w, (INTSML)(xpos-EXPLORERBLOCKSIZE), (INTSML)ypos,
				(INTSML)xpos, (INTSML)ypos, &us_facetgra, 0);
			if (en->subexplorernode != NOEXPLORERNODE)
			{
				/* draw the "-" in the box: it has children */
				screendrawline(w, (INTSML)(xpos-EXPLORERBLOCKSIZE+2),
					(INTSML)(ypos+EXPLORERBLOCKSIZE/2), (INTSML)(xpos-2),
						(INTSML)(ypos+EXPLORERBLOCKSIZE/2), &us_facetgra, 0);
				if ((en->flags&EXNODEOPEN) == 0)
				{
					/* make the "-" a "+" because it is not expanded */
					screendrawline(w, (INTSML)(xpos-EXPLORERBLOCKSIZE/2), (INTSML)(ypos+2),
						(INTSML)(xpos-EXPLORERBLOCKSIZE/2),
							(INTSML)(ypos+EXPLORERBLOCKSIZE-2), &us_facetgra, 0);
				}
			}

			/* draw the connecting lines */
			if (en->lib == NOLIBRARY)
			{
				us_facetgra.col = DGRAY;
				screendrawline(w, (INTSML)(xpos-EXPLORERBLOCKSIZE-1), (INTSML)(ypos+EXPLORERBLOCKSIZE/2),
					(INTSML)(xpos-EXPLORERBLOCKSIZE/2-EXPLORERINDENT),
						(INTSML)(ypos+EXPLORERBLOCKSIZE/2), &us_facetgra, 0);
				topypos = w->usehy - firstline * us_exploretextheight - 1;
				if (topypos > w->usehy) topypos = w->usehy;
				screendrawline(w, (INTSML)(xpos-EXPLORERBLOCKSIZE/2-EXPLORERINDENT),
					(INTSML)(ypos+EXPLORERBLOCKSIZE/2),
					(INTSML)(xpos-EXPLORERBLOCKSIZE/2-EXPLORERINDENT),
						(INTSML)topypos, &us_facetgra, 0);
				us_facetgra.col = BLACK;
			}

			/* draw the name */
			screendrawtext(w, (INTSML)(xpos+4), (INTSML)(ypos-2), name, &us_facetgra);
			screengettextsize(w, name, &wid, &hei);
			en->textwidth = wid;
			en->flags |= EXNODESHOWN;
			en->x = xpos;   en->y = ypos;
		}
		line++;

		/* now draw children */
		if ((en->flags&EXNODEOPEN) != 0 && en->subexplorernode != NOEXPLORERNODE)
		{
			line = us_showexplorerstruct(w, en->subexplorernode, indent+1, line);
		}
	}

	/* pop the explorer stack */
	us_explorerdepth--;
	return(line);
}

/*
 * Routine to increment the stack depth "us_explorerdepth" and to expand the stack
 * globals "us_explorerstack" and "us_explorertoplinestack" if necessary.
 */
INTSML us_expandexplorerdepth(void)
{
	REGISTER INTBIG newlimit, i;
	REGISTER EXPLORERNODE **stk, **topstk;

	if (us_explorerdepth >= us_explorerstacksize)
	{
		newlimit = us_explorerdepth + 10;
		stk = (EXPLORERNODE **)emalloc(newlimit * (sizeof (EXPLORERNODE *)), us_aid->cluster);
		if (stk == 0) return(1);
		topstk = (EXPLORERNODE **)emalloc(newlimit * (sizeof (EXPLORERNODE *)), us_aid->cluster);
		if (topstk == 0) return(1);
		for(i=0; i<us_explorerdepth; i++) stk[i] = us_explorerstack[i];
		for(i=0; i<us_explorertoplinedepth; i++) topstk[i] = us_explorertoplinestack[i];
		if (us_explorerstacksize > 0)
		{
			efree((char *)us_explorerstack);
			efree((char *)us_explorertoplinestack);
		}
		us_explorerstack = stk;
		us_explorertoplinestack = topstk;
		us_explorerstacksize = newlimit;
	}
	us_explorerdepth++;
	return(0);
}

/*
 * Routine called when the hierarchy has changed: rebuilds the explorer
 * structure and preserves as much information as possible when
 * redisplaying it.
 */
void us_redoexplorerwindow(void)
{
	REGISTER WINDOWPART *w;
	REGISTER INTBIG lasttopline;
	REGISTER EXPLORERNODE *en, *oldtopnode, *oldusedtop;

	/* see if there is an explorer window */
	for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
		if ((w->state&WINDOWTYPE) == EXPLORERWINDOW) break;
	if (w == NOWINDOWPART) return;

	/* remember the former list of explorer nodes */
	oldusedtop = us_explorernodeused;
	us_explorernodeused = NOEXPLORERNODE;
	oldtopnode = us_firstexplorernode;
	us_firstexplorernode = NOEXPLORERNODE;

	/* rebuild the explorer structure */
	us_buildexplorerstruct();

	/* find the place where the former "first line" used to be, copy expansion */
	us_explorerdepth = 0;
	lasttopline = us_explorefirstline;
	us_explorefirstline = 0;
	us_explorernodenowselected = NOEXPLORERNODE;
	(void)us_scannewexplorerstruct(us_firstexplorernode, oldtopnode, 0);
	us_explorernodeselected = us_explorernodenowselected;
	if (lasttopline == 0) us_explorefirstline = 0;
	us_exploreredisphandler(w);

	/* free the former list of explorer nodes */
	while (oldusedtop != NOEXPLORERNODE)
	{
		en = oldusedtop;
		oldusedtop = en->nextexplorernode;
		us_freeexplorernode(en);
	}
}

/*
 * Helper routine for "us_redoexplorerwindow" which scans for the proper "top line" in the
 * explorer window and copies the "expansion" bits from the old structure.  The new
 * explorer node being examined is "firsten", and the old one (if there is one) is
 * "oldfirsten".  The current line number in the explorer window is "line", and the
 * routine returns the line number after this node has been scanned.
 */
INTBIG us_scannewexplorerstruct(EXPLORERNODE *firsten, EXPLORERNODE *oldfirsten, INTBIG line)
{
	REGISTER EXPLORERNODE *en, *oen, *ten;
	REGISTER INTBIG i, newline;

	/* push the explorer stack */
	if (us_expandexplorerdepth() != 0) return(line);

	for(en = firsten; en != NOEXPLORERNODE; en = en->nextsubexplorernode)
	{
		/* record the position in the stack */
		us_explorerstack[us_explorerdepth-1] = en;

		/* see if this line is the former top line */
		if (us_explorerdepth == us_explorertoplinedepth)
		{
			for(i=0; i<us_explorerdepth; i++)
			{
				ten = us_explorerstack[i];
				oen = us_explorertoplinestack[i];
				if (ten->lib != oen->lib || ten->facet != oen->facet) break;
			}
			if (i >= us_explorerdepth) us_explorefirstline = line;
		}

		line++;

		/* copy the expansion bits */
		oen = NOEXPLORERNODE;
		if (oldfirsten != NOEXPLORERNODE)
		{
			for(oen = oldfirsten; oen != NOEXPLORERNODE; oen = oen->nextsubexplorernode)
			{
				if (oen->lib == en->lib && oen->facet == en->facet) break;
			}
			if (oen != NOEXPLORERNODE)
			{
				if (oen == us_explorernodeselected)
					us_explorernodenowselected = en;
				en->flags = (en->flags & ~EXNODEOPEN) | (oen->flags & EXNODEOPEN);
			}
		}

		/* now scan children */
		if (en->subexplorernode != NOEXPLORERNODE)
		{
			if (oen != NOEXPLORERNODE) oen = oen->subexplorernode;
			newline = us_scannewexplorerstruct(en->subexplorernode, oen, line);
			if ((en->flags&EXNODEOPEN) != 0) line = newline;
		}
	}

	/* pop the explorer stack */
	us_explorerdepth--;
	return(line);
}

/*
 * Routine to return the currently selected facet in the explorer window.
 * Returns NONODEPROTO if none selected.
 */
NODEPROTO *us_currentexplorernode(void)
{
	REGISTER WINDOWPART *w;

	/* see if there is an explorer window */
	for(w = el_topwindowpart; w != NOWINDOWPART; w = w->nextwindowpart)
		if ((w->state&WINDOWTYPE) == EXPLORERWINDOW) break;
	if (w == NOWINDOWPART) return(NONODEPROTO);
	if (us_explorernodeselected == NOEXPLORERNODE) return(NONODEPROTO);
	return(us_explorernodeselected->facet);
}

/*
 * Redisplay routine for explorer window "w".
 */
void us_exploreredisphandler(WINDOWPART *w)
{
	INTSML wid, hei;
	REGISTER INTBIG visiblelines, thumbsize, thumbtop, thumbarea;
	REGISTER EXPLORERNODE *en;
	WINDOWPART ww;

	us_erasewindow(w);

	/* determine text size */
	us_exploretextsize = TXT10P;
	screensettextsize(w, us_exploretextsize);
	screengettextsize(w, "X", &wid, &hei);
	us_exploretextheight = hei;
	visiblelines = (w->usehy - w->usely) / us_exploretextheight;

	/* reset indication of which lines are visible */
	for(en = us_explorernodeused; en != NOEXPLORERNODE; en = en->nextexplorernode)
		en->flags &= ~EXNODESHOWN;

	/* redraw the explorer information */
	us_explorerdepth = 0;
	us_explorerhittopline = 0;
	us_exploretotallines = us_showexplorerstruct(w, us_firstexplorernode, 0, -us_explorefirstline) +
		us_explorefirstline;

	if (us_explorernodeselected != NOEXPLORERNODE) us_highlightexplorernode(w);

	/* draw the vertical slider on the right */
	ww.screenlx = ww.uselx = w->uselx;
	ww.screenhx = ww.usehx = w->usehx;
	ww.screenly = ww.usely = w->usely;
	ww.screenhy = ww.usehy = w->usehy;
	ww.frame = w->frame;
	ww.state = DISPWINDOW;
	computewindowscale(&ww);
	us_drawverticalslider(w, (INTSML)(w->usehx-DISPLAYSLIDERSIZE), w->usely, w->usehy, 0);

	thumbsize = visiblelines * 100 / us_exploretotallines;
	if (thumbsize >= 100) thumbsize = 100;
	if (thumbsize == 100 && us_explorefirstline == 0) return;
	thumbtop = us_explorefirstline * 100 / us_exploretotallines;
	thumbarea = w->usehy - w->usely - DISPLAYSLIDERSIZE*2;

	w->thumbhy = w->usehy - DISPLAYSLIDERSIZE - thumbarea * thumbtop / 100;
	w->thumbly = w->thumbhy - thumbarea * thumbsize / 100;
	if (w->thumbhy > w->usehy-DISPLAYSLIDERSIZE-2) w->thumbhy = w->usehy-DISPLAYSLIDERSIZE-2;
	if (w->thumbly < w->usely+DISPLAYSLIDERSIZE+2) w->thumbly = w->usely+DISPLAYSLIDERSIZE+2;
	us_drawverticalsliderthumb(w, w->usehx-DISPLAYSLIDERSIZE, w->usely, w->usehy,
		w->thumbly, w->thumbhy);
}

void us_highlightexplorernode(WINDOWPART *w)
{
	REGISTER INTBIG lowx, highx, lowy, highy;

	if ((us_explorernodeselected->flags&EXNODESHOWN) == 0) return;
	lowx = us_explorernodeselected->x + 3;
	highx = lowx + us_explorernodeselected->textwidth;
	if (highx > w->usehx-DISPLAYSLIDERSIZE) highx = w->usehx-DISPLAYSLIDERSIZE;
	lowy = us_explorernodeselected->y - 2;
	highy = lowy + us_exploretextheight;
	screeninvertbox(w, (INTSML)lowx, (INTSML)highx, (INTSML)lowy, (INTSML)highy);
}

/*
 * Routine called when up or down slider arrows are clicked.
 */
INTSML us_explorerarrowdown(INTBIG x, INTBIG y)
{
	INTBIG visiblelines;

	if (x < us_explorerwindow->usehx - DISPLAYSLIDERSIZE) return(0);
	visiblelines = (us_explorerwindow->usehy - us_explorerwindow->usely) / us_exploretextheight;
	switch (us_explorersliderpart)
	{
		case 0:   /* down arrow clicked */
			if (y > us_explorerwindow->usely + DISPLAYSLIDERSIZE) return(0);
			if (us_exploretotallines - us_explorefirstline <= visiblelines) return(0);
			us_explorefirstline++;
			us_exploreredisphandler(us_explorerwindow);
			break;
		case 1:   /* clicked below thumb */
			if (y > us_explorerwindow->thumbly) return(0);
			if (us_exploretotallines - us_explorefirstline <= visiblelines) return(0);
			us_explorefirstline += visiblelines-1;
			if (us_exploretotallines - us_explorefirstline < visiblelines)
				us_explorefirstline = us_exploretotallines - visiblelines;
			us_exploreredisphandler(us_explorerwindow);
			break;
		case 2:   /* clicked on thumb (not done here) */
			break;
		case 3:   /* clicked above thumb */
			if (y < us_explorerwindow->thumbhy) return(0);
			us_explorefirstline -= visiblelines-1;
			if (us_explorefirstline < 0) us_explorefirstline = 0;
			us_exploreredisphandler(us_explorerwindow);
			break;
		case 4:   /* up arrow clicked */
			if (y <= us_explorerwindow->usehy - DISPLAYSLIDERSIZE) return(0);
			if (us_explorefirstline > 0)
			{
				us_explorefirstline--;
				us_exploreredisphandler(us_explorerwindow);
			}
			break;
	}
	return(0);
}

/*
 * Button handler for explorer window "w".  Button "but" was pushed at (x, y).
 */
void us_explorebuttonhandler(WINDOWPART *w, INTSML but, INTSML x, INTSML y)
{
	REGISTER EXPLORERNODE *en;
	REGISTER WINDOWPART *ow;
	INTBIG lx, hx, ly, hy, visiblelines, newtop;

	if (x >= w->usehx - DISPLAYSLIDERSIZE)
	{
		/* click in vertical slider */
		visiblelines = (w->usehy - w->usely) / us_exploretextheight;
		if (y <= w->usely + DISPLAYSLIDERSIZE)
		{
			/* down arrow: shift base line (may repeat) */
			us_explorerwindow = w;
			us_explorersliderpart = 0;
			trackcursor(0, us_nullup, us_nullvoid, us_explorerarrowdown, us_nullchar,
				us_nullvoid, TRACKNORMAL);
			return;
		}
		if (y > w->usehy - DISPLAYSLIDERSIZE)
		{
			/* up arrow: shift base line (may repeat) */
			us_explorerwindow = w;
			us_explorersliderpart = 4;
			trackcursor(0, us_nullup, us_nullvoid, us_explorerarrowdown, us_nullchar,
				us_nullvoid, TRACKNORMAL);
			return;
		}
		if (y < w->thumbly)
		{
			/* below thumb: shift way down (may repeat) */
			us_explorerwindow = w;
			us_explorersliderpart = 1;
			trackcursor(0, us_nullup, us_nullvoid, us_explorerarrowdown, us_nullchar,
				us_nullvoid, TRACKNORMAL);
			return;
		}
		if (y > w->thumbhy)
		{
			/* above thumb: shift way up (may repeat) */
			us_explorerwindow = w;
			us_explorersliderpart = 3;
			trackcursor(0, us_nullup, us_nullvoid, us_explorerarrowdown, us_nullchar,
				us_nullvoid, TRACKNORMAL);
			return;
		}

		/* on the thumb: track its motion */
		if (visiblelines >= us_exploretotallines) return;
		us_vthumbbegin(y, w, w->usehx - DISPLAYSLIDERSIZE, w->usely, w->usehy,
			us_exploretotallines, 0);
		trackcursor(0, us_nullup, us_nullvoid, us_vthumbdown, us_nullchar,
			us_evthumbdone, TRACKNORMAL);
		newtop = us_evthumbend();
		if (newtop >= 0)
		{
			us_explorefirstline = (INTSML)newtop;
			us_exploreredisphandler(w);
		}
		return;
	}

	/* deselect */
	if (us_explorernodeselected != NOEXPLORERNODE) us_highlightexplorernode(w);
	us_explorernodeselected = NOEXPLORERNODE;

	for(en = us_explorernodeused; en != NOEXPLORERNODE; en = en->nextexplorernode)
	{
		if ((en->flags&EXNODESHOWN) == 0) continue;
		if (y < en->y || y >= en->y + us_exploretextheight) continue;
		if (x >= en->x - EXPLORERBLOCKSIZE && x <= en->x)
		{
			/* hit the box to the left of the name */
			if ((en->flags & EXNODEOPEN) != 0) en->flags &= ~EXNODEOPEN; else
				en->flags |= EXNODEOPEN;
			us_exploreredisphandler(w);
			return;
		}
		if (x >= en->x+3 && x <= en->x+3+en->textwidth)
		{
			/* hit the name of a facet/library: highlight it */
			us_explorernodeselected = en;
			us_highlightexplorernode(w);

			if (doublebutton(but) != 0)
			{
				/* double-click on name */
				if (en->facet == NONODEPROTO)
				{
					/* make this the current library */
					us_switchtolibrary(en->lib);
				} else
				{
					/* show this facet */
					for(ow = el_topwindowpart; ow != NOWINDOWPART; ow = ow->nextwindowpart)
					{
						if (ow == w) continue;
						if (strcmp(w->location, "entire") != 0)
						{
							if (ow->frame != w->frame) continue;
						}
						break;
					}
					us_fullview(en->facet, &lx, &hx, &ly, &hy);
					if (ow == NOWINDOWPART)
					{
						/* no other window can be found: create one */
						us_switchtofacet(en->facet, lx, hx, ly, hy, NONODEINST, NOPORTPROTO, 1, 0);
					} else
					{
						us_highlightwindow(ow, 0);
						us_switchtofacet(en->facet, lx, hx, ly, hy, NONODEINST, NOPORTPROTO, 0, 0);
					}
				}
			}
		}
	}
}

INTSML us_explorecharhandler(WINDOWPART *w, INTSML cmd)
{
	REGISTER NODEPROTO *np;
	extern COMCOMP us_yesnop;
	char *pars[3];
	REGISTER INTBIG i;

	if (cmd == 0177 || cmd == 010)
	{
		/* delete selected facet */
		if (us_explorernodeselected == NOEXPLORERNODE)
		{
			ttyputerr("Select a facet name before deleting it");
			return(0);
		}
		np = us_explorernodeselected->facet;
		if (np == NONODEPROTO)
		{
			pars[0] = "kill";
			pars[1] = us_explorernodeselected->lib->libname;
			us_library(2, pars);
			return(0);
		}
		if (np->firstinst != NONODEINST)
		{
			ttyputerr("Can only delete top-level facets");
			return(0);
		}
		(void)initinfstr();
		(void)addstringtoinfstr("Are you sure you want to delete facet ");
		(void)addstringtoinfstr(describenodeproto(np));
		i = ttygetparam(returninfstr(), &us_yesnop, 3, pars);
		if (i == 1)
		{
			if (pars[0][0] == 'y')
			{
				us_clearhighlightcount();
				pars[0] = describenodeproto(np);
				us_killfacet(1, pars);
			}
		}
		return(0);
	}
	return(us_charhandler(w, cmd));
}

/*
 * Routine to allocate a new "explorer node".  Returs NOEXPLORERNODE on error.
 */
EXPLORERNODE *us_allocexplorernode(void)
{
	REGISTER EXPLORERNODE *en;

	if (us_explorernodefree != NOEXPLORERNODE)
	{
		en = us_explorernodefree;
		us_explorernodefree = en->nextexplorernode;
	} else
	{
		en = (EXPLORERNODE *)emalloc(sizeof (EXPLORERNODE), us_aid->cluster);
		if (en == 0) return(NOEXPLORERNODE);
	}
	en->subexplorernode = NOEXPLORERNODE;
	en->nextsubexplorernode = NOEXPLORERNODE;
	en->facet = NONODEPROTO;
	en->lib = NOLIBRARY;
	en->flags = 0;
	en->count = 0;

	/* put into the global linked list */
	en->nextexplorernode = us_explorernodeused;
	us_explorernodeused = en;
	return(en);
}

/*
 * Routine to free explorer node "en" to the pool of unused nodes.
 */
void us_freeexplorernode(EXPLORERNODE *en)
{
	en->nextexplorernode = us_explorernodefree;
	us_explorernodefree = en;
}

/*********************************** ARC SUPPORT ***********************************/

/*
 * routine to modify the arcs in list "list".  The "change" field has the
 * following meaning:
 *  change=0   make arc rigid
 *  change=1   make arc un-rigid
 *  change=2   make arc fixed-angle
 *  change=3   make arc not fixed-angle
 *  change=4   make arc slidable
 *  change=5   make arc nonslidable
 *  change=6   make arc temporarily rigid
 *  change=7   make arc temporarily un-rigid
 *  change=8   make arc ends extend by half width
 *  change=9   make arc ends not extend by half width
 *  change=10  make arc directional
 *  change=11  make arc not directional
 *  change=12  make arc negated
 *  change=13  make arc not negated
 *  change=14  make arc skip the tail end
 *  change=15  make arc not skip the tail end
 *  change=16  make arc skip the head end
 *  change=17  make arc not skip the head end
 *  change=18  reverse ends of the arc
 *  change=19  clear arc constraints
 *  change=20  print arc constraints
 *  change=21  set special arc constraint from "prop"
 *  change=22  add to special arc constraint from "prop"
 *  change=23  toggle arc rigid
 *  change=24  toggle arc fixed-angle
 *  change=25  toggle arc slidable
 *  change=26  toggle arc ends extend
 *  change=27  toggle arc directional
 *  change=28  toggle arc negated
 *  change=29  toggle arc skip the tail
 *  change=30  toggle arc skip the head
 * If "redraw" is nonzero then the arcs are re-drawn to reflect the change.
 * The routine returns the number of arcs that were modified (-1 if an error
 * occurred).
 */
INTSML us_modarcbits(INTSML change, INTSML redraw, char *prop, GEOM **list)
{
	REGISTER INTSML total, affected;
	REGISTER INTBIG newvalue;
	REGISTER ARCINST *ai;

	/* must be a facet in the window */
	if (us_needfacet() == NONODEPROTO) return(-1);

	/* run through the list */
	affected = 0;
	for(total=0; list[total] != NOGEOM; total++)
	{
		if (list[total]->entrytype != OBJARCINST) continue;
		ai = list[total]->entryaddr.ai;

		/* notify system that arc is to be re-drawn */
		if (redraw != 0) startobjectchange((INTBIG)ai, VARCINST);

		/* change the arc state bits */
		switch (change)
		{
			case 0:			/* arc rigid */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPERIGID, 0) == 0)
					affected++;
				break;
			case 1:			/* arc un-rigid */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPEUNRIGID, 0) == 0)
					affected++;
				break;
			case 2:			/* arc fixed-angle */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPEFIXEDANGLE, 0) == 0)
					affected++;
				break;
			case 3:			/* arc not fixed-angle */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPENOTFIXEDANGLE, 0) == 0)
					affected++;
				break;
			case 4:			/* arc slidable */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPESLIDABLE, 0) == 0)
					affected++;
				break;
			case 5:			/* arc nonslidable */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPENOTSLIDABLE, 0) == 0)
					affected++;
				break;
			case 6:			/* arc temporarily rigid */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPETEMPRIGID, 0) == 0)
					affected++;
				break;
			case 7:			/* arc temporarily un-rigid */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPETEMPUNRIGID, 0) == 0)
					affected++;
				break;
			case 8:			/* arc ends extend by half width */
				if ((ai->userbits&NOEXTEND) != 0) affected++;
				(void)setval((INTBIG)ai, VARCINST, "userbits", ai->userbits & ~NOEXTEND, VINTEGER);
				break;
			case 9:			/* arc ends not extend by half width */
				if ((ai->userbits&NOEXTEND) == 0) affected++;
				(void)setval((INTBIG)ai, VARCINST, "userbits", ai->userbits|NOEXTEND, VINTEGER);
				break;
			case 10:			/* arc directional */
				if ((ai->userbits&ISDIRECTIONAL) == 0) affected++;
				(void)setval((INTBIG)ai, VARCINST, "userbits", ai->userbits|ISDIRECTIONAL, VINTEGER);
				break;
			case 11:			/* arc not directional */
				if ((ai->userbits&ISDIRECTIONAL) != 0) affected++;
				(void)setval((INTBIG)ai, VARCINST, "userbits", ai->userbits & ~ISDIRECTIONAL, VINTEGER);
				break;
			case 12:			/* arc negated */
				if ((ai->userbits&ISNEGATED) == 0) affected++;
				newvalue = ai->userbits | ISNEGATED;

				/* don't put the negation circle on a pin */
				if (((ai->end[0].nodeinst->proto->userbits&NFUNCTION) >> NFUNCTIONSH) == NPPIN &&
					((ai->end[1].nodeinst->proto->userbits&NFUNCTION) >> NFUNCTIONSH) != NPPIN)
						newvalue |= REVERSEEND;

				/* prefer output negation to input negation */
				if ((ai->end[0].portarcinst->proto->userbits&STATEBITS) == INPORT &&
					(ai->end[1].portarcinst->proto->userbits&STATEBITS) == OUTPORT)
						newvalue |= REVERSEEND;

				(void)setval((INTBIG)ai, VARCINST, "userbits", newvalue, VINTEGER);
				break;
			case 13:			/* arc not negated */
				if ((ai->userbits&ISNEGATED) != 0) affected++;
				(void)setval((INTBIG)ai, VARCINST, "userbits", ai->userbits & ~ISNEGATED, VINTEGER);
				break;
			case 14:			/* arc skip the tail end */
				if ((ai->userbits&NOTEND0) == 0) affected++;
				(void)setval((INTBIG)ai, VARCINST, "userbits", ai->userbits|NOTEND0, VINTEGER);
				break;
			case 15:			/* arc not skip the tail end */
				if ((ai->userbits&NOTEND0) != 0) affected++;
				(void)setval((INTBIG)ai, VARCINST, "userbits", ai->userbits & ~NOTEND0, VINTEGER);
				break;
			case 16:			/* arc skip the head end */
				if ((ai->userbits&NOTEND1) == 0) affected++;
				(void)setval((INTBIG)ai, VARCINST, "userbits", ai->userbits|NOTEND1, VINTEGER);
				break;
			case 17:			/* arc not skip the head end */
				if ((ai->userbits&NOTEND1) != 0) affected++;
				(void)setval((INTBIG)ai, VARCINST, "userbits", ai->userbits & ~NOTEND1, VINTEGER);
				break;
			case 18:			/* reverse ends of the arc */
				affected++;
				newvalue = (ai->userbits & ~REVERSEEND) | ((~ai->userbits) & REVERSEEND);
				(void)setval((INTBIG)ai, VARCINST, "userbits", newvalue, VINTEGER);
				break;
			case 19:			/* clear special arc properties */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST,
					CHANGETYPEFIXEDANGLE, (INTBIG)"") == 0) affected++;
				break;
			case 20:			/* print special arc properties */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST,
					CHANGETYPENOTFIXEDANGLE, (INTBIG)"") == 0) affected++;
				break;
			case 21:			/* set special arc properties */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST,
					CHANGETYPERIGID, (INTBIG)prop) == 0) affected++;
				break;
			case 22:			/* add to special arc properties */
				if ((*el_curconstraint->setobject)((INTBIG)ai, VARCINST,
					CHANGETYPEUNRIGID, (INTBIG)prop) == 0) affected++;
				break;
			case 23:			/* arc toggle rigid */
				if ((ai->userbits&FIXED) != 0)
					(void)(*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPEUNRIGID, 0); else
						(void)(*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPERIGID, 0);
				affected++;
				break;
			case 24:			/* arc toggle fixed-angle */
				if ((ai->userbits&FIXANG) != 0)
					(void)(*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPENOTFIXEDANGLE, 0); else
						(void)(*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPEFIXEDANGLE, 0);
				affected++;
				break;
			case 25:			/* arc toggle slidable */
				if ((ai->userbits&CANTSLIDE) != 0)
					(void)(*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPESLIDABLE, 0); else
						(void)(*el_curconstraint->setobject)((INTBIG)ai, VARCINST, CHANGETYPENOTSLIDABLE, 0);
				affected++;
				break;
			case 26:			/* arc toggle ends extend */
				if ((ai->userbits&NOEXTEND) != 0) newvalue = ai->userbits & ~NOEXTEND; else
					newvalue = ai->userbits | ~NOEXTEND;
				(void)setval((INTBIG)ai, VARCINST, "userbits", newvalue, VINTEGER);
				affected++;
				break;
			case 27:			/* arc toggle directional */
				if ((ai->userbits&ISDIRECTIONAL) != 0) newvalue = ai->userbits & ~ISDIRECTIONAL; else
					newvalue = ai->userbits | ISDIRECTIONAL;
				(void)setval((INTBIG)ai, VARCINST, "userbits", newvalue, VINTEGER);
				affected++;
				break;
			case 28:			/* arc toggle negated */
				if ((ai->userbits&ISNEGATED) != 0) newvalue = ai->userbits & ~ISNEGATED; else
					newvalue = ai->userbits | ISNEGATED;

				if ((newvalue&ISNEGATED) != 0)
				{
					/* don't put the negation circle on a pin */
					if (((ai->end[0].nodeinst->proto->userbits&NFUNCTION) >> NFUNCTIONSH) == NPPIN &&
						((ai->end[1].nodeinst->proto->userbits&NFUNCTION) >> NFUNCTIONSH) != NPPIN)
							newvalue |= REVERSEEND;

					/* prefer output negation to input negation */
					if ((ai->end[0].portarcinst->proto->userbits&STATEBITS) == INPORT &&
						(ai->end[1].portarcinst->proto->userbits&STATEBITS) == OUTPORT)
							newvalue |= REVERSEEND;
				}

				(void)setval((INTBIG)ai, VARCINST, "userbits", newvalue, VINTEGER);
				affected++;
				break;
			case 29:			/* arc toggle skip the tail */
				if ((ai->userbits&NOTEND0) != 0) newvalue = ai->userbits & ~NOTEND0; else
					newvalue = ai->userbits | NOTEND0;
				(void)setval((INTBIG)ai, VARCINST, "userbits", newvalue, VINTEGER);
				affected++;
				break;
			case 30:			/* arc toggle skip the head */
				if ((ai->userbits&NOTEND1) != 0) newvalue = ai->userbits & ~NOTEND1; else
					newvalue = ai->userbits | NOTEND1;
				(void)setval((INTBIG)ai, VARCINST, "userbits", newvalue, VINTEGER);
				affected++;
				break;
		}

		/* notify the system that the arc is done and can be re-drawn */
		if (redraw != 0) endobjectchange((INTBIG)ai, VARCINST);
	}

	return(affected);
}

/*
 * routine to set the arc name of arc "ai" to the name "name".  If "name" is
 * zero, remove the name
 */
void us_setarcname(ARCINST *ai, char *name)
{
	REGISTER VARIABLE *var;
	REGISTER INTBIG descript;

	startobjectchange((INTBIG)ai, VARCINST);
	if (name == 0) (void)delvalkey((INTBIG)ai, VARCINST, el_arc_name); else
	{
		var = getvalkey((INTBIG)ai, VARCINST, VSTRING, el_arc_name);
		if (var != NOVARIABLE) descript = var->textdescript; else
			descript = defaulttextdescript(NOGEOM);
		var = setvalkey((INTBIG)ai, VARCINST, el_arc_name, (INTBIG)name, VSTRING|VDISPLAY);
		if (var == NOVARIABLE) return;
		var->textdescript = descript;

		/* for zero-width arcs, adjust the location */
		if (ai->width == 0)
		{
			if (ai->end[0].ypos == ai->end[1].ypos)
			{
				/* zero-width horizontal arc has name above */
				modifydescript((INTBIG)ai, VARCINST, var,
					(var->textdescript & ~VTPOSITION) | VTPOSUP);
			} else if (ai->end[0].xpos == ai->end[1].xpos)
			{
				/* zero-width vertical arc has name to right */
				modifydescript((INTBIG)ai, VARCINST, var,
					(var->textdescript & ~VTPOSITION) | VTPOSRIGHT);
			}
		}
	}
	endobjectchange((INTBIG)ai, VARCINST);
}

/*
 * routine to determine the "userbits" to use for an arc of type "ap".
 */
INTBIG us_makearcuserbits(ARCPROTO *ap)
{
	REGISTER INTBIG bits, protobits;
	REGISTER VARIABLE *var;

	var = getvalkey((INTBIG)us_aid, VAID, VINTEGER, us_arcstyle);
	if (var != NOVARIABLE) protobits = var->addr; else
	{
		var = getvalkey((INTBIG)ap, VARCPROTO, VINTEGER, us_arcstyle);
		if (var != NOVARIABLE) protobits = var->addr; else
			protobits = ap->userbits;
	}
	bits = 0;
	if ((protobits&WANTFIXANG) != 0)      bits |= FIXANG;
	if ((protobits&WANTFIX) != 0)         bits |= FIXED;
	if ((protobits&WANTCANTSLIDE) != 0)   bits |= CANTSLIDE;
	if ((protobits&WANTNOEXTEND) != 0)    bits |= NOEXTEND;
	if ((protobits&WANTNEGATED) != 0)     bits |= ISNEGATED;
	if ((protobits&WANTDIRECTIONAL) != 0) bits |= ISDIRECTIONAL;
	return(bits);
}

/*********************************** NODE SUPPORT ***********************************/

/*
 * routine to travel through the network starting at nodeinst "ni" and set
 * "bit" in all nodes connected with arcs that do not spread.
 */
void us_nettravel(NODEINST *ni, INTBIG bit)
{
	REGISTER INTSML i;
	REGISTER PORTARCINST *pi;

	if ((ni->userbits & bit) != 0) return;
	ni->userbits |= bit;

	for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
	{
		if ((pi->conarcinst->userbits & SPREADA) != 0) continue;
		for(i=0; i<2; i++) us_nettravel(pi->conarcinst->end[i].nodeinst, bit);
	}
}

/*
 * Helper routine for "us_rotate()" to mark selected nodes that need not be
 * connected with an invisible arc.
 */
void us_spreadrotateconnection(NODEINST *theni)
{
	REGISTER PORTARCINST *pi;
	REGISTER ARCINST *ai;
	REGISTER NODEINST *ni;
	REGISTER INTBIG other;

	for(pi = theni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
	{
		ai = pi->conarcinst;
		if (ai->temp1 == 0) continue;
		if (ai->end[0].portarcinst == pi) other = 1; else other = 0;
		ni = ai->end[other].nodeinst;
		if (ni->temp1 != 0) continue;
		ni->temp1 = 1;
		us_spreadrotateconnection(ni);
	}
}

/*
 * routine to recursively travel along all arcs coming out of nodeinst "ni"
 * and set the SPREADN bit in the connecting nodeinst "userbits" if that node
 * is connected horizontally (if "hor" is nonzero) or connected vertically (if
 * "hor" is zero).  This is called from "spread" to propagate along manhattan
 * arcs that are in the correct orientation (along the spread line).
 */
void us_manhattantravel(NODEINST *ni, INTSML hor)
{
	REGISTER PORTARCINST *pi;
	REGISTER NODEINST *other;
	REGISTER ARCINST *ai;

	ni->userbits |= SPREADN;
	for(pi=ni->firstportarcinst; pi!=NOPORTARCINST; pi=pi->nextportarcinst)
	{
		ai = pi->conarcinst;
		if (hor != 0)
		{
			/* "hor" is nonzero: only want horizontal arcs */
			if (((ai->userbits&AANGLE)>>AANGLESH) != 0 &&
				((ai->userbits&AANGLE)>>AANGLESH) != 180) continue;
		} else
		{
			/* "hor" is zero: only want vertical arcs */
			if (((ai->userbits&AANGLE)>>AANGLESH) != 90 &&
				((ai->userbits&AANGLE)>>AANGLESH) != 270) continue;
		}
		if (ai->end[0].portarcinst == pi) other = ai->end[1].nodeinst; else
			other = ai->end[0].nodeinst;
		if ((other->userbits&SPREADN) != 0) continue;
		us_manhattantravel(other, hor);
	}
}

/*
 * routine to replace node "oldni" with a new one of type "newnp"
 * and return the new node.  Also removes any node-specific variables.
 */
NODEINST *us_replacenodeinst(NODEINST *oldni, NODEPROTO *newnp)
{
	REGISTER NODEINST *newni;
	REGISTER VARIABLE *var;
	REGISTER PORTARCINST *pi;
	REGISTER INTBIG i;
	typedef struct
	{
		char *variablename;
		NODEPROTO *prim;
	} POSSIBLEVARIABLES;
	static POSSIBLEVARIABLES killvariables[] =
	{
		{"SCHEM_flipflop_type",    NONODEPROTO},
		{"SCHEM_transistor_type",  NONODEPROTO},
		{"SCHEM_source",           NONODEPROTO},
		{"SCHEM_meter_type",       NONODEPROTO},
		{"SCHEM_diode",            NONODEPROTO},
		{"SCHEM_capacitance",      NONODEPROTO},
		{"SCHEM_resistance",       NONODEPROTO},
		{"SCHEM_inductance",       NONODEPROTO},
		{"SCHEM_twoport_type",     NONODEPROTO},
		{"SCHEM_function",         NONODEPROTO},
		{0, 0}
	};

	/* first start changes to node and all arcs touching this node */
	startobjectchange((INTBIG)oldni, VNODEINST);
	for(pi = oldni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
		startobjectchange((INTBIG)pi->conarcinst, VARCINST);

	/* replace the node */
	newni = replacenodeinst(oldni, newnp);
	if (newni != NONODEINST)
	{
		/* initialize the variable list */
		if (killvariables[0].prim == NONODEPROTO)
		{
			killvariables[0].prim = sch_ffprim;
			killvariables[1].prim = sch_transistorprim;
			killvariables[2].prim = sch_sourceprim;
			killvariables[3].prim = sch_meterprim;
			killvariables[4].prim = sch_diodeprim;
			killvariables[5].prim = sch_capacitorprim;
			killvariables[6].prim = sch_resistorprim;
			killvariables[7].prim = sch_inductorprim;
			killvariables[8].prim = sch_twoportprim;
			killvariables[9].prim = sch_bboxprim;
		}

		/* remove variables that make no sense */
		for(i=0; killvariables[i].variablename != 0; i++)
		{
			if (newni->proto == killvariables[i].prim) continue;
			var = getval((INTBIG)newni, VNODEINST, -1, killvariables[i].variablename);
			if (var != NOVARIABLE)
				delval((INTBIG)newni, VNODEINST, killvariables[i].variablename);
		}

		/* end changes to node and all arcs touching this node */
		endobjectchange((INTBIG)newni, VNODEINST);
		for(pi = newni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
			endobjectchange((INTBIG)pi->conarcinst, VARCINST);
	}
	return(newni);
}

void us_erasenodeinst(NODEINST *ni)
{
	REGISTER PORTARCINST *pi, *npi;
	REGISTER ARCINST *ai;
	REGISTER NODEINST *ni1, *ni2;

	/* erase all connecting arcs to this nodeinst */
	for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = npi)
	{
		npi = pi->nextportarcinst;

		/* don't delete if already dead */
		ai = pi->conarcinst;
		if ((ai->userbits&DEADA) != 0) continue;

		/* see if nodes need to be undrawn to account for "Steiner Point" changes */
		ni1 = ai->end[0].nodeinst;   ni2 = ai->end[1].nodeinst;
		if ((ni1->proto->userbits&WIPEON1OR2) != 0) startobjectchange((INTBIG)ni1, VNODEINST);
		if ((ni2->proto->userbits&WIPEON1OR2) != 0) startobjectchange((INTBIG)ni2, VNODEINST);

		startobjectchange((INTBIG)ai, VARCINST);
		if (killarcinst(ai)) ttyputerr("Error killing arc");

		/* see if nodes need to be redrawn to account for "Steiner Point" changes */
		if ((ni1->proto->userbits&WIPEON1OR2) != 0) endobjectchange((INTBIG)ni1, VNODEINST);
		if ((ni2->proto->userbits&WIPEON1OR2) != 0) endobjectchange((INTBIG)ni2, VNODEINST);
	}

	/* see if this nodeinst is a port of the facet */
	startobjectchange((INTBIG)ni, VNODEINST);
	if (ni->firstportexpinst != NOPORTEXPINST) us_undoportproto(ni, NOPORTPROTO);

	/* now erase the nodeinst */
	if (killnodeinst(ni)) ttyputerr("Error from killnodeinst");
}

/*
 * routine to kill a node between two arcs and join the arc as one.  Returns an error
 * code according to its success.
 */
INTSML us_erasepassthru(NODEINST *ni, INTSML allowdiffs)
{
	INTSML i, j;
	PORTARCINST *pi;
	NODEINST *reconno[2];
	PORTPROTO *reconpt[2];
	INTBIG reconx[2], recony[2], wid, bits, dx[2], dy[2];
	ARCINST *ai, *reconar[2];

	/* disallow erasing if lock is on */
	if (us_canedit(ni->parent, NONODEPROTO, 1) != 0) return(2);

	/* look for two arcs that will get merged */
	j = 0;
	for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
	{
		if (j >= 2) { j = 0;   break; }
		reconar[j] = pi->conarcinst;
		for(i=0; i<2; i++) if (pi->conarcinst->end[i].nodeinst != ni)
		{
			reconno[j] = pi->conarcinst->end[i].nodeinst;
			reconpt[j] = pi->conarcinst->end[i].portarcinst->proto;
			reconx[j] = pi->conarcinst->end[i].xpos;
			dx[j] = reconx[j] - pi->conarcinst->end[i==0].xpos;
			recony[j] = pi->conarcinst->end[i].ypos;
			dy[j] = recony[j] - pi->conarcinst->end[i==0].ypos;
		}
		j++;
	}
	if (j != 2) return(j);

	/* verify that the two arcs to merge have the same type */
	if (reconar[0]->proto != reconar[1]->proto) return(-1);

	if (allowdiffs == 0)
	{
		/* verify that the two arcs to merge have the same width */
		if (reconar[0]->width != reconar[1]->width) return(-2);

		/* verify that the two arcs have the same slope */
		if ((dx[1]*dy[0]) != (dx[0]*dy[1])) return(-3);
	}

	/* remember facts about the new arcinst */
	wid = reconar[0]->width;
	bits = reconar[0]->userbits | reconar[1]->userbits;

	/* special code to handle directionality */
	if ((bits&(ISDIRECTIONAL|ISNEGATED|NOTEND0|NOTEND1|REVERSEEND)) != 0)
	{
		/* reverse ends if the arcs point the wrong way */
		for(i=0; i<2; i++)
		{
			if (reconar[i]->end[i].nodeinst == ni)
			{
				if ((reconar[i]->userbits&REVERSEEND) == 0)
					reconar[i]->userbits |= REVERSEEND; else
						reconar[i]->userbits &= ~REVERSEEND;
			}
		}
		bits = reconar[0]->userbits | reconar[1]->userbits;

		/* two negations make a positive */
		if ((reconar[0]->userbits&ISNEGATED) != 0 &&
			(reconar[1]->userbits&ISNEGATED) != 0) bits &= ~ISNEGATED;
	}

	/* erase the nodeinst, as requested (this will erase connecting arcs) */
	us_erasenodeinst(ni);

	/* make the new arcinst */
	ai = newarcinst(reconar[0]->proto, wid, bits, reconno[0], reconpt[0], reconx[0],
		recony[0], reconno[1], reconpt[1], reconx[1], recony[1], ni->parent);
	if (ai == NOARCINST) return(-5);

	(void)copyvars((INTBIG)reconar[0], VARCINST, (INTBIG)ai, VARCINST);
	(void)copyvars((INTBIG)reconar[1], VARCINST, (INTBIG)ai, VARCINST);
	endobjectchange((INTBIG)ai, VARCINST);
	ai->changed = 0;
	return(2);
}

/*********************************** ARRAYING FROM A FILE ***********************************/

#define MAXLINE 200
INTSML us_getlinefromfile(FILE *io, char *line, INTSML limit);

#define NOARRAYALIGN ((ARRAYALIGN *)-1)

typedef struct Iarrayalign
{
	char *facet;
	char *inport;
	char *outport;
	struct Iarrayalign *nextarrayalign;
} ARRAYALIGN;

#define NOPORTASSOCIATE ((PORTASSOCIATE *)-1)

typedef struct Iportassociate
{
	NODEINST *ni;
	PORTPROTO *pp;
	PORTPROTO *corepp;
	struct Iportassociate *nextportassociate;
} PORTASSOCIATE;

/*
 * Routine to read file "file" and create an array.  The file has this format:
 *   celllibrary LIBFILE
 *   facet FACETNAME
 *   core FACETNAME
 *   align FACETNAME INPORT OUTPORT
 *   place FACETNAME [gap=DIST] [padport=coreport]*
 *   rotate (c | cc)
 */
void us_arrayfromfile(char *file)
{
	FILE *io;
	char *truename, line[MAXLINE], *pt, *start, save, *par[2],
		*libname;
	REGISTER INTBIG lineno, gap, gapx, gapy, lx, ly, hx, hy, cx, cy;
	INTBIG ax, ay, ox, oy;
	REGISTER PORTPROTO *pp;
	REGISTER INTSML angle;
	REGISTER LIBRARY *lib, *savelib;
	REGISTER NODEPROTO *np, *facet, *corenp;
	REGISTER NODEINST *ni, *lastni;
	REGISTER ARCINST *ai;
	REGISTER ARRAYALIGN *aa, *firstaa;
	REGISTER PORTASSOCIATE *pa, *firstpa;
	extern AIDENTRY *io_aid;

	io = xopen(file, FILETYPEARRAY, 0, &truename);
	if (io == 0) return;

	firstaa = NOARRAYALIGN;
	firstpa = NOPORTASSOCIATE;
	lineno = 0;
	angle = 0;
	facet = corenp = NONODEPROTO;
	lastni = NONODEINST;
	lib = NOLIBRARY;
	for(;;)
	{
		if (us_getlinefromfile(io, line, MAXLINE) != 0) break;
		lineno++;
		pt = line;
		while (*pt == ' ' || *pt == '\t') pt++;
		if (*pt == 0 || *pt == ';') continue;
		start = pt;
		while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
		if (*pt == 0)
		{
			us_abortcommand("Line %ld: too short", lineno);
			break;
		}
		*pt++ = 0;
		if (namesame(start, "celllibrary") == 0)
		{
			while (*pt == ' ' || *pt == '\t') pt++;
			start = pt;
			while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
			*pt = 0;
			libname = skippath(start);
			lib = getlibrary(libname);
			if (lib == NOLIBRARY)
			{
				lib = newlibrary(libname, start);
				if (askaid(io_aid, "read", (INTBIG)lib, (INTBIG)"binary", 0) != 0)
				{
					us_abortcommand("Line %ld: cannot read library %s", lineno,
						start);
					break;
				}
			}
			continue;
		}
		if (namesame(start, "facet") == 0)
		{
			while (*pt == ' ' || *pt == '\t') pt++;
			start = pt;
			while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
			*pt = 0;
			facet = newnodeproto(start, el_curlib);
			if (facet == NONODEPROTO)
			{
				us_abortcommand("Line %ld: unable to create facet '%s'", lineno, start);
				break;
			}
			continue;
		}
		if (namesame(start, "core") == 0)
		{
			while (*pt == ' ' || *pt == '\t') pt++;
			start = pt;
			while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
			*pt = 0;
			corenp = getnodeproto(start);
			if (corenp == NONODEPROTO)
			{
				us_abortcommand("Line %ld: cannot find core facet '%s'", lineno, start);
				break;
			}
			continue;
		}
		if (namesame(start, "rotate") == 0)
		{
			while (*pt == ' ' || *pt == '\t') pt++;
			start = pt;
			while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
			*pt++ = 0;
			if (namesame(start, "c") == 0) angle = (angle + 2700) % 3600; else
				if (namesame(start, "cc") == 0) angle = (angle + 900) % 3600; else
			{
				us_abortcommand("Line %ld: incorrect rotation: %s", lineno, start);
				break;
			}
			continue;
		}
		if (namesame(start, "align") == 0)
		{
			aa = (ARRAYALIGN *)emalloc(sizeof (ARRAYALIGN), el_tempcluster);
			if (aa == 0) break;
			while (*pt == ' ' || *pt == '\t') pt++;
			start = pt;
			while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
			if (*pt == 0)
			{
				us_abortcommand("Line %ld: missing 'in port' name", lineno);
				break;
			}
			*pt++ = 0;
			allocstring(&aa->facet, start, el_tempcluster);

			while (*pt == ' ' || *pt == '\t') pt++;
			start = pt;
			while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
			if (*pt == 0)
			{
				us_abortcommand("Line %ld: missing 'out port'", lineno);
				break;
			}
			*pt++ = 0;
			allocstring(&aa->inport, start, el_tempcluster);

			while (*pt == ' ' || *pt == '\t') pt++;
			start = pt;
			while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
			*pt = 0;
			allocstring(&aa->outport, start, el_tempcluster);
			aa->nextarrayalign = firstaa;
			firstaa = aa;
			continue;
		}
		if (namesame(start, "place") == 0)
		{
			if (facet == NONODEPROTO)
			{
				us_abortcommand("Line %ld: no 'facet' line specified for 'place'",
					lineno);
				break;
			}
			while (*pt == ' ' || *pt == '\t') pt++;
			start = pt;
			while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
			save = *pt;
			*pt = 0;

			np = getnodeproto(start);
			if (np == NONODEPROTO && lib != NOLIBRARY && lib != el_curlib)
			{
				savelib = el_curlib;
				el_curlib = lib;
				np = getnodeproto(start);
				el_curlib = savelib;
				if (np != NONODEPROTO)
				{
					np = us_copyrecursively(np, np->cell->cellname,
						el_curlib, np->cellview, 0, "");
				}
			}
			if (np == NONODEPROTO)
			{
				us_abortcommand("Line %ld: cannot find facet '%s'", lineno, start);
				break;
			}
			*pt = save;
			gap = 0;
			while (*pt != 0)
			{
				while (*pt == ' ' || *pt == '\t') pt++;
				if (*pt == 0) break;
				start = pt;
				while (*pt != ' ' && *pt != '\t' && *pt != '=' && *pt != 0) pt++;
				save = *pt;
				*pt = 0;
				if (namesame(start, "gap") == 0)
				{
					*pt = save;
					if (*pt != '=')
					{
						us_abortcommand("Line %ld: missing '=' after 'gap'", lineno);
						break;
					}
					pt++;
					while (*pt == ' ' || *pt == '\t') pt++;
					gap = atoi(pt) * el_curtech->deflambda;
					while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
				} else
				{
					pa = (PORTASSOCIATE *)emalloc(sizeof (PORTASSOCIATE), el_tempcluster);
					if (pa == 0) break;
					pa->ni = NONODEINST;
					pa->pp = getportproto(np, start);
					if (pa->pp == NOPORTPROTO)
					{
						us_abortcommand("Line %ld: no port '%s' on facet '%s'",
							lineno, start, describenodeproto(np));
						break;
					}
					*pt = save;
					if (*pt != '=')
					{
						us_abortcommand("Line %ld: missing '=' after pad port name",
							lineno);
						break;
					}
					pt++;
					while (*pt == ' ' || *pt == '\t') pt++;
					start = pt;
					while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
					save = *pt;
					*pt = 0;
					if (corenp == NONODEPROTO)
					{
						us_abortcommand("Line %ld: no core facet for association",
							lineno);
						break;
					}
					pa->corepp = getportproto(corenp, start);
					if (pa->corepp == NOPORTPROTO)
					{
						us_abortcommand("Line %ld: no port '%s' on facet '%s'",
							lineno, start, describenodeproto(corenp));
						break;
					}
					*pt = save;
					pa->nextportassociate = firstpa;
					firstpa = pa;
				}
			}

			/* place the pad */
			if (lastni != NONODEINST)
			{
				/* find the "outport" on the last node */
				for(aa = firstaa; aa != NOARRAYALIGN; aa = aa->nextarrayalign)
					if (namesame(aa->facet, lastni->proto->cell->cellname) == 0) break;
				if (aa == NOARRAYALIGN)
				{
					us_abortcommand("Line %ld: no port alignment given for facet %s",
						lineno, describenodeproto(lastni->proto));
					break;
				}
				for(pp = lastni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
					if (namesame(aa->outport, pp->protoname) == 0) break;
				if (pp == NOPORTPROTO)
				{
					us_abortcommand("Line %ld: no port called '%s' on facet %s",
						lineno, aa->outport, describenodeproto(lastni->proto));
					break;
				}
				portposition(lastni, pp, &ax, &ay);

				/* find the "inport" on the new node */
				for(aa = firstaa; aa != NOARRAYALIGN; aa = aa->nextarrayalign)
					if (namesame(aa->facet, np->cell->cellname) == 0) break;
				if (aa == NOARRAYALIGN)
				{
					us_abortcommand("Line %ld: no port alignment given for facet %s",
						lineno, describenodeproto(np));
					break;
				}
				for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
					if (namesame(aa->inport, pp->protoname) == 0) break;
				if (pp == NOPORTPROTO)
				{
					us_abortcommand("Line %ld: no port called '%s' on facet %s",
						lineno, aa->inport, describenodeproto(np));
					break;
				}
			}
			cx = cy = 0;
			lx = cx - (np->highx - np->lowx) / 2;   hx = lx + (np->highx - np->lowx);
			ly = cy - (np->highy - np->lowy) / 2;   hy = ly + (np->highy - np->lowy);
			ni = newnodeinst(np, lx, hx, ly, hy, 0, angle, facet);
			if (ni == NONODEINST)
			{
				us_abortcommand("Line %ld: problem creating %s instance",
					lineno, describenodeproto(np));
				break;
			}
			if (lastni != NONODEINST)
			{
				switch (angle)
				{
					case 0:    gapx =  gap;   gapy =    0;   break;
					case 900:  gapx =    0;   gapy =  gap;   break;
					case 1800: gapx = -gap;   gapy =    0;   break;
					case 2700: gapx =    0;   gapy = -gap;   break;
				}
				portposition(ni, pp, &ox, &oy);
				modifynodeinst(ni, ax-ox+gapx, ay-oy+gapy, ax-ox+gapx, ay-oy+gapy, 0, 0);
			}
			endobjectchange((INTBIG)ni, VNODEINST);
			lastni = ni;

			/* fill in the port associations */
			for(pa = firstpa; pa != NOPORTASSOCIATE; pa = pa->nextportassociate)
				if (pa->ni == NONODEINST) pa->ni = ni;
			continue;
		}
		us_abortcommand("Line %ld: unknown keyword '%s'", lineno, start);
		break;
	}

	/* place the core if one was specified */
	if (corenp != NONODEPROTO)
	{
		(*el_curconstraint->solve)(facet);
		cx = (facet->lowx + facet->highx) / 2;
		cy = (facet->lowy + facet->highy) / 2;
		lx = cx - (corenp->highx - corenp->lowx) / 2;
		hx = lx + (corenp->highx - corenp->lowx);
		ly = cy - (corenp->highy - corenp->lowy) / 2;
		hy = ly + (corenp->highy - corenp->lowy);
		ni = newnodeinst(corenp, lx, hx, ly, hy, 0, 0, facet);
		if (ni != NONODEINST)
		{
			endobjectchange((INTBIG)ni, VNODEINST);
		}

		/* attach unrouted wires */
		for(pa = firstpa; pa != NOPORTASSOCIATE; pa = pa->nextportassociate)
		{
			if (pa->ni == NONODEINST) continue;
			portposition(ni, pa->corepp, &ox, &oy);
			portposition(pa->ni, pa->pp, &ax, &ay);
			ai = newarcinst(gen_unroutedarc, gen_unroutedarc->nominalwidth,
				us_makearcuserbits(gen_unroutedarc), ni, pa->corepp, ox, oy,
					pa->ni, pa->pp, ax, ay, facet);
			if (ai != NOARCINST)
			{
				endobjectchange((INTBIG)ai, VARCINST);
			}
		}
	}

	/* done with the array file */
	xclose(io);

	/* cleanup memory */
	while (firstpa != NOPORTASSOCIATE)
	{
		pa = firstpa;
		firstpa = pa->nextportassociate;
		efree((char *)pa);
	}
	while (firstaa != NOARRAYALIGN)
	{
		aa = firstaa;
		firstaa = aa->nextarrayalign;
		efree(aa->facet);
		efree(aa->inport);
		efree(aa->outport);
		efree((char *)aa);
	}

	/* show the new facet */
	par[0] = describenodeproto(facet);
	us_editfacet(1, par);
}

INTSML us_getlinefromfile(FILE *io, char *line, INTSML limit)
{
	REGISTER char *pt;
	REGISTER INTSML i, c;

	pt = line;
	for(i=0; i<limit; i++)
	{
		c = xgetc(io);
		if (c == EOF) return(1);
		if (c == '\n' || c == '\r') break;
		*pt++ = (char)c;
	}
	*pt = 0;
	return(0);
}

/*********************************** NODE AND ARC SUPPORT ***********************************/

/*
 * routine to yank the contents of complex node instance "topno" into its
 * parent facet.
 */
void us_yankonenode(NODEINST *topno)
{
	REGISTER NODEINST *ni, *newni;
	REGISTER ARCINST *ai, *newar;
	REGISTER PORTARCINST *pi, *nextpi;
	REGISTER PORTEXPINST *pe, *nextpe;
	REGISTER PORTPROTO *pp, *newpp;
	REGISTER NODEPROTO *np;
	REGISTER ARCPROTO *ap;
	NODEINST *noa[2];
	PORTPROTO *pta[2];
	REGISTER char *portname;
	REGISTER INTBIG wid, i, oldbits, lowx, highx, lowy, highy;
	XARRAY localtrans, localrot, trans;
	INTBIG nox[2], noy[2], newxc, newyc, xc, yc;
	INTSML newang;
	static POLYGON *poly = NOPOLYGON;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);

	/* make transformation matrix for this facet */
	np = topno->proto;
	maketrans(topno, localtrans);
	makerot(topno, localrot);
	transmult(localtrans, localrot, trans);

	/* copy the nodes */
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		/* do not yank "facet center" primitives */
		if (ni->proto == gen_facetcenterprim)
		{
			ni->temp1 = (INTBIG)NONODEINST;
			continue;
		}

		/* this "center" computation is unstable for odd size nodes */
		xc = (ni->lowx + ni->highx) / 2;   yc = (ni->lowy + ni->highy) / 2;
		xform(xc, yc, &newxc, &newyc, trans);
		lowx = ni->lowx + newxc - xc;
		lowy = ni->lowy + newyc - yc;
		highx = ni->highx + newxc - xc;
		highy = ni->highy + newyc - yc;
		if (ni->transpose == 0) newang = ni->rotation + topno->rotation; else
			newang = ni->rotation + 3600 - topno->rotation;
		newang = newang % 3600;   if (newang < 0) newang += 3600;
		newni = newnodeinst(ni->proto, lowx, highx, lowy, highy,
			(INTSML)((ni->transpose+topno->transpose)&1), newang, topno->parent);
		if (newni == NONODEINST)
		{
			us_abortcommand("Cannot create node in this facet");
			return;
		}
		ni->temp1 = (INTBIG)newni;
		newni->userbits = ni->userbits;
		newni->textdescript = ni->textdescript;
		(void)copyvars((INTBIG)ni, VNODEINST, (INTBIG)newni, VNODEINST);
		endobjectchange((INTBIG)newni, VNODEINST);
	}

	/* copy the arcs */
	for(ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		/* ignore arcs connected to nodes that didn't get yanked */
		if ((NODEINST *)ai->end[0].nodeinst->temp1 == NONODEINST ||
			(NODEINST *)ai->end[1].nodeinst->temp1 == NONODEINST) continue;

		xform(ai->end[0].xpos, ai->end[0].ypos, &nox[0], &noy[0], trans);
		xform(ai->end[1].xpos, ai->end[1].ypos, &nox[1], &noy[1], trans);

		/* make sure end 0 fits in the port */
		shapeportpoly((NODEINST *)ai->end[0].nodeinst->temp1, ai->end[0].portarcinst->proto, poly, 0);
		if (isinside(nox[0], noy[0], poly) == 0)
			portposition((NODEINST *)ai->end[0].nodeinst->temp1,
				ai->end[0].portarcinst->proto, &nox[0], &noy[0]);

		/* make sure end 1 fits in the port */
		shapeportpoly((NODEINST *)ai->end[1].nodeinst->temp1, ai->end[1].portarcinst->proto, poly, 0);
		if (isinside(nox[1], noy[1], poly) == 0)
			portposition((NODEINST *)ai->end[1].nodeinst->temp1,
				ai->end[1].portarcinst->proto, &nox[1], &noy[1]);

		newar = newarcinst(ai->proto, ai->width, ai->userbits, (NODEINST *)ai->end[0].nodeinst->temp1,
			ai->end[0].portarcinst->proto, nox[0], noy[0], (NODEINST *)ai->end[1].nodeinst->temp1,
				ai->end[1].portarcinst->proto, nox[1], noy[1], topno->parent);
		if (newar == NOARCINST)
		{
			us_abortcommand("Cannot create arc in this facet");
			return;
		}
		(void)copyvars((INTBIG)ai, VARCINST, (INTBIG)newar, VARCINST);
		for(i=0; i<2; i++)
			(void)copyvars((INTBIG)ai->end[i].portarcinst, VPORTARCINST,
				(INTBIG)newar->end[i].portarcinst, VPORTARCINST);
		endobjectchange((INTBIG)newar, VARCINST);
	}

	/* replace arcs to this facet */
	for(pi = topno->firstportarcinst; pi != NOPORTARCINST; pi = nextpi)
	{
		/* remember facts about this arcinst */
		nextpi = pi->nextportarcinst;
		ai = pi->conarcinst;  ap = ai->proto;
		if ((ai->userbits&DEADA) != 0) continue;
		wid = ai->width;  oldbits = ai->userbits;
		for(i=0; i<2; i++)
		{
			noa[i] = ai->end[i].nodeinst;
			pta[i] = ai->end[i].portarcinst->proto;
			nox[i] = ai->end[i].xpos;   noy[i] = ai->end[i].ypos;
			if (noa[i] != topno) continue;
			noa[i] = (NODEINST *)ai->end[i].portarcinst->proto->subnodeinst->temp1;
			pta[i] = ai->end[i].portarcinst->proto->subportproto;
		}
		if (noa[0] == NONODEINST || noa[1] == NONODEINST) continue;
		startobjectchange((INTBIG)ai, VARCINST);
		if (killarcinst(ai)) ttyputerr("Error killing arc");
		newar = newarcinst(ap, wid, oldbits, noa[0], pta[0], nox[0], noy[0],
			noa[1], pta[1], nox[1], noy[1], topno->parent);
		if (newar == NOARCINST)
		{
			us_abortcommand("Cannot create arc to this facet");
			return;
		}

		/* copy variables (this presumes killed arc is not yet deallocated) */
		(void)copyvars((INTBIG)ai, VARCINST, (INTBIG)newar, VARCINST);
		for(i=0; i<2; i++)
			(void)copyvars((INTBIG)ai->end[i].portarcinst, VPORTARCINST,
				(INTBIG)newar->end[i].portarcinst, VPORTARCINST);
		endobjectchange((INTBIG)newar, VARCINST);
	}

	/* replace the exported ports */
	for(pe = topno->firstportexpinst; pe != NOPORTEXPINST; pe = nextpe)
	{
		nextpe = pe->nextportexpinst;
		pp = pe->proto;
		if ((NODEINST *)pp->subnodeinst->temp1 == NONODEINST) continue;
		if (moveportproto(topno->parent, pe->exportproto,
			(NODEINST *)pp->subnodeinst->temp1, pp->subportproto))
				ttyputerr("Moveportproto error");
	}

	/* copy the exported ports if requested */
	if ((us_useroptions&DUPCOPIESPORTS) != 0)
		for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		ni = (NODEINST *)pp->subnodeinst->temp1;
		if (ni == NONODEINST) continue;

		/* don't copy if the port is wired or already exported */
		for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
			if (pi->proto == pp->subportproto) break;
		if (pi != NOPORTARCINST) continue;
		for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
			if (pe->proto == pp->subportproto) break;
		if (pe != NOPORTEXPINST) continue;

		/* copy the port */
		portname = us_uniqueportname(pp->protoname, topno->parent);
		newpp = newportproto(topno->parent, ni, pp->subportproto, portname);
		if (newpp == NOPORTPROTO)
			ttyputerr("Cannot keep port %s", pp->protoname); else
		{
			newpp->userbits = (newpp->userbits & ~STATEBITS) | (pp->userbits & STATEBITS);
			newpp->textdescript = pp->textdescript;
			(void)copyvars((INTBIG)pp, VPORTPROTO, (INTBIG)newpp, VPORTPROTO);
			(void)copyvars((INTBIG)pp->subportexpinst, VPORTEXPINST,
				(INTBIG)newpp->subportexpinst, VPORTEXPINST);
		}
	}

	/* delete the facet */
	startobjectchange((INTBIG)topno, VNODEINST);
	if (killnodeinst(topno)) ttyputerr("Killnodeinst error");
}

/*
 * Routine to cut the selected nodes and arcs from the current facet.
 * They are copied to the "clipboard" facet in the "clipboard" library.
 */
void us_cutobjects(WINDOWPART *w)
{
	REGISTER NODEPROTO *np;
	REGISTER GEOM **list, *geom;
	REGISTER NODEINST *ni;
	REGISTER INTBIG i;
	REGISTER VIEW *saveview;

	/* get objects to cut */
	np = us_needfacet();
	if (np == NONODEPROTO) return;
	list = us_gethighlighted(OBJNODEINST | OBJARCINST);
	if (list[0] == NOGEOM)
	{
		us_abortcommand("First select objects to copy");
		return;
	}

	/* remove contents of clipboard */
	while (us_clipboardfacet->firstarcinst != NOARCINST)
	{
		startobjectchange((INTBIG)us_clipboardfacet->firstarcinst, VARCINST);
		killarcinst(us_clipboardfacet->firstarcinst);
	}
	while (us_clipboardfacet->firstportproto != NOPORTPROTO)
		killportproto(us_clipboardfacet, us_clipboardfacet->firstportproto);
	while (us_clipboardfacet->firstnodeinst != NONODEINST)
	{
		startobjectchange((INTBIG)us_clipboardfacet->firstnodeinst, VNODEINST);
		killnodeinst(us_clipboardfacet->firstnodeinst);
	}

	/* copy objects to clipboard */
	us_clipboardfacet->cellview = np->cellview;
	saveview = us_clipboardfacet->cellview;
	us_copylisttofacet(list, np, us_clipboardfacet, 0, 0);
	us_clipboardfacet->cellview = saveview;

	/* then delete it all */
	us_clearhighlightcount();
	for(i=0; list[i] != NOGEOM; i++)
	{
		geom = list[i];
		if (geom->entrytype != OBJARCINST) continue;
		startobjectchange((INTBIG)geom->entryaddr.ai, VARCINST);
		killarcinst(geom->entryaddr.ai);
	}
	for(i=0; list[i] != NOGEOM; i++)
	{
		geom = list[i];
		if (geom->entrytype != OBJNODEINST) continue;
		ni = geom->entryaddr.ni;
		while(ni->firstportexpinst != NOPORTEXPINST)
			killportproto(np, ni->firstportexpinst->exportproto);
		startobjectchange((INTBIG)geom->entryaddr.ni, VNODEINST);
		killnodeinst(ni);
	}
}

/*
 * Routine to copy the selected nodes and arcs from the current facet.
 * They are copied to the "clipboard" facet in the "clipboard" library.
 */
void us_copyobjects(WINDOWPART *w)
{
	REGISTER NODEPROTO *np;
	REGISTER GEOM **list;
	REGISTER VIEW *saveview;

	/* get objects to copy */
	np = us_needfacet();
	if (np == NONODEPROTO) return;
	list = us_gethighlighted(OBJNODEINST | OBJARCINST);
	if (list[0] == NOGEOM)
	{
		us_abortcommand("First select objects to copy");
		return;
	}

	/* remove contents of clipboard */
	while (us_clipboardfacet->firstarcinst != NOARCINST)
	{
		startobjectchange((INTBIG)us_clipboardfacet->firstarcinst, VARCINST);
		killarcinst(us_clipboardfacet->firstarcinst);
	}
	while (us_clipboardfacet->firstportproto != NOPORTPROTO)
		killportproto(us_clipboardfacet, us_clipboardfacet->firstportproto);
	while (us_clipboardfacet->firstnodeinst != NONODEINST)
	{
		startobjectchange((INTBIG)us_clipboardfacet->firstnodeinst, VNODEINST);
		killnodeinst(us_clipboardfacet->firstnodeinst);
	}

	/* copy objects to clipboard */
	us_clipboardfacet->cellview = np->cellview;
	saveview = us_clipboardfacet->cellview;
	us_copylisttofacet(list, np, us_clipboardfacet, 0, 0);
	us_clipboardfacet->cellview = saveview;
}

/*
 * Routine to paste nodes and arcs from the clipboard to the current facet.
 * They are copied from the "clipboard" facet in the "clipboard" library.
 */
void us_pasteobjects(WINDOWPART *w)
{
	REGISTER NODEPROTO *np;
	REGISTER GEOM **list;
	REGISTER INTBIG total;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER INTSML interactiveplace;

	/* get objects to paste */
	np = us_needfacet();
	if (np == NONODEPROTO) return;
	total = 0;
	for(ni = us_clipboardfacet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		total++;
	for(ai = us_clipboardfacet->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
		total++;
	if (total == 0)
	{
		us_abortcommand("Nothing in the clipboard to paste");
		return;
	}
	list = (GEOM **)emalloc((total+1) * (sizeof (GEOM *)), el_tempcluster);
	if (list == 0) return;
	total = 0;
	for(ni = us_clipboardfacet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		list[total++] = ni->geom;
	for(ai = us_clipboardfacet->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
		list[total++] = ai->geom;
	list[total] = NOGEOM;

	/* paste them into the current facet */
/*	if ((us_useroptions&NOMOVEAFTERDUP) != 0) interactiveplace = 0; else */
		interactiveplace = 1;
	us_copylisttofacet(list, us_clipboardfacet, np, 1, interactiveplace);
	efree((char *)list);
}

/*
 * Routine to copy the list of objects in "list" (NOGEOM terminated) from "fromfacet"
 * to "tofacet".  If "highlight" is nonzero, highlight the objects in the new facet.
 * If "interactiveplace" is nonzero, interactively select the location in the new facet.
 */
void us_copylisttofacet(GEOM **list, NODEPROTO *fromfacet, NODEPROTO *tofacet, INTSML highlight,
	INTSML interactiveplace)
{
	REGISTER NODEINST *ni, *newni, **nodelist;
	REGISTER ARCINST *ai, *newar;
	REGISTER PORTEXPINST *pe;
	REGISTER PORTPROTO *pp;
	REGISTER INTSML i, j;
	REGISTER INTBIG wid, dx, dy;
	REGISTER char *portname;
	INTBIG xcur, ycur, lx, hx, ly, hy, bestlx, bestly;
	INTSML total;
	REGISTER VARIABLE *var;
	HIGHLIGHT newhigh;
	static POLYGON *poly = NOPOLYGON;

	/* make sure the destination facet can be modified */
	if (us_canedit(tofacet, NONODEPROTO, 1) != 0) return;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);

	/* make sure they are all in the same facet */
	for(i=0; list[i] != NOGEOM; i++)
	{
		if (fromfacet != geomparent(list[i]))
		{
			us_abortcommand("All duplicated objects must be in the same facet");
			return;
		}
	}

	/* mark all nodes (including those touched by highlighted arcs) */
	for(ni = fromfacet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		ni->temp1 = 0;
	for(i=0; list[i] != NOGEOM; i++) if (list[i]->entrytype == OBJARCINST)
	{
		ai = list[i]->entryaddr.ai;
		ai->end[0].nodeinst->temp1 = ai->end[1].nodeinst->temp1 = 1;
	}
	for(i=0; list[i] != NOGEOM; i++)
	{
		if (list[i]->entrytype != OBJNODEINST) continue;
		ni = list[i]->entryaddr.ni;

		/* check for facet instance lock */
		if (ni->proto->primindex == 0 && (tofacet->userbits&NILOCKED) != 0)
		{
			if (us_canedit(tofacet, ni->proto, 1) != 0) continue;
		}
		ni->temp1 = 1;
	}

	/* count the number of nodes */
	for(total=0, ni = fromfacet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		if (ni->temp1 != 0) total++;
	if (total == 0) return;

	/* build a list that includes all nodes touching copied arcs */
	nodelist = (NODEINST **)emalloc((total * (sizeof (NODEINST *))), el_tempcluster);
	if (nodelist == 0) return;
	for(i=0, ni = fromfacet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		if (ni->temp1 != 0) nodelist[i++] = ni;

	/* check for recursion */
	for(i=0; i<total; i++) if (nodelist[i]->proto->primindex == 0)
		if (nodelist[i]->proto->primindex == 0)
	{
		if (isachildof(tofacet, nodelist[i]->proto))
		{
			us_abortcommand("Cannot: that would be recursive");
			efree((char *)nodelist);
			return;
		}
	}

	/* figure out lower-left corner of this collection of objects */
	us_getlowleft(nodelist[0], &bestlx, &bestly);
	for(i=1; i<total; i++)
	{
		us_getlowleft(nodelist[i], &lx, &ly);
		if (lx < bestlx) bestlx = lx;
		if (ly < bestly) bestly = ly;
	}
	for(i=0; list[i] != NOGEOM; i++) if (list[i]->entrytype == OBJARCINST)
	{
		ai = list[i]->entryaddr.ai;
		wid = ai->width - arcwidthoffset(ai);
		makearcpoly(ai->length, wid, ai, poly, FILLED);
		getbbox(poly, &lx, &hx, &ly, &hy);
		if (lx < bestlx) bestlx = lx;
		if (ly < bestly) bestly = ly;
	}

	/* adjust this corner so that, after grid alignment, objects are in the same location */
	gridalign(&bestlx, &bestly, us_alignment);

	/* special case when moving one node: account for facet center */
	if (total == 1 && list[1] == NOGEOM)
	{
		ni = nodelist[0];
		corneroffset(ni, ni->proto, ni->rotation, ni->transpose, &lx, &ly,
			(INTSML)((us_useroptions&CENTEREDPRIMITIVES) != 0 ? 1 : 0));
		bestlx = ni->lowx + lx;
		bestly = ni->lowy + ly;

		if (ni->proto->primindex != 0 && (us_useroptions&CENTEREDPRIMITIVES) == 0)
		{
			/* adjust this corner so that, after grid alignment, objects are in the same location */
			gridalign(&bestlx, &bestly, us_alignment);
		}
	}

	/* remove highlighting if planning to highlight new stuff */
	if (highlight != 0) us_clearhighlightcount();

	if (interactiveplace != 0)
	{
		/* adjust the cursor position if selecting interactively */
		if ((us_aid->aidstate&INTERACTIVE) != 0)
		{
			us_multidraginit(bestlx, bestly, list, nodelist, total, 0, 0);
			trackcursor(0, us_ignoreup, us_multidragbegin, us_multidragdown,
				us_stoponchar, us_multidragup, TRACKDRAGGING);
			if (el_pleasestop != 0)
			{
				efree((char *)nodelist);
				return;
			}
		}

		/* get aligned cursor co-ordinates */
		if (us_demandxy(&xcur, &ycur))
		{
			efree((char *)nodelist);
			return;
		}
		gridalign(&xcur, &ycur, us_alignment);

		dx = xcur-bestlx;
		dy = ycur-bestly;
	} else
	{
		if (us_dupdistset == 0)
		{
			us_dupdistset = 1;
			us_dupx = el_curtech->deflambda * 10;
			us_dupy = el_curtech->deflambda * 10;
		}
		dx = us_dupx;
		dy = us_dupy;
	}

	/* create the new objects */
	for(i=0; i<total; i++)
	{
		ni = nodelist[i];
		newni = newnodeinst(ni->proto, ni->lowx+dx, ni->highx+dx, ni->lowy+dy,
			ni->highy+dy, ni->transpose, ni->rotation, tofacet);
		if (newni == NONODEINST)
		{
			us_abortcommand("Cannot create node");
			efree((char *)nodelist);
			return;
		}
		newni->userbits = ni->userbits & ~(WIPED|NSHORT);
		(void)us_copyvars((INTBIG)ni, VNODEINST, (INTBIG)newni, VNODEINST);
		endobjectchange((INTBIG)newni, VNODEINST);
		ni->temp1 = (INTBIG)newni;
		if (i == 0) us_dupnode = newni;

		/* copy the ports, too */
		if ((us_useroptions&DUPCOPIESPORTS) != 0)
		{
			for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
			{
				portname = us_uniqueportname(pe->exportproto->protoname, tofacet);
				pp = newportproto(tofacet, newni, pe->proto, portname);
				if (pp == NOPORTPROTO)
				{
					us_abortcommand("Cannot create port %s", portname);
					efree((char *)nodelist);
					return;
				}
				pp->userbits = pe->exportproto->userbits;
				pp->textdescript = pe->exportproto->textdescript;
				if (copyvars((INTBIG)pe->exportproto, VPORTPROTO, (INTBIG)pp, VPORTPROTO) != 0)
				{
					efree((char *)nodelist);
					return;
				}
			}
		}
	}
	for(i=0; list[i] != NOGEOM; i++) if (list[i]->entrytype == OBJARCINST)
	{
		ai = list[i]->entryaddr.ai;
		newar = newarcinst(ai->proto, ai->width, ai->userbits,
			(NODEINST *)ai->end[0].nodeinst->temp1, ai->end[0].portarcinst->proto, ai->end[0].xpos+dx,
				ai->end[0].ypos+dy, (NODEINST *)ai->end[1].nodeinst->temp1,
					ai->end[1].portarcinst->proto, ai->end[1].xpos+dx, ai->end[1].ypos+dy, tofacet);
		if (newar == NOARCINST)
		{
			us_abortcommand("Cannot create arc");
			efree((char *)nodelist);
			return;
		}
		(void)us_copyvars((INTBIG)ai, VARCINST, (INTBIG)newar, VARCINST);
		for(j=0; j<2; j++)
			(void)copyvars((INTBIG)ai->end[j].portarcinst, VPORTARCINST,
				(INTBIG)newar->end[j].portarcinst, VPORTARCINST);
		endobjectchange((INTBIG)newar, VARCINST);
		ai->temp1 = (INTBIG)newar;
	}

	/* highlight the copy */
	if (highlight != 0)
	{
		for(i=0; i<total; i++)
		{
			ni = (NODEINST *)nodelist[i]->temp1;
			newhigh.status = HIGHFROM;
			newhigh.fromport = NOPORTPROTO;
			newhigh.frompoint = 0;
			newhigh.facet = tofacet;
			newhigh.fromgeom = ni->geom;

			/* special case for displayable text on invisible pins */
			if (ni->proto == gen_invispinprim)
			{
				for(j=0; j<ni->numvar; j++)
				{
					var = &ni->firstvar[j];
					if ((var->type&VDISPLAY) != 0) break;
				}
				if (j < ni->numvar)
				{
					newhigh.status = HIGHTEXT;
					newhigh.fromvar = var;
				}
			}
			(void)us_addhighlight(&newhigh);
		}
		for(i=0; list[i] != NOGEOM; i++) if (list[i]->entrytype == OBJARCINST)
		{
			ai = (ARCINST *)list[i]->entryaddr.ai->temp1;
			newhigh.status = HIGHFROM;
			newhigh.fromport = NOPORTPROTO;
			newhigh.frompoint = 0;
			newhigh.facet = tofacet;
			newhigh.fromgeom = ai->geom;
			(void)us_addhighlight(&newhigh);
		}
	}
	efree((char *)nodelist);
}

/*
 * routine to move the arcs in the GEOM module list "list" (terminated by
 * NOGEOM) and the "total" nodes in the list "nodelist" by (dx, dy).
 */
void us_manymove(GEOM **list, NODEINST **nodelist, INTSML total, INTBIG dx, INTBIG dy)
{
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai, *oai;
	REGISTER NODEPROTO *np;
	REGISTER INTBIG i, j, k, otherend;
	INTBIG e[2];

	/* special case if moving only one node */
	if (total == 1 && list[1] == NOGEOM)
	{
		ni = nodelist[0];
		startobjectchange((INTBIG)ni, VNODEINST);
		modifynodeinst(ni, dx, dy, dx, dy, 0, 0);
		endobjectchange((INTBIG)ni, VNODEINST);
		return;
	}

	/* remember the location of every node */
	np = geomparent(list[0]);
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		ni->temp1 = ni->lowx;
		ni->temp2 = ni->lowy;
	}
	for(ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		ai->temp1 = (ai->end[0].xpos + ai->end[1].xpos) / 2;
		ai->temp2 = (ai->end[0].ypos + ai->end[1].ypos) / 2;
	}

	/* look at all nodes and move them appropriately */
	for(i=0; list[i] != NOGEOM; i++)
	{
		if (list[i]->entrytype != OBJNODEINST) continue;
		ni = list[i]->entryaddr.ni;
		if (ni->lowx == ni->temp1 && ni->lowy == ni->temp2)
		{
			for(j=0; list[j] != NOGEOM; j++)
				if (list[j]->entrytype == OBJARCINST)
					(void)(*el_curconstraint->setobject)((INTBIG)list[j]->entryaddr.ai,
						VARCINST, CHANGETYPETEMPRIGID, 0);
			startobjectchange((INTBIG)ni, VNODEINST);
			modifynodeinst(ni, dx-(ni->lowx-ni->temp1), dy-(ni->lowy-ni->temp2),
				dx-(ni->lowx-ni->temp1), dy-(ni->lowy-ni->temp2), 0, 0);
			endobjectchange((INTBIG)ni, VNODEINST);
		}
	}

	/* look at all arcs and move them appropriately */
	for(i=0; list[i] != NOGEOM; i++)
	{
		if (list[i]->entrytype != OBJARCINST) continue;
		ai = list[i]->entryaddr.ai;
		if (ai->temp1 != (ai->end[0].xpos + ai->end[1].xpos) / 2 ||
			ai->temp2 != (ai->end[0].ypos + ai->end[1].ypos) / 2) continue;

		/* see if the arc moves in its ports */
		if ((ai->userbits&(FIXED|CANTSLIDE)) != 0) e[0] = e[1] = 0; else
		{
			e[0] = db_stillinport(ai, 0, ai->end[0].xpos+dx, ai->end[0].ypos+dy);
			e[1] = db_stillinport(ai, 1, ai->end[1].xpos+dx, ai->end[1].ypos+dy);
		}

		/* if both ends slide in their port, move the arc */
		if (e[0] != 0 && e[1] != 0)
		{
			startobjectchange((INTBIG)ai, VARCINST);
			(void)modifyarcinst(ai, 0, dx, dy, dx, dy);
			endobjectchange((INTBIG)ai, VARCINST);
			continue;
		}

		/* if neither end can slide in its port, move the nodes */
		if (e[0] == 0 && e[1] == 0)
		{
			for(k=0; k<2; k++)
			{
				ni = ai->end[k].nodeinst;
				if (ni->lowx != ni->temp1 || ni->lowy != ni->temp2) continue;

				/* fix all arcs that aren't sliding */
				for(j=0; list[j] != NOGEOM; j++)
				{
					if (list[j]->entrytype != OBJARCINST) continue;
					oai = list[j]->entryaddr.ai;
					if (oai->temp1 != (oai->end[0].xpos + oai->end[1].xpos) / 2 ||
						oai->temp2 != (oai->end[0].ypos + oai->end[1].ypos) / 2) continue;
					if (db_stillinport(oai, 0, ai->end[0].xpos+dx, ai->end[0].ypos+dy) != 0 ||
						db_stillinport(oai, 1, ai->end[1].xpos+dx, ai->end[1].ypos+dy) != 0) continue;
					(void)(*el_curconstraint->setobject)((INTBIG)oai,
						VARCINST, CHANGETYPETEMPRIGID, 0);
				}
				startobjectchange((INTBIG)ni, VNODEINST);
				modifynodeinst(ni, dx-(ni->lowx-ni->temp1), dy-(ni->lowy-ni->temp2),
					dx-(ni->lowx-ni->temp1), dy-(ni->lowy-ni->temp2), 0, 0);
				endobjectchange((INTBIG)ni, VNODEINST);
			}
			continue;
		}

		/* only one end is slidable: move other node and the arc */
		for(k=0; k<2; k++)
		{
			if (e[k] != 0) continue;
			ni = ai->end[k].nodeinst;
			if (ni->lowx == ni->temp1 && ni->lowy == ni->temp2)
			{
				/* node "ni" hasn't moved yet but must because arc motion forces it */
				for(j=0; list[j] != NOGEOM; j++)
				{
					if (list[j]->entrytype != OBJARCINST) continue;
					oai = list[j]->entryaddr.ai;
					if (oai->temp1 != (oai->end[0].xpos + oai->end[1].xpos) / 2 ||
						oai->temp2 != (oai->end[0].ypos + oai->end[1].ypos) / 2) continue;
					if (oai->end[0].nodeinst == ni) otherend = 1; else otherend = 0;
					if (db_stillinport(oai, (INTSML)otherend, ai->end[otherend].xpos+dx,
						ai->end[otherend].ypos+dy) != 0) continue;
					(void)(*el_curconstraint->setobject)((INTBIG)oai,
						VARCINST, CHANGETYPETEMPRIGID, 0);
				}
				startobjectchange((INTBIG)ni, VNODEINST);
				modifynodeinst(ni, dx-(ni->lowx-ni->temp1), dy-(ni->lowy-ni->temp2),
					dx-(ni->lowx-ni->temp1), dy-(ni->lowy-ni->temp2), 0, 0);
				endobjectchange((INTBIG)ni, VNODEINST);

				if (ai->temp1 != (ai->end[0].xpos + ai->end[1].xpos) / 2 ||
					ai->temp2 != (ai->end[0].ypos + ai->end[1].ypos) / 2) continue;
				startobjectchange((INTBIG)ai, VARCINST);
				(void)modifyarcinst(ai, 0, dx, dy, dx, dy);
				endobjectchange((INTBIG)ai, VARCINST);
			}
		}
	}
}

/*********************************** VARIABLE SUPPORT ***********************************/

INTSML us_copyvars(INTBIG fromaddr, INTBIG fromtype, INTBIG toaddr, INTBIG totype)
{
	REGISTER INTSML ret;
	REGISTER INTBIG i, hyphenpos, len;
	REGISTER VARIABLE *var, *thisvar;
	REGISTER INTBIG highest;
	REGISTER NODEINST *ni, *thisni;
	REGISTER NODEPROTO *facet;
	REGISTER ARCINST *ai, *thisai;
	REGISTER char *objname, *othername;
	char addon[20];

	ret = copyvars(fromaddr, fromtype, toaddr, totype);
	if (ret != 0) return(ret);

	/* examine destination for node or arc names and make them unique */
	if (totype == VNODEINST)
	{
		thisvar = getvalkey(toaddr, totype, VSTRING, el_node_name);
		if (thisvar != NOVARIABLE)
		{
			/* find a unique node name */
			thisni = (NODEINST *)toaddr;
			objname = (char *)thisvar->addr;

			/* see if this variable name has the form "XXX-NUMBER" */
			len = strlen(objname);
			for(hyphenpos = len-1; hyphenpos>1; hyphenpos--)
				if (ispunct(objname[hyphenpos]) || !isanumber(&objname[hyphenpos])) break;
			if (hyphenpos == len-1 || !ispunct(objname[hyphenpos])) hyphenpos = len;

			highest = -1;
			for(ni = thisni->parent->firstnodeinst; ni != NONODEINST;
				ni = ni->nextnodeinst)
			{
				if (ni == thisni) continue;
				var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, el_node_name);
				if (var == NOVARIABLE) continue;
				othername = (char *)var->addr;
				if (namesamen(othername, objname, hyphenpos) != 0) continue;
				if (ispunct(othername[hyphenpos]) &&
					isanumber(&othername[hyphenpos+1]) != 0)
				{
					i = atoi(&othername[hyphenpos+1]);
					if (i > highest) highest = i;
				}
			}

			if (highest >= 0)
			{
				/* assign name based on highest number found */
				(void)sprintf(addon, "%c%ld", us_separatechar, highest+1);
				(void)initinfstr();
				for(i=0; i<hyphenpos; i++) (void)addtoinfstr(objname[i]);
				(void)addstringtoinfstr(addon);
				(void)setvalkey(toaddr, totype, el_node_name,
					(INTBIG)returninfstr(), VSTRING|(thisvar->type&VDISPLAY));
			} else
			{
				/* assign name with "_1" at the end */
				(void)initinfstr();
				(void)addstringtoinfstr(objname);
				(void)addtoinfstr((char)us_separatechar);
				(void)addstringtoinfstr("1");
				(void)setvalkey(toaddr, totype, el_node_name,
					(INTBIG)returninfstr(), VSTRING|(thisvar->type&VDISPLAY));
			}
		}
	}
	if (totype == VARCINST)
	{
		/* do not modify arc names in schematics: they become disjoint networks */
		thisai = (ARCINST *)toaddr;
		facet = thisai->parent;
		if (facet->cellview == el_schematicview ||
			(facet->cellview->viewstate&MULTIPAGEVIEW) != 0) return(ret);

		thisvar = getvalkey(toaddr, totype, VSTRING, el_arc_name);
		if (thisvar != NOVARIABLE)
		{
			/* find a unique arc name */
			objname = (char *)thisvar->addr;

			/* see if this variable name has the form "XXX-NUMBER" */
			len = strlen(objname);
			for(hyphenpos = len-1; hyphenpos>1; hyphenpos--)
				if (ispunct(objname[hyphenpos]) || !isanumber(&objname[hyphenpos])) break;
			if (hyphenpos == len-1 || !ispunct(objname[hyphenpos])) hyphenpos = len;

			highest = -1;
			for(ai = thisai->parent->firstarcinst; ai != NOARCINST;
				ai = ai->nextarcinst)
			{
				if (ai == thisai) continue;
				var = getvalkey((INTBIG)ai, VARCINST, VSTRING, el_arc_name);
				if (var == NOVARIABLE) continue;
				othername = (char *)var->addr;
				if (namesamen(othername, objname, hyphenpos) != 0) continue;
				if (ispunct(othername[hyphenpos]) &&
					isanumber(&othername[hyphenpos+1]) != 0)
				{
					i = atoi(&othername[hyphenpos+1]);
					if (i > highest) highest = i;
				}
			}

			if (highest >= 0)
			{
				/* assign name based on highest number found */
				(void)sprintf(addon, "%c%ld", us_separatechar, highest+1);
				(void)initinfstr();
				for(i=0; i<hyphenpos; i++) (void)addtoinfstr(objname[i]);
				(void)addstringtoinfstr(addon);
				(void)setvalkey(toaddr, totype, el_arc_name,
					(INTBIG)returninfstr(), VSTRING|(thisvar->type&VDISPLAY));
			} else
			{
				/* assign name with "_1" at the end */
				(void)initinfstr();
				(void)addstringtoinfstr(objname);
				(void)addtoinfstr((char)us_separatechar);
				(void)addstringtoinfstr("1");
				(void)setvalkey(toaddr, totype, el_arc_name,
					(INTBIG)returninfstr(), VSTRING|(thisvar->type&VDISPLAY));
			}
		}
	}
	return(ret);
}

/*********************************** PORT SUPPORT ***********************************/

/*
 * routine to obtain details about a "port" command in "count" and "par".
 * The node under consideration is in "ni", and the port under consideration
 * if "ppt" (which is NOPORTPROTO if no particular port is under consideration).
 * The port characteristic bits are set in "bits" and the parts of these bits
 * that are set have mask bits set into "mask".  The port to be affected is
 * returned.  If "wantexp" is nonzero, the desired port should already be
 * exported (otherwise it should not be exported).  If "intendedname" is set,
 * it is the name that will be given to the port when it is exported.  The
 * routine returns NOPORTPROTO if there is an error.
 */
PORTPROTO *us_portdetails(PORTPROTO *ppt, INTSML count, char *par[], NODEINST *ni,
	INTBIG *bits, INTBIG *mask, INTSML wantexp, char *intendedname)
{
	REGISTER PORTPROTO *wantpp, *pp;
	HIGHLIGHT high;
	INTBIG x, y;
	REGISTER INTSML i, l, m, pindex, specify;
	REGISTER INTBIG onlx, only, onhx, onhy, bestx, besty;
	REGISTER PORTEXPINST *pe;
	REGISTER NODEPROTO *np;
	REGISTER VARIABLE *var;
	REGISTER char *pt;
	char *newpar;
	static struct
	{
		char  *name;
		INTSML  significant;
		UINTBIG bits, mask;
	} portparse[] =
	{
		{"input",         1, INPORT,           STATEBITS},
		{"output",        1, OUTPORT,          STATEBITS},
		{"bidirectional", 2, BIDIRPORT,        STATEBITS},
		{"power",         1, PWRPORT,          STATEBITS},
		{"ground",        1, GNDPORT,          STATEBITS},
		{"clock1",        6, C1PORT,           STATEBITS},
		{"clock2",        6, C2PORT,           STATEBITS},
		{"clock3",        6, C3PORT,           STATEBITS},
		{"clock4",        6, C4PORT,           STATEBITS},
		{"clock5",        6, C5PORT,           STATEBITS},
		{"clock6",        6, C6PORT,           STATEBITS},
		{"clock",         1, CLKPORT,          STATEBITS},
		{"refout",        4, REFOUTPORT,       STATEBITS},
		{"refin",         4, REFINPORT,        STATEBITS},
		{"none",          1, 0,                STATEBITS|PORTDRAWN|BODYONLY},
		{"always-drawn",  1, PORTDRAWN,        PORTDRAWN},
		{"body-only",     2, BODYONLY,         BODYONLY},
		{NULL, 0, 0, 0}
	};

	/* quick sanity check first */
	np = ni->proto;
	if (np->firstportproto == NOPORTPROTO)
	{
		us_abortcommand("This node has no ports");
		return(NOPORTPROTO);
	}

	/* prepare to parse parameters */
	wantpp = NOPORTPROTO;
	specify = 0;
	*bits = *mask = 0;

	/* look at all parameters */
	for(i=0; i<count; i++)
	{
		l = strlen(pt = par[i]);

		/* check the basic characteristics from the table */
		for(m=0; portparse[m].name != 0; m++)
			if (namesamen(pt, portparse[m].name, l) == 0 && l >= portparse[m].significant)
		{
			*bits |= portparse[m].bits;
			*mask |= portparse[m].mask;
			break;
		}
		if (portparse[m].name != 0) continue;

		if (namesamen(pt, "specify", l) == 0 && l >= 1)
		{ specify++; continue; }
		if (namesamen(pt, "use", l) == 0 && l >= 1)
		{
			if (i+1 >= count)
			{
				us_abortcommand("Usage: port use PORTNAME");
				return(NOPORTPROTO);
			}
			i++;
			if (wantexp == 0)
			{
				/* want to export: look for any port on the node */
				for(wantpp = np->firstportproto; wantpp != NOPORTPROTO; wantpp = wantpp->nextportproto)
					if (namesame(wantpp->protoname, par[i]) == 0) break;
				if (wantpp == NOPORTPROTO)
				{
					us_abortcommand("No port called %s", par[i]);
					return(NOPORTPROTO);
				}
			} else
			{
				/* want exported ports: look specificially for them */
				for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
					if (namesame(pe->exportproto->protoname, par[i]) == 0) break;
				if (pe != NOPORTEXPINST) wantpp = pe->exportproto; else
				{
					us_abortcommand("No port exported as %s", par[i]);
					return(NOPORTPROTO);
				}
			}
			continue;
		}
		us_abortcommand("Bad PORT option: %s", pt);
		return(NOPORTPROTO);
	}

	/* if no port explicitly found, use default (if any) */
	if (wantpp == NOPORTPROTO) wantpp = ppt;

	/* if no port found and heuristics are allowed, try them */
	if (wantpp == NOPORTPROTO && specify == 0)
	{
		/* if there is only one possible port, use it */
		if (wantexp == 0)
		{
			/* look for only unexported port */
			pindex = 0;
			for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
			{
				for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
					if (pe->proto == pp) break;
				if (pe != NOPORTEXPINST) continue;
				pindex++;
				wantpp = pp;
			}
			if (pindex != 1) wantpp = NOPORTPROTO;
		} else
		{
			/* if there is one exported port return it */
			pe = ni->firstportexpinst;
			if (pe != NOPORTEXPINST && pe->nextportexpinst == NOPORTEXPINST)
				wantpp = pe->exportproto;
		}

		/* if a port is highlighted, use it */
		if (wantpp == NOPORTPROTO)
		{
			var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_highlighted);
			if (var != NOVARIABLE)
			{
				if (getlength(var) == 1)
				{
					(void)us_makehighlight(((char **)var->addr)[0], &high);
					if ((high.status&HIGHTYPE) == HIGHFROM && high.fromport != NOPORTPROTO)
					{
						pp = high.fromport;
						if (wantexp == 0) wantpp = pp; else
						{
							for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
								if (pe->proto == pp) break;
							if (pe != NOPORTEXPINST) wantpp = pe->exportproto; else
							{
								us_abortcommand("Port %s must be exported first", pp->protoname);
								return(NOPORTPROTO);
							}
						}
					}
				}
			}
		}

		/* if exporting port with the same name as the subportinst, use it */
		if (wantpp == NOPORTPROTO && *intendedname != 0 && wantexp == 0)
		{
			for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
				if (namesame(intendedname, pp->protoname) == 0)
			{
				wantpp = pp;
				break;
			}
		}

		/* if port is on one edge of the facet and is being exported, use it */
		if (wantpp == NOPORTPROTO && wantexp == 0)
		{
			if (ni->geom->lowx == ni->parent->lowx) onlx = 1; else onlx = 0;
			if (ni->geom->highx == ni->parent->highx) onhx = 1; else onhx = 0;
			if (ni->geom->lowy == ni->parent->lowy) only = 1; else only = 0;
			if (ni->geom->highy == ni->parent->highy) onhy = 1; else onhy = 0;
			if (onlx+onhx+only+onhy == 1)
			{
				/* look for one port on the node that is on the proper edge */
				bestx = (ni->lowx+ni->highx)/2;
				besty = (ni->lowy+ni->highy)/2;
				wantpp = NOPORTPROTO;
				for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
				{
					portposition(ni, pp, &x, &y);
					if (onlx != 0 && x == bestx) wantpp = NOPORTPROTO;
					if (onlx != 0 && x < bestx)
					{
						wantpp = pp;  bestx = x;
					}
					if (onhx != 0 && x == bestx) wantpp = NOPORTPROTO;
					if (onhx != 0 && x > bestx)
					{
						wantpp = pp;  bestx = x;
					}
					if (only != 0 && y == besty) wantpp = NOPORTPROTO;
					if (only != 0 && y < besty)
					{
						wantpp = pp;  besty = y;
					}
					if (onhy != 0 && y == besty) wantpp = NOPORTPROTO;
					if (onhy != 0 && y > besty)
					{
						wantpp = pp;  besty = y;
					}
				}
			}
		}
	}

	/* give up and ask the port name wanted */
	if (wantpp == NOPORTPROTO)
	{
		us_identifyports(ni, ni->proto, LAYERA);
		ttyputerr("Which port of node %s is to be the port:", describenodeproto(np));
		pindex = 0;
		for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
			ttyputmsg("%d: %s", ++pindex, pp->protoname);
		for(;;)
		{
			newpar = ttygetline("Select a number: ");
			if (newpar == 0 || *newpar == 0)
			{
				us_abortedmsg();
				break;
			}
			i = atoi(newpar);
			if (i <= 0 || i > pindex)
			{
				ttyputerr("Please select a number from 1 to %d (default aborts)", pindex);
				continue;
			}

			/* convert to a port */
			x = 0;
			for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
				if (++x == i) break;
			if (wantexp == 0) wantpp = pp; else
			{
				for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
					if (pe->proto == pp) break;
				if (pe == NOPORTEXPINST)
				{
					ttyputerr("That port is not exported");
					continue;
				}
				wantpp = pe->exportproto;
			}
			break;
		}
		us_identifyports(ni, ni->proto, ALLOFF);
	}

	/* finally, return the port */
	return(wantpp);
}

/*
 * routine to recursively delete ports at nodeinst "ni" and all arcs connected
 * to them anywhere.  If "spt" is not NOPORTPROTO, delete only that portproto
 * on this nodeinst (and its hierarchically related ports).  Otherwise delete
 * all portprotos on this nodeinst.
 */
void us_undoportproto(NODEINST *ni, PORTPROTO *spt)
{
	REGISTER PORTEXPINST *pe, *nextpe;

	for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = nextpe)
	{
		nextpe = pe->nextportexpinst;
		if (spt != NOPORTPROTO && spt != pe->exportproto) continue;
		if (killportproto(pe->exportproto->parent, pe->exportproto))
			ttyputerr("killportproto error");
	}
}

/*
 * Routine to synchronize the ports in facets in the current library with
 * like-named facets in library "olib".
 */
void us_portsynchronize(LIBRARY *olib)
{
	REGISTER NODEPROTO *np, *onp;
	REGISTER PORTPROTO *pp, *opp;
	REGISTER INTBIG lx, hx, ly, hy, newports, nofacets;
	REGISTER NODEINST *ni, *oni;

	newports = nofacets = 0;
	for(np = el_curlib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
	{
		/* find this facet in the other library */
		for(onp = olib->firstnodeproto; onp != NONODEPROTO; onp = onp->nextnodeproto)
		{
			if (namesame(np->cell->cellname, onp->cell->cellname) != 0) continue;

			/* synchronize the ports */
			for(opp = onp->firstportproto; opp != NOPORTPROTO; opp = opp->nextportproto)
			{
				/* see if that other facet's port is in this one */
				for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
					if (namesame(opp->protoname, pp->protoname) == 0) break;
				if (pp != NOPORTPROTO) continue;

				/* must add port "opp" to facet "np" */
				oni = opp->subnodeinst;
				if (oni->proto->primindex == 0)
				{
					if (nofacets == 0)
						ttyputerr("Cannot yet export ports that come from other facet instances (i.e. port %s in facet %s)",
							opp->protoname, describenodeproto(onp));
					nofacets = 1;
					continue;
				}

				/* presume that the facets have the same coordinate system */
				lx = oni->lowx;
				hx = oni->highx;
				ly = oni->lowy;
				hy = oni->highy;

				ni = newnodeinst(oni->proto, lx, hx, ly, hy, oni->transpose, oni->rotation, np);
				if (ni == NONODEINST) continue;
				pp = newportproto(np, ni, opp->subportproto, opp->protoname);
				if (pp == NOPORTPROTO) return;
				pp->userbits = opp->userbits;
				pp->textdescript = opp->textdescript;
				if (copyvars((INTBIG)opp, VPORTPROTO, (INTBIG)pp, VPORTPROTO) != 0)
					return;
				endobjectchange((INTBIG)ni, VNODEINST);
				newports++;
			}
		}
	}
	ttyputmsg("Created %ld new %s", newports, makeplural("port", newports));
}

/*
 * routine to determine a path down from the currently highlighted port.  Returns
 * the subnode and subport in "hini" and "hipp" (sets them to NONODEINST and
 * NOPORTPROTO if no lower path is defined).
 */
void us_findlowerport(NODEINST **hini, PORTPROTO **hipp)
{
	HIGHLIGHT high;
	REGISTER VARIABLE *var;
	REGISTER INTSML len;
	NODEINST *ni;
	PORTPROTO *pp;

	/* presume no lower port */
	*hini = NONODEINST;
	*hipp = NOPORTPROTO;

	/* must be 1 highlighted object */
	var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_highlighted);
	if (var == NOVARIABLE) return;
	len = (INTSML)getlength(var);
	if (len > 1) return;

	/* get the highlighted object */
	if (us_makehighlight(((char **)var->addr)[0], &high) != 0) return;

	/* see if it is a port */
	if ((high.status&HIGHTYPE) == HIGHTEXT)
	{
		if (high.fromvar != NOVARIABLE || high.fromport == NOPORTPROTO) return;
		pp = high.fromport->subportproto;
	} else
	{
		if ((high.status&HIGHTYPE) != HIGHFROM || high.fromport == NOPORTPROTO) return;
		pp = high.fromport;
	}

	/* see if port is on instance */
	ni = high.fromgeom->entryaddr.ni;
	if (ni->proto->primindex != 0) return;

	/* describe source of the port */
	*hini = pp->subnodeinst;
	*hipp = pp->subportproto;
}

/*
 * Routine to interactively select an instance of "inp" higher in the hierarchy.
 * Returns the instance that was selected (NONODEINST if none).
 */
NODEINST *us_pickhigherinstance(NODEPROTO *inp)
{
	REGISTER NODEPROTO **newfacetlist;
	REGISTER NODEINST *ni, **newinstlist;
	REGISTER POPUPMENU *pm;
	POPUPMENU *cpopup;
	REGISTER POPUPMENUITEM *mi, *selected;
	INTSML butstate;
	REGISTER INTSML facetcount, i, k, *newinstcount;
	char buf[50];

	/* make a list of choices, up the hierarchy */
	facetcount = 0;
	for(ni = inp->firstinst; ni != NONODEINST; ni = ni->nextinst)
	{
		/* ignore this instance if it is a duplicate */
		for(i=0; i<facetcount; i++) if (us_pickinstfacetlist[i] == ni->parent) break;
		if (i < facetcount)
		{
			us_pickinstinstcount[i]++;
			continue;
		}

		/* ensure room in the list */
		if (facetcount >= us_pickinstlistsize)
		{
			k = us_pickinstlistsize + 32;
			newfacetlist = (NODEPROTO **)emalloc(k * (sizeof (NODEPROTO *)), us_aid->cluster);
			newinstlist = (NODEINST **)emalloc(k * (sizeof (NODEINST *)), us_aid->cluster);
			newinstcount = (INTSML *)emalloc(k * SIZEOFINTSML, us_aid->cluster);
			if (newfacetlist == 0 || newinstlist == 0 || newinstcount == 0) return(0);
			for(i=0; i<facetcount; i++)
			{
				newfacetlist[i] = us_pickinstfacetlist[i];
				newinstlist[i] = us_pickinstinstlist[i];
				newinstcount[i] = us_pickinstinstcount[i];
			}
			if (us_pickinstlistsize != 0)
			{
				efree((char *)us_pickinstfacetlist);
				efree((char *)us_pickinstinstlist);
				efree((char *)us_pickinstinstcount);
			}
			us_pickinstfacetlist = newfacetlist;
			us_pickinstinstlist = newinstlist;
			us_pickinstinstcount = newinstcount;
			us_pickinstlistsize = k;
		}

		us_pickinstfacetlist[facetcount] = ni->parent;
		us_pickinstinstlist[facetcount]  = ni;
		us_pickinstinstcount[facetcount] = 1;
		facetcount++;
	}

	/* if no instances of this facet found, exit */
	if (facetcount == 0) return(NONODEINST);

	/* if only one instance, answer is easy */
	if (facetcount == 1)
	{
		return(us_pickinstinstlist[0]);
	}

	/* make a menu of all facets connected to this exported port */
	pm = (POPUPMENU *)emalloc(sizeof(POPUPMENU), el_tempcluster);
	if (pm == 0) return(NONODEINST);

	mi = (POPUPMENUITEM *)emalloc(facetcount * (sizeof (POPUPMENUITEM)), el_tempcluster);
	if (mi == 0)
	{
		efree((char *)pm);
		return(NONODEINST);
	}
	for (i=0; i<facetcount; i++)
	{
		(void)initinfstr();
		(void)addstringtoinfstr(us_pickinstfacetlist[i]->cell->cellname);
		if (us_pickinstinstcount[i] > 1)
		{
			(void)sprintf(buf, " (%d instances)", us_pickinstinstcount[i]);
			(void)addstringtoinfstr(buf);
		}
		(void)allocstring(&mi[i].attribute, returninfstr(), el_tempcluster);
		mi[i].value = 0;
		mi[i].valueparse = NOCOMCOMP;
		mi[i].maxlen = -1;
		mi[i].response = NOUSERCOM;
		mi[i].changed = 0;
	}
	pm->name = "noname";
	pm->list = mi;
	pm->total = facetcount;
	pm->header = "Which facet up the hierarchy?";

	/* display and select from the menu */
	butstate = 0;
	cpopup = pm;
	selected = us_popupmenu(&cpopup, &butstate, 1, -1, -1, 0);

	/* free up allocated menu space */
	for (k=0; k<facetcount; k++)
		efree(mi[k].attribute);
	efree((char *)mi);
	efree((char *)pm);

	/* stop if display doesn't support popup menus */
	if (selected == 0) return(NONODEINST);
	if (selected == NOPOPUPMENUITEM) return(NONODEINST);
	for (i=0; i<facetcount; i++)
	{
		if (selected != &mi[i]) continue;
		return(us_pickinstinstlist[i]);
	}

	return(NONODEINST);
}

/*
 * routine to re-export port "pp" on nodeinst "ni".  Returns nonzero if there
 * is an error
 */
INTSML us_reexportport(PORTPROTO *pp, NODEINST *ni)
{
	REGISTER INTSML i;
	REGISTER VARIABLE *var;
	char *portname, *sportname;
	REGISTER PORTPROTO *ppt;

	/* generate an initial guess for the new port name */
	i = initinfstr();
	i += addstringtoinfstr(pp->protoname);

	/* add in local node name if applicable */
	var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, el_node_name);
	if (var != NOVARIABLE) i += addstringtoinfstr((char *)var->addr);

	if (i != 0)
	{
		ttyputerr("Memory low");
		return(1);
	}
	(void)allocstring(&sportname, returninfstr(), el_tempcluster);
	portname = us_uniqueportname(sportname, ni->parent);
	efree(sportname);

	/* export the port */
	ttyputmsg("Exporting port %s of node %s as %s", pp->protoname, describenodeinst(ni), portname);
	startobjectchange((INTBIG)ni, VNODEINST);
	ppt = newportproto(ni->parent, ni, pp, portname);
	if (ppt == NOPORTPROTO)
	{
		us_abortcommand("Error creating port %s", portname);
		return(1);
	}
	ppt->textdescript = pp->textdescript;
	ppt->userbits = pp->userbits;
	if (copyvars((INTBIG)pp, VPORTPROTO, (INTBIG)ppt, VPORTPROTO) != 0)
		return(0);
	endobjectchange((INTBIG)ni, VNODEINST);
	return(0);
}

/*
 * routine to rename port "pp" to be "pt"
 */
void us_renameport(PORTPROTO *pp, char *pt)
{
	char *ch;
	REGISTER INTSML badname;
	REGISTER PORTPROTO *opp;
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *np;
	extern AIDENTRY *net_aid;

	badname = 0;
	for(ch = pt; *ch != 0; ch++)
	{
		if (*ch > ' ' && *ch < 0177) continue;
		*ch = 'X';
		badname++;
	}
	if (badname != 0)
		ttyputerr("Port has invalid characters, renamed to '%s'", pt);

	/* check for duplicate name */
	if (strcmp(pp->protoname, pt) == 0)
	{
		ttyputmsg("Port name has not changed");
		return;
	}

	np = pp->parent;
	for(opp = np->firstportproto; opp != NOPORTPROTO; opp = opp->nextportproto)
		if (opp != pp && namesame(opp->protoname, pt) == 0) break;
	if (opp == NOPORTPROTO) ch = pt; else
	{
		ch = us_uniqueportname(pt, np);
		ttyputmsg("Already a port called %s, calling this %s", pt, ch);
	}

	/* look at all instances of this nodeproto for use on display */
	startobjectchange((INTBIG)pp->subnodeinst, VNODEINST);
	for(ni = np->firstinst; ni != NONODEINST; ni = ni->nextinst)
	{
		/* no port names in expanded facets */
		if ((ni->userbits & NEXPAND) == 0) startobjectchange((INTBIG)ni, VNODEINST);
	}

	/* change the port name */
	ttyputverbose("Port %s renamed to %s", pp->protoname, ch);
	(void)setval((INTBIG)pp, VPORTPROTO, "protoname", (INTBIG)ch, VSTRING);

	/* look at all instances of this nodeproto for use on display */
	for(ni = np->firstinst; ni != NONODEINST; ni = ni->nextinst)
	{
		/* no port names in expanded facets */
		if ((ni->userbits & NEXPAND) == 0) endobjectchange((INTBIG)ni, VNODEINST);
	}
	endobjectchange((INTBIG)pp->subnodeinst, VNODEINST);

	/* tell the network maintainter to reevaluate this facet */
	(void)askaid(net_aid, "re-number", (INTBIG)pp->parent);
}

/*
 * routine to create a port in facet "np" called "portname".  The port resides on
 * node "ni", port "pp" in the facet.  The userbits field will have "bits" set where
 * "mask" points.  The text descriptor is "textdescript".
 * Also, check across icon/contents boundary to create a parallel port if possible
 */
void us_makenewportproto(NODEPROTO *np, NODEINST *ni, PORTPROTO *pp,
	char *portname, INTBIG mask, INTBIG bits, INTBIG textdescript)
{
	REGISTER NODEPROTO *onp, *pinproto;
	REGISTER NODEINST *pinni, *boxni;
	REGISTER ARCPROTO *wiretype;
	INTBIG x, y, portcount, portlen, *newportlocation, psx, psy;
	REGISTER INTBIG character, boxlx, boxhx, boxly, boxhy, rangelx, rangehx,
		rangely, rangehy;
	REGISTER PORTPROTO *ppt, *opp, *bpp, *rightport, *topport, *leftport, *bottomport;

	startobjectchange((INTBIG)ni, VNODEINST);
	ppt = newportproto(np, ni, pp, portname);
	if (ppt == NOPORTPROTO)
	{
		us_abortcommand("Error creating the port");
		(void)us_pophighlight(0);
		return;
	}
	if ((mask&STATEBITS) != 0) ppt->userbits = (ppt->userbits & ~STATEBITS) | (bits & STATEBITS);
	if ((mask&PORTDRAWN) != 0) ppt->userbits = (ppt->userbits & ~PORTDRAWN) | (bits & PORTDRAWN);
	if ((mask&BODYONLY) != 0)  ppt->userbits = (ppt->userbits & ~BODYONLY) | (bits & BODYONLY);
	if (ni->proto->primindex == 0) ppt->textdescript = textdescript;
	endobjectchange((INTBIG)ni, VNODEINST);

	/* ignore new port if not intended for icon */
	if ((pp->userbits&BODYONLY) != 0) return;

	/* see if there is an associated icon facet */
	onp = NONODEPROTO;
	if (np->cellview != el_iconview) onp = iconview(np);
	if (onp == NONODEPROTO) return;

	/* find the box in the icon facet */
	for(boxni = onp->firstnodeinst; boxni != NONODEINST; boxni = boxni->nextnodeinst)
		if (boxni->proto == sch_bboxprim) break;
	if (boxni == NONODEINST)
	{
		boxlx = boxhx = (onp->lowx + onp->highx) / 2;
		boxly = boxhy = (onp->lowy + onp->highy) / 2;
		rangelx = onp->lowx;
		rangehx = onp->highx;
		rangely = onp->lowy;
		rangehy = onp->highy;
	} else
	{
		rangelx = boxlx = boxni->lowx;
		rangehx = boxhx = boxni->highx;
		rangely = boxly = boxni->lowy;
		rangehy = boxhy = boxni->highy;
	}

	/* icon facet found, quit if this port is already there */
	for(opp = onp->firstportproto; opp != NOPORTPROTO; opp = opp->nextportproto)
		if (namesame(opp->protoname, ppt->protoname) == 0) return;

	/* add a port to the icon */
	character = ppt->userbits & STATEBITS;

	/* special detection for power and ground ports */
	if (portispower(pp) != 0 || portisground(pp) != 0) character = GNDPORT;

	/* count the number of ports */
	portlen = 0;
	for(opp = onp->firstportproto; opp != NOPORTPROTO; opp = opp->nextportproto) portlen++;

	if (portlen > us_netportlimit)
	{
		newportlocation = (INTBIG *)emalloc(portlen * SIZEOFINTBIG, us_aid->cluster);
		if (newportlocation == 0) return;
		if (us_netportlimit > 0) efree((char *)us_netportlocation);
		us_netportlocation = newportlocation;
		us_netportlimit = portlen;
	}
	portcount = 0;
	switch (character)
	{
		case OUTPORT:
			for(opp = onp->firstportproto; opp != NOPORTPROTO; opp = opp->nextportproto)
			{
				portposition(opp->subnodeinst, opp->subportproto, &x, &y);
				if (x > boxhx) us_netportlocation[portcount++] = y;
			}
			y = us_findnewplace(us_netportlocation, portcount, rangely, rangehy);
			x = onp->highx;
			if (boxni != NONODEINST && onp->highx == boxni->highx) x += el_curtech->deflambda * 4;
			break;
		case BIDIRPORT:
			for(opp = onp->firstportproto; opp != NOPORTPROTO; opp = opp->nextportproto)
			{
				portposition(opp->subnodeinst, opp->subportproto, &x, &y);
				if (y < boxly) us_netportlocation[portcount++] = x;
			}
			x = us_findnewplace(us_netportlocation, portcount, rangelx, rangehx);
			y = onp->lowy;
			if (boxni != NONODEINST && onp->lowy == boxni->lowy) y -= el_curtech->deflambda * 4;
			break;
		case PWRPORT:
		case GNDPORT:
			for(opp = onp->firstportproto; opp != NOPORTPROTO; opp = opp->nextportproto)
			{
				portposition(opp->subnodeinst, opp->subportproto, &x, &y);
				if (y > boxhy) us_netportlocation[portcount++] = x;
			}
			x = us_findnewplace(us_netportlocation, portcount, rangelx, rangehx);
			y = onp->highy;
			if (boxni != NONODEINST && onp->highy == boxni->highy) y += el_curtech->deflambda * 4;
			break;
		default:		/* INPORT, unlabeled, and all CLOCK ports */
			for(opp = onp->firstportproto; opp != NOPORTPROTO; opp = opp->nextportproto)
			{
				portposition(opp->subnodeinst, opp->subportproto, &x, &y);
				if (x < boxlx) us_netportlocation[portcount++] = y;
			}
			y = us_findnewplace(us_netportlocation, portcount, rangely, rangehy);
			x = onp->lowx;
			if (boxni != NONODEINST && onp->lowx == boxni->lowx) x -= el_curtech->deflambda * 4;
			break;
	}

	/* determine type of pin */
	pinproto = gen_invispinprim;
	wiretype = sch_wirearc;
	if (ppt->subnodeinst != NONODEINST)
	{
		bpp = ppt;
		while (bpp->subnodeinst->proto->primindex == 0) bpp = bpp->subportproto;
		if (bpp->subnodeinst->proto == sch_buspinprim)
		{
			pinproto = sch_buspinprim;
			wiretype = sch_busarc;
		}
	}

	/* create the pin */
	defaultnodesize(pinproto, &psx, &psy);
	pinni = newnodeinst(pinproto, x-psx/2, x+psx/2, y-psy/2, y+psy/2, 0, 0, onp);
	if (pinni == NONODEINST) return;

	/* wire the pin if there is a center box */
	if (boxni != NONODEINST)
	{
		rightport = sch_bboxprim->firstportproto;
		topport = rightport->nextportproto;
		leftport = topport->nextportproto;
		bottomport = leftport->nextportproto;
		switch (character)
		{
			case OUTPORT:
				us_addleadtoicon(onp, wiretype, pinni, boxni, rightport, x, y,
					boxhx, y);
				break;
			case BIDIRPORT:
				us_addleadtoicon(onp, wiretype, pinni, boxni, bottomport, x, y,
					x, boxly);
				break;
			case PWRPORT:
			case GNDPORT:
				us_addleadtoicon(onp, wiretype, pinni, boxni, topport, x, y,
					x, boxhy);
				break;
			default:		/* INPORT, unlabeled, and all CLOCK ports */
				us_addleadtoicon(onp, wiretype, pinni, boxni, leftport, x, y,
					boxlx, y);
				break;
		}
	}

	/* export the port that should be on this pin */
	us_makenewportproto(onp, pinni, pinproto->firstportproto, portname, mask, bits|PORTDRAWN, 0);
}

/*
 * routine to find the largest gap in an integer array that is within the bounds
 * "low" to "high".  The array is sorted by this routine.
 */
INTBIG us_findnewplace(INTBIG *arr, INTBIG count, INTBIG low, INTBIG high)
{
	REGISTER INTBIG swap, i, gapwid, gappos;
	REGISTER INTSML sorted;

	/* easy if nothing in the array */
	if (count <= 0) return((low + high) / 2);

	/* first sort the array */
	sorted = 0;
	while (sorted == 0)
	{
		sorted = 1;
		for(i=1; i<count; i++)
		{
			if (arr[i-1] <= arr[i]) continue;
			swap = arr[i-1];
			arr[i-1] = arr[i];
			arr[i] = swap;
			sorted = 0;
		}
	}

	/* now find the widest gap */
	gapwid = 0;
	gappos = (low + high) / 2;
	for(i=1; i<count; i++)
	{
		if (arr[i] - arr[i-1] > gapwid)
		{
			gapwid = arr[i] - arr[i-1];
			gappos = (arr[i-1] + arr[i]) / 2;
		}
	}
	if (arr[0] - low > gapwid)
	{
		gapwid = arr[0] - low;
		gappos = (low + arr[0]) / 2;
	}
	if (high - arr[count-1] > gapwid)
	{
		gapwid = high - arr[count-1];
		gappos = (arr[count-1] + high) / 2;
	}
	return(gappos);
}

/*********************************** FINDING ***********************************/

/*
 * routine to find an object/port close to (wantx, wanty) in the current facet.
 * If there is more than one object/port under the cursor, they are returned
 * in reverse sequential order, provided that the most recently found
 * object is described in "curhigh".  The next close object is placed in
 * "curhigh".  If "exclusively" is nonzero, find only nodes or arcs of the
 * current prototype.  If "another" is nonzero, this is the second find,
 * and should not consider text objects.  If "findport" is nonzero, port selection
 * is also desired.  If "under" is nonzero, only find objects exactly under the
 * desired cursor location.  If "special" is nonzero, special selection rules apply.
 */
void us_findobject(INTBIG wantx, INTBIG wanty, WINDOWPART *win, HIGHLIGHT *curhigh,
	INTSML exclusively, INTSML another, INTSML findport, INTSML under, INTSML special)
{
	HIGHLIGHT best, lastdirect, prevdirect, bestdirect;
	REGISTER PORTPROTO *pp;
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *np;
	REGISTER INTBIG dist;
	REGISTER VARIABLE *var;
	REGISTER INTSML phase, startphase, i, tot;
	INTSML looping;
	INTBIG bestdist;
	static POLYGON *poly = NOPOLYGON;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);

	/* initialize */
	bestdist = HUGEINT;
	looping = 0;
	best.fromgeom = NOGEOM;             best.status = 0;
	bestdirect.fromgeom = NOGEOM;       bestdirect.status = 0;
	lastdirect.fromgeom = NOGEOM;       lastdirect.status = 0;
	prevdirect.fromgeom = NOGEOM;       prevdirect.status = 0;

	/* ignore facets if requested */
	startphase = 0;
	if (special == 0 && (us_useroptions&NOINSTANCESELECT) != 0) startphase = 1;

	/* search the relevant objects in the circuit */
	np = win->curnodeproto;
	for(phase = startphase; phase < 3; phase++)
		us_recursivelysearch(np->rtree, exclusively, another, findport,
			under, special, curhigh, &best, &bestdirect, &lastdirect, &prevdirect, &looping,
				&bestdist, wantx, wanty, win, phase);

	/* check for displayable variables on the facet */
	tot = tech_displayablefacetvars(np);
	for(i=0; i<tot; i++)
	{
		var = tech_filldisplayablefacetvar(np, poly);
		us_maketextpoly(poly->string, win, (np->lowx + np->highx) / 2,
			(np->lowy + np->highy) / 2, NONODEINST, whattech(np),
				var->textdescript, poly);
		poly->style = FILLED;
		dist = polydistance(poly, wantx, wanty);
		if (dist < 0)
		{
			if ((curhigh->status&HIGHTYPE) == HIGHTEXT && curhigh->fromgeom == NOGEOM &&
				(curhigh->fromvar == var || curhigh->fromport == NOPORTPROTO))
			{
				looping = 1;
				prevdirect.status = lastdirect.status;
				prevdirect.fromgeom = lastdirect.fromgeom;
				prevdirect.fromvar = lastdirect.fromvar;
				prevdirect.fromport = lastdirect.fromport;
			}
			lastdirect.status = HIGHTEXT;
			lastdirect.fromgeom = NOGEOM;
			lastdirect.fromport = NOPORTPROTO;
			lastdirect.fromvar = var;
			if (dist < bestdist)
			{
				bestdirect.status = HIGHTEXT;
				bestdirect.fromgeom = NOGEOM;
				bestdirect.fromvar = var;
				bestdirect.fromport = NOPORTPROTO;
			}
		}

		/* see if it is closer than others */
		if (dist < bestdist)
		{
			best.status = HIGHTEXT;
			best.fromgeom = NOGEOM;
			best.fromvar = var;
			best.fromport = NOPORTPROTO;
			bestdist = dist;
		}
	}

	/* use best direct hit if one exists, otherwise best any-kind-of-hit */
	if (bestdirect.status != 0)
	{
		curhigh->status = bestdirect.status;
		curhigh->fromgeom = bestdirect.fromgeom;
		curhigh->fromvar = bestdirect.fromvar;
		curhigh->fromport = bestdirect.fromport;
		curhigh->snapx = bestdirect.snapx;
		curhigh->snapy = bestdirect.snapy;
	} else
	{
		if (under == 0)
		{
			curhigh->status = best.status;
			curhigh->fromgeom = best.fromgeom;
			curhigh->fromvar = best.fromvar;
			curhigh->fromport = best.fromport;
			curhigh->snapx = best.snapx;
			curhigh->snapy = best.snapy;
		} else
		{
			curhigh->status = 0;
			curhigh->fromgeom = NOGEOM;
			curhigh->fromvar = NOVARIABLE;
			curhigh->fromport = NOPORTPROTO;
			curhigh->frompoint = 0;
		}
	}

	/* see if looping through direct hits */
	if (looping != 0)
	{
		/* made direct hit on previously selected object: looping through */
		if (prevdirect.status != 0)
		{
			curhigh->status = prevdirect.status;
			curhigh->fromgeom = prevdirect.fromgeom;
			curhigh->fromvar = prevdirect.fromvar;
			curhigh->fromport = prevdirect.fromport;
			curhigh->snapx = prevdirect.snapx;
			curhigh->snapy = prevdirect.snapy;
		} else if (lastdirect.status != 0)
		{
			curhigh->status = lastdirect.status;
			curhigh->fromgeom = lastdirect.fromgeom;
			curhigh->fromvar = lastdirect.fromvar;
			curhigh->fromport = lastdirect.fromport;
			curhigh->snapx = lastdirect.snapx;
			curhigh->snapy = lastdirect.snapy;
		}
	}

	if (curhigh->fromgeom == NOGEOM) curhigh->facet = np; else
		curhigh->facet = geomparent(curhigh->fromgeom);

	/* quit now if nothing found */
	if (curhigh->status == 0) return;

	/* find the closest port if this is a nodeinst and no port hit directly */
	if ((curhigh->status&HIGHTYPE) == HIGHFROM && curhigh->fromgeom->entrytype == OBJNODEINST &&
		curhigh->fromport == NOPORTPROTO)
	{
		ni = curhigh->fromgeom->entryaddr.ni;
		for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			shapeportpoly(ni, pp, poly, 0);

			/* get distance of desired point to polygon */
			dist = polydistance(poly, wantx, wanty);
			if (dist < 0)
			{
				curhigh->fromport = pp;
				break;
			}
			if (curhigh->fromport == NOPORTPROTO) bestdist = dist;
			if (dist > bestdist) continue;
			bestdist = dist;   curhigh->fromport = pp;
		}
	}
}

/*
 * routine to search R-tree "rtree" for objects that are close to (wantx, wanty)
 * in window "win".  Those that are found are passed to "us_checkoutobject"
 * for proximity evaluation, along with the evaluation parameters "curhigh",
 * "best", "bestdirect", "lastdirect", "prevdirect", "looping", "bestdist",
 * "exclusively", "another", "findport", and "under".  The "phase" value ranges
 * from 0 to 2 according to the type of object desired.
 */
void us_recursivelysearch(RTNODE *rtree, INTSML exclusively, INTSML another, INTSML findport,
	INTSML under, INTSML findspecial, HIGHLIGHT *curhigh, HIGHLIGHT *best, HIGHLIGHT *bestdirect,
	HIGHLIGHT *lastdirect, HIGHLIGHT *prevdirect, INTSML *looping, INTBIG *bestdist,
	INTBIG wantx, INTBIG wanty, WINDOWPART *win, INTSML phase)
{
	REGISTER GEOM *geom;
	REGISTER INTSML i, bestrt, found;
	REGISTER INTBIG disttort, bestdisttort, slop, directhitdist;
	INTBIG lx, hx, ly, hy;

	found = 0;
	bestdisttort = HUGEINT;
	slop = el_curtech->deflambda * 10;
	directhitdist = muldiv(EXACTSELECTDISTANCE, win->screenhx - win->screenlx, win->usehx - win->uselx);
	if (directhitdist > slop) slop = directhitdist;
	for(i=0; i<rtree->total; i++)
	{
		db_rtnbbox(rtree, i, &lx, &hx, &ly, &hy);

		/* accumulate best R-tree module in case none are direct hits */
		disttort = abs(wantx - (lx+hx)/2) + abs(wanty - (ly+hy)/2);
		if (disttort < bestdisttort)
		{
			bestdisttort = disttort;
			bestrt = i;
		}

		/* see if this R-tree node is a direct hit */
		if (exclusively == 0 &&
			(lx > wantx+slop || hx < wantx-slop || ly > wanty+slop || hy < wanty-slop)) continue;
		found++;

		/* search it */
		if (rtree->flag != 0)
		{
			geom = (GEOM *)rtree->pointers[i];
			switch (phase)
			{
				case 0:			/* only allow complex nodes */
					if (geom->entrytype != OBJNODEINST) break;
					if (geom->entryaddr.ni->proto->primindex != 0) break;
					us_checkoutobject(geom, exclusively, another, findport, findspecial,
						curhigh, best, bestdirect, lastdirect, prevdirect, looping,
							bestdist, wantx, wanty, win);
					break;
				case 1:			/* only allow arcs */
					if (geom->entrytype != OBJARCINST) break;
					us_checkoutobject(geom, exclusively, another, findport, findspecial,
						curhigh, best, bestdirect, lastdirect, prevdirect, looping,
							bestdist, wantx, wanty, win);
					break;
				case 2:			/* only allow primitive nodes */
					if (geom->entrytype != OBJNODEINST) break;
					if (geom->entryaddr.ni->proto->primindex == 0) break;
					us_checkoutobject(geom, exclusively, another, findport, findspecial,
						curhigh, best, bestdirect, lastdirect, prevdirect, looping,
							bestdist, wantx, wanty, win);
					break;
			}
		} else us_recursivelysearch((RTNODE *)rtree->pointers[i], exclusively,
			another, findport, under, findspecial, curhigh, best, bestdirect, lastdirect,
				prevdirect, looping, bestdist, wantx, wanty, win, phase);
	}

	if (found != 0) return;
	if (bestdisttort == HUGEINT) return;
	if (under != 0) return;

	/* nothing found, use the closest */
	if (rtree->flag != 0)
	{
		geom = (GEOM *)rtree->pointers[bestrt];
		switch (phase)
		{
			case 0:			/* only allow complex nodes */
				if (geom->entrytype != OBJNODEINST) break;
				if (geom->entryaddr.ni->proto->primindex != 0) break;
				us_checkoutobject(geom, exclusively, another, findport, findspecial,
					curhigh, best, bestdirect, lastdirect, prevdirect, looping,
						bestdist, wantx, wanty, win);
				break;
			case 1:			/* only allow arcs */
				if (geom->entrytype != OBJARCINST) break;
				us_checkoutobject(geom, exclusively, another, findport, findspecial,
					curhigh, best, bestdirect, lastdirect, prevdirect, looping,
						bestdist, wantx, wanty, win);
				break;
			case 2:			/* only allow primitive nodes */
				if (geom->entrytype != OBJNODEINST) break;
				if (geom->entryaddr.ni->proto->primindex == 0) break;
				us_checkoutobject(geom, exclusively, another, findport, findspecial,
					curhigh, best, bestdirect, lastdirect, prevdirect, looping,
						bestdist, wantx, wanty, win);
				break;
		}
	} else us_recursivelysearch((RTNODE *)rtree->pointers[bestrt], exclusively,
		another, findport, under, findspecial, curhigh, best, bestdirect, lastdirect,
			prevdirect, looping, bestdist, wantx, wanty, win, phase);
}

/*
 * search helper routine to include object "geom" in the search for the
 * closest object to the cursor position at (wantx, wanty) in window "win".
 * If "exclusively" is nonzero, ignore nodes or arcs that are not of the
 * current type.  If "another" is nonzero, ignore text objects.  If "findport"
 * is nonzero, ports are being selected so facet names should not.  The closest
 * object is "*bestdist" away and is described in "best".  The closest direct
 * hit is in "bestdirect".  If that direct hit is the same as the last hit
 * (kept in "curhigh") then the last direct hit (kept in "lastdirect") is
 * moved to the previous direct hit (kept in "prevdirect") and the "looping"
 * flag is set.  This indicates that the "prevdirect" object should be used
 * (if it exists) and that the "lastdirect" object should be used failing that.
 */
void us_checkoutobject(GEOM *geom, INTSML exclusively, INTSML another, INTSML findport,
	INTSML findspecial, HIGHLIGHT *curhigh, HIGHLIGHT *best, HIGHLIGHT *bestdirect,
	HIGHLIGHT *lastdirect, HIGHLIGHT *prevdirect, INTSML *looping, INTBIG *bestdist,
	INTBIG wantx, INTBIG wanty, WINDOWPART *win)
{
	REGISTER PORTPROTO *pp;
	VARIABLE *var;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	static POLYGON *poly = NOPOLYGON;
	PORTPROTO *port;
	REGISTER INTSML i;
	REGISTER INTBIG dist, directhitdist;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);

	/* compute threshold for direct hits */
	directhitdist = muldiv(EXACTSELECTDISTANCE, win->screenhx - win->screenlx, win->usehx - win->uselx);

	if (geom->entrytype == OBJNODEINST)
	{
		/* examine a node object */
		ni = geom->entryaddr.ni;

		/* do not "find" hard-to-find nodes if "findspecial" is not set */
		if (findspecial == 0 && (ni->userbits&HARDSELECTN) != 0) return;

		/* skip if being exclusive */
		if (exclusively != 0 && ni->proto != us_curnodeproto) return;

		/* try text on the node (if not searching for "another") */
		if (another == 0 && exclusively == 0)
		{
			us_initnodetext(ni, findspecial);
			for(;;)
			{
				if (us_getnodetext(ni, win, poly, &var, &port) != 0) break;

				/* get distance of desired point to polygon */
				dist = polydistance(poly, wantx, wanty);

				/* direct hit */
				if (dist < directhitdist)
				{
					if (curhigh->fromgeom == geom && (curhigh->status&HIGHTYPE) == HIGHTEXT &&
						(curhigh->fromvar == var || curhigh->fromport == port))
					{
						*looping = 1;
						prevdirect->status = lastdirect->status;
						prevdirect->fromgeom = lastdirect->fromgeom;
						prevdirect->fromvar = lastdirect->fromvar;
						prevdirect->fromport = lastdirect->fromport;
					}
					lastdirect->status = HIGHTEXT;
					lastdirect->fromgeom = geom;
					lastdirect->fromport = port;
					lastdirect->fromvar = var;
					if (dist < *bestdist)
					{
						bestdirect->status = HIGHTEXT;
						bestdirect->fromgeom = geom;
						bestdirect->fromvar = var;
						bestdirect->fromport = port;
					}
				}

				/* see if it is closer than others */
				if (dist < *bestdist)
				{
					best->status = HIGHTEXT;
					best->fromgeom = geom;
					best->fromvar = var;
					best->fromport = port;
					*bestdist = dist;
				}
			}
		}

		/* do not "find" Invisible-Pins if they have text */
		if (ni->proto == gen_invispinprim)
		{
			for(i=0; i<ni->numvar; i++)
			{
				var = &ni->firstvar[i];
				if ((var->type&VDISPLAY) != 0) return;
			}
		}

		/* get the distance to the object */
		dist = us_disttoobject(wantx, wanty, geom);

		/* direct hit */
		if (dist < directhitdist)
		{
			if (curhigh->fromgeom == geom && (curhigh->status&HIGHTYPE) != HIGHTEXT)
			{
				*looping = 1;
				prevdirect->status = lastdirect->status;
				prevdirect->fromgeom = lastdirect->fromgeom;
				prevdirect->fromvar = lastdirect->fromvar;
				prevdirect->fromport = lastdirect->fromport;
				prevdirect->snapx = lastdirect->snapx;
				prevdirect->snapy = lastdirect->snapy;

				/* see if there is another port under the cursor */
				if (curhigh->fromport != NOPORTPROTO)
				{
					for(pp = curhigh->fromport->nextportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
					{
						shapeportpoly(ni, pp, poly, 0);
						if (isinside(wantx, wanty, poly) != 0)
						{
							prevdirect->status = HIGHFROM;
							prevdirect->fromgeom = geom;
							prevdirect->fromport = pp;
							break;
						}
					}
				}
			}
			lastdirect->status = HIGHFROM;
			lastdirect->fromgeom = geom;
			lastdirect->fromport = NOPORTPROTO;
			us_selectsnap(lastdirect, wantx, wanty);
			if (dist < *bestdist)
			{
				bestdirect->status = HIGHFROM;
				bestdirect->fromgeom = geom;
				bestdirect->fromport = NOPORTPROTO;
				us_selectsnap(bestdirect, wantx, wanty);
			}
		}

		/* see if it is closer than others */
		if (dist < *bestdist)
		{
			best->status = HIGHFROM;
			best->fromgeom = geom;
			best->fromport = NOPORTPROTO;
			us_selectsnap(best, wantx, wanty);
			*bestdist = dist;
		}
	} else
	{
		/* examine an arc object */
		ai = geom->entryaddr.ai;

		/* do not "find" hard-to-find arcs if "findspecial" is not set */
		if (findspecial == 0 && (ai->userbits&HARDSELECTA) != 0) return;

		/* skip if being exclusive */
		if (exclusively != 0 && ai->proto != us_curarcproto) return;

		/* try text on the arc (if not searching for "another") */
		if (another == 0 && exclusively == 0)
		{
			us_initarctext(ai, findspecial);
			for(;;)
			{
				if (us_getarctext(ai, win, poly, &var) != 0) break;

				/* get distance of desired point to polygon */
				dist = polydistance(poly, wantx, wanty);

				/* direct hit */
				if (dist < directhitdist)
				{
					if (curhigh->fromgeom == geom && (curhigh->status&HIGHTYPE) == HIGHTEXT &&
						(curhigh->fromvar == var))
					{
						*looping = 1;
						prevdirect->status = lastdirect->status;
						prevdirect->fromgeom = lastdirect->fromgeom;
						prevdirect->fromvar = lastdirect->fromvar;
						prevdirect->fromport = lastdirect->fromport;
					}
					lastdirect->status = HIGHTEXT;
					lastdirect->fromgeom = geom;
					lastdirect->fromvar = var;
					lastdirect->fromport = NOPORTPROTO;
					if (dist < *bestdist)
					{
						bestdirect->status = HIGHTEXT;
						bestdirect->fromgeom = geom;
						bestdirect->fromvar = var;
						us_selectsnap(bestdirect, wantx, wanty);
						bestdirect->fromport = NOPORTPROTO;
					}
				}

				/* see if it is closer than others */
				if (dist < *bestdist)
				{
					best->status = HIGHTEXT;
					best->fromgeom = geom;
					best->fromvar = var;
					best->fromport = NOPORTPROTO;
					us_selectsnap(best, wantx, wanty);
					*bestdist = dist;
				}
			}
		}

		/* get distance to arc */
		dist = us_disttoobject(wantx, wanty, geom);

		/* direct hit */
		if (dist < directhitdist)
		{
			if (curhigh->fromgeom == geom && (curhigh->status&HIGHTYPE) != HIGHTEXT)
			{
				*looping = 1;
				prevdirect->status = lastdirect->status;
				prevdirect->fromgeom = lastdirect->fromgeom;
				prevdirect->fromvar = lastdirect->fromvar;
				prevdirect->fromport = lastdirect->fromport;
				prevdirect->snapx = lastdirect->snapx;
				prevdirect->snapy = lastdirect->snapy;
			}
			lastdirect->status = HIGHFROM;
			lastdirect->fromgeom = geom;
			lastdirect->fromport = NOPORTPROTO;
			us_selectsnap(lastdirect, wantx, wanty);
			if (dist < *bestdist)
			{
				bestdirect->status = HIGHFROM;
				bestdirect->fromgeom = geom;
				bestdirect->fromvar = NOVARIABLE;
				bestdirect->fromport = NOPORTPROTO;
				us_selectsnap(bestdirect, wantx, wanty);
			}
		}

		/* see if it is closer than others */
		if (dist < *bestdist)
		{
			best->status = HIGHFROM;
			best->fromgeom = geom;
			best->fromvar = NOVARIABLE;
			best->fromport = NOPORTPROTO;
			us_selectsnap(best, wantx, wanty);
			*bestdist = dist;
		}
	}
}

/*
 * routine to determine whether the cursor (xcur, ycur) is over the object in "high".
 */
INTSML us_cursoroverhigh(HIGHLIGHT *high, INTBIG xcur, INTBIG ycur, WINDOWPART *win)
{
	VARIABLE *var;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER NODEPROTO *np;
	static POLYGON *poly = NOPOLYGON;
	PORTPROTO *port;
	REGISTER INTSML i, tot;
	REGISTER INTBIG directhitdist;

	/* must be in the same facet */
	if (high->facet != win->curnodeproto) return(0);

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);

	/* compute threshold for direct hits */
	directhitdist = muldiv(EXACTSELECTDISTANCE, win->screenhx - win->screenlx,
		win->usehx - win->uselx);

	/* could be selected text */
	if ((high->status&HIGHTEXT) != 0)
	{
		if (high->fromgeom == NOGEOM)
		{
			np = high->facet;
			tot = tech_displayablefacetvars(np);
			for(i=0; i<tot; i++)
			{
				var = tech_filldisplayablefacetvar(np, poly);
				if (high->fromvar != var) continue;
				us_maketextpoly(poly->string, win, (np->lowx + np->highx) / 2,
					(np->lowy + np->highy) / 2, NONODEINST, whattech(np),
						var->textdescript, poly);
				poly->style = FILLED;
				if (polydistance(poly, xcur, ycur) <= directhitdist) return(1);
			}
			return(0);
		}

		/* examine all text on the object */
		if (high->fromgeom->entrytype == OBJNODEINST)
		{
			ni = high->fromgeom->entryaddr.ni;
			us_initnodetext(ni, 1);
		} else
		{
			ai = high->fromgeom->entryaddr.ai;
			us_initarctext(ai, 1);
		}

		for(;;)
		{
			if (high->fromgeom->entrytype == OBJNODEINST)
			{
				if (us_getnodetext(ni, win, poly, &var, &port) != 0) break;
			} else
			{
				if (us_getarctext(ai, win, poly, &var) != 0) break;
				port = NOPORTPROTO;
			}
			if (high->fromvar != var || high->fromport != port) continue;

			/* accept if on */
			if (polydistance(poly, xcur, ycur) <= directhitdist) return(1);
		}
		return(0);
	}

	/* must be a single node or arc selected */
	if ((high->status&HIGHFROM) == 0) return(0);

	/* see if the point is over the object */
	if (us_disttoobject(xcur, ycur, high->fromgeom) <= directhitdist) return(1);
	return(0);
}

/*
 * routine to add snapping selection to the highlight in "best".
 */
void us_selectsnap(HIGHLIGHT *best, INTBIG wantx, INTBIG wanty)
{
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER INTSML j, k;
	INTBIG cx, cy;
	static POLYGON *poly = NOPOLYGON;
	XARRAY trans;

	if ((us_state&SNAPMODE) == SNAPMODENONE) return;

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);

	if (best->fromgeom->entrytype == OBJNODEINST)
	{
		ni = best->fromgeom->entryaddr.ni;
		if (ni->proto->primindex == 0)
		{
			if ((us_state&SNAPMODE) == SNAPMODECENTER)
			{
				corneroffset(ni, ni->proto, ni->rotation, ni->transpose, &cx, &cy, 0);
				us_setbestsnappoint(best, wantx, wanty, ni->lowx+cx, ni->lowy+cy, 0, 0);
			}
			return;
		}
		makerot(ni, trans);
		k = nodepolys(ni);
		for(j=0; j<k; j++)
		{
			shapenodepoly(ni, j, poly);
			xformpoly(poly, trans);
			us_selectsnappoly(best, poly, wantx, wanty);
		}
	} else
	{
		ai = best->fromgeom->entryaddr.ai;
		k = arcpolys(ai);
		for(j=0; j<k; j++)
		{
			shapearcpoly(ai, j, poly);
			us_selectsnappoly(best, poly, wantx, wanty);
		}
	}
}

void us_selectsnappoly(HIGHLIGHT *best, POLYGON *poly, INTBIG wantx, INTBIG wanty)
{
	REGISTER INTBIG radius, sea;
	REGISTER INTSML j, k, tan, perp;
	INTBIG testx, testy;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER GEOM *geom;
	XARRAY trans;
	REGISTER INTSML angle, otherang, i, last;
	static POLYGON *interpoly = NOPOLYGON;

	switch (us_state&SNAPMODE)
	{
		case SNAPMODECENTER:
			if (poly->style == CIRCLE || poly->style == DISC || poly->style == CIRCLEARC)
			{
				us_setbestsnappoint(best, wantx, wanty, poly->xv[0], poly->yv[0], 0, 0);
			} else if (poly->style != OPENED && poly->style != OPENEDT1 && poly->style != OPENEDT2 &&
				poly->style != OPENEDT3 && poly->style != CLOSED)
			{
				getcenter(poly, &testx, &testy);
				us_setbestsnappoint(best, wantx, wanty, testx, testy, 0, 0);
			}
			break;
		case SNAPMODEMIDPOINT:
			if (poly->style == OPENED || poly->style == OPENEDT1 || poly->style == OPENEDT2 ||
				poly->style == OPENEDT3 || poly->style == CLOSED)
			{
				for(i=0; i<poly->count; i++)
				{
					if (i == 0)
					{
						if (poly->style != CLOSED) continue;
						last = poly->count - 1;
					} else last = i-1;
					testx = (poly->xv[last] + poly->xv[i]) / 2;
					testy = (poly->yv[last] + poly->yv[i]) / 2;
					us_setbestsnappoint(best, wantx, wanty, testx, testy, 0, 0);
				}
			} else if (poly->style == VECTORS)
			{
				for(i=0; i<poly->count; i += 2)
				{
					testx = (poly->xv[i+1] + poly->xv[i]) / 2;
					testy = (poly->yv[i+1] + poly->yv[i]) / 2;
					us_setbestsnappoint(best, wantx, wanty, testx, testy, 0, 0);
				}
			}
			break;
		case SNAPMODEENDPOINT:
			if (poly->style == OPENED || poly->style == OPENEDT1 || poly->style == OPENEDT2 ||
				poly->style == OPENEDT3 || poly->style == CLOSED)
			{
				for(i=0; i<poly->count; i++)
					us_setbestsnappoint(best, wantx, wanty, poly->xv[i], poly->yv[i], 0, 0);
			} else if (poly->style == VECTORS)
			{
				for(i=0; i<poly->count; i += 2)
				{
					us_setbestsnappoint(best, wantx, wanty, poly->xv[i], poly->yv[i], 0, 0);
					us_setbestsnappoint(best, wantx, wanty, poly->xv[i+1], poly->yv[i+1], 0, 0);
				}
			} else if (poly->style == CIRCLEARC)
			{
				us_setbestsnappoint(best, wantx, wanty, poly->xv[1], poly->yv[1], 0, 0);
				us_setbestsnappoint(best, wantx, wanty, poly->xv[2], poly->yv[2], 0, 0);
			}
			break;
		case SNAPMODETANGENT:
		case SNAPMODEPERP:
			if (poly->style == OPENED || poly->style == OPENEDT1 || poly->style == OPENEDT2 ||
				poly->style == OPENEDT3 || poly->style == CLOSED)
			{
				for(i=0; i<poly->count; i++)
				{
					if (i == 0)
					{
						if (poly->style != CLOSED) continue;
						last = poly->count - 1;
					} else last = i-1;
					angle = figureangle(poly->xv[last],poly->yv[last], poly->xv[i],poly->yv[i]);
					otherang = (angle+900) % 3600;
					if (intersect(poly->xv[last],poly->yv[last], angle, wantx, wanty, otherang,
						&testx, &testy) >= 0)
					{
						if (testx >= mini(poly->xv[last], poly->xv[i]) &&
							testx <= maxi(poly->xv[last], poly->xv[i]) &&
							testy >= mini(poly->yv[last], poly->yv[i]) &&
							testy <= maxi(poly->yv[last], poly->yv[i]))
						{
							us_setbestsnappoint(best, wantx, wanty, testx, testy, 0, 1);
						}
					}
				}
			} else if (poly->style == VECTORS)
			{
				for(i=0; i<poly->count; i += 2)
				{
					angle = figureangle(poly->xv[i],poly->yv[i], poly->xv[i+1],poly->yv[i+1]);
					otherang = (angle+900) % 3600;
					if (intersect(poly->xv[i],poly->yv[i], angle, wantx, wanty, otherang,
						&testx, &testy) >= 0)
					{
						if (testx >= mini(poly->xv[i], poly->xv[i+1]) &&
							testx <= maxi(poly->xv[i], poly->xv[i+1]) &&
							testy >= mini(poly->yv[i], poly->yv[i+1]) &&
							testy <= maxi(poly->yv[i], poly->yv[i+1]))
						{
							us_setbestsnappoint(best, wantx, wanty, testx, testy, 0, 1);
						}
					}
				}
			} else if (poly->style == CIRCLE || poly->style == DISC || poly->style == CIRCLEARC)
			{
				if (poly->xv[0] == wantx && poly->yv[0] == wanty) break;
				angle = figureangle(poly->xv[0],poly->yv[0], wantx,wanty);
				radius = computedistance(poly->xv[0],poly->yv[0], poly->xv[1],poly->yv[1]);
				testx = poly->xv[0] + mult(radius, cosine(angle));
				testy = poly->yv[0] + mult(radius, sine(angle));
				if (poly->style == CIRCLEARC && us_pointonarc(testx, testy, poly) == 0) break;
				if ((us_state&SNAPMODE) == SNAPMODETANGENT) tan = 1; else tan = 0;
				if ((us_state&SNAPMODE) == SNAPMODEPERP) perp = 1; else perp = 0;
				us_setbestsnappoint(best, wantx, wanty, testx, testy, tan, perp);
			}
			break;
		case SNAPMODEQUAD:
			if (poly->style == CIRCLE || poly->style == DISC)
			{
				radius = computedistance(poly->xv[0],poly->yv[0], poly->xv[1],poly->yv[1]);
				us_setbestsnappoint(best, wantx, wanty, poly->xv[0]+radius, poly->yv[0], 0, 0);
				us_setbestsnappoint(best, wantx, wanty, poly->xv[0]-radius, poly->yv[0], 0, 0);
				us_setbestsnappoint(best, wantx, wanty, poly->xv[0], poly->yv[0]+radius, 0, 0);
				us_setbestsnappoint(best, wantx, wanty, poly->xv[0], poly->yv[0]-radius, 0, 0);
			} else if (poly->style == CIRCLEARC)
			{
				radius = computedistance(poly->xv[0],poly->yv[0], poly->xv[1],poly->yv[1]);
				if (us_pointonarc(poly->xv[0]+radius, poly->yv[0], poly) != 0)
					us_setbestsnappoint(best, wantx, wanty, poly->xv[0]+radius, poly->yv[0], 0, 0);
				if (us_pointonarc(poly->xv[0]-radius, poly->yv[0], poly) != 0)
					us_setbestsnappoint(best, wantx, wanty, poly->xv[0]-radius, poly->yv[0], 0, 0);
				if (us_pointonarc(poly->xv[0], poly->yv[0]+radius, poly) != 0)
					us_setbestsnappoint(best, wantx, wanty, poly->xv[0], poly->yv[0]+radius, 0, 0);
				if (us_pointonarc(poly->xv[0], poly->yv[0]-radius, poly) != 0)
					us_setbestsnappoint(best, wantx, wanty, poly->xv[0], poly->yv[0]-radius, 0, 0);
			}
			break;
		case SNAPMODEINTER:
			/* get intersection polygon */
			if (interpoly == NOPOLYGON) interpoly = allocstaticpolygon(4, us_aid->cluster);

			/* search in area around this object */
			sea = initsearch(best->fromgeom->lowx, best->fromgeom->highx, best->fromgeom->lowy,
				best->fromgeom->highy, geomparent(best->fromgeom));
			for(;;)
			{
				geom = nextobject(sea);
				if (geom == NOGEOM) break;
				if (geom == best->fromgeom) continue;
				if (geom->entrytype == OBJNODEINST)
				{
					ni = geom->entryaddr.ni;
					if (ni->proto->primindex == 0) continue;
					makerot(ni, trans);
					k = nodepolys(ni);
					for(j=0; j<k; j++)
					{
						shapenodepoly(ni, j, interpoly);
						xformpoly(interpoly, trans);
						us_intersectsnappoly(best, poly, interpoly, wantx, wanty);
					}
				} else
				{
					ai = geom->entryaddr.ai;
					k = arcpolys(ai);
					for(j=0; j<k; j++)
					{
						shapearcpoly(ai, j, interpoly);
						us_intersectsnappoly(best, poly, interpoly, wantx, wanty);
					}
				}
			}
			break;
	}
}

/*
 * routine to find the intersection between polygons "poly" and "interpoly" and set this as the snap
 * point in highlight "best" (the cursor is at (wantx,wanty).
 */
void us_intersectsnappoly(HIGHLIGHT *best, POLYGON *poly, POLYGON *interpoly, INTBIG wantx, INTBIG wanty)
{
	REGISTER POLYGON *swappoly;
	REGISTER INTSML i, last;

	if (interpoly->style == OPENED || interpoly->style == OPENEDT1 || interpoly->style == OPENEDT2 ||
		interpoly->style == OPENEDT3 || interpoly->style == CLOSED || interpoly->style == VECTORS)
	{
		swappoly = poly;   poly = interpoly;   interpoly = swappoly;
	}

	if (poly->style == OPENED || poly->style == OPENEDT1 || poly->style == OPENEDT2 ||
		poly->style == OPENEDT3 || poly->style == CLOSED)
	{
		for(i=0; i<poly->count; i++)
		{
			if (i == 0)
			{
				if (poly->style != CLOSED) continue;
				last = poly->count - 1;
			} else last = i-1;
			us_intersectsnapline(best, poly->xv[last],poly->yv[last], poly->xv[i],poly->yv[i],
				interpoly, wantx, wanty);
		}
		return;
	}
	if (poly->style == VECTORS)
	{
		for(i=0; i<poly->count; i += 2)
		{
			us_intersectsnapline(best, poly->xv[i],poly->yv[i], poly->xv[i+1],poly->yv[i+1],
				interpoly, wantx, wanty);
		}
		return;
	}
}

/*
 * routine to find the intersection between the line from (x1,y1) to (x2,y2) and polygon "interpoly".
 * This is set this as the snap point in highlight "best" (the cursor is at (wantx,wanty).
 */
void us_intersectsnapline(HIGHLIGHT *best, INTBIG x1, INTBIG y1, INTBIG x2, INTBIG y2,
	POLYGON *interpoly, INTBIG wantx, INTBIG wanty)
{
	REGISTER INTSML i, last, angle, interangle;
	INTBIG ix, iy, ix1, iy1, ix2, iy2;

	angle = figureangle(x1,y1, x2,y2);
	if (interpoly->style == OPENED || interpoly->style == OPENEDT1 || interpoly->style == OPENEDT2 ||
		interpoly->style == OPENEDT3 || interpoly->style == CLOSED)
	{
		for(i=0; i<interpoly->count; i++)
		{
			if (i == 0)
			{
				if (interpoly->style != CLOSED) continue;
				last = interpoly->count - 1;
			} else last = i-1;
			interangle = figureangle(interpoly->xv[last],interpoly->yv[last], interpoly->xv[i],interpoly->yv[i]);
			if (intersect(x1,y1, angle, interpoly->xv[last],interpoly->yv[last], interangle, &ix, &iy) < 0)
				continue;
			if (ix < mini(x1,x2)) continue;
			if (ix > maxi(x1,x2)) continue;
			if (iy < mini(y1,y2)) continue;
			if (iy > maxi(y1,y2)) continue;
			if (ix < mini(interpoly->xv[last],interpoly->xv[i])) continue;
			if (ix > maxi(interpoly->xv[last],interpoly->xv[i])) continue;
			if (iy < mini(interpoly->yv[last],interpoly->yv[i])) continue;
			if (iy > maxi(interpoly->yv[last],interpoly->yv[i])) continue;
			us_setbestsnappoint(best, wantx, wanty, ix, iy, 0, 0);
		}
		return;
	}
	if (interpoly->style == VECTORS)
	{
		for(i=0; i<interpoly->count; i += 2)
		{
			interangle = figureangle(interpoly->xv[i],interpoly->yv[i], interpoly->xv[i+1],interpoly->yv[i+1]);
			if (intersect(x1,y1, angle, interpoly->xv[i],interpoly->yv[i], interangle, &ix, &iy) < 0)
				continue;
			if (ix < mini(x1,x2)) continue;
			if (ix > maxi(x1,x2)) continue;
			if (iy < mini(y1,y2)) continue;
			if (iy > maxi(y1,y2)) continue;
			if (ix < mini(interpoly->xv[i],interpoly->xv[i+1])) continue;
			if (ix > maxi(interpoly->xv[i],interpoly->xv[i+1])) continue;
			if (iy < mini(interpoly->yv[i],interpoly->yv[i+1])) continue;
			if (iy > maxi(interpoly->yv[i],interpoly->yv[i+1])) continue;
			us_setbestsnappoint(best, wantx, wanty, ix, iy, 0, 0);
		}
		return;
	}
	if (interpoly->style == CIRCLEARC)
	{
		i = circlelineintersection(interpoly->xv[0], interpoly->yv[0], interpoly->xv[1], interpoly->yv[1],
			x1, y1, x2, y2, &ix1, &iy1, &ix2, &iy2, 0);
		if (i >= 1)
		{
			if (us_pointonarc(ix1, iy1, interpoly) != 0)
				us_setbestsnappoint(best, wantx, wanty, ix1, iy1, 0, 0);
			if (i >= 2)
			{
				if (us_pointonarc(ix2, iy2, interpoly) != 0)
					us_setbestsnappoint(best, wantx, wanty, ix2, iy2, 0, 0);
			}
		}
		return;
	}
	if (interpoly->style == CIRCLE)
	{
		i = circlelineintersection(interpoly->xv[0], interpoly->yv[0], interpoly->xv[1], interpoly->yv[1],
			x1, y1, x2, y2, &ix1, &iy1, &ix2, &iy2, 0);
		if (i >= 1)
		{
			us_setbestsnappoint(best, wantx, wanty, ix1, iy1, 0, 0);
			if (i >= 2)
			{
				us_setbestsnappoint(best, wantx, wanty, ix2, iy2, 0, 0);
			}
		}
		return;
	}
}

/*
 * Routine to adjust the two highlight modules "firsthigh" and "secondhigh" to account for the
 * fact that one or both has a tangent snap point that must be tangent to the other's snap point.
 */
void us_adjusttangentsnappoints(HIGHLIGHT *firsthigh, HIGHLIGHT *secondhigh)
{
	INTBIG fx, fy, sx, sy, pfx[4], pfy[4], psx[4], psy[4], ix1, iy1, ix2, iy2;
	REGISTER INTBIG frad, srad, rad, dist, bestdist;
	REGISTER INTSML j, k, dps, bestone;
	double ang, oang, dx, dy;
	static POLYGON *firstpoly = NOPOLYGON, *secondpoly = NOPOLYGON;
	POLYGON *swappoly;
	HIGHLIGHT *swaphighlight;
	REGISTER NODEINST *ni;
	XARRAY trans;

	/* get polygon describing first object */
	if ((firsthigh->status&HIGHSNAPTAN) != 0)
	{
		if (firstpoly == NOPOLYGON) firstpoly = allocstaticpolygon(4, us_aid->cluster);
		if (firsthigh->fromgeom->entrytype != OBJNODEINST) return;
		ni = firsthigh->fromgeom->entryaddr.ni;
		if (ni->proto->primindex == 0) return;
		makerot(ni, trans);
		k = nodepolys(ni);
		for(j=0; j<k; j++)
		{
			shapenodepoly(ni, j, firstpoly);
			if (firstpoly->style == CIRCLEARC || firstpoly->style == CIRCLE ||
				firstpoly->style == DISC) break;
		}
		if (j >= k) return;
		xformpoly(firstpoly, trans);
	}

	/* get polygon describing second object */
	if ((secondhigh->status&HIGHSNAPTAN) != 0)
	{
		if (secondpoly == NOPOLYGON) secondpoly = allocstaticpolygon(4, us_aid->cluster);
		if (secondhigh->fromgeom->entrytype != OBJNODEINST) return;
		ni = secondhigh->fromgeom->entryaddr.ni;
		if (ni->proto->primindex == 0) return;
		makerot(ni, trans);
		k = nodepolys(ni);
		for(j=0; j<k; j++)
		{
			shapenodepoly(ni, j, secondpoly);
			if (secondpoly->style == CIRCLEARC || secondpoly->style == CIRCLE ||
				secondpoly->style == DISC) break;
		}
		if (j >= k) return;
		xformpoly(secondpoly, trans);
	}

	if ((firsthigh->status&HIGHSNAPTAN) != 0)
	{
		if ((secondhigh->status&HIGHSNAPTAN) != 0)
		{
			/* tangent on both curves: find radii and make sure first is larger */
			frad = computedistance(firstpoly->xv[0], firstpoly->yv[0],
				firstpoly->xv[1], firstpoly->yv[1]);
			srad = computedistance(secondpoly->xv[0], secondpoly->yv[0],
				secondpoly->xv[1], secondpoly->yv[1]);
			if (frad < srad)
			{
				swappoly = firstpoly;       firstpoly = secondpoly;   secondpoly = swappoly;
				swaphighlight = firsthigh;  firsthigh = secondhigh;   secondhigh = swaphighlight;
				rad = frad;                 frad = srad;              srad = rad;
			}

			/* find tangent lines along outside of two circles */
			dps = 0;
			if (frad == srad)
			{
				/* special case when radii are equal: construct simple outside tangent lines */
				dx = (double)(secondpoly->xv[0]-firstpoly->xv[0]);
				dy = (double)(secondpoly->yv[0]-firstpoly->yv[0]);
				if (dx == 0.0 && dy == 0.0)
				{
					us_abortcommand("Domain error during tangent computation");
					return;
				}
				ang = atan2(dy, dx);
				oang = ang + EPI / 2.0;
				if (oang > EPI * 2.0) oang -= EPI * 2.0;
				pfx[dps] = firstpoly->xv[0] + rounddouble(cos(oang) * (double)frad);
				pfy[dps] = firstpoly->yv[0] + rounddouble(sin(oang) * (double)frad);
				psx[dps] = secondpoly->xv[0] + rounddouble(cos(oang) * (double)srad);
				psy[dps] = secondpoly->yv[0] + rounddouble(sin(oang) * (double)srad);
				dps++;

				oang = ang - EPI / 2.0;
				if (oang < -EPI * 2.0) oang += EPI * 2.0;
				pfx[dps] = firstpoly->xv[0] + rounddouble(cos(oang) * (double)frad);
				pfy[dps] = firstpoly->yv[0] + rounddouble(sin(oang) * (double)frad);
				psx[dps] = secondpoly->xv[0] + rounddouble(cos(oang) * (double)srad);
				psy[dps] = secondpoly->yv[0] + rounddouble(sin(oang) * (double)srad);
				dps++;
			} else
			{
				if (circletangents(secondpoly->xv[0], secondpoly->yv[0],
					firstpoly->xv[0], firstpoly->yv[0], firstpoly->xv[0]+frad-srad, firstpoly->yv[0],
						&ix1, &iy1, &ix2, &iy2) == 0)
				{
					dx = (double)(ix1-firstpoly->xv[0]);   dy = (double)(iy1-firstpoly->yv[0]);
					if (dx == 0.0 && dy == 0.0)
					{
						us_abortcommand("Domain error during tangent computation");
						return;
					}
					ang = atan2(dy, dx);
					pfx[dps] = firstpoly->xv[0] + rounddouble(cos(ang) * (double)frad);
					pfy[dps] = firstpoly->yv[0] + rounddouble(sin(ang) * (double)frad);
					psx[dps] = secondpoly->xv[0] + rounddouble(cos(ang) * (double)srad);
					psy[dps] = secondpoly->yv[0] + rounddouble(sin(ang) * (double)srad);
					dps++;

					dx = (double)(ix2-firstpoly->xv[0]);   dy = (double)(iy2-firstpoly->yv[0]);
					if (dx == 0.0 && dy == 0.0)
					{
						us_abortcommand("Domain error during tangent computation");
						return;
					}
					ang = atan2(dy, dx);
					pfx[dps] = firstpoly->xv[0] + rounddouble(cos(ang) * (double)frad);
					pfy[dps] = firstpoly->yv[0] + rounddouble(sin(ang) * (double)frad);
					psx[dps] = secondpoly->xv[0] + rounddouble(cos(ang) * (double)srad);
					psy[dps] = secondpoly->yv[0] + rounddouble(sin(ang) * (double)srad);
					dps++;
				}
			}

			/* find tangent lines that cross between two circles */
			if (circletangents(secondpoly->xv[0], secondpoly->yv[0],
				firstpoly->xv[0], firstpoly->yv[0], firstpoly->xv[0]+frad+srad, firstpoly->yv[0],
					&ix1, &iy1, &ix2, &iy2) == 0)
			{
				dx = (double)(ix1-firstpoly->xv[0]);   dy = (double)(iy1-firstpoly->yv[0]);
				if (dx == 0.0 && dy == 0.0)
				{
					us_abortcommand("Domain error during tangent computation");
					return;
				}
				ang = atan2(dy, dx);
				pfx[dps] = firstpoly->xv[0] + rounddouble(cos(ang) * (double)frad);
				pfy[dps] = firstpoly->yv[0] + rounddouble(sin(ang) * (double)frad);
				psx[dps] = secondpoly->xv[0] - rounddouble(cos(ang) * (double)srad);
				psy[dps] = secondpoly->yv[0] - rounddouble(sin(ang) * (double)srad);
				dps++;

				dx = (double)(ix2-firstpoly->xv[0]);   dy = (double)(iy2-firstpoly->yv[0]);
				if (dx == 0.0 && dy == 0.0)
				{
					us_abortcommand("Domain error during tangent computation");
					return;
				}
				ang = atan2(dy, dx);
				pfx[dps] = firstpoly->xv[0] + rounddouble(cos(ang) * (double)frad);
				pfy[dps] = firstpoly->yv[0] + rounddouble(sin(ang) * (double)frad);
				psx[dps] = secondpoly->xv[0] - rounddouble(cos(ang) * (double)srad);
				psy[dps] = secondpoly->yv[0] - rounddouble(sin(ang) * (double)srad);
				dps++;
			}

			/* screen out points that are not on arcs */
			k = 0;
			for(j=0; j<dps; j++)
			{
				if (firstpoly->style == CIRCLEARC && us_pointonarc(pfx[j], pfy[j], firstpoly) == 0)
					continue;
				if (secondpoly->style == CIRCLEARC && us_pointonarc(psx[j], psy[j], secondpoly) == 0)
					continue;
				pfx[k] = pfx[j];   pfy[k] = pfy[j];
				psx[k] = psx[j];   psy[k] = psy[j];
				k++;
			}
			dps = k;
			if (dps == 0) return;

			/* now find the tangent line that is closest to the snap points */
			us_getsnappoint(firsthigh, &fx, &fy);
			us_getsnappoint(secondhigh, &sx, &sy);
			for(j=0; j<dps; j++)
			{
				dist = computedistance(pfx[j],pfy[j], fx,fy) + computedistance(psx[j],psy[j], sx,sy);
				if (j == 0 || dist < bestdist)
				{
					bestdist = dist;
					bestone = j;
				}
			}

			/* set the best one */
			us_xformpointtonode(pfx[bestone], pfy[bestone], firsthigh->fromgeom->entryaddr.ni,
				&firsthigh->snapx, &firsthigh->snapy);
			us_xformpointtonode(psx[bestone], psy[bestone], secondhigh->fromgeom->entryaddr.ni,
				&secondhigh->snapx, &secondhigh->snapy);
		} else
		{
			/* compute tangent to first object */
			us_getsnappoint(secondhigh, &sx, &sy);
			us_adjustonetangent(firsthigh, firstpoly, sx, sy);
		}
	} else
	{
		if ((secondhigh->status&HIGHSNAPTAN) != 0)
		{
			us_getsnappoint(firsthigh, &fx, &fy);
			us_adjustonetangent(secondhigh, secondpoly, fx, fy);
		}
	}
}

/*
 * Routine to adjust the snap point on "high" so that it is tangent to its curved
 * polygon "poly" and runs through (x, y).
 */
void us_adjustonetangent(HIGHLIGHT *high, POLYGON *poly, INTBIG x, INTBIG y)
{
	REGISTER NODEINST *ni;
	INTBIG ix1, iy1, ix2, iy2, fx, fy;
	REGISTER INTBIG xv, yv;

	if (high->fromgeom->entrytype != OBJNODEINST) return;
	ni = high->fromgeom->entryaddr.ni;
	us_getsnappoint(high, &fx, &fy);
	if (circletangents(x, y, poly->xv[0], poly->yv[0], poly->xv[1], poly->yv[1],
		&ix1, &iy1, &ix2, &iy2) != 0) return;
	if (computedistance(fx, fy, ix1, iy1) > computedistance(fx, fy, ix2, iy2))
	{
		xv = ix1;   ix1 = ix2;   ix2 = xv;
		yv = iy1;   iy1 = iy2;   iy2 = yv;
	}

	if (poly->style != CIRCLEARC || us_pointonarc(ix1, iy1, poly) != 0)
	{
		us_xformpointtonode(ix1, iy1, ni, &high->snapx, &high->snapy);
		return;
	}
	if (poly->style != CIRCLEARC || us_pointonarc(ix2, iy2, poly) != 0)
	{
		us_xformpointtonode(ix2, iy2, ni, &high->snapx, &high->snapy);
		return;
	}
}

/*
 * Routine to adjust the two highlight modules "firsthigh" and "secondhigh" to account for the
 * fact that one or both has a perpendicular snap point that must be perpendicular
 * to the other's snap point.
 */
void us_adjustperpendicularsnappoints(HIGHLIGHT *firsthigh, HIGHLIGHT *secondhigh)
{
	INTBIG fx, fy;
	static POLYGON *secondpoly = NOPOLYGON;
	REGISTER NODEINST *ni;
	XARRAY trans;

	if ((secondhigh->status&HIGHSNAPPERP) != 0)
	{
		/* get polygon describing second object */
		if (secondpoly == NOPOLYGON) secondpoly = allocstaticpolygon(4, us_aid->cluster);
		if (secondhigh->fromgeom->entrytype != OBJNODEINST) return;
		ni = secondhigh->fromgeom->entryaddr.ni;
		if (ni->proto->primindex == 0) return;
		makerot(ni, trans);
		(void)nodepolys(ni);
		shapenodepoly(ni, 0, secondpoly);
		xformpoly(secondpoly, trans);

		us_getsnappoint(firsthigh, &fx, &fy);
		us_adjustoneperpendicular(secondhigh, secondpoly, fx, fy);
	}
}

/*
 * Routine to adjust the snap point on "high" so that it is perpendicular to
 * polygon "poly" and point (x, y).
 */
void us_adjustoneperpendicular(HIGHLIGHT *high, POLYGON *poly, INTBIG x, INTBIG y)
{
	REGISTER NODEINST *ni;
	REGISTER INTBIG rad;
	INTBIG ix, iy;
	REGISTER INTSML ang;

	if (high->fromgeom->entrytype != OBJNODEINST) return;
	ni = high->fromgeom->entryaddr.ni;

	if (poly->style == CIRCLE || poly->style == CIRCLEARC)
	{
		/* compute perpendicular point */
		ang = figureangle(poly->xv[0], poly->yv[0], x, y);
		rad = computedistance(poly->xv[0], poly->yv[0], poly->xv[1], poly->yv[1]);
		ix = poly->xv[0] + mult(cosine(ang), rad);
		iy = poly->yv[0] + mult(sine(ang), rad);
		if (poly->style == CIRCLEARC || us_pointonarc(ix, iy, poly) == 0) return;
		us_xformpointtonode(ix, iy, ni, &high->snapx, &high->snapy);
		return;
	}

	/* handle straight line perpendiculars */
	ix = x;   iy = y;
	(void)closestpointtosegment(poly->xv[0], poly->yv[0], poly->xv[1], poly->yv[1], &ix, &iy);
	if (ix != x || iy != y) us_xformpointtonode(ix, iy, ni, &high->snapx, &high->snapy);
}

/*
 * routine to determine whether the point (x, y) is on the arc in "poly".
 * returns nonzero if so.
 */
INTSML us_pointonarc(INTBIG x, INTBIG y, POLYGON *poly)
{
	REGISTER INTSML angle, startangle, endangle;

	if (poly->style != CIRCLEARC) return(0);

	angle = figureangle(poly->xv[0],poly->yv[0], x,y);
	endangle = figureangle(poly->xv[0],poly->yv[0], poly->xv[1],poly->yv[1]);
	startangle = figureangle(poly->xv[0],poly->yv[0], poly->xv[2],poly->yv[2]);

	if (endangle > startangle)
	{
		if (angle >= startangle && angle <= endangle) return(1);
	} else
	{
		if (angle >= startangle || angle <= endangle) return(1);
	}
	return(0);
}

/*
 * routine to get the true coordinate of the snap point in "high" and place it in (x,y).
 */
void us_getsnappoint(HIGHLIGHT *high, INTBIG *x, INTBIG *y)
{
	REGISTER NODEINST *ni;
	XARRAY trans;
	INTBIG xt, yt;

	if (high->fromgeom->entrytype == OBJNODEINST)
	{
		ni = high->fromgeom->entryaddr.ni;
		makeangle(ni->rotation, ni->transpose, trans);
		xform(high->snapx, high->snapy, &xt, &yt, trans);
		*x = (ni->highx + ni->lowx) / 2 + xt;
		*y = (ni->highy + ni->lowy) / 2 + yt;
	} else
	{
		*x = (high->fromgeom->highx + high->fromgeom->lowx) / 2 + high->snapx;
		*y = (high->fromgeom->highy + high->fromgeom->lowy) / 2 + high->snapy;
	}
}

void us_setbestsnappoint(HIGHLIGHT *best, INTBIG wantx, INTBIG wanty, INTBIG newx, INTBIG newy, INTSML tan, INTSML perp)
{
	REGISTER INTBIG olddist, newdist;
	INTBIG oldx, oldy;

	if ((best->status & HIGHSNAP) != 0)
	{
		us_getsnappoint(best, &oldx, &oldy);
		olddist = computedistance(wantx, wanty, oldx, oldy);
		newdist = computedistance(wantx, wanty, newx, newy);
		if (newdist >= olddist) return;
	}

	/* set the snap point */
	if (best->fromgeom->entrytype == OBJNODEINST)
	{
		us_xformpointtonode(newx, newy, best->fromgeom->entryaddr.ni, &best->snapx, &best->snapy);
	} else
	{
		best->snapx = newx - (best->fromgeom->highx + best->fromgeom->lowx) / 2;
		best->snapy = newy - (best->fromgeom->highy + best->fromgeom->lowy) / 2;
	}
	best->status |= HIGHSNAP;
	if (tan != 0) best->status |= HIGHSNAPTAN;
	if (perp != 0) best->status |= HIGHSNAPPERP;
}

void us_xformpointtonode(INTBIG x, INTBIG y, NODEINST *ni, INTBIG *xo, INTBIG *yo)
{
	XARRAY trans;
	INTBIG xv, yv;

	if (ni->transpose != 0) makeangle(ni->rotation, ni->transpose, trans); else
		makeangle((INTSML)((3600 - ni->rotation)%3600), 0, trans);
	xv = x - (ni->highx + ni->lowx) / 2;
	yv = y - (ni->highy + ni->lowy) / 2;
	xform(xv, yv, xo, yo, trans);
}

/*
 * routine to return the object that is closest to point (rdx, rdy)
 * in facet "facet".  Searches nodes first.
 * This is used in the "create join-angle" command.
 */
GEOM *us_getclosest(INTBIG rdx, INTBIG rdy, NODEPROTO *facet)
{
	REGISTER GEOM *geom, *highgeom, *bestgeom;
	REGISTER INTBIG sea, bestdist, dist;
	static POLYGON *poly = NOPOLYGON;
	REGISTER VARIABLE *var;
	HIGHLIGHT high;

	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);

	highgeom = NOGEOM;
	var = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_highlighted);
	if (var != NOVARIABLE)
	{
		if (getlength(var) == 1)
		{
			(void)us_makehighlight(((char **)var->addr)[0], &high);
			highgeom = high.fromgeom;
		}
	}

	/* see if there is a direct hit on another node */
	sea = initsearch(rdx, rdx, rdy, rdy, facet);
	bestdist = HUGEINT;
	for(;;)
	{
		geom = nextobject(sea);
		if (geom == NOGEOM) break;
		if (geom == highgeom) continue;
		if (geom->entrytype != OBJNODEINST) continue;
		if ((geom->entryaddr.ni->userbits&HARDSELECTN) != 0) continue;
		dist = us_disttoobject(rdx, rdy, geom);
		if (dist > bestdist) continue;
		bestdist = dist;
		bestgeom = geom;
	}
	if (bestdist < 0) return(bestgeom);

	/* look at arcs second */
	bestdist = HUGEINT;
	sea = initsearch(rdx, rdx, rdy, rdy, facet);
	for(;;)
	{
		geom = nextobject(sea);
		if (geom == NOGEOM) break;
		if (geom == highgeom) continue;
		if (geom->entrytype != OBJARCINST) continue;
		if ((geom->entryaddr.ai->userbits&HARDSELECTA) != 0) continue;
		dist = us_disttoobject(rdx, rdy, geom);
		if (dist > bestdist) continue;
		bestdist = dist;
		bestgeom = geom;
	}
	if (bestdist < 0) return(bestgeom);
	return(NOGEOM);
}

/*
 * Routine to return the distance from point (x,y) to object "geom".
 * Negative values are direct hits.
 */
INTBIG us_disttoobject(INTBIG x, INTBIG y, GEOM *geom)
{
	XARRAY trans;
	REGISTER INTBIG wid, bestdist, dist, fun;
	REGISTER INTSML count, box;
	static POLYGON *poly = NOPOLYGON;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	INTBIG plx, ply, phx, phy;

	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, us_aid->cluster);
	if (geom->entrytype == OBJNODEINST)
	{
		ni = geom->entryaddr.ni;
		makerot(ni, trans);

		/* special case for MOS transistors: examine the gate/active tabs */
		fun = (ni->proto->userbits&NFUNCTION) >> NFUNCTIONSH;
		if (fun == NPTRANMOS || fun == NPTRAPMOS || fun == NPTRADMOS)
		{
			count = nodepolys(ni);
			bestdist = HUGEINT;
			for(box=0; box<count; box++)
			{
				shapenodepoly(ni, box, poly);
				fun = layerfunction(ni->proto->tech, poly->layer) & LFTYPE;
				if (layerispoly(fun) == 0 && fun != LFDIFF) continue;
				xformpoly(poly, trans);
				dist = polydistance(poly, x, y);
				if (dist < bestdist) bestdist = dist;
			}
			return(bestdist);
		}

		/* special case for 1-polygon primitives: check precise distance to cursor */
		if (ni->proto->primindex != 0 && (ni->proto->userbits&NEDGESELECT) != 0)
		{
			count = nodepolys(ni);
			bestdist = HUGEINT;
			for(box=0; box<count; box++)
			{
				shapenodepoly(ni, box, poly);
				if ((poly->desc->colstyle&INVISIBLE) != 0) continue;
				xformpoly(poly, trans);
				dist = polydistance(poly, x, y);
				if (dist < bestdist) bestdist = dist;
			}
			return(bestdist);
		}

		/* get the bounds of the node in a polygon */
		nodesizeoffset(ni, &plx, &ply, &phx, &phy);
		maketruerectpoly(ni->lowx+plx, ni->highx-phx, ni->lowy+ply, ni->highy-phy, poly);
		poly->style = FILLEDRECT;
		xformpoly(poly, trans);
		return(polydistance(poly, x, y));
	}

	/* determine distance to arc */
	ai = geom->entryaddr.ai;

	/* if arc is selectable precisely, check distance to cursor */
	if ((ai->proto->userbits&AEDGESELECT) != 0)
	{
		count = arcpolys(ai);
		bestdist = HUGEINT;
		for(box=0; box<count; box++)
		{
			shapearcpoly(ai, box, poly);
			if ((poly->desc->colstyle&INVISIBLE) != 0) continue;
			dist = polydistance(poly, x, y);
			if (dist < bestdist) bestdist = dist;
		}
		return(bestdist);
	}

	/* standard distance to the arc */
	wid = ai->width - arcwidthoffset(ai);
	if (wid == 0) wid = ai->proto->tech->deflambda;
	if (curvedarcoutline(ai, poly, FILLED, wid) != 0)
		makearcpoly(ai->length, wid, ai, poly, FILLED);
	return(polydistance(poly, x, y));
}

/*********************************** TEXT ON NODES/ARCS ***********************************/

static INTSML us_nodearcvarptr;
static INTSML us_nodearcvarcount;
static INTSML us_portvarptr;
static INTSML us_portvarcount;
static INTSML us_nodenameflg;
static PORTEXPINST *us_nodeexpptr;
static PORTPROTO *us_portptr;

void us_initnodetext(NODEINST *ni, INTSML findspecial)
{
	us_nodearcvarptr = 0;
	us_nodearcvarcount = tech_displayablenvars(ni);
	us_portvarptr = 0;
	us_portvarcount = 0;

	if (findspecial != 0)
	{
		us_nodenameflg = 0;
	} else
	{
		us_nodenameflg = 1;

		/* if the "special" option is not set and node text is disabled, skip it */
		if ((us_useroptions&NOTEXTSELECT) != 0) us_nodearcvarptr = ni->numvar;
	}
	us_nodeexpptr = ni->firstportexpinst;
}

INTSML us_getnodetext(NODEINST *ni, WINDOWPART *win, POLYGON *poly, VARIABLE **var,
	PORTPROTO **port)
{
	INTBIG xc, yc;
	REGISTER PORTEXPINST *pe;
	XARRAY trans;

	if (us_nodenameflg == 0)
	{
		us_nodenameflg = 1;
		if (ni->proto->primindex == 0 && (ni->userbits&NEXPAND) == 0)
		{
			*var = NOVARIABLE;
			*port = NOPORTPROTO;
			us_maketextpoly(describenodeproto(ni->proto), win,
				(ni->lowx + ni->highx) / 2, (ni->lowy + ni->highy) / 2,
					ni, whattech(ni->proto), ni->textdescript, poly);
			poly->style = FILLED;
			return(0);
		}
	}
	if (us_nodearcvarptr < us_nodearcvarcount)
	{
		*var = tech_filldisplayablenvar(ni, poly);
#if 0
		us_maketextpoly(poly->string, win, (ni->lowx + ni->highx) / 2,
			(ni->lowy + ni->highy) / 2, ni, whattech(ni->proto), (*var)->textdescript, poly);
#else
		makerot(ni, trans);
		xform(poly->xv[0], poly->yv[0], &poly->xv[0], &poly->yv[0], trans);
		us_filltextpoly(poly->string, win, poly->xv[0], poly->yv[0],
			trans, whattech(ni->proto), (*var)->textdescript, poly);
#endif
		*port = NOPORTPROTO;
		us_nodearcvarptr++;
		poly->style = FILLED;
		return(0);
	}

	if (us_portvarptr < us_portvarcount)
	{
		*var = tech_filldisplayableportvar(us_portptr, poly);
		portposition(us_portptr->subnodeinst, us_portptr->subportproto, &xc, &yc);
		us_maketextpoly(poly->string, win, xc, yc, us_portptr->subnodeinst,
			whattech(us_portptr->subnodeinst->proto), (*var)->textdescript, poly);
		*port = us_portptr;
		us_portvarptr++;
		poly->style = FILLED;
		return(0);
	}

	/* check exported ports on the node */
	if (us_nodeexpptr != NOPORTEXPINST)
	{
		pe = us_nodeexpptr;
		us_portptr = pe->exportproto;
		us_portvarcount = tech_displayableportvars(us_portptr);
		us_portvarptr = 0;
		*port = pe->exportproto;
		*var = NOVARIABLE;
		us_nodeexpptr = pe->nextportexpinst;

		/* build polygon that surrounds text */
		portposition(ni, (*port)->subportproto, &xc, &yc);
		us_maketextpoly((*port)->protoname, win, xc, yc, ni, whattech(ni->proto),
			(*port)->textdescript, poly);
		poly->style = FILLED;
		return(0);
	}

	return(1);
}

void us_initarctext(ARCINST *ai, INTSML findspecial)
{
	us_nodearcvarptr = 0;
	us_nodearcvarcount = tech_displayableavars(ai);
	if (findspecial == 0)
	{
		/* if the "special" option is not set and arc text is disabled, skip it */
		if ((us_useroptions&NOTEXTSELECT) != 0) us_nodearcvarptr = ai->numvar;
	}
}

INTSML us_getarctext(ARCINST *ai, WINDOWPART *win, POLYGON *poly, VARIABLE **var)
{
	if (us_nodearcvarptr < us_nodearcvarcount)
	{
		*var = tech_filldisplayableavar(ai, poly);
		us_maketextpoly(poly->string, win,
			(ai->end[0].xpos + ai->end[1].xpos) / 2, (ai->end[0].ypos + ai->end[1].ypos) / 2,
				NONODEINST, ai->proto->tech, (*var)->textdescript, poly);
		us_nodearcvarptr++;
		poly->style = FILLED;
		return(0);
	}
	return(1);
}

/*
 * routine to build a polygon in "poly" that has four points describing the
 * text in "str" with descriptor "descript".  The text is in window "win"
 * on an object whose center is (xc,yc) and is on node "ni" (or not if NONODEINST)
 * and uses technology "tech".
 */
void us_maketextpoly(char *str, WINDOWPART *win, INTBIG xc, INTBIG yc, NODEINST *ni,
	TECHNOLOGY *tech, INTBIG descript, POLYGON *poly)
{
	INTBIG newxc, newyc, lambda;
	XARRAY trans;

	/* determine location of text */
	if (ni == NONODEINST) transid(trans); else
		makeangle(ni->rotation, ni->transpose, trans);

	lambda = tech->deflambda;
	newxc = (descript&VTXOFF)>>VTXOFFSH;
	if ((descript&VTXOFFNEG) != 0) newxc = -newxc;
	newxc = newxc * lambda / 4;
	newyc = (descript&VTYOFF)>>VTYOFFSH;
	if ((descript&VTYOFFNEG) != 0) newyc = -newyc;
	newyc = newyc * lambda / 4;
	xform(newxc, newyc, &newxc, &newyc, trans);
	xc += newxc;   yc += newyc;
	us_filltextpoly(str, win, xc, yc, trans, tech, descript, poly);
}

void us_filltextpoly(char *str, WINDOWPART *win, INTBIG xc, INTBIG yc, XARRAY trans,
	TECHNOLOGY *tech, INTBIG descript, POLYGON *poly)
{
	INTBIG xw, yw;
	INTSML tsx, tsy;

	/* determine size of text */
	screensettextsize(win, truefontsize((INTSML)((descript&VTSIZE)>>VTSIZESH), win, tech));
	screengettextsize(win, str, &tsx, &tsy);
	xw = muldiv(tsx, win->screenhx - win->screenlx, win->usehx - win->uselx);
	yw = muldiv(tsy, win->screenhy - win->screenly, win->usehy - win->usely);

	switch (descript&VTPOSITION)
	{
		case VTPOSCENT:      poly->style = TEXTCENT;      break;
		case VTPOSBOXED:     poly->style = TEXTBOX;       break;
		case VTPOSUP:        poly->style = TEXTBOT;       break;
		case VTPOSDOWN:      poly->style = TEXTTOP;       break;
		case VTPOSLEFT:      poly->style = TEXTRIGHT;     break;
		case VTPOSRIGHT:     poly->style = TEXTLEFT;      break;
		case VTPOSUPLEFT:    poly->style = TEXTBOTRIGHT;  break;
		case VTPOSUPRIGHT:   poly->style = TEXTBOTLEFT;   break;
		case VTPOSDOWNLEFT:  poly->style = TEXTTOPRIGHT;  break;
		case VTPOSDOWNRIGHT: poly->style = TEXTTOPLEFT;   break;
	}
	poly->style = rotatelabel(poly->style, trans);

	switch (poly->style)
	{
		case TEXTTOP:                    yc -= yw/2;   break;
		case TEXTBOT:                    yc += yw/2;   break;
		case TEXTLEFT:     xc += xw/2;                 break;
		case TEXTRIGHT:    xc -= xw/2;                 break;
		case TEXTTOPLEFT:  xc += xw/2;   yc -= yw/2;   break;
		case TEXTBOTLEFT:  xc += xw/2;   yc += yw/2;   break;
		case TEXTTOPRIGHT: xc -= xw/2;   yc -= yw/2;   break;
		case TEXTBOTRIGHT: xc -= xw/2;   yc += yw/2;   break;
	}

	/* construct polygon with actual size */
	poly->xv[0] = xc - xw/2;   poly->yv[0] = yc - yw/2;
	poly->xv[1] = xc - xw/2;   poly->yv[1] = yc + yw/2;
	poly->xv[2] = xc + xw/2;   poly->yv[2] = yc + yw/2;
	poly->xv[3] = xc + xw/2;   poly->yv[3] = yc - yw/2;
	poly->count = 4;
	poly->layer = -1;
	poly->style = CLOSED;
}

/*
 * routine to handle the motion of the text object in "high".  The specific
 * command for motion is in the "count" strings in "par"
 */
void us_movetext(HIGHLIGHT *high, INTSML count, char *par[])
{
	REGISTER INTSML l, len;
	REGISTER INTBIG descript, lambda, amt, itemHit;
	INTBIG xcur, ycur, xc, yc, xw, yw, dx, dy, addr, type, lx, hx, ly, hy;
	XARRAY trans;
	INTSML tsx, tsy;
	REGISTER NODEINST *ni;
	REGISTER TECHNOLOGY *tech;
	REGISTER NODEPROTO *np;
	REGISTER char *str, *pp;
	extern DIALOG us_movetodialog;

	/* turn off all highlighting */
	us_clearhighlightcount();

	/* no arguments: simple motion */
	if (count == 0)
	{
		/* move text to cursor position: get co-ordinates of cursor */
		if (us_demandxy(&xcur, &ycur)) return;
		gridalign(&xcur, &ycur, us_alignment);

		/* make sure the cursor is in the right facet */
		np = us_needfacet();
		if (np == NONODEPROTO) return;
		if (np != high->facet)
		{
			us_abortcommand("Cannot move text to another facet");
			(void)us_addhighlight(high);
			return;
		}

		/* get descriptor and text string */
		ni = us_gethighnodeinst(high);
		descript = us_gethighdescript(high);
		tech = us_hightech(high);
		us_gethighaddrtype(high, &addr, &type);
		tech = us_getobjectinfo(addr, type, &lx, &hx, &ly, &hy);
		xc = (lx + hx) / 2;
		yc = (ly + hy) / 2;
		str = us_gethighstring(high);

		/* determine number of lines of text and text size */
		len = 1;
		if (high->fromvar != NOVARIABLE && (high->fromvar->type&VISARRAY) != 0)
			len = (INTSML)getlength(high->fromvar);
		if (len > 1)
		{
			xw = high->fromgeom->highx - high->fromgeom->lowx;
			yw = high->fromgeom->highy - high->fromgeom->lowy;
		} else
		{
			screensettextsize(el_curwindowpart, truefontsize((INTSML)((descript&VTSIZE)>>VTSIZESH),
				el_curwindowpart, tech));
			screengettextsize(el_curwindowpart, str, &tsx, &tsy);
			xw = muldiv(tsx, el_curwindowpart->screenhx-el_curwindowpart->screenlx,
				el_curwindowpart->usehx-el_curwindowpart->uselx);
			yw = muldiv(tsy, el_curwindowpart->screenhy-el_curwindowpart->screenly,
				el_curwindowpart->usehy-el_curwindowpart->usely);
		}

		/* adjust the cursor position if selecting interactively */
		if ((us_aid->aidstate&INTERACTIVE) != 0)
		{
			us_textmoveinit(xc, yc, xw, yw, descript, high->fromgeom);
			trackcursor(0, us_ignoreup, us_textmovebegin, us_textmovedown,
				us_stoponchar, us_dragup, TRACKDRAGGING);
			if (el_pleasestop != 0) return;
			if (us_demandxy(&xcur, &ycur) != 0) return;
			gridalign(&xcur, &ycur, us_alignment);
		}

		dx = xcur-xc;    dy = ycur-yc;

		/* move entire node if it is an "invisible pin" */
		if (ni != NONODEINST && ni->proto == gen_invispinprim)
		{
			startobjectchange((INTBIG)ni, VNODEINST);
			modifynodeinst(ni, dx, dy, dx, dy, 0, 0);
			endobjectchange((INTBIG)ni, VNODEINST);
			(void)us_addhighlight(high);
			return;
		}

		/* undraw the text */
		startobjectchange((INTBIG)high->fromgeom->entryaddr.blind,
			high->fromgeom->entrytype == OBJNODEINST ? VNODEINST : VARCINST);

		/* set the new descriptor on the text */
		if (ni != NONODEINST)
		{
			if (ni->transpose != 0)
				makeangle(ni->rotation, ni->transpose, trans); else
					makeangle((INTSML)((3600-ni->rotation)%3600), 0, trans);
			xform(dx, dy, &dx, &dy, trans);
		}
		lambda = figurelambda(high->fromgeom);
		descript = us_setdescriptoffset(descript, dx*4/lambda, dy*4/lambda);
		us_modifytextdescript(high, descript);

		/* redisplay the text */
		endobjectchange((INTBIG)high->fromgeom->entryaddr.blind,
			high->fromgeom->entrytype == OBJNODEINST ? VNODEINST : VARCINST);
		(void)us_addhighlight(high);

		/* modify all higher-level nodes if port moved */
		if (high->fromvar == NOVARIABLE && high->fromport != NOPORTPROTO)
		{
			for(ni = high->fromport->parent->firstinst; ni != NONODEINST; ni = ni->nextinst)
			{
				if ((ni->userbits&NEXPAND) != 0 && (high->fromport->userbits&PORTDRAWN) == 0)
					continue;
				startobjectchange((INTBIG)ni, VNODEINST);
				endobjectchange((INTBIG)ni, VNODEINST);
			}
		}
		return;
	}

	/* handle absolute motion option "move to X Y" */
	l = (INTSML)strlen(pp = par[0]);
	if (namesamen(pp, "angle", l) == 0 && l >= 1)
	{
		us_abortcommand("Cannot move text along an angle");
		return;
	}
	if (namesamen(pp, "to", l) == 0 && l >= 1)
	{
		descript = us_gethighdescript(high);
		ni = us_gethighnodeinst(high);

		/* get absolute position to place object */
		if (count == 2 && namesamen(par[1], "dialog", strlen(par[1])) == 0)
		{
			/* get coordinates from dialog */
			if (DiaInitDialog(&us_movetodialog) != 0) return;
			DiaSetText(3, latoa(ni->lowx));
			DiaSetText(5, latoa(ni->lowy));
			for(;;)
			{
				itemHit = DiaNextHit();
				if (itemHit == CANCEL || itemHit == OK) break;
			}
			xcur = atola(DiaGetText(3));
			ycur = atola(DiaGetText(5));
			DiaDoneDialog();
			if (itemHit == CANCEL) return;
		} else
		{
			/* get coordinates from command line */
			if (count < 3)
			{
				us_abortcommand("Usage: move to X Y");
				/* (void)us_addhighlight(high); */
				return;
			}
			xcur = atola(par[1]);
			ycur = atola(par[2]);
		}

		/* move entire node if it is an "invisible pin" */
		if (ni != NONODEINST && ni->proto == gen_invispinprim)
		{
			dx = xcur - ni->lowx;   dy = ycur - ni->lowy;
			startobjectchange((INTBIG)ni, VNODEINST);
			modifynodeinst(ni, dx, dy, dx, dy, 0, 0);
			endobjectchange((INTBIG)ni, VNODEINST);
			(void)us_addhighlight(high);
			return;
		}

		/* undraw the text */
		startobjectchange((INTBIG)high->fromgeom->entryaddr.blind,
			high->fromgeom->entrytype == OBJNODEINST ? VNODEINST : VARCINST);

		if (ni != NONODEINST)
		{
			if (ni->transpose != 0)
				makeangle(ni->rotation, ni->transpose, trans); else
					makeangle((INTSML)((3600-ni->rotation)%3600), 0, trans);
			xform(xcur, ycur, &xcur, &ycur, trans);
		}

		/* set the new descriptor on the text */
		lambda = figurelambda(high->fromgeom);
		descript = us_setdescriptoffset(descript, xcur*4/lambda, ycur*4/lambda);
		us_modifytextdescript(high, descript);

		/* redisplay the text */
		endobjectchange((INTBIG)high->fromgeom->entryaddr.blind,
			high->fromgeom->entrytype == OBJNODEINST ? VNODEINST : VARCINST);
		(void)us_addhighlight(high);

		/* modify all higher-level nodes if port moved */
		if (high->fromvar == NOVARIABLE && high->fromport != NOPORTPROTO)
		{
			for(ni = high->fromport->parent->firstinst; ni != NONODEINST; ni = ni->nextinst)
			{
				if ((ni->userbits&NEXPAND) != 0 &&
					(high->fromport->userbits&PORTDRAWN) == 0) continue;
				startobjectchange((INTBIG)ni, VNODEINST);
				endobjectchange((INTBIG)ni, VNODEINST);
			}
		}
		return;
	}

	/* if direction is specified, get it */
	dx = dy = 0;
	if (namesamen(pp, "up", l) == 0 && l >= 1)
	{
		if (count < 2) amt = el_curtech->deflambda; else amt = atola(par[1]);
		dy += amt;
	} else if (namesamen(pp, "down", l) == 0 && l >= 1)
	{
		if (count < 2) amt = el_curtech->deflambda; else amt = atola(par[1]);
		dy -= amt;
	} else if (namesamen(pp, "left", l) == 0 && l >= 1)
	{
		if (count < 2) amt = el_curtech->deflambda; else amt = atola(par[1]);
		dx -= amt;
	} else if (namesamen(pp, "right", l) == 0 && l >= 1)
	{
		if (count < 2) amt = el_curtech->deflambda; else amt = atola(par[1]);
		dx += amt;
	} else
	{
		us_abortcommand("Invalid MOVE option: %s", par[0]);
		(void)us_addhighlight(high);
		return;
	}

	if (dx == 0 && dy == 0) return;

	/* now move the object */
	ni = us_gethighnodeinst(high);
	descript = us_gethighdescript(high);
	us_gethighaddrtype(high, &addr, &type);

	/* move entire node if it is an "invisible pin" */
	if (ni != NONODEINST && ni->proto == gen_invispinprim && type == VNODEINST)
	{
		startobjectchange((INTBIG)ni, VNODEINST);
		modifynodeinst(ni, dx, dy, dx, dy, 0, 0);
		endobjectchange((INTBIG)ni, VNODEINST);
		(void)us_addhighlight(high);
		return;
	}

	/* make offsets be the proper scale */
	xcur = (descript&VTXOFF) >> VTXOFFSH;
	if ((descript&VTXOFFNEG) != 0) xcur = -xcur;
	ycur = (descript&VTYOFF) >> VTYOFFSH;
	if ((descript&VTYOFFNEG) != 0) ycur = -ycur;

	if (ni != NONODEINST)
	{
		if (ni->transpose != 0)
			makeangle(ni->rotation, ni->transpose, trans); else
				makeangle((INTSML)((3600-ni->rotation)%3600), 0, trans);
		xform(dx, dy, &dx, &dy, trans);
	}

	/* recompute the descriptor */
	tech = us_hightech(high);
	lambda = tech->deflambda;
	descript = us_setdescriptoffset(descript, xcur+dx*4/lambda, ycur+dy*4/lambda);

	/* undraw the text */
	if (type == VNODEPROTO)
	{
		us_undrawfacetvariable(high->fromvar, high->facet);
	} else if (type == VPORTPROTO)
	{
		startobjectchange((INTBIG)(((PORTPROTO *)addr)->subnodeinst), VNODEINST);
	} else
	{
		startobjectchange(addr, type);
	}

	/* set the new descriptor on the text */
	us_modifytextdescript(high, descript);

	/* redisplay the text */
	if (type == VNODEPROTO)
	{
		us_drawfacetvariable(high->fromvar, high->facet);
	} else if (type == VPORTPROTO)
	{
		endobjectchange((INTBIG)(((PORTPROTO *)addr)->subnodeinst), VNODEINST);
	} else
	{
		endobjectchange(addr, type);
	}
	(void)us_addhighlight(high);

	/* modify all higher-level nodes if port moved */
	if (high->fromvar == NOVARIABLE && high->fromport != NOPORTPROTO)
	{
		for(ni = high->fromport->parent->firstinst; ni != NONODEINST; ni = ni->nextinst)
		{
			if ((ni->userbits&NEXPAND) != 0 && (high->fromport->userbits&PORTDRAWN) == 0) continue;
			startobjectchange((INTBIG)ni, VNODEINST);
			endobjectchange((INTBIG)ni, VNODEINST);
		}
	}
}
