/*
 * Electric(tm) VLSI Design System
 *
 * File: usrctech.c
 * User interface aid: technology translation module
 * Written by: Steven M. Rubin, Static Free Software
 * Schematic conversion written by: Nora Ryan, Schlumberger Palo Alto Research
 *
 * 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 "efunction.h"
#include "egraphics.h"
#include "tech.h"
#include "tecgen.h"
#include "tecschem.h"
#include "usr.h"
#include "drc.h"
#include "usredtec.h"		/* for technology documentation */
#include <math.h>

#define TRAN_PIN   -1
#define TRAN_FACET -2

/* prototypes for local routines */
NODEPROTO *us_tran_linkage(char*, VIEW*, NODEPROTO*);
void us_tran_logmakenodes(NODEPROTO*, NODEPROTO*, TECHNOLOGY*);
NODEINST *us_tran_logmakenode(NODEPROTO*, NODEINST*, INTSML, INTSML, NODEPROTO*, TECHNOLOGY*);
void us_tran_logmakearcs(NODEPROTO*, NODEPROTO*);
void us_tran_logadjustnodes(NODEPROTO*);
void us_tran_makemanhattan(NODEPROTO*);
PORTPROTO *us_tranconvpp(NODEINST*, PORTPROTO*);
INTSML us_tranismos(NODEINST*);
PORTPROTO *us_trangetproto(NODEINST*, INTSML);
NODEPROTO *us_tran_makelayoutfacets(NODEPROTO*, char*, TECHNOLOGY*, TECHNOLOGY*, VIEW*);
INTSML us_tran_makelayoutparts(NODEPROTO*, NODEPROTO*, TECHNOLOGY*, TECHNOLOGY*, VIEW*);
void us_tran_convertuniversals(NODEPROTO*, TECHNOLOGY*);
void us_tran_replacenodeinst(NODEINST*, NODEPROTO*);
void us_tran_replacearcinst(ARCINST*, ARCPROTO*);
INTSML us_tran_validstate(ARCINST*, INTSML, TECHNOLOGY*, ARCPROTO**);
void us_tranplacenode(NODEINST*, NODEPROTO*, NODEPROTO*, TECHNOLOGY*, TECHNOLOGY*);
PORTPROTO *us_convport(NODEINST*, NODEINST*, PORTPROTO*);
ARCPROTO *us_figurenewaproto(ARCPROTO*, TECHNOLOGY*);
NODEPROTO *us_figurenewnproto(NODEINST*, TECHNOLOGY*);
void us_dumpfields(char***, INTSML, INTSML, FILE*, char*);

/*
 * this routine converts facet "oldfacet" to one of technology "newtech".
 * Returns the address of the new facet
 */
NODEPROTO *us_convertfacet(NODEPROTO *oldfacet, TECHNOLOGY *newtech)
{
	NODEPROTO *newfacet, *np;
	NODEPROTO *(*localconversion)(NODEPROTO*, TECHNOLOGY*);
	REGISTER TECHNOLOGY *oldtech;
	REGISTER ARCINST *ai;
	REGISTER LIBRARY *lib;
	VARIABLE *var;

	/* cannot convert text-only views */
	if ((oldfacet->cellview->viewstate&TEXTVIEW) != 0)
	{
		ttyputerr("Cannot convert textual views: only layout and schematic");
		return(0);
	}

	/* separate code if converting to the schematic technology */
	if (newtech == sch_tech)
	{
		/*
		 * Look to see if the technology has its own routine.
		 * Note that it may call some of the functions in this file!
		 */
		var = getval((INTBIG)el_curtech, VTECHNOLOGY, VADDRESS, "TECH_schematic_conversion");
		if (var != NOVARIABLE)
		{
			localconversion = (NODEPROTO *(*)(NODEPROTO*, TECHNOLOGY*))var->addr;
			newfacet = (*(localconversion))(oldfacet, newtech);
			if (newfacet == NONODEPROTO) return(NONODEPROTO);
		} else    /* Convert to schematic here */
		{
			/* create facet in new technology */
			newfacet = us_tran_linkage(oldfacet->cell->cellname, el_schematicview, oldfacet);
			if (newfacet == NONODEPROTO) return(NONODEPROTO);

			/* create the parts in this facet */
			us_tran_logmakenodes(oldfacet, newfacet, newtech);
			us_tran_logmakearcs(oldfacet, newfacet);

			/* now make adjustments for manhattan-ness */
			for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
				for(np = lib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
					np->temp1 = 0;
			us_tran_logadjustnodes(newfacet);

			/* set "FIXANG" if reasonable */
			for(ai = newfacet->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
			{
				if (ai->end[0].xpos == ai->end[1].xpos &&
					ai->end[0].ypos == ai->end[1].ypos) continue;
				if ((figureangle(ai->end[0].xpos, ai->end[0].ypos, ai->end[1].xpos,
					ai->end[1].ypos)%450) == 0) ai->userbits |= FIXANG;
			}
		}

		/* after adjusting contents, must re-solve to get proper facet size */
		(*el_curconstraint->solve)(newfacet);
	} else
	{
		/* do general conversion between technologies */
		oldtech = whattech(oldfacet);
		newfacet = us_tran_makelayoutfacets(oldfacet, oldfacet->cell->cellname,
			oldtech, newtech, el_layoutview);
	}
	return(newfacet);
}

/*
 * routine to create a new facet called "newfacetname" that is to be the
 * equivalent to an old facet in "facet".  The view type of the new facet is
 * in "newfacetview" and the view type of the old facet is in "facetview"
 */
NODEPROTO *us_tran_linkage(char *newfacetname, VIEW *newfacetview, NODEPROTO *facet)
{
	NODEPROTO *newfacet;
	REGISTER char *facetname;

	/* create the new facet */
	if (newfacetview->sviewname[0] == 0) facetname = newfacetname; else
	{
		(void)initinfstr();
		(void)addstringtoinfstr(newfacetname);
		(void)addtoinfstr('{');
		(void)addstringtoinfstr(newfacetview->sviewname);
		(void)addtoinfstr('}');
		facetname = returninfstr();
	}
	newfacet = newnodeproto(facetname, facet->cell->lib);
	if (newfacet == NONODEPROTO)
		ttyputmsg("Could not create facet: %s", facetname); else
			ttyputmsg("Creating new facet: %s", facetname);
	return(newfacet);
}

/********************** CODE FOR CONVERSION TO SCHEMATIC **********************/

void us_tran_logmakenodes(NODEPROTO *facet, NODEPROTO *newfacet, TECHNOLOGY *newtech)
{
	NODEINST *ni, *schemni;
	NODEPROTO *onp;
	PORTEXPINST *pexp;
	REGISTER PORTPROTO *pp, *pp2;
	REGISTER PORTARCINST *pi;
	INTSML type, rotate, trans;
	INTBIG len, wid, vartype;
	char ttype[30];

	/*
	 * for each node, create a new node in the newfacet, of the correct
	 * logical type.  Also, store a pointer to the new node in the old
	 * node's temp1.  This is used in the arc translation part of the
	 * program to find the new ends of each arc.
	 */
	for(ni = facet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		type = us_tranismos(ni);
		switch (type)
		{
			case TRAN_PIN:
				/* compute new x, y coordinates */
				schemni = us_tran_logmakenode(sch_wirepinprim, ni, 0, 0, newfacet, newtech);
				break;
			case TRAN_FACET:
				for(onp = ni->proto->cell->firstincell; onp != NONODEPROTO; onp = onp->nextincell)
					if (onp->cellview == el_schematicview) break;
				if (onp == NONODEPROTO)
				{
					onp = us_convertfacet(ni->proto, newtech);
					if (onp == NONODEPROTO) break;
				}
				schemni = us_tran_logmakenode(onp, ni, ni->transpose, ni->rotation,
					newfacet, newtech);
				break;
			case NPUNKNOWN:	/* could not match it */
				 schemni = NONODEINST;
				 break;
			default:		/* always a transistor */
				rotate = ni->rotation;
				trans = ni->transpose;
				for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
					if (pi->proto == ni->proto->firstportproto) break;
				if (pi != NOPORTARCINST)
				{
					if (trans == 0) rotate += 900; else rotate += 2700;
					trans = 1 - trans;
				}
				rotate += 2700;
				while (rotate >= 3600) rotate -= 3600;
				schemni = us_tran_logmakenode(sch_transistorprim, ni, trans, rotate,
					newfacet, newtech);

				/* start with the name of the transistor */
				(void)initinfstr();
				switch (type)
				{
					case NPTRAPMOS:
						(void)addstringtoinfstr("pmos");    break;
					case NPTRANMOS:
						(void)addstringtoinfstr("nmos");    break;
					case NPTRADMOS:
						(void)addstringtoinfstr("dmos");    break;
					case NPTRAPNP:
						(void)addstringtoinfstr("pnp");     break;
					case NPTRANPN:
						(void)addstringtoinfstr("npn");     break;
					case NPTRANJFET:
						(void)addstringtoinfstr("njfet");   break;
					case NPTRAPJFET:
						(void)addstringtoinfstr("pjfet");   break;
					case NPTRADMES:
						(void)addstringtoinfstr("dmes");    break;
					case NPTRAEMES:
						(void)addstringtoinfstr("emes");    break;
					case NPTRANE2L:
						(void)addstringtoinfstr("e2l");     break;
					default:
						 break;
				}

				/* add in the size */
				transistorsize(ni, &len, &wid);
				if (len >= 0 && wid >= 0)
				{
					len /= ni->proto->tech->deflambda;
					wid /= ni->proto->tech->deflambda;
					if (type == NPTRAPMOS || type == NPTRANMOS || type == NPTRADMOS)
						(void)sprintf(ttype, "%ld/%ld", wid, len); else
							(void)sprintf(ttype, "%ld", len*wid);
					(void)addstringtoinfstr(ttype);
				}

				/* decide whether or not to display the size information */
				vartype = VSTRING;
				if (ni->highx - ni->lowx != ni->proto->highx - ni->proto->lowx ||
					ni->highy - ni->lowy != ni->proto->highy - ni->proto->lowy)
						vartype |= VDISPLAY;

				/* store this information on the transistor */
				(void)setvalkey((INTBIG)schemni, VNODEINST, sch_transistortypekey,
					(INTBIG)returninfstr(), vartype);
		}

		/* store the new node in the old node */
		ni->temp1 = (INTBIG)schemni;

		/* reexport ports */
		if (schemni != NONODEINST)
		{
			for(pexp = ni->firstportexpinst; pexp != NOPORTEXPINST; pexp = pexp->nextportexpinst)
			{
				pp = us_tranconvpp(ni, pexp->proto);
				if (pp == NOPORTPROTO) continue;
				pp2 = newportproto(newfacet, schemni, pp, pexp->exportproto->protoname);
				if (pp2 == NOPORTPROTO) return;
				pp2->userbits = (pp2->userbits & ~STATEBITS) |
					(pexp->exportproto->userbits & STATEBITS);
				pp2->textdescript = pexp->exportproto->textdescript;
				if (copyvars((INTBIG)pexp->exportproto, VPORTPROTO, (INTBIG)pp2, VPORTPROTO) != 0)
					return;
			}
		}
	}
}

NODEINST *us_tran_logmakenode(NODEPROTO *prim, NODEINST *orig, INTSML trn, INTSML rot,
	NODEPROTO *newfacet, TECHNOLOGY *newtech)
{
	REGISTER INTBIG cx, cy, scaleu, scaled;
	INTBIG sx, sy;
	REGISTER NODEINST *newni;
	REGISTER TECHNOLOGY *oldtech;

	scaleu = newtech->deflambda;
	if (orig->proto->primindex == 0) oldtech = whattech(orig->proto); else
		oldtech = orig->proto->tech;
	scaled = oldtech->deflambda;
	cx = muldiv((orig->lowx+orig->highx)/2, scaleu, scaled) - (prim->highx-prim->lowx)/2;
	cy = muldiv((orig->lowy+orig->highy)/2, scaleu, scaled) - (prim->highy-prim->lowy)/2;
	defaultnodesize(prim, &sx, &sy);
	newni = newnodeinst(prim, cx, cx+sx, cy, cy+sy, trn, rot, newfacet);
	return(newni);
}

void us_tran_logmakearcs(NODEPROTO *facet, NODEPROTO *newfacet)
{
	PORTPROTO *oldpp1, *oldpp2, *newpp1, *newpp2;
	ARCINST *oldai, *newai;
	NODEINST *newni1, *newni2;
	INTBIG x1, x2, y1, y2, bits;

	/*
	 * for each arc in facet, find the ends in the new technology, and
	 * make a new arc to connect them in the new facet.
	 */
	for(oldai = facet->firstarcinst; oldai != NOARCINST; oldai = oldai->nextarcinst)
	{
		newni1 = (NODEINST *)oldai->end[0].nodeinst->temp1;
		newni2 = (NODEINST *)oldai->end[1].nodeinst->temp1;
		if (newni1 == NONODEINST || newni2 == NONODEINST) continue;
		oldpp1 = oldai->end[0].portarcinst->proto;
		oldpp2 = oldai->end[1].portarcinst->proto;

		/* find the logical portproto for the first end node */
		newpp1 = us_tranconvpp(oldai->end[0].nodeinst, oldpp1);
		if (newpp1 == NOPORTPROTO) continue;

		/* find the logical portproto for the second end node */
		newpp2 = us_tranconvpp(oldai->end[1].nodeinst, oldpp2);
		if (newpp2 == NOPORTPROTO) continue;

		/* find the endpoints of the arc */
		portposition(newni1, newpp1, &x1, &y1);
		portposition(newni2, newpp2, &x2, &y2);

		/* create the new arc */
		bits = us_makearcuserbits(sch_wirearc) & ~(FIXANG|FIXED);
		newai = newarcinst(sch_wirearc, defaultarcwidth(sch_wirearc), bits,
			newni1, newpp1, x1, y1, newni2, newpp2, x2, y2, newfacet);
		if (newai == NOARCINST) break;
		endobjectchange((INTBIG)newai, VARCINST);
	}
}

/*
 * routine to adjust the pin nodes in facet "newfacet" so that there is maximum
 * "manhattan"ness
 */
void us_tran_logadjustnodes(NODEPROTO *newfacet)
{
	REGISTER NODEINST *ni;

	/* handle recursion */
	if (newfacet->temp1 != 0) return;
	newfacet->temp1++;
	for(ni = newfacet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		if (ni->proto->primindex == 0) us_tran_logadjustnodes(ni->proto);

	us_tran_makemanhattan(newfacet);
}

#define	MAXADJUST        5

void us_tran_makemanhattan(NODEPROTO *newfacet)
{
	REGISTER NODEINST *ni;
	REGISTER PORTARCINST *pi;
	REGISTER ARCINST *ai;
	REGISTER INTSML e, count, i, j;
	REGISTER INTBIG dist, bestdist, bestx, besty, xp, yp;
	INTBIG x[MAXADJUST], y[MAXADJUST];

	/* adjust this facet */
	for(ni = newfacet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto->primindex == 0) continue;
		if (((ni->proto->userbits&NFUNCTION) >> NFUNCTIONSH) != NPPIN) continue;

		/* see if this pin can be adjusted so that all wires are manhattan */
		count = 0;
		for(pi=ni->firstportarcinst; pi!=NOPORTARCINST; pi=pi->nextportarcinst)
		{
			ai = pi->conarcinst;
			if (ai->end[0].nodeinst == ni)
			{
				if (ai->end[1].nodeinst == ni) continue;
				e = 1;
			} else e = 0;
			x[count] = ai->end[e].xpos;   y[count] = ai->end[e].ypos;
			count++;
			if (count >= MAXADJUST) break;
		}
		if (count == 0) continue;

		/* now adjust for all these points */
		xp = (ni->lowx + ni->highx) / 2;   yp = (ni->lowy + ni->highy) / 2;
		bestdist = HUGEINT;
		for(i=0; i<count; i++) for(j=0; j<count; j++)
		{
			dist = abs(xp - x[i]) + abs(yp - y[j]);
			if (dist > bestdist) continue;
			bestdist = dist;
			bestx = x[i];   besty = y[j];
		}

		/* if there was a better place, move the node */
		if (bestdist != HUGEINT)
			modifynodeinst(ni, bestx-xp, besty-yp, bestx-xp, besty-yp, 0, 0);
	}
}

/* find the logical portproto corresponding to the mos portproto of ni */
PORTPROTO *us_tranconvpp(NODEINST *ni, PORTPROTO *mospp)
{
	PORTPROTO *schempp, *pp;
	NODEINST *schemni;
	INTSML port;

	schemni = (NODEINST *)ni->temp1;

	switch (us_tranismos(schemni))
	{
		case TRAN_PIN:
			schempp = schemni->proto->firstportproto;
			break;
		case TRAN_FACET:
			for(schempp = schemni->proto->firstportproto; schempp != NOPORTPROTO;
				schempp = schempp->nextportproto)
					if (namesame(schempp->protoname, mospp->protoname) == 0) break;
			break;
		default: /* transistor */
			for(port = 1, pp = ni->proto->firstportproto; pp != NOPORTPROTO;
				pp = pp->nextportproto, port++)
					if (pp == mospp) break;	 /* partic. port in MOS */
			schempp = us_trangetproto(schemni, port);
			break;
	}
	return(schempp);
}

/*
 * this routine figures out if the current nodeinst is a MOS component
 * (a wire or transistor).  If it's a transistor, return corresponding
 * define from efunction.h; if it's a passive connector, return TRAN_PIN;
 * if it's a facet, return TRAN_FACET; else return NPUNKNOWN.
 */
INTSML us_tranismos(NODEINST *ni)
{
	if (ni->proto->primindex == 0) return(TRAN_FACET);
	switch((ni->proto->userbits & NFUNCTION) >> NFUNCTIONSH)
	{
		case NPTRANMOS:   return(NPTRANMOS);
		case NPTRADMOS:   return(NPTRADMOS);
		case NPTRAPMOS:   return(NPTRAPMOS);
		case NPTRANPN:    return(NPTRANPN);
		case NPTRAPNP:    return(NPTRAPNP);
		case NPTRANJFET:  return(NPTRANJFET);
		case NPTRAPJFET:  return(NPTRAPJFET);
		case NPTRADMES:   return(NPTRADMES);
		case NPTRAEMES:   return(NPTRAEMES);
		case NPTRANE2L:   return(NPTRANE2L);
		case NPTRANSREF:  return(NPTRANSREF);
		case NPPIN:       return(TRAN_PIN);
		case NPCONTACT:   return(TRAN_PIN);
		case NPNODE:      return(TRAN_PIN);
		case NPCONNECT:   return(TRAN_PIN);
		case NPSUBSTRATE: return(TRAN_PIN);
		case NPWELL:      return(TRAN_PIN);
	}
	return(NPUNKNOWN);
}

PORTPROTO *us_trangetproto(NODEINST *ni, INTSML port)
{
	PORTPROTO *pp;
	INTSML count = port, i;

	if (count == 4) count = 3; else
		if (count == 3) count = 1;

	for(i = 1, pp = ni->proto->firstportproto; pp != NOPORTPROTO && i < count;
		pp = pp->nextportproto, i++) /* get portproto for schematic */
				;
	return(pp);
}

/************************* CODE FOR CONVERSION TO LAYOUT *************************/

/*
 * routine to recursively descend from facet "oldfacet" and find subfacets that
 * have to be converted.  When all subfacets have been converted, convert this
 * one into a new one called "newfacetname".  The technology for the old facet
 * is "oldtech" and the technology to use for the new facet is "newtech".  The
 * old view type is "oldview" and the new view type is "nview".
 */
NODEPROTO *us_tran_makelayoutfacets(NODEPROTO *oldfacet, char *newfacetname,
	TECHNOLOGY *oldtech, TECHNOLOGY *newtech, VIEW *nview)
{
	REGISTER NODEPROTO *newfacet, *rnp;
	REGISTER INTBIG bits;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	char *str;

	/* first convert the sub-facets */
	for(ni = oldfacet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		/* ignore primitives */
		if (ni->proto->primindex != 0) continue;

		/* ignore facets with associations */
		for(rnp = ni->proto->cell->firstincell; rnp != NONODEPROTO; rnp = rnp->nextincell)
			if (rnp->cellview == nview) break;
		if (rnp != NONODEPROTO) continue;

		/* make up a name for this facet */
		(void)allocstring(&str, ni->proto->cell->cellname, el_tempcluster);

		(void)us_tran_makelayoutfacets(ni->proto, str, oldtech, newtech, nview);
		efree(str);
	}

	/* create the facet and fill it with parts */
	newfacet = us_tran_linkage(newfacetname, nview, oldfacet);
	if (newfacet == NONODEPROTO) return(NONODEPROTO);
	if (us_tran_makelayoutparts(oldfacet, newfacet, oldtech, newtech, nview) != 0)
	{
		/* adjust for maximum Manhattan-ness */
		us_tran_makemanhattan(newfacet);

		/* reset shrinkage values and constraints to defaults (is this needed? !!!) */
		for(ai = newfacet->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
		{
			bits = us_makearcuserbits(ai->proto);
			if ((bits&FIXED) != 0) ai->userbits |= FIXED;
			if ((bits&FIXANG) != 0) ai->userbits |= FIXANG;
			(void)setshrinkvalue(ai, 0);
		}
	}

	return(newfacet);
}

/*
 * routine to create a new facet in "newfacet" from the contents of an old facet
 * in "oldfacet".  The technology for the old facet is "oldtech" and the
 * technology to use for the new facet is "newtech".
 */
INTSML us_tran_makelayoutparts(NODEPROTO *oldfacet, NODEPROTO *newfacet,
	TECHNOLOGY *oldtech, TECHNOLOGY *newtech, VIEW *nview)
{
	REGISTER NODEPROTO *newnp;
	REGISTER NODEINST *ni, *end1, *end2;
	ARCPROTO *ap, *newap;
	ARCINST *ai;
	REGISTER PORTPROTO *mospp1, *mospp2, *schempp1, *schempp2;
	INTBIG x1, y1, x2, y2, lx1, hx1, ly1, hy1, lx2, hx2, ly2, hy2, tx1, ty1, tx2, ty2;
	REGISTER INTBIG newwid, newbits, oldlambda, newlambda;
	REGISTER INTSML badarcs, i, j, univarcs;
	static POLYGON *poly1 = NOPOLYGON, *poly2 = NOPOLYGON;

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

	/* get lambda values */
	oldlambda = oldtech->deflambda;
	newlambda = newtech->deflambda;

	/* first convert the nodes */
	for(ni = oldfacet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		/* handle sub-facets */
		if (ni->proto->primindex == 0)
		{
			for(newnp = ni->proto->cell->firstincell; newnp != NONODEPROTO; newnp = newnp->nextincell)
				if (newnp->cellview == nview) break;
			if (newnp != NONODEPROTO)
			{
				ttyputerr("No equivalent facet for %s", describenodeproto(ni->proto));
				continue;
			}
			us_tranplacenode(ni, newnp, newfacet, oldtech, newtech);
			continue;
		}

		/* handle primitives */
		newnp = us_figurenewnproto(ni, newtech);
		us_tranplacenode(ni, newnp, newfacet, oldtech, newtech);
	}

	/*
	 * for each arc in facet, find the ends in the new technology, and
	 * make a new arc to connect them in the new facet
	 */
	badarcs = univarcs = 0;
	for(ai = oldfacet->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		/* get the nodes and ports on the two ends of the arc */
		end1 = (NODEINST *)ai->end[0].nodeinst->temp1;
		end2 = (NODEINST *)ai->end[1].nodeinst->temp1;
		if (end1 == 0 || end2 == 0) continue;
		mospp1 = ai->end[0].portarcinst->proto;
		mospp2 = ai->end[1].portarcinst->proto;
		schempp1 = us_convport(ai->end[0].nodeinst, end1, mospp1);
		schempp2 = us_convport(ai->end[1].nodeinst, end2, mospp2);

		/* set bits in arc prototypes that can make the connection */
		for(ap = newtech->firstarcproto; ap != NOARCPROTO; ap = ap->nextarcproto)
			ap->userbits &= ~CANCONNECT;
		for(i=0; schempp1->connects[i] != NOARCPROTO; i++)
		{
			for(j=0; schempp2->connects[j] != NOARCPROTO; j++)
			{
				if (schempp1->connects[i] != schempp2->connects[j]) continue;
				schempp1->connects[i]->userbits |= CANCONNECT;
				break;
			}
		}

		/* compute arc type and see if it is acceptable */
		newap = us_figurenewaproto(ai->proto, newtech);
		if (newap->tech == newtech && (newap->userbits&CANCONNECT) == 0)
		{
			/* not acceptable: see if there are any valid ones */
			for(newap = newtech->firstarcproto; newap != NOARCPROTO; newap = newap->nextarcproto)
				if ((newap->userbits&CANCONNECT) != 0) break;

			/* none are valid: use universal */
			if (newap == NOARCPROTO) newap = gen_universalarc;
		}

		/* determine new arc width */
		newbits = ai->userbits;
		if (newap == gen_universalarc)
		{
			newwid = 0;
			univarcs++;
			newbits &= ~(FIXED | FIXANG);
		} else
		{
			newwid = ai->width - arcwidthoffset(ai);
			newwid = muldiv(newwid, newlambda, oldlambda) + arcprotowidthoffset(newap);
			if (newwid <= 0) newwid = defaultarcwidth(newap);
		}

		/* find the endpoints of the arc */
		x1 = muldiv(ai->end[0].xpos, newlambda, oldlambda);
		y1 = muldiv(ai->end[0].ypos, newlambda, oldlambda);
		shapeportpoly(end1, schempp1, poly1, 0);
		x2 = muldiv(ai->end[1].xpos, newlambda, oldlambda);
		y2 = muldiv(ai->end[1].ypos, newlambda, oldlambda);
		shapeportpoly(end2, schempp2, poly2, 0);

		/* see if the new arc can connect without end adjustment */
		if (isinside(x1, y1, poly1) == 0 || isinside(x2, y2, poly2) == 0)
		{
			/* arc cannot be run exactly ... presume port centers */
			portposition(end1, schempp1, &x1, &y1);
			portposition(end2, schempp2, &x2, &y2);
			if ((newbits & FIXANG) != 0)
			{
				/* old arc was fixed-angle so look for a similar-angle path */
				reduceportpoly(poly1, end1, schempp1, newwid-arcprotowidthoffset(newap));
				getbbox(poly1, &lx1, &hx1, &ly1, &hy1);
				reduceportpoly(poly2, end2, schempp2, newwid-arcprotowidthoffset(newap));
				getbbox(poly2, &lx2, &hx2, &ly2, &hy2);
				if (arcconnects((INTSML)(((ai->userbits&AANGLE) >> AANGLESH) * 10), lx1, hx1, ly1, hy1,
					lx2, hx2, ly2, hy2, &tx1, &ty1, &tx2, &ty2) == 0) badarcs++; else
				{
					x1 = tx1;   y1 = ty1;
					x2 = tx2;   y2 = ty2;
				}
			}
		}
		/* create the new arc */
		if (newarcinst(newap, newwid, newbits, end1, schempp1, x1, y1,
			end2, schempp2, x2, y2, newfacet) == NOARCINST)
		{
			ttyputmsg("Facet %s: can't run arc from node %s port %s at (%s,%s)",
				describenodeproto(newfacet), describenodeinst(end1),
					schempp1->protoname, latoa(x1), latoa(y1));
			ttyputmsg("   to node %s port %s at (%s,%s)", describenodeinst(end2),
				schempp2->protoname, latoa(x2), latoa(y2));
		}
	}

	/* print warning if arcs were made nonmanhattan */
	if (badarcs != 0)
		ttyputmsg("WARNING: %d %s made not-fixed-angle in facet %s", badarcs,
			makeplural("arc", badarcs), describenodeproto(newfacet));
	return(univarcs);
}

void us_tranplacenode(NODEINST *ni, NODEPROTO *newnp, NODEPROTO *newfacet,
	TECHNOLOGY *oldtech, TECHNOLOGY *newtech)
{
	INTBIG lx, ly, hx, hy, nlx, nly, nhx, nhy, bx, by;
	REGISTER INTBIG newsx, newsy, x1, y1, newlx, newhx, newly, newhy, *newtrace,
		oldlambda, newlambda;
	XARRAY trans;
	REGISTER INTSML i, len, trn;
	REGISTER PORTEXPINST *pexp;
	REGISTER NODEINST *newni;
	REGISTER PORTPROTO *pp, *pp2;
	REGISTER VARIABLE *var;

	newlambda = newtech->deflambda;
	oldlambda = oldtech->deflambda;

	/* scale edge offsets if this is a primitive */
	trn = ni->transpose;
	if (ni->proto->primindex != 0)
	{
		/* special case for schematic transistors: get size from description */
		if (((ni->proto->userbits&NFUNCTION) >> NFUNCTIONSH) == NPTRANS)
		{
			transistorsize(ni, &bx, &by);
			lx = (ni->lowx + ni->highx - bx) / 2;
			hx = (ni->lowx + ni->highx + bx) / 2;
			ly = (ni->lowy + ni->highy - by) / 2;
			hy = (ni->lowy + ni->highy + by) / 2;
			trn = 1 - trn;

			/* compute scaled size for new node */
			newsx = muldiv(hx - lx, newlambda, el_curtech->deflambda);
			newsy = muldiv(hy - ly, newlambda, el_curtech->deflambda);
		} else
		{
			/* compute true bounds of old node */
			nodesizeoffset(ni, &lx, &ly, &hx, &hy);
			lx = ni->lowx + lx;   hx = ni->highx - hx;
			ly = ni->lowy + ly;   hy = ni->highy - hy;

			/* compute scaled size for new node */
			newsx = muldiv(hx - lx, newlambda, oldlambda);
			newsy = muldiv(hy - ly, newlambda, oldlambda);
		}

		/* compute center of old node */
		x1 = muldiv((hx + lx) / 2, newlambda, oldlambda);
		y1 = muldiv((hy + ly) / 2, newlambda, oldlambda);

		/* get offsets for new node type */
		nodeprotosizeoffset(newnp, &nlx, &nly, &nhx, &nhy);

		/* compute bounds of the new node */
		newlx = x1 - newsx/2 - nlx;   newhx = newlx + newsx + nlx + nhx;
		newly = y1 - newsy/2 - nly;   newhy = newly + newsy + nly + nhy;
	} else
	{
		x1 = (newnp->highx+newnp->lowx)/2 - (ni->proto->highx+ni->proto->lowx)/2;
		y1 = (newnp->highy+newnp->lowy)/2 - (ni->proto->highy+ni->proto->lowy)/2;
		makeangle(ni->rotation, ni->transpose, trans);
		xform(x1, y1, &bx, &by, trans);
		newlx = ni->lowx + bx;   newhx = ni->highx + bx;
		newly = ni->lowy + by;   newhy = ni->highy + by;
		newlx += ((newhx-newlx) - (newnp->highx-newnp->lowx)) / 2;
		newhx = newlx + newnp->highx - newnp->lowx;
		newly += ((newhy-newly) - (newnp->highy-newnp->lowy)) / 2;
		newhy = newly + newnp->highy - newnp->lowy;
	}

	/* create the node */
	newni = newnodeinst(newnp, newlx, newhx, newly, newhy, trn, ni->rotation, newfacet);
	if (newni == NONODEINST) return;
	newni->userbits |= (ni->userbits & (NEXPAND | WIPED | NSHORT));
	ni->temp1 = (INTBIG)newni;

	/* copy "trace" information if there is any */
	var = gettrace(ni);
	if (var != NOVARIABLE)
	{
		len = (INTSML)getlength(var);
		newtrace = emalloc((len * SIZEOFINTBIG), el_tempcluster);
		if (newtrace == 0) return;
		for(i=0; i<len; i++)
			newtrace[i] = muldiv(((INTBIG *)var->addr)[i], newlambda, oldlambda);
		(void)setvalkey((INTBIG)newni, VNODEINST, el_trace, (INTBIG)newtrace,
			VINTEGER|VISARRAY|(len<<VLENGTHSH));
		efree((char *)newtrace);
	}

	/* re-export any ports on the node */
	for(pexp = ni->firstportexpinst; pexp != NOPORTEXPINST; pexp = pexp->nextportexpinst)
	{
		pp = us_convport(ni, newni, pexp->proto);
		pp2 = newportproto(newfacet, newni, pp, pexp->exportproto->protoname);
		if (pp2 == NOPORTPROTO) return;
		pp2->userbits = (pp2->userbits & ~STATEBITS) | (pexp->exportproto->userbits & STATEBITS);
		pp2->textdescript = pexp->exportproto->textdescript;
		if (copyvars((INTBIG)pexp->exportproto, VPORTPROTO, (INTBIG)pp2, VPORTPROTO) != 0)
			return;
	}
}

/*
 * routine to determine the port to use on node "newni" assuming that it should
 * be the same as port "oldpp" on equivalent node "ni"
 */
PORTPROTO *us_convport(NODEINST *ni, NODEINST *newni, PORTPROTO *oldpp)
{
	REGISTER PORTPROTO *pp, *npp;
	REGISTER INTSML oldfun, newfun;

	if (newni->proto->primindex == 0)
	{
		/* facets can associate by comparing names */
		for(pp = newni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
			if (strcmp(pp->protoname, oldpp->protoname) == 0) return(pp);
	}

	/* if functions are different, handle some special cases */
	oldfun = (INTSML)((ni->proto->userbits&NFUNCTION) >> NFUNCTIONSH);
	newfun = (INTSML)((newni->proto->userbits&NFUNCTION) >> NFUNCTIONSH);
	if (oldfun != newfun)
	{
		if (oldfun == NPTRANS && isfet(newni->geom))
		{
			/* converting from stick-figure to layout */
			pp = ni->proto->firstportproto;   npp = newni->proto->firstportproto;
			if (pp == oldpp) return(npp);
			pp = pp->nextportproto;           npp = npp->nextportproto;
			if (pp == oldpp) return(npp);
			pp = pp->nextportproto;           npp = npp->nextportproto->nextportproto;
			if (pp == oldpp) return(npp);
		}
	}

	/* associate by position in port list */
	for(pp=ni->proto->firstportproto, npp=newni->proto->firstportproto;
		pp != NOPORTPROTO && npp != NOPORTPROTO;
			pp = pp->nextportproto, npp = npp->nextportproto)
				if (pp == oldpp) return(npp);

	/* special case again: one-port capacitors are OK */
	if (oldfun == NPCAPAC && newfun == NPCAPAC) return(newni->proto->firstportproto);

	/* association has failed: assume the first port */
	ttyputmsg("No port association between %s, port %s and %s",
		describenodeproto(ni->proto), oldpp->protoname,
			describenodeproto(newni->proto));
	return(newni->proto->firstportproto);
}

/*
 * routine to determine the equivalent prototype in technology "newtech" for
 * node prototype "oldnp".
 */
ARCPROTO *us_figurenewaproto(ARCPROTO *oldap, TECHNOLOGY *newtech)
{
	REGISTER INTBIG type;
	REGISTER ARCPROTO *ap;

	/* schematic wires become universal arcs */
	if (oldap == sch_wirearc) return(gen_universalarc);

	/* determine the proper association of this node */
	type = (oldap->userbits & AFUNCTION) >> AFUNCTIONSH;
	for(ap = newtech->firstarcproto; ap != NOARCPROTO; ap = ap->nextarcproto)
		if ((INTBIG)((ap->userbits&AFUNCTION) >> AFUNCTIONSH) == type) break;
	if (ap == NOARCPROTO)
	{
		ttyputmsg("No equivalent arc for %s", describearcproto(oldap));
		return(oldap);
	}
	return(ap);
}

/*
 * routine to determine the equivalent prototype in technology "newtech" for
 * node prototype "oldnp".
 */
NODEPROTO *us_figurenewnproto(NODEINST *oldni, TECHNOLOGY *newtech)
{
	REGISTER INTSML type, i, j, k;
	REGISTER ARCPROTO *ap, *oap;
	REGISTER INTBIG important, funct;
	REGISTER NODEPROTO *np, *rnp, *oldnp;
	REGISTER NODEINST *ni;
	char *dummy;
	static POLYGON *poly = NOPOLYGON;

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

	/* easy translation if complex or already in the proper technology */
	oldnp = oldni->proto;
	if (oldnp->primindex == 0 || oldnp->tech == newtech) return(oldnp);

	/* if this is a layer node, check the layer functions */
	type = nodefunction(oldni, &dummy);
	if (type == NPNODE)
	{
		/* get the polygon describing the first box of the old node */
		(void)nodepolys(oldni);
		shapenodepoly(oldni, 0, poly);
		important = LFTYPE | LFPSEUDO | LFNONELEC;
		funct = layerfunction(oldnp->tech, poly->layer) & important;

		/* now search for that function in the other technology */
		for(np = newtech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
		{
			if (((np->userbits&NFUNCTION) >> NFUNCTIONSH) != NPNODE) continue;
			ni = dummynode();
			ni->proto = np;
			(void)nodepolys(ni);
			shapenodepoly(ni, 0, poly);
			if ((layerfunction(newtech, poly->layer)&important) == funct)
				return(np);
		}
	}

	/* see if one node in the new technology has the same function */
	for(i = 0, np = newtech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
		if ((INTSML)((np->userbits&NFUNCTION) >> NFUNCTIONSH) == type)
	{
		rnp = np;   i++;
	}
	if (i == 1) return(rnp);

	/* if there are too many matches, determine which is proper from arcs */
	if (i > 1)
	{
		for(np = newtech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
		{
			if ((INTSML)((np->userbits&NFUNCTION) >> NFUNCTIONSH) != type) continue;

			/* see if this node has equivalent arcs */
			for(j=0; oldnp->firstportproto->connects[j] != NOARCPROTO; j++)
			{
				oap = oldnp->firstportproto->connects[j];
				if (oap->tech == gen_tech) continue;

				for(k=0; np->firstportproto->connects[k] != NOARCPROTO; k++)
				{
					ap = np->firstportproto->connects[k];
					if (ap->tech == gen_tech) continue;
					if ((ap->userbits&AFUNCTION) == (oap->userbits&AFUNCTION)) break;
				}
				if (np->firstportproto->connects[k] == NOARCPROTO) break;
			}
			if (oldnp->firstportproto->connects[j] == NOARCPROTO) break;
		}
		if (np != NONODEPROTO)
		{
			rnp = np;
			i = 1;
		}
	}

	/* give up if it still cannot be determined */
	if (i != 1)
	{
		ttyputmsg("No equivalent node for %s", describenodeproto(oldnp));
		return(oldnp);
	}
	return(rnp);
}

/************************* CODE FOR PRINTING TECHNOLOGIES *************************/

extern LIST us_teclayer_functions[];
extern LIST us_tecarc_functions[];
extern LIST us_tecnode_functions[];

#define	MAXCOLS 10

void us_printtechnology(TECHNOLOGY *tech)
{
	FILE *f;
	char *name, *fieldname, *colorsymbol, thefield[50], *truename, **fields[MAXCOLS];
	REGISTER char **names, **colors, **styles, **cifs, **gdss,
		**funcs, **layers, **layersizes, **extensions, **angles, **wipes, **ports,
		**portsizes, **portangles, **connections;
	REGISTER INTSML i, j, k, l, m, tot, base, saveunit;
	REGISTER INTBIG func, area, bits;
	INTBIG lx, hx, ly, hy;
	REGISTER ARCINST *ai;
	REGISTER ARCPROTO *ap;
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *np;
	REGISTER PORTPROTO *pp;
	REGISTER VARIABLE *cifvar, *gdsvar, *funcvar, *var;
	GRAPHICS *gra;
	REGISTER TECH_ARCLAY *arclay;
	REGISTER TECH_NODES *nodestr;
	TECH_POLYGON *lay;
	static POLYGON *poly = NOPOLYGON;
	extern INTSML tech_realpolys;

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

	(void)initinfstr();
	(void)addstringtoinfstr(tech->techname);
	(void)addstringtoinfstr(".doc");
	name = returninfstr();
	f = xcreate(name, FILETYPETEXT, "Technology Documentation File", &truename);
	if (f == NULL)
	{
		if (truename != 0) us_abortcommand("Cannot write %s", truename);
		return;
	}
	ttyputverbose("Writing: %s", name);

	/****************************** dump layers ******************************/

	/* get layer variables */
	cifvar = getval((INTBIG)tech, VTECHNOLOGY, VSTRING|VISARRAY, "IO_cif_layer_names");
	gdsvar = getval((INTBIG)tech, VTECHNOLOGY, VINTEGER|VISARRAY, "IO_gds_layer_numbers");
	funcvar = getval((INTBIG)tech, VTECHNOLOGY, VINTEGER|VISARRAY, "TECH_layer_function");

	/* allocate space for all layer fields */
	names = (char **)emalloc((tech->layercount+1) * (sizeof (char *)), el_tempcluster);
	colors = (char **)emalloc((tech->layercount+1) * (sizeof (char *)), el_tempcluster);
	styles = (char **)emalloc((tech->layercount+1) * (sizeof (char *)), el_tempcluster);
	cifs = (char **)emalloc((tech->layercount+1) * (sizeof (char *)), el_tempcluster);
	gdss = (char **)emalloc((tech->layercount+1) * (sizeof (char *)), el_tempcluster);
	funcs = (char **)emalloc((tech->layercount+1) * (sizeof (char *)), el_tempcluster);
	if (names == 0 || colors == 0 || styles == 0 || cifs == 0 || gdss == 0 || funcs == 0)
		return;

	/* load the header */
	(void)allocstring(&names[0], "Layer", el_tempcluster);
	(void)allocstring(&colors[0], "Color", el_tempcluster);
	(void)allocstring(&styles[0], "Style", el_tempcluster);
	(void)allocstring(&cifs[0], "CIF", el_tempcluster);
	(void)allocstring(&gdss[0], "GDS", el_tempcluster);
	(void)allocstring(&funcs[0], "Function", el_tempcluster);

	/* compute each layer */
	for(i=0; i<tech->layercount; i++)
	{
		(void)allocstring(&names[i+1], layername(tech, i), el_tempcluster);

		gra = tech->layers[i];
		if (ecolorname(gra->col, &fieldname, &colorsymbol) != 0) fieldname = "?";
		(void)allocstring(&colors[i+1], fieldname, el_tempcluster);

		if (el_curwindowpart == NOWINDOWPART) fieldname = "unkwn."; else
		{
			switch (gra->colstyle&(NATURE|INVISIBLE))
			{
				case SOLIDC:              fieldname = "solid";    break;
				case PATTERNED:           fieldname = "pat.";     break;
				case INVISIBLE|SOLIDC:    fieldname = "INVsol";   break;
				case INVISIBLE|PATTERNED: fieldname = "INVpat";   break;
			}
		}
		(void)allocstring(&styles[i+1], fieldname, el_tempcluster);

		if (cifvar == NOVARIABLE) fieldname = "---"; else
			fieldname = ((char **)cifvar->addr)[i];
		(void)allocstring(&cifs[i+1], fieldname, el_tempcluster);

		if (gdsvar == NOVARIABLE) fieldname = "---"; else
		{
			(void)sprintf(thefield, "%ld", ((INTBIG *)gdsvar->addr)[i]);
			fieldname = thefield;
		}
		(void)allocstring(&gdss[i+1], fieldname, el_tempcluster);

		if (funcvar == NOVARIABLE) fieldname = "---"; else
		{
			func = ((INTBIG *)funcvar->addr)[i];
			(void)initinfstr();
			us_tecedaddfunstring(func);
			fieldname = returninfstr();
		}
		(void)allocstring(&funcs[i+1], fieldname, el_tempcluster);
	}

	/* write the layer information */
	fields[0] = names;   fields[1] = colors;   fields[2] = styles;
	fields[3] = cifs;    fields[4] = gdss;     fields[5] = funcs;
	us_dumpfields(fields, 6, (INTSML)(tech->layercount+1), f, "LAYERS");
	for(i=0; i<=tech->layercount; i++)
	{
		efree(names[i]);
		efree(colors[i]);
		efree(styles[i]);
		efree(cifs[i]);
		efree(gdss[i]);
		efree(funcs[i]);
	}
	efree((char *)names);
	efree((char *)colors);
	efree((char *)styles);
	efree((char *)cifs);
	efree((char *)gdss);
	efree((char *)funcs);

	/****************************** dump arcs ******************************/

	/* allocate space for all arc fields */
	ai = dummyarc();
	ai->end[0].xpos = -2000;   ai->end[0].ypos = 0;
	ai->end[1].xpos = 2000;    ai->end[1].ypos = 0;
	ai->length = 4000;
	ai->userbits |= NOEXTEND;
	tot = 1;
	for(ap=tech->firstarcproto; ap != NOARCPROTO; ap = ap->nextarcproto)
	{
		ai->proto = ap;
		if (tech->arcpolys != 0) j = (*(tech->arcpolys))(ai); else
			j = tech->arcprotos[ap->arcindex]->laycount;
		tot += j;
	}
	names = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	layers = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	layersizes = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	extensions = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	angles = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	wipes = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	funcs = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	if (names == 0 || layers == 0 || layersizes == 0 || extensions == 0 || angles == 0 ||
		wipes == 0 || funcs == 0) return;

	/* load the header */
	(void)allocstring(&names[0], "Arc", el_tempcluster);
	(void)allocstring(&layers[0], "Layer", el_tempcluster);
	(void)allocstring(&layersizes[0], "Size", el_tempcluster);
	(void)allocstring(&extensions[0], "Extend", el_tempcluster);
	(void)allocstring(&angles[0], "Angle", el_tempcluster);
	(void)allocstring(&wipes[0], "Wipes", el_tempcluster);
	(void)allocstring(&funcs[0], "Function", el_tempcluster);

	tot = 1;
	for(ap=tech->firstarcproto; ap != NOARCPROTO; ap = ap->nextarcproto)
	{
		(void)allocstring(&names[tot], ap->protoname, el_tempcluster);

		var = getvalkey((INTBIG)ap, VARCPROTO, VINTEGER, us_arcstyle);
		if (var != NOVARIABLE) bits = var->addr; else
			bits = ap->userbits;
		if ((bits&WANTNOEXTEND) == 0) fieldname = "yes"; else fieldname = "no";
		(void)allocstring(&extensions[tot], fieldname, el_tempcluster);
		(void)sprintf(thefield, "%ld", (ap->userbits&AANGLEINC) >> AANGLEINCSH);
		(void)allocstring(&angles[tot], thefield, el_tempcluster);
		if ((ap->userbits&CANWIPE) == 0) fieldname = "no"; else fieldname = "yes";
		(void)allocstring(&wipes[tot], fieldname, el_tempcluster);
		func = (ap->userbits&AFUNCTION) >> AFUNCTIONSH;
		(void)allocstring(&funcs[tot], us_tecarc_functions[func].name, el_tempcluster);

		ai->proto = ap;
		if (tech->arcpolys != 0) j = (*(tech->arcpolys))(ai); else
			tech_realpolys = j = tech->arcprotos[ap->arcindex]->laycount;
		for(k=0; k<j; k++)
		{
			ai->width = defaultarcwidth(ap);
			if (tech->shapearcpoly != 0) (*(tech->shapearcpoly))(ai, k, poly); else
			{
				arclay = &tech->arcprotos[ap->arcindex]->list[k];
				makearcpoly(ai->length, defaultarcwidth(ap)-arclay->off*tech->deflambda/WHOLE,
					ai, poly, arclay->style);
				poly->layer = arclay->lay;
			}
			(void)allocstring(&layers[tot], layername(tech, poly->layer), el_tempcluster);
			area = (INTBIG)(fabs(areapoly(poly)) / 4000.0);
			saveunit = el_units & DISPLAYUNITS;
			el_units = (el_units & ~DISPLAYUNITS) | DISPUNITMIC;
			(void)allocstring(&layersizes[tot], latoa(area), el_tempcluster);
			el_units = (el_units & ~DISPLAYUNITS) | saveunit;
			if (k > 0)
			{
				(void)allocstring(&names[tot], "", el_tempcluster);
				(void)allocstring(&extensions[tot], "", el_tempcluster);
				(void)allocstring(&angles[tot], "", el_tempcluster);
				(void)allocstring(&wipes[tot], "", el_tempcluster);
				(void)allocstring(&funcs[tot], "", el_tempcluster);
			}
			tot++;
		}
	}

	/* write the arc information */
	fields[0] = names;        fields[1] = layers;   fields[2] = layersizes;
	fields[3] = extensions;   fields[4] = angles;   fields[5] = wipes;
	fields[6] = funcs;
	us_dumpfields(fields, 7, tot, f, "ARCS");
	for(i=0; i<tot; i++)
	{
		efree(names[i]);
		efree(layers[i]);
		efree(layersizes[i]);
		efree(extensions[i]);
		efree(angles[i]);
		efree(wipes[i]);
		efree(funcs[i]);
	}
	efree((char *)names);
	efree((char *)layers);
	efree((char *)layersizes);
	efree((char *)extensions);
	efree((char *)angles);
	efree((char *)wipes);
	efree((char *)funcs);

	/****************************** dump nodes ******************************/

	/* allocate space for all node fields */
	ni = dummynode();
	tot = 1;
	for(np = tech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
	{
		ni->proto = np;
		if (tech->nodepolys != 0) j = (*(tech->nodepolys))(ni); else
			j = tech->nodeprotos[np->primindex-1]->layercount;
		l = 0;
		for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			m = 0;
			for(k=0; pp->connects[k] != NOARCPROTO; k++)
				if (pp->connects[k]->tech == tech) m++;
			if (m == 0) m = 1;
			l += m;
		}
		tot += (INTSML)maxi(j, l);
	}
	names = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	funcs = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	layers = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	layersizes = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	ports = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	portsizes = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	portangles = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	connections = (char **)emalloc(tot * (sizeof (char *)), el_tempcluster);
	if (names == 0 || funcs == 0 || layers == 0 || layersizes == 0 || ports == 0 ||
		portsizes == 0 || portangles == 0 || connections == 0) return;

	/* load the header */
	(void)allocstring(&names[0], "Node", el_tempcluster);
	(void)allocstring(&funcs[0], "Function", el_tempcluster);
	(void)allocstring(&layers[0], "Layers", el_tempcluster);
	(void)allocstring(&layersizes[0], "Size", el_tempcluster);
	(void)allocstring(&ports[0], "Ports", el_tempcluster);
	(void)allocstring(&portsizes[0], "Size", el_tempcluster);
	(void)allocstring(&portangles[0], "Angle", el_tempcluster);
	(void)allocstring(&connections[0], "Connections", el_tempcluster);

	tot = 1;
	for(np = tech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
	{
		base = tot;
		(void)allocstring(&names[tot], np->primname, el_tempcluster);

		func = (np->userbits&NFUNCTION) >> NFUNCTIONSH;
		(void)allocstring(&funcs[tot], us_tecnode_functions[func].name, el_tempcluster);

		ni->proto = np;
		ni->lowx = np->lowx;   ni->highx = np->highx;
		ni->lowy = np->lowy;   ni->highy = np->highy;
		if (tech->nodepolys != 0) j = (*(tech->nodepolys))(ni); else
			tech_realpolys = j = tech->nodeprotos[np->primindex-1]->layercount;
		for(k=0; k<j; k++)
		{
			if (tech->shapenodepoly != 0) (*(tech->shapenodepoly))(ni, k, poly); else
			{
				nodestr = tech->nodeprotos[np->primindex-1];
				if (nodestr->special == SERPTRANS)
				{
					tech_filltrans(poly, &lay, nodestr->gra, ni, tech->deflambda, k,
						(TECH_PORTS *)0);
				} else
				{
					lay = &nodestr->layerlist[k];
					tech_fillpoly(poly, lay, ni, tech->deflambda, FILLED);
				}
			}
			(void)allocstring(&layers[tot], layername(tech, poly->layer), el_tempcluster);
			getbbox(poly, &lx, &hx, &ly, &hy);
			saveunit = el_units & DISPLAYUNITS;
			el_units = (el_units & ~DISPLAYUNITS) | DISPUNITMIC;
			(void)sprintf(thefield, "%s x %s", latoa(hx-lx), latoa(hy-ly));
			(void)allocstring(&layersizes[tot], thefield, el_tempcluster);
			el_units = (el_units & ~DISPLAYUNITS) | saveunit;
			if (k > 0)
			{
				(void)allocstring(&names[tot], "", el_tempcluster);
				(void)allocstring(&funcs[tot], "", el_tempcluster);
			}
			tot++;
		}
		for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			(void)allocstring(&ports[base], pp->protoname, el_tempcluster);
			shapeportpoly(ni, pp, poly, 0);
			getbbox(poly, &lx, &hx, &ly, &hy);
			saveunit = (INTSML)(el_curtech->userbits & DISPLAYUNITS);
			el_units = (el_units & ~DISPLAYUNITS) | DISPUNITMIC;
			(void)sprintf(thefield, "%s x %s", latoa(hx-lx), latoa(hy-ly));
			(void)allocstring(&portsizes[base], thefield, el_tempcluster);
			el_units = (el_units & ~DISPLAYUNITS) | saveunit;
			if (((pp->userbits&PORTARANGE) >> PORTARANGESH) == 180) (void)strcpy(thefield, ""); else
				(void)sprintf(thefield, "%ld", (pp->userbits&PORTANGLE) >> PORTANGLESH);
			(void)allocstring(&portangles[base], thefield, el_tempcluster);
			m = 0;
			for(k=0; pp->connects[k] != NOARCPROTO; k++)
			{
				if (pp->connects[k]->tech != tech) continue;
				(void)allocstring(&connections[base], pp->connects[k]->protoname, el_tempcluster);
				if (m != 0)
				{
					(void)allocstring(&ports[base], "", el_tempcluster);
					(void)allocstring(&portsizes[base], "", el_tempcluster);
					(void)allocstring(&portangles[base], "", el_tempcluster);
				}
				m++;
				base++;
			}
			if (m == 0) (void)allocstring(&connections[base++], "<NONE>", el_tempcluster);
		}
		for( ; base < tot; base++)
		{
			(void)allocstring(&ports[base], "", el_tempcluster);
			(void)allocstring(&portsizes[base], "", el_tempcluster);
			(void)allocstring(&portangles[base], "", el_tempcluster);
			(void)allocstring(&connections[base], "", el_tempcluster);
		}
		for( ; tot < base; tot++)
		{
			(void)allocstring(&names[tot], "", el_tempcluster);
			(void)allocstring(&funcs[tot], "", el_tempcluster);
			(void)allocstring(&layers[tot], "", el_tempcluster);
			(void)allocstring(&layersizes[tot], "", el_tempcluster);
		}
	}

	/* write the node information */
	fields[0] = names;        fields[1] = funcs;    fields[2] = layers;
	fields[3] = layersizes;   fields[4] = ports;    fields[5] = portsizes;
	fields[6] = portangles;   fields[7] = connections;
	us_dumpfields(fields, 8, tot, f, "NODES");
	for(i=0; i<tot; i++)
	{
		efree(names[i]);
		efree(funcs[i]);
		efree(layers[i]);
		efree(layersizes[i]);
		efree(ports[i]);
		efree(portsizes[i]);
		efree(portangles[i]);
		efree(connections[i]);
	}
	efree((char *)names);
	efree((char *)funcs);
	efree((char *)layers);
	efree((char *)layersizes);
	efree((char *)ports);
	efree((char *)portsizes);
	efree((char *)portangles);
	efree((char *)connections);

	xclose(f);
}

void us_dumpfields(char ***fields, INTSML count, INTSML length, FILE *f, char *title)
{
	INTSML widths[MAXCOLS];
	REGISTER INTSML len, i, j, k, totwid, stars;

	totwid = 0;
	for(i=0; i<count; i++)
	{
		widths[i] = 8;
		for(j=0; j<length; j++)
		{
			len = strlen(fields[i][j]);
			if (len > widths[i]) widths[i] = len;
		}
		widths[i]++;
		totwid += widths[i];
	}

	stars = (totwid - strlen(title) - 2) / 2;
	for(i=0; i<stars; i++) xprintf(f, "*");
	xprintf(f, " %s ", title);
	for(i=0; i<stars; i++) xprintf(f, "*");
	xprintf(f, "\n");

	for(j=0; j<length; j++)
	{
		for(i=0; i<count; i++)
		{
			xprintf(f, "%s", fields[i][j]);
			if (i == count-1) continue;
			for(k=strlen(fields[i][j]); k<widths[i]; k++) xprintf(f, " ");
		}
		xprintf(f, "\n");
	}
	xprintf(f, "\n");
}
