/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: AutoStitch.java
 * Routing tool: Auto-Stitcher (places wires where geometry touches).
 * Written by Steven M. Rubin.
 *
 * Copyright (c) 2003, Static Free Software. All rights reserved.
 *
 * 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 3 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.routing;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ObjectQTree;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.geometry.PolyBase;
import com.sun.electric.database.geometry.PolyMerge;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.HierarchyEnumerator;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortOriginal;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.Name;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.topology.Geometric;
import com.sun.electric.database.topology.IconNodeInst;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.topology.RTBounds;
import com.sun.electric.database.topology.RTNode;
import com.sun.electric.database.variable.EditWindow_;
import com.sun.electric.database.variable.ElectricObject;
import com.sun.electric.database.variable.UserInterface;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.user.CircuitChangeJobs;
import com.sun.electric.tool.user.Highlighter;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.ui.EditWindow;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.ECoord;
import com.sun.electric.util.math.EDimension;
import com.sun.electric.util.math.FixpRectangle;
import com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.GenMath;

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * Class which implements the Auto Stitching tool.
 */
public class AutoStitch
{
	/** true to ignore true pin size */								private static final boolean ZEROSIZEPINS = true;

	/** router used to wire */  									private InteractiveRouter router;
    /** default sizes and text descriptors */                       private final EditingPreferences ep;
	/** list of all routes to be created at end of analysis */		private List<Route> allRoutes;
	/** list of pins that may be inline pins due to created arcs */	private List<NodeInst> possibleInlinePins;
	/** cache of true pin sizes */									private Map<PortInst,Double> truePinSize;
	/** set of nodes to check (prevents duplicate checks) */		private Set<NodeInst> nodeMark;
	/** edge alignment for arcs */									private EDimension alignment;
	/** true to stitch pure-layer nodes */							private boolean includePureLayerNodes;

    /****************************************** CONTROL ******************************************/

	/**
	 * Method to do auto-stitching.
	 * @param highlighted true to stitch only the highlighted objects.
	 * False to stitch the entire current cell.
	 * @param forced true if the stitching was explicitly requested (and so results should be printed).
	 */
	public static void autoStitch(boolean highlighted, boolean forced)
	{
		UserInterface ui = Job.getUserInterface();
		Cell cell = ui.needCurrentCell();
		if (cell == null) return;

		List<NodeInst> nodesToStitch = null;
		List<ArcInst> arcsToStitch = null;
		Rectangle2D limitBound = null;

		if (highlighted)
		{
			nodesToStitch = new ArrayList<NodeInst>();
			arcsToStitch = new ArrayList<ArcInst>();
			EditWindow_ wnd = ui.getCurrentEditWindow_();
			if (wnd == null) return;
			List<Geometric> highs = wnd.getHighlightedEObjs(true, true);
			limitBound = wnd.getHighlightedArea();
			for(Geometric geom : highs)
			{
				ElectricObject eObj = geom;
				if (eObj instanceof PortInst) eObj = ((PortInst)eObj).getNodeInst();
				if (eObj instanceof NodeInst)
				{
					NodeInst ni = (NodeInst)eObj;
					if (!ni.isCellInstance())
					{
						PrimitiveNode pnp = (PrimitiveNode)ni.getProto();
						if (pnp.getTechnology() == Generic.tech()) continue;
						if (pnp.getFunction() == PrimitiveNode.Function.NODE) continue;
					}
					nodesToStitch.add((NodeInst)eObj);
				} else if (eObj instanceof ArcInst)
				{
					arcsToStitch.add((ArcInst)eObj);
				}
			}
			if (nodesToStitch.size() == 0 && arcsToStitch.size() == 0)
			{
				if (forced) System.out.println("Nothing selected to auto-route");
				return;
			}
		}

		double lX = 0, hX = 0, lY = 0, hY = 0;
		if (limitBound != null)
		{
			lX = limitBound.getMinX();
			hX = limitBound.getMaxX();
			lY = limitBound.getMinY();
			hY = limitBound.getMaxY();
		}

		// find out the preferred routing arc
		new AutoStitchJob(cell, nodesToStitch, arcsToStitch, lX, hX, lY, hY, forced);
	}

	/**
	 * Class to do auto-stitching in a new thread.
	 */
	private static class AutoStitchJob extends Job
	{
		private Cell cell;
		private List<NodeInst> nodesToStitch;
		private List<ArcInst> arcsToStitch;
		private double lX, hX, lY, hY;
		private boolean forced;
		private AutoOptions prefs;
		private EDimension alignment;
		private List<ArcInst> newArcs;

		private AutoStitchJob(Cell cell, List<NodeInst> nodesToStitch, List<ArcInst> arcsToStitch,
			double lX, double hX, double lY, double hY, boolean forced)
		{
			super("Auto-Stitch", Routing.getRoutingTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
			this.cell = cell;
			this.nodesToStitch = nodesToStitch;
			this.arcsToStitch = arcsToStitch;
			this.lX = lX;
			this.hX = hX;
			this.lY = lY;
			this.hY = hY;
			this.forced = forced;
			setReportExecutionFlag(true);
			prefs = new AutoOptions(false);
		    ECoord resolution = cell.getTechnology().getFactoryResolution();
		    alignment = new EDimension(resolution, resolution);
			startJob();
		}

        @Override
		public boolean doIt() throws JobException
		{
			Rectangle2D limitBound = null;
			if (lX != hX && lY != hY)
				limitBound = new Rectangle2D.Double(lX, lY, hX-lX, hY-lY);
            EditingPreferences ep = getEditingPreferences();
            newArcs = runAutoStitch(cell, nodesToStitch, arcsToStitch, this, null, limitBound, forced, false, ep, prefs, false, alignment);
            fieldVariableChanged("newArcs");
			return true;
		}

        @Override
        public void terminateOK()
        {
            // highlight the new arcs
            EditWindow wnd = EditWindow.needCurrent();
            Highlighter highlighter = wnd.getHighlighter();
            highlighter.clear();
        	for(ArcInst ai : newArcs)
        		highlighter.addElectricObject(ai, ai.getParent());
            highlighter.finished();
        }
	}

	/**
	 * This is the public interface for Auto-stitching when done in batch mode.
	 * @param cell the cell in which to stitch.
	 * @param nodesToStitch a list of NodeInsts to stitch (null to use all in the cell).
	 * @param arcsToStitch a list of ArcInsts to stitch (null to use all in the cell).
	 * @param job the Job running this, for aborting.
	 * @param stayInside is the area in which to route (null to route arbitrarily).
	 * @param limitBound if not null, only consider connections that occur in this area.
	 * @param forced true if the stitching was explicitly requested (and so results should be printed).
	 * @param includePureLayerNodes true to route pure-layer nodes (normally ignored).
     * @param ep EditingPreferences
	 * @param prefs routing preferences.
	 * @param showProgress true to show progress.
	 * @param alignment grid alignment for edges of arcs (null if none).
	 * @return a List of ArcInst objects that were created.
	 */
	public static List<ArcInst> runAutoStitch(Cell cell, List<NodeInst> nodesToStitch, List<ArcInst> arcsToStitch, Job job,
		PolyMerge stayInside, Rectangle2D limitBound, boolean forced, boolean includePureLayerNodes,
		EditingPreferences ep, AutoOptions prefs, boolean showProgress, EDimension alignment)
	{
		// initialization
		if (cell.isAllLocked())
		{
			System.out.println("WARNING: Cell " + cell.describe(false) + " is locked: no changes can be made");
			return null;
		}

		AutoStitch as = new AutoStitch(ep);
		as.alignment = alignment;
		as.includePureLayerNodes = includePureLayerNodes;
		return as.runNow(cell, nodesToStitch, arcsToStitch, job, stayInside, limitBound, forced, prefs, showProgress);
	}

	private AutoStitch(EditingPreferences ep)
	{
        this.ep = ep;
		possibleInlinePins = new ArrayList<NodeInst>();
		truePinSize = new HashMap<PortInst,Double>();
        router = new SimpleWirer(ep);
	}

	private List<ArcInst> getArcsToStitch(Cell cell, List<ArcInst> arcsToStitch)
	{
		List<ArcInst> newArcsToStitch = new ArrayList<ArcInst>();
		if (arcsToStitch == null)
		{
			for(Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); )
				newArcsToStitch.add(it.next());
		} else
		{
			for(ArcInst ai : arcsToStitch)
				if (ai.isLinked()) newArcsToStitch.add(ai);
		}
		return newArcsToStitch;
	}

	private List<NodeInst> getNodesToStitch(Cell cell, List<NodeInst> nodesToStitch)
	{
		List<NodeInst> newNodesToStitch = new ArrayList<NodeInst>();
		if (nodesToStitch == null)
		{
			// no data from highlighter
			for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
			{
				NodeInst ni = it.next();
				if (ni.isIconOfParent()) continue;
				if (!ni.isCellInstance())
				{
					PrimitiveNode pnp = (PrimitiveNode)ni.getProto();
					if (pnp.getTechnology() == Generic.tech()) continue;
					if (!includePureLayerNodes && pnp.getFunction() == PrimitiveNode.Function.NODE) continue;
				}
				newNodesToStitch.add(ni);
			}
		} else
		{
			for(NodeInst ni : nodesToStitch)
				if (ni.isLinked()) newNodesToStitch.add(ni);
		}
		return newNodesToStitch;
	}

	/**
	 * Method to run auto-stitching.
	 * @param cell the cell in which to stitch.
	 * @param nodesToStitch a list of NodeInsts to stitch (null to use all in the cell).
	 * @param arcsToStitch a list of ArcInsts to stitch (null to use all in the cell).
	 * @param job the Job running this, for aborting.
	 * @param stayInside is the area in which to route (null to route arbitrarily).
	 * @param limitBound if not null, only consider connections that occur in this area.
	 * @param forced true if the stitching was explicitly requested (and so results should be printed).
	 * @param prefs routing preferences.
	 * @param showProgress true to show progress.
	 * @return a List of ArcInst objects that were created.
	 */
	private List<ArcInst> runNow(Cell cell, List<NodeInst> origNodesToStitch, List<ArcInst> origArcsToStitch, Job job,
		PolyMerge stayInside, Rectangle2D limitBound, boolean forced, AutoOptions prefs, boolean showProgress)
	{
		if (showProgress) Job.getUserInterface().setProgressNote("Initializing routing");
		ArcProto preferredArc = prefs.preferredArc;

		// gather objects to stitch
		List<NodeInst> nodesToStitch = getNodesToStitch(cell, origNodesToStitch);
		List<ArcInst> arcsToStitch = getArcsToStitch(cell, origArcsToStitch);

		// next mark nodes to be checked
		nodeMark = new HashSet<NodeInst>();
		for(NodeInst ni : nodesToStitch)
			nodeMark.add(ni);
		Map<ArcProto,Integer> arcsCreatedMap = new HashMap<ArcProto,Integer>();
		Map<NodeProto,Integer> nodesCreatedMap = new HashMap<NodeProto,Integer>();
		allRoutes = new ArrayList<Route>();

		// compute the number of tasks to perform and start progress bar
		int totalToStitch = nodesToStitch.size() + arcsToStitch.size();
		if (prefs.createExports) totalToStitch *= 2;
		totalToStitch += arcsToStitch.size();
		int soFar = 0;

		if (job != null && job.checkAbort()) return null;

		// if creating exports, make first pass in which exports must be created
		if (prefs.createExports)
		{
			if (showProgress) Job.getUserInterface().setProgressNote("Routing " + totalToStitch + " objects with export creation...");

            // make global network map
            GatherNetworksVisitor gatherNetworks = new GatherNetworksVisitor();
            HierarchyEnumerator.enumerateCell(cell, VarContext.globalContext, gatherNetworks);

            Map<Long,List<PolyConnection>> overlapMap = new HashMap<Long,List<PolyConnection>>();
            for(NodeInst ni : nodesToStitch)
			{
				soFar++;
				if (showProgress && (soFar%100) == 0)
				{
					if (job != null && job.checkAbort()) return null;
					Job.getUserInterface().setProgressValue(soFar * 100 / totalToStitch);
				}
				checkExportCreationStitching(ni, overlapMap, gatherNetworks);
			}

			// now run through the arcinsts to be checked for export-creation stitching
			for(ArcInst ai : arcsToStitch)
			{
				soFar++;
				if (showProgress && (soFar%100) == 0)
				{
					if (job != null && job.checkAbort()) return null;
					Job.getUserInterface().setProgressValue(soFar * 100 / totalToStitch);
				}

				// only interested in arcs that are wider than their nodes (and have geometry that sticks out)
				if (!arcTooWide(ai)) continue;
				if (!ai.isLinked()) continue;
				checkExportCreationStitching(ai, overlapMap, gatherNetworks);
			}

            if (showProgress) Job.getUserInterface().setProgressNote("Gathering " + totalToStitch + " objects for export creation...");

            // check for existing exports, or make export if needed
            for (Long netID : overlapMap.keySet())
            {
                List<PolyConnection> polyConns = overlapMap.get(netID);
                makeExport(polyConns);
            }

            // now run these arcs and reinitialize the list
			makeConnections(showProgress, arcsCreatedMap, nodesCreatedMap, stayInside);
			allRoutes = new ArrayList<Route>();
			if (showProgress) Job.getUserInterface().setProgressNote("Initializing routing");

			// reinitialize list of objects to examine
			nodesToStitch = getNodesToStitch(cell, origNodesToStitch);
			arcsToStitch = getArcsToStitch(cell, origArcsToStitch);
		}

		// next precompute bounds on all nodes in cell
		Map<NodeInst, ObjectQTree> nodePortBounds = new HashMap<NodeInst, ObjectQTree>();
		for(Iterator<NodeInst> nIt = cell.getNodes(); nIt.hasNext(); )
		{
			NodeInst ni = nIt.next();
			Rectangle2D niBounds = ni.getBounds();
			ObjectQTree oqt = new ObjectQTree(niBounds);
			for(Iterator<PortInst> it = ni.getPortInsts(); it.hasNext(); )
			{
				PortInst pi = it.next();
				PortProto pp = pi.getPortProto();
				PortOriginal fp = new PortOriginal(ni, pp);
				FixpTransform trans = fp.getTransformToTop();
				NodeInst rNi = fp.getBottomNodeInst();

				double xSize = rNi.getXSize(), ySize = rNi.getYSize();
				if (ZEROSIZEPINS && rNi.getFunction() == PrimitiveNode.Function.PIN)
				{
					double pinSize = getPortSize(pi);
					xSize = ySize = pinSize;
				}
				Rectangle2D bounds = new Rectangle2D.Double(rNi.getAnchorCenterX() - xSize/2,
					rNi.getAnchorCenterY() - ySize/2, xSize, ySize);
				DBMath.transformRect(bounds, trans);
				if (!oqt.add(pi, bounds))
					System.out.println("ERROR: Cell " + cell.describe(false) + ", node " + ni.describe(false) +
						", port " + pp.getName() + " could not be added to quad-tree (bounds are " +
						bounds.getMinX() + "<=X<=" + bounds.getMaxX() + " and " +
						bounds.getMinY() + "<=Y<=" + bounds.getMaxY() + ")");
			}
			nodePortBounds.put(ni, oqt);
		}

		// finally, initialize the information about which layer is smallest on each arc
		Map<ArcProto,Layer> arcLayers = new HashMap<ArcProto,Layer>();

		// get the topology object for knowing what is connected
		StitchingTopology top = new StitchingTopology(cell);

		if (showProgress) Job.getUserInterface().setProgressNote("Routing " + totalToStitch + " objects...");

		// first check for arcs that daisy-chain many nodes
		for(ArcInst ai : arcsToStitch)
		{
			soFar++;
			if (showProgress && (soFar%100) == 0)
			{
				if (job != null && job.checkAbort()) return null;
				Job.getUserInterface().setProgressValue(soFar * 100 / totalToStitch);
			}
			checkDaisyChain(ai, nodePortBounds, stayInside, top);
		}

		if (allRoutes.size() > 0)
		{
			// found daisy-chain elements: do them now
			System.out.println("Auto-routing detected " + allRoutes.size() + " daisy-chained arcs");
			for(Route route : allRoutes)
			{
				List<ArcInst> newArcs = Router.createRouteNoJob(route, cell, arcsCreatedMap, nodesCreatedMap, ep);
				if (newArcs == null)
					System.out.println("AUTO STITCHER FAILED TO MAKE DAISY-CHAIN ARC");
			}

			// reset for the rest of the analysis
			allRoutes = new ArrayList<Route>();
			top = new StitchingTopology(cell);

			// reinitialize list of objects to examine
			nodesToStitch = getNodesToStitch(cell, origNodesToStitch);
			arcsToStitch = getArcsToStitch(cell, origArcsToStitch);
		}

		// now run through the nodeinsts to be checked for stitching
		for(NodeInst ni : nodesToStitch)
		{
			soFar++;
			if (showProgress && (soFar%100) == 0)
			{
				if (job != null && job.checkAbort()) return null;
				Job.getUserInterface().setProgressValue(soFar * 100 / totalToStitch);
			}
			checkStitching(ni, nodePortBounds, arcLayers, stayInside, top, limitBound, preferredArc);
		}

		// now run through the arcinsts to be checked for stitching
		for(ArcInst ai : arcsToStitch)
		{
			soFar++;
			if (showProgress && (soFar%100) == 0)
			{
				if (job != null && job.checkAbort()) return null;
				Job.getUserInterface().setProgressValue(soFar * 100 / totalToStitch);
			}

			if (!ai.isLinked()) continue;

			// only interested in arcs that are wider than their nodes (and have geometry that sticks out)
			if (!arcTooWide(ai)) continue;
			checkStitching(ai, nodePortBounds, arcLayers, stayInside, top, limitBound, preferredArc);
		}

		// create the routes
		List<ArcInst> newArcs = makeConnections(showProgress, arcsCreatedMap, nodesCreatedMap, stayInside);

		// report results
        boolean beep = User.isPlayClickSoundsWhenCreatingArcs();
		if (forced) Router.reportRoutingResults("AUTO ROUTING", arcsCreatedMap, nodesCreatedMap, beep);

		// check for any inline pins due to created wires
		if (showProgress)
		{
			if (job != null && job.checkAbort()) return null;
			Job.getUserInterface().setProgressValue(0);
			Job.getUserInterface().setProgressNote("Cleaning up pins...");
		}
		List<CircuitChangeJobs.Reconnect> pinsToPassThrough = new ArrayList<CircuitChangeJobs.Reconnect>();
		for (NodeInst ni : possibleInlinePins)
		{
			if (ni.isInlinePin())
			{
				CircuitChangeJobs.Reconnect re = CircuitChangeJobs.Reconnect.erasePassThru(ni, false, true, ep);
				if (re != null)
				{
					pinsToPassThrough.add(re);
				}
			}
		}
		if (pinsToPassThrough.size() > 0)
		{
			CircuitChangeJobs.CleanupChanges ccJob = new CircuitChangeJobs.CleanupChanges(cell, true, Collections.<NodeInst>emptySet(),
				pinsToPassThrough, new HashMap<NodeInst,EPoint>(), new ArrayList<NodeInst>(), new HashSet<ArcInst>(), 0, 0, 0);
			try
			{
				ccJob.doIt();
			} catch (JobException e)
			{
			}
		}
		return newArcs;
	}

	private double getPortSize(PortInst pi)
	{
		Double size = truePinSize.get(pi);
		if (size != null) return size.doubleValue();

		double widestArc = 0;
		PortInst bottomPort = pi;
		NodeInst bottomNi;
		for(;;)
		{
			bottomNi = bottomPort.getNodeInst();
			PortProto bottomPp = bottomPort.getPortProto();

			// analyze all arcs connected to bottomPort
			for(Iterator<Connection> it = bottomPort.getConnections(); it.hasNext(); )
			{
				Connection con = it.next();
				ArcInst ai = con.getArc();
				int end = con.getEndIndex();
				if (!ai.isExtended(end)) continue;
				widestArc = Math.max(ai.getLambdaBaseWidth(), widestArc);
			}

			// if at the bottom, stop
        	if (!bottomNi.isCellInstance()) break;
            bottomPort = ((Export)bottomPp).getOriginalPort();
        }
		if (widestArc > bottomNi.getXSize() && widestArc > bottomNi.getYSize())
			widestArc = Math.max(bottomNi.getXSize(), bottomNi.getYSize());
		truePinSize.put(pi, Double.valueOf(widestArc));
		return widestArc;
	}

	private List<ArcInst> makeConnections(boolean showProgress, Map<ArcProto,Integer> arcsCreatedMap,
		Map<NodeProto,Integer> nodesCreatedMap, PolyMerge stayInside)
	{
		// create the routes
		int totalToStitch = allRoutes.size();
		int soFar = 0;
		if (showProgress)
		{
			Job.getUserInterface().setProgressValue(0);
			Job.getUserInterface().setProgressNote("Creating " + totalToStitch + " wires...");
		}
		Collections.sort(allRoutes, new CompRoutes());
		List<ArcInst> newArcs = new ArrayList<ArcInst>();
		for (Route route : allRoutes)
		{
			soFar++;
			if (showProgress && (soFar%100) == 0)
				Job.getUserInterface().setProgressValue(soFar * 100 / totalToStitch);

			RouteElement re = route.get(0);
			Cell c = re.getCell();

			// see if the route is unnecessary because of existing connections
			RouteElementPort start = route.getStart();
			RouteElementPort end = route.getEnd();
			PortInst startPi = start.getPortInst();
			PortInst endPi = end.getPortInst();
			if (startPi != null && endPi != null)
			{
				boolean already = false;
				for(Iterator<Connection> cIt = startPi.getConnections(); cIt.hasNext(); )
				{
					Connection con = cIt.next();
					ArcInst existingAI = con.getArc();
					if (existingAI.getHead() == con)
					{
						if (existingAI.getTail().getPortInst() == endPi) { already = true;   break; }
					} else
					{
						if (existingAI.getHead().getPortInst() == endPi) { already = true;   break; }
					}
				}
				if (already) continue;
			}

//			// if forced to fit in area, don't auto-rotate arcs
//			if (stayInside != null)
//			{
//		        for (RouteElement e : route)
//		        {
//		            if (e.getAction() == RouteElement.RouteElementAction.newArc)
//		            {
//		                RouteElementArc rea = (RouteElementArc)e;
//		                rea.setArcAngle(0);
//		            }
//		        }
//			}
			List<ArcInst> newArcsThisRoute = Router.createRouteNoJob(route, c, arcsCreatedMap, nodesCreatedMap, ep);
			for(ArcInst ai: newArcsThisRoute) newArcs.add(ai);
		}
		return newArcs;
	}

	/****************************************** ARCS THAT DAISY-CHAIN ******************************************/

	private static class DaisyChainPoint
	{
		PortInst pi;
		EPoint location;

		DaisyChainPoint(PortInst p, Point2D loc)
		{
			pi = p;
			location = EPoint.fromLambda(loc.getX(), loc.getY());
		}
	}

	/**
	 * Class to sort DaisyChainPoints.
	 */
	private static class SortDaisyPoints implements Comparator<DaisyChainPoint>
	{
        @Override
		public int compare(DaisyChainPoint dcp1, DaisyChainPoint dcp2)
		{
			if (dcp1.location.getX() < dcp2.location.getX()) return 1;
			if (dcp1.location.getX() > dcp2.location.getX()) return -1;
			if (dcp1.location.getY() < dcp2.location.getY()) return 1;
			if (dcp1.location.getY() > dcp2.location.getY()) return -1;
			return 0;
		}
	}

	/**
	 * Method to see if an ArcInst daisy-chains over multiple ports.
	 * @param ai the ArcInst in question.
	 * @param nodePortBounds quad-tree bounds information for all nodes in the Cell.
	 * @param stayInside is the area in which to route (null to route arbitrarily).
	 * @param top network information for the Cell with these objects.
	 */
	private void checkDaisyChain(ArcInst ai, Map<NodeInst, ObjectQTree> nodePortBounds, PolyMerge stayInside, StitchingTopology top)
	{
		// do not daisy-chain busses
		if (ai.getProto() == Schematics.tech().bus_arc) return;

		// make a list of PortInsts that are on the centerline of this arc
		Cell cell = ai.getParent();

		Network arcNet = top.getArcNetwork(ai);
		Point2D e1 = ai.getHeadLocation();
		Point2D e2 = ai.getTailLocation();
		List<DaisyChainPoint> daisyPoints = new ArrayList<DaisyChainPoint>();
		Rectangle2D searchBounds = ai.getBounds();
		for(Iterator<Geometric> it = cell.searchIterator(searchBounds); it.hasNext(); )
		{
			Geometric geom = it.next();
			if (geom instanceof NodeInst)
			{
				NodeInst ni = (NodeInst)geom;

				// find ports on this arc
				ObjectQTree oqt = nodePortBounds.get(ni);
				Set<?> set = oqt.find(searchBounds);
				if (set != null)
				{
					for (Object obj : set)
					{
						PortInst pi = (PortInst)obj;
						if (!pi.getPortProto().getBasePort().connectsTo(ai.getProto())) continue;
						PolyBase portPoly = pi.getPoly();
						Point2D closest = GenMath.closestPointToSegment(e1, e2, portPoly.getCenter());

						// if this port can connect, save it
						if (DBMath.pointInRect(closest, portPoly.getBounds2D()))
						{
							// ignore if they are already connected
							Network portNet = top.getPortNetwork(pi);
							if (portNet == arcNet) continue;
							daisyPoints.add(new DaisyChainPoint(pi, closest));
						}
					}
				}
			}
		}

		// now see if there are multiple intermediate daisy-chain points
		if (daisyPoints.size() <= 1) return;
		Collections.sort(daisyPoints, new SortDaisyPoints());

		Route route = new Route();
        String name = ai.getName();
//System.out.println("===DELETING DAISY CHAIN ARC FROM ("+ai.getHeadLocation().getX()+","+ai.getHeadLocation().getY()+") TO ("+
//ai.getTailLocation().getX()+","+ai.getTailLocation().getY()+") IN CELL "+ai.getParent().describe(false));
//        route.add(RouteElementArc.deleteArc(ai));
name=null;

        RouteElementPort headRE = RouteElementPort.existingPortInst(ai.getHeadPortInst(), ai.getHeadLocation(), ep);
        RouteElementPort tailRE = RouteElementPort.existingPortInst(ai.getTailPortInst(), ai.getTailLocation(), ep);
        DaisyChainPoint firstDCP = daisyPoints.get(0);
        DaisyChainPoint lastDCP = daisyPoints.get(daisyPoints.size()-1);
        double distOK = firstDCP.location.distance(ai.getHeadLocation()) +
        	lastDCP.location.distance(ai.getTailLocation());
        double distSwap = firstDCP.location.distance(ai.getTailLocation()) +
    		lastDCP.location.distance(ai.getHeadLocation());
        if (distOK > distSwap)
        {
        	RouteElementPort swap = headRE;   headRE = tailRE;   tailRE = swap;
        }

//        if (headRE.getNodeInst().getNumConnections() == 1 && headRE.getLocation().equals(firstDCP.location))
//        {
//        	route.add(RouteElementPort.deleteNode(headRE.getNodeInst()));
//        	headRE = null;
//        }
//        if (tailRE.getNodeInst().getNumConnections() == 1 && tailRE.getLocation().equals(lastDCP.location))
//        {
//        	route.add(RouteElementPort.deleteNode(tailRE.getNodeInst()));
//        	tailRE = null;
//        }

        for(DaisyChainPoint dcp : daisyPoints)
        {
            RouteElementPort dcpRE = RouteElementPort.existingPortInst(dcp.pi, dcp.location, ep);
            if (headRE != null && headRE.getPortInst() != tailRE.getPortInst())
            {
            	RouteElementArc re = RouteElementArc.newArc(cell, ai.getProto(), ai.getLambdaBaseWidth(), headRE, dcpRE,
	        		headRE.getConnectingSite().getCenter(), dcpRE.getConnectingSite().getCenter(), name,
	        		ai.getTextDescriptor(ArcInst.ARC_NAME), ai, ai.isHeadExtended(), ai.isTailExtended(), stayInside);
	            route.add(re);
            }
        	headRE = dcpRE;
        	name = null;
        }
        if (tailRE != null)
        {
        	if (headRE.getPortInst() != tailRE.getPortInst())
        	{
	        	RouteElementArc re = RouteElementArc.newArc(cell, ai.getProto(), ai.getLambdaBaseWidth(), headRE, tailRE,
		    		headRE.getConnectingSite().getCenter(), tailRE.getConnectingSite().getCenter(), name,
		    		ai.getTextDescriptor(ArcInst.ARC_NAME), ai, ai.isHeadExtended(), ai.isTailExtended(), stayInside);
		        route.add(re);
        	}
        }
		allRoutes.add(route);
	}

	/****************************************** NORMAL STITCHING ******************************************/
//private void scanRTree(RTNode rTop)
//{
//	for(int i=0; i<rTop.getTotal(); i++)
//	{
//		Object obj = rTop.getChild(i);
//		if (obj instanceof RTNode) scanRTree((RTNode)obj); else
//		{
//			if (obj instanceof NodeInst)
//			{
//				NodeInst n = (NodeInst)obj;
//				if (n.getFunction() == PrimitiveNode.Function.NODE || n.getName().equals("plnode@29"))
//				{
//					Rectangle2D r = n.getBounds();
//					System.out.println("   R-TREE HAS NODE "+n.describe(false)+" AT "+r.getMinX()+"<=X<="+
//						r.getMaxX()+" AND "+r.getMinY()+"<=Y<="+r.getMaxY());
//				}
//			}
//		}
//	}
//}

/**
	 * Method to check an object for possible stitching to neighboring objects.
	 * @param geom the object to check for stitching.
	 * @param nodePortBounds quad-tree bounds information for all nodes in the Cell.
	 * @param arcLayers a map from ArcProtos to Layers.
	 * @param stayInside is the area in which to route (null to route arbitrarily).
	 * @param top network information for the Cell with these objects.
	 * @param limitBound if not null, only consider connections that occur in this area.
	 * @param preferredArc preferred ArcProto to use.
	 */
//private static final boolean DEBUGFORDIMA = false;
	private void checkStitching(Geometric geom, Map<NodeInst, ObjectQTree> nodePortBounds, Map<ArcProto,Layer> arcLayers,
		PolyMerge stayInside, StitchingTopology top, Rectangle2D limitBound, ArcProto preferredArc)
	{
		Cell cell = geom.getParent();
		NodeInst ni = null;
		if (geom instanceof NodeInst) ni = (NodeInst)geom;

		// make a list of other geometrics that touch or overlap this one (copy it because the main list will change)
		List<Geometric> geomsInArea = new ArrayList<Geometric>();
		Rectangle2D geomBounds = geom.getBounds();
		double epsilon = DBMath.getEpsilon();
		Rectangle2D searchBounds = new Rectangle2D.Double(geomBounds.getMinX()-epsilon, geomBounds.getMinY()-epsilon,
			geomBounds.getWidth()+epsilon*2, geomBounds.getHeight()+epsilon*2);
//if (DEBUGFORDIMA)
//{
//	if (ni != null && ni.getName().equals("plnode@29"))
//	{
//		System.out.println("==== R-TREE TEST ====");
//		for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
//		{
//			NodeInst n = it.next();
//			if (n.getName().equals("plnode@29"))
//			{
//				Rectangle2D r = n.getBounds();
//				System.out.println("   CELL HAS NODE "+n.describe(false)+" AT "+r.getMinX()+"<=X<="+
//					r.getMaxX()+" AND "+r.getMinY()+"<=Y<="+r.getMaxY());
//			}
//		}
//		RTNode rTop = cell.getTopology().getRTree();
//		scanRTree(rTop);
//	}
//}
		for(Iterator<Geometric> it = cell.searchIterator(searchBounds); it.hasNext(); )
		{
			Geometric oGeom = it.next();
			if (oGeom != geom) geomsInArea.add(oGeom);
		}
		for(Geometric oGeom : geomsInArea)
		{
			// find another node in this area
			if (oGeom instanceof ArcInst)
			{
				// other geometric is an ArcInst
				ArcInst oAi = (ArcInst)oGeom;

				if (ni == null)
				{
					// only interested in arcs that are wider than their nodes (and have geometry that sticks out)
					if (!arcTooWide(oAi)) continue;

					// compare arc "geom" against arc "oAi"
					compareTwoArcs((ArcInst)geom, oAi, stayInside, top);
					continue;
				}

				// compare node "ni" against arc "oAi"
				if (ni.isCellInstance())
				{
					compareNodeInstWithArc(ni, oAi, stayInside, top, nodePortBounds);
				} else
				{
					compareNodePrimWithArc(ni, oAi, stayInside, top);
				}
			} else
			{
				// other geometric a NodeInst
				NodeInst oNi = (NodeInst)oGeom;
				if (!oNi.isCellInstance())
				{
					PrimitiveNode pnp = (PrimitiveNode)oNi.getProto();
					if (pnp.getTechnology() == Generic.tech()) continue;
					if (!includePureLayerNodes && pnp.getFunction() == PrimitiveNode.Function.NODE) continue;
				}

				if (ni == null)
				{
					// compare arc "geom" against node "oNi"
					if (oNi.isCellInstance())
					{
						compareNodeInstWithArc(oNi, (ArcInst)geom, stayInside, top, nodePortBounds);
					} else
					{
						compareNodePrimWithArc(oNi, (ArcInst)geom, stayInside, top);
					}
					continue;
				}

				// compare node "ni" against node "oNi"
				compareTwoNodes(ni, oNi, nodePortBounds, arcLayers, stayInside, top, limitBound, preferredArc);
			}
		}
	}

	/**
	 * Method to compare two nodes and see if they should be connected.
	 * @param ni the first NodeInst to compare.
	 * @param oNi the second NodeInst to compare.
	 * @param nodePortBounds quad-tree bounds information for all nodes in the Cell.
	 * @param arcLayers a map from ArcProtos to Layers.
	 * @param stayInside is the area in which to route (null to route arbitrarily).
	 * @param top network information for the Cell with these nodes.
	 * @param limitBound if not null, only consider connections that occur in this area.
	 * @param preferredArc preferred ArcProto to use.
	 */
	private void compareTwoNodes(NodeInst ni, NodeInst oNi, Map<NodeInst,ObjectQTree> nodePortBounds,
		Map<ArcProto,Layer> arcLayers, PolyMerge stayInside,
		StitchingTopology top, Rectangle2D limitBound, ArcProto preferredArc)
	{
		// if both nodes are being checked, examine them only once
        if (nodeMark.contains(oNi) && oNi.getNodeId() <= ni.getNodeId()) return;

		// now look at every layer in this node
		Rectangle2D oBounds = oNi.getBounds();
		if (ni.isCellInstance())
		{
			// complex node instance: look at all ports near this bound
			ObjectQTree oqt = nodePortBounds.get(ni);
			Rectangle2D biggerBounds = new Rectangle2D.Double(oBounds.getMinX()-1, oBounds.getMinY()-1, oBounds.getWidth()+2, oBounds.getHeight()+2);
			Set<?> set = oqt.find(biggerBounds);
			if (set != null)
			{
				for (Object obj : set)
				{
                    PortInst pi = (PortInst)obj;
					PortProto pp = pi.getPortProto();

					// find the primitive node at the bottom of this port
					FixpTransform trans = ni.rotateOut();
					NodeInst rNi = ni;
					PortProto rPp = pp;
					while (rNi.isCellInstance())
					{
						FixpTransform temp = rNi.translateOut();
						temp.preConcatenate(trans);
						Export e = (Export)rPp;
						rNi = e.getOriginalPort().getNodeInst();
						rPp = e.getOriginalPort().getPortProto();

						trans = rNi.rotateOut();
						trans.preConcatenate(temp);
					}

					// determine the smallest layer for all possible arcs
					ArcProto [] connections = pp.getBasePort().getConnections();
					for(int i=0; i<connections.length; i++)
					{
						findSmallestLayer(connections[i], arcLayers);
					}

					// look at all polygons on this nodeinst
					boolean usePortPoly = false;
					Poly [] nodePolys = shapeOfNode(rNi);
					int tot = nodePolys.length;
					if (tot == 0 || rNi.getProto() == Generic.tech().simProbeNode)
					{
						usePortPoly = true;
						tot = 1;
					}
					Netlist subNetlist = rNi.getParent().getNetlist();
					for(int j=0; j<tot; j++)
					{
						Layer layer = null;
						Poly poly = null;
						if (usePortPoly)
						{
							poly = ni.getShapeOfPort(pp);
							layer = poly.getLayer();
						} else
						{
							poly = nodePolys[j];

							// only want electrically connected polygons
							if (poly.getPort() == null) continue;

							// only want polygons on correct part of this nodeinst
							if (!subNetlist.portsConnected(rNi, rPp, poly.getPort())) continue;

							// transformed polygon
							poly.transform(trans);

							// if the polygon layer is pseudo, substitute real layer
							layer = poly.getLayer();
						}

						// see which arc can make the connection
						boolean connected = false;
						for(int pass=0; pass<2; pass++)
						{
							for(int i=0; i<connections.length; i++)
							{
								ArcProto ap = connections[i];
								if (pass == 0)
								{
									if (ap != preferredArc) continue;
								} else
								{
									if (ap == preferredArc) continue;

									// arc must be in the same technology
									if (ap.getTechnology() != rNi.getProto().getTechnology()) continue;
								}

								// this polygon must be the smallest arc layer
								if (!usePortPoly)
								{
									Layer oLayer = arcLayers.get(ap);
									if (!layer.getTechnology().sameLayer(oLayer, layer)) continue;
								}

								// pass it on to the next test
								connected = testPoly(ni, pp, ap, poly, oNi, top, nodePortBounds, arcLayers, stayInside, limitBound);
                                if (connected) break;
							}
							if (connected) break;
						}
						if (connected) break;
					}
				}
			}
		} else
		{
			// primitive node: check its layers
			FixpTransform trans = ni.rotateOut();

			// save information about the other node
			double oX = oNi.getAnchorCenterX();
			double oY = oNi.getAnchorCenterY();

			// look at all polygons on this nodeinst
			boolean usePortPoly = false;
			Poly [] polys = shapeOfNode(ni);
			int tot = polys.length;
			if (tot == 0 || ni.getProto() == Generic.tech().simProbeNode)
			{
				usePortPoly = true;
				tot = 1;
			}
			for(int j=0; j<tot; j++)
			{
				PortProto rPp = null;
				Poly polyPtr = null;
				if (usePortPoly)
				{
					// search all ports for the closest
					PortProto bestPp = null;
					double bestDist = 0;
					for(Iterator<PortProto> pIt = ni.getProto().getPorts(); pIt.hasNext(); )
					{
						PortProto tPp = pIt.next();

						// compute best distance to the other node
						Poly portPoly = ni.getShapeOfPort(tPp);
						double x = portPoly.getCenterX();
						double y = portPoly.getCenterY();
						double dist = Math.abs(x-oX) + Math.abs(y-oY);
						if (bestPp == null)
						{
							bestDist = dist;
							bestPp = tPp;
						}
						if (dist > bestDist) continue;
						bestPp = tPp;   bestDist = dist;
					}
					if (bestPp == null) continue;
					rPp = bestPp;
					polyPtr = ni.getShapeOfPort(rPp);
				} else
				{
					polyPtr = polys[j];

					// only want electrically connected polygons
					if (polyPtr.getPort() == null) continue;

					// search all ports for the closest connected to this layer
					PortProto bestPp = null;
					double bestDist = 0;
					for(Iterator<PortProto> pIt = ni.getProto().getPorts(); pIt.hasNext(); )
					{
						PortProto tPp = pIt.next();
						if (!top.portsConnected(ni, tPp, polyPtr.getPort())) continue;

						// compute best distance to the other node
						Poly portPoly = ni.getShapeOfPort(tPp);
						double x = portPoly.getCenterX();
						double y = portPoly.getCenterY();
						double dist = Math.abs(x-oX) + Math.abs(y-oY);
						if (bestPp == null) bestDist = dist;
						if (dist > bestDist) continue;
						bestPp = tPp;   bestDist = dist;
					}
					if (bestPp == null) continue;
					rPp = bestPp;

					// transformed the polygon
					polyPtr.transform(trans);
				}

				// if the polygon layer is pseudo, substitute real layer
				Layer layer = polyPtr.getLayer();

				// stop now if already an arc on this port to other node
				boolean found = false;
				for(Iterator<Connection> cIt = ni.getConnections(); cIt.hasNext(); )
				{
					Connection con = cIt.next();
					PortInst pi = con.getPortInst();
					if (!top.portsConnected(ni, rPp, pi.getPortProto())) continue;
					if (con.getArc().getHeadPortInst().getNodeInst() == oNi ||
						con.getArc().getTailPortInst().getNodeInst() == oNi) { found = true;   break; }
				}
				if (found) continue;

				// see if an arc is possible
				boolean connected = false;
				ArcProto [] connections = rPp.getBasePort().getConnections();
				for(int pass=0; pass<2; pass++)
				{
					for(int i=0; i<connections.length; i++)
					{
						ArcProto ap = connections[i];
						if (pass == 0)
						{
							if (ap != preferredArc) continue;
						} else
						{
							if (ap == preferredArc) continue;
						}

						// arc must be in the same technology
						if (ap.getTechnology() != ni.getProto().getTechnology()) break;

						// this polygon must be the smallest arc layer
						findSmallestLayer(ap, arcLayers);
						if (!usePortPoly)
						{
							Layer oLayer = arcLayers.get(ap);
							if (!ap.getTechnology().sameLayer(oLayer, layer)) continue;
						}

						// pass it on to the next test
						connected = testPoly(ni, rPp, ap, polyPtr, oNi, top, nodePortBounds, arcLayers, stayInside, limitBound);
						if (connected) break;
					}
					if (connected) break;
				}
				if (connected) break;
			}
		}
	}

	/**
	 * Method to compare two arcs and see if they should be connected.
	 * @param ai1 the first ArcInst to compare.
	 * @param ai2 the second ArcInst to compare.
	 * @param stayInside is the area in which to route (null to route arbitrarily).
	 * @param top the Netlist information for the Cell with the arcs.
	 */
	private void compareTwoArcs(ArcInst ai1, ArcInst ai2, PolyMerge stayInside, StitchingTopology top)
	{
		// if connected, stop now
		if (ai1.getProto() != ai2.getProto()) return;
		Network net1 = top.getArcNetwork(ai1);
		Network net2 = top.getArcNetwork(ai2);
		if (net1 == net2) return;

		// look at all polygons on the first arcinst
		Poly [] polys1 = ai1.getProto().getTechnology().getShapeOfArc(ai1);
		int tot1 = polys1.length;
		Poly [] polys2 = ai2.getProto().getTechnology().getShapeOfArc(ai2);
		int tot2 = polys2.length;
		for(int i1=0; i1<tot1; i1++)
		{
			Poly poly1 = polys1[i1];
			Layer layer1 = poly1.getLayer();
			Layer.Function fun = layer1.getFunction();
			if (!fun.isMetal() && !fun.isDiff() && !fun.isPoly()) continue;
			Rectangle2D bounds1 = poly1.getBounds2D();

			// compare them against all of the polygons in the second arcinst
			for(int i2=0; i2<tot2; i2++)
			{
				Poly poly2 = polys2[i2];
				if (layer1 != poly2.getLayer()) continue;

				// two polygons on the same layer...are they even near each other?
				Rectangle2D bounds2 = poly2.getBounds2D();
				if (!bounds1.intersects(bounds2)) continue;

				// do precise test for touching

				// connect their closest ends
				Rectangle2D intersection = new Rectangle2D.Double();
				Rectangle2D.intersect(bounds1, bounds2, intersection);
				double x = intersection.getCenterX();
				double y = intersection.getCenterY();

				// run the wire
				connectObjects(ai1, net1, ai2, net2, ai1.getParent(), new Point2D.Double(x,y), stayInside, top);
				return;
			}
		}
	}

	/**
	 * Method to compare a node instance and an arc to see if they touch and should be connected.
	 * @param ni the NodeInst to compare.
	 * @param ai the ArcInst to compare.
	 * @param stayInside is the area in which to route (null to route arbitrarily).
	 * @param top the Netlist information for the Cell with the node and arc.
	 * @param nodePortBounds quad-tree bounds information for all nodes in the Cell.
	 */
	private void compareNodeInstWithArc(NodeInst ni, ArcInst ai, PolyMerge stayInside, StitchingTopology top,
		Map<NodeInst,ObjectQTree> nodePortBounds)
	{
		Network arcNet = top.getArcNetwork(ai);

		// find all ports on the instance that are near the arc
		ObjectQTree oqt = nodePortBounds.get(ni);
		Rectangle2D aBounds = ai.getBounds();
		Rectangle2D biggerBounds = new Rectangle2D.Double(aBounds.getMinX()-1, aBounds.getMinY()-1, aBounds.getWidth()+2, aBounds.getHeight()+2);
		Set<?> set = oqt.find(biggerBounds);
		if (set == null || set.size() == 0) return;

		// look at all polygons on the arcinst
		Poly [] arcPolys = ai.getProto().getTechnology().getShapeOfArc(ai);
		int aTot = arcPolys.length;
		for(int i=0; i<aTot; i++)
		{
			Poly arcPoly = arcPolys[i];
			Layer arcLayer = arcPoly.getLayer();
			Layer.Function arcLayerFun = arcLayer.getFunction();
			if (!arcLayerFun.isMetal() && !arcLayerFun.isDiff() && !arcLayerFun.isPoly()) continue;

			// find ports near the arc
			for (Object obj : set)
			{
				PortInst pi = (PortInst)obj;

				// ignore if already connected
				Network portNet = top.getPortNetwork(pi);
				if (portNet == arcNet) continue;

				// find the primitive node at the bottom of the port
				FixpTransform trans = ni.rotateOut();
				NodeInst rNi = ni;
				PortProto rPp = pi.getPortProto();
				while (rNi.isCellInstance())
				{
					FixpTransform temp = rNi.translateOut();
					temp.preConcatenate(trans);
					Export e = (Export)rPp;
					rNi = e.getOriginalPort().getNodeInst();
					rPp = e.getOriginalPort().getPortProto();

					trans = rNi.rotateOut();
					trans.preConcatenate(temp);
				}

				// see if anything on the base node touches the arc
				Poly [] polys = shapeOfNode(rNi);
				int tot = polys.length;
				for(int j=0; j<tot; j++)
				{
					Poly baseNodePoly = polys[j];
					Layer nodeLayer = baseNodePoly.getLayer();
					if (nodeLayer == null) continue;
					if (nodeLayer.getFunction() != arcLayerFun) continue;
					baseNodePoly.transform(trans);
					double polyDist = arcPoly.separation(baseNodePoly);
					if (polyDist >= DBMath.getEpsilon()) continue;

					// arc touches the port: connect them
					Poly portPoly = pi.getPoly();
					double portCX = portPoly.getCenterX();
					double portCY = portPoly.getCenterY();

					Rectangle2D arcBounds = arcPoly.getBounds2D();
					double aCX = arcBounds.getCenterX();
					double aCY = arcBounds.getCenterY();
					Point2D bend1 = new Point2D.Double(portCX, aCY);
					Point2D bend2 = new Point2D.Double(aCX, portCY);
					if (stayInside != null)
					{
						if (!stayInside.contains(arcLayer, bend1)) bend1 = bend2;
					} else
					{
						if (!arcPoly.contains(bend1)) bend1 = bend2;
					}
					connectObjects(ai, arcNet, pi, portNet, ai.getParent(), bend1, stayInside, top);
					return;
				}
			}
		}
	}

	/**
	 * Method to compare a primitive node and an arc to see if they touch and should be connected.
	 * @param ni the NodeInst to compare.
	 * @param ai the ArcInst to compare.
	 * @param stayInside is the area in which to route (null to route arbitrarily).
	 * @param top the Netlist information for the Cell with the node and arc.
	 */
	private void compareNodePrimWithArc(NodeInst ni, ArcInst ai, PolyMerge stayInside, StitchingTopology top)
	{
		Network arcNet = top.getArcNetwork(ai);

		// gather information about the node
		Poly [] nodePolys = shapeOfNode(ni);
		int nTot = nodePolys.length;
		FixpTransform trans = ni.rotateOut();

		// look at all polygons on the arcinst
		Poly [] arcPolys = ai.getProto().getTechnology().getShapeOfArc(ai);
		int aTot = arcPolys.length;
		for(int i=0; i<aTot; i++)
		{
			Poly arcPoly = arcPolys[i];
			Layer arcLayer = arcPoly.getLayer();
			Layer.Function arcLayerFun = arcLayer.getFunction();
			if (!arcLayerFun.isMetal() && !arcLayerFun.isDiff() && !arcLayerFun.isPoly()) continue;
			Rectangle2D arcBounds = arcPoly.getBounds2D();
			double aCX = arcBounds.getCenterX();
			double aCY = arcBounds.getCenterY();

			// compare them against all of the polygons in the node
			for(int j=0; j<nTot; j++)
			{
				Poly nodePoly = nodePolys[j];
				nodePoly.transform(trans);

				// they must be on the same layer and touch
				Layer nodeLayer = nodePoly.getLayer();
				if (nodeLayer.getFunction() != arcLayerFun) continue;
				double polyDist = arcPoly.separation(nodePoly);
				if (polyDist >= DBMath.getEpsilon()) continue;

				// only want electrically connected polygons
				if (nodePoly.getPort() == null) continue;

				// search all ports for the closest connected to this layer
				PortProto bestPp = null;
				double bestDist = 0;
				for(Iterator<PortProto> pIt = ni.getProto().getPorts(); pIt.hasNext(); )
				{
					PortProto tPp = pIt.next();
					if (!top.portsConnected(ni, tPp, nodePoly.getPort())) continue;

					// compute best distance to the other node
					Poly portPoly = ni.getShapeOfPort(tPp);
					double portCX = portPoly.getCenterX();
					double portCY = portPoly.getCenterY();
					double dist = Math.abs(portCX-aCX) + Math.abs(portCY-aCY);
					if (bestPp == null) bestDist = dist;
					if (dist > bestDist) continue;
					bestPp = tPp;   bestDist = dist;
				}
				if (bestPp == null) continue;

				// run the wire
				PortInst pi = ni.findPortInstFromEquivalentProto(bestPp);
				Poly portPoly = ni.getShapeOfPort(bestPp);
				double portCX = portPoly.getCenterX();
				double portCY = portPoly.getCenterY();
				Network nodeNet = top.getPortNetwork(pi);
				if (arcNet == nodeNet) continue;
				if (alignment != null)
				{
					if (alignment.getWidth() > 0)
					{
						portCX = Math.round(portCX / alignment.getWidth()) * alignment.getWidth();
						aCX = Math.round(aCX / alignment.getWidth()) * alignment.getWidth();
					}
					if (alignment.getHeight() > 0)
					{
						portCY = Math.round(portCY / alignment.getHeight()) * alignment.getHeight();
						aCY = Math.round(aCY / alignment.getHeight()) * alignment.getHeight();
					}
				}
				Point2D bend1 = new Point2D.Double(portCX, aCY);
				Point2D bend2 = new Point2D.Double(aCX, portCY);
				if (stayInside != null)
				{
					if (!stayInside.contains(arcLayer, bend1)) bend1 = bend2;
				} else
				{
					if (!arcPoly.contains(bend1)) bend1 = bend2;
				}
				connectObjects(ai, arcNet, pi, nodeNet, ai.getParent(), bend1, stayInside, top);
				return;
			}
		}
	}

	/**
	 * Method to connect two nodes if they touch.
	 * @param ni the first node to test.
	 * @param pp the port on the first node to test.
	 * @param ap the arcproto to use when connecting the nodes.
	 * @param poly the polygon on the first node to test.
	 * @param oNi the second node to test.
	 * @param top network information for the cell with the nodes.
	 * @param nodePortBounds quad-tree bounds information for all nodes in the Cell.
	 * @param arcLayers a map from ArcProtos to Layers.
	 * @param stayInside is the area in which to route (null to route arbitrarily).
	 * @param limitBound if not null, only consider connections that occur in this area.
	 * @return true if connections were made.
	 */
	private boolean testPoly(NodeInst ni, PortProto pp, ArcProto ap, Poly poly, NodeInst oNi, StitchingTopology top,
		Map<NodeInst, ObjectQTree> nodePortBounds,
		Map<ArcProto,Layer> arcLayers, PolyMerge stayInside, Rectangle2D limitBound)
	{
//System.out.println("FOR LAYER "+ap.getName()+" CONSIDER PORT "+pp.getName()+" OF NODE "+ni.describe(false));
		// get network associated with the node/port
		PortInst pi = ni.findPortInstFromEquivalentProto(pp);
		Name portNK = pp.getNameKey();
		boolean connectionsMade = false;

		// keep track of which networks we've already tied together
        Set<Network> netsConnectedTo = new TreeSet<Network>();
        Network net = top.getNodeNetwork(ni, pp);
        if (net == null) return false;
        netsConnectedTo.add(net);

        // now look at every layer in this node
		if (oNi.isCellInstance())
		{
			// complex cell: look at all exports
			Rectangle2D bounds = poly.getBounds2D();

			// find ports near this bound
			ObjectQTree oqt = nodePortBounds.get(oNi);
			Rectangle2D biggerBounds = new Rectangle2D.Double(bounds.getMinX()-1, bounds.getMinY()-1, bounds.getWidth()+2, bounds.getHeight()+2);
			Set<?> set = oqt.find(biggerBounds);
			if (set != null)
			{
				for (Object obj : set)
				{
                    PortInst oPi = (PortInst)obj;
					PortProto mPp = oPi.getPortProto();

					// port must be able to connect to the arc
					if (!mPp.getBasePort().connectsTo(ap)) continue;

					Network oNet = null;
					Name oPortNK = mPp.getNameKey();
			        if (portNK.isBus() && oPortNK.isBus())
			        {
						// both ports are busses: do not stitch if their sizes differ
//System.out.println("ROUTING BUS ARC "+ap.describe());
			        	if (portNK.busWidth() != oPortNK.busWidth()) continue;
			        	ap = Schematics.tech().bus_arc;
			        } else if (portNK.isBus() || oPortNK.isBus())
			        {
			        	continue;
			        } else
			        {
						// do not stitch where there is already an electrical connection
			        	oNet = top.getPortNetwork(oNi.findPortInstFromEquivalentProto(mPp));
						if (net != null && oNet == net) continue;
			        }

					// do not stitch if there is already an arc connecting these two ports
					boolean ignore = false;
					for (Iterator<Connection> piit = oPi.getConnections(); piit.hasNext(); )
					{
						Connection conn = piit.next();
						ArcInst ai = conn.getArc();
						if (ai.getHeadPortInst() == pi) ignore = true;
						if (ai.getTailPortInst() == pi) ignore = true;
					}
					if (ignore) continue;

					// find the primitive node at the bottom of this port
					FixpTransform trans = oNi.rotateOut();
					NodeInst rNi = oNi;
					PortProto rPp = mPp;
					while (rNi.isCellInstance())
					{
						FixpTransform temp = rNi.translateOut();
						temp.preConcatenate(trans);
						Export e = (Export)rPp;
						rNi = e.getOriginalPort().getNodeInst();
						rPp = e.getOriginalPort().getPortProto();

						trans = rNi.rotateOut();
						trans.preConcatenate(temp);
					}

                    // keep track of which sub-networks we've already considered
                    Cell subcell = (Cell)oNi.getProto();
                    Netlist netlist = subcell.getNetlist();
                    Network netm = null;
                    if (mPp instanceof Export) {
                        Export mPpe = (Export)mPp;
                        netm = netlist.getNetwork(mPpe, 0);
                        assert netm != null;
                        if (netsConnectedTo.contains(netm)) continue;
                    }

					// see how much geometry is on this node
					Poly [] polys = shapeOfNode(rNi);
					int tot = polys.length;
					if (tot == 0)
					{
						// not a geometric primitive: look for ports that touch
						Poly oPoly = oNi.getShapeOfPort(mPp);
						if (comparePoly(oNi, mPp, oPoly, oNet, ni, pp, poly, net, ap, stayInside, top, limitBound))
                        {
							connectionsMade = true;
                            if (netm != null) netsConnectedTo.add(netm);
                        }
					} else
					{
						// a geometric primitive: look for ports on layers that touch
						Netlist subNetlist = rNi.getParent().getNetlist();
						for(int j=0; j<tot; j++)
						{
							Poly oPoly = polys[j];

							// only want electrically connected polygons
							if (oPoly.getPort() == null) continue;

							// only want polygons connected to correct part of nodeinst
							if (!subNetlist.portsConnected(rNi, rPp, oPoly.getPort())) continue;

							// if the polygon layer is pseudo, substitute real layer
							if (ni.getProto() != Generic.tech().simProbeNode)
							{
								Layer oLayer = oPoly.getLayer();
								Layer apLayer = arcLayers.get(ap);
								if (!oLayer.getTechnology().sameLayer(oLayer, apLayer)) continue;
							}

							// transform the polygon and pass it on to the next test
							oPoly.transform(trans);
							if (comparePoly(oNi, mPp, oPoly, oNet, ni, pp, poly, net, ap, stayInside, top, limitBound))
							{
								connectionsMade = true;
                                if (netm != null) netsConnectedTo.add(netm);
								break;
							}
						}
					}
				}
			}
		} else
		{
			// primitive node: check its layers
			FixpTransform trans = oNi.rotateOut();

			// determine target point
			double ox = poly.getCenterX();
			double oy = poly.getCenterY();

			// look at all polygons on nodeinst oNi
			Poly [] polys = shapeOfNode(oNi);
			int tot = polys.length;
			if (tot == 0)
			{
				// not a geometric primitive: look for ports that touch
				PortProto bestPp = null;
				double bestDist = 0;
				for(Iterator<PortProto> pIt = oNi.getProto().getPorts(); pIt.hasNext(); )
				{
					PortProto rPp = pIt.next();

					// compute best distance to the other node
					Poly portPoly = oNi.getShapeOfPort(rPp);
					double dist = Math.abs(portPoly.getCenterX()-ox) + Math.abs(portPoly.getCenterY()-oy);
					if (bestPp == null)
					{
						bestDist = dist;
						bestPp = rPp;
					}
					if (dist > bestDist) continue;
					bestPp = rPp;   bestDist = dist;
				}
				if (bestPp != null)
				{
					PortProto rPp = bestPp;
					Network oNet = top.getPortNetwork(oNi.findPortInstFromEquivalentProto(bestPp));
					if (net == null || oNet != net)
					{
						// port must be able to connect to the arc
						if (rPp.getBasePort().connectsTo(ap))
						{
							// transformed the polygon and pass it on to the next test
							Poly oPoly = oNi.getShapeOfPort(rPp);
							if (comparePoly(oNi, rPp, oPoly, oNet, ni, pp, poly, net, ap, stayInside, top, limitBound))
								return true;
						}
					}
				}
			} else
			{
				// a geometric primitive: look for ports on layers that touch
				for(int j=0; j<tot; j++)
				{
					Poly oPoly = polys[j];

					// only want electrically connected polygons
					if (oPoly.getPort() == null) continue;

					// if the polygon layer is pseudo, substitute real layer
					Layer oLayer = oPoly.getLayer();

					// this must be the smallest layer on the arc
					Layer apLayer = arcLayers.get(ap);
					if (!apLayer.getTechnology().sameLayer(apLayer, oLayer)) continue;

					// do not stitch where there is already an electrical connection
					PortInst oPi = oNi.findPortInstFromEquivalentProto(oPoly.getPort());
					Network oNet = top.getPortNetwork(oPi);
					if (net != null && oNet == net) continue;

					// search all ports for the closest connected to this layer
					PortProto bestPp = null;
					double bestDist = 0;
					for(Iterator<PortProto> pIt = oNi.getProto().getPorts(); pIt.hasNext(); )
					{
						PortProto rPp = pIt.next();
						if (!top.portsConnected(oNi, rPp, oPoly.getPort())) continue;

						// compute best distance to the other node
						Poly portPoly = oNi.getShapeOfPort(rPp);
						double dist = Math.abs(ox-portPoly.getCenterX()) + Math.abs(oy-portPoly.getCenterY());
						if (bestPp == null) bestDist = dist;
						if (dist > bestDist) continue;
						bestPp = rPp;   bestDist = dist;
					}
					if (bestPp == null) continue;
					PortProto rPp = bestPp;

					// port must be able to connect to the arc
					if (!rPp.getBasePort().connectsTo(ap)) continue;

					// transformed the polygon and pass it on to the next test
					oPoly.transform(trans);
					if (comparePoly(oNi, rPp, oPoly, oNet, ni, pp, poly, net, ap, stayInside, top, limitBound))
						return true;
				}
			}
		}
		return connectionsMade;
	}

	/**
	 * Method to compare two polygons.  If these polygons touch
	 * or overlap then the two nodes should be connected.
	 * @param oNi the NodeInst responsible for the first polygon.
	 * @param opp the PortProto responsible for the first polygon.
	 * @param oPoly the first polygon.
	 * @param oNet the Network responsible for the first polygon.
	 * @param ni the NodeInst responsible for the second polygon.
	 * @param pp the PortProto responsible for the second polygon.
	 * @param poly the second polygon.
	 * @param net the Network responsible for the second polygon.
	 * @param ap the type of arc to use when stitching the nodes.
	 * @param stayInside is the area in which to route (null to route arbitrarily).
	 * @param top the netlist for the Cell with the polygons.
	 * @param limitBound if not null, only consider connections that occur in this area.
	 * @return true if the connection is made.
	 */
	private boolean comparePoly(NodeInst oNi, PortProto opp, Poly oPoly, Network oNet,
		NodeInst ni, PortProto pp, Poly poly, Network net,
		ArcProto ap, PolyMerge stayInside, StitchingTopology top, Rectangle2D limitBound)
	{
		double sep = poly.separation(oPoly);
//System.out.println("   DISTANCE BETWEEN PORT "+pp.getName()+" OF NODE "+ni.describe(false)+" AND PORT "+opp.getName()+" OF NODE "+oNi.describe(false)+" IS "+sep);
		// find the bounding boxes of the polygons
		if (sep > DBMath.getEpsilon()) return false;

		// be sure the closest ports are being used
		Poly portPoly = ni.getShapeOfPort(pp);
		Point2D portCenter = new Point2D.Double(portPoly.getCenterX(), portPoly.getCenterY());
		Poly oPortPoly = oNi.getShapeOfPort(opp);
		Point2D oPortCenter = new Point2D.Double(oPortPoly.getCenterX(), oPortPoly.getCenterY());

		if (stayInside == null)
		{
			if (ni.isCellInstance() || oNi.isCellInstance())
			{
				Rectangle2D polyBounds = portPoly.getBounds2D();
				Rectangle2D oPolyBounds = oPortPoly.getBounds2D();

				// quit now if bounding boxes don't intersect
				if ((polyBounds.getMinX() > oPolyBounds.getMaxX() || oPolyBounds.getMinX() > polyBounds.getMaxX()) &&
					(polyBounds.getMinY() > oPolyBounds.getMaxY() || oPolyBounds.getMinY() > polyBounds.getMaxY())) return false;
			}
		}

		double dist = portCenter.distance(oPortCenter);
		for(Iterator<PortProto> it = oNi.getProto().getPorts(); it.hasNext(); )
		{
			PortProto tPp = it.next();
			if (tPp == opp) continue;
			if (!top.portsConnected(oNi, tPp, opp)) continue;
			if (!tPp.getBasePort().connectsTo(ap)) continue;
			portPoly = oNi.getShapeOfPort(tPp);
			Point2D tPortCenter = new Point2D.Double(portPoly.getCenterX(), portPoly.getCenterY());
			double tDist = portCenter.distance(tPortCenter);
			if (tDist >= dist) continue;
			dist = tDist;
			opp = tPp;
			oPortCenter.setLocation(tPortCenter);
		}
		for(Iterator<PortProto> it = ni.getProto().getPorts(); it.hasNext(); )
		{
			PortProto tPp = it.next();
			if (tPp == pp) continue;
			if (!top.portsConnected(ni, tPp, pp)) continue;
			if (!tPp.getBasePort().connectsTo(ap)) continue;
			portPoly = ni.getShapeOfPort(tPp);
			Point2D tPortCenter = new Point2D.Double(portPoly.getCenterX(), portPoly.getCenterY());
			double tDist = oPortCenter.distance(tPortCenter);
			if (tDist >= dist) continue;
			dist = tDist;
			pp = tPp;
			portCenter.setLocation(tPortCenter);
		}

		// reject connection if it is out of the limit bounds
		if (limitBound != null)
		{
			if (!DBMath.pointInRect(portCenter, limitBound) && !DBMath.pointInRect(oPortCenter, limitBound))
				return false;
		}

		// find some dummy position to help run the arc
		double x = (oPortCenter.getX() + portCenter.getX()) / 2;
		double y = (oPortCenter.getY() + portCenter.getY()) / 2;
		if (alignment != null)
		{
			if (alignment.getWidth() > 0)
				x = Math.round(x / alignment.getWidth()) * alignment.getWidth();
			if (alignment.getHeight() > 0)
				y = Math.round(y / alignment.getHeight()) * alignment.getHeight();
		}

		// run the wire
		PortInst pi = ni.findPortInstFromEquivalentProto(pp);
		PortInst opi = oNi.findPortInstFromEquivalentProto(opp);
//System.out.println("   *** MAKING THE CONNECTION");

		// remember the current ArcProto and force the desired one
		ArcProto oldAP = User.getUserTool().getCurrentArcProto();
		User.getUserTool().setCurrentArcProtoTemporarily(ap);

		// make the connection
		boolean didConnect = connectObjects(pi, net, opi, oNet, ni.getParent(), new Point2D.Double(x,y), stayInside, top);

		// restore the current ArcProto
		User.getUserTool().setCurrentArcProtoTemporarily(oldAP);
		return didConnect;
	}

	/**
	 * Method to connect two objects if they touch.
	 * @param eobj1 the first object (either an ArcInst or a PortInst).
	 * @param net1 the network on which the first object resides.
	 * @param eobj2 the second object (either an ArcInst or a PortInst).
	 * @param net2 the network on which the second object resides.
	 * @param cell the Cell in which these objects reside.
	 * @param ctr bend point suggestion when making "L" connection.
	 * @param stayInside is the area in which to route (null to route arbitrarily).
	 * @param top the topology of the cell.
	 * @return true if a connection is made.
	 */
	private boolean connectObjects(ElectricObject eobj1, Network net1, ElectricObject eobj2, Network net2,
		Cell cell, Point2D ctr, PolyMerge stayInside, StitchingTopology top)
	{
		// run the wire
		NodeInst ni1 = null;
		if (eobj1 instanceof NodeInst) ni1 = (NodeInst)eobj1; else
			if (eobj1 instanceof PortInst) ni1 = ((PortInst)eobj1).getNodeInst();

		NodeInst ni2 = null;
		if (eobj2 instanceof NodeInst) ni2 = (NodeInst)eobj2; else
			if (eobj2 instanceof PortInst) ni2 = ((PortInst)eobj2).getNodeInst();

		Rectangle2D nullRect = null;
		Route route = router.planRoute(cell, eobj1, eobj2, ctr, stayInside, ep, true, true, nullRect, alignment);
		if (route.size() == 0) return false;

        allRoutes.add(route);
		top.connect(net1, net2);

		// if either ni or oNi is a pin primitive, see if it is a candidate for clean-up
		if (ni1 != null)
		{
			if (ni1.getFunction().isPin() &&
				!ni1.hasExports() && !ni1.hasConnections())
			{
				if (!possibleInlinePins.contains(ni1))
					possibleInlinePins.add(ni1);
			}
		}
		if (ni2 != null)
		{
			if (ni2.getFunction().isPin() &&
				!ni2.hasExports() && !ni2.hasConnections())
			{
				if (!possibleInlinePins.contains(ni2))
					possibleInlinePins.add(ni2);
			}
		}
		return true;
	}

    /****************************************** EXPORT-CREATION STITCHING ******************************************/

	/**
	 * Method to check an object for possible stitching to neighboring objects with export creation.
	 * Actual stitching is not done, but necessary exports are created.
	 * @param geom the object to check for stitching.
	 */
	private void checkExportCreationStitching(Geometric geom, Map<Long,List<PolyConnection>> overlapMap, GatherNetworksVisitor gatherNetworks)
	{
		Cell cell = geom.getParent();
		NodeInst ni = null;
		if (geom instanceof NodeInst) ni = (NodeInst)geom;

		// make a list of other geometrics that touch or overlap this one (copy it because the main list will change)
		List<Geometric> geomsInArea = new ArrayList<Geometric>();
		Rectangle2D geomBounds = geom.getBounds();
		double epsilon = DBMath.getEpsilon();
		Rectangle2D searchBounds = new Rectangle2D.Double(geomBounds.getMinX()-epsilon, geomBounds.getMinY()-epsilon,
			geomBounds.getWidth()+epsilon*2, geomBounds.getHeight()+epsilon*2);
		for(Iterator<Geometric> it = cell.searchIterator(searchBounds); it.hasNext(); )
		{
			Geometric oGeom = it.next();
			if (oGeom != geom) geomsInArea.add(oGeom);
		}
		for(Geometric oGeom : geomsInArea)
		{
			// find another node in this area
			if (oGeom instanceof ArcInst)
			{
				// other geometric is an ArcInst
				ArcInst oAi = (ArcInst)oGeom;

				if (ni == null) continue;

				// compare node "ni" against arc "oAi"
				if (ni.isCellInstance())
					compareNodeInstWithArcMakeExport(ni, oAi, overlapMap, gatherNetworks);
			} else
			{
				// other geometric a NodeInst
				NodeInst oNi = (NodeInst)oGeom;
				if (!oNi.isCellInstance())
				{
					PrimitiveNode pnp = (PrimitiveNode)oNi.getProto();
					if (pnp.getTechnology() == Generic.tech()) continue;
					if (!includePureLayerNodes && pnp.getFunction() == PrimitiveNode.Function.NODE) continue;
					if (includePureLayerNodes && pnp.getFunction() == PrimitiveNode.Function.NODE) {
                        // check that pure layer node has routable layer (this filters dummy layers, etc)
                        boolean hasValidLayer = false;
                        for (Iterator<Layer> layIt = pnp.getLayerIterator(); layIt.hasNext(); ) {
                            Layer l = layIt.next();
                            if (l.getFunction().isMetal() || l.getFunction().isDiff() || l.getFunction().isPoly()) {
                                hasValidLayer = true; break;
                            }
                        }
                        if (!hasValidLayer) continue;
                    } else {
                        continue;
                    }
                }

				if (ni == null)
				{
					// compare arc "geom" against node "oNi"
					if (oNi.isCellInstance())
						compareNodeInstWithArcMakeExport(oNi, (ArcInst)geom, overlapMap, gatherNetworks);
					continue;
				}

				// compare node "ni" against node "oNi"
				if (ni.isCellInstance())
				{
					compareTwoNodesMakeExport(ni, oNi, overlapMap, gatherNetworks);
				}
			}
		}
	}

	/**
	 * Method to compare a node instance and an arc to see if they touch and should be connected and an export created.
	 * @param ni the NodeInst to compare.
	 * @param ai the ArcInst to compare.
	 */
	private void compareNodeInstWithArcMakeExport(NodeInst ni, ArcInst ai, Map<Long,List<PolyConnection>> overlapMap, GatherNetworksVisitor gatherNetworks)
	{
		// get the polygon and layer that needs to connect
		Poly arcPoly = null;
		Poly [] arcPolys = ai.getProto().getTechnology().getShapeOfArc(ai);
		int aTot = arcPolys.length;
		for(int i=0; i<aTot; i++)
		{
			arcPoly = arcPolys[i];
			Layer arcLayer = arcPoly.getLayer();
			Layer.Function arcLayerFun = arcLayer.getFunction();
			if (arcLayerFun.isMetal() || arcLayerFun.isDiff() || arcLayerFun.isPoly()) break;
			arcPoly = null;
		}
		if (arcPoly == null) return;

        // this is the arc ai at the top level
        int netID = gatherNetworks.getGlobalNetworkID(VarContext.globalContext, ai.getHeadPortInst());
        SubPolygon sp2 = new SubPolygon(arcPoly, VarContext.globalContext, netID, ai);

		// look for geometry inside the cell that touches the arc, and make an export so it can connect
		ArcTouchVisitor atv = new ArcTouchVisitor(ai, arcPoly, ni, false, gatherNetworks);
		HierarchyEnumerator.enumerateCell(ni.getParent(), VarContext.globalContext, atv);
		SubPolygon sp = atv.getExportDrillLocation();
		if (sp != null)
		{
            registerPoly(overlapMap, new PolyConnection(sp, sp2));
            //makeExportDrill((NodeInst)sp.theObj, sp.poly.getPort(), sp.context, null, null);
			return;
		}

		// try arcs
		atv.setDoArcs(true);
		HierarchyEnumerator.enumerateCell(ni.getParent(), VarContext.globalContext, atv);
		sp = atv.getExportDrillLocation();
		if (sp != null)
		{
            registerPoly(overlapMap, new PolyConnection(sp, sp2));
		}
	}

    /**
     * Make an export if necessary to connect the two networks.
     * Each pair of sub-polygons in the list represents the same two networks,
     * so being able to connect one pair means the others will also be connected.
     * @param polys A list of poly connections (pairs of sub-polygons)
     */
    private void makeExport(List<PolyConnection> polys)
    {
        if (polys.size() == 0) return;

        // check for existing export
        Point2D sp1AtTop = null, sp2AtTop = null;
        for (PolyConnection p : polys)
        {
            sp1AtTop = isExportedToTop(p.sp1);
            sp2AtTop = isExportedToTop(p.sp2);
            if (sp1AtTop != null && sp2AtTop != null) return; // both exported to top
        }
        if (sp1AtTop != null) sp1AtTop = new Point2D.Double(sp1AtTop.getX(), sp1AtTop.getY()); //convert Epoint to Point2D
        if (sp2AtTop != null) sp2AtTop = new Point2D.Double(sp2AtTop.getX(), sp2AtTop.getY()); //convert Epoint to Point2D

        // none can connect at top level, export up first pair
        PolyConnection p = polys.get(0);
        List<Line2D> overlappingEdges = new ArrayList<Line2D>();
        List<PolyBase> intersectionList = p.sp1.poly.getIntersection(p.sp2.poly, overlappingEdges);
        PolyBase preferredExportArea = null;
        if (intersectionList != null && intersectionList.size() > 0) {
            preferredExportArea = intersectionList.get(0);
        } else if (overlappingEdges.size() > 0) {
            preferredExportArea = new PolyBase(overlappingEdges.get(0).getBounds());
        }

        // figure out which arc to use to connect the two - needed to decide how to create exports
        ArcProto ap = null;
        if (p.sp1.theObj instanceof ArcInst) {
            ap = ((ArcInst)p.sp1.theObj).getProto();
        }
        if (p.sp2.theObj instanceof ArcInst) {
            ap = ((ArcInst)p.sp2.theObj).getProto();
        }
        if (ap == null) {
            ap = Router.getArcToUse(p.sp1.poly.getPort(), p.sp2.poly.getPort());
        }

        // make nodeinst exports first to get locations for arc exports
        if (sp1AtTop == null && (p.sp1.theObj instanceof NodeInst)) {
            sp1AtTop = makeExportDrill((NodeInst)p.sp1.theObj, p.sp1.poly.getPort(), p.sp1.context, preferredExportArea, ap);
        }
        if (sp2AtTop == null && (p.sp2.theObj instanceof NodeInst)) {
            sp2AtTop = makeExportDrill((NodeInst)p.sp2.theObj, p.sp2.poly.getPort(), p.sp2.context, preferredExportArea, ap);
        }

        // make arc instance connections
        if (sp1AtTop == null && (p.sp1.theObj instanceof ArcInst) && sp2AtTop != null) {
            makeExportDrillOnArc(sp2AtTop, p.sp1, preferredExportArea);
            //System.out.println("Making export on arc for netID "+p.sp1.netID);
        }
        if (sp2AtTop == null && (p.sp2.theObj instanceof ArcInst) && sp1AtTop != null) {
            makeExportDrillOnArc(sp1AtTop, p.sp2, preferredExportArea);
            //System.out.println("Making export on arc for netID "+p.sp2.netID);
        }
    }

    /**
     * See if the sub-polygon is exported all the way to the top level of the var context.
     * @param sp the sub-polygon
     * @return true if exported to the top level, false otherwise
     */
    private Point2D isExportedToTop(SubPolygon sp)
    {
        Geometric geom = sp.theObj;
        if (geom instanceof NodeInst)
        {
            NodeInst ni = (NodeInst)geom;
            PortInst pi = ni.findPortInstFromEquivalentProto(sp.poly.getPort());
            return isExportedToTop(pi, sp.context);
        }
        if (geom instanceof ArcInst)
        {
            ArcInst ai = (ArcInst)geom;
            PortInst pi1 = ai.getHead().getPortInst();
            PortInst pi2 = ai.getTail().getPortInst();
            Point2D point1 = isExportedToTop(pi1, sp.context);
            if (point1 != null) return point1;
            return isExportedToTop(pi2, sp.context);
        }
        return null;
    }

    /**
     * See if the portinst is exported all the way to the top level of the var context.
     * @param pi the port instance
     * @param context context
     * @return true if exported to the top level, false otherwise
     */
    private Point2D isExportedToTop(PortInst pi, VarContext context)
    {
        while (context != VarContext.globalContext)
        {
            if (pi.getExports().hasNext())
            {
                Export e = pi.getExports().next();
                Nodable no = context.getNodable();
                if (no instanceof NodeInst)
                {
                    NodeInst ni = (NodeInst)no;
                    pi = ni.findPortInstFromEquivalentProto(e);
                    context = context.pop();
                } else
                {
                    return null;
                }
            } else
            {
                return null;
            }
        }
        return pi.getCenter();
    }

    /**
	 * HierarchyEnumerator subclass to find cell geometry that touches an arc at the upper level.
	 */
	private class ArcTouchVisitor extends HierarchyEnumerator.Visitor
	{
		private ArcInst arcOfInterest;
		private Poly arcPoly;
		private int arcNetID;
		private NodeInst cellOfInterest;
		private boolean doArcs;
		private Rectangle2D arcBounds;
		private SubPolygon bestSubPolygon;
        private GatherNetworksVisitor gatherNetworks;

        public ArcTouchVisitor(ArcInst arcOfInterest, Poly arcPoly, NodeInst cellOfInterest, boolean doArcs, GatherNetworksVisitor gatherNetworks)
		{
			this.arcOfInterest = arcOfInterest;
			this.arcPoly = arcPoly;
			this.cellOfInterest = cellOfInterest;
			this.doArcs = doArcs;
			arcNetID = -1;
			arcBounds = arcPoly.getBounds2D();
			bestSubPolygon = null;
            this.gatherNetworks = gatherNetworks;
        }

		public SubPolygon getExportDrillLocation() { return bestSubPolygon; }

		public void setDoArcs(boolean doArcs) { this.doArcs = doArcs; }

        @Override
		public boolean enterCell(HierarchyEnumerator.CellInfo info) { return true; }

        @Override
		public void exitCell(HierarchyEnumerator.CellInfo info)
		{
			if (info.isRootCell()) return;
			Netlist nl = info.getNetlist();

			if (doArcs)
			{
				// look at all arcs and see if they intersect the arc
				for(Iterator<ArcInst> it = info.getCell().getArcs(); it.hasNext(); )
				{
					ArcInst ai = it.next();
					ArcProto ap = ai.getProto();
					FixpTransform arcTrans = info.getTransformToRoot();
					Technology tech = ap.getTechnology();
					Poly [] arcInstPolyList = tech.getShapeOfArc(ai);
					for(int i=0; i<arcInstPolyList.length; i++)
					{
						PolyBase poly = arcInstPolyList[i];
						if (poly.getLayer() != arcPoly.getLayer()) continue;
						int netID = -1;
						Network net = nl.getNetwork(ai, 0);
						if (net != null) netID = info.getNetID(net);
						if (netID == arcNetID) continue;
						poly.transform(arcTrans);
						double dist = poly.separation(arcPoly);
						if (dist >= DBMath.getEpsilon()) continue;
						int netIDglobal = gatherNetworks.getGlobalNetworkID(info.getContext(), ai.getHeadPortInst());
                        SubPolygon sp = new SubPolygon(poly, info.getContext(), netIDglobal, ai);
						bestSubPolygon = sp;
					}
				}
			} else
			{
				// look at all nodes and see if they intersect the arc
				for(Iterator<NodeInst> it = info.getCell().getNodes(); it.hasNext(); )
				{
					NodeInst ni = it.next();
					if (ni.isCellInstance()) continue;
					PrimitiveNode pNp = (PrimitiveNode)ni.getProto();
					FixpTransform nodeTrans = ni.rotateOut(info.getTransformToRoot());
					Technology tech = pNp.getTechnology();
					Poly [] nodeInstPolyList = tech.getShapeOfNode(ni, true, true, null);
					for(int i=0; i<nodeInstPolyList.length; i++)
					{
						PolyBase poly = nodeInstPolyList[i];
						if (poly.getLayer() != arcPoly.getLayer()) continue;
						int netID = -1, netIDglobal = -1;
						if (poly.getPort() != null)
						{
                            netIDglobal = gatherNetworks.getGlobalNetworkID(info.getContext(), ni.findPortInstFromEquivalentProto(poly.getPort()));
							Network net = nl.getNetwork(ni, poly.getPort(), 0);
							if (net != null) netID = info.getNetID(net);
						}
						if (netID == arcNetID) continue;
						poly.transform(nodeTrans);
						double dist = poly.separation(arcPoly);
						if (dist >= DBMath.getEpsilon()) continue;
						SubPolygon sp = new SubPolygon(poly, info.getContext(), netIDglobal, ni);
						if (bestSubPolygon != null)
						{
							if (!ni.hasExports()) continue;
						}
						bestSubPolygon = sp;
					}
				}
			}
		}

        @Override
		public boolean visitNodeInst(Nodable no, HierarchyEnumerator.CellInfo info)
		{
			NodeInst ni = no.getNodeInst();
			if (info.isRootCell())
			{
				// ignore any subcells that aren't the one being examined
				if (ni != cellOfInterest) return false;

				// cache the network ID of the arc
				if (arcNetID < 0)
				{
					Netlist nl = info.getNetlist();
					Network net = nl.getNetwork(arcOfInterest, 0);
					if (net != null) arcNetID = info.getNetID(net);
				}
				return true;
			}

			// only examine subcells if they intersect the arc
			if (!ni.isCellInstance()) return false;
			Rectangle2D b = ni.getBounds();
			Rectangle2D bounds = new Rectangle2D.Double(b.getMinX(), b.getMinY(), b.getWidth(), b.getHeight());
			FixpTransform trans = info.getTransformToRoot();
			DBMath.transformRect(bounds, trans);
			if (DBMath.rectsIntersect(bounds, arcBounds)) return true;
			return false;
		}
	}

	/**
	 * Method to create a stack of exports to reach a NodeInst down the hierarchy.
	 * @param ni the NodeInst at the bottom of the hierarchy being exported.
	 * @param exportThis the port on the NodeInst being exported.
	 * @param where the hierarchical stack that defines the path to the top.
	 * @return the coordinate at the top-level where the drill happened.
	 */
	private Point2D makeExportDrill(NodeInst ni, PortProto exportThis, VarContext where, PolyBase preferredExportArea, ArcProto ap)
	{
		Point2D topCoord = new Point2D.Double(0, 0);
		while (where != VarContext.globalContext)
		{
			PortInst pi = ni.findPortInstFromEquivalentProto(exportThis);
			if (pi == null) break;
			Iterator<Export> eIt = pi.getExports();
            boolean existingExportFound = false;
			if (eIt.hasNext())
			{
				exportThis = eIt.next();
			} else
			{
                Rectangle2D bounds = ni.getBounds();
                Cell cell = ni.getParent();

                String exportName = getExportNameInCell(cell, pi);
                // special case for primitive node (bottom most port) - export a pin at a different location to make it
                // easier to connect to at the top level
                if (!ni.isCellInstance() && preferredExportArea != null && ap != null) // primitive node
                {
                    PrimitiveNode pn = (PrimitiveNode)ni.getProto();
                    // unfortunately preferredExportArea is relative to the top level, but we need it relative to the current level
                    for (Iterator<Nodable> noit = where.getPathIterator(); noit.hasNext(); )
                    {
                        Nodable no = noit.next();
                        if (!(no instanceof NodeInst)) break;
                        NodeInst niHier = (NodeInst)no;
                        FixpTransform trans = niHier.transformIn();
                        preferredExportArea.transform(trans);
                    }

                    // check if there is already an export on this network in the preferred area,
                    // and that it can connect to pi
                    Netlist netlist = cell.getNetlist();
                    Network net = netlist.getNetwork(pi);
                    for (Iterator<Export> eit = cell.getExports(); eit.hasNext(); )
                    {
                        Export ex = eit.next();
                        PortInst expi = ex.getOriginalPort();
                        if (preferredExportArea.contains(expi.getCenter()))
                        {
                            if (net != netlist.getNetwork(expi)) continue;
                            if (ex.connectsTo(ap)) {
                                existingExportFound = true;
                                exportThis = ex;
                                break;
                            }
                        }
                    }

                    if (!existingExportFound)
                    {
                        Point2D center = preferredExportArea.getCenter();
                        if (alignment != null) {
                            double x = center.getX();
                            double y = center.getY();
                            if (alignment.getWidth() > 0) x = Math.round(x/alignment.getWidth()) * alignment.getWidth();
                            if (alignment.getHeight() > 0) y = Math.round(y/alignment.getHeight()) * alignment.getHeight();
                            center = new Point2D.Double(x, y);
                        }

                        // if the new export center is still within bounds
                        if (DBMath.pointInRect(center, bounds))
                        {
                            // make the new export if it is a primitive node (avoid huge export area), or if the export would
                            // not be within the preferred area
                            if (!preferredExportArea.contains(pi.getCenter()) || pn.getFunction() == PrimitiveNode.Function.NODE)
                            {
                                // make export on pin and wire to pi, rather than exporting pi
                                PrimitiveNode pin = ap.findPinProto();
                                NodeInst pinNi = NodeInst.newInst(pin, ep, center, pin.getDefWidth(ep), pin.getDefHeight(ep), cell);
                                Route route = router.planRoute(cell, pinNi.getOnlyPortInst(), pi, center, null, ep, false, false, null, null);
                                if (Router.createRouteNoJob(route, cell, new HashMap<ArcProto,Integer>(), new HashMap<NodeProto,Integer>(), ep) != null) {
                                    pi = pinNi.getOnlyPortInst();
                                } else {
                                    if (pinNi != null) pinNi.kill(); // delete if route failed
                                }
                            }
                        }
                    }
                }
                if (!existingExportFound)
                    exportThis = Export.newInst(cell, pi, exportName, ep);
            }
			ni = where.getNodable().getNodeInst();
			where = where.pop();
			FixpTransform trans = ni.transformOut();
			trans.transform(pi.getPoly().getCenter(), topCoord);
		}
		return topCoord;
	}

	/**
	 * Method to create a stack of exports to reach an ArcInst down the hierarchy.
	 * @param topLoc the coordinate at the top level where the ArcInst should be broken.
	 * @param sp the context to the ArcInst.
	 */
	private void makeExportDrillOnArc(Point2D topLoc, SubPolygon sp, PolyBase preferredExportArea)
	{
		// save information about the arc
		ArcInst lowAI = (ArcInst)sp.theObj;
		if (!lowAI.isLinked()) return;
		String arcName = lowAI.getName();
		if (lowAI.getNameKey().isTempname()) arcName = null;
		int angle = lowAI.getAngle();
		ArcProto ap = lowAI.getProto();
		double width = lowAI.getLambdaBaseWidth();
		Cell cell = lowAI.getParent();

        for (Iterator<Nodable> noit = sp.context.getPathIterator(); noit.hasNext(); )
        {
            NodeInst niHier = noit.next().getNodeInst();
            FixpTransform trans = niHier.transformIn();
            trans.transform(topLoc, topLoc);
            if (preferredExportArea != null)
                preferredExportArea.transform(trans);
        }

        // check if there is already an export on this network in the preferred area,
        // and that it can connect to ai
        Netlist netlist = cell.getNetlist();
        Network net = netlist.getNetwork(lowAI, 0);
        for (Iterator<Export> eit = cell.getExports(); eit.hasNext(); )
        {
            Export ex = eit.next();
            PortInst expi = ex.getOriginalPort();
            if (preferredExportArea.contains(expi.getCenter()))
            {
                if (net != netlist.getNetwork(expi)) continue;
                if (ex.connectsTo(ap)) {
                    // re-export up the hierarchy
                    makeExportDrill(ex.getOriginalPort().getNodeInst(), ex, sp.context.pop(), preferredExportArea, ap);
                    return;
                }
            }
        }

		// make a pin at the desired location on the arc
        NodeInst ni = null;

        // make sure the location is on the arc
        if (GenMath.distToLine(lowAI.getHeadLocation(), lowAI.getTailLocation(), topLoc) > 0)
            return;
        PrimitiveNode pNp = lowAI.getProto().findPinProto();
        ni = NodeInst.makeInstance(pNp, ep, topLoc, pNp.getDefWidth(ep), pNp.getDefHeight(ep), cell);

        // insert the pin into the arc
        ArcInst newAi1 = ArcInst.makeInstanceBase(ap, ep, width, lowAI.getHeadPortInst(), ni.getOnlyPortInst(), lowAI.getHeadLocation(), topLoc, null);
        ArcInst newAi2 = ArcInst.makeInstanceBase(ap, ep, width, ni.getOnlyPortInst(), lowAI.getTailPortInst(), topLoc, lowAI.getTailLocation(), null);
        newAi1.setHeadNegated(lowAI.isHeadNegated());
        newAi1.setHeadExtended(lowAI.isHeadExtended());
        newAi1.setHeadArrowed(lowAI.isHeadArrowed());
        newAi2.setTailNegated(lowAI.isTailNegated());
        newAi2.setTailExtended(lowAI.isTailExtended());
        newAi2.setTailArrowed(lowAI.isTailArrowed());
        lowAI.kill();
        if (arcName != null)
        {
            if (lowAI.getHeadLocation().distance(topLoc) > lowAI.getTailLocation().distance(topLoc))
            {
                newAi1.setName(arcName, ep);
                newAi1.copyTextDescriptorFrom(lowAI, ArcInst.ARC_NAME);
            } else
            {
                newAi2.setName(arcName, ep);
                newAi2.copyTextDescriptorFrom(lowAI, ArcInst.ARC_NAME);
            }
        }
        newAi1.setAngle(angle);
        newAi2.setAngle(angle);

        makeExportDrill(ni, ni.getOnlyPortInst().getPortProto(), sp.context, preferredExportArea, ap);
	}

	private String getExportNameInCell(Cell cell, PortInst pi)
	{
		Netlist nl = cell.getNetlist();
		Network net = nl.getNetwork(pi);
		String exportName = null;
		for(Iterator<String> nIt = net.getExportedNames(); nIt.hasNext(); )
		{
			String eName = nIt.next();
			if (eName.startsWith("E") && eName.length() > 1 && TextUtils.isDigit(eName.charAt(1)))
				continue;
			if (exportName == null || exportName.length() < eName.length()) exportName = eName;
		}
		if (exportName == null) exportName = "E1";
		exportName = ElectricObject.uniqueObjectName(exportName, cell, PortProto.class, false, true);
		return exportName;
	}

	/**
	 * Class to define a polygon that is down in the hierarchy and has a context.
	 */
	private static class SubPolygon implements RTBounds
	{
		PolyBase poly;
		int netID;
		VarContext context;
		Geometric theObj;

		SubPolygon(PolyBase poly, VarContext context, int netID, Geometric theObj)
		{
			this.poly = poly;
			this.context = context;
			this.netID = netID;
			this.theObj = theObj;
		}

        @Override
		public FixpRectangle getBounds() { return poly.getBounds2D(); }
	}

	/**
	 * Class to define two polygons that will connect down in the hierarchy.
	 */
	private static class PolyConnection
	{
		SubPolygon sp1, sp2;
		double distance;

		PolyConnection(SubPolygon sp1, SubPolygon sp2)
		{
			this.sp1 = sp1;
			this.sp2 = sp2;
			distance = sp1.poly.getCenter().distance(sp2.poly.getCenter());
		}
	}

	/**
	 * Method to compare two node instances to see if anything inside of them needs to connect.
	 * May have to create exports to make the connection.
	 * @param ni1 the first cell instance being checked.
	 * @param ni2 the second cell instance being checked.
	 */
	private void compareTwoNodesMakeExport(NodeInst ni1, NodeInst ni2, Map<Long,List<PolyConnection>> overlapMap, GatherNetworksVisitor gatherNetworks)
	{
		// force the second to be a cell instance
		if (!ni2.isCellInstance())
		{
			NodeInst swap = ni1;   ni1 = ni2;   ni2 = swap;
		}
		if (!ni2.isCellInstance()) return;

		// ignore stuff from different technologies
        if (ni1.getProto().getTechnology() != ni2.getProto().getTechnology()) return;

		// first find the area of intersection between the two nodes
		Rectangle2D bound1 = ni1.getBounds();
		Rectangle2D bound2 = ni2.getBounds();
		bound1 = new Rectangle2D.Double(bound1.getMinX()-DBMath.getEpsilon(), bound1.getMinY()-DBMath.getEpsilon(),
			bound1.getWidth()+DBMath.getEpsilon()*2, bound1.getHeight()+DBMath.getEpsilon()*2);
		bound2 = new Rectangle2D.Double(bound2.getMinX()-DBMath.getEpsilon(), bound2.getMinY()-DBMath.getEpsilon(),
			bound2.getWidth()+DBMath.getEpsilon()*2, bound2.getHeight()+DBMath.getEpsilon()*2);
		if (!DBMath.rectsIntersect(bound1, bound2)) return;
		Rectangle2D intersectArea = bound1.createIntersection(bound2);

		// now find all polygons in Node 1 that are in the intersection area
		RTNode<SubPolygon> rtree = null;
		if (ni1.isCellInstance())
		{
			GatherPolygonVisitor gpv = new GatherPolygonVisitor(intersectArea, ni1, gatherNetworks);
			HierarchyEnumerator.enumerateCell(ni1.getParent(), VarContext.globalContext, gpv);
			rtree = gpv.getRTree();
		} else
		{
			rtree = RTNode.makeTopLevel();
			Technology tech = ni1.getProto().getTechnology();
			Poly[] polys = tech.getShapeOfNode(ni1, true, true, null);
			FixpTransform trans = ni1.rotateOut();
			for(int i=0; i<polys.length; i++)
			{
				Poly poly = polys[i];
				poly.transform(trans);
                int netID = -1;
                if (poly.getPort() != null) {
                    netID = gatherNetworks.getGlobalNetworkID(VarContext.globalContext, ni1.findPortInstFromEquivalentProto(poly.getPort()));
                }
                if (!DBMath.rectsIntersect(poly.getBounds2D(), intersectArea)) continue;
				rtree = RTNode.linkGeom(null, rtree, new SubPolygon(poly, VarContext.globalContext, netID, ni1));
			}
		}

		// now take the list into the second node and look for connections
		CheckPolygonVisitor cpv = new CheckPolygonVisitor(rtree, intersectArea, ni2, gatherNetworks);
		HierarchyEnumerator.enumerateCell(ni2.getParent(), VarContext.globalContext, cpv);
		List<PolyConnection> polysFound = cpv.getFoundConnections();

        // add to map of things that overlap should be connected
        for (PolyConnection p : polysFound) {
            registerPoly(overlapMap, p);
        }
	}

	/**
	 * HierarchyEnumerator subclass to check all geometry in a cell against polygons that may intersect.
	 */
	private class CheckPolygonVisitor extends HierarchyEnumerator.Visitor
	{
		private RTNode<SubPolygon> rtree;
		private Rectangle2D intersectArea;
		private NodeInst cellOfInterest;
		private List<PolyConnection> connectionsFound;
        private GatherNetworksVisitor gatherNetworks;

        public CheckPolygonVisitor(RTNode<SubPolygon> rtree, Rectangle2D intersectArea, NodeInst cellOfInterest, GatherNetworksVisitor gatherNetworks)
		{
			this.rtree = rtree;
			this.intersectArea = intersectArea;
			this.cellOfInterest = cellOfInterest;
            this.gatherNetworks = gatherNetworks;
            connectionsFound = new ArrayList<PolyConnection>();
		}

		public List<PolyConnection> getFoundConnections() { return connectionsFound; }

        @Override
		public boolean enterCell(HierarchyEnumerator.CellInfo info) { return true; }

        @Override
		public void exitCell(HierarchyEnumerator.CellInfo info)
		{
			FixpTransform toTop = info.getTransformToRoot();

			// check all nodes against the list
			for(Iterator<NodeInst> it = info.getCell().getNodes(); it.hasNext(); )
			{
				NodeInst ni = it.next();
				if (ni.isCellInstance()) continue;
				FixpTransform nodeTrans = ni.rotateOut(toTop);
				Technology tech = ni.getProto().getTechnology();
				Poly[] polys = tech.getShapeOfNode(ni, true, true, null);
				for(int i=0; i<polys.length; i++)
				{
					Poly poly = polys[i];
					if (poly.getPort() == null) continue;
					poly.transform(nodeTrans);
					if (!DBMath.rectsIntersect(poly.getBounds2D(), intersectArea)) continue;
					for(Iterator<SubPolygon> sea = new RTNode.Search<SubPolygon>(poly.getBounds2D(), rtree, true); sea.hasNext(); )
					{
						SubPolygon sp = sea.next();
						if (sp.poly.getLayer() != poly.getLayer()) continue;
						if (sp.poly.separation(poly) >= DBMath.getEpsilon()) continue;
						int netID = gatherNetworks.getGlobalNetworkID(info.getContext(), ni.findPortInstFromEquivalentProto(poly.getPort()));
						SubPolygon sp2 = new SubPolygon(poly, info.getContext(), netID, ni);
						addConnection(sp, sp2);
					}
				}
			}

			// check all arcs against the list
			for(Iterator<ArcInst> it = info.getCell().getArcs(); it.hasNext(); )
			{
				ArcInst ai = it.next();
				Technology tech = ai.getProto().getTechnology();
				Poly [] arcPolyList = tech.getShapeOfArc(ai);
				for(int i=0; i<arcPolyList.length; i++)
				{
					PolyBase poly = arcPolyList[i];
					poly.transform(toTop);
					if (!DBMath.rectsIntersect(poly.getBounds2D(), intersectArea)) continue;
					for(Iterator<SubPolygon> sea = new RTNode.Search<SubPolygon>(poly.getBounds2D(), rtree, true); sea.hasNext(); )
					{
						SubPolygon sp = sea.next();
						if (sp.poly.getLayer() != poly.getLayer()) continue;
						if (sp.poly.separation(poly) > 0) continue;
						int netID = gatherNetworks.getGlobalNetworkID(info.getContext(), ai.getHeadPortInst());
						SubPolygon sp2 = new SubPolygon(poly, info.getContext(), netID, ai);
						addConnection(sp, sp2);
					}
				}
			}
		}

        @Override
		public boolean visitNodeInst(Nodable no, HierarchyEnumerator.CellInfo info)
		{
			// only examine subcells if they intersect the area of interest
			NodeInst ni = no.getNodeInst();
			if (info.isRootCell())
			{
				// ignore any subcells that aren't the one being examined
				if (ni != cellOfInterest) return false;
			}
			if (!ni.isCellInstance()) return false;
			Rectangle2D b = ni.getBounds();
			Rectangle2D bounds = new Rectangle2D.Double(b.getMinX(), b.getMinY(), b.getWidth(), b.getHeight());
			FixpTransform trans = info.getTransformToRoot();
			DBMath.transformRect(bounds, trans);
			if (DBMath.rectsIntersect(bounds, intersectArea)) return true;
			return false;
		}

		private void addConnection(SubPolygon sp1, SubPolygon sp2)
		{
			double distance = sp1.poly.getCenter().distance(sp2.poly.getCenter());
			boolean found = false;
			for(PolyConnection pc : connectionsFound)
			{
				if (pc.sp1.netID == sp1.netID && pc.sp2.netID == sp2.netID)
				{
					// found this connection: see if it is closer
					boolean replace = distance < pc.distance;
					int oldNodeCount = (pc.sp1.theObj instanceof NodeInst?1:0) + (pc.sp2.theObj instanceof NodeInst?1:0);
					int newNodeCount = (sp1.theObj instanceof NodeInst?1:0) + (sp2.theObj instanceof NodeInst?1:0);
					if (newNodeCount > oldNodeCount) replace = true; else
						if (newNodeCount < oldNodeCount) replace = false;
					if (replace)
					{
						pc.sp1 = sp1;
						pc.sp2 = sp2;
						pc.distance = distance;
					}
					found = true;
					break;
				}
			}
			if (!found)
			{
				connectionsFound.add(new PolyConnection(sp1, sp2));
			}
		}
	}

	/**
	 * HierarchyEnumerator subclass to find all geometry in a cell that touches a desired area at the upper level.
	 */
	private class GatherPolygonVisitor extends HierarchyEnumerator.Visitor
	{
		private RTNode<SubPolygon> rtree;
		private Rectangle2D intersectArea;
		private NodeInst cellOfInterest;
        private GatherNetworksVisitor gatherNetworks;

        public GatherPolygonVisitor(Rectangle2D intersectArea, NodeInst cellOfInterest, GatherNetworksVisitor gatherNetworks)
		{
			rtree = RTNode.makeTopLevel();
			this.intersectArea = intersectArea;
			this.cellOfInterest = cellOfInterest;
            this.gatherNetworks = gatherNetworks;
        }

		public RTNode<SubPolygon> getRTree() { return rtree; }

        @Override
		public boolean enterCell(HierarchyEnumerator.CellInfo info) { return true; }

        @Override
		public void exitCell(HierarchyEnumerator.CellInfo info)
		{
			FixpTransform toTop = info.getTransformToRoot();

			// add all nodes to the list
			for(Iterator<NodeInst> it = info.getCell().getNodes(); it.hasNext(); )
			{
				NodeInst ni = it.next();
				if (ni.isCellInstance()) continue;
				FixpTransform nodeTrans = ni.rotateOut(toTop);
				Technology tech = ni.getProto().getTechnology();
				Poly[] polys = tech.getShapeOfNode(ni, true, true, null);
				for(int i=0; i<polys.length; i++)
				{
					Poly poly = polys[i];
					Layer.Function nodeLayerFun = poly.getLayer().getFunction();
					if (!nodeLayerFun.isMetal() && !nodeLayerFun.isDiff() && !nodeLayerFun.isPoly()) continue;
					if (poly.getPort() == null) continue;
					poly.transform(nodeTrans);
					if (!DBMath.rectsIntersect(poly.getBounds2D(), intersectArea)) continue;
					int netID = gatherNetworks.getGlobalNetworkID(info.getContext(), ni.findPortInstFromEquivalentProto(poly.getPort()));
					rtree = RTNode.linkGeom(null, rtree, new SubPolygon(poly, info.getContext(), netID, ni));
				}
			}

			// add all arcs to the list
			for(Iterator<ArcInst> it = info.getCell().getArcs(); it.hasNext(); )
			{
				ArcInst ai = it.next();
				Technology tech = ai.getProto().getTechnology();
				Poly [] arcPolyList = tech.getShapeOfArc(ai);
				for(int i=0; i<arcPolyList.length; i++)
				{
					PolyBase poly = arcPolyList[i];
					Layer.Function arcLayerFun = poly.getLayer().getFunction();
					if (!arcLayerFun.isMetal() && !arcLayerFun.isDiff() && !arcLayerFun.isPoly()) continue;
					poly.transform(toTop);
					if (!DBMath.rectsIntersect(poly.getBounds2D(), intersectArea)) continue;
					int netID = gatherNetworks.getGlobalNetworkID(info.getContext(), ai.getHeadPortInst());
					rtree = RTNode.linkGeom(null, rtree, new SubPolygon(poly, info.getContext(), netID, ai));
				}
			}
		}

        @Override
		public boolean visitNodeInst(Nodable no, HierarchyEnumerator.CellInfo info)
		{
			// only examine subcells if they intersect the area of interest
			NodeInst ni = no.getNodeInst();
			if (info.isRootCell())
			{
				// ignore any subcells that aren't the one being examined
				if (ni != cellOfInterest) return false;
			}
			if (!ni.isCellInstance()) return false;
			Rectangle2D b = ni.getBounds();
			Rectangle2D bounds = new Rectangle2D.Double(b.getMinX(), b.getMinY(), b.getWidth(), b.getHeight());
			FixpTransform trans = info.getTransformToRoot();
			DBMath.transformRect(bounds, trans);
			if (DBMath.rectsIntersect(bounds, intersectArea)) return true;
			return false;
		}
	}

    private void registerPoly(Map<Long,List<PolyConnection>> overlapMap, PolyConnection p)
    {
        // make sure lower netID is in sp1
        if (p.sp1.netID > p.sp2.netID) {
            SubPolygon spTemp = p.sp1;
            p.sp1 = p.sp2;
            p.sp2 = spTemp;
        }

        long netID1 = p.sp1.netID;
        long netID2 = p.sp2.netID;
        if (netID1 == netID2) return; // already connected
        if (netID1 < 0 || netID2 < 0) {
            System.out.println("Ignoring poly "+p.sp1.theObj.describe(false)+", netID is "+netID1);
            System.out.println("Ignoring poly "+p.sp2.theObj.describe(false)+", netID is "+netID2);
            return;
        }

        // get unique key
        Long key = Long.valueOf((netID1 << Integer.SIZE) | (netID2));
        List<PolyConnection> polys = overlapMap.get(key);
        if (polys == null) {
            polys = new ArrayList<PolyConnection>();
            overlapMap.put(key,polys);
        }
        polys.add(p);
    }

    /**
     * HierarchyEnumerator subclass to gather a global consistent network map
     */
    private class GatherNetworksVisitor extends HierarchyEnumerator.Visitor
    {
        // String key is varContext.getInstPath(".")
        private Map<String,Map<PortInst,Integer>> networkToNetID = new HashMap<String,Map<PortInst,Integer>>();

        @Override
        public boolean enterCell(HierarchyEnumerator.CellInfo info) {
            return true;
        }

        @Override
        public void exitCell(HierarchyEnumerator.CellInfo info) {
            Cell cell = info.getCell();
            VarContext context = info.getContext();
            String key = context.getInstPath(".");
            Map<PortInst,Integer> netIdMap = networkToNetID.get(key);
            if (netIdMap == null) {
                netIdMap = new HashMap<PortInst,Integer>();
                networkToNetID.put(key, netIdMap);
            }

            Netlist netlist = info.getNetlist();
            for (Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); ) {
                NodeInst ni = it.next();
                for (Iterator<PortInst> pit = ni.getPortInsts(); pit.hasNext(); ) {
                    PortInst pi = pit.next();
                    Network net = netlist.getNetwork(pi);
                    if (net == null) continue;
                    int netID = info.getNetID(net);
                    netIdMap.put(pi, Integer.valueOf(netID));
                }
            }
        }

        @Override
        public boolean visitNodeInst(Nodable ni, HierarchyEnumerator.CellInfo info) {
            return true;
        }

        private int getGlobalNetworkID(VarContext context, PortInst pi) {
            if (pi == null) return -1;
            if (context == null) return -1;
            String key = context.getInstPath(".");
            Map<PortInst,Integer> netIdMap = networkToNetID.get(key);
            if (netIdMap == null) return -1;
            if (!netIdMap.containsKey(pi)) return -1;
            return netIdMap.get(pi).intValue();
        }
    }

    /****************************************** SUPPORT ******************************************/

	/**
	 * Method to determine if an arc is too wide for its ends.
	 * Arcs that are wider than their nodes stick out from those nodes,
	 * and their geometry must be considered, even though the nodes have been checked.
	 * @param ai the ArcInst to check.
	 * @return true if the arc is wider than its end nodes
	 */
	private boolean arcTooWide(ArcInst ai)
	{
		boolean headTooWide = true;
		NodeInst hNi = ai.getHeadPortInst().getNodeInst();
		if (hNi.isCellInstance()) headTooWide = false; else
			if (ai.getLambdaBaseWidth() <= hNi.getLambdaBaseXSize() &&
				ai.getLambdaBaseWidth() <= hNi.getLambdaBaseYSize()) headTooWide = false;

		boolean tailTooWide = true;
		NodeInst tNi = ai.getTailPortInst().getNodeInst();
		if (tNi.isCellInstance()) tailTooWide = false; else
			if (ai.getLambdaBaseWidth() <= tNi.getLambdaBaseXSize() &&
				ai.getLambdaBaseWidth() <= tNi.getLambdaBaseYSize()) tailTooWide = false;

		return headTooWide || tailTooWide;
	}

	/**
	 * Method to get the shape of a node as a list of Polys.
	 * The auto-router uses this instead of Technology.getShapeOfNode()
	 * because this gets electrical layers and makes invisible pins be visible
	 * if they have coverage from connecting arcs.
	 * @param ni the node to inspect.  It must be primitive.
	 * @return an array of Poly objects that describe the node.
	 */
	private Poly [] shapeOfNode(NodeInst ni)
	{
		// compute the list of polygons
		Technology tech = ni.getProto().getTechnology();
		if (tech.isSchematics()) return new Poly[0];
		Poly [] nodePolys = tech.getShapeOfNode(ni, true, true, null);
		if (nodePolys.length == 0) return nodePolys;

		// if this is a pin, check the arcs that cover it
		if (ni.getFunction().isPin())
		{
			// pins must be covered by an arc that is extended and has enough width to cover the pin
			boolean gotOne = false;
			Rectangle2D coverage = null;
			for(Iterator<Connection> it = ni.getConnections(); it.hasNext(); )
			{
				Connection con = it.next();
				ArcInst ai = con.getArc();
				if (ai.getLambdaBaseWidth() >= ni.getLambdaBaseXSize() &&
					ai.getLambdaBaseWidth() >= ni.getLambdaBaseYSize() && ai.isHeadExtended() && ai.isTailExtended())
				{
					gotOne = true;
					break;
				}

				// figure out how much of the pin is covered by the arc
				Poly [] arcPolys = ai.getProto().getTechnology().getShapeOfArc(ai);
				if (arcPolys.length == 0) continue;
				Poly arcPoly = arcPolys[0];
				Rectangle2D arcBounds = arcPoly.getBounds2D();
				Rectangle2D arcBoundsLimited = new Rectangle2D.Double();
				Rectangle2D.intersect(nodePolys[0].getBounds2D(), arcBounds, arcBoundsLimited);
				if (coverage == null)
				{
					coverage = arcBoundsLimited;
				} else
				{
					// not known, intersection is a bit restrictive...
					Rectangle2D.union(coverage, arcBoundsLimited, coverage);
				}
			}
			if (!gotOne) // && !ni.hasExports())
			{
				if (coverage == null) return new Poly[0];

				Poly newPoly = new Poly(coverage);
				newPoly.setStyle(nodePolys[0].getStyle());
				newPoly.setLayer(nodePolys[0].getLayerOrPseudoLayer());
				newPoly.setPort(nodePolys[0].getPort());
				FixpTransform trans = ni.rotateIn();
				newPoly.transform(trans);
				nodePolys[0] = newPoly;
			}
		}
		return nodePolys;
	}

	/**
	 * Method to find and cache the smallest layer on an ArcProto.
	 * @param ap the ArcProto being examined.
	 * @param arcLayers a map from ArcProtos to their smallest Layers.
	 */
	private void findSmallestLayer(ArcProto ap, Map<ArcProto,Layer> arcLayers)
	{
		// quit if the value has already been computed
		if (arcLayers.get(ap) != null) return;

		// find the smallest layer
		Layer smallestLayer = ap.getLayer(0);
		ECoord smallestExtend = ap.getLayerExtend(0);
        for (int arcLayer = 1; arcLayer < ap.getNumArcLayers(); arcLayer++) {
            ECoord extend = ap.getLayerExtend(arcLayer);
            if (extend.compareTo(smallestExtend) < 0) {
                smallestLayer = ap.getLayer(arcLayer);
                smallestExtend = extend;
            }
        }
        arcLayers.put(ap, smallestLayer);
	}

	/**
	 * Class to sort Routes.
	 */
	private static class CompRoutes implements Comparator<Route>
	{
        @Override
		public int compare(Route r1, Route r2)
		{
			// separate nodes from arcs
			RouteElementPort r1s = r1.getStart();
			RouteElementPort r1e = r1.getEnd();
			RouteElementPort r2s = r2.getStart();
			RouteElementPort r2e = r2.getEnd();
			boolean r1ToArc = r1s.getPortInst() == null || r1e.getPortInst() == null;
			boolean r2ToArc = r2s.getPortInst() == null || r2e.getPortInst() == null;
			if (r1ToArc && !r2ToArc) return 1;
			if (!r1ToArc && r2ToArc) return -1;
			if (r1ToArc && r2ToArc)
			{
				ArcProto ap1 = null, ap2 = null;
				if (r1s.getNewArcs().hasNext()) ap1 = ((RouteElementArc)(r1s.getNewArcs().next())).getArcProto();
				if (r1e.getNewArcs().hasNext()) ap1 = ((RouteElementArc)(r1e.getNewArcs().next())).getArcProto();
				if (r2s.getNewArcs().hasNext()) ap2 = ((RouteElementArc)(r2s.getNewArcs().next())).getArcProto();
				if (r2e.getNewArcs().hasNext()) ap2 = ((RouteElementArc)(r2e.getNewArcs().next())).getArcProto();
				if (ap1 == null || ap2 == null) return 0;
				return ap1.compareTo(ap2);
			}

			// get the first route in proper order
			NodeInst n1s = r1s.getPortInst().getNodeInst();
			NodeInst n1e = r1e.getPortInst().getNodeInst();
			if (n1s.compareTo(n1e) < 0)
			{
				NodeInst s = n1s;   n1s = n1e;   n1e = s;
				RouteElementPort se = r1s;   r1s = r1e;   r1e = se;
			}

			// get the second route in proper order
			NodeInst n2s = r2s.getPortInst().getNodeInst();
			NodeInst n2e = r2e.getPortInst().getNodeInst();
			if (n2s.compareTo(n2e) < 0)
			{
				NodeInst s = n2s;   n2s = n2e;   n2e = s;
				RouteElementPort se = r2s;   r2s = r2e;   r2e = se;
			}

			// sort by the starting and ending nodes
			int res = n1s.compareTo(n2s);
			if (res != 0) return res;
			res = n1e.compareTo(n2e);
			if (res != 0) return res;

			// sort by the starting and ending port names
			res = r1s.getPortInst().getPortProto().getName().compareTo(r2s.getPortInst().getPortProto().getName());
			if (res != 0) return res;
			res = r1e.getPortInst().getPortProto().getName().compareTo(r2e.getPortInst().getPortProto().getName());
			if (res != 0) return res;
			return 0;
		}
	}

	/**
	 * Class to handle complex topology in the cell.
	 * Accounts for existing as well as planned connections.
	 */
	private static class StitchingTopology
	{
		private Netlist netlist;
		private Map<Network,List<Network>> connected;

		StitchingTopology(Cell cell)
		{
			netlist = cell.getNetlist();
			if (netlist == null)
			{
				System.out.println("Auto-router cannot get netlist information for cell " + cell.describe(false));
			}
			connected = new HashMap<Network,List<Network>>();
		}

		/**
		 * Method to return the Network associated with a given node/port combination.
		 * @param ni the NodeInst in question.
		 * @param pp the PortProto on the NodeInst in question.
		 * @return the Network associated with that node/port.
		 */
        Network getNodeNetwork(NodeInst ni, PortProto pp)
        {
            Nodable no = ni.getNodable(0);
            Network net;
            if (ni instanceof IconNodeInst)
            {
                net = netlist.getNetwork(no, pp.getNameKey().subname(0));
            } else
            {
                net = netlist.getNetwork(no, pp, 0);
            }
            return getRealNet(net);
        }

		/**
		 * Method to return the Network associated with a given PortInst.
		 * @param pi the PortInst in question.
		 * @return the Network associated with that PortInst.
		 */
		Network getPortNetwork(PortInst pi)
		{
	        PortProto portProto = pi.getPortProto();
	        if (portProto.getNameKey().isBus())
	        {
	        	Network net = netlist.getNetwork(pi.getNodeInst(), portProto, 0);
				return getRealNet(net);
	        }
			Network net = netlist.getNetwork(pi);
			return getRealNet(net);
		}

		/**
		 * Method to return the Network associated with a given ArcInst.
		 * @param ai the ArcInst in question.
		 * @return the Network associated with that ArcInst.
		 */
		Network getArcNetwork(ArcInst ai)
		{
			Network net = netlist.getNetwork(ai, 0);
			return getRealNet(net);
		}

		/**
		 * Method to tell whether two ports on a node are connected.
		 * @param ni the NodeInst in question.
		 * @param pp1 the first PortProto on that NodeInst.
		 * @param pp2 the first PortProto on that NodeInst.
		 * @return true if the ports are connected.
		 */
		boolean portsConnected(NodeInst ni, PortProto pp1, PortProto pp2)
		{
			return netlist.portsConnected(ni, pp1, pp2);
		}

		/**
		 * Method to convert a Network to the actual one, once intended connections are made.
		 * @param net the original Network.
		 * @return the actual Network, for comparison purposes.
		 */		
		private Network getRealNet(Network net)
		{
			List<Network> nets = connected.get(net);
			if (nets == null) return net;
			return nets.get(0);
		}

		/**
		 * Method to plan for the connection of two Networks.
		 * @param net1 the first Network that will be connected.
		 * @param net2 the second Network that will be connected.
		 */
		public void connect(Network net1, Network net2)
		{
			List<Network> conNets1 = connected.get(net1);
			List<Network> conNets2 = connected.get(net2);

			// if both nets are unknown, link one to the other
			if (conNets1 == null && conNets2 == null)
			{
				List<Network> both = new ArrayList<Network>();
				both.add(net1);
				both.add(net2);
				connected.put(net1, both);
				connected.put(net2, both);
				return;
			}

			// if one net is unknown, link it to the known network
			if (conNets1 == null)
			{
				if (!conNets2.contains(net1)) conNets2.add(net1);
				connected.put(net1, conNets2);
				return;
			}
			if (conNets2 == null)
			{
				if (!conNets1.contains(net2)) conNets1.add(net2);
				connected.put(net2, conNets1);
				return;
			}

			// if both nets are known, link them
			List<Network> combined = new ArrayList<Network>();
			for(Network net : conNets1) combined.add(net);
			for(Network net : conNets2)
			{
				if (!combined.contains(net)) combined.add(net);
			}
			for(Network net : combined)
				connected.put(net, combined);
		}
	}

	/**
	 * Class to package Preferences for the server.
	 */
	public static class AutoOptions implements Serializable
	{
		public boolean createExports;
		public ArcProto preferredArc;

		public AutoOptions(boolean factory)
		{
            if (factory)
            {
                createExports = false;
                preferredArc = Technology.getCurrent().getArcs().next();
            } else
            {
                createExports = Routing.isAutoStitchCreateExports();
                preferredArc = Routing.getPreferredRoutingArcProto();
            }
		}
	}

}
