/*
 * Electric(tm) VLSI Design System
 *
 * File: usredtecp.c
 * User interface technology editor: conversion from library to technology
 * 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 "efunction.h"
#include "tech.h"
#include "tecgen.h"
#include "tecart.h"
#include "usr.h"
#include "drc.h"
#include "usredtec.h"

/* the globals that define a technology */
static INTBIG           us_tecflags;
static INTBIG           us_teclayer_count;
static char           **us_teclayer_iname = 0;
static char           **us_teclayer_names = 0;
static char           **us_teccif_layers = 0;
static char           **us_tecdxf_layers = 0;
static INTBIG          *us_tecgds_layers = 0;
static INTBIG          *us_teclayer_function = 0;
static char           **us_teclayer_letters = 0;
static DRCRULES        *us_tecdrc_rules = 0;
static float           *us_tecspice_res = 0;
static float           *us_tecspice_cap = 0;
static float           *us_tecspice_ecap = 0;
static INTBIG          *us_tec3d_height = 0;
static INTBIG          *us_tec3d_thickness = 0;
static INTBIG           us_tecarc_count;
static INTBIG          *us_tecarc_widoff = 0;
static INTBIG           us_tecnode_count;
static INTBIG          *us_tecnode_widoff = 0;
static INTBIG          *us_tecnode_grab = 0;
static INTBIG           us_tecnode_grabcount;
static TECH_COLORMAP    us_teccolmap[32];

static PCON  *us_tecedfirstpcon = NOPCON;	/* list of port connections */
static RULE  *us_tecedfirstrule = NORULE;	/* list of rules */

/* working memory for "us_tecedmakeprim()" */
static INTBIG *us_tecedmakepx, *us_tecedmakepy, *us_tecedmakefactor,
	*us_tecedmakeleftdist, *us_tecedmakerightdist, *us_tecedmakebotdist,
	*us_tecedmaketopdist, *us_tecedmakecentxdist, *us_tecedmakecentydist, *us_tecedmakeratiox,
	*us_tecedmakeratioy, *us_tecedmakecx, *us_tecedmakecy;
static INTBIG us_tecedmakearrlen = 0;

/* working memory for "us_teceditgetdependents()" */
static LIBRARY **us_teceddepliblist;
static INTBIG    us_teceddepliblistsize = 0;

/* prototypes for local routines */
static void     us_tecedcheck(TECHNOLOGY*);
static BOOLEAN  us_tecedmakefactors(LIBRARY**, INTBIG, TECHNOLOGY*);
static BOOLEAN  us_tecedmakelayers(LIBRARY**, INTBIG, TECHNOLOGY*);
static BOOLEAN  us_tecedmakearcs(LIBRARY**, INTBIG, TECHNOLOGY*);
static BOOLEAN  us_tecedmakenodes(LIBRARY**, INTBIG, TECHNOLOGY*);
static INTBIG   us_tecedfindport(TECH_NODES*, EXAMPLE*, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG);
static BOOLEAN  us_tecedassociateexamples(EXAMPLE*, NODEPROTO*);
static BOOLEAN  us_tecedmakeprim(EXAMPLE*, NODEPROTO*, TECHNOLOGY*);
static void     us_tecedforcearrays(INTBIG);
static INTBIG  *us_tecedstretchpoints(INTBIG*, INTBIG*, INTBIG, INTBIG*, SAMPLE*, NODEPROTO*, EXAMPLE*);
static BOOLEAN  us_tecedmulticut(SAMPLE*, EXAMPLE*, NODEPROTO*);
static PCON    *us_tecedaddportlist(INTBIG, INTBIG*);
static char    *us_tecedmakesymbol(char*);
static char    *us_tecedmakefract(INTBIG);
static void     us_teceditdumpdrctab(FILE*, void*, TECHNOLOGY*, BOOLEAN);
static char    *us_tecedmakeupper(char*);
static char    *us_tecededgelabel(INTBIG, INTBIG, BOOLEAN);
static void     us_tecedfreetechmemory(void);
static RULE    *us_tecedaddrule(INTBIG list[8], INTBIG count, BOOLEAN multcut, char *istext);
static int      us_samplecoordascending(const void *e1, const void *e2);
static void     us_teceditdumplayers(FILE *f, TECHNOLOGY *tech, char *techname);
static void     us_teceditdumparcs(FILE *f, TECHNOLOGY *tech, char *techname);
static void     us_teceditdumpnodes(FILE *f, TECHNOLOGY *tech, char *techname);
static void     us_teceditdumpvars(FILE *f, TECHNOLOGY *tech, char *techname);

/*
 * Routine to free all memory associated with this module.
 */
void us_freeedtecpmemory(void)
{
	if (us_tecedmakearrlen != 0)
	{
		efree((char *)us_tecedmakepx);
		efree((char *)us_tecedmakepy);
		efree((char *)us_tecedmakecx);
		efree((char *)us_tecedmakecy);
		efree((char *)us_tecedmakefactor);
		efree((char *)us_tecedmakeleftdist);
		efree((char *)us_tecedmakerightdist);
		efree((char *)us_tecedmakebotdist);
		efree((char *)us_tecedmaketopdist);
		efree((char *)us_tecedmakecentxdist);
		efree((char *)us_tecedmakecentydist);
		efree((char *)us_tecedmakeratiox);
		efree((char *)us_tecedmakeratioy);
	}
	if (us_teceddepliblistsize != 0) efree((char *)us_teceddepliblist);
	us_tecedfreetechmemory();
}

/*
 * the routine invoked for the "technology edit library-to-tech" command.  Dumps
 * C code if "dumpc" is nonzero
 */
void us_tecfromlibinit(LIBRARY *lib, char *techname, BOOLEAN dumpc)
{
	REGISTER FILE *f;
	REGISTER TECHNOLOGY *tech;
	REGISTER CLUSTER *clus;
	REGISTER VARIABLE *var, *ovar;
	REGISTER char **varnames;
	char *truename, *newtechname;
	static TECH_VARIABLES us_tecvariables[2] = {{NULL, NULL, 0.0, 0},
                                                {NULL, NULL, 0.0, 0}};
	INTBIG oldlam;
	LIBRARY **dependentlibs;
	REGISTER INTBIG dependentlibcount;
	REGISTER INTBIG i, j, modified;

	/* make sure network tool is on */
	if ((net_tool->toolstate&TOOLON) == 0)
	{
		ttyputerr(_("Network tool must be running...turning it on"));
		toolturnon(net_tool, FALSE);
		ttyputerr(_("...now reissue the technology editing command"));
		return;
	}

	/* loop until the name is valid */
	if (techname == 0) techname = lib->libname;
	if (allocstring(&newtechname, techname, el_tempcluster)) return;
	modified = 0;
	for(;;)
	{
		/* search by hand because "gettechnology" handles partial matches */
		for(tech = el_technologies; tech != NOTECHNOLOGY; tech = tech->nexttechnology)
			if (namesame(newtechname, tech->techname) == 0) break;
		if (tech == NOTECHNOLOGY) break;
		(void)initinfstr();
		(void)addstringtoinfstr(newtechname);
		(void)addtoinfstr('X');
		(void)reallocstring(&newtechname, returninfstr(), el_tempcluster);
		modified= 1;
	}

	/* create the technology */
	(void)initinfstr();
	(void)addstringtoinfstr("tech:");
	(void)addstringtoinfstr(newtechname);
	clus = alloccluster(returninfstr());
	if (clus == NOCLUSTER) return;
	tech = alloctechnology(clus);
	if (tech == NOTECHNOLOGY) return;

	/* set the technology name */
	if (allocstring(&tech->techname, newtechname, clus)) return;
	efree((char *)newtechname);
	if (modified != 0)
		ttyputmsg(_("Warning: already a technology called %s.  Naming this %s"),
			techname, tech->techname);

	/* set technology description */
	if (allocstring(&tech->techdescript, tech->techname, clus)) return;

	/* free any previous memory for the technology */
	us_tecedfreetechmemory();

	/* get list of dependent libraries */
	dependentlibcount = us_teceditgetdependents(lib, &dependentlibs);

	/* initialize the state of this technology */
	us_tecflags = 0;
	if (us_tecedmakefactors(dependentlibs, dependentlibcount, tech)) return;

	/* build layer structures */
	if (us_tecedmakelayers(dependentlibs, dependentlibcount, tech)) return;

	/* build arc structures */
	if (us_tecedmakearcs(dependentlibs, dependentlibcount, tech)) return;

	/* build node structures */
	if (us_tecedmakenodes(dependentlibs, dependentlibcount, tech)) return;

	/* copy any miscellaneous variables (should use dependent libraries facility) */
	var = getval((INTBIG)lib, VLIBRARY, VSTRING|VISARRAY, "EDTEC_variable_list");
	if (var != NOVARIABLE)
	{
		j = getlength(var);
		varnames = (char **)var->addr;
		for(i=0; i<j; i++)
		{
			ovar = getval((INTBIG)lib, VLIBRARY, -1, varnames[i]);
			if (ovar == NOVARIABLE) continue;
			(void)setval((INTBIG)tech, VTECHNOLOGY, varnames[i], ovar->addr, ovar->type);
		}
	}

	/* check technology for consistency */
	us_tecedcheck(tech);

	if (dumpc)
	{
		/* print the technology */
		(void)initinfstr();
		(void)addstringtoinfstr(techname);
		(void)addstringtoinfstr(".c");
		f = xcreate(returninfstr(), el_filetypetext, _("Technology Code File"), &truename);
		if (f == NULL)
		{
			if (truename != 0) ttyputerr(_("Cannot write %s"), truename);
			return;
		}
		ttyputverbose(M_("Writing: %s"), truename);

		/* write the layers, arcs, and nodes */
		us_teceditdumplayers(f, tech, techname);
		us_teceditdumparcs(f, tech, techname);
		us_teceditdumpnodes(f, tech, techname);
		us_teceditdumpvars(f, tech, techname);

		/* clean up */
		xclose(f);
	}

	/* finish initializing the technology */
	if ((us_tecflags&HASGRAB) == 0) us_tecvariables[0].name = 0; else
	{
		us_tecvariables[0].name = "prototype_center";
		us_tecvariables[0].value = (char *)us_tecnode_grab;
		us_tecvariables[0].type = us_tecnode_grabcount/3;
	}
	tech->variables = us_tecvariables;
	if (tech_doinitprocess(tech)) return;
	if (tech_doaddportsandvars(tech)) return;

	/* install the technology fully */
	addtechnology(tech);

	/* let the user interface process it */
	us_figuretechopaque(tech);

	/* switch to this technology */
	ttyputmsg(_("Technology %s built.  Switching to it."), tech->techname);
	us_setnodeproto(NONODEPROTO);
	us_setarcproto(NOARCPROTO, TRUE);
	oldlam = el_curlib->lambda[el_curtech->techindex];

	/* disable option tracking while colormap is updated */
	(void)setvalkey((INTBIG)us_tool, VTOOL, us_ignoreoptionchangeskey, 1,
		VINTEGER|VDONTSAVE);
	us_getcolormap(tech, COLORSEXISTING, TRUE);
	var = getvalkey((INTBIG)us_tool, VTOOL, VINTEGER, us_ignoreoptionchangeskey);
	if (var != NOVARIABLE)
		(void)delvalkey((INTBIG)us_tool, VTOOL, us_ignoreoptionchangeskey);

	(void)setvalkey((INTBIG)us_tool, VTOOL, us_current_technology_key, (INTBIG)tech,
		VTECHNOLOGY|VDONTSAVE);

	/* fix up the menu entries */
	us_setmenunodearcs();
	if ((us_state&NONPERSISTENTCURNODE) == 0) us_setnodeproto(tech->firstnodeproto);
	us_setarcproto(tech->firstarcproto, TRUE);
}

void us_tecedcheck(TECHNOLOGY *tech)
{
	REGISTER INTBIG i, j, k, l;
	REGISTER TECH_POLYGON *plist;
	REGISTER TECH_NODES *nlist;

	/* make sure there is a pure-layer node for every nonpseudo layer */
	for(i=0; i<tech->layercount; i++)
	{
		if ((us_teclayer_function[i]&LFPSEUDO) != 0) continue;
		for(j=0; j<tech->nodeprotocount; j++)
		{
			nlist = tech->nodeprotos[j];
			if (((nlist->initialbits&NFUNCTION)>>NFUNCTIONSH) != NPNODE) continue;
			plist = &nlist->layerlist[0];
			if (plist->layernum == i) break;
		}
		if (j < tech->nodeprotocount) continue;
		ttyputmsg(_("Warning: Layer %s has no associated pure-layer node"),
			us_teclayer_names[i]);
	}

	/* make sure there is a pin for every arc and that it uses pseudo-layers */
	for(i=0; i<tech->arcprotocount; i++)
	{
		for(j=0; j<tech->nodeprotocount; j++)
		{
			nlist = tech->nodeprotos[j];
			if (((nlist->initialbits&NFUNCTION)>>NFUNCTIONSH) != NPPIN) continue;
			for(k=0; k<nlist->portcount; k++)
			{
				for(l=1; nlist->portlist[k].portarcs[l] >= 0; l++)
					if (nlist->portlist[k].portarcs[l] == i) break;
				if (nlist->portlist[k].portarcs[l] >= 0) break;
			}
			if (k < nlist->portcount) break;
		}
		if (j < tech->nodeprotocount)
		{
			/* pin found: make sure it uses pseudo-layers */
			nlist = tech->nodeprotos[j];
			for(k=0; k<nlist->layercount; k++)
			{
				plist = &nlist->layerlist[k];
				if ((us_teclayer_function[plist->layernum]&LFPSEUDO) == 0) break;
			}
			if (k < nlist->layercount)
				ttyputmsg(_("Warning: Pin %s is not composed of pseudo-layers"),
					tech->nodeprotos[j]->nodename);
			continue;
		}
		ttyputmsg(_("Warning: Arc %s has no associated pin node"), tech->arcprotos[i]->arcname);
	}
}

void us_tecedfreetechmemory(void)
{
	REGISTER INTBIG i;
	REGISTER PCON *pc;
	REGISTER RULE *r;

	/* free DRC layer name information */
	if (us_teceddrclayernames != 0)
	{
		for(i=0; i<us_teceddrclayers; i++) efree(us_teceddrclayernames[i]);
		efree((char *)us_teceddrclayernames);
		us_teceddrclayernames = 0;
	}

	if (us_teclayer_iname != 0)
	{
		for(i=0; i<us_teclayer_count; i++)
			if (us_teclayer_iname[i] != 0) efree((char *)us_teclayer_iname[i]);
		efree((char *)us_teclayer_iname);
		us_teclayer_iname = 0;
	}
	if (us_teclayer_names != 0)
	{
		for(i=0; i<us_teclayer_count; i++)
			if (us_teclayer_names[i] != 0) efree((char *)us_teclayer_names[i]);
		efree((char *)us_teclayer_names);
		us_teclayer_names = 0;
	}
	if (us_teccif_layers != 0)
	{
		for(i=0; i<us_teclayer_count; i++)
			if (us_teccif_layers[i] != 0) efree((char *)us_teccif_layers[i]);
		efree((char *)us_teccif_layers);
		us_teccif_layers = 0;
	}
	if (us_tecdxf_layers != 0)
	{
		for(i=0; i<us_teclayer_count; i++)
			if (us_tecdxf_layers[i] != 0) efree((char *)us_tecdxf_layers[i]);
		efree((char *)us_tecdxf_layers);
		us_tecdxf_layers = 0;
	}
	if (us_tecgds_layers != 0)
	{
		efree((char *)us_tecgds_layers);
		us_tecgds_layers = 0;
	}
	if (us_teclayer_function != 0)
	{
		efree((char *)us_teclayer_function);
		us_teclayer_function = 0;
	}
	if (us_teclayer_letters != 0)
	{
		for(i=0; i<us_teclayer_count; i++)
			if (us_teclayer_letters[i] != 0) efree((char *)us_teclayer_letters[i]);
		efree((char *)us_teclayer_letters);
		us_teclayer_letters = 0;
	}
	if (us_tecdrc_rules != 0)
	{
		dr_freerules(us_tecdrc_rules);
		us_tecdrc_rules = 0;
	}
	if (us_tecspice_res != 0)
	{
		efree((char *)us_tecspice_res);
		us_tecspice_res = 0;
	}
	if (us_tecspice_cap != 0)
	{
		efree((char *)us_tecspice_cap);
		us_tecspice_cap = 0;
	}
	if (us_tecspice_ecap != 0)
	{
		efree((char *)us_tecspice_ecap);
		us_tecspice_ecap = 0;
	}
	if (us_tec3d_height != 0)
	{
		efree((char *)us_tec3d_height);
		us_tec3d_height = 0;
	}
	if (us_tec3d_thickness != 0)
	{
		efree((char *)us_tec3d_thickness);
		us_tec3d_thickness = 0;
	}

	if (us_tecarc_widoff != 0)
	{
		efree((char *)us_tecarc_widoff);
		us_tecarc_widoff = 0;
	}

	if (us_tecnode_widoff != 0)
	{
		efree((char *)us_tecnode_widoff);
		us_tecnode_widoff = 0;
	}
	if (us_tecnode_grab != 0)
	{
		efree((char *)us_tecnode_grab);
		us_tecnode_grab = 0;
	}

	while (us_tecedfirstpcon != NOPCON)
	{
		pc = us_tecedfirstpcon;
		us_tecedfirstpcon = us_tecedfirstpcon->nextpcon;
		efree((char *)pc->connects);
		efree((char *)pc->assoc);
		efree((char *)pc);
	}

	while (us_tecedfirstrule != NORULE)
	{
		r = us_tecedfirstrule;
		us_tecedfirstrule = us_tecedfirstrule->nextrule;
		efree((char *)r->value);
		efree((char *)r);
	}
}

/*
 * routine to scan the "dependentlibcount" libraries in "dependentlibs",
 * and get global factors for technology "tech".  Returns true on error.
 */
BOOLEAN us_tecedmakefactors(LIBRARY **dependentlibs, INTBIG dependentlibcount, TECHNOLOGY *tech)
{
	REGISTER NODEPROTO *np;
	REGISTER NODEINST *ni;
	REGISTER VARIABLE *var;
	REGISTER INTBIG opt;
	REGISTER char *str;
	REGISTER INTBIG i;

	np = NONODEPROTO;
	for(i=dependentlibcount-1; i>=0; i--)
	{
		for(np = dependentlibs[i]->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			if (namesame(np->cell->cellname, "factors") == 0) break;
		if (np != NONODEPROTO) break;
	}
	if (np == NONODEPROTO)
	{
		tech->deflambda = 2000;
		return(FALSE);
	}

	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		opt = us_tecedgetoption(ni);
		var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, art_messagekey);
		if (var == NOVARIABLE) continue;
		str = (char *)var->addr;
		switch (opt)
		{
			case TECHLAMBDA:	/* lambda */
				tech->deflambda = myatoi(&str[8]);
				break;
			case TECHDESCRIPT:	/* description */
				(void)reallocstring(&tech->techdescript, &str[13], tech->cluster);
				break;
			default:
				us_tecedpointout(ni, np);
				ttyputerr(_("Unknown object in miscellaneous-information facet"));
				return(TRUE);
		}
	}
	return(FALSE);
}

/*
 * routine to scan the "dependentlibcount" libraries in "dependentlibs",
 * and build the layer structures for it in technology "tech".  Returns true on error.
 */
BOOLEAN us_tecedmakelayers(LIBRARY **dependentlibs, INTBIG dependentlibcount, TECHNOLOGY *tech)
{
	REGISTER NODEPROTO *np;
	NODEPROTO **sequence;
	REGISTER INTBIG i, j, l, total;
	REGISTER INTBIG *drcptr, drcsize;
	REGISTER char *ab;
	REGISTER VARIABLE *var;

	/* first find the number of layers */
	tech->layercount = us_teceditfindsequence(dependentlibs, dependentlibcount, "layer-",
		"EDTEC_layersequence", &sequence);
	if (tech->layercount <= 0)
	{
		ttyputerr(_("No layers found"));
		if ((us_tool->toolstate&NODETAILS) == 0)
			ttyputerr(_("Create them with the 'edit-layer' option"));
		return(TRUE);
	}

	/* allocate the arrays for the layers */
	us_teclayer_count = tech->layercount;
	drcsize = us_teclayer_count*us_teclayer_count/2 + (us_teclayer_count+1)/2;

	us_teclayer_iname = (char **)emalloc((us_teclayer_count * (sizeof (char *))), us_tool->cluster);
	if (us_teclayer_iname == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) us_teclayer_iname[i] = 0;

	tech->layers = (GRAPHICS **)emalloc(((us_teclayer_count+1) * (sizeof (GRAPHICS *))), tech->cluster);
	if (tech->layers == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) tech->layers[i] = (GRAPHICS *)emalloc(sizeof (GRAPHICS), tech->cluster);
	tech->layers[us_teclayer_count] = NOGRAPHICS;

	us_teclayer_names = (char **)emalloc((us_teclayer_count * (sizeof (char *))), us_tool->cluster);
	if (us_teclayer_names == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) us_teclayer_names[i] = 0;

	us_teccif_layers = (char **)emalloc((us_teclayer_count * (sizeof (char *))), us_tool->cluster);
	if (us_teccif_layers == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) us_teccif_layers[i] = 0;

	us_tecdxf_layers = (char **)emalloc((us_teclayer_count * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecdxf_layers == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) us_tecdxf_layers[i] = 0;

	us_tecgds_layers = emalloc((us_teclayer_count * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecgds_layers == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) us_tecgds_layers[i] = -1;

	us_teclayer_function = emalloc((us_teclayer_count * SIZEOFINTBIG), us_tool->cluster);
	if (us_teclayer_function == 0) return(TRUE);

	us_teclayer_letters = (char **)emalloc((us_teclayer_count * (sizeof (char *))), us_tool->cluster);
	if (us_teclayer_letters == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) us_teclayer_letters[i] = 0;

	us_tecspice_res = (float *)emalloc((us_teclayer_count * (sizeof (float))), us_tool->cluster);
	if (us_tecspice_res == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) us_tecspice_res[i] = 0.0;

	us_tecspice_cap = (float *)emalloc((us_teclayer_count * (sizeof (float))), us_tool->cluster);
	if (us_tecspice_cap == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) us_tecspice_cap[i] = 0.0;

	us_tecspice_ecap = (float *)emalloc((us_teclayer_count * (sizeof (float))), us_tool->cluster);
	if (us_tecspice_ecap == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) us_tecspice_ecap[i] = 0.0;

	us_tec3d_height = (INTBIG *)emalloc((us_teclayer_count * SIZEOFINTBIG), us_tool->cluster);
	if (us_tec3d_height == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) us_tec3d_height[i] = 0;

	us_tec3d_thickness = (INTBIG *)emalloc((us_teclayer_count * SIZEOFINTBIG), us_tool->cluster);
	if (us_tec3d_thickness == 0) return(TRUE);
	for(i=0; i<us_teclayer_count; i++) us_tec3d_thickness[i] = 0;

	/* get the design rules */
	us_tecedgetlayernamelist();
	if (us_tecdrc_rules != 0)
	{
		dr_freerules(us_tecdrc_rules);
		us_tecdrc_rules = 0;
	}
	us_tecdrc_rules = dr_allocaterules(us_teceddrclayers, "EDITED TECHNOLOGY");
	if (us_tecdrc_rules == NODRCRULES) return(TRUE);
	for(i=0; i<us_teceddrclayers; i++)
		(void)allocstring(&us_tecdrc_rules->layernames[i], us_teceddrclayernames[i], el_tempcluster);
	var = NOVARIABLE;
	for(i=dependentlibcount-1; i>=0; i--)
	{
		var = getval((INTBIG)dependentlibs[i], VLIBRARY, VSTRING|VISARRAY, "EDTEC_DRC");
		if (var != NOVARIABLE) break;
	}
	if (us_teceditgetdrcarrays(var, us_tecdrc_rules)) return(TRUE);

	/* now scan each layer and fill in the data */
	for(total=0; total<us_teclayer_count; total++)
	{
		/* set the layer name */
		np = sequence[total];
		(void)allocstring(&us_teclayer_names[total], &np->cell->cellname[6], us_tool->cluster);

		if (us_teceditgetlayerinfo(np, tech->layers[total], &us_teccif_layers[total],
			&us_teclayer_function[total], &us_teclayer_letters[total], &us_tecdxf_layers[total],
				&us_tecgds_layers[total], &us_tecspice_res[total], &us_tecspice_cap[total],
					&us_tecspice_ecap[total], &us_tecdrc_rules->minwidth[total],
						&us_tec3d_height[total], &us_tec3d_thickness[total])) return(TRUE);
		if (us_teccif_layers[total] != 0 && namesame(us_teccif_layers[total], "xx") != 0)
			us_tecflags |= HASCIF;
		if (us_tecdxf_layers[total] != 0) us_tecflags |= HASDXF;
		if (us_tecgds_layers[total] != -1) us_tecflags |= HASGDS;
		if (us_tecspice_res[total] != 0.0) us_tecflags |= HASSPIRES;
		if (us_tecspice_cap[total] != 0.0) us_tecflags |= HASSPICAP;
		if (us_tecspice_ecap[total] != 0.0) us_tecflags |= HASSPIECAP;
		if (us_tec3d_height[total] != 0 || us_tec3d_thickness[total] != 0) us_tecflags |= HAS3DINFO;
		tech->layers[total]->firstvar = NOVARIABLE;
		tech->layers[total]->numvar = 0;
	}

	for(i=0; i<total; i++)
	{
		(void)allocstring(&us_teclayer_iname[i], makeabbrev(us_teclayer_names[i], TRUE),
			us_tool->cluster);

		/* loop until the name is unique */
		for(;;)
		{
			/* see if a previously assigned abbreviation is the same */
			for(j=0; j<i; j++)
				if (namesame(us_teclayer_iname[i], us_teclayer_iname[j]) == 0)
					break;
			if (j >= i) break;

			/* name conflicts: change it */
			l = strlen(ab = us_teclayer_iname[i]);
			if (ab[l-1] >= '0' && ab[l-1] <= '8') ab[l-1]++; else
			{
				(void)initinfstr();
				(void)addstringtoinfstr(ab);
				(void)addtoinfstr('0');
				(void)reallocstring(&us_teclayer_iname[i], returninfstr(), us_tool->cluster);
			}
		}
	}

	/* get the color map */
	var = NOVARIABLE;
	for(i=dependentlibcount-1; i>=0; i--)
	{
		var = getval((INTBIG)dependentlibs[i], VLIBRARY, VINTEGER|VISARRAY, "EDTEC_colormap");
		if (var != NOVARIABLE) break;
	}
	if (var != NOVARIABLE)
	{
		us_tecflags |= HASCOLORMAP;
		drcptr = (INTBIG *)var->addr;
		for(i=0; i<32; i++)
		{
			us_teccolmap[i].red = (INTSML)drcptr[(i<<2)*3];
			us_teccolmap[i].green = (INTSML)drcptr[(i<<2)*3+1];
			us_teccolmap[i].blue = (INTSML)drcptr[(i<<2)*3+2];
		}
	}

	/* see which design rules exist */
	for(i=0; i<us_teceddrclayers; i++)
	{
		if (us_tecdrc_rules->minwidth[i] >= 0) us_tecflags |= HASDRCMINWID;
		if (*us_tecdrc_rules->minwidthR[i] != 0) us_tecflags |= HASDRCMINWIDR;
	}
	for(i=0; i<drcsize; i++)
	{
		if (us_tecdrc_rules->conlist[i] >= 0) us_tecflags |= HASCONDRC;
		if (*us_tecdrc_rules->conlistR[i] != 0) us_tecflags |= HASCONDRCR;
		if (us_tecdrc_rules->unconlist[i] >= 0) us_tecflags |= HASUNCONDRC;
		if (*us_tecdrc_rules->unconlistR[i] != 0) us_tecflags |= HASUNCONDRCR;
		if (us_tecdrc_rules->conlistW[i] >= 0) us_tecflags |= HASCONDRCW;
		if (*us_tecdrc_rules->conlistWR[i] != 0) us_tecflags |= HASCONDRCWR;
		if (us_tecdrc_rules->unconlistW[i] >= 0) us_tecflags |= HASUNCONDRCW;
		if (*us_tecdrc_rules->unconlistWR[i] != 0) us_tecflags |= HASUNCONDRCWR;
		if (us_tecdrc_rules->conlistM[i] >= 0) us_tecflags |= HASCONDRCM;
		if (*us_tecdrc_rules->conlistMR[i] != 0) us_tecflags |= HASCONDRCMR;
		if (us_tecdrc_rules->unconlistM[i] >= 0) us_tecflags |= HASUNCONDRCM;
		if (*us_tecdrc_rules->unconlistMR[i] != 0) us_tecflags |= HASUNCONDRCMR;
		if (us_tecdrc_rules->edgelist[i] >= 0) us_tecflags |= HASEDGEDRC;
		if (*us_tecdrc_rules->edgelistR[i] != 0) us_tecflags |= HASEDGEDRCR;
	}

	/* store this information on the technology object */
	(void)setval((INTBIG)tech, VTECHNOLOGY, "TECH_layer_names", (INTBIG)us_teclayer_names,
		VSTRING|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
	(void)setval((INTBIG)tech, VTECHNOLOGY, "TECH_layer_function", (INTBIG)us_teclayer_function,
		VINTEGER|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
	(void)setvalkey((INTBIG)tech, VTECHNOLOGY, us_layer_letters_key, (INTBIG)us_teclayer_letters,
		VSTRING|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
	if ((us_tecflags&HASCOLORMAP) != 0)
		(void)setval((INTBIG)tech, VTECHNOLOGY, "USER_color_map", (INTBIG)us_teccolmap,
			VCHAR|VDONTSAVE|VISARRAY|((sizeof us_teccolmap)<<VLENGTHSH));
	if ((us_tecflags&HASCIF) != 0)
		(void)setval((INTBIG)tech, VTECHNOLOGY, "IO_cif_layer_names", (INTBIG)us_teccif_layers,
			VSTRING|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
	if ((us_tecflags&HASDXF) != 0)
		(void)setval((INTBIG)tech, VTECHNOLOGY, "IO_dxf_layer_names", (INTBIG)us_tecdxf_layers,
			VSTRING|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
	if ((us_tecflags&HASGDS) != 0)
		(void)setval((INTBIG)tech, VTECHNOLOGY, "IO_gds_layer_numbers", (INTBIG)us_tecgds_layers,
			VINTEGER|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));

	if ((us_tecflags&(HASCONDRCW|HASUNCONDRCW)) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_wide_limitkey, us_tecdrc_rules->widelimit,
			VFRACT|VDONTSAVE);
	if ((us_tecflags&HASDRCMINWID) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_min_widthkey, (INTBIG)us_tecdrc_rules->minwidth,
			VFRACT|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
	if ((us_tecflags&HASDRCMINWIDR) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_min_width_rulekey, (INTBIG)us_tecdrc_rules->minwidthR,
			VSTRING|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
	if ((us_tecflags&HASCONDRC) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_connected_distanceskey,
			(INTBIG)us_tecdrc_rules->conlist, VFRACT|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASCONDRCR) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_connected_distances_rulekey,
			(INTBIG)us_tecdrc_rules->conlistR, VSTRING|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASUNCONDRC) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_unconnected_distanceskey,
			(INTBIG)us_tecdrc_rules->unconlist, VFRACT|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASUNCONDRCR) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_unconnected_distances_rulekey,
			(INTBIG)us_tecdrc_rules->unconlistR, VSTRING|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASCONDRCW) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_connected_distancesWkey,
			(INTBIG)us_tecdrc_rules->conlistW, VFRACT|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASCONDRCWR) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_connected_distancesW_rulekey,
			(INTBIG)us_tecdrc_rules->conlistWR, VSTRING|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASUNCONDRCW) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_unconnected_distancesWkey,
			(INTBIG)us_tecdrc_rules->unconlistW, VFRACT|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASUNCONDRCWR) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_unconnected_distancesW_rulekey,
			(INTBIG)us_tecdrc_rules->unconlistWR, VSTRING|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASCONDRCM) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_connected_distancesMkey,
			(INTBIG)us_tecdrc_rules->conlistM, VFRACT|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASCONDRCMR) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_connected_distancesM_rulekey,
			(INTBIG)us_tecdrc_rules->conlistMR, VSTRING|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASUNCONDRCM) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_unconnected_distancesMkey,
			(INTBIG)us_tecdrc_rules->unconlistM, VFRACT|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASUNCONDRCMR) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_unconnected_distancesM_rulekey,
			(INTBIG)us_tecdrc_rules->unconlistMR, VSTRING|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASEDGEDRC) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_edge_distanceskey,
			(INTBIG)us_tecdrc_rules->edgelist, VFRACT|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASEDGEDRCR) != 0)
		(void)setvalkey((INTBIG)tech, VTECHNOLOGY, dr_edge_distances_rulekey,
			(INTBIG)us_tecdrc_rules->edgelistR, VSTRING|VDONTSAVE|VISARRAY|(drcsize<<VLENGTHSH));
	if ((us_tecflags&HASSPIRES) != 0)
		(void)setval((INTBIG)tech, VTECHNOLOGY, "SIM_spice_resistance", (INTBIG)us_tecspice_res,
			VFLOAT|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
	if ((us_tecflags&HASSPICAP) != 0)
		(void)setval((INTBIG)tech, VTECHNOLOGY, "SIM_spice_capacitance", (INTBIG)us_tecspice_cap,
			VFLOAT|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
	if ((us_tecflags&HASSPIECAP) != 0)
		(void)setval((INTBIG)tech, VTECHNOLOGY, "SIM_spice_edge_capacitance", (INTBIG)us_tecspice_ecap,
			VFLOAT|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
	if ((us_tecflags&HAS3DINFO) != 0)
	{
		(void)setval((INTBIG)tech, VTECHNOLOGY, "TECH_layer_3dheight", (INTBIG)us_tec3d_height,
			VINTEGER|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
		(void)setval((INTBIG)tech, VTECHNOLOGY, "TECH_layer_3dthickness", (INTBIG)us_tec3d_thickness,
			VINTEGER|VDONTSAVE|VISARRAY|(tech->layercount<<VLENGTHSH));
	}

	efree((char *)sequence);
	return(FALSE);
}

/*
 * routine to parse DRC line "str" and fill the factors "connected" (set nonzero
 * if rule is for connected layers), "amt" (rule distance), "layer1" and "layer2"
 * (the layers).  Presumes that there are "maxlayers" layer names in the
 * array "layernames".  Returns true on error.
 */
BOOLEAN us_tecedgetdrc(char *str, BOOLEAN *connected, BOOLEAN *wide, BOOLEAN *multi, INTBIG *widrule,
	BOOLEAN *edge, INTBIG *amt, INTBIG *layer1, INTBIG *layer2, char **rule, INTBIG maxlayers,
	char **layernames)
{
	REGISTER char *pt;
	REGISTER INTBIG save;

	*connected = *wide = *multi = *edge = FALSE;
	for( ; *str != 0; str++)
	{
		if (tolower(*str) == 'c')
		{
			*connected = TRUE;
			continue;
		}
		if (tolower(*str) == 'w')
		{
			*wide = TRUE;
			continue;
		}
		if (tolower(*str) == 'm')
		{
			*multi = TRUE;
			continue;
		}
		if (tolower(*str) == 'e')
		{
			*edge = TRUE;
			continue;
		}
		break;
	}
	*widrule = 0;
	if (tolower(*str) == 's')
	{
		*widrule = 1;
		str++;
	} else if (tolower(*str) == 'l')
	{
		*widrule = 2;
		str++;
	}

	/* get the distance */
	pt = str;
	while (*pt != 0 && *pt != ' ' && *pt != '\t') pt++;
	while (*pt == ' ' || *pt == '\t') pt++;
	*amt = atofr(str);

	/* get the first layer */
	if (*widrule != 2)
	{
		str = pt;
		if (*str == 0)
		{
			ttyputerr(_("Cannot find layer names on DRC line"));
			return(TRUE);
		}
		while (*pt != 0 && *pt != ' ' && *pt != '\t') pt++;
		if (*pt == 0)
		{
			ttyputerr(_("Cannot find layer name on DRC line"));
			return(TRUE);
		}
		save = *pt;
		*pt = 0;
		for(*layer1 = 0; *layer1 < maxlayers; (*layer1)++)
			if (namesame(str, layernames[*layer1]) == 0) break;
		*pt++ = (char)save;
		if (*layer1 >= maxlayers)
		{
			ttyputerr(_("First DRC layer name unknown"));
			return(TRUE);
		}
		while (*pt == ' ' || *pt == '\t') pt++;
	}

	/* get the second layer */
	if (*widrule == 0)
	{
		str = pt;
		while (*pt != 0 && *pt != ' ' && *pt != '\t') pt++;
		save = *pt;
		*pt = 0;
		for(*layer2 = 0; *layer2 < maxlayers; (*layer2)++)
			if (namesame(str, layernames[*layer2]) == 0) break;
		*pt = (char)save;
		if (*layer2 >= maxlayers)
		{
			ttyputerr(_("Second DRC layer name unknown"));
			return(TRUE);
		}
	}

	while (*pt == ' ' || *pt == '\t') pt++;
	*rule = pt;
	return(FALSE);
}

/*
 * routine to scan the "dependentlibcount" libraries in "dependentlibs",
 * and build the arc structures for it in technology "tech".  Returns true on error.
 */
BOOLEAN us_tecedmakearcs(LIBRARY **dependentlibs, INTBIG dependentlibcount,
	TECHNOLOGY *tech)
{
	REGISTER NODEPROTO *np;
	NODEPROTO **sequence;
	REGISTER INTBIG arcindex, count, j, k, layerindex, typ;
	REGISTER INTBIG maxwid, hwid, wid, lambda;
	REGISTER char *str;
	REGISTER NODEINST *ni;
	REGISTER EXAMPLE *nelist;
	REGISTER SAMPLE *ns;
	REGISTER VARIABLE *var;

	/* count the number of arcs in the technology */
	us_tecarc_count = us_teceditfindsequence(dependentlibs, dependentlibcount, "arc-",
		"EDTEC_arcsequence", &sequence);
	if (us_tecarc_count <= 0)
	{
		ttyputerr(_("No arcs found"));
		if ((us_tool->toolstate&NODETAILS) == 0)
			ttyputerr(_("Create them with the 'edit-arc' option"));
		return(TRUE);
	}
	tech->arcprotocount = us_tecarc_count;

	/* allocate the arcs */
	tech->arcprotos = (TECH_ARCS **)emalloc(((us_tecarc_count+1) * (sizeof (TECH_ARCS *))),
		tech->cluster);
	if (tech->arcprotos == 0) return(TRUE);
	tech->arcprotos[us_tecarc_count] = ((TECH_ARCS *)-1);
	us_tecarc_widoff = emalloc((us_tecarc_count * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecarc_widoff == 0) return(TRUE);

	/* create the arc structures */
	lambda = el_curlib->lambda[el_curtech->techindex];
	for(arcindex=0; arcindex<us_tecarc_count; arcindex++)
	{
		/* build a list of examples found in this arc */
		np = sequence[arcindex];
		nelist = us_tecedgetexamples(np, FALSE);
		if (nelist == NOEXAMPLE) return(TRUE);
		if (nelist->nextexample != NOEXAMPLE)
		{
			us_tecedfreeexamples(nelist);
			us_tecedpointout(NONODEINST, np);
			ttyputerr(_("Can only be one example of %s but more were found"),
				describenodeproto(np));
			return(TRUE);
		}

		/* get width and polygon count information */
		count = 0;
		maxwid = hwid = -1;
		for(ns = nelist->firstsample; ns != NOSAMPLE; ns = ns->nextsample)
		{
			wid = mini(ns->node->highx - ns->node->lowx, ns->node->highy - ns->node->lowy);
			if (wid > maxwid) maxwid = wid;
			if (ns->layer == NONODEPROTO) hwid = wid; else count++;
		}

		/* error if there is no highlight box */
		if (hwid < 0)
		{
			us_tecedfreeexamples(nelist);
			us_tecedpointout(NONODEINST, np);
			ttyputerr(_("No highlight layer found in %s"), describenodeproto(np));
			if ((us_tool->toolstate&NODETAILS) == 0)
				ttyputmsg(_("Use 'place-layer' option to create HIGHLIGHT"));
			return(TRUE);
		}

		/* create and fill the basic structure entries for this arc */
		tech->arcprotos[arcindex] = (TECH_ARCS *)emalloc(sizeof (TECH_ARCS), tech->cluster);
		if (tech->arcprotos[arcindex] == 0) return(TRUE);
		(void)allocstring(&tech->arcprotos[arcindex]->arcname, &np->cell->cellname[4],
			tech->cluster);
		tech->arcprotos[arcindex]->arcwidth = maxwid * WHOLE / lambda;
		tech->arcprotos[arcindex]->arcindex = arcindex;
		tech->arcprotos[arcindex]->creation = NOARCPROTO;
		tech->arcprotos[arcindex]->laycount = count;
		us_tecarc_widoff[arcindex] = (maxwid - hwid) * WHOLE / lambda;
		if (us_tecarc_widoff[arcindex] != 0) us_tecflags |= HASARCWID;

		/* look for descriptive nodes in the facet */
		tech->arcprotos[arcindex]->initialbits = 0;
		for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		{
			typ = us_tecedgetoption(ni);
			var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, art_messagekey);
			if (var == NOVARIABLE) continue;

			/* the "Function:" node */
			if (typ == ARCFUNCTION)
			{
				str = &((char *)var->addr)[10];
				for(j=0; us_tecarc_functions[j].name != 0; j++)
					if (namesame(str, us_tecarc_functions[j].name) == 0)
				{
					tech->arcprotos[arcindex]->initialbits |=
						(us_tecarc_functions[j].value << AFUNCTIONSH);
					break;
				}
			}

			/* the "Fixed-angle:" node */
			if (typ == ARCFIXANG)
			{
				str = (char *)var->addr;
				if (str[9] == ':') str = &str[11]; else str = &str[13];
				if (namesame(str, "yes") == 0)
					tech->arcprotos[arcindex]->initialbits |= WANTFIXANG;
			}

			/* the "Wipes pins:" node */
			if (typ == ARCWIPESPINS)
			{
				str = &((char *)var->addr)[12];
				if (namesame(str, "yes") == 0)
					tech->arcprotos[arcindex]->initialbits |= CANWIPE;
			}

			/* the "Extend arcs:" node */
			if (typ == ARCNOEXTEND)
			{
				str = &((char *)var->addr)[13];
				if (namesame(str, "no") == 0)
					tech->arcprotos[arcindex]->initialbits |= WANTNOEXTEND;
			}

			/* the "Angle increment:" node */
			if (typ == ARCINC)
			{
				j = myatoi(&((char *)var->addr)[17]) % 360;
				if (j < 0) j += 360;
				tech->arcprotos[arcindex]->initialbits &= ~AANGLEINC;
				tech->arcprotos[arcindex]->initialbits |= (j << AANGLEINCSH);
			}
		}

		/* allocate the individual arc layer structures */
		tech->arcprotos[arcindex]->list = (TECH_ARCLAY *)emalloc((count * (sizeof (TECH_ARCLAY))),
			tech->cluster);
		if (tech->arcprotos[arcindex]->list == 0) return(TRUE);

		/* fill the individual arc layer structures */
		layerindex = 0;
		for(k=0; k<2; k++)
			for(ns = nelist->firstsample; ns != NOSAMPLE; ns = ns->nextsample)
		{
			if (ns->layer == NONODEPROTO) continue;

			/* get the layer index */
			for(j=0; j<tech->layercount; j++)
				if (namesame(&ns->layer->cell->cellname[6], us_teclayer_names[j]) == 0) break;
			if (j >= tech->layercount)
			{
				us_tecedfreeexamples(nelist);
				ttyputerr(_("Cannot find layer %s"), describenodeproto(ns->layer));
				return(TRUE);
			}

			/* only add overlappable layers when k=0 */
			if (k == 0)
			{
				if (tech->layers[j]->bits == LAYERO) continue;
			} else
			{
				if (tech->layers[j]->bits != LAYERO) continue;
			}

			tech->arcprotos[arcindex]->list[layerindex].lay = j;

			/* determine the style of this arc layer */
			if (ns->node->proto == art_filledboxprim)
				tech->arcprotos[arcindex]->list[layerindex].style = FILLED; else
					tech->arcprotos[arcindex]->list[layerindex].style = CLOSED;

			/* determine the width offset of this arc layer */
			wid = mini(ns->node->highx-ns->node->lowx, ns->node->highy-ns->node->lowy);
			tech->arcprotos[arcindex]->list[layerindex].off = (maxwid-wid) * WHOLE /
				lambda;

			layerindex++;
		}
		us_tecedfreeexamples(nelist);
	}

	/* store width offset on the technology */
	if ((us_tecflags&HASARCWID) != 0)
		(void)setval((INTBIG)tech, VTECHNOLOGY, "TECH_arc_width_offset", (INTBIG)us_tecarc_widoff,
			VFRACT|VDONTSAVE|VISARRAY|(us_tecarc_count<<VLENGTHSH));
	efree((char *)sequence);
	return(FALSE);
}

/*
 * routine to scan the "dependentlibcount" libraries in "dependentlibs",
 * and build the node structures for it in technology "tech".  Returns true on error.
 */
BOOLEAN us_tecedmakenodes(LIBRARY **dependentlibs, INTBIG dependentlibcount,
	TECHNOLOGY *tech)
{
	REGISTER NODEPROTO *np;
	NODEPROTO **sequence;
	REGISTER NODEINST *ni;
	REGISTER VARIABLE *var;
	REGISTER char *str;
	REGISTER INTBIG *list, save1, nfunction, x1pos, x2pos, y1pos, y2pos, net, lambda;
	REGISTER INTBIG i, j, k, l, m, pass, nodeindex, sty, difindex, polindex,
		serpdifind, opt, nsindex, err, portchecked;
	INTBIG pol1port, pol2port, dif1port, dif2port;
	INTBIG serprule[8];
	REGISTER EXAMPLE *nelist;
	REGISTER SAMPLE *ns, *ons, *diflayer, *pollayer;
	REGISTER PCON *pc;
	REGISTER RULE *r;
	REGISTER TECH_NODES *tlist;

	/* no rectangle rules */
	us_tecedfirstrule = NORULE;

	us_tecnode_count = us_teceditfindsequence(dependentlibs, dependentlibcount, "node-",
		"EDTEC_nodesequence", &sequence);
	if (us_tecnode_count <= 0)
	{
		ttyputerr(_("No nodes found"));
		if ((us_tool->toolstate&NODETAILS) == 0)
			ttyputerr(_("Create them with the 'edit-node' option"));
		return(TRUE);
	}
	tech->nodeprotocount = us_tecnode_count;

	/* allocate the nodes */
	tech->nodeprotos = (TECH_NODES **)emalloc((us_tecnode_count+1) *
		(sizeof (TECH_NODES *)), tech->cluster);
	if (tech->nodeprotos == 0) return(TRUE);
	tech->nodeprotos[us_tecnode_count] = ((TECH_NODES *)-1);
	us_tecnode_widoff = emalloc((4*us_tecnode_count * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecnode_widoff == 0) return(TRUE);
	us_tecnode_grab = emalloc((3*us_tecnode_count * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecnode_grab == 0) return(TRUE);
	us_tecnode_grabcount = 0;

	/* get the nodes */
	lambda = el_curlib->lambda[el_curtech->techindex];
	nodeindex = 0;
	for(pass=0; pass<3; pass++)
		for(m=0; m<us_tecnode_count; m++)
	{
		/* make sure this is the right type of node for this pass of the nodes */
		np = sequence[m];
		nfunction = NPUNKNOWN;
		for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		{
			/* get the node function */
			if (us_tecedgetoption(ni) != NODEFUNCTION) continue;
			var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, art_messagekey);
			if (var == NOVARIABLE) continue;
			str = &((char *)var->addr)[10];
			for(j=0; j<MAXNODEFUNCTION; j++)
				if (namesame(str, nodefunctionname(j, NONODEINST)) == 0) break;
			if (j < MAXNODEFUNCTION)
			{
				nfunction = j;
				break;
			}
		}

		/* only want pins on pass 0, pure-layer nodes on pass 2 */
		if (pass == 0 && nfunction != NPPIN) continue;
		if (pass == 1 && (nfunction == NPPIN || nfunction == NPNODE)) continue;
		if (pass == 2 && nfunction != NPNODE) continue;

		/* build a list of examples found in this node */
		nelist = us_tecedgetexamples(np, TRUE);
		if (nelist == NOEXAMPLE) return(TRUE);

		/* associate the samples in each example */
		if (us_tecedassociateexamples(nelist, np))
		{
			us_tecedfreeexamples(nelist);
			return(TRUE);
		}

		/* derive primitives from the examples */
		if (us_tecedmakeprim(nelist, np, tech))
		{
			us_tecedfreeexamples(nelist);
			return(TRUE);
		}

		/* allocate and fill the TECH_NODES structure */
		tlist = (TECH_NODES *)emalloc(sizeof (TECH_NODES), tech->cluster);
		if (tlist == 0) return(TRUE);
		tech->nodeprotos[nodeindex] = tlist;
		(void)allocstring(&tlist->nodename, &np->cell->cellname[5], tech->cluster);
		tlist->nodeindex = nodeindex + 1;
		tlist->creation = NONODEPROTO;
		tlist->xsize = (nelist->hx-nelist->lx)*WHOLE/lambda;
		tlist->ysize = (nelist->hy-nelist->ly)*WHOLE/lambda;
		tlist->layerlist = 0;
		tlist->layercount = 0;
		tlist->special = 0;
		tlist->f1 = 0;
		tlist->f2 = 0;
		tlist->f3 = 0;
		tlist->f4 = 0;
		tlist->f5 = 0;
		tlist->f6 = 0;
		tlist->gra = 0;
		tlist->ele = 0;

		/* determine user bits */
		tlist->initialbits = nfunction<<NFUNCTIONSH;
		for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		{
			opt = us_tecedgetoption(ni);

			/* pick up square node information */
			if (opt == NODESQUARE)
			{
				var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, art_messagekey);
				if (var == NOVARIABLE) continue;
				str = &((char *)var->addr)[13];
				if (namesame(str, "yes") == 0) tlist->initialbits |= NSQUARE;
				continue;
			}

			/* pick up invisible on 1 or 2 arc information */
			if (opt == NODEWIPES)
			{
				var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, art_messagekey);
				if (var == NOVARIABLE) continue;
				str = &((char *)var->addr)[28];
				if (namesame(str, "yes") == 0)
					tlist->initialbits = WIPEON1OR2 | (tlist->initialbits & ~(ARCSWIPE|ARCSHRINK));
				continue;
			}

			/* pick up lockable information */
			if (opt == NODELOCKABLE)
			{
				var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, art_messagekey);
				if (var == NOVARIABLE) continue;
				str = &((char *)var->addr)[10];
				if (namesame(str, "yes") == 0) tlist->initialbits |= LOCKEDPRIM;
				continue;
			}

			/* pick up serpentine transistor information */
			if (opt == NODESERPENTINE)
			{
				var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, art_messagekey);
				if (var == NOVARIABLE) continue;
				str = &((char *)var->addr)[23];
				if (namesame(str, "yes") == 0)
				{
					if (tlist->special != 0)
					{
						us_tecedfreeexamples(nelist);
						us_tecedpointout(ni, np);
						ttyputerr(_("Serpentine %s must have Transistor function"),
							describenodeproto(np));
						return(TRUE);
					}
					tlist->special = SERPTRANS;
					tlist->initialbits |= (NODESHRINK | HOLDSTRACE);
				}
				continue;
			}
		}

		/* analyze special node function circumstances */
		switch (nfunction)
		{
			case NPNODE:
				if (tlist->special != 0)
				{
					us_tecedfreeexamples(nelist);
					us_tecedpointout(NONODEINST, np);
					ttyputerr(_("Pure layer %s can not be serpentine"), describenodeproto(np));
					return(TRUE);
				}
				tlist->special = POLYGONAL;
				tlist->initialbits |= HOLDSTRACE;
				break;
			case NPPIN:
				if ((tlist->initialbits&WIPEON1OR2) == 0)
				{
					tlist->initialbits |= ARCSWIPE;
					tlist->initialbits |= ARCSHRINK;
				}
				break;
		}

		/* count the number of ports on this node */
		tlist->portcount = 0;
		for(ns = nelist->firstsample; ns != NOSAMPLE; ns = ns->nextsample)
			if (ns->layer == gen_portprim) tlist->portcount++;
		if (tlist->portcount == 0)
		{
			us_tecedfreeexamples(nelist);
			us_tecedpointout(NONODEINST, np);
			ttyputerr(_("No ports found in %s"), describenodeproto(np));
			if ((us_tool->toolstate&NODETAILS) == 0)
				ttyputmsg(_("Use 'place-layer port' option to create one"));
			return(TRUE);
		}

		/* allocate space for the ports */
		tlist->portlist = (TECH_PORTS *)emalloc((tlist->portcount * (sizeof (TECH_PORTS))),
			tech->cluster);
		if (tlist->portlist == 0) return(TRUE);

		/* fill the port structures */
		pol1port = pol2port = dif1port = dif2port = -1;
		i = 0;
		for(ns = nelist->firstsample; ns != NOSAMPLE; ns = ns->nextsample)
		{
			if (ns->layer != gen_portprim) continue;

			/* port connections */
			var = getval((INTBIG)ns->node, VNODEINST, VNODEPROTO|VISARRAY, "EDTEC_connects");
			if (var == NOVARIABLE) pc = us_tecedaddportlist(0, (INTBIG *)0); else
			{
				/* convert "arc-FACET" pointers to indices */
				l = getlength(var);
				list = emalloc((l * SIZEOFINTBIG), el_tempcluster);
				if (list == 0) return(TRUE);
				portchecked = 0;
				for(j=0; j<l; j++)
				{
					/* find arc that connects */
					for(k=0; k<tech->arcprotocount; k++)
						if (namesame(tech->arcprotos[k]->arcname,
							&((NODEPROTO **)var->addr)[j]->cell->cellname[4]) == 0) break;
					if (k >= tech->arcprotocount)
					{
						us_tecedfreeexamples(nelist);
						us_tecedpointout(ns->node, ns->node->parent);
						ttyputerr(_("Invalid connection list on port in %s"), describenodeproto(np));
						if ((us_tool->toolstate&NODETAILS) == 0)
							ttyputmsg(_("Use 'change' option to remove arc %s"),
								&((NODEPROTO **)var->addr)[j]->cell->cellname[4]);
						return(TRUE);
					}

					/* fill in the connection information */
					list[j] = k;

					/* find port characteristics for possible transistors */
					if (portchecked != 0) continue;
					switch ((tech->arcprotos[k]->initialbits&AFUNCTION)>>AFUNCTIONSH)
					{
						case APPOLY1:   case APPOLY2:
							if (pol1port < 0)
							{
								pol1port = i;
								portchecked++;
							} else if (pol2port < 0)
							{
								pol2port = i;
								portchecked++;
							}
							break;
						case APDIFF:    case APDIFFP:   case APDIFFN:
						case APDIFFS:   case APDIFFW:
							if (dif1port < 0)
							{
								dif1port = i;
								portchecked++;
							} else if (dif2port < 0)
							{
								dif2port = i;
								portchecked++;
							}
							break;
					}
				}

				/* store complete list in memory */
				pc = us_tecedaddportlist(l, list);
				efree((char *)list);
			}

			/* link connection list to the port */
			if (pc == NOPCON) return(TRUE);
			tlist->portlist[i].portarcs = pc->connects;

			/* port name */
			var = getval((INTBIG)ns->node, VNODEINST, VSTRING, "EDTEC_portname");
			if (var == NOVARIABLE) continue;
			for(str = (char *)var->addr; *str != 0; str++)
				if (*str <= ' ' || *str >= 0177)
			{
				us_tecedfreeexamples(nelist);
				us_tecedpointout(ns->node, np);
				ttyputerr(_("Invalid port name: '%s'"), (char *)var->addr);
				return(TRUE);
			}
			(void)allocstring(&tlist->portlist[i].protoname, (char *)var->addr, tech->cluster);

			/* port angle and range */
			tlist->portlist[i].initialbits = 0;
			var = getval((INTBIG)ns->node, VNODEINST, VINTEGER, "EDTEC_portangle");
			if (var != NOVARIABLE)
				tlist->portlist[i].initialbits |= var->addr << PORTANGLESH;
			var = getval((INTBIG)ns->node, VNODEINST, VINTEGER, "EDTEC_portrange");
			if (var != NOVARIABLE)
				tlist->portlist[i].initialbits |= var->addr << PORTARANGESH; else
					tlist->portlist[i].initialbits |= 180 << PORTARANGESH;

			/* port connectivity */
			net = i;
			if (ns->node->firstportarcinst != NOPORTARCINST)
			{
				j = 0;
				for(ons = nelist->firstsample; ons != ns; ons = ons->nextsample)
				{
					if (ons->layer != gen_portprim) continue;
					if (ons->node->firstportarcinst != NOPORTARCINST)
					{
						if (ns->node->firstportarcinst->conarcinst->network ==
							ons->node->firstportarcinst->conarcinst->network)
						{
							net = j;
							break;
						}
					}
					j++;
				}
			}
			tlist->portlist[i].initialbits |= (net << PORTNETSH);

			/* port area rule */
			r = ns->rule;
			if (r == NORULE) continue;
			tlist->portlist[i].lowxmul = (INTSML)r->value[0];
			tlist->portlist[i].lowxsum = (INTSML)r->value[1];
			tlist->portlist[i].lowymul = (INTSML)r->value[2];
			tlist->portlist[i].lowysum = (INTSML)r->value[3];
			tlist->portlist[i].highxmul = (INTSML)r->value[4];
			tlist->portlist[i].highxsum = (INTSML)r->value[5];
			tlist->portlist[i].highymul = (INTSML)r->value[6];
			tlist->portlist[i].highysum = (INTSML)r->value[7];
			i++;
		}

		/* on FET transistors, make sure ports 0 and 2 are poly */
		if (nfunction == NPTRANMOS || nfunction == NPTRADMOS || nfunction == NPTRAPMOS ||
			nfunction == NPTRADMES || nfunction == NPTRAEMES)
		{
			if (pol1port < 0 || pol2port < 0 || dif1port < 0 || dif2port < 0)
			{
				us_tecedfreeexamples(nelist);
				us_tecedpointout(NONODEINST, np);
				ttyputerr(_("Need 2 gate and 2 active ports on field-effect transistor %s"),
					describenodeproto(np));
				return(TRUE);
			}
			if (pol1port != 0)
			{
				if (pol2port == 0) us_tecedswapports(&pol1port, &pol2port, tlist); else
				if (dif1port == 0) us_tecedswapports(&pol1port, &dif1port, tlist); else
				if (dif2port == 0) us_tecedswapports(&pol1port, &dif2port, tlist);
			}
			if (pol2port != 2)
			{
				if (dif1port == 2) us_tecedswapports(&pol2port, &dif1port, tlist); else
				if (dif2port == 2) us_tecedswapports(&pol2port, &dif2port, tlist);
			}
			if (dif1port != 1) us_tecedswapports(&dif1port, &dif2port, tlist);

			/* also make sure that dif1port is positive and dif2port is negative */
			x1pos = (tlist->portlist[dif1port].lowxmul*tlist->xsize +
				tlist->portlist[dif1port].lowxsum +
					tlist->portlist[dif1port].highxmul*tlist->xsize +
						tlist->portlist[dif1port].highxsum) / 2;
			x2pos = (tlist->portlist[dif2port].lowxmul*tlist->xsize +
				tlist->portlist[dif2port].lowxsum +
					tlist->portlist[dif2port].highxmul*tlist->xsize +
						tlist->portlist[dif2port].highxsum) / 2;
			y1pos = (tlist->portlist[dif1port].lowymul*tlist->ysize +
				tlist->portlist[dif1port].lowysum +
					tlist->portlist[dif1port].highymul*tlist->ysize +
						tlist->portlist[dif1port].highysum) / 2;
			y2pos = (tlist->portlist[dif2port].lowymul*tlist->ysize +
				tlist->portlist[dif2port].lowysum +
					tlist->portlist[dif2port].highymul*tlist->ysize +
						tlist->portlist[dif2port].highysum) / 2;
			if (abs(x1pos-x2pos) > abs(y1pos-y2pos))
			{
				if (x1pos < x2pos)
				{
					us_tecedswapports(&dif1port, &dif2port, tlist);
					j = dif1port;   dif1port = dif2port;   dif2port = j;
				}
			} else
			{
				if (y1pos < y2pos)
				{
					us_tecedswapports(&dif1port, &dif2port, tlist);
					j = dif1port;   dif1port = dif2port;   dif2port = j;
				}
			}

			/* also make sure that pol1port is negative and pol2port is positive */
			x1pos = (tlist->portlist[pol1port].lowxmul*tlist->xsize +
				tlist->portlist[pol1port].lowxsum +
					tlist->portlist[pol1port].highxmul*tlist->xsize +
						tlist->portlist[pol1port].highxsum) / 2;
			x2pos = (tlist->portlist[pol2port].lowxmul*tlist->xsize +
				tlist->portlist[pol2port].lowxsum +
					tlist->portlist[pol2port].highxmul*tlist->xsize +
						tlist->portlist[pol2port].highxsum) / 2;
			y1pos = (tlist->portlist[pol1port].lowymul*tlist->ysize +
				tlist->portlist[pol1port].lowysum +
					tlist->portlist[pol1port].highymul*tlist->ysize +
						tlist->portlist[pol1port].highysum) / 2;
			y2pos = (tlist->portlist[pol2port].lowymul*tlist->ysize +
				tlist->portlist[pol2port].lowysum +
					tlist->portlist[pol2port].highymul*tlist->ysize +
						tlist->portlist[pol2port].highysum) / 2;
			if (abs(x1pos-x2pos) > abs(y1pos-y2pos))
			{
				if (x1pos > x2pos)
				{
					us_tecedswapports(&pol1port, &pol2port, tlist);
					j = pol1port;   pol1port = pol2port;   pol2port = j;
				}
			} else
			{
				if (y1pos > y2pos)
				{
					us_tecedswapports(&pol1port, &pol2port, tlist);
					j = pol1port;   pol1port = pol2port;   pol2port = j;
				}
			}
		}

		/* count the number of layers on the node */
		tlist->layercount = 0;
		for(ns = nelist->firstsample; ns != NOSAMPLE; ns = ns->nextsample)
		{
			if (ns->rule != NORULE && ns->layer != gen_portprim &&
				ns->layer != gen_facetcenterprim && ns->layer != NONODEPROTO)
					tlist->layercount++;
		}

		/* allocate space for the layers */
		if (tlist->special != SERPTRANS)
		{
			tlist->layerlist = (TECH_POLYGON *)emalloc((tlist->layercount *
				(sizeof (TECH_POLYGON))), tech->cluster);
			if (tlist->layerlist == 0) return(TRUE);
		} else
		{
			tlist->gra = (TECH_SERPENT *)emalloc(((sizeof (TECH_SERPENT)) * tlist->layercount),
				tech->cluster);
			if (tlist->gra == 0) return(TRUE);
			tlist->ele = (TECH_SERPENT *)emalloc(((sizeof (TECH_SERPENT)) * (tlist->layercount+1)),
				tech->cluster);
			if (tlist->ele == 0) return(TRUE);
		}

		/* fill the layer structures (3 times: overlappable, opaque, multicut) */
		i = 0;
		pollayer = diflayer = NOSAMPLE;
		for(k=0; k<3; k++)
			for(nsindex=0, ns = nelist->firstsample; ns != NOSAMPLE; nsindex++, ns = ns->nextsample)
		{
			r = ns->rule;
			if (r == NORULE || ns->layer == gen_portprim ||
				ns->layer == gen_facetcenterprim || ns->layer == NONODEPROTO) continue;

			/* add cut layers last (only when k=2) */
			if (k == 2)
			{
				if (!r->multicut) continue;
				if (tlist->special != 0)
				{
					us_tecedfreeexamples(nelist);
					us_tecedpointout(ns->node, ns->node->parent);
					ttyputerr(_("%s is too complex (multiple cuts AND serpentine)"),
						describenodeproto(np));
					return(TRUE);
				}
				tlist->special = MULTICUT;
				tlist->f1 = (INTSML)(r->multixs*WHOLE/lambda);
				tlist->f2 = (INTSML)(r->multiys*WHOLE/lambda);
				tlist->f3 = (INTSML)(r->multiindent*WHOLE/lambda);
				tlist->f4 = (INTSML)(r->multisep*WHOLE/lambda);
			} else
			{
				if (r->multicut) continue;
			}

			/* layer number */
			for(j=0; j<tech->layercount; j++)
				if (namesame(&ns->layer->cell->cellname[6], us_teclayer_names[j]) == 0) break;
			if (j >= tech->layercount)
			{
				ttyputerr(_("Cannot find layer %s"), describenodeproto(ns->layer));
				return(TRUE);
			}

			/* only add overlappable layers when k=0 */
			if (k == 0)
			{
				if (tech->layers[j]->bits == LAYERO) continue;
			} else if (k == 1)
			{
				if (tech->layers[j]->bits != LAYERO) continue;
			}

			/* layer style */
			sty = -1;
			if (ns->node->proto == art_filledboxprim)             sty = FILLEDRECT; else
			if (ns->node->proto == art_boxprim)                   sty = CLOSEDRECT; else
			if (ns->node->proto == art_crossedboxprim)            sty = CROSSED; else
			if (ns->node->proto == art_filledpolygonprim)         sty = FILLED; else
			if (ns->node->proto == art_closedpolygonprim)         sty = CLOSED; else
			if (ns->node->proto == art_openedpolygonprim)         sty = OPENED; else
			if (ns->node->proto == art_openeddottedpolygonprim)   sty = OPENEDT1; else
			if (ns->node->proto == art_openeddashedpolygonprim)   sty = OPENEDT2; else
			if (ns->node->proto == art_openedthickerpolygonprim)  sty = OPENEDT3; else
			if (ns->node->proto == art_filledcircleprim)          sty = DISC; else
			if (ns->node->proto == art_circleprim)
			{
				var = getvalkey((INTBIG)ns->node, VNODEINST, VINTEGER, art_degreeskey);
				if (var != NOVARIABLE) sty = CIRCLEARC; else sty = CIRCLE;
			} else if (ns->node->proto == art_thickcircleprim)
			{
				var = getvalkey((INTBIG)ns->node, VNODEINST, VINTEGER, art_degreeskey);
				if (var != NOVARIABLE) sty = THICKCIRCLEARC; else sty = THICKCIRCLE;
			} else if (ns->node->proto == gen_invispinprim)
			{
				var = getvalkey((INTBIG)ns->node, VNODEINST, VSTRING|VISARRAY, art_messagekey);
				if (var != NOVARIABLE)
				{
					switch (TDGETPOS(var->textdescript))
					{
						case VTPOSBOXED:     sty = TEXTBOX;       break;
						case VTPOSCENT:      sty = TEXTCENT;      break;
						case VTPOSUP:        sty = TEXTBOT;       break;
						case VTPOSDOWN:      sty = TEXTTOP;       break;
						case VTPOSLEFT:      sty = TEXTRIGHT;     break;
						case VTPOSRIGHT:     sty = TEXTLEFT;      break;
						case VTPOSUPLEFT:    sty = TEXTBOTRIGHT;  break;
						case VTPOSUPRIGHT:   sty = TEXTBOTLEFT;   break;
						case VTPOSDOWNLEFT:  sty = TEXTTOPRIGHT;  break;
						case VTPOSDOWNRIGHT: sty = TEXTTOPLEFT;   break;
					}
				}
			}
			if (sty == -1)
				ttyputmsg(_("Cannot determine style to use for %s node in %s"),
					describenodeproto(ns->node->proto), describenodeproto(np));

			/* load the layer structure(s) */
			if (tlist->special == SERPTRANS)
			{
				/* determine port numbers for serpentine transistors */
				if (layerismetal(us_teclayer_function[j]))
				{
					tlist->gra[i].basics.portnum = 0;
				} else if (layerispoly(us_teclayer_function[j]))
				{
					pollayer = ns;
					if (pol1port >= 0)
						tlist->gra[i].basics.portnum = (INTSML)pol1port; else
							tlist->gra[i].basics.portnum = 0;
					polindex = i;
				} else if ((us_teclayer_function[j]&LFTYPE) == LFDIFF)
				{
					diflayer = ns;
					difindex = i;
					tlist->gra[i].basics.portnum = 0;
				} else
				{
					tlist->gra[i].basics.portnum = -1;
				}

				tlist->gra[i].basics.layernum = (INTSML)j;
				tlist->gra[i].basics.count = r->count/4;
				tlist->gra[i].basics.style = (INTSML)sty;
				if (tlist->gra[i].basics.count == 2 && (sty == CROSSED ||
					sty == FILLEDRECT || sty == FILLED || sty == CLOSEDRECT || sty == CLOSED))
				{
					tlist->gra[i].basics.representation = BOX;
					tlist->gra[i].basics.count = 4;
				} else tlist->gra[i].basics.representation = POINTS;
				tlist->gra[i].basics.points = r->value;
				tlist->gra[i].lwidth = (INTSML)nsindex;
				tlist->gra[i].rwidth = 0;
				tlist->gra[i].extend = 0;
			} else
			{
				tlist->layerlist[i].portnum = (INTSML)us_tecedfindport(tlist, nelist,
					ns->node->lowx, ns->node->highx, ns->node->lowy, ns->node->highy,
						lambdaofnode(ns->node));
				tlist->layerlist[i].layernum = (INTSML)j;
				tlist->layerlist[i].count = r->count/4;
				tlist->layerlist[i].style = (INTSML)sty;
				tlist->layerlist[i].representation = POINTS;
				if (sty == CROSSED || sty == FILLEDRECT || sty == FILLED || sty == CLOSEDRECT ||
					sty == CLOSED)
				{
					if (r->count == 8)
					{
						tlist->layerlist[i].representation = BOX;
						tlist->layerlist[i].count = 4;
					} else if (r->count == 16)
					{
						tlist->layerlist[i].representation = MINBOX;
						tlist->layerlist[i].count = 4;
					}
				}
				tlist->layerlist[i].points = r->value;
			}

			/* mark this rectangle rule "used" */
			r->used = TRUE;
			i++;
		}

		/* finish up serpentine transistors */
		if (tlist->special == SERPTRANS)
		{
			if (diflayer == NOSAMPLE || pollayer == NOSAMPLE || dif1port < 0)
			{
				us_tecedfreeexamples(nelist);
				us_tecedpointout(NONODEINST, np);
				ttyputerr(_("No diffusion and polysilicon layers in transistor %s"),
					describenodeproto(np));
				return(TRUE);
			}

			/* compute port extension factors */
			tlist->f1 = tlist->layercount+1;
			if (tlist->portlist[dif1port].lowxsum >
				tlist->portlist[dif1port].lowysum)
			{
				/* vertical diffusion layer: determine polysilicon width */
				tlist->f4 = (INTSML)(tlist->ysize - (tlist->gra[polindex].basics.points[3] -
					tlist->gra[polindex].basics.points[7]));

				/* determine diffusion port rule */
				tlist->f2 = (INTSML)(tlist->portlist[dif1port].lowxsum -
					tlist->gra[difindex].basics.points[1]);
				tlist->f3 = (INTSML)(tlist->gra[polindex].basics.points[3] -
					abs(tlist->portlist[dif1port].lowysum));

				/* determine polysilicon port rule */
				tlist->f5 = (INTSML)(tlist->portlist[pol1port].lowysum -
					tlist->gra[polindex].basics.points[3]);
				tlist->f6 = (INTSML)(tlist->gra[difindex].basics.points[1] -
					abs(tlist->portlist[pol1port].lowxsum));
			} else
			{
				/* horizontal diffusion layer: determine polysilicon width */
				tlist->f4 = (INTSML)(tlist->xsize - (tlist->gra[polindex].basics.points[1] -
					tlist->gra[polindex].basics.points[5]));

				/* determine diffusion port rule */
				tlist->f2 = (INTSML)(tlist->portlist[dif1port].lowysum -
					tlist->gra[difindex].basics.points[3]);
				tlist->f3 = (INTSML)(tlist->gra[polindex].basics.points[1] -
					abs(tlist->portlist[dif1port].lowxsum));

				/* determine polysilicon port rule */
				tlist->f5 = (INTSML)(tlist->portlist[pol1port].lowxsum -
					tlist->gra[polindex].basics.points[1]);
				tlist->f6 = (INTSML)(tlist->gra[difindex].basics.points[3] -
					abs(tlist->portlist[pol1port].lowysum));
			}

			/* find width and extension from comparison to poly layer */
			for(i=0; i<tlist->layercount; i++)
			{
				for(nsindex=0, ns = nelist->firstsample; ns != NOSAMPLE;
					nsindex++, ns = ns->nextsample)
						if (tlist->gra[i].lwidth == nsindex) break;
				if (ns == NOSAMPLE)
				{
					us_tecedfreeexamples(nelist);
					us_tecedpointout(NONODEINST, np);
					ttyputerr(_("Internal error in serpentine %s"), describenodeproto(np));
					continue;
				}

				if (pollayer->node->highx-pollayer->node->lowx >
					pollayer->node->highy-pollayer->node->lowy)
				{
					/* horizontal layer */
					tlist->gra[i].lwidth = (INTSML)((ns->node->highy - (ns->parent->ly + ns->parent->hy)/2) *
						WHOLE/lambda);
					tlist->gra[i].rwidth = (INTSML)(((ns->parent->ly + ns->parent->hy)/2 - ns->node->lowy) *
						WHOLE/lambda);
					tlist->gra[i].extend = (INTSML)((diflayer->node->lowx - ns->node->lowx) * WHOLE /
						lambda);
				} else
				{
					/* vertical layer */
					tlist->gra[i].lwidth = (INTSML)((ns->node->highx - (ns->parent->lx + ns->parent->hx)/2) *
						WHOLE/lambda);
					tlist->gra[i].rwidth = (INTSML)(((ns->parent->lx + ns->parent->hx)/2 - ns->node->lowx) *
						WHOLE/lambda);
					tlist->gra[i].extend = (INTSML)((diflayer->node->lowy - ns->node->lowy) * WHOLE /
						lambda);
				}
			}

			/* copy basic graphics to electrical version, doubling diffusion */
			i = 0;
			for(j=0; j<tlist->layercount; j++)
			{
				if (j != difindex) k = 1; else
				{
					k = 2;

					/* copy rectangle rule and prepare for electrical layers */
					r = diflayer->rule;
					if (r->count != 8)
					{
						us_tecedfreeexamples(nelist);
						us_tecedpointout(NONODEINST, np);
						ttyputerr(_("Nonrectangular diffusion in Serpentine %s"),
							describenodeproto(np));
						return(TRUE);
					}
					for(l=0; l<r->count; l++) serprule[l] = r->value[l];
					if (serprule[0] != -H0 || serprule[2] != -H0 ||
						serprule[4] != H0 || serprule[6] != H0)
					{
						us_tecedfreeexamples(nelist);
						us_tecedpointout(NONODEINST, np);
						ttyputerr(_("Unusual diffusion in Serpentine %s"), describenodeproto(np));
						return(TRUE);
					}
					if (tlist->xsize - serprule[1] + serprule[5] <
						tlist->ysize - serprule[3] + serprule[7]) serpdifind = 2; else
							serpdifind = 0;
				}
				for(l=0; l<k; l++)
				{
					tlist->ele[i].basics.layernum = tlist->gra[j].basics.layernum;
					tlist->ele[i].basics.count = tlist->gra[j].basics.count;
					tlist->ele[i].basics.style = tlist->gra[j].basics.style;
					tlist->ele[i].basics.representation = tlist->gra[j].basics.representation;
					tlist->ele[i].basics.points = tlist->gra[j].basics.points;
					tlist->ele[i].lwidth = tlist->gra[j].lwidth;
					tlist->ele[i].rwidth = tlist->gra[j].rwidth;
					tlist->ele[i].extend = tlist->gra[j].extend;
					if (k == 1) tlist->ele[i].basics.portnum = tlist->gra[j].basics.portnum; else
						switch (l)
					{
						case 0:
							tlist->ele[i].basics.portnum = (INTSML)dif1port;
							tlist->ele[i].rwidth = -tlist->gra[polindex].lwidth;
							save1 = serprule[serpdifind+1];
							serprule[serpdifind] = H0;
							/* serprule[serpdifind+1] = pollayer->rule->value[serpdifind+5]; */
							serprule[serpdifind+1] = 0;
							r = us_tecedaddrule(serprule, 8, FALSE, (char *)0);
							if (r == NORULE) return(TRUE);
							r->used = TRUE;
							tlist->ele[i].basics.points = r->value;
							serprule[serpdifind] = -H0;
							serprule[serpdifind+1] = save1;
							break;
						case 1:
							tlist->ele[i].basics.portnum = (INTSML)dif2port;
							tlist->ele[i].lwidth = -tlist->gra[polindex].rwidth;
							save1 = serprule[serpdifind+5];
							serprule[serpdifind+4] = -H0;
							serprule[serpdifind+5] = 0;
							/* serprule[serpdifind+5] = pollayer->rule->value[serpdifind+1]; */
							r = us_tecedaddrule(serprule, 8, FALSE, (char *)0);
							if (r == NORULE) return(TRUE);
							r->used = TRUE;
							tlist->ele[i].basics.points = r->value;
							serprule[serpdifind+4] = H0;
							serprule[serpdifind+5] = save1;
							break;
					}
					i++;
				}
			}
		}

		/* extract width offset information */
		us_tecnode_widoff[nodeindex*4] = 0;
		us_tecnode_widoff[nodeindex*4+1] = 0;
		us_tecnode_widoff[nodeindex*4+2] = 0;
		us_tecnode_widoff[nodeindex*4+3] = 0;
		for(ns = nelist->firstsample; ns != NOSAMPLE; ns = ns->nextsample)
			if (ns->layer == NONODEPROTO) break;
		if (ns != NOSAMPLE)
		{
			r = ns->rule;
			if (r != NORULE)
			{
				err = 0;
				switch (r->value[0])		/* left edge offset */
				{
					case -H0:
						us_tecnode_widoff[nodeindex*4] = r->value[1];
						break;
					case H0:
						us_tecnode_widoff[nodeindex*4] = tlist->xsize + r->value[1];
						break;
					default:
						err++;
						break;
				}
				switch (r->value[2])		/* bottom edge offset */
				{
					case -H0:
						us_tecnode_widoff[nodeindex*4+2] = r->value[3];
						break;
					case H0:
						us_tecnode_widoff[nodeindex*4+2] = tlist->ysize + r->value[3];
						break;
					default:
						err++;
						break;
				}
				switch (r->value[4])		/* right edge offset */
				{
					case H0:
						us_tecnode_widoff[nodeindex*4+1] = -r->value[5];
						break;
					case -H0:
						us_tecnode_widoff[nodeindex*4+1] = tlist->xsize - r->value[5];
						break;
					default:
						err++;
						break;
				}
				switch (r->value[6])		/* top edge offset */
				{
					case H0:
						us_tecnode_widoff[nodeindex*4+3] = -r->value[7];
						break;
					case -H0:
						us_tecnode_widoff[nodeindex*4+3] = tlist->ysize - r->value[7];
						break;
					default:
						err++;
						break;
				}
				if (err != 0)
				{
					us_tecedfreeexamples(nelist);
					us_tecedpointout(ns->node, ns->node->parent);
					ttyputmsg(_("Highlighting cannot scale from center in %s"), describenodeproto(np));
					return(TRUE);
				}
			} else
			{
				us_tecedfreeexamples(nelist);
				us_tecedpointout(ns->node, ns->node->parent);
				ttyputerr(_("No rule found for highlight in %s"), describenodeproto(np));
				return(TRUE);
			}
		} else
		{
			us_tecedfreeexamples(nelist);
			us_tecedpointout(NONODEINST, np);
			ttyputerr(_("No highlight found in %s"), describenodeproto(np));
			if ((us_tool->toolstate&NODETAILS) == 0)
				ttyputmsg(_("Use 'place-layer' option to create HIGHLIGHT"));
			return(TRUE);
		}

		/* get grab point information */
		for(ns = nelist->firstsample; ns != NOSAMPLE; ns = ns->nextsample)
			if (ns->layer == gen_facetcenterprim) break;
		if (ns != NOSAMPLE)
		{
			us_tecnode_grab[us_tecnode_grabcount++] = nodeindex+1;
			us_tecnode_grab[us_tecnode_grabcount++] = (ns->node->geom->lowx +
				ns->node->geom->highx - nelist->lx - nelist->hx)/2 *
				el_curlib->lambda[tech->techindex] / lambda;
			us_tecnode_grab[us_tecnode_grabcount++] = (ns->node->geom->lowy +
				ns->node->geom->highy - nelist->ly - nelist->hy)/2 *
				el_curlib->lambda[tech->techindex] / lambda;
			us_tecflags |= HASGRAB;
		}

		/* free all examples */
		us_tecedfreeexamples(nelist);

		/* advance the fill pointer */
		nodeindex++;
	}

	/* store width offset on the technology */
	(void)setval((INTBIG)tech, VTECHNOLOGY, "TECH_node_width_offset", (INTBIG)us_tecnode_widoff,
		VFRACT|VDONTSAVE|VISARRAY|((us_tecnode_count*4)<<VLENGTHSH));
	efree((char *)sequence);
	return(FALSE);
}

/*
 * routine to find the closest port to the layer describe by "lx<=X<=hx" and
 * "ly<+Y<=hy" in the list "nelist".  The ports are listed in "tlist".  The algorithm
 * is to find a port that overlaps this layer.  If there is only one, or if all of
 * them electrically connect, use that.  If there are no such ports, or multiple
 * unconnected ports, presume that the layer is not related to any port.
 */
INTBIG us_tecedfindport(TECH_NODES *tlist, EXAMPLE *nelist, INTBIG lx, INTBIG hx, INTBIG ly,
	INTBIG hy, INTBIG lambda)
{
	REGISTER INTBIG bestport, l, oldnet, newnet;
	INTBIG portlx, porthx, portly, porthy;
	REGISTER INTBIG swap;

	bestport = -1;
	for(l=0; l<tlist->portcount; l++)
	{
		subrange(nelist->lx, nelist->hx, tlist->portlist[l].lowxmul,
			tlist->portlist[l].lowxsum, tlist->portlist[l].highxmul,
				tlist->portlist[l].highxsum, &portlx, &porthx, lambda);
		if (portlx > porthx)
		{
			swap = portlx;   portlx = porthx;   porthx = swap;
		}
		subrange(nelist->ly, nelist->hy, tlist->portlist[l].lowymul,
			tlist->portlist[l].lowysum, tlist->portlist[l].highymul,
				tlist->portlist[l].highysum, &portly, &porthy, lambda);
		if (portlx > porthx)
		{
			swap = portly;   portly = porthy;   porthy = swap;
		}

		/* ignore the port if there is no intersection */
		if (lx > porthx || hx < portlx || ly > porthy || hy < portly) continue;

		/* if there is no previous overlapping port, use this */
		if (bestport == -1)
		{
			bestport = l;
			continue;
		}

		/* if these two ports connect, all is well */
		newnet = (tlist->portlist[l].initialbits & PORTNET) >> PORTNETSH;
		oldnet = (tlist->portlist[bestport].initialbits & PORTNET) >> PORTNETSH;
		if (newnet == oldnet) continue;

		/* two unconnected ports intersect layer: make it free */
		return(-1);
	}
	return(bestport);
}

/*
 * Routine to free the examples created by "us_tecedgetexamples()".
 */
void us_tecedfreeexamples(EXAMPLE *nelist)
{
	REGISTER EXAMPLE *ne;
	REGISTER SAMPLE *ns;

	while (nelist != NOEXAMPLE)
	{
		ne = nelist;
		nelist = nelist->nextexample;
		while (ne->firstsample != NOSAMPLE)
		{
			ns = ne->firstsample;
			ne->firstsample = ne->firstsample->nextsample;
			efree((char *)ns);
		}
		efree((char *)ne);
	}
}

/*
 * routine to parse the node examples in facet "np" and return a list of
 * EXAMPLEs (one per example).  "isnode" is true if this is a node
 * being examined.  Returns NOEXAMPLE on error.
 */
EXAMPLE *us_tecedgetexamples(NODEPROTO *np, BOOLEAN isnode)
{
	REGISTER SAMPLE *ns;
	REGISTER EXAMPLE *ne, *nelist, *bestne;
	REGISTER NODEINST *ni, *otherni;
	REGISTER INTBIG sea, sizex, sizey, newsize, locx, locy, lambda, hcount, funct;
	REGISTER BOOLEAN foundone, gotbbox;
	INTBIG lx, hx, ly, hy, sflx, sfhx, sfly, sfhy;
	REGISTER GEOM *geom;
	REGISTER VARIABLE *var;

	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		ni->temp1 = (INTBIG)NOEXAMPLE;

		/* ignore special nodes with function information */
		funct = us_tecedgetoption(ni);
		if (funct != LAYERPATCH && funct != PORTOBJ && funct != HIGHLIGHTOBJ &&
			funct != CENTEROBJ) ni->temp1 = 0;
	}

	nelist = NOEXAMPLE;
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->temp1 != (INTBIG)NOEXAMPLE) continue;

		/* get a new cluster of nodes */
		ne = (EXAMPLE *)emalloc((sizeof (EXAMPLE)), us_tool->cluster);
		if (ne == 0) return(NOEXAMPLE);
		ne->firstsample = NOSAMPLE;
		gotbbox = FALSE;
		us_tecedgetbbox(ni, &sflx, &sfhx, &sfly, &sfhy);
		ne->nextexample = nelist;
		nelist = ne;

		/* now find all others that touch this area */
		foundone = TRUE;
		hcount = 0;
		while (foundone)
		{
			foundone = FALSE;

			/* begin to search the area so far */
			sea = initsearch(sflx, sfhx, sfly, sfhy, np);
			if (sea == -1) return(NOEXAMPLE);
			for(;;)
			{
				/* get next node in the area */
				geom = nextobject(sea);
				if (geom == NOGEOM) break;
				if (!geom->entryisnode) continue;
				otherni = geom->entryaddr.ni;

				/* make sure the node is valid */
				if (otherni->temp1 != (INTBIG)NOEXAMPLE)
				{
					if (otherni->temp1 == (INTBIG)ne) continue;
					us_tecedpointout(otherni, np);
					ttyputerr(_("Examples are too close in %s"), describenodeproto(np));
					termsearch(sea);
					return(NOEXAMPLE);
				}
				otherni->temp1 = (INTBIG)ne;

				/* add it to the cluster */
				ns = (SAMPLE *)emalloc((sizeof (SAMPLE)), us_tool->cluster);
				if (ns == 0) return(NOEXAMPLE);
				ns->node = otherni;
				ns->rule = NORULE;
				ns->parent = ne;
				ns->nextsample = ne->firstsample;
				ne->firstsample = ns;
				ns->assoc = NOSAMPLE;
				ns->xpos = (otherni->geom->lowx + otherni->geom->highx) / 2;
				ns->ypos = (otherni->geom->lowy + otherni->geom->highy) / 2;
				if (otherni->proto == gen_portprim)
				{
					if (!isnode)
					{
						us_tecedpointout(otherni, np);
						ttyputerr(_("%s cannot have ports.  Delete this"), describenodeproto(np));
						termsearch(sea);
						return(NOEXAMPLE);
					}
					ns->layer = gen_portprim;
				} else if (otherni->proto == gen_facetcenterprim)
				{
					if (!isnode)
					{
						us_tecedpointout(otherni, np);
						ttyputerr(_("%s cannot have a grab point.  Delete this"), describenodeproto(np));
						termsearch(sea);
						return(NOEXAMPLE);
					}
					ns->layer = gen_facetcenterprim;
				} else
				{
					var = getval((INTBIG)otherni, VNODEINST, VNODEPROTO,
						"EDTEC_layer");
					if (var == NOVARIABLE)
					{
						us_tecedpointout(otherni, np);
						ttyputerr(_("No layer information on this sample in %s"),
							describenodeproto(np));
						if ((us_tool->toolstate&NODETAILS) == 0)
							ttyputmsg(_("Use 'change' option or delete it"));
						termsearch(sea);
						return(NOEXAMPLE);
					}
					ns->layer = (NODEPROTO *)var->addr;
					if (ns->layer == NONODEPROTO) hcount++;
				}

				/* accumulate state if this is not a "grab point" mark */
				if (otherni->proto != gen_facetcenterprim)
				{
					us_tecedgetbbox(otherni, &lx, &hx, &ly, &hy);
					if (!gotbbox)
					{
						ne->lx = lx;   ne->hx = hx;
						ne->ly = ly;   ne->hy = hy;
						gotbbox = TRUE;
					} else
					{
						if (lx < ne->lx) ne->lx = lx;
						if (hx > ne->hx) ne->hx = hx;
						if (ly < ne->ly) ne->ly = ly;
						if (hy > ne->hy) ne->hy = hy;
					}
					sflx = ne->lx;   sfhx = ne->hx;
					sfly = ne->ly;   sfhy = ne->hy;
				}
				foundone = TRUE;
			}
		}
		if (hcount == 0)
		{
			us_tecedpointout(NONODEINST, np);
			ttyputerr(_("No highlight layer in %s example"), describenodeproto(np));
			if ((us_tool->toolstate&NODETAILS) == 0)
				ttyputmsg(_("Use 'place-layer' option to create HIGHLIGHT"));
			return(NOEXAMPLE);
		}
		if (hcount != 1)
		{
			us_tecedpointout(NONODEINST, np);
			ttyputerr(_("Too many highlight layers in %s example.  Delete some"), describenodeproto(np));
			return(NOEXAMPLE);
		}
	}
	if (nelist == NOEXAMPLE)
	{
		us_tecedpointout(NONODEINST, np);
		ttyputerr(_("No examples found in %s"), describenodeproto(np));
		if ((us_tool->toolstate&NODETAILS) == 0)
			ttyputmsg(_("Use 'place-layer' option to produce some geometry"));
		return(nelist);
	}

	/*
	 * now search the list for the smallest, most upper-right example
	 * (the "main" example)
	 */
	lambda = el_curlib->lambda[el_curtech->techindex];
	sizex = (nelist->hx - nelist->lx) / lambda;
	sizey = (nelist->hy - nelist->ly) / lambda;
	locx = (nelist->lx + nelist->hx) / 2;
	locy = (nelist->ly + nelist->hy) / 2;
	bestne = nelist;
	for(ne = nelist; ne != NOEXAMPLE; ne = ne->nextexample)
	{
		newsize = (ne->hx-ne->lx) / lambda;
		newsize *= (ne->hy-ne->ly) / lambda;
		if (newsize > sizex*sizey) continue;
		if (newsize == sizex*sizey && (ne->lx+ne->hx)/2 >= locx && (ne->ly+ne->hy)/2 <= locy)
			continue;
		sizex = (ne->hx - ne->lx) / lambda;
		sizey = (ne->hy - ne->ly) / lambda;
		locx = (ne->lx + ne->hx) / 2;
		locy = (ne->ly + ne->hy) / 2;
		bestne = ne;
	}

	/* place the main example at the top of the list */
	if (bestne != nelist)
	{
		for(ne = nelist; ne != NOEXAMPLE; ne = ne->nextexample)
			if (ne->nextexample == bestne)
		{
			ne->nextexample = bestne->nextexample;
			break;
		}
		bestne->nextexample = nelist;
		nelist = bestne;
	}

	/* done */
	return(nelist);
}

/*
 * Routine to associate the samples of example "nelist" in facet "np"
 * Returns true if there is an error
 */
BOOLEAN us_tecedassociateexamples(EXAMPLE *nelist, NODEPROTO *np)
{
	REGISTER EXAMPLE *ne;
	REGISTER SAMPLE *ns, *nslist, *nsfound, **listsort, **thissort;
	REGISTER INTBIG total, i;
	REGISTER char *name, *othername;
	REGISTER VARIABLE *var;

	/* if there is only one example, no association */
	if (nelist->nextexample == NOEXAMPLE) return(FALSE);

	/* associate each example "ne" with the original in "nelist" */
	for(ne = nelist->nextexample; ne != NOEXAMPLE; ne = ne->nextexample)
	{
		/* clear associations for every sample "ns" in the example "ne" */
		for(ns = ne->firstsample; ns != NOSAMPLE; ns = ns->nextsample)
			ns->assoc = NOSAMPLE;

		/* associate every sample "ns" in the example "ne" */
		for(ns = ne->firstsample; ns != NOSAMPLE; ns = ns->nextsample)
		{
			if (ns->assoc != NOSAMPLE) continue;

			/* cannot have center in other examples */
			if (ns->layer == gen_facetcenterprim)
			{
				us_tecedpointout(ns->node, ns->node->parent);
				ttyputerr(_("Grab point should only be in main example of %s"),
					describenodeproto(np));
				return(TRUE);
			}

			/* count number of similar layers in original example "nelist" */
			for(total = 0, nslist = nelist->firstsample; nslist != NOSAMPLE;
				nslist = nslist->nextsample)
			{
				if (nslist->layer != ns->layer) continue;
				total++;
				nsfound = nslist;
			}

			/* no similar layer found in the original: error */
			if (total == 0)
			{
				us_tecedpointout(ns->node, ns->node->parent);
				ttyputerr(_("Layer %s not found in main example of %s"),
					us_tecedsamplename(ns->layer), describenodeproto(np));
				return(TRUE);
			}

			/* just one in the original: simple association */
			if (total == 1)
			{
				ns->assoc = nsfound;
				continue;
			}

			/* if it is a port, associate by port name */
			if (ns->layer == gen_portprim)
			{
				var = getval((INTBIG)ns->node, VNODEINST, VSTRING, "EDTEC_portname");
				if (var == NOVARIABLE)
				{
					us_tecedpointout(ns->node, ns->node->parent);
					ttyputerr(_("Port does not have a name name in %s"), describenodeproto(np));
					return(TRUE);
				}
				name = (char *)var->addr;

				/* search the original for that port */
				for(nslist = nelist->firstsample; nslist != NOSAMPLE; nslist = nslist->nextsample)
					if (nslist->layer == gen_portprim)
				{
					var = getval((INTBIG)nslist->node, VNODEINST, VSTRING, "EDTEC_portname");
					if (var == NOVARIABLE)
					{
						us_tecedpointout(nslist->node, nslist->node->parent);
						ttyputerr(_("Port does not have a name in %s"), describenodeproto(np));
						return(TRUE);
					}
					othername = (char *)var->addr;
					if (namesame(name, othername) != 0) continue;
					ns->assoc = nslist;
					break;
				}
				if (nslist == NOSAMPLE)
				{
					us_tecedpointout(NONODEINST, np);
					ttyputerr(_("Could not find port %s in all examples of %s"),
						name, describenodeproto(np));
					return(TRUE);
				}
				continue;
			}

			/* count the number of this layer in example "ne" */
			for(i = 0, nslist = ne->firstsample; nslist != NOSAMPLE;
				nslist = nslist->nextsample)
					if (nslist->layer == ns->layer) i++;

			/* if number of similar layers differs: error */
			if (total != i)
			{
				us_tecedpointout(ns->node, ns->node->parent);
				ttyputerr(_("Layer %s found %ld times in main example, %ld in other"),
					us_tecedsamplename(ns->layer), total, i);
				ttyputmsg(_("Make the counts consistent"));
				return(TRUE);
			}

			/* make a list of samples on this layer in original */
			listsort = (SAMPLE **)emalloc((total * (sizeof (SAMPLE *))), el_tempcluster);
			if (listsort == 0) return(TRUE);
			for(i = 0, nslist = nelist->firstsample; nslist != NOSAMPLE;
				nslist = nslist->nextsample)
					if (nslist->layer == ns->layer) listsort[i++] = nslist;

			/* make a list of samples on this layer in example "ne" */
			thissort = (SAMPLE **)emalloc((total * (sizeof (SAMPLE *))), el_tempcluster);
			if (thissort == 0) return(TRUE);
			for(i = 0, nslist = ne->firstsample; nslist != NOSAMPLE; nslist = nslist->nextsample)
				if (nslist->layer == ns->layer) thissort[i++] = nslist;

			/* sort each list in X/Y/shape */
			esort(listsort, total, sizeof (SAMPLE *), us_samplecoordascending);
			esort(thissort, total, sizeof (SAMPLE *), us_samplecoordascending);

			/* see if the lists have duplication */
			for(i=1; i<total; i++)
				if ((thissort[i]->xpos == thissort[i-1]->xpos &&
					thissort[i]->ypos == thissort[i-1]->ypos &&
						thissort[i]->node->proto == thissort[i-1]->node->proto) ||
					(listsort[i]->xpos == listsort[i-1]->xpos &&
						listsort[i]->ypos == listsort[i-1]->ypos &&
							listsort[i]->node->proto == listsort[i-1]->node->proto)) break;
			if (i >= total)
			{
				/* association can be made in X */
				for(i=0; i<total; i++) thissort[i]->assoc = listsort[i];
				efree((char *)thissort);
				efree((char *)listsort);
				continue;
			}

			/* don't know how to associate this sample */
			us_tecedpointout(thissort[i]->node, thissort[i]->node->parent);
			ttyputerr(_("Sample %s is unassociated in %s"),
				us_tecedsamplename(thissort[i]->layer), describenodeproto(np));
			efree((char *)thissort);
			efree((char *)listsort);
			return(TRUE);
		}

		/* final check: make sure every sample in original example associates */
		for(nslist = nelist->firstsample; nslist != NOSAMPLE;
			nslist = nslist->nextsample) nslist->assoc = NOSAMPLE;
		for(ns = ne->firstsample; ns != NOSAMPLE; ns = ns->nextsample)
			ns->assoc->assoc = ns;
		for(nslist = nelist->firstsample; nslist != NOSAMPLE;
			nslist = nslist->nextsample) if (nslist->assoc == NOSAMPLE)
		{
			if (nslist->layer == gen_facetcenterprim) continue;
			us_tecedpointout(nslist->node, nslist->node->parent);
			ttyputerr(_("Layer %s found in main example, but not others in %s"),
				us_tecedsamplename(nslist->layer), describenodeproto(np));
			return(TRUE);
		}
	}
	return(FALSE);
}

/*
 * Helper routine to "xx()" for sorting samples by coordinate value.
 */
int us_samplecoordascending(const void *e1, const void *e2)
{
	SAMPLE *s1, *s2;

	s1 = *((SAMPLE **)e1);
	s2 = *((SAMPLE **)e2);
	if (s1->xpos != s2->xpos) return(s1->xpos - s2->xpos);
	if (s1->ypos != s2->ypos) return(s1->ypos - s2->ypos);
	return(s1->node->proto - s2->node->proto);
}

/* flags about the edge positions in the examples */
#define TOEDGELEFT       01		/* constant to left edge */
#define TOEDGERIGHT      02		/* constant to right edge */
#define TOEDGETOP        04		/* constant to top edge */
#define TOEDGEBOT       010		/* constant to bottom edge */
#define FROMCENTX       020		/* constant in X to center */
#define FROMCENTY       040		/* constant in Y to center */
#define RATIOCENTX     0100		/* fixed ratio from X center to edge */
#define RATIOCENTY     0200		/* fixed ratio from Y center to edge */

BOOLEAN us_tecedmakeprim(EXAMPLE *nelist, NODEPROTO *np, TECHNOLOGY *tech)
{
	REGISTER SAMPLE *ns, *nso;
	REGISTER EXAMPLE *ne;
	REGISTER INTBIG total, count, newcount, i, truecount;
	REGISTER INTBIG *newrule, r, dist;
	XARRAY trans;
	REGISTER char *str;
	REGISTER VARIABLE *var, *var2, *var3;
	REGISTER NODEINST *ni;

	/* look at every sample "ns" in the main example "nelist" */
	for(ns = nelist->firstsample; ns != NOSAMPLE; ns = ns->nextsample)
	{
		/* ignore grab point specification */
		if (ns->layer == gen_facetcenterprim) continue;

		/* if there is only one example: make sample scale with edge */
		if (nelist->nextexample == NOEXAMPLE)
		{
			/* see if there is polygonal information */
			if (ns->node->proto == art_filledpolygonprim ||
				ns->node->proto == art_closedpolygonprim ||
				ns->node->proto == art_openedpolygonprim ||
				ns->node->proto == art_openeddottedpolygonprim ||
				ns->node->proto == art_openeddashedpolygonprim ||
				ns->node->proto == art_openedthickerpolygonprim)
			{
				var = getvalkey((INTBIG)ns->node, VNODEINST, VINTEGER|VISARRAY, el_trace_key);
			} else var = NOVARIABLE;
			if (var != NOVARIABLE)
			{
				/* make sure the arrays hold "count" points */
				count = getlength(var) / 2;
				us_tecedforcearrays(count);

				/* fill the array */
				makerot(ns->node, trans);
				for(i=0; i<count; i++)
				{
					xform((ns->node->geom->lowx + ns->node->geom->highx)/2 +
						((INTBIG *)var->addr)[i*2],
							(ns->node->geom->lowy + ns->node->geom->highy)/2 +
								((INTBIG *)var->addr)[i*2+1], &us_tecedmakepx[i], &us_tecedmakepy[i], trans);
					us_tecedmakefactor[i] = FROMCENTX|FROMCENTY;
				}
			} else
			{
				/* see if it is an arc of a circle */
				count = 2;
				if (ns->node->proto == art_circleprim || ns->node->proto == art_thickcircleprim)
				{
					var = getvalkey((INTBIG)ns->node, VNODEINST, VINTEGER, art_degreeskey);
					if (var != NOVARIABLE) count = 3;
				} else var = NOVARIABLE;

				/* make sure the arrays hold enough points */
				us_tecedforcearrays(count);

				/* set sample description */
				if (var != NOVARIABLE)
				{
					/* handle circular arc sample */
					us_tecedmakepx[0] = (ns->node->geom->lowx + ns->node->geom->highx) / 2;
					us_tecedmakepy[0] = (ns->node->geom->lowy + ns->node->geom->highy) / 2;
					makerot(ns->node, trans);
					dist = ns->node->geom->highx - us_tecedmakepx[0];
					xform(us_tecedmakepx[0] + mult(dist, cosine(var->addr)),
						us_tecedmakepy[0] + mult(dist, sine(var->addr)), &us_tecedmakepx[1], &us_tecedmakepy[1], trans);
					xform(ns->node->geom->highx,
						(ns->node->geom->lowy + ns->node->geom->highy) / 2, &us_tecedmakepx[2], &us_tecedmakepy[2], trans);
					us_tecedmakefactor[0] = FROMCENTX|FROMCENTY;
					us_tecedmakefactor[1] = RATIOCENTX|RATIOCENTY;
					us_tecedmakefactor[2] = RATIOCENTX|RATIOCENTY;
				} else if (ns->node->proto == art_circleprim || ns->node->proto == art_thickcircleprim ||
					ns->node->proto == art_filledcircleprim)
				{
					/* handle circular sample */
					us_tecedmakepx[0] = (ns->node->geom->lowx + ns->node->geom->highx) / 2;
					us_tecedmakepy[0] = (ns->node->geom->lowy + ns->node->geom->highy) / 2;
					us_tecedmakepx[1] = ns->node->geom->highx;
					us_tecedmakepy[1] = (ns->node->geom->lowy + ns->node->geom->highy) / 2;
					us_tecedmakefactor[0] = FROMCENTX|FROMCENTY;
					us_tecedmakefactor[1] = TOEDGERIGHT|FROMCENTY;
				} else
				{
					/* rectangular sample: get the bounding box in (px, py) */
					us_tecedgetbbox(ns->node, &us_tecedmakepx[0], &us_tecedmakepx[1], &us_tecedmakepy[0], &us_tecedmakepy[1]);

					/* preset stretch factors to go to the edges of the box */
					us_tecedmakefactor[0] = TOEDGELEFT|TOEDGEBOT;
					us_tecedmakefactor[1] = TOEDGERIGHT|TOEDGETOP;
				}
			}

			/* add the rule to the collection */
			newrule = us_tecedstretchpoints(us_tecedmakepx,us_tecedmakepy, count, us_tecedmakefactor, ns, np,
				nelist);
			if (newrule == 0) return(TRUE);
			var = getvalkey((INTBIG)ns->node, VNODEINST, VSTRING|VISARRAY, art_messagekey);
			if (var == NOVARIABLE) str = (char *)0; else
				str = ((char **)var->addr)[0];
			ns->rule = us_tecedaddrule(newrule, count*4, FALSE, str);
			if (ns->rule == NORULE) return(TRUE);
			efree((char *)newrule);
			continue;
		}

		/* look at other examples and find samples associated with this */
		nelist->studysample = ns;
		for(ne = nelist->nextexample; ne != NOEXAMPLE; ne = ne->nextexample)
		{
			/* count number of samples associated with the main sample */
			total = 0;
			for(nso = ne->firstsample; nso != NOSAMPLE; nso = nso->nextsample)
				if (nso->assoc == ns)
			{
				ne->studysample = nso;
				total++;
			}
			if (total == 0)
			{
				us_tecedpointout(ns->node, ns->node->parent);
				ttyputerr(_("Still unassociated sample in %s (shouldn't happen)"),
					describenodeproto(np));
				return(TRUE);
			}

			/* if there are multiple associations, it must be a contact cut */
			if (total > 1)
			{
				/* make sure the layer is real geometry, not highlight or a port */
				if (ns->layer == NONODEPROTO || ns->layer == gen_portprim)
				{
					us_tecedpointout(ns->node, ns->node->parent);
					ttyputerr(_("Only contact layers may be iterated in examples of %s"),
						describenodeproto(np));
					return(TRUE);
				}

				/* make sure the contact cut layer is opaque */
				for(i=0; i<tech->layercount; i++)
					if (namesame(&ns->layer->cell->cellname[6], us_teclayer_names[i]) == 0)
				{
					if (tech->layers[i]->bits != LAYERO)
					{
						us_tecedpointout(ns->node, ns->node->parent);
						ttyputerr(_("Multiple contact layers must be nonoverlappable in %s"),
							describenodeproto(np));
						return(TRUE);
					}
					break;
				}

				/* add the rule */
				if (us_tecedmulticut(ns, nelist, np)) return(TRUE);
				break;
			}
		}
		if (ne != NOEXAMPLE) continue;

		/* associations done for this sample, now analyze them */
		if (ns->node->proto == art_filledpolygonprim ||
			ns->node->proto == art_closedpolygonprim ||
			ns->node->proto == art_openedpolygonprim ||
			ns->node->proto == art_openeddottedpolygonprim ||
			ns->node->proto == art_openeddashedpolygonprim ||
			ns->node->proto == art_openedthickerpolygonprim)
		{
			var = getvalkey((INTBIG)ns->node, VNODEINST, VINTEGER|VISARRAY, el_trace_key);
		} else var = NOVARIABLE;
		if (var != NOVARIABLE)
		{
			truecount = count = getlength(var) / 2;

			/* make sure the arrays hold "count" points */
			us_tecedforcearrays(count);
			makerot(ns->node, trans);
			for(i=0; i<count; i++)
				xform((ns->node->geom->lowx + ns->node->geom->highx)/2 + ((INTBIG *)var->addr)[i*2],
					(ns->node->geom->lowy + ns->node->geom->highy)/2 +
						((INTBIG *)var->addr)[i*2+1], &us_tecedmakepx[i], &us_tecedmakepy[i], trans);
		} else
		{
			/* make sure the arrays hold enough points */
			count = 2;
			if (ns->node->proto == art_circleprim || ns->node->proto == art_thickcircleprim)
			{
				var3 = getvalkey((INTBIG)ns->node, VNODEINST, VINTEGER, art_degreeskey);
				if (var3 != NOVARIABLE) count = 3;
			} else var3 = NOVARIABLE;
			truecount = count;
			if (var3 == NOVARIABLE)
			{
				var2 = getval((INTBIG)ns->node, VNODEINST, VSTRING, "EDTEC_minbox");
				if (var2 != NOVARIABLE) count *= 2;
			}
			us_tecedforcearrays(count);

			/* set sample description */
			if (var3 != NOVARIABLE)
			{
				/* handle circular arc sample */
				us_tecedmakepx[0] = (ns->node->geom->lowx + ns->node->geom->highx) / 2;
				us_tecedmakepy[0] = (ns->node->geom->lowy + ns->node->geom->highy) / 2;
				makerot(ns->node, trans);
				dist = ns->node->geom->highx - us_tecedmakepx[0];
				xform(us_tecedmakepx[0] + mult(dist, cosine(var3->addr)),
					us_tecedmakepy[0] + mult(dist, sine(var3->addr)), &us_tecedmakepx[1], &us_tecedmakepy[1], trans);
				xform(ns->node->geom->highx,
					(ns->node->geom->lowy + ns->node->geom->highy) / 2, &us_tecedmakepx[2], &us_tecedmakepy[2], trans);
			} else if (ns->node->proto == art_circleprim || ns->node->proto == art_thickcircleprim ||
				ns->node->proto == art_filledcircleprim)
			{
				/* handle circular sample */
				us_tecedmakepx[0] = (ns->node->geom->lowx + ns->node->geom->highx) / 2;
				us_tecedmakepy[0] = (ns->node->geom->lowy + ns->node->geom->highy) / 2;
				us_tecedmakepx[1] = ns->node->geom->highx;
				us_tecedmakepy[1] = (ns->node->geom->lowy + ns->node->geom->highy) / 2;
			} else
			{
				/* rectangular sample: get the bounding box in (us_tecedmakepx, us_tecedmakepy) */
				us_tecedgetbbox(ns->node, &us_tecedmakepx[0], &us_tecedmakepx[1], &us_tecedmakepy[0], &us_tecedmakepy[1]);
			}
			if (var2 != NOVARIABLE)
			{
				us_tecedmakepx[2] = us_tecedmakepx[0];   us_tecedmakepy[2] = us_tecedmakepy[0];
				us_tecedmakepx[3] = us_tecedmakepx[1];   us_tecedmakepy[3] = us_tecedmakepy[1];
			}
		}

		for(i=0; i<count; i++)
		{
			us_tecedmakeleftdist[i] = us_tecedmakepx[i] - nelist->lx;
			us_tecedmakerightdist[i] = nelist->hx - us_tecedmakepx[i];
			us_tecedmakebotdist[i] = us_tecedmakepy[i] - nelist->ly;
			us_tecedmaketopdist[i] = nelist->hy - us_tecedmakepy[i];
			us_tecedmakecentxdist[i] = us_tecedmakepx[i] - (nelist->lx+nelist->hx)/2;
			us_tecedmakecentydist[i] = us_tecedmakepy[i] - (nelist->ly+nelist->hy)/2;
			if (nelist->hx == nelist->lx) us_tecedmakeratiox[i] = 0; else
				us_tecedmakeratiox[i] = (us_tecedmakepx[i] - (nelist->lx+nelist->hx)/2) * WHOLE / (nelist->hx-nelist->lx);
			if (nelist->hy == nelist->ly) us_tecedmakeratioy[i] = 0; else
				us_tecedmakeratioy[i] = (us_tecedmakepy[i] - (nelist->ly+nelist->hy)/2) * WHOLE / (nelist->hy-nelist->ly);
			if (i < truecount)
				us_tecedmakefactor[i] = TOEDGELEFT | TOEDGERIGHT | TOEDGETOP | TOEDGEBOT | FROMCENTX |
					FROMCENTY | RATIOCENTX | RATIOCENTY; else
						us_tecedmakefactor[i] = FROMCENTX | FROMCENTY;
		}
		for(ne = nelist->nextexample; ne != NOEXAMPLE; ne = ne->nextexample)
		{
			ni = ne->studysample->node;
			if (ni->proto == art_filledpolygonprim ||
				ni->proto == art_closedpolygonprim ||
				ni->proto == art_openedpolygonprim ||
				ni->proto == art_openeddottedpolygonprim ||
				ni->proto == art_openeddashedpolygonprim ||
				ni->proto == art_openedthickerpolygonprim)
			{
				var = getvalkey((INTBIG)ni, VNODEINST, VINTEGER|VISARRAY, el_trace_key);
			} else var = NOVARIABLE;
			if (var != NOVARIABLE)
			{
				newcount = getlength(var) / 2;
				makerot(ni, trans);
				for(i=0; i<mini(truecount, newcount); i++)
					xform((ni->geom->lowx + ni->geom->highx)/2 + ((INTBIG *)var->addr)[i*2],
						(ni->geom->lowy + ni->geom->highy)/2 +
							((INTBIG *)var->addr)[i*2+1], &us_tecedmakecx[i], &us_tecedmakecy[i], trans);
			} else
			{
				newcount = 2;
				if (ni->proto == art_circleprim || ni->proto == art_thickcircleprim)
				{
					var3 = getvalkey((INTBIG)ni, VNODEINST, VINTEGER, art_degreeskey);
					if (var3 != NOVARIABLE) newcount = 3;
				} else var3 = NOVARIABLE;
				if (var3 != NOVARIABLE)
				{
					us_tecedmakecx[0] = (ni->geom->lowx + ni->geom->highx) / 2;
					us_tecedmakecy[0] = (ni->geom->lowy + ni->geom->highy) / 2;
					makerot(ni, trans);
					dist = ni->geom->highx - us_tecedmakecx[0];
					xform(us_tecedmakecx[0] + mult(dist, cosine(var3->addr)),
						us_tecedmakecy[0] + mult(dist, sine(var3->addr)), &us_tecedmakecx[1], &us_tecedmakecy[1], trans);
					xform(ni->geom->highx, (ni->geom->lowy + ni->geom->highy) / 2,
						&us_tecedmakecx[2], &us_tecedmakecy[2], trans);
				} else if (ni->proto == art_circleprim || ni->proto == art_thickcircleprim ||
					ni->proto == art_filledcircleprim)
				{
					us_tecedmakecx[0] = (ni->geom->lowx + ni->geom->highx) / 2;
					us_tecedmakecy[0] = (ni->geom->lowy + ni->geom->highy) / 2;
					us_tecedmakecx[1] = ni->geom->highx;
					us_tecedmakecy[1] = (ni->geom->lowy + ni->geom->highy) / 2;
				} else
				{
					us_tecedgetbbox(ni, &us_tecedmakecx[0], &us_tecedmakecx[1], &us_tecedmakecy[0], &us_tecedmakecy[1]);
				}
			}
			if (newcount != truecount)
			{
				us_tecedpointout(ni, ni->parent);
				ttyputerr(_("Main example of %s has %ld points but this has %ld in %s"),
					us_tecedsamplename(ne->studysample->layer),
						truecount, newcount, describenodeproto(np));
				return(TRUE);
			}

			for(i=0; i<truecount; i++)
			{
				/* see if edges are fixed distance from example edge */
				if (us_tecedmakeleftdist[i] != us_tecedmakecx[i] - ne->lx) us_tecedmakefactor[i] &= ~TOEDGELEFT;
				if (us_tecedmakerightdist[i] != ne->hx - us_tecedmakecx[i]) us_tecedmakefactor[i] &= ~TOEDGERIGHT;
				if (us_tecedmakebotdist[i] != us_tecedmakecy[i] - ne->ly) us_tecedmakefactor[i] &= ~TOEDGEBOT;
				if (us_tecedmaketopdist[i] != ne->hy - us_tecedmakecy[i]) us_tecedmakefactor[i] &= ~TOEDGETOP;

				/* see if edges are fixed distance from example center */
				if (us_tecedmakecentxdist[i] != us_tecedmakecx[i] - (ne->lx+ne->hx)/2) us_tecedmakefactor[i] &= ~FROMCENTX;
				if (us_tecedmakecentydist[i] != us_tecedmakecy[i] - (ne->ly+ne->hy)/2) us_tecedmakefactor[i] &= ~FROMCENTY;

				/* see if edges are fixed ratio from example center */
				if (ne->hx == ne->lx) r = 0; else
					r = (us_tecedmakecx[i] - (ne->lx+ne->hx)/2) * WHOLE / (ne->hx-ne->lx);
				if (r != us_tecedmakeratiox[i]) us_tecedmakefactor[i] &= ~RATIOCENTX;
				if (ne->hy == ne->ly) r = 0; else
					r = (us_tecedmakecy[i] - (ne->ly+ne->hy)/2) * WHOLE / (ne->hy-ne->ly);
				if (r != us_tecedmakeratioy[i]) us_tecedmakefactor[i] &= ~RATIOCENTY;
			}

			/* make sure port information is on the primary example */
			if (ns->layer != gen_portprim) continue;

			/* check port angle */
			var = getval((INTBIG)ns->node, VNODEINST, VINTEGER, "EDTEC_portangle");
			var2 = getval((INTBIG)ni, VNODEINST, VINTEGER, "EDTEC_portangle");
			if (var == NOVARIABLE && var2 != NOVARIABLE)
			{
				us_tecedpointout(NONODEINST, np);
				ttyputerr(_("Warning: moving port angle to main example of %s"),
					describenodeproto(np));
				(void)setval((INTBIG)ns->node, VNODEINST, "EDTEC_portangle", var2->addr, VINTEGER);
			}

			/* check port range */
			var = getval((INTBIG)ns->node, VNODEINST, VINTEGER, "EDTEC_portrange");
			var2 = getval((INTBIG)ni, VNODEINST, VINTEGER, "EDTEC_portrange");
			if (var == NOVARIABLE && var2 != NOVARIABLE)
			{
				us_tecedpointout(NONODEINST, np);
				ttyputerr(_("Warning: moving port range to main example of %s"), describenodeproto(np));
				(void)setval((INTBIG)ns->node, VNODEINST, "EDTEC_portrange", var2->addr, VINTEGER);
			}

			/* check connectivity */
			var = getval((INTBIG)ns->node, VNODEINST, VNODEPROTO|VISARRAY, "EDTEC_connects");
			var2 = getval((INTBIG)ni, VNODEINST, VNODEPROTO|VISARRAY, "EDTEC_connects");
			if (var == NOVARIABLE && var2 != NOVARIABLE)
			{
				us_tecedpointout(NONODEINST, np);
				ttyputerr(_("Warning: moving port connections to main example of %s"),
					describenodeproto(np));
				(void)setval((INTBIG)ns->node, VNODEINST, "EDTEC_connects",
					var2->addr, VNODEPROTO|VISARRAY|(getlength(var2)<<VLENGTHSH));
			}
		}

		/* error check for the highlight layer */
		if (ns->layer == NONODEPROTO)
			for(i=0; i<truecount; i++)
				if ((us_tecedmakefactor[i]&(TOEDGELEFT|TOEDGERIGHT)) == 0 ||
					(us_tecedmakefactor[i]&(TOEDGETOP|TOEDGEBOT)) == 0)
		{
			us_tecedpointout(ns->node, ns->node->parent);
			ttyputerr(_("Highlight must be constant distance from edge in %s"), describenodeproto(np));
			return(TRUE);
		}

		/* finally, make a rule for this sample */
		newrule = us_tecedstretchpoints(us_tecedmakepx, us_tecedmakepy, count, us_tecedmakefactor, ns, np, nelist);
		if (newrule == 0) return(TRUE);

		/* add the rule to the global list */
		var = getvalkey((INTBIG)ns->node, VNODEINST, VSTRING|VISARRAY, art_messagekey);
		if (var == NOVARIABLE) str = (char *)0; else
			str = ((char **)var->addr)[0];
		ns->rule = us_tecedaddrule(newrule, count*4, FALSE, str);
		if (ns->rule == NORULE) return(TRUE);
		efree((char *)newrule);
	}
	return(FALSE);
}

/*
 * routine to ensure that the 13 global arrays are all at least "want" long.
 * Their current size is "us_tecedmakearrlen".
 */
void us_tecedforcearrays(INTBIG want)
{
	if (us_tecedmakearrlen >= want) return;
	if (us_tecedmakearrlen != 0)
	{
		efree((char *)us_tecedmakepx);
		efree((char *)us_tecedmakepy);
		efree((char *)us_tecedmakecx);
		efree((char *)us_tecedmakecy);
		efree((char *)us_tecedmakefactor);
		efree((char *)us_tecedmakeleftdist);
		efree((char *)us_tecedmakerightdist);
		efree((char *)us_tecedmakebotdist);
		efree((char *)us_tecedmaketopdist);
		efree((char *)us_tecedmakecentxdist);
		efree((char *)us_tecedmakecentydist);
		efree((char *)us_tecedmakeratiox);
		efree((char *)us_tecedmakeratioy);
	}
	us_tecedmakearrlen = want;
	us_tecedmakepx = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakepx == 0) return;
	us_tecedmakepy = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakepy == 0) return;
	us_tecedmakecx = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakecx == 0) return;
	us_tecedmakecy = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakecy == 0) return;
	us_tecedmakefactor = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakefactor == 0) return;
	us_tecedmakeleftdist = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakeleftdist == 0) return;
	us_tecedmakerightdist = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakerightdist == 0) return;
	us_tecedmakebotdist = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakebotdist == 0) return;
	us_tecedmaketopdist = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmaketopdist == 0) return;
	us_tecedmakecentxdist = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakecentxdist == 0) return;
	us_tecedmakecentydist = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakecentydist == 0) return;
	us_tecedmakeratiox = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakeratiox == 0) return;
	us_tecedmakeratioy = emalloc((want * SIZEOFINTBIG), us_tool->cluster);
	if (us_tecedmakeratioy == 0) return;
}

/*
 * routine to adjust the "count"-long array of points in "px" and "py" according
 * to the stretch factor bits in "factor" and return an array that describes
 * these points.  Returns zero on error.
 */
INTBIG *us_tecedstretchpoints(INTBIG *px, INTBIG *py, INTBIG count, INTBIG *factor,
	SAMPLE *ns, NODEPROTO *np, EXAMPLE *nelist)
{
	REGISTER INTBIG *newrule, lambda;
	REGISTER INTBIG i;

	newrule = emalloc((count*4*SIZEOFINTBIG), el_tempcluster);
	if (newrule == 0) return(0);

	lambda = el_curlib->lambda[el_curtech->techindex];
	for(i=0; i<count; i++)
	{
		/* determine the X algorithm */
		if ((factor[i]&TOEDGELEFT) != 0)
		{
			/* left edge rule */
			newrule[i*4] = -H0;
			newrule[i*4+1] = (px[i]-nelist->lx) * WHOLE/lambda;
		} else if ((factor[i]&TOEDGERIGHT) != 0)
		{
			/* right edge rule */
			newrule[i*4] = H0;
			newrule[i*4+1] = (px[i]-nelist->hx) * WHOLE/lambda;
		} else if ((factor[i]&FROMCENTX) != 0)
		{
			/* center rule */
			newrule[i*4] = 0;
			newrule[i*4+1] = (px[i]-(nelist->lx+nelist->hx)/2) * WHOLE/lambda;
		} else if ((factor[i]&RATIOCENTX) != 0)
		{
			/* constant stretch rule */
			if (nelist->hx == nelist->lx) newrule[i*4] = 0; else
				newrule[i*4] = (px[i] - (nelist->lx+nelist->hx)/2) * WHOLE / (nelist->hx-nelist->lx);
			newrule[i*4+1] = 0;
		} else
		{
			us_tecedpointout(ns->node, ns->node->parent);
			ttyputerr(_("Cannot determine X stretching rule for layer %s in %s"),
				us_tecedsamplename(ns->layer), describenodeproto(np));
			return(0);
		}

		/* determine the Y algorithm */
		if ((factor[i]&TOEDGEBOT) != 0)
		{
			/* bottom edge rule */
			newrule[i*4+2] = -H0;
			newrule[i*4+3] = (py[i]-nelist->ly) * WHOLE/lambda;
		} else if ((factor[i]&TOEDGETOP) != 0)
		{
			/* top edge rule */
			newrule[i*4+2] = H0;
			newrule[i*4+3] = (py[i]-nelist->hy) * WHOLE/lambda;
		} else if ((factor[i]&FROMCENTY) != 0)
		{
			/* center rule */
			newrule[i*4+2] = 0;
			newrule[i*4+3] = (py[i]-(nelist->ly+nelist->hy)/2) * WHOLE/lambda;
		} else if ((factor[i]&RATIOCENTY) != 0)
		{
			/* constant stretch rule */
			if (nelist->hy == nelist->ly) newrule[i*4+2] = 0; else
				newrule[i*4+2] = (py[i] - (nelist->ly+nelist->hy)/2) * WHOLE /
					(nelist->hy-nelist->ly);
			newrule[i*4+3] = 0;
		} else
		{
			us_tecedpointout(ns->node, ns->node->parent);
			ttyputerr(_("Cannot determine Y stretching rule for layer %s in %s"),
				us_tecedsamplename(ns->layer), describenodeproto(np));
			return(0);
		}
	}
	return(newrule);
}

/*
 * routine to build a rule for multiple contact-cut sample "ns" from the
 * overall example list in "nelist".  Returns true on error.
 */
BOOLEAN us_tecedmulticut(SAMPLE *ns, EXAMPLE *nelist, NODEPROTO *np)
{
	INTBIG rule[8];
	REGISTER INTBIG total, i;
	REGISTER INTBIG xsep, ysep, sepx, sepy;
	REGISTER SAMPLE **nslist, *nso, *hs;
	REGISTER EXAMPLE *ne;

	rule[0] = -H0;   rule[1] = K1;
	rule[2] = -H0;   rule[3] = K1;
	rule[4] = -H0;   rule[5] = K3;
	rule[6] = -H0;   rule[7] = K3;
	ns->rule = us_tecedaddrule(rule, 8, TRUE, (char *)0);
	if (ns->rule == NORULE) return(TRUE);

	/* find the highlight layer */
	for(hs = nelist->firstsample; hs != NOSAMPLE; hs = hs->nextsample)
		if (hs->layer == NONODEPROTO) break;
	if (hs == NOSAMPLE)
	{
		us_tecedpointout(NONODEINST, np);
		ttyputerr(_("No highlight layer on contact %s"), describenodeproto(np));
		if ((us_tool->toolstate&NODETAILS) == 0)
			ttyputmsg(_("Use 'place-layer' option to create HIGHLIGHT"));
		return(TRUE);
	}

	/* determine size of each cut */
	ns->rule->multixs = ns->node->highx - ns->node->lowx;
	ns->rule->multiys = ns->node->highy - ns->node->lowy;

	/* determine indentation of cuts */
	ns->rule->multiindent = ns->node->lowx - hs->node->lowx;
	if (hs->node->highx - ns->node->highx != ns->rule->multiindent ||
		ns->node->lowy - hs->node->lowy != ns->rule->multiindent ||
		hs->node->highy - ns->node->highy != ns->rule->multiindent)
	{
		us_tecedpointout(ns->node, ns->node->parent);
		ttyputerr(_("Multiple contact cuts must be indented uniformly in %s"),
			describenodeproto(np));
		return(TRUE);
	}

	/* look at every example after the first */
	xsep = ysep = -1;
	for(ne = nelist->nextexample; ne != NOEXAMPLE; ne = ne->nextexample)
	{
		/* count number of samples equivalent to the main sample */
		total = 0;
		for(nso = ne->firstsample; nso != NOSAMPLE; nso = nso->nextsample)
			if (nso->assoc == ns)
		{
			/* make sure size is proper */
			if (ns->rule->multixs != nso->node->highx - nso->node->lowx ||
				ns->rule->multiys != nso->node->highy - nso->node->lowy)
			{
				us_tecedpointout(nso->node, nso->node->parent);
				ttyputerr(_("Multiple contact cuts must not differ in size in %s"),
					describenodeproto(np));
				return(TRUE);
			}
			total++;
		}

		/* allocate space for these samples */
		nslist = (SAMPLE **)emalloc((total * (sizeof (SAMPLE *))), el_tempcluster);
		if (nslist == 0) return(TRUE);

		/* fill the list of samples */
		i = 0;
		for(nso = ne->firstsample; nso != NOSAMPLE; nso = nso->nextsample)
			if (nso->assoc == ns) nslist[i++] = nso;

		/* analyze the samples for separation */
		for(i=1; i<total; i++)
		{
			/* find separation */
			sepx = abs((nslist[i-1]->node->highx + nslist[i-1]->node->lowx) / 2 -
				(nslist[i]->node->highx + nslist[i]->node->lowx) / 2);
			sepy = abs((nslist[i-1]->node->highy + nslist[i-1]->node->lowy) / 2 -
				(nslist[i]->node->highy + nslist[i]->node->lowy) / 2);

			/* check for validity */
			if (sepx < ns->rule->multixs && sepy < ns->rule->multiys)
			{
				us_tecedpointout(nslist[i]->node, nslist[i]->node->parent);
				ttyputerr(_("Multiple contact cuts must not overlap in %s"),
					describenodeproto(np));
				efree((char *)nslist);
				return(TRUE);
			}

			/* accumulate minimum separation */
			if (sepx >= ns->rule->multixs)
			{
				if (xsep < 0) xsep = sepx; else
				{
					if (xsep > sepx) xsep = sepx;
				}
			}
			if (sepy >= ns->rule->multiys)
			{
				if (ysep < 0) ysep = sepy; else
				{
					if (ysep > sepy) ysep = sepy;
				}
			}
		}

		/* finally ensure that all separations are multiples of "multisep" */
		for(i=1; i<total; i++)
		{
			/* find X separation */
			sepx = abs((nslist[i-1]->node->highx + nslist[i-1]->node->lowx) / 2 -
				(nslist[i]->node->highx + nslist[i]->node->lowx) / 2);
			sepy = abs((nslist[i-1]->node->highy + nslist[i-1]->node->lowy) / 2 -
				(nslist[i]->node->highy + nslist[i]->node->lowy) / 2);
			if (sepx / xsep * xsep != sepx)
			{
				us_tecedpointout(nslist[i]->node, nslist[i]->node->parent);
				ttyputerr(_("Multiple contact cut X spacing must be uniform in %s"),
					describenodeproto(np));
				efree((char *)nslist);
				return(TRUE);
			}

			/* find Y separation */
			if (sepy / ysep * ysep != sepy)
			{
				us_tecedpointout(nslist[i]->node, nslist[i]->node->parent);
				ttyputerr(_("Multiple contact cut Y spacing must be uniform in %s"),
					describenodeproto(np));
				efree((char *)nslist);
				return(TRUE);
			}
		}
		efree((char *)nslist);
	}
	ns->rule->multisep = xsep - ns->rule->multixs;
	if (ns->rule->multisep != ysep - ns->rule->multiys)
	{
		us_tecedpointout(NONODEINST, np);
		ttyputerr(_("Multiple contact cut X and Y spacing must be the same in %s"),
			describenodeproto(np));
		return(TRUE);
	}
	return(FALSE);
}

/*
 * routine to add the "len"-long list of port connections in "conlist" to
 * the list of port connections, and return the port connection entry
 * for this one.  Returns NOPCON on error
 */
PCON *us_tecedaddportlist(INTBIG len, INTBIG *conlist)
{
	REGISTER PCON *pc;
	REGISTER INTBIG i, j;

	/* find a port connection that is the same */
	for(pc = us_tecedfirstpcon; pc != NOPCON; pc = pc->nextpcon)
	{
		if (pc->total != len) continue;
		for(j=0; j<pc->total; j++) pc->assoc[j] = 0;
		for(i=0; i<len; i++)
		{
			for(j=0; j<pc->total; j++) if (pc->connects[j+1] == conlist[i])
			{
				pc->assoc[j]++;
				break;
			}
		}
		for(j=0; j<pc->total; j++) if (pc->assoc[j] == 0) break;
		if (j >= pc->total) return(pc);
	}

	/* not found: add to list */
	pc = (PCON *)emalloc((sizeof (PCON)), us_tool->cluster);
	if (pc == 0) return(NOPCON);
	pc->total = len;
	pc->connects = emalloc(((len+5)*SIZEOFINTBIG), us_tool->cluster);
	if (pc->connects == 0) return(NOPCON);
	pc->assoc = emalloc((len*SIZEOFINTBIG), us_tool->cluster);
	if (pc->assoc == 0) return(NOPCON);
	for(j=0; j<len; j++) pc->connects[j+1] = conlist[j];
	pc->connects[0] = -1;
	pc->connects[len+1] = AUNIV;
	pc->connects[len+2] = AINVIS;
	pc->connects[len+3] = AUNROUTED;
	pc->connects[len+4] = -1;
	pc->nextpcon = us_tecedfirstpcon;
	us_tecedfirstpcon = pc;
	return(pc);
}

/*
 * routine to get the list of libraries that are used in the construction
 * of library "lib".  Returns the number of libraries, terminated with "lib",
 * and sets the list in "liblist".
 */
INTBIG us_teceditgetdependents(LIBRARY *lib, LIBRARY ***liblist)
{
	REGISTER VARIABLE *var;
	REGISTER INTBIG i, j, total;
	REGISTER char *pt;

	/* get list of dependent libraries */
	var = getval((INTBIG)lib, VLIBRARY, VSTRING|VISARRAY, "EDTEC_dependent_libraries");
	if (var == NOVARIABLE) j = 0; else j = getlength(var);
	if (j >= us_teceddepliblistsize)
	{
		if (us_teceddepliblistsize != 0) efree((char *)us_teceddepliblist);
		us_teceddepliblist = (LIBRARY **)emalloc((j+1) * (sizeof (LIBRARY *)), us_tool->cluster);
		if (us_teceddepliblist == 0) return(0);
		us_teceddepliblistsize = j+1;
	}

	total = 0;
	for(i=0; i<j; i++)
	{
		pt = ((char **)var->addr)[i];
		us_teceddepliblist[total++] = getlibrary(pt);
		if (us_teceddepliblist[total-1] == NOLIBRARY)
		{
			ttyputerr(_("Cannot find dependent technology library %s, ignoring"), pt);
			total--;
			continue;
		}
		if (us_teceddepliblist[total-1] == lib)
		{
			ttyputerr(_("Library cannot depend on itself, ignoring dependency"));
			total--;
			continue;
		}
	}
	us_teceddepliblist[total++] = lib;
	*liblist = us_teceddepliblist;
	return(total);
}

/*
 * general-purpose routine to scan the "dependentlibcount" libraries in "dependentlibs",
 * looking for facets that begin with the string "match".  It then uses the
 * variable "seqname" on the last library to determine an ordering of the facets.
 * Then, it returns the facets in the array "sequence" and returns the number of them.
 * Returns 0 on error.
 */
INTBIG us_teceditfindsequence(LIBRARY **dependentlibs, INTBIG dependentlibcount,
	char *match, char *seqname, NODEPROTO ***sequence)
{
	REGISTER INTBIG total;
	REGISTER INTBIG i, j, k, l, npsize, matchcount;
	REGISTER NODEPROTO *np, *lnp, **nplist, **newnplist;
	REGISTER VARIABLE *var;
	REGISTER LIBRARY *olderlib, *laterlib;

	/* look backwards through libraries for the appropriate facets */
	matchcount = strlen(match);
	total = 0;
	npsize = 0;
	for(i=dependentlibcount-1; i>=0; i--)
	{
		olderlib = dependentlibs[i];
		for(np = olderlib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			if (namesamen(np->cell->cellname, match, matchcount) == 0)
		{
			/* see if this facet is used in a later library */
			for(j=i+1; j<dependentlibcount; j++)
			{
				laterlib = dependentlibs[j];
				for(lnp = laterlib->firstnodeproto; lnp != NONODEPROTO; lnp = lnp->nextnodeproto)
					if (namesame(lnp->cell->cellname, np->cell->cellname) == 0)
				{
					/* got older and later version of same facet: check dates */
					if (lnp->revisiondate < np->revisiondate)
						ttyputmsg(_("Warning: library %s has newer %s than library %s"),
							olderlib->libname, np->cell->cellname, laterlib->libname);
					break;
				}
				if (lnp != NONODEPROTO) break;
			}

			/* if no later library has this, add to total */
			if (j >= dependentlibcount)
			{
				if (total >= npsize)
				{
					newnplist = (NODEPROTO **)emalloc((npsize+10) * (sizeof (NODEPROTO *)),
						el_tempcluster);
					if (newnplist == 0) return(0);

					/* LINTED "nplist" used in proper order */
					for(k=0; k<total; k++) newnplist[k] = nplist[k];
					if (npsize != 0) efree((char *)nplist);
					nplist = newnplist;
					npsize += 10;
				}
				newnplist[total++] = np;
			}
		}
	}
	if (total <= 0) return(0);

	/* if there is no sequence, simply return the list */
	var = getval((INTBIG)dependentlibs[dependentlibcount-1], VLIBRARY, VSTRING|VISARRAY, seqname);
	if (var == NOVARIABLE)
	{
		*sequence = nplist;
		return(total);
	}

	/* allocate a new list to be built from the sequence */
	*sequence = (NODEPROTO **)emalloc(total * (sizeof (NODEPROTO *)), el_tempcluster);
	if (*sequence == 0) return(0);

	j = getlength(var);
	k = 0;
	for(i=0; i<j; i++)
	{
		for(l = 0; l < total; l++)
		{
			np = nplist[l];
			if (np == NONODEPROTO) continue;
			if (namesame(&np->cell->cellname[matchcount], ((char **)var->addr)[i]) == 0) break;
		}
		if (l >= total) continue;
		(*sequence)[k++] = np;
		nplist[l] = NONODEPROTO;
	}
	for(l = 0; l < total; l++)
	{
		np = nplist[l];
		if (np != NONODEPROTO) (*sequence)[k++] = np;
	}
	efree((char *)nplist);
	return(total);
}

RULE *us_tecedaddrule(INTBIG list[8], INTBIG count, BOOLEAN multcut, char *istext)
{
	REGISTER RULE *r;
	REGISTER INTBIG i, textinc;

	for(r = us_tecedfirstrule; r != NORULE; r = r->nextrule)
	{
		if (multcut != r->multicut) continue;
		if (istext != 0 && r->istext != 0)
		{
			if (namesame(istext, (char *)r->value[count]) != 0) continue;
		} else if (istext != 0 || r->istext != 0) continue;
		if (count != r->count) continue;
		for(i=0; i<count; i++) if (r->value[i] != list[i]) break;
		if (i >= count) return(r);
	}

	r = (RULE *)emalloc((sizeof (RULE)), us_tool->cluster);
	if (r == 0) return(NORULE);
	if (istext != 0) textinc = 1; else textinc = 0;
	r->value = emalloc(((count+textinc) * SIZEOFINTBIG), us_tool->cluster);
	if (r->value == 0) return(NORULE);
	r->count = count;
	r->nextrule = us_tecedfirstrule;
	r->used = FALSE;
	r->multicut = multcut;
	us_tecedfirstrule = r;
	for(i=0; i<count; i++) r->value[i] = list[i];
	r->istext = 0;
	if (istext != 0)
	{
		(void)allocstring((char **)(&r->value[count]), istext, us_tool->cluster);
		r->istext = 1;
	}
	return(r);
}

/****************************** WRITE TECHNOLOGY ******************************/

/*
 * routine to dump the layer information in technology "tech" to the stream in
 * "f".
 */
void us_teceditdumplayers(FILE *f, TECHNOLOGY *tech, char *techname)
{
	char *sym, *colorname, *colorsymbol, date[30];
	REGISTER INTBIG i, j, k, l;
	REGISTER char *l1, *l2, *l3, *l4, *l5;

	/* write information for "tectable.c" */
	xprintf(f, "#if 0\n");
	xprintf(f, _("/* the next 4 lines belong at the top of 'tectable.c': */\n"));
	xprintf(f, "extern GRAPHICS *%s_layers[];\n", us_tecedmakesymbol(techname));
	xprintf(f, "extern TECH_ARCS *%s_arcprotos[];\n", us_tecedmakesymbol(techname));
	xprintf(f, "extern TECH_NODES *%s_nodeprotos[];\n", us_tecedmakesymbol(techname));
	xprintf(f, "extern TECH_VARIABLES %s_variables[];\n", us_tecedmakesymbol(techname));
	xprintf(f, "\n/* the next 8 lines belong in the 'el_technologylist' array of 'tectable.c': */\n");
	xprintf(f, "\t{\"%s\", 0, %ld, NONODEPROTO,NOARCPROTO,NOVARIABLE,0,NOCOMCOMP,NOCLUSTER,\t/* info */\n",
		us_tecedmakesymbol(techname), tech->deflambda);
	xprintf(f, "\tN_(\"%s\"),\t/* description */\n", tech->techdescript);
	xprintf(f, "\t0, %s_layers,", us_tecedmakesymbol(techname));
	xprintf(f, " 0, %s_arcprotos,", us_tecedmakesymbol(techname));
	xprintf(f, " 0, %s_nodeprotos,", us_tecedmakesymbol(techname));
	xprintf(f, " %s_variables,\t/* tables */\n", us_tecedmakesymbol(techname));
	xprintf(f, "\t0, 0, 0, 0, 0,\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/* control routines */\n");
	xprintf(f, "\t0, 0, 0, 0, 0,\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/* node routines */\n");
	xprintf(f, "\t0,\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/* port routine */\n");
	xprintf(f, "\t0, 0, 0,\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/* arc routines */\n");
	xprintf(f, "\tNOTECHNOLOGY, NONEGATEDARCS|STATICTECHNOLOGY, 0, 0},\t\t\t\t\t\t\t/* miscellaneous */\n");
	xprintf(f, "#endif\n");

	/* write legal banner */
	xprintf(f, "/*\n");
	xprintf(f, " * Electric(tm) VLSI Design System\n");
	xprintf(f, " *\n");
	xprintf(f, " * File: %s.c\n", techname);
	xprintf(f, " * %s technology description\n", techname);
	xprintf(f, " * Generated automatically from a library\n");
	xprintf(f, " *\n");
	strcpy(date, timetostring(getcurrenttime()));
	date[24] = 0;
	xprintf(f, " * Copyright (c) %s Static Free Software.\n", &date[20]);
	xprintf(f, " *\n");
	xprintf(f, " * Electric(tm) is free software; you can redistribute it and/or modify\n");
	xprintf(f, " * it under the terms of the GNU General Public License as published by\n");
	xprintf(f, " * the Free Software Foundation; either version 2 of the License, or\n");
	xprintf(f, " * (at your option) any later version.\n");
	xprintf(f, " *\n");
	xprintf(f, " * Electric(tm) is distributed in the hope that it will be useful,\n");
	xprintf(f, " * but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
	xprintf(f, " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n");
	xprintf(f, " * GNU General Public License for more details.\n");
	xprintf(f, " *\n");
	xprintf(f, " * You should have received a copy of the GNU General Public License\n");
	xprintf(f, " * along with Electric(tm); see the file COPYING.  If not, write to\n");
	xprintf(f, " * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,\n");
	xprintf(f, " * Boston, Mass 02111-1307, USA.\n");
	xprintf(f, " *\n");
	xprintf(f, " * Static Free Software\n");
	xprintf(f, " * 4119 Alpine Road\n");
	xprintf(f, " * Portola Valley, California 94028\n");
	xprintf(f, " * info@staticfreesoft.com\n");
	xprintf(f, " */\n");

	/* write header */
	xprintf(f, "#include \"global.h\"\n");
	xprintf(f, "#include \"egraphics.h\"\n");
	xprintf(f, "#include \"tech.h\"\n");
	xprintf(f, "#include \"efunction.h\"\n");

	/* write the layer declarations */
	xprintf(f, "\n/******************** LAYERS ********************/\n");
	k = 8;
	for(i=0; i<tech->layercount; i++) k = maxi(k, strlen(us_teclayer_iname[i]));
	k++;
	xprintf(f, "\n#define MAXLAYERS");
	for(j=8; j<k; j++) xprintf(f, " ");
	xprintf(f, "%ld\n", tech->layercount);
	for(i=0; i<tech->layercount; i++)
	{
		xprintf(f, "#define L%s", us_teclayer_iname[i]);
		for(j=strlen(us_teclayer_iname[i]); j<k; j++) xprintf(f, " ");
		xprintf(f, "%ld\t\t\t\t/* %s */\n", i, us_teclayer_names[i]);
	}
	xprintf(f, "\n");

	/* write the layer descriptions */
	for(i=0; i<tech->layercount; i++)
	{
		xprintf(f, "static GRAPHICS %s_%s_lay = {", us_tecedmakesymbol(techname),
			us_teclayer_iname[i]);
		switch (tech->layers[i]->bits)
		{
			case LAYERT1: xprintf(f, "LAYERT1, ");   break;
			case LAYERT2: xprintf(f, "LAYERT2, ");   break;
			case LAYERT3: xprintf(f, "LAYERT3, ");   break;
			case LAYERT4: xprintf(f, "LAYERT4, ");   break;
			case LAYERT5: xprintf(f, "LAYERT5, ");   break;
			case LAYERO:  xprintf(f, "LAYERO, ");    break;
		}
		if (ecolorname(tech->layers[i]->col, &colorname, &colorsymbol)) colorsymbol = "unknown";
		xprintf(f, "%s, ", colorsymbol);
		if ((tech->layers[i]->colstyle&NATURE) == SOLIDC) xprintf(f, "SOLIDC"); else
		{
			xprintf(f, "PATTERNED");
			if ((tech->layers[i]->colstyle&OUTLINEPAT) != 0) xprintf(f, "|OUTLINEPAT");
		}
		xprintf(f, ", ");
		if ((tech->layers[i]->bwstyle&NATURE) == SOLIDC) xprintf(f, "SOLIDC"); else
		{
			xprintf(f, "PATTERNED");
			if ((tech->layers[i]->bwstyle&OUTLINEPAT) != 0) xprintf(f, "|OUTLINEPAT");
		}
		xprintf(f, ",");

		xprintf(f, "\n");
		for(j=0; j<8; j++) if (tech->layers[i]->raster[j] != 0) break;
		if (j >= 8)
			xprintf(f, "\t{0,0,0,0,0,0,0,0}, NOVARIABLE, 0};\n"); else
		{
			for(j=0; j<8; j++)
			{
				xprintf(f, "\t");
				if (j == 0) xprintf(f, "{");
				xprintf(f, "0x%04x", tech->layers[i]->raster[j]&0xFFFF);
				if (j == 7) xprintf(f, "}");
				xprintf(f, ",");
				if (j > 0 && j < 7) xprintf(f, " ");
				xprintf(f, "  /* ");
				for(k=0; k<16; k++)
					if ((tech->layers[i]->raster[j] & (1 << (15-k))) != 0)
						xprintf(f, "X"); else xprintf(f, " ");
				xprintf(f, " */\n");
			}
			xprintf(f, "\tNOVARIABLE, 0};\n");
		}
	}

	/* write the aggregation of all layers */
	sym = us_tecedmakesymbol(techname);
	l = strlen(sym) + 34;
	xprintf(f, "\nGRAPHICS *%s_layers[MAXLAYERS+1] = {", sym);
	for(i=0; i<tech->layercount; i++)
	{
		sym = us_tecedmakesymbol(techname);
		if (l + strlen(sym) + strlen(us_teclayer_iname[i]) + 8 > 80)
		{
			xprintf(f, "\n\t");
			l = 4;
		}
		xprintf(f, "&%s_%s_lay, ", sym, us_teclayer_iname[i]);
		l += strlen(sym) + strlen(us_teclayer_iname[i]) + 8;
	}
	xprintf(f, "NOGRAPHICS};\n");

	/* write the layer names */
	sym = us_tecedmakesymbol(techname);
	l = strlen(sym) + 40;
	xprintf(f, "static char *%s_layer_names[MAXLAYERS] = {", sym);
	for(i=0; i<tech->layercount; i++)
	{
		if (i != 0) { xprintf(f, ", "); l += 2; }
		if (us_teclayer_names[i] == 0) sym = ""; else sym = us_teclayer_names[i];
		if (l + strlen(sym) + 2 > 80)
		{
			xprintf(f, "\n\t");
			l = 4;
		}
		xprintf(f, "\"%s\"", sym);
		l += strlen(sym) + 2;
	}
	xprintf(f, "};\n");

	/* write the CIF layer names */
	if ((us_tecflags&HASCIF) != 0)
	{
		xprintf(f, "static char *%s_cif_layers[MAXLAYERS] = {", us_tecedmakesymbol(techname));
		for(i=0; i<tech->layercount; i++)
		{
			if (i != 0) xprintf(f, ", ");
			if (us_teccif_layers[i] == 0) xprintf(f, "\"\""); else
				xprintf(f, "\"%s\"", us_teccif_layers[i]);
		}
		xprintf(f, "};\n");
	}

	/* write the DXF layer numbers */
	if ((us_tecflags&HASDXF) != 0)
	{
		xprintf(f, "static char *%s_dxf_layers[MAXLAYERS] = {", us_tecedmakesymbol(techname));
		for(i=0; i<tech->layercount; i++)
		{
			if (i != 0) xprintf(f, ", ");
			xprintf(f, "\"%s\"", us_tecdxf_layers[i]);
		}
		xprintf(f, "};\n");
	}

	/* write the Calma GDS-II layer number */
	if ((us_tecflags&HASGDS) != 0)
	{
		xprintf(f, "static INTBIG %s_gds_layers[MAXLAYERS] = {", us_tecedmakesymbol(techname));
		for(i=0; i<tech->layercount; i++)
		{
			if (i != 0) xprintf(f, ", ");
			xprintf(f, "%ld", us_tecgds_layers[i]);
		}
		xprintf(f, "};\n");
	}

	/* write the layer functions */
	sym = us_tecedmakesymbol(techname);
	l = strlen(sym) + 43;
	xprintf(f, "static INTBIG %s_layer_function[MAXLAYERS] = {", sym);
	for(i=0; i<tech->layercount; i++)
	{
		if (i != 0) { xprintf(f, ", "); l += 2; }
		(void)initinfstr();
		(void)addstringtoinfstr(us_teclayer_functions[us_teclayer_function[i]&LFTYPE].constant);
		for(j=0; us_teclayer_functions[j].name != 0; j++)
		{
			if (us_teclayer_functions[j].value <= LFTYPE) continue;
			if ((us_teclayer_function[i]&us_teclayer_functions[j].value) != 0)
			{
				(void)addtoinfstr('|');
				(void)addstringtoinfstr(us_teclayer_functions[j].constant);
			}
		}
		if (tech->layers[i]->bits == LAYERT1) (void)addstringtoinfstr("|LFTRANS1"); else
		if (tech->layers[i]->bits == LAYERT2) (void)addstringtoinfstr("|LFTRANS2"); else
		if (tech->layers[i]->bits == LAYERT3) (void)addstringtoinfstr("|LFTRANS3"); else
		if (tech->layers[i]->bits == LAYERT4) (void)addstringtoinfstr("|LFTRANS4"); else
		if (tech->layers[i]->bits == LAYERT5) (void)addstringtoinfstr("|LFTRANS5");
		sym = returninfstr();
		if (l + strlen(sym) > 80)
		{
			xprintf(f, "\n\t");
			l = 4;
		}
		xprintf(f, "%s", sym);
		l += strlen(sym);
	}
	xprintf(f, "};\n");

	/* write the layer letters */
	xprintf(f, "static char *%s_layer_letters[MAXLAYERS] = {", us_tecedmakesymbol(techname));
	for(i=0; i<tech->layercount; i++)
	{
		if (i != 0) xprintf(f, ", ");
		if (us_teclayer_letters[i] == 0) xprintf(f, "\"\""); else
			xprintf(f, "\"%s\"", us_teclayer_letters[i]);
	}
	xprintf(f, "};\n");

	/* write the SPICE information */
	if ((us_tecflags&HASSPIRES) != 0)
	{
		xprintf(f, "static float %s_sim_spice_resistance[MAXLAYERS] = {",
			us_tecedmakesymbol(techname));
		for(i=0; i<tech->layercount; i++)
		{
			if (i != 0) xprintf(f, ", ");
			xprintf(f, "%g", us_tecspice_res[i]);
		}
		xprintf(f, "};\n");
	}
	if ((us_tecflags&HASSPICAP) != 0)
	{
		xprintf(f, "static float %s_sim_spice_capacitance[MAXLAYERS] = {",
			us_tecedmakesymbol(techname));
		for(i=0; i<tech->layercount; i++)
		{
			if (i != 0) xprintf(f, ", ");
			xprintf(f, "%g", us_tecspice_cap[i]);
		}
		xprintf(f, "};\n");
	}
	if ((us_tecflags&HASSPIECAP) != 0)
	{
		xprintf(f, "static float %s_sim_spice_edge_cap[MAXLAYERS] = {",
			us_tecedmakesymbol(techname));
		for(i=0; i<tech->layercount; i++)
		{
			if (i != 0) xprintf(f, ", ");
			xprintf(f, "%g", us_tecspice_ecap[i]);
		}
		xprintf(f, "};\n");
	}

	/* write the 3D information */
	if ((us_tecflags&HAS3DINFO) != 0)
	{
		xprintf(f, "static INTBIG %s_3dheight_layers[MAXLAYERS] = {",
			us_tecedmakesymbol(techname));
		for(i=0; i<tech->layercount; i++)
		{
			if (i != 0) xprintf(f, ", ");
			xprintf(f, "%ld", us_tec3d_height[i]);
		}
		xprintf(f, "};\n");
		xprintf(f, "static INTBIG %s_3dthick_layers[MAXLAYERS] = {",
			us_tecedmakesymbol(techname));
		for(i=0; i<tech->layercount; i++)
		{
			if (i != 0) xprintf(f, ", ");
			xprintf(f, "%ld", us_tec3d_thickness[i]);
		}
		xprintf(f, "};\n");
	}

	/* write the color map */
	if ((us_tecflags&HASCOLORMAP) != 0)
	{
		/* determine the five overlapping layers */
		l1 = l2 = l3 = l4 = l5 = 0;
		for(i=0; i<tech->layercount; i++)
		{
			if (tech->layers[i]->bits == LAYERT1 && l1 == 0)
				l1 = us_teclayer_names[i]; else
			if (tech->layers[i]->bits == LAYERT2 && l2 == 0)
				l2 = us_teclayer_names[i]; else
			if (tech->layers[i]->bits == LAYERT3 && l3 == 0)
				l3 = us_teclayer_names[i]; else
			if (tech->layers[i]->bits == LAYERT4 && l4 == 0)
				l4 = us_teclayer_names[i]; else
			if (tech->layers[i]->bits == LAYERT5 && l5 == 0)
				l5 = us_teclayer_names[i];
		}
		if (l1 == 0) l1 = "layer 1";
		if (l2 == 0) l2 = "layer 2";
		if (l3 == 0) l3 = "layer 3";
		if (l4 == 0) l4 = "layer 4";
		if (l5 == 0) l5 = "layer 5";
		xprintf(f, "\nstatic TECH_COLORMAP %s_colmap[32] =\n{\n", us_tecedmakesymbol(techname));
		for(i=0; i<32; i++)
		{
			xprintf(f, "\t{%3d,%3d,%3d}, /* %2d: ", us_teccolmap[i].red,
				us_teccolmap[i].green, us_teccolmap[i].blue, i);
			if ((i&1) != 0) xprintf(f, "%s", l1); else
				for(j=0; j<(INTBIG)strlen(l1); j++) xprintf(f, " ");
			xprintf(f, "+");
			if ((i&2) != 0) xprintf(f, "%s", l2); else
				for(j=0; j<(INTBIG)strlen(l2); j++) xprintf(f, " ");
			xprintf(f, "+");
			if ((i&4) != 0) xprintf(f, "%s", l3); else
				for(j=0; j<(INTBIG)strlen(l3); j++) xprintf(f, " ");
			xprintf(f, "+");
			if ((i&8) != 0) xprintf(f, "%s", l4); else
				for(j=0; j<(INTBIG)strlen(l4); j++) xprintf(f, " ");
			xprintf(f, "+");
			if ((i&16) != 0) xprintf(f, "%s", l5); else
				for(j=0; j<(INTBIG)strlen(l5); j++) xprintf(f, " ");
			xprintf(f, " */\n");
		}
		xprintf(f, "};\n");
	}

	/* write design rules */
	if ((us_tecflags&(HASDRCMINWID|HASCONDRC|HASUNCONDRC|HASCONDRCW|HASUNCONDRCW|HASCONDRCM|HASUNCONDRCM|HASEDGEDRC)) != 0)
	{
		xprintf(f, "\n/******************** DESIGN RULES ********************/\n");

		/* write the DRC minimum width information */
		if ((us_tecflags&HASDRCMINWID) != 0)
		{
			xprintf(f, "static INTBIG %s_minimum_width[MAXLAYERS] = {",
				us_tecedmakesymbol(techname));
			for(i=0; i<tech->layercount; i++)
			{
				if (i != 0) xprintf(f, ", ");
				xprintf(f, "%s", us_tecedmakefract(us_tecdrc_rules->minwidth[i]));
			}
			xprintf(f, "};\n");
			if ((us_tecflags&HASDRCMINWIDR) != 0)
			{
				xprintf(f, "static char *%s_minimum_width_rule[MAXLAYERS] = {",
					us_tecedmakesymbol(techname));
				for(i=0; i<tech->layercount; i++)
				{
					if (i != 0) xprintf(f, ", ");
					xprintf(f, "\"%s\"", us_tecdrc_rules->minwidthR[i]);
				}
				xprintf(f, "};\n");
			}
		}

		if ((us_tecflags&HASCONDRC) != 0)
		{
			xprintf(f, "\nstatic INTBIG %s_connectedtable[] = {\n",
				us_tecedmakesymbol(techname));
			us_teceditdumpdrctab(f, us_tecdrc_rules->conlist, tech, FALSE);
			if ((us_tecflags&HASCONDRCR) != 0)
			{
				xprintf(f, "\nstatic char *%s_connectedtable_rule[] = {\n",
					us_tecedmakesymbol(techname));
				us_teceditdumpdrctab(f, us_tecdrc_rules->conlistR, tech, TRUE);
			}
		}
		if ((us_tecflags&HASUNCONDRC) != 0)
		{
			xprintf(f, "\nstatic INTBIG %s_unconnectedtable[] = {\n",
				us_tecedmakesymbol(techname));
			us_teceditdumpdrctab(f, us_tecdrc_rules->unconlist, tech, FALSE);
			if ((us_tecflags&HASUNCONDRCR) != 0)
			{
				xprintf(f, "\nstatic char *%s_unconnectedtable_rule[] = {\n",
					us_tecedmakesymbol(techname));
				us_teceditdumpdrctab(f, us_tecdrc_rules->unconlistR, tech, TRUE);
			}
		}
		if ((us_tecflags&HASCONDRCW) != 0)
		{
			xprintf(f, "\nstatic INTBIG %s_connectedtable_wide[] = {\n",
				us_tecedmakesymbol(techname));
			us_teceditdumpdrctab(f, us_tecdrc_rules->conlistW, tech, FALSE);
			if ((us_tecflags&HASCONDRCWR) != 0)
			{
				xprintf(f, "\nstatic char *%s_connectedtable_wide_rule[] = {\n",
					us_tecedmakesymbol(techname));
				us_teceditdumpdrctab(f, us_tecdrc_rules->conlistWR, tech, TRUE);
			}
		}
		if ((us_tecflags&HASUNCONDRCW) != 0)
		{
			xprintf(f, "\nstatic INTBIG %s_unconnectedtable_wide[] = {\n",
				us_tecedmakesymbol(techname));
			us_teceditdumpdrctab(f, us_tecdrc_rules->unconlistW, tech, FALSE);
			if ((us_tecflags&HASUNCONDRCWR) != 0)
			{
				xprintf(f, "\nstatic char *%s_unconnectedtable_wide_rule[] = {\n",
					us_tecedmakesymbol(techname));
				us_teceditdumpdrctab(f, us_tecdrc_rules->unconlistWR, tech, TRUE);
			}
		}
		if ((us_tecflags&HASCONDRCM) != 0)
		{
			xprintf(f, "\nstatic INTBIG %s_connectedtable_multi[] = {\n",
				us_tecedmakesymbol(techname));
			us_teceditdumpdrctab(f, us_tecdrc_rules->conlistM, tech, FALSE);
			if ((us_tecflags&HASCONDRCMR) != 0)
			{
				xprintf(f, "\nstatic char *%s_connectedtable_multi_rule[] = {\n",
					us_tecedmakesymbol(techname));
				us_teceditdumpdrctab(f, us_tecdrc_rules->conlistMR, tech, TRUE);
			}
		}
		if ((us_tecflags&HASUNCONDRCM) != 0)
		{
			xprintf(f, "\nstatic INTBIG %s_unconnectedtable_multi[] = {\n",
				us_tecedmakesymbol(techname));
			us_teceditdumpdrctab(f, us_tecdrc_rules->unconlistM, tech, FALSE);
			if ((us_tecflags&HASUNCONDRCMR) != 0)
			{
				xprintf(f, "\nstatic char *%s_unconnectedtable_multi_rule[] = {\n",
					us_tecedmakesymbol(techname));
				us_teceditdumpdrctab(f, us_tecdrc_rules->unconlistMR, tech, TRUE);
			}
		}
		if ((us_tecflags&HASEDGEDRC) != 0)
		{
			xprintf(f, "\nstatic INTBIG %s_edgetable[] = {\n",
				us_tecedmakesymbol(techname));
			us_teceditdumpdrctab(f, us_tecdrc_rules->edgelist, tech, FALSE);
			if ((us_tecflags&HASEDGEDRCR) != 0)
			{
				xprintf(f, "\nstatic char *%s_edgetable_rule[] = {\n",
					us_tecedmakesymbol(techname));
				us_teceditdumpdrctab(f, us_tecdrc_rules->edgelistR, tech, TRUE);
			}
		}
	}
}

void us_teceditdumpdrctab(FILE *f, void *distances, TECHNOLOGY *tech, BOOLEAN isstring)
{
	REGISTER INTBIG i, j;
	REGISTER INTBIG amt, mod, *amtlist;
	char shortname[7], *msg, **distlist;

	for(i=0; i<6; i++)
	{
		xprintf(f, "/*            ");
		for(j=0; j<tech->layercount; j++)
		{
			if ((INTBIG)strlen(us_teclayer_iname[j]) <= i) xprintf(f, " "); else
				xprintf(f, "%c", us_teclayer_iname[j][i]);
			xprintf(f, "  ");
		}
		xprintf(f, " */\n");
	}
	if (isstring) distlist = (char **)distances; else
		amtlist = (INTBIG *)distances;
	for(j=0; j<tech->layercount; j++)
	{
		(void)strncpy(shortname, us_teclayer_iname[j], 6);
		shortname[6] = 0;
		xprintf(f, "/* %-6s */ ", shortname);
		for(i=0; i<j; i++) xprintf(f, "   ");
		for(i=j; i<tech->layercount; i++)
		{
			if (isstring)
			{
				msg = *distlist++;
				xprintf(f, "\"%s\"", msg);
			} else
			{
				amt = *amtlist++;
				if (amt < 0) xprintf(f, "XX"); else
				{
					mod = amt % WHOLE;
					if (mod == 0) xprintf(f, "K%ld", amt/WHOLE); else
					if (mod == WHOLE/2) xprintf(f, "H%ld", amt/WHOLE); else
					if (mod == WHOLE/4) xprintf(f, "Q%ld", amt/WHOLE); else
					if (mod == WHOLE/4*3) xprintf(f, "T%ld", amt/WHOLE); else
						xprintf(f, "%ld", amt);
				}
			}
			if (j != tech->layercount-1 || i != tech->layercount-1)
				xprintf(f, ",");
		}
		xprintf(f, "\n");
	}
	xprintf(f, "};\n");
}

/*
 * routine to dump the arc information in technology "tech" to the stream in
 * "f".
 */
void us_teceditdumparcs(FILE *f, TECHNOLOGY *tech, char *techname)
{
	REGISTER INTBIG i, j, k;

	/* print the header */
	xprintf(f, "\n/******************** ARCS ********************/\n");

	/* compute the width of the widest arc name */
	k = 12;
	for(i=0; i<tech->arcprotocount; i++)
		k = maxi(k, strlen(tech->arcprotos[i]->arcname));
	k++;

	/* write the number of arcs */
	xprintf(f, "\n#define ARCPROTOCOUNT");
	for(j=12; j<k; j++) xprintf(f, " ");
	xprintf(f, "%ld\n", tech->arcprotocount);

	/* write defines for each arc */
	for(i=0; i<tech->arcprotocount; i++)
	{
		xprintf(f, "#define A%s", us_tecedmakeupper(tech->arcprotos[i]->arcname));
		for(j=strlen(tech->arcprotos[i]->arcname); j<k; j++)
			xprintf(f, " ");
		xprintf(f, "%ld\t\t\t\t/* %s */\n", i, tech->arcprotos[i]->arcname);
	}

	/* now write the arcs */
	for(i=0; i<tech->arcprotocount; i++)
	{
		xprintf(f, "\nstatic TECH_ARCLAY %s_al_%ld[] = {",
			us_tecedmakesymbol(techname), i);
		for(k=0; k<tech->arcprotos[i]->laycount; k++)
		{
			if (k != 0) xprintf(f, ", ");
			xprintf(f, "{");
			xprintf(f, "L%s,", us_teclayer_iname[tech->arcprotos[i]->list[k].lay]);
			if (tech->arcprotos[i]->list[k].off == 0) xprintf(f, "0,"); else
				xprintf(f, "%s,", us_tecedmakefract(tech->arcprotos[i]->list[k].off));
			if (tech->arcprotos[i]->list[k].style == FILLED) xprintf(f, "FILLED}"); else
				xprintf(f, "CLOSED}");
		}
		xprintf(f, "};\n");
		xprintf(f, "static TECH_ARCS %s_a_%ld = {\n", us_tecedmakesymbol(techname), i);
		xprintf(f, "\t\"%s\", ", tech->arcprotos[i]->arcname);
		xprintf(f, "%s, ", us_tecedmakefract(tech->arcprotos[i]->arcwidth));
		xprintf(f, "A%s,NOARCPROTO,\n", us_tecedmakeupper(tech->arcprotos[i]->arcname));
		xprintf(f, "\t%d, %s_al_%ld,\n", tech->arcprotos[i]->laycount,
			us_tecedmakesymbol(techname), i);
		for(j=0; us_tecarc_functions[j].name != 0; j++)
			if (us_tecarc_functions[j].value ==
				(INTBIG)((tech->arcprotos[i]->initialbits&AFUNCTION)>>AFUNCTIONSH))
		{
			xprintf(f, "\t(%s<<AFUNCTIONSH)", us_tecarc_functions[j].constant);
			break;
		}
		if (us_tecarc_functions[j].name == 0)                    xprintf(f, "\t(APUNKNOWN<<AFUNCTIONSH)");
		if ((tech->arcprotos[i]->initialbits&WANTFIXANG) != 0)   xprintf(f, "|WANTFIXANG");
		if ((tech->arcprotos[i]->initialbits&CANWIPE) != 0)      xprintf(f, "|CANWIPE");
		if ((tech->arcprotos[i]->initialbits&WANTNOEXTEND) != 0) xprintf(f, "|WANTNOEXTEND");
		xprintf(f, "|(%ld<<AANGLEINCSH)", (tech->arcprotos[i]->initialbits&AANGLEINC)>>AANGLEINCSH);
		xprintf(f, "};\n");
	}

	/* print the summary */
	xprintf(f, "\nTECH_ARCS *%s_arcprotos[ARCPROTOCOUNT+1] = {\n\t",
		us_tecedmakesymbol(techname));
	for(i=0; i<tech->arcprotocount; i++)
		xprintf(f, "&%s_a_%ld, ", us_tecedmakesymbol(techname), i);
	xprintf(f, "((TECH_ARCS *)-1)};\n");

	/* print the variable with the width offsets */
	if ((us_tecflags&HASARCWID) != 0)
	{
		xprintf(f, "\nstatic INTBIG %s_arc_widoff[ARCPROTOCOUNT] = {",
			us_tecedmakesymbol(techname));
		for(i=0; i<tech->arcprotocount; i++)
		{
			if (i != 0) xprintf(f, ", ");
			if (us_tecarc_widoff[i] == 0) xprintf(f, "0"); else
				xprintf(f, "%s", us_tecedmakefract(us_tecarc_widoff[i]));
		}
		xprintf(f, "};\n");
	}
}

/*
 * routine to dump the node information in technology "tech" to the stream in
 * "f".
 */
void us_teceditdumpnodes(FILE *f, TECHNOLOGY *tech, char *techname)
{
	REGISTER RULE *r;
	REGISTER INTBIG i, j, k, l, tot;
	char *ab, *sym;
	BOOLEAN yaxis;
	REGISTER PCON *pc;
	REGISTER TECH_POLYGON *plist;
	REGISTER TECH_SERPENT *slist;
	REGISTER TECH_NODES *nlist;

	/* make abbreviations for each node */
	for(i=0; i<tech->nodeprotocount; i++)
	{
		(void)allocstring(&ab, makeabbrev(tech->nodeprotos[i]->nodename, FALSE), el_tempcluster);
		tech->nodeprotos[i]->creation = (NODEPROTO *)ab;

		/* loop until the name is unique */
		for(;;)
		{
			/* see if a previously assigned abbreviation is the same */
			for(j=0; j<i; j++)
				if (namesame(ab, (char *)tech->nodeprotos[j]->creation) == 0) break;
			if (j == i) break;

			/* name conflicts: change it */
			l = strlen(ab);
			if (ab[l-1] >= '0' && ab[l-1] <= '8') ab[l-1]++; else
			{
				(void)initinfstr();
				(void)addstringtoinfstr(ab);
				(void)addtoinfstr('0');
				(void)reallocstring(&ab, returninfstr(), el_tempcluster);
				tech->nodeprotos[i]->creation = (NODEPROTO *)ab;
			}
		}
	}

	/* write the port lists */
	xprintf(f, "\n/******************** PORT CONNECTIONS ********************/\n\n");
	i = 1;
	for(pc = us_tecedfirstpcon; pc != NOPCON; pc = pc->nextpcon)
	{
		pc->pcindex = i++;
		xprintf(f, "static INTBIG %s_pc_%ld[] = {-1, ",
			us_tecedmakesymbol(techname), pc->pcindex);
		for(j=0; j<pc->total; j++)
		{
			k = pc->connects[j+1];
			xprintf(f, "A%s, ", us_tecedmakeupper(tech->arcprotos[k]->arcname));
		}
		xprintf(f, "ALLGEN, -1};\n");
	}

	xprintf(f, "\n/******************** RECTANGLE DESCRIPTIONS ********************/");
	xprintf(f, "\n\n");

	/* print box information */
	i = 1;
	for(r = us_tecedfirstrule; r != NORULE; r = r->nextrule)
	{
		if (!r->used) continue;
		r->rindex = i++;
		xprintf(f, "static INTBIG %s_box%ld[%ld] = {",
			us_tecedmakesymbol(techname), r->rindex, r->count);
		for(j=0; j<r->count; j += 2)
		{
			if (j != 0) xprintf(f, ", ");
			if ((j%4) == 0) yaxis = FALSE; else yaxis = TRUE;
			xprintf(f, "%s", us_tecededgelabel(r->value[j], r->value[j+1], yaxis));
		}
		if (r->istext != 0)
			xprintf(f, ", \"%s\"", (char *)r->value[r->count]);
		xprintf(f, "};\n");
	}

	xprintf(f, "\n/******************** NODES ********************/\n");

	/* compute widest node name */
	k = 13;
	for(i=0; i<tech->nodeprotocount; i++)
		k = maxi(k, strlen((char *)tech->nodeprotos[i]->creation));
	k++;

	/* write the total define */
	xprintf(f, "\n#define NODEPROTOCOUNT");
	for(j=13; j<k; j++) xprintf(f, " ");
	xprintf(f, "%ld\n", tech->nodeprotocount);

	/* write the other defines */
	for(i=0; i<tech->nodeprotocount; i++)
	{
		ab = (char *)tech->nodeprotos[i]->creation;
		xprintf(f, "#define N%s", us_tecedmakeupper(ab));
		for(j=strlen(ab); j<k; j++) xprintf(f, " ");
		xprintf(f, "%ld\t\t\t\t/* %s */\n", i+1, tech->nodeprotos[i]->nodename);
	}

	/* print node information */
	for(i=0; i<tech->nodeprotocount; i++)
	{
		/* header comment */
		nlist = tech->nodeprotos[i];
		ab = (char *)nlist->creation;
		xprintf(f, "\n/* %s */\n", nlist->nodename);

		/* print ports */
		xprintf(f, "static TECH_PORTS %s_%s_p[] = {\n", us_tecedmakesymbol(techname), ab);
		for(j=0; j<nlist->portcount; j++)
		{
			if (j != 0) xprintf(f, ",\n");

			/* the name of the connection structure */
			for(pc = us_tecedfirstpcon; pc != NOPCON; pc = pc->nextpcon)
				if (pc->connects == nlist->portlist[j].portarcs) break;
			if (pc != NOPCON)
				xprintf(f, "\t{%s_pc_%ld, ", us_tecedmakesymbol(techname), pc->pcindex);

			/* the port name */
			xprintf(f, "\"%s\", NOPORTPROTO, ", nlist->portlist[j].protoname);

			/* the port userbits */
			xprintf(f, "(%ld<<PORTARANGESH)",
				(nlist->portlist[j].initialbits&PORTARANGE)>>PORTARANGESH);
			if ((nlist->portlist[j].initialbits&PORTANGLE) != 0)
				xprintf(f, "|(%ld<<PORTANGLESH)",
					(nlist->portlist[j].initialbits&PORTANGLE)>>PORTANGLESH);
			if ((nlist->portlist[j].initialbits&PORTNET) != 0)
				xprintf(f, "|(%ld<<PORTNETSH)", (nlist->portlist[j].initialbits&PORTNET)>>PORTNETSH);
			xprintf(f, ",\n");

			/* the port area */
			xprintf(f, "\t\t%s, %s, %s, %s}",
				us_tecededgelabel(nlist->portlist[j].lowxmul, nlist->portlist[j].lowxsum, FALSE),
				us_tecededgelabel(nlist->portlist[j].lowymul, nlist->portlist[j].lowysum, TRUE),
				us_tecededgelabel(nlist->portlist[j].highxmul, nlist->portlist[j].highxsum, FALSE),
				us_tecededgelabel(nlist->portlist[j].highymul, nlist->portlist[j].highysum, TRUE));
		}
		xprintf(f, "};\n");

		/* print layers */
		for(k=0; k<2; k++)
		{
			if (nlist->special == SERPTRANS)
			{
				if (k == 0)
				{
					xprintf(f, "static TECH_SERPENT %s_%s_l[] = {\n",
						us_tecedmakesymbol(techname), ab);
					tot = nlist->layercount;
				} else
				{
					xprintf(f, "static TECH_SERPENT %s_%sE_l[] = {\n",
						us_tecedmakesymbol(techname), ab);
					tot = nlist->layercount + 1;
				}
			} else
			{
				if (k != 0) continue;
				xprintf(f, "static TECH_POLYGON %s_%s_l[] = {\n",
					us_tecedmakesymbol(techname), ab);
				tot = nlist->layercount;
			}
			for(j=0; j<tot; j++)
			{
				if (j != 0) xprintf(f, ",\n");
				xprintf(f, "\t");
				if (nlist->special == SERPTRANS)
				{
					xprintf(f, "{");
					if (k == 0) plist = &nlist->gra[j].basics;
						else plist = &nlist->ele[j].basics;
				} else plist = &nlist->layerlist[j];
				xprintf(f, "{L%s,", us_teclayer_iname[plist->layernum]);
				xprintf(f, " %d,", plist->portnum);
				xprintf(f, " %d,", plist->count);
				switch (plist->style)
				{
					case FILLEDRECT:     xprintf(f, " FILLEDRECT,");     break;
					case CLOSEDRECT:     xprintf(f, " CLOSEDRECT,");     break;
					case CROSSED:        xprintf(f, " CROSSED,");        break;
					case FILLED:         xprintf(f, " FILLED,");         break;
					case CLOSED:         xprintf(f, " CLOSED,");         break;
					case OPENED:         xprintf(f, " OPENED,");         break;
					case OPENEDT1:       xprintf(f, " OPENEDT1,");       break;
					case OPENEDT2:       xprintf(f, " OPENEDT2,");       break;
					case OPENEDT3:       xprintf(f, " OPENEDT3,");       break;
					case VECTORS:        xprintf(f, " VECTORS,");        break;
					case CIRCLE:         xprintf(f, " CIRCLE,");         break;
					case THICKCIRCLE:    xprintf(f, " THICKCIRCLE,");    break;
					case DISC:           xprintf(f, " DISC,");           break;
					case CIRCLEARC:      xprintf(f, " CIRCLEARC,");      break;
					case THICKCIRCLEARC: xprintf(f, " THICKCIRCLEARC,"); break;
					case TEXTCENT:       xprintf(f, " TEXTCENT,");       break;
					case TEXTTOP:        xprintf(f, " TEXTTOP,");        break;
					case TEXTBOT:        xprintf(f, " TEXTBOT,");        break;
					case TEXTLEFT:       xprintf(f, " TEXTLEFT,");       break;
					case TEXTRIGHT:      xprintf(f, " TEXTRIGHT,");      break;
					case TEXTTOPLEFT:    xprintf(f, " TEXTTOPLEFT,");    break;
					case TEXTBOTLEFT:    xprintf(f, " TEXTBOTLEFT,");    break;
					case TEXTTOPRIGHT:   xprintf(f, " TEXTTOPRIGHT,");   break;
					case TEXTBOTRIGHT:   xprintf(f, " TEXTBOTRIGHT,");   break;
					case TEXTBOX:        xprintf(f, " TEXTBOX,");        break;
					default:             xprintf(f, " ????,");           break;
				}
				switch (plist->representation)
				{
					case BOX:    xprintf(f, " BOX,");     break;
					case MINBOX: xprintf(f, " MINBOX,");  break;
					case POINTS: xprintf(f, " POINTS,");  break;
					default:     xprintf(f, " ????,");    break;
				}
				for(r = us_tecedfirstrule; r != NORULE; r = r->nextrule)
					if (r->value == plist->points) break;
				if (r != NORULE)
					xprintf(f, " %s_box%ld", us_tecedmakesymbol(techname), r->rindex); else
						xprintf(f, " %s_box??", us_tecedmakesymbol(techname));
				xprintf(f, "}");
				if (nlist->special == SERPTRANS)
				{
					if (k == 0) slist = &nlist->gra[j]; else
						slist = &nlist->ele[j];
					xprintf(f, ", %s", us_tecedmakefract(slist->lwidth));
					xprintf(f, ", %s", us_tecedmakefract(slist->rwidth));
					xprintf(f, ", %s}", us_tecedmakefract(slist->extend));
				}
			}
			xprintf(f, "};\n");
		}

		/* print the node information */
		xprintf(f, "static TECH_NODES %s_%s = {\n", us_tecedmakesymbol(techname), ab);
		xprintf(f, "\t\"%s\", N%s, NONODEPROTO,\n", nlist->nodename, us_tecedmakeupper(ab));
		xprintf(f, "\t%s,", us_tecedmakefract(nlist->xsize));
		xprintf(f, " %s,\n", us_tecedmakefract(nlist->ysize));
		xprintf(f, "\t%d, %s_%s_p,\n", nlist->portcount, us_tecedmakesymbol(techname), ab);
		if (nlist->special == SERPTRANS)
			xprintf(f, "\t%d, (TECH_POLYGON *)0,\n", nlist->layercount); else
				xprintf(f, "\t%d, %s_%s_l,\n", nlist->layercount,
					us_tecedmakesymbol(techname), ab);
		j = (nlist->initialbits&NFUNCTION)>>NFUNCTIONSH;
		if (j < 0 || j >= MAXNODEFUNCTION) j = 0;
		xprintf(f, "\t(%s<<NFUNCTIONSH)", nodefunctionconstantname(j));
		if ((nlist->initialbits&WIPEON1OR2) != 0) xprintf(f, "|WIPEON1OR2");
		if ((nlist->initialbits&HOLDSTRACE) != 0) xprintf(f, "|HOLDSTRACE");
		if ((nlist->initialbits&NSQUARE) != 0)    xprintf(f, "|NSQUARE");
		if ((nlist->initialbits&ARCSWIPE) != 0)   xprintf(f, "|ARCSWIPE");
		if ((nlist->initialbits&ARCSHRINK) != 0)  xprintf(f, "|ARCSHRINK");
		if ((nlist->initialbits&NODESHRINK) != 0) xprintf(f, "|NODESHRINK");
		if ((nlist->initialbits&LOCKEDPRIM) != 0) xprintf(f, "|LOCKEDPRIM");
		xprintf(f, ",\n");
		switch (nlist->special)
		{
			case 0:
				xprintf(f, "\t0,0,0,0,0,0,0,0,0");
				break;
			case SERPTRANS:
				xprintf(f, "\tSERPTRANS,%d,", nlist->f1);
				xprintf(f, "%s,", us_tecedmakefract(nlist->f2));
				xprintf(f, "%s,", us_tecedmakefract(nlist->f3));
				xprintf(f, "%s,", us_tecedmakefract(nlist->f4));
				xprintf(f, "%s,", us_tecedmakefract(nlist->f5));
				xprintf(f, "%s,", us_tecedmakefract(nlist->f6));
				xprintf(f, "%s_%s_l,", us_tecedmakesymbol(techname), ab);
				xprintf(f, "%s_%sE_l", us_tecedmakesymbol(techname), ab);
				break;
			case MULTICUT:
				xprintf(f, "\tMULTICUT,%s,", us_tecedmakefract(nlist->f1));
				xprintf(f, "%s,", us_tecedmakefract(nlist->f2));
				xprintf(f, "%s,", us_tecedmakefract(nlist->f3));
				xprintf(f, "%s,0,0,0,0", us_tecedmakefract(nlist->f4));
				break;
			case POLYGONAL:
				xprintf(f, "\tPOLYGONAL,0,0,0,0,0,0,0,0");
				break;
		}
		xprintf(f, "};\n");
	}

	/* print summary of nodes */
	xprintf(f, "\nTECH_NODES *%s_nodeprotos[NODEPROTOCOUNT+1] = {\n\t",
		us_tecedmakesymbol(techname));
	l = 4;
	for(i=0; i<tech->nodeprotocount; i++)
	{
		sym = us_tecedmakesymbol(techname);
		if (l + strlen(sym) + strlen((char *)tech->nodeprotos[i]->creation) + 4 > 80)
		{
			xprintf(f, "\n\t");
			l = 4;
		}
		xprintf(f, "&%s_%s, ", sym, (char *)tech->nodeprotos[i]->creation);
		l += strlen(sym) + strlen((char *)tech->nodeprotos[i]->creation) + 4;
	}
	xprintf(f, "((TECH_NODES *)-1)};\n");

	/* print highlight offset information */
	xprintf(f, "\nstatic INTBIG %s_node_widoff[NODEPROTOCOUNT*4] = {\n\t",
		us_tecedmakesymbol(techname));
	l = 4;
	for(i=0; i<tech->nodeprotocount; i++)
	{
		if (i != 0) { xprintf(f, ", "); l += 2; }
		(void)initinfstr();
		if (us_tecnode_widoff[i*4] == 0) (void)addtoinfstr('0'); else
			(void)addstringtoinfstr(us_tecedmakefract(us_tecnode_widoff[i*4]));
		(void)addtoinfstr(',');
		if (us_tecnode_widoff[i*4+1] == 0) (void)addtoinfstr('0'); else
			(void)addstringtoinfstr(us_tecedmakefract(us_tecnode_widoff[i*4+1]));
		(void)addtoinfstr(',');
		if (us_tecnode_widoff[i*4+2] == 0) (void)addtoinfstr('0'); else
			(void)addstringtoinfstr(us_tecedmakefract(us_tecnode_widoff[i*4+2]));
		(void)addtoinfstr(',');
		if (us_tecnode_widoff[i*4+3] == 0) (void)addtoinfstr('0'); else
			(void)addstringtoinfstr(us_tecedmakefract(us_tecnode_widoff[i*4+3]));
		sym = returninfstr();
		l += strlen(sym);
		if (l > 80)
		{
			xprintf(f, "\n\t");
			l = 4;
		}
		xprintf(f, "%s", sym);
	}
	xprintf(f, "};\n");

	/* print grab point informaton if it exists */
	if ((us_tecflags&HASGRAB) != 0 && us_tecnode_grabcount > 0)
	{
		xprintf(f, "\nstatic INTBIG %s_centergrab[] = {\n", us_tecedmakesymbol(techname));
		for(i=0; i<us_tecnode_grabcount; i += 3)
		{
			ab = (char *)tech->nodeprotos[us_tecnode_grab[i]-1]->creation;
			xprintf(f, "\tN%s, %ld, %ld", us_tecedmakeupper(ab), us_tecnode_grab[i+1],
				us_tecnode_grab[i+2]);
			if (i != us_tecnode_grabcount-3) xprintf(f, ",\n");
		}
		xprintf(f, "\n};\n");
	}

	/* clean up */
	for(i=0; i<tech->nodeprotocount; i++)
	{
		efree((char *)tech->nodeprotos[i]->creation);
		tech->nodeprotos[i]->creation = NONODEPROTO;
	}
}

/*
 * routine to dump the variable information in technology "tech" to the stream in
 * "f".
 */
void us_teceditdumpvars(FILE *f, TECHNOLOGY *tech, char *techname)
{
	REGISTER INTBIG i, j, k;
	REGISTER char *pt;
	REGISTER VARIABLE *var;

	xprintf(f, "\n/******************** VARIABLE AGGREGATION ********************/\n");

	/* write any miscellaneous string array variables */
	for(i=0; us_knownvars[i].varname != 0; i++)
	{
		var = getval((INTBIG)tech, VTECHNOLOGY, -1, us_knownvars[i].varname);
		if (var == NOVARIABLE) continue;
		if ((var->type&(VTYPE|VISARRAY)) == (VSTRING|VISARRAY))
		{
			xprintf(f, "\nchar *%s_%s[] = {\n", us_tecedmakesymbol(techname),
				us_knownvars[i].varname);
			j = getlength(var);
			for(k=0; k<j; k++)
			{
				xprintf(f, "\t\"");
				for(pt = ((char **)var->addr)[k]; *pt != 0; pt++)
				{
					if (*pt == '"') xprintf(f, "\\");
					xprintf(f, "%c", *pt);
				}
				xprintf(f, "\",\n");
			}
			xprintf(f, "\tNOSTRING};\n");
		}
	}

	xprintf(f, "\nTECH_VARIABLES %s_variables[] =\n{\n", us_tecedmakesymbol(techname));

	xprintf(f, "\t{\"TECH_layer_names\", (char *)%s_layer_names, 0.0,\n",
		us_tecedmakesymbol(techname));
	xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)},\n");

	xprintf(f, "\t{\"TECH_layer_function\", (char *)%s_layer_function, 0.0,\n",
		us_tecedmakesymbol(techname));
	xprintf(f, "\t\tVINTEGER|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)},\n");

	xprintf(f, "\t{\"TECH_node_width_offset\", (char *)%s_node_widoff, 0.0,\n",
		us_tecedmakesymbol(techname));
	xprintf(f, "\t\tVFRACT|VDONTSAVE|VISARRAY|((NODEPROTOCOUNT*4)<<VLENGTHSH)},\n");
	if ((us_tecflags&HASGRAB) != 0 && us_tecnode_grabcount > 0)
		xprintf(f, "\t{\"prototype_center\", (char *)%s_centergrab, 0.0, %ld},\n",
			us_tecedmakesymbol(techname), us_tecnode_grabcount/3);

	if ((us_tecflags&HASARCWID) != 0)
	{
		xprintf(f, "\t{\"TECH_arc_width_offset\", (char *)%s_arc_widoff, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFRACT|VDONTSAVE|VISARRAY|(ARCPROTOCOUNT<<VLENGTHSH)},\n");
	}

	if ((us_tecflags&HAS3DINFO) != 0)
	{
		xprintf(f, "\t{\"TECH_layer_3dthickness\", (char *)%s_3dthick_layers, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVINTEGER|VDONTSAVE|VISARRAY|(ARCPROTOCOUNT<<VLENGTHSH)},\n");
		xprintf(f, "\t{\"TECH_layer_3dheight\", (char *)%s_3dheight_layers, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVINTEGER|VDONTSAVE|VISARRAY|(ARCPROTOCOUNT<<VLENGTHSH)},\n");
	}

	xprintf(f, "\t{\"USER_layer_letters\", (char *)%s_layer_letters, 0.0,\n",
		us_tecedmakesymbol(techname));
	xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)},\n");

	if ((us_tecflags&HASCOLORMAP) != 0)
	{
		xprintf(f, "\t{\"USER_color_map\", (char *)%s_colmap, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVCHAR|VDONTSAVE|VISARRAY|((sizeof %s_colmap)<<VLENGTHSH)},\n",
			us_tecedmakesymbol(techname));
	}

	if ((us_tecflags&HASCIF) != 0)
	{
		xprintf(f, "\t{\"IO_cif_layer_names\", (char *)%s_cif_layers, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)},\n");
	}

	if ((us_tecflags&HASDXF) != 0)
	{
		xprintf(f, "\t{\"IO_dxf_layer_names\", (char *)%s_dxf_layers, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)},\n");
	}

	if ((us_tecflags&HASGDS) != 0)
	{
		xprintf(f, "\t{\"IO_gds_layer_numbers\", (char *)%s_gds_layers, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVINTEGER|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)},\n");
	}

	if ((us_tecflags&HASDRCMINWID) != 0)
	{
		xprintf(f, "\t{\"DRC_min_width\", (char *)%s_minimum_width, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFRACT|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)},\n");
		if ((us_tecflags&HASDRCMINWIDR) != 0)
		{
			xprintf(f, "\t{\"DRC_min_width_rule\", (char *)%s_minimum_width_rule, 0.0,\n",
				us_tecedmakesymbol(techname));
			xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)},\n");
		}
	}
	if ((us_tecflags&(HASCONDRCW|HASUNCONDRCW)) != 0)
	{
		xprintf(f, "\t{\"DRC_wide_limit\", (char *)%ld, 0.0,\n",
			us_tecdrc_rules->widelimit);
		xprintf(f, "\t\tVFRACT|VDONTSAVE},\n");
	}

	if ((us_tecflags&HASCONDRC) != 0)
	{
		xprintf(f, "\t{\"DRC_min_connected_distances\", (char *)%s_connectedtable, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFRACT|VDONTSAVE|VISARRAY|\n");
		xprintf(f, "\t\t\t(((sizeof %s_connectedtable)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
			us_tecedmakesymbol(techname));
		if ((us_tecflags&HASCONDRCR) != 0)
		{
			xprintf(f, "\t{\"DRC_min_connected_distances_rule\", (char *)%s_connectedtable_rule, 0.0,\n",
				us_tecedmakesymbol(techname));
			xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|\n");
			xprintf(f, "\t\t\t(((sizeof %s_connectedtable_rule)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
				us_tecedmakesymbol(techname));
		}
	}
	if ((us_tecflags&HASUNCONDRC) != 0)
	{
		xprintf(f, "\t{\"DRC_min_unconnected_distances\", (char *)%s_unconnectedtable, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFRACT|VDONTSAVE|VISARRAY|\n");
		xprintf(f, "\t\t   (((sizeof %s_unconnectedtable)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
			us_tecedmakesymbol(techname));
		if ((us_tecflags&HASUNCONDRCR) != 0)
		{
			xprintf(f, "\t{\"DRC_min_unconnected_distances_rule\", (char *)%s_unconnectedtable_rule, 0.0,\n",
				us_tecedmakesymbol(techname));
			xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|\n");
			xprintf(f, "\t\t\t(((sizeof %s_unconnectedtable_rule)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
				us_tecedmakesymbol(techname));
		}
	}

	if ((us_tecflags&HASCONDRCW) != 0)
	{
		xprintf(f, "\t{\"DRC_min_connected_distances_wide\", (char *)%s_connectedtable_wide, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFRACT|VDONTSAVE|VISARRAY|\n");
		xprintf(f, "\t\t\t(((sizeof %s_connectedtable_wide)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
			us_tecedmakesymbol(techname));
		if ((us_tecflags&HASCONDRCWR) != 0)
		{
			xprintf(f, "\t{\"DRC_min_connected_distances_wide_rule\", (char *)%s_connectedtable_wide_rule, 0.0,\n",
				us_tecedmakesymbol(techname));
			xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|\n");
			xprintf(f, "\t\t\t(((sizeof %s_connectedtable_wide_rule)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
				us_tecedmakesymbol(techname));
		}
	}
	if ((us_tecflags&HASUNCONDRCW) != 0)
	{
		xprintf(f, "\t{\"DRC_min_unconnected_distances_wide\", (char *)%s_unconnectedtable_wide, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFRACT|VDONTSAVE|VISARRAY|\n");
		xprintf(f, "\t\t   (((sizeof %s_unconnectedtable_wide)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
			us_tecedmakesymbol(techname));
		if ((us_tecflags&HASUNCONDRCWR) != 0)
		{
			xprintf(f, "\t{\"DRC_min_unconnected_distances_wide_rule\", (char *)%s_unconnectedtable_wide_rule, 0.0,\n",
				us_tecedmakesymbol(techname));
			xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|\n");
			xprintf(f, "\t\t\t(((sizeof %s_unconnectedtable_wide)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
				us_tecedmakesymbol(techname));
		}
	}

	if ((us_tecflags&HASCONDRCM) != 0)
	{
		xprintf(f, "\t{\"DRC_min_connected_distances_multi\", (char *)%s_connectedtable_multi, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFRACT|VDONTSAVE|VISARRAY|\n");
		xprintf(f, "\t\t\t(((sizeof %s_connectedtable_multi)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
			us_tecedmakesymbol(techname));
		if ((us_tecflags&HASCONDRCMR) != 0)
		{
			xprintf(f, "\t{\"DRC_min_connected_distances_multi_rule\", (char *)%s_connectedtable_multi_rule, 0.0,\n",
				us_tecedmakesymbol(techname));
			xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|\n");
			xprintf(f, "\t\t\t(((sizeof %s_connectedtable_multi_rule)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
				us_tecedmakesymbol(techname));
		}
	}
	if ((us_tecflags&HASUNCONDRCM) != 0)
	{
		xprintf(f, "\t{\"DRC_min_unconnected_distances_multi\", (char *)%s_unconnectedtable_multi, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFRACT|VDONTSAVE|VISARRAY|\n");
		xprintf(f, "\t\t   (((sizeof %s_unconnectedtable_multi)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
			us_tecedmakesymbol(techname));
		if ((us_tecflags&HASUNCONDRCMR) != 0)
		{
			xprintf(f, "\t{\"DRC_min_unconnected_distances_multi_rule\", (char *)%s_unconnectedtable_multi_rule, 0.0,\n",
				us_tecedmakesymbol(techname));
			xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|\n");
			xprintf(f, "\t\t\t(((sizeof %s_unconnectedtable_multi)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
				us_tecedmakesymbol(techname));
		}
	}
	if ((us_tecflags&HASEDGEDRC) != 0)
	{
		xprintf(f, "\t{\"DRC_min_edge_distances\", (char *)%s_edgetable, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFRACT|VDONTSAVE|VISARRAY|\n");
		xprintf(f, "\t\t   (((sizeof %s_edgetable)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
			us_tecedmakesymbol(techname));
		if ((us_tecflags&HASEDGEDRCR) != 0)
		{
			xprintf(f, "\t{\"DRC_min_edge_distances_rule\", (char *)%s_edgetable_rule, 0.0,\n",
				us_tecedmakesymbol(techname));
			xprintf(f, "\t\tVSTRING|VDONTSAVE|VISARRAY|\n");
			xprintf(f, "\t\t\t(((sizeof %s_edgetable)/SIZEOFINTBIG)<<VLENGTHSH)},\n",
				us_tecedmakesymbol(techname));
		}
	}

	if ((us_tecflags&HASSPIRES) != 0)
	{
		xprintf(f, "\t{\"SIM_spice_resistance\", (char *)%s_sim_spice_resistance, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFLOAT|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)},\n");
	}
	if ((us_tecflags&HASSPICAP) != 0)
	{
		xprintf(f, "\t{\"SIM_spice_capacitance\", (char *)%s_sim_spice_capacitance, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFLOAT|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)},\n");
	}
	if ((us_tecflags&HASSPIECAP) != 0)
	{
		xprintf(f, "\t{\"SIM_spice_edge_capacitance\", (char *)%s_sim_spice_edge_cap, 0.0,\n",
			us_tecedmakesymbol(techname));
		xprintf(f, "\t\tVFLOAT|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)},\n");
	}

	/* throw in pointers to any miscellaneous variables */
	for(i=0; us_knownvars[i].varname != 0; i++)
	{
		var = getval((INTBIG)tech, VTECHNOLOGY, -1, us_knownvars[i].varname);
		if (var == NOVARIABLE) continue;
		xprintf(f, "\t{\"%s\", ", us_knownvars[i].varname);
		switch (var->type&(VTYPE|VISARRAY))
		{
			case VINTEGER:
				xprintf(f, "(char *)%ld, 0.0, VINTEGER|VDONTSAVE", var->addr);
				break;
			case VFLOAT:
				xprintf(f, "(char *)0, %g, VFLOAT|VDONTSAVE", castfloat(var->addr));
				break;
			case VSTRING:
				xprintf(f, "\"%s\", 0.0, VSTRING|VDONTSAVE", (char *)var->addr);
				break;
			case VSTRING|VISARRAY:
				xprintf(f, "(char *)%s_%s, 0.0,\n\t\tVSTRING|VDONTSAVE|VISARRAY|(MAXLAYERS<<VLENGTHSH)",
					us_tecedmakesymbol(techname), us_knownvars[i].varname);
				break;
		}
		xprintf(f, "},\n");
	}

	xprintf(f, "\t{NULL, NULL, 0.0, 0}\n};\n");
}

/*
 * routine to convert the multiplication and addition factors in "mul" and
 * "add" into proper constant names.  The "yaxis" is false for X and 1 for Y
 */
char *us_tecededgelabel(INTBIG mul, INTBIG add, BOOLEAN yaxis)
{
	char line[20];
	REGISTER INTBIG amt;

	(void)initinfstr();

	/* handle constant distance from center (handles halves up to 5.5) */
	if (mul == 0 && (add%H0) == 0 && abs(add) < K6)
	{
		(void)addstringtoinfstr("CENTER");
		if (add == 0) return(returninfstr());
		if (!yaxis)
		{
			if (add < 0) (void)addtoinfstr('L'); else (void)addtoinfstr('R');
		} else
		{
			if (add < 0) (void)addtoinfstr('D'); else (void)addtoinfstr('U');
		}
		amt = abs(add);
		switch (amt%WHOLE)
		{
			case 0:  (void)sprintf(line, "%ld",  amt/WHOLE);   break;
			case H0: (void)sprintf(line, "%ldH", amt/WHOLE);   break;
		}
		(void)addstringtoinfstr(line);
		return(returninfstr());
	}

	/* handle constant distance from edge (handles quarters up to 10, halves to 20) */
	if ((mul == H0 || mul == -H0) &&
		(((add%Q0) == 0 && abs(add) < K10) || ((add%H0) == 0 && abs(add) < K20)))
	{
		if (!yaxis)
		{
			if (mul < 0) (void)addstringtoinfstr("LEFT"); else
				(void)addstringtoinfstr("RIGHT");
		} else
		{
			if (mul < 0) (void)addstringtoinfstr("BOT"); else
				(void)addstringtoinfstr("TOP");
		}
		if (add == 0) (void)addstringtoinfstr("EDGE"); else
		{
			amt = abs(add);
			switch (amt%WHOLE)
			{
				case 0:  (void)sprintf(line, "IN%ld",  amt/WHOLE);   break;
				case Q0: (void)sprintf(line, "IN%ldQ", amt/WHOLE);   break;
				case H0: (void)sprintf(line, "IN%ldH", amt/WHOLE);   break;
				case T0: (void)sprintf(line, "IN%ldT", amt/WHOLE);   break;
			}
			(void)addstringtoinfstr(line);
		}
		return(returninfstr());
	}

	/* generate two-value description */
	(void)addstringtoinfstr(us_tecedmakefract(mul));
	(void)addtoinfstr(',');
	(void)addstringtoinfstr(us_tecedmakefract(add));
	return(returninfstr());
}

/*
 * routine to convert the fractional value "amt" to a technology constant.
 * The presumption is that quarter values exist from K0 to K10, that
 * half values exist up to K20, that whole values exist up to K30, and
 * that other values are not necessarily defined in "tech.h".
 */
char *us_tecedmakefract(INTBIG amt)
{
	static char line[20];
	REGISTER INTBIG whole;
	REGISTER char *pt;

	pt = line;
	if (amt < 0)
	{
		*pt++ = '-';
		amt = -amt;
	}
	whole = amt/WHOLE;
	switch (amt%WHOLE)
	{
		case 0:
			if (whole <= 30) (void)sprintf(pt, "K%ld", whole); else
				(void)sprintf(pt, "%ld", amt);
			break;
		case Q0:
			if (whole <= 10) (void)sprintf(pt, "Q%ld", whole); else
				(void)sprintf(pt, "%ld", amt);
			break;
		case H0:
			if (whole <= 20) (void)sprintf(pt, "H%ld", whole); else
				(void)sprintf(pt, "%ld", amt);
			break;
		case T0:
			if (whole <= 10) (void)sprintf(pt, "T%ld", whole); else
				(void)sprintf(pt, "%ld", amt);
			break;
		default:
			(void)sprintf(pt, "%ld", amt);
			break;
	}
	return(line);
}

/*
 * routine to convert all characters in string "str" to upper case and to
 * change any nonalphanumeric characters to a "_"
 */
char *us_tecedmakeupper(char *str)
{
	REGISTER char ch;

	(void)initinfstr();
	while (*str != 0)
	{
		ch = *str++;
		if (islower(ch)) ch = toupper(ch);
		if (!isalnum(ch)) ch = '_';
		(void)addtoinfstr(ch);
	}
	return(returninfstr());
}

/*
 * routine to change any nonalphanumeric characters in string "str" to a "_"
 */
char *us_tecedmakesymbol(char *str)
{
	REGISTER char ch;

	(void)initinfstr();
	while (*str != 0)
	{
		ch = *str++;
		if (!isalnum(ch)) ch = '_';
		(void)addtoinfstr(ch);
	}
	return(returninfstr());
}
