All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.electric.tool.extract.Connectivity Maven / Gradle / Ivy

There is a newer version: 9.02-e
Show newest version
/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Connectivity.java
 * Module to do node extraction (extract connectivity from a pure-layout cell)
 * Written by Steven M. Rubin, Sun Microsystems.
 *
 * Copyright (c) 2005, Oracle and/or its affiliates. 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 Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 */
package com.sun.electric.tool.extract;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ERectangle;
import com.sun.electric.database.geometry.GeometryHandler;
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.geometry.PolySweepMerge;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
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.PortProto;
import com.sun.electric.database.text.Name;
import com.sun.electric.database.text.TextUtils;
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.HeadConnection;
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.DisplayedText;
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.Variable;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.PrimitivePort;
import com.sun.electric.technology.SizeOffset;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.Technology.NodeLayer;
import com.sun.electric.technology.Technology.TechPoint;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.routing.AutoStitch;
import com.sun.electric.tool.routing.AutoStitch.AutoOptions;
import com.sun.electric.tool.user.ErrorLogger;
import com.sun.electric.tool.user.Highlight;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.dialogs.EDialog;
import com.sun.electric.tool.user.ui.TopLevel;
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 com.sun.electric.util.math.MutableBoolean;
import com.sun.electric.util.math.MutableInteger;
import com.sun.electric.util.math.Orientation;

import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;

/**
 * This is the Connectivity extractor.
 *
 * Still need to handle non-Manhattan contacts
 */
public class Connectivity
{
	/** true to prevent objects smaller than minimum size */	private static final boolean ENFORCEMINIMUMSIZE = false;
	/** true to debug centerline determination */				private static final boolean DEBUGCENTERLINES = false;
	/** true to debug object creation */						private static final boolean DEBUGSTEPS = false;
	/** true to debug contact extraction */						private static final boolean DEBUGCONTACTS = false;
	/** amount to scale values before merging */				private static final double SCALEFACTOR = DBMath.GRID;

	/** the current technology for extraction */				private Technology tech;
	/** layers to use for given arc functions */				private Map layerForFunction;
	/** the layer to use for "polysilicon" geometry */			private Layer polyLayer;
	/** temporary layers to use for geometric manipulation */	private Layer tempLayer1;
	/** the layers to use for "active" geometry */				private Layer activeLayer, pActiveLayer, nActiveLayer;
	/** the layers to use for "select" geometry */				private Layer pSelectLayer, nSelectLayer;
	/** the real "active" layers */								private Layer realPActiveLayer, realNActiveLayer;
    /** the well and substrate layers */                        private Layer wellLayer, substrateLayer;
    /** associates arc prototypes with layers */				private Map arcsForLayer;
	/** map of extracted cells */								private Map convertedCells;
	/** map of cut layers to lists of polygons on that layer */	private Map allCutLayers;
	/** set of pure-layer nodes that are not processed */		private Set ignoreNodes;
	/** set of contacts that are not used for extraction */		private Set bogusContacts;
    /** PrimitiveNodes for p-diffusion and n-diffusion */       private PrimitiveNode diffNode, pDiffNode, nDiffNode;
    /** list of Exports to restore after extraction */			private List exportsToRestore;
	/** auto-generated exports that may need better names */	private List generatedExports;
	/** true if this is a P-well process (presume P-well) */	private boolean pSubstrateProcess;
	/** true if this is a N-well process (presume N-well) */	private boolean nSubstrateProcess;
	/** helper variables for computing N/P process factors */	private boolean hasWell, hasPWell, hasNWell;
	/** true to unify N and P active layers */					private boolean unifyActive;
	/** helper variables for computing N and P active unify */	private boolean haveNActive, havePActive;
	/** true to ignore select/well around active layers */		private boolean ignoreActiveSelectWell;
	/** true to grid align the extracted geometry */			private boolean gridAlignExtraction;
	/** true to approximate cut placement */					private boolean approximateCuts;
	/** true if extracting hierarchically */					private boolean recursive;
	/** the smallest polygon acceptable for merging */			private double smallestPoly;
	/** debugging: list of objects created */					private List addedRectangles;
	/** debugging: list of objects created */					private List addedLines;
	/** list of exported pins to realize at the end */			private List pinsForLater;
	/** ErrorLogger to keep up with errors during extraction */ private ErrorLogger errorLogger;
	/** total number of cells to extract when recursing */		private int totalCells;
	/** total number of cells extracted when recursing */		private int cellsExtracted;
	/** Job that is holding the process */						private Job job;
    /** EditingPreferences */                                   private EditingPreferences ep;
	/** Grid alignment for edges */								private EDimension alignment;

	/**
	 * Method to examine the current cell and extract it's connectivity in a new one.
	 * @param recursive true to recursively extract the hierarchy below this cell.
	 */
	public static void extractCurCell(boolean recursive)
	{
		Cell curCell = Job.getUserInterface().needCurrentCell();
		if (curCell == null)
		{
			System.out.println("Must be editing a cell with pure layer nodes.");
			return;
		}
		new ExtractJob(curCell, recursive);
	}

	private static class ExtractJob extends Job
	{
		private Cell cell, newCell;
		private boolean recursive;
		private double smallestPolygonSize;
		private int activeHandling;
		private String expansionPattern;
		private boolean gridAlignExtraction;
        private final ECoord scaledResolution;
		private boolean approximateCuts;
		private boolean flattenPcells;
		private boolean usePureLayerNodes;
		/** debugging: list of objects created */	private List> addedBatchRectangles;
		/** debugging: list of objects created */	private List> addedBatchLines;
		/** debugging: list of objects created */	private List addedBatchNames;
		/** */ private ErrorLogger errorLogger;

		private ExtractJob(Cell cell, boolean recursive)
		{
			super("Extract Connectivity from " + cell, Extract.getExtractTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
			this.cell = cell;
			this.recursive = recursive;
			this.errorLogger = ErrorLogger.newInstance("Extraction Tool on cell " + cell.getName());
			smallestPolygonSize = Extract.isIgnoreTinyPolygons() ? Extract.getSmallestPolygonSize() : 0;
			activeHandling = Extract.getActiveHandling();
			expansionPattern = Extract.getCellExpandPattern().trim();
			gridAlignExtraction = Extract.isGridAlignExtraction();
            Technology tech = cell.getTechnology();
            scaledResolution = tech.getFactoryResolution();
//            scaledResolution = tech.getFactoryScaledResolution();
//            scaledResolution = new DRC.DRCPreferences(false).getResolution(tech);
			approximateCuts = Extract.isApproximateCuts();
			flattenPcells = Extract.isFlattenPcells();
			usePureLayerNodes = Extract.isUsePureLayerNodes();
			startJob();
		}

		public boolean doIt() throws JobException
		{
			// get pattern for matching cells to expand
			List pats = new ArrayList();
			if (expansionPattern.length() > 0)
			{
				String [] patParts = expansionPattern.split(",");
				for(int i=0; i>();
				addedBatchLines = new ArrayList>();
				addedBatchNames = new ArrayList();
			}
			Job.getUserInterface().startProgressDialog("Extracting", null);

			Connectivity c = new Connectivity(cell, this, errorLogger, smallestPolygonSize, activeHandling,
				gridAlignExtraction, scaledResolution, approximateCuts, recursive, pats);

			if (recursive) c.totalCells = c.countExtracted(cell, pats, flattenPcells);
			c.cellsExtracted = 0;

			newCell = c.doExtract(cell, recursive, pats, flattenPcells, usePureLayerNodes,
				true, this, addedBatchRectangles, addedBatchLines, addedBatchNames);
			if (newCell == null)
				System.out.println("ERROR: Extraction of cell " + cell.describe(false) + " failed");

			Job.getUserInterface().stopProgressDialog();
			fieldVariableChanged("addedBatchRectangles");
			fieldVariableChanged("addedBatchLines");
			fieldVariableChanged("addedBatchNames");
			fieldVariableChanged("newCell");
			fieldVariableChanged("errorLogger");
			return true;
		}

		public void terminateOK()
		{
			UserInterface ui = Job.getUserInterface();
			EditWindow_ wnd = ui.displayCell(newCell);
			Job.getUserInterface().termLogging(errorLogger, false, false);

			if (DEBUGSTEPS)
			{
				// show results of each step
				JFrame jf = null;
				if (!TopLevel.isMDIMode()) jf = TopLevel.getCurrentJFrame();
				ShowExtraction theDialog = new ShowExtraction(jf, addedBatchRectangles, addedBatchLines, addedBatchNames);
				theDialog.setVisible(true);
			} else
			{
				// highlight pure layer nodes
				if (newCell != null) // cell is null if job was aborted
				{
					for(Iterator it = newCell.getNodes(); it.hasNext(); )
					{
						NodeInst ni = it.next();
						PrimitiveNode.Function fun = ni.getFunction();
						if (fun == PrimitiveNode.Function.NODE)
							wnd.addElectricObject(ni, newCell);
					}
				}
				else
					System.out.println("Extraction job was aborted");
			}
		}
	}

    /**
     * Constructor to initialize connectivity extraction.
     * @param cell the cell
     * @param j the job
     * @param eLog the error logger
     * @param smallestPolygonSize the smallest polygon size
     * @param activeHandling
	 * 0: Insist on two different active layers (N and P) and also proper select/well surrounds (the default).
	 * 1: Ignore active distinctions and use select/well surrounds to distinguish N from P.
	 * 2: Insist on two different active layers (N and P) but ignore select/well surrounds.
     * @param gridAlignExtraction true to align extraction to some the technology grid
     * @param approximateCuts approximate cuts
     * @param recursive run recursively
     * @param pats a List of cell name patterns that will be flattened.
     */
	private Connectivity(Cell cell, Job j, ErrorLogger eLog, double smallestPolygonSize, int activeHandling,
		boolean gridAlignExtraction, ECoord scaledResolution, boolean approximateCuts, boolean recursive,
		List pats)
	{
	    this.approximateCuts = approximateCuts;
		this.recursive = recursive;
		tech = cell.getTechnology();
		convertedCells = new HashMap();
		smallestPoly = (SCALEFACTOR * SCALEFACTOR) * smallestPolygonSize;
		bogusContacts = new HashSet();
		errorLogger = eLog;
		job = j;
        ep = job.getEditingPreferences();

        this.gridAlignExtraction = gridAlignExtraction;
	    alignment = new EDimension(scaledResolution, scaledResolution);

        diffNode = pDiffNode = nDiffNode = null;
        // find pure-layer nodes that are never involved in higher-level components, and should be ignored
		ignoreNodes = new HashSet();
		for(Iterator pIt = tech.getNodes(); pIt.hasNext(); )
		{
			PrimitiveNode np = pIt.next();
			if (np.getFunction() != PrimitiveNode.Function.NODE) continue;
			Technology.NodeLayer [] nLays = np.getNodeLayers();
			boolean validLayers = false;
			for(int i=0; i it = tech.getLayers(); it.hasNext(); )
		{
			Layer layer = it.next();
			Layer.Function fun = layer.getFunction();
			if (polyLayer == null && fun == Layer.Function.POLY1) polyLayer = layer;
            if (activeLayer == null && fun == Layer.Function.DIFF) activeLayer = layer;
            if (pActiveLayer == null && fun == Layer.Function.DIFFP) pActiveLayer = layer;
            if (nActiveLayer == null && fun == Layer.Function.DIFFN) nActiveLayer = layer;
			if (realPActiveLayer == null && fun == Layer.Function.DIFFP) realPActiveLayer = layer;
			if (realNActiveLayer == null && fun == Layer.Function.DIFFN) realNActiveLayer = layer;
            if (pSelectLayer == null && fun == Layer.Function.IMPLANTP) pSelectLayer = layer;
            if (nSelectLayer == null && fun == Layer.Function.IMPLANTN) nSelectLayer = layer;
            if (pSubstrateProcess) // p-substrate
            {
                if (wellLayer == null && fun == Layer.Function.WELLN) wellLayer = layer;
                if (substrateLayer == null && fun == Layer.Function.WELLP) substrateLayer = layer;
            }
            if (nSubstrateProcess) // n-substrate
            {
                if (wellLayer == null && fun == Layer.Function.WELLP) wellLayer = layer;
                if (substrateLayer == null && fun == Layer.Function.WELLN) substrateLayer = layer;
            }
        }
		polyLayer = polyLayer.getNonPseudoLayer();
		if (polyLayer != null)
			tempLayer1 = polyLayer.getPseudoLayer();
		if (havePActive != haveNActive)
		{
			// only one active layer found: force ignorance of the distinction
			if (activeHandling != 1)
				System.out.println("Found only one type of active layer: ignoring N/P distinction.");
			activeHandling = 1;
            if (!haveNActive) nActiveLayer = pActiveLayer;
            if (!havePActive) pActiveLayer = nActiveLayer;
		}
        unifyActive = false;
        if (activeHandling == 1)
        {
        	// ignoring n/p distinction in active handling
            unifyActive = true;
        }
        if ((pActiveLayer == null || nActiveLayer == null) && activeLayer != null)
        {
        	// technology has only one active layer: unify them
            unifyActive = true;
            pActiveLayer = nActiveLayer = activeLayer;
        }
        if (pActiveLayer != null) pActiveLayer = pActiveLayer.getNonPseudoLayer();
        if (nActiveLayer != null) nActiveLayer = nActiveLayer.getNonPseudoLayer();

		// figure out which arcs to use for a layer
		arcsForLayer = new HashMap();
		for(Iterator it = tech.getLayers(); it.hasNext(); )
		{
			Layer layer = it.next();
			Layer.Function fun = layer.getFunction();
			if (fun.isDiff() || fun.isPoly() || fun.isMetal())
			{
				ArcProto.Function oFun = null;
				if (fun.isMetal()) oFun = ArcProto.Function.getMetal(fun.getLevel());
				if (fun.isPoly()) oFun = ArcProto.Function.getPoly(fun.getLevel());
				if (oFun == null) continue;
				ArcProto type = null;
				for(Iterator aIt = tech.getArcs(); aIt.hasNext(); )
				{
					ArcProto ap = aIt.next();
					if (ap.getFunction() == oFun) { type = ap;   break; }
				}
				if (type != null) arcsForLayer.put(layer, type);
			}
		}

		// build the mapping from any layer to the proper ones for the geometric database
		layerForFunction = new HashMap();
		for(Iterator it = tech.getLayers(); it.hasNext(); )
		{
			Layer layer = it.next();
			Layer.Function fun = layer.getFunction();
			if (unifyActive)
			{
				if (fun == Layer.Function.DIFFP || fun == Layer.Function.DIFFN)
					fun = Layer.Function.DIFF;
			}
			if (layerForFunction.get(fun) == null)
				layerForFunction.put(fun, layer);
		}
	}

	/**
	 * Method to log errors during node extraction.
	 */
	private void addErrorLog(Cell cell, String msg, EPoint... pList)
	{
		List pointList = new ArrayList();
		for(EPoint p : pList)
			pointList.add(p);
		errorLogger.logMessage(msg, pointList, cell, -1, true);
		System.out.println(msg);
	}

	private int countExtracted(Cell oldCell, List pats, boolean flattenPcells)
	{
		int numExtracted = 1;
		for(Iterator it = oldCell.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();
			if (ni.isCellInstance())
			{
				Cell subCell = (Cell)ni.getProto();

				// do not recurse if this subcell will be expanded
				if (isCellFlattened(subCell, pats, flattenPcells)) continue;

				Cell convertedCell = convertedCells.get(subCell);
				if (convertedCell == null)
				{
					numExtracted += countExtracted(subCell, pats, flattenPcells);
				}
			}
		}
		return numExtracted;
	}

	/**
	 * Method to determine whether to flatten a cell.
	 * @param cell the cell in question.
	 * @param pats patterns of cells to be flattened.
	 * @param flattenPcells true if Cadence Pcells are to be flattened.
	 * @return true if the cell should be flattened.
	 */
	private boolean isCellFlattened(Cell cell, List pats, boolean flattenPcells)
	{
		// do not recurse if this subcell will be expanded
		for(Pattern pat : pats)
		{
			Matcher mat = pat.matcher(cell.noLibDescribe());
			if (mat.find()) return true;
		}

		if (flattenPcells)
		{
			String cellName = cell.noLibDescribe();
			int twoDollar = cellName.lastIndexOf("$$");
			if (twoDollar > 0)
			{
				String endPart = cellName.substring(twoDollar+2);
				for(int i=0; i pats, boolean flattenPcells, boolean usePureLayerNodes,
		boolean top, Job job, List> addedBatchRectangles, List> addedBatchLines, List addedBatchNames)
	{
		if (recursive)
		{
			// first see if subcells need to be converted
			for(Iterator it = oldCell.getNodes(); it.hasNext(); )
			{
				NodeInst ni = it.next();
				if (ni.isCellInstance())
				{
					Cell subCell = (Cell)ni.getProto();

					// do not recurse if this subcell will be expanded
					if (isCellFlattened(subCell, pats, flattenPcells)) continue;

					Cell convertedCell = convertedCells.get(subCell);
					if (convertedCell == null)
					{
						Cell result = doExtract(subCell, recursive, pats, flattenPcells, usePureLayerNodes,
							false, job, addedBatchRectangles, addedBatchLines, addedBatchNames);
						if (result == null)
							System.out.println("ERROR: Extraction of cell " + subCell.describe(false) + " failed");
					}
				}
			}
		}

		// create the new version of the cell
		String newCellName = oldCell.getName() + oldCell.getView().getAbbreviationExtension();
		Cell newCell = Cell.makeInstance(ep, oldCell.getLibrary(), newCellName);
		if (newCell == null)
		{
			System.out.println("Cannot create new cell: " + newCellName);
			return null;
		}
		convertedCells.put(oldCell, newCell);

		// create a merge for the geometry in the cell
		PolyMerge merge = new PolyMerge();
        PolyMerge selectMerge = new PolyMerge();

        // convert the nodes
		if (!startSection(oldCell, "Gathering geometry in " + oldCell + "..."))		// HAS PROGRESS IN IT
			return null; // aborted
		Set expandedCells = new HashSet();
		exportsToRestore = new ArrayList();
		generatedExports = new ArrayList();
		pinsForLater = new ArrayList();
		allCutLayers = new TreeMap();
		extractCell(oldCell, newCell, pats, flattenPcells, expandedCells, merge, selectMerge, GenMath.MATID, Orientation.IDENT);
		if (expandedCells.size() > 0)
		{
			System.out.print("These cells were expanded:");
			for(Cell c : expandedCells)
				System.out.print(" " + c.describe(false));
			System.out.println();
		}

		// now remember the original merge
		PolyMerge originalMerge = new PolyMerge();
		originalMerge.addMerge(merge, new FixpTransform());

		// start by extracting vias
		initDebugging();
		if (!startSection(oldCell, "Extracting vias...")) return null; // aborted
		if (!extractVias(merge, originalMerge, oldCell, newCell, usePureLayerNodes)) return null; // aborted
		termDebugging(addedBatchRectangles, addedBatchLines, addedBatchNames, "Vias");

		// now extract transistors
		initDebugging();
		if (!startSection(oldCell, "Extracting transistors...")) return null; // aborted
		extractTransistors(merge, originalMerge, newCell, usePureLayerNodes);
		termDebugging(addedBatchRectangles, addedBatchLines, addedBatchNames, "Transistors");

        if (usePureLayerNodes) {
            // dump back in original routing layers
            if (!startSection(oldCell, "Adding in original routing layers...")) return null;
            addInRoutingLayers(oldCell, newCell, merge, originalMerge, usePureLayerNodes);

        } else {

            // extend geometry that sticks out in space
    /*
            initDebugging();
            if (!startSection(oldCell, "Extracting extensions...")) return null; // aborted
            extendGeometry(merge, originalMerge, newCell, true, usePureLayerNodes);
            termDebugging(addedBatchRectangles, addedBatchLines, addedBatchNames, "StickOuts");
    */

            // look for wires and pins
            initDebugging();
            if (!startSection(oldCell, "Extracting wires...")) return null; // aborted
            if (makeWires(merge, originalMerge, newCell, usePureLayerNodes)) return newCell;
            termDebugging(addedBatchRectangles, addedBatchLines, addedBatchNames, "Wires");

            // convert any geometry that connects two networks
            initDebugging();
            if (!startSection(oldCell, "Extracting connections...")) return null; // aborted
            extendGeometry(merge, originalMerge, newCell, false, usePureLayerNodes);
            termDebugging(addedBatchRectangles, addedBatchLines, addedBatchNames, "Bridges");
        }

        // dump any remaining layers back in as extra pure layer nodes
        initDebugging();
        if (!startSection(oldCell, "Extracting leftover geometry...")) return null; // aborted
        convertAllGeometry(merge, originalMerge, newCell, usePureLayerNodes);
        termDebugging(addedBatchRectangles, addedBatchLines, addedBatchNames, "Pures");

        // reexport any that were there before
		if (!startSection(oldCell, "Adding connecting wires...")) return null; // aborted
		cleanupExports(oldCell, newCell);

		// cleanup by auto-stitching
		PolyMerge originalUnscaledMerge = new PolyMerge();
		double shrinkage = 1.0 / SCALEFACTOR;
		FixpTransform shrink = new FixpTransform(shrinkage, 0, 0, shrinkage, 0, 0);
		originalUnscaledMerge.addMerge(originalMerge, shrink);
		Set allArcs = null;

        allArcs = new HashSet();
        for(Iterator it = newCell.getArcs(); it.hasNext(); )
            allArcs.add(it.next());

        // make sure current arc is not universal arc, otherwise it makes the InteractiveRouter (used by AutoStitch) prefer that arc
        if (User.getUserTool().getCurrentArcProto() == Generic.tech().universal_arc) {
            User.getUserTool().setCurrentArcProto(newCell.getTechnology().getArcs().next());
        }

        // TODO: originalMerge passed to auto stitcher really needs to include subcell geometry too, in order
        // for the auto-stitcher to know where it can place arcs. However, building and maintaining such a hash map
        // might take up a lot of memory.
        AutoOptions prefs = new AutoOptions(true);
		prefs.createExports = true;
		AutoStitch.runAutoStitch(newCell, null, null, job, originalUnscaledMerge, null, false, true, ep, prefs, !recursive, alignment);

        // check all the arcs that auto-stitching added, and replace them by universal arcs if they are off-grid
        if (alignment != null && (alignment.getWidth() > 0 || alignment.getHeight() > 0))
        {
            for(Iterator it = newCell.getArcs(); it.hasNext(); )
            {
                ArcInst ai = it.next();
                if (allArcs.contains(ai)) continue;
                Rectangle2D bounds = ai.getBounds();
                long lX = (long)(bounds.getMinX() / alignment.getWidth());
                long hX = (long)(bounds.getMaxX() / alignment.getWidth());
                long lY = (long)(bounds.getMinY() / alignment.getHeight());
                long hY = (long)(bounds.getMaxY() / alignment.getHeight());
                if (lX * alignment.getWidth() != bounds.getMinX() ||
                	lY * alignment.getHeight() != bounds.getMinY() ||
                	hX * alignment.getWidth() != bounds.getMaxX() ||
                	hY * alignment.getHeight() != bounds.getMaxY())
                {
                    // replace
                    Connection head = ai.getHead();
                    Connection tail = ai.getTail();
                    ArcInst newAi = ArcInst.makeInstanceBase(Generic.tech().universal_arc, ep, 0, head.getPortInst(), tail.getPortInst(),
                            head.getLocation(), tail.getLocation(), null);
                    if (newAi != null)
                    {
                        newAi.setHeadExtended(false);
                        newAi.setTailExtended(false);
                        ai.kill();
                    }
                }
            }
        }

        if (DEBUGSTEPS)
		{
			initDebugging();
			for(Iterator it = newCell.getArcs(); it.hasNext(); )
			{
				ArcInst ai = it.next();
				if (allArcs.contains(ai)) continue;
				Poly arcPoly = ai.makeLambdaPoly(ai.getGridBaseWidth(), Poly.Type.CLOSED);
				addedRectangles.add(ERectangle.fromLambda(arcPoly.getBounds2D()));
			}
			termDebugging(addedBatchRectangles, addedBatchLines, addedBatchNames, "Stitches");
		}
		System.out.println("Extraction done.");

		cellsExtracted++;
		if (recursive) Job.getUserInterface().setProgressValue(cellsExtracted * 100 / totalCells);
		return newCell;
	}

	/**
	 * Method to start a new connection section.
	 * @param msg message to display in progress window
	 * @return False if the job is scheduled for abort or was aborted
	 */
	private boolean startSection(Cell cell, String msg)
	{
		System.out.println(msg);
		if (job.checkAbort())
			return false;
		if (recursive) msg = cell.getName() + " - " + msg;
		Job.getUserInterface().setProgressNote(msg);
		if (!recursive) Job.getUserInterface().setProgressValue(0);
		return true;
	}

	private void initDebugging()
	{
		if (DEBUGSTEPS)
		{
			addedRectangles = new ArrayList();
			addedLines = new ArrayList();
		}
	}

	private void termDebugging(List> addedBatchRectangles,
		List> addedBatchLines, List addedBatchNames, String descr)
	{
		if (DEBUGSTEPS)
		{
			addedBatchRectangles.add(addedRectangles);
			addedBatchLines.add(addedLines);
			addedBatchNames.add(descr);
		}
	}

	private static class ExportedPin
	{
		Point2D location;
		NodeInst ni;
		FixpTransform trans;

		ExportedPin(NodeInst ni, Point2D location, FixpTransform trans)
		{
			this.ni = ni;
			this.location = location;
			this.trans = trans;
		}
	}

	/**
	 * Method to extract a cell's contents into the merge.
	 * @param oldCell the cell being extracted.
	 * @param newCell the new cell being created.
	 * @param pats patterns of subcell names that will be expanded.
	 * @param flattenPcells true to expand Cadence Pcells (which end with $$number).
	 * @param expandedCells a set of cells that matched the pattern and were expanded.
	 * @param merge the merge to be filled.
	 * @param prevTrans the transformation coming into this cell.
	 */
	private void extractCell(Cell oldCell, Cell newCell, List pats, boolean flattenPcells, Set expandedCells,
		PolyMerge merge, PolyMerge selectMerge, FixpTransform prevTrans, Orientation orient)
	{
		Map newNodes = new HashMap();
		int totalNodes = oldCell.getNumNodes();
        EDimension alignementToGrid = ep.getAlignmentToGrid();

        // first get select, so we can determine proper active type
        if (!unifyActive && !ignoreActiveSelectWell)
        {
            for (Iterator nIt = oldCell.getNodes(); nIt.hasNext(); )
            {
                NodeInst ni = nIt.next();
                if (ni.isCellInstance()) continue;
                Poly [] polys = tech.getShapeOfNode(ni);
                for(int j=0; j nIt = oldCell.getNodes(); nIt.hasNext(); )
		{
			NodeInst ni = nIt.next();
			soFar++;
			if (!recursive && (soFar % 100) == 0) Job.getUserInterface().setProgressValue(soFar * 100 / totalNodes);
			if (ni.getProto() == Generic.tech().cellCenterNode) continue;

			// see if the node can be copied or must be extracted
			NodeProto copyType = null;
			if (ni.isCellInstance())
			{
				Cell subCell = (Cell)ni.getProto();

				// if subcell is expanded, do it now
				boolean flatIt = isCellFlattened(subCell, pats, flattenPcells);
				if (flatIt)
				{
					// expanding the subcell
					expandedCells.add(subCell);
					FixpTransform subTrans = ni.translateOut(ni.rotateOut(prevTrans));
					Orientation or = orient.concatenate(ni.getOrient());
					extractCell(subCell, newCell, pats, flattenPcells, expandedCells, merge, selectMerge, subTrans, or);
					continue;
				}

				// subcell not expanded, figure out what gets placed in the new cell
				copyType = convertedCells.get(subCell);
				if (copyType == null) copyType = subCell;
			} else
			{
				PrimitiveNode np = (PrimitiveNode)ni.getProto();

				// special case for exported but unconnected pins: save for later
				if (np.getFunction().isPin())
				{
					if (ni.hasExports() && !ni.hasConnections())
					{
						ExportedPin ep = new ExportedPin(ni, ni.getTrueCenter(), prevTrans);
						pinsForLater.add(ep);
						continue;
					}
				}
				if (np.getFunction() != PrimitiveNode.Function.NODE) copyType = ni.getProto(); else
				{
					if (ignoreNodes.contains(np)) copyType = ni.getProto();
				}
			}

			// copy it now if requested
			if (copyType != null)
			{
				double sX = ni.getXSize();
				double sY = ni.getYSize();
				if (copyType instanceof Cell)
				{
					Rectangle2D cellBounds = ((Cell)copyType).getBounds();
					sX = cellBounds.getWidth();
					sY = cellBounds.getHeight();
				}
				Point2D instanceAnchor = new Point2D.Double(0, 0);
				prevTrans.transform(ni.getAnchorCenter(), instanceAnchor);

				String name = null;
				Name nameKey = ni.getNameKey();
				if (!nameKey.isTempname()) name = ni.getName();
				Orientation or = orient.concatenate(ni.getOrient());
				if (name != null && newCell.findNode(name) != null) name = null;
				NodeInst newNi = NodeInst.makeInstance(copyType, ep, instanceAnchor, sX, sY,
					newCell, or, name, ni.getTechSpecific());
				if (newNi == null)
				{
					addErrorLog(newCell, "Problem creating new instance of " + ni.getProto(), new EPoint(sX, sY));
					continue;
				}
				newNodes.put(ni, newNi);

				// copy exports too
				for(Iterator it = ni.getExports(); it.hasNext(); )
				{
					Export e = it.next();
					PortInst pi = newNi.findPortInstFromProto(e.getOriginalPort().getPortProto());
					Export.newInstance(newCell, pi, e.getName(), ep);
				}
				continue;
			}

			// see if the size is at an odd coordinate (and may suffer rounding problems)
			boolean growABit = false;
			if (((int)(ni.getXSize() * DBMath.GRID) % 2) != 0 ||
				((int)(ni.getYSize() * DBMath.GRID) % 2) != 0) growABit = true;

			// extract the geometry from the pure-layer node
			FixpTransform trans = ni.rotateOut(prevTrans);
			Poly [] polys = tech.getShapeOfNode(ni);
			for(int j=0; j sea = new RTNode.Search(poly.getBounds2D(), cInfo.getRTree(), true); sea.hasNext(); )
					{
						CutBound cBound = sea.next();
						if (cBound.getBounds().equals(poly.getBounds2D())) { found = true;   break; }
					}
					if (!found)
					{
						cInfo.addCut(poly);
					}
				} else
				{
					Rectangle2D box = poly.getBox();
					if (box == null) merge.addPolygon(layer, poly); else
					{
						if (box.getWidth() > 0 && box.getHeight() > 0)
						{
							if (layer.getName().equals("DeviceMark"))
								System.out.println("DEVICE MARK IS "+box.getWidth()+"x"+box.getHeight());
							merge.addRectangle(layer, box);
						}
					}
				}
			}

			// save exports on pure-layer nodes for restoration later
			for(Iterator it = ni.getExports(); it.hasNext(); )
			{
				Export e = it.next();
				exportsToRestore.add(e);
			}
		}

		// throw all arcs into the new cell, too
		for(Iterator aIt = oldCell.getArcs(); aIt.hasNext(); )
		{
			ArcInst ai = aIt.next();
			NodeInst end1 = newNodes.get(ai.getHeadPortInst().getNodeInst());
			NodeInst end2 = newNodes.get(ai.getTailPortInst().getNodeInst());
			if (end1 == null || end2 == null) continue;
			PortInst pi1 = end1.findPortInstFromProto(ai.getHeadPortInst().getPortProto());
			PortInst pi2 = end2.findPortInstFromProto(ai.getTailPortInst().getPortProto());
			Point2D headLocation = new Point2D.Double(0, 0);
			Point2D tailLocation = new Point2D.Double(0, 0);
			prevTrans.transform(ai.getHeadLocation(), headLocation);
			prevTrans.transform(ai.getTailLocation(), tailLocation);
			ArcInst.makeInstanceBase(ai.getProto(), ep, ai.getLambdaBaseWidth(), pi1, pi2,
				headLocation, tailLocation, ai.getName());
		}

		// throw all cell text into the new cell, too
		for(Iterator vIt = oldCell.getParametersAndVariables(); vIt.hasNext(); )
		{
			Variable var = vIt.next();
            Variable newVar = Variable.newInstance(var.getKey(), var.getObject(), var.getTextDescriptor());
            if (var.getTextDescriptor().isParam())
            	newCell.getCellGroup().addParam(newVar);
            else
            	newCell.addVar(newVar);
			new DisplayedText(newCell, var.getKey());
		}
	}

	/**
	 * Method to determine if this is a "p-well" or "n-well" process.
	 * Examines the top-level cell to see which well layers are found.
	 * @param cell the top-level Cell.
	 * @param recursive true to examine recursively.
	 * @param pats exclusion pattern for cell names.
	 * @param activeHandling
	 * 0: Insist on two different active layers (N and P) and also proper select/well surrounds (the default).
	 * 1: Ignore active distinctions and use select/well surrounds to distinguish N from P.
	 * 2: Insist on two different active layers (N and P) but ignore select/well surrounds.
	 */
	private void findMissingWells(Cell cell, boolean recursive, List pats, int activeHandling)
	{
		hasWell = hasPWell = hasNWell = false;
		haveNActive = havePActive = false;
		recurseMissingWells(cell, recursive, pats);
		if (!hasPWell)
		{
			pSubstrateProcess = true;
			System.out.println("Presuming a P-substrate process");
		} else if (!hasNWell && !hasWell)
		{
			nSubstrateProcess = true;
			System.out.println("Presuming an N-substrate process");
		}

		// see how active layers should be handled
		ignoreActiveSelectWell = (activeHandling == 2);
	}

	/**
	 * Method to recursively invoke "examineCellForMissingWells".
	 * @param cell the top-level Cell.
	 * @param recursive true to examine recursively.
	 * @param pats exclusion patterns for cell names.
	 */
	private void recurseMissingWells(Cell cell, boolean recursive, List pats)
	{
		examineCellForMissingWells(cell);
		if (recursive)
		{
			// now see if subcells need to be converted
			for(Iterator it = cell.getNodes(); it.hasNext(); )
			{
				NodeInst ni = it.next();
				if (ni.isCellInstance())
				{
					Cell subCell = (Cell)ni.getProto();

					// do not recurse if this subcell will be expanded
					boolean matches = false;
					for(Pattern pat : pats)
					{
						Matcher mat = pat.matcher(subCell.noLibDescribe());
						if (mat.find()) { matches = true;   break; }
					}
					if (matches) continue;

					Cell convertedCell = convertedCells.get(subCell);
					if (convertedCell == null)
					{
						recurseMissingWells(subCell, recursive, pats);
					}
				}
			}
		}
	}

	/**
	 * Method to scan a cell for implant layers that would indicate a default N or P process.
	 * @param cell the Cell to examine.
	 * Sets the field variables "hasWell", "hasPWell", and "hasNWell".
	 */
	private void examineCellForMissingWells(Cell cell)
	{
		for(Iterator it = cell.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();
			if (ni.isCellInstance()) continue;
			Poly[] polys = ni.getProto().getTechnology().getShapeOfNode(ni);
			for(int i=0; i nIt = cell.getNodes(); nIt.hasNext(); )
		{
			NodeInst ni = nIt.next();
			if (ni.isCellInstance()) continue;
			PrimitiveNode np = (PrimitiveNode)ni.getProto();
			if (np == Generic.tech().cellCenterNode) continue;

			// see if the node will be extracted
			PrimitiveNode copyType = null;
			if (np.getFunction() != PrimitiveNode.Function.NODE) copyType = np; else
			{
				if (ignoreNodes.contains(np)) copyType = np;
			}

			// examine it if so
			if (copyType != null)
			{
				NodeLayer [] nLayers = copyType.getNodeLayers();
				for(int i=0; i aIt = cell.getArcs(); aIt.hasNext(); )
		{
			ArcInst ai = aIt.next();
			for(int i=0; i> geomToWire = new TreeMap>();
		for (Layer layer : merge.getKeySet())
		{
			Layer.Function fun = layer.getFunction();
			if (fun.isDiff() || fun.isPoly() || fun.isMetal())
			{
				List polyList = getMergePolys(merge, layer, null);
				totPolys += polyList.size();
				geomToWire.put(layer, polyList);
			}
		}

		// examine each wire layer, looking for a skeletal structure that approximates it
		int soFar = 0;
		Set allLayers = geomToWire.keySet();
		for (Layer layer : allLayers)
		{
			// examine the geometry on the layer
			List polyList = geomToWire.get(layer);
			for(PolyBase poly : polyList)
			{
				if (!recursive) Job.getUserInterface().setProgressValue(soFar * 100 / totPolys);
				soFar++;

				// figure out which arcproto to use here
				ArcProto ap = findArcProtoForPoly(layer, poly, originalMerge);
				if (ap == null) continue;

				// reduce the geometry to a skeleton of centerlines
				double minWidth = 1;
				if (ENFORCEMINIMUMSIZE) minWidth = scaleUp(ap.getDefaultLambdaBaseWidth(ep));
				List lines = findCenterlines(poly, layer, minWidth, merge, originalMerge);

				// now realize the wires
				for(Centerline cl : lines)
				{
                    ap = findArcProtoForPoly(layer, poly, originalMerge);
                    Point2D loc1Unscaled = new Point2D.Double();
					Point2D loc2Unscaled = new Point2D.Double();
					PortInst pi1 = locatePortOnCenterline(cl, loc1Unscaled, layer, ap, true, newCell);
					Point2D loc1 = new Point2D.Double(scaleUp(loc1Unscaled.getX()), scaleUp(loc1Unscaled.getY()));
					PortInst pi2 = locatePortOnCenterline(cl, loc2Unscaled, layer, ap, false, newCell);
					Point2D loc2 = new Point2D.Double(scaleUp(loc2Unscaled.getX()), scaleUp(loc2Unscaled.getY()));

					// make sure the wire fits
					MutableBoolean headExtend = new MutableBoolean(true), tailExtend = new MutableBoolean(true);

					// adjust extension to get alignment right
					if (loc1.getX() == loc2.getX())
					{
						// vertical arc: adjust extension to make sure top and bottom are on grid
						double loc1Y = loc1Unscaled.getY();
						double loc2Y = loc2Unscaled.getY();
						double halfWidth = cl.width/2/SCALEFACTOR;
						double loc1YExtend = loc1Y + (loc1Y < loc2Y ? -halfWidth : halfWidth);
						double loc2YExtend = loc2Y + (loc2Y < loc1Y ? -halfWidth : halfWidth);
						if (!isOnGrid(loc1YExtend, alignment.getHeight()) && isOnGrid(loc1Y, alignment.getHeight()))
							headExtend.setValue(false);
						if (!isOnGrid(loc2YExtend, alignment.getHeight()) && isOnGrid(loc2Y, alignment.getHeight()))
							tailExtend.setValue(false);
					} else if (loc1.getY() == loc2.getY())
					{
						// horizontal arc: adjust extension to make sure left and right are on grid
						double loc1X = loc1Unscaled.getX();
						double loc2X = loc2Unscaled.getX();
						double halfWidth = cl.width/2/SCALEFACTOR;
						double loc1XExtend = loc1X + (loc1X < loc2X ? -halfWidth : halfWidth);
						double loc2XExtend = loc2X + (loc2X < loc1X ? -halfWidth : halfWidth);
						if (!isOnGrid(loc1XExtend, alignment.getWidth()) && isOnGrid(loc1X, alignment.getWidth()))
							headExtend.setValue(false);
						if (!isOnGrid(loc2XExtend, alignment.getWidth()) && isOnGrid(loc2X, alignment.getWidth()))
							tailExtend.setValue(false);
					}

					boolean fits = originalMerge.arcPolyFits(layer, loc1, loc2, cl.width, headExtend, tailExtend);
					if (DEBUGCENTERLINES) System.out.println("FIT="+fits+" "+cl);
					if (!fits)
					{
						// arc does not fit, try reducing width
						double wid = cl.width / SCALEFACTOR;
						long x = Math.round(wid / alignment.getWidth());
						double gridWid = x * alignment.getWidth();
						if (gridWid < wid)
						{
							// grid-aligning the width results in a smaller value...try it
							cl.width = scaleUp(gridWid);
							fits = originalMerge.arcPolyFits(layer, loc1, loc2, cl.width, headExtend, tailExtend);
							if (DEBUGCENTERLINES) System.out.println("   WID="+(cl.width/SCALEFACTOR)+" FIT="+fits);
						} else
						{
							// see if width can be reduced by a small amount and still fit
							cl.width--;
							fits = originalMerge.arcPolyFits(layer, loc1, loc2, cl.width, headExtend, tailExtend);
							if (DEBUGCENTERLINES) System.out.println("   WID="+(cl.width/SCALEFACTOR)+" FIT="+fits);
						}
					}
					while (!fits)
					{
						double wid = cl.width - SCALEFACTOR;
						if (wid < 0) break;
						cl.width = wid;
						fits = originalMerge.arcPolyFits(layer, loc1, loc2, cl.width, headExtend, tailExtend);
						if (DEBUGCENTERLINES) System.out.println("   WID="+(cl.width/SCALEFACTOR)+" FIT="+fits);
					}
					if (!fits || (loc1Unscaled.distance(loc2Unscaled) == 0 && !headExtend.booleanValue() && !tailExtend.booleanValue()))
					{
						cl.width = 0;
						ap = Generic.tech().universal_arc;
					}
                    if (loc1Unscaled.distance(loc2Unscaled) == 0 && !headExtend.booleanValue() && !tailExtend.booleanValue()) {
                        //System.out.println("zero length arc in make wires");
                    }
					// create the wire
					ArcInst ai = realizeArc(ap, pi1, pi2, loc1Unscaled, loc2Unscaled, cl.width / SCALEFACTOR,
						!headExtend.booleanValue(), !tailExtend.booleanValue(), usePureLayerNodes, merge);
					if (ai == null)
					{
						String msg = "Cell " + newCell.describe(false) + ": Failed to run arc " + ap.getName() +
							" from (" + loc1Unscaled.getX() + "," +
							loc1Unscaled.getY() + ") on node " + pi1.getNodeInst().describe(false) + " to (" +
							loc2Unscaled.getX() + "," + loc2Unscaled.getY() + ") on node " + pi2.getNodeInst().describe(false);
						addErrorLog(newCell, msg, new EPoint(loc1Unscaled.getX(), loc1Unscaled.getY()),
							new EPoint(loc2Unscaled.getX(), loc2Unscaled.getY()));
					}
				}
			}
		}

        // add in pure layer node for remaining geometrics
        for (Layer layer : allLayers)
        {
            List polyList = getMergePolys(merge, layer, null);
            for(PolyBase poly : polyList)
            {
                ArcProto ap = findArcProtoForPoly(layer, poly, originalMerge);
                if (ap == null) continue;
                PrimitiveNode pin = ap.findPinProto();

                List niList = makePureLayerNodeFromPoly(poly, newCell, merge);
                merge.subtract(layer, poly);
                // connect up to enclosed pins
                for (NodeInst ni : niList)
                {
                    PortInst fPi = ni.getOnlyPortInst();
                    Rectangle2D polyBounds = ni.getBounds();
                    Rectangle2D searchBound = new Rectangle2D.Double(polyBounds.getMinX(), polyBounds.getMinY(),
                        polyBounds.getWidth(), polyBounds.getHeight());
                    for(Iterator it = newCell.searchIterator(searchBound); it.hasNext(); )
                    {
                        Geometric geom = it.next();
                        if (!(geom instanceof NodeInst)) continue;
                        NodeInst oNi = (NodeInst)geom;
                        if (oNi == ni) continue;
                        if (oNi.getProto() != pin) continue;
                        // make sure center of pin is in bounds
                        if (!DBMath.pointInsideRect(oNi.getAnchorCenter(), searchBound)) continue;

                        // replace arcs that end on pin to end on pure layer node
                        for (Iterator cit = oNi.getConnections(); cit.hasNext(); ) {
                            Connection conn = cit.next();
                            Connection oConn;
                            ArcInst ai = conn.getArc();
                            if (ai.getProto() == Generic.tech().universal_arc) continue;
                            ArcInst newAi;
                            if (conn instanceof HeadConnection) {
                                oConn = ai.getTail();
                                newAi = ArcInst.makeInstanceBase(ap, ep, ai.getLambdaBaseWidth(), fPi, oConn.getPortInst(),
                                        conn.getLocation(), oConn.getLocation(), null);
                            } else {
                                oConn = ai.getHead();
                                newAi = ArcInst.makeInstanceBase(ap, ep, ai.getLambdaBaseWidth(), oConn.getPortInst(), fPi,
                                        oConn.getLocation(), conn.getLocation(), null);
                            }
                            if (newAi != null) {
                                newAi.setHeadExtended(ai.isHeadExtended());
                                newAi.setTailExtended(ai.isTailExtended());
                                if (newAi.getLambdaLength() == 0)
                                    System.out.println("arc inst of zero length connecting pure layer nodes");
                                ai.kill();
                            } else {
                                String msg = "Cell " + newCell.describe(false) + ": Failed to replace arc " + ap.getName() +
                                    " from (" + conn.getLocation().getX() + "," +
                                    conn.getLocation().getY() + ") on node " + ni.describe(false) + " to (" +
                                    oConn.getLocation().getX() + "," + oConn.getLocation().getY() + ")";
                                addErrorLog(newCell, msg, new EPoint(conn.getLocation().getX(), conn.getLocation().getY()),
                                    new EPoint(oConn.getLocation().getX(), oConn.getLocation().getY()));
                            }
                        }
                    }
                }
            }
        }
        if (true) return false;

        // examine each wire layer, looking for a simple rectangle that covers it
		for(Layer layer : allLayers)
		{
			// examine the geometry on the layer
			List polyList = getMergePolys(merge, layer, null);
			for(PolyBase poly : polyList)
			{
				Rectangle2D bounds = poly.getBounds2D();

				// make sure polygon is in the merge
				Poly rectPoly = new Poly(bounds);
				if (!originalMerge.contains(layer, rectPoly)) continue;

				// grid align the edges of this rectangle
				double lX = bounds.getMinX()/SCALEFACTOR, hX = bounds.getMaxX()/SCALEFACTOR;
				double lY = bounds.getMinY()/SCALEFACTOR, hY = bounds.getMaxY()/SCALEFACTOR;
				double alignX = alignment.getWidth();
				double alignY = alignment.getHeight();
				if (!isOnGrid(lX, alignX)) lX = Math.ceil(lX / alignX) * alignX;
				if (!isOnGrid(hX, alignX)) hX = Math.floor(hX / alignX) * alignX;
				if (!isOnGrid(lY, alignY)) lY = Math.ceil(lY / alignY) * alignY;
				if (!isOnGrid(hY, alignY)) hY = Math.floor(hY / alignY) * alignY;
				if (lX >= hX || lY >= hY) continue;

				// grid align the center of this rectangle
/*
				double cX = (lX + hX) / 2, cY = (lY + hY) / 2;
				if (!isOnGrid(cX, alignX))
				{
					// try expanding to the right so center is aligned
					double cXright = Math.ceil(cX / alignX) * alignX;
					Poly testPoly = new Poly(cXright*SCALEFACTOR, cY*SCALEFACTOR, ((cXright-lX) * 2)*SCALEFACTOR, (hY-lY)*SCALEFACTOR);
					if (originalMerge.contains(layer, testPoly)) bounds = testPoly.getBounds2D(); else
					{
						// try expanding to the left so center is aligned
						double cXleft = Math.floor(cX / alignX) * alignX;
						testPoly = new Poly(cXleft*SCALEFACTOR, cY*SCALEFACTOR, ((hX-cXleft) * 2)*SCALEFACTOR, (hY-lY)*SCALEFACTOR);
						if (originalMerge.contains(layer, testPoly)) bounds = testPoly.getBounds2D(); else
						{
							// try contracting on the right so center is aligned
							testPoly = new Poly(cXright*SCALEFACTOR, cY*SCALEFACTOR, ((hX-cXright) * 2)*SCALEFACTOR, (hY-lY)*SCALEFACTOR);
							if (originalMerge.contains(layer, testPoly)) bounds = testPoly.getBounds2D(); else
							{
								// try contracting on the left so center is aligned
								testPoly = new Poly(cXleft*SCALEFACTOR, cY*SCALEFACTOR, ((cXleft-lX) * 2)*SCALEFACTOR, (hY-lY)*SCALEFACTOR);
								if (originalMerge.contains(layer, testPoly)) bounds = testPoly.getBounds2D(); else
									continue;
							}
						}
					}
					if (bounds.getWidth() <= 0) continue;
					lX = bounds.getMinX()/SCALEFACTOR;
					hX = bounds.getMaxX()/SCALEFACTOR;
					cX = (lX + hX) / 2;
				}

				if (!isOnGrid(cY, alignY))
				{
					// try expanding upward so center is aligned
					double cYup = Math.ceil(cY / alignY) * alignY;
					Poly testPoly = new Poly(cX*SCALEFACTOR, cYup*SCALEFACTOR, (hX-lX)*SCALEFACTOR, ((cYup-lY) * 2)*SCALEFACTOR);
					if (originalMerge.contains(layer, testPoly)) bounds = testPoly.getBounds2D(); else
					{
						// try expanding downward so center is aligned
						double cYdown = Math.floor(cY / alignY) * alignY;
						testPoly = new Poly(cX*SCALEFACTOR, cYdown*SCALEFACTOR, (hX-lX)*SCALEFACTOR, ((hY-cYdown) * 2)*SCALEFACTOR);
						if (originalMerge.contains(layer, testPoly)) bounds = testPoly.getBounds2D(); else
						{
							// try contracting upward so center is aligned
							testPoly = new Poly(cX*SCALEFACTOR, cYup*SCALEFACTOR, (hX-lX)*SCALEFACTOR, ((hY-cYup) * 2)*SCALEFACTOR);
							if (originalMerge.contains(layer, testPoly)) bounds = testPoly.getBounds2D(); else
							{
								// try contracting downward so center is aligned
								testPoly = new Poly(cX*SCALEFACTOR, cYdown*SCALEFACTOR, (hX-lX)*SCALEFACTOR, ((cYdown-lY) * 2)*SCALEFACTOR);
								if (originalMerge.contains(layer, testPoly)) bounds = testPoly.getBounds2D(); else
									continue;
							}
						}
					}
					if (bounds.getHeight() <= 0) continue;
				}
*/

				// figure out which arc prototype to use for the layer
				ArcProto ap = findArcProtoForPoly(layer, poly, originalMerge);
				if (ap == null) continue;

				// determine the endpoints of the arc
				Point2D loc1, loc2;
				double width;
                if (bounds.getWidth() > bounds.getHeight())
				{
                    // horizontal arc
                    width = bounds.getHeight();
                    loc1 = new Point2D.Double((bounds.getMinX()) / SCALEFACTOR, bounds.getCenterY() / SCALEFACTOR);
					loc2 = new Point2D.Double((bounds.getMaxX()) / SCALEFACTOR, bounds.getCenterY() / SCALEFACTOR);
				} else
				{
                    // vertical arc
                    width = bounds.getWidth();
                    loc1 = new Point2D.Double(bounds.getCenterX() / SCALEFACTOR, (bounds.getMinY()) / SCALEFACTOR);
					loc2 = new Point2D.Double(bounds.getCenterX() / SCALEFACTOR, (bounds.getMaxY()) / SCALEFACTOR);
				}
				PortInst pi1 = wantConnectingNodeAt(loc1, ap, width / SCALEFACTOR, newCell);
				PortInst pi2 = wantConnectingNodeAt(loc2, ap, width / SCALEFACTOR, newCell);
				realizeArc(ap, pi1, pi2, loc1, loc2, width / SCALEFACTOR, true, true, usePureLayerNodes, merge);
			}
		}
		return false;
	}

    /**
     * Choose the right type of active for the active in the merge (takes into account select)
     */
    private void fixActiveNodes(Cell cell)
    {
        if (unifyActive) return; // no need to fix active
        if (ignoreActiveSelectWell) return;
        // if unifyActive is set, then Electric tech must only have one active
        // Otherwise, imported GDS may only have one active, but Electric has two, so
        // we need to fix that here.

        PrimitiveNode pDiffNode = null, nDiffNode = null;
        for (Iterator it = cell.getTechnology().getNodes(); it.hasNext(); )
        {
            PrimitiveNode pn = it.next();
            if (pn.getFunction() == PrimitiveNode.Function.NODE) {
                Layer layer = pn.getLayerIterator().next();
                if (layer.getFunction() == Layer.Function.DIFFN)
                    nDiffNode = pn;
                if (layer.getFunction() == Layer.Function.DIFFP)
                    pDiffNode = pn;
            }
        }

        // first get all select layers
        PolyMerge merge = new PolyMerge();
        for (Iterator it = cell.getNodes(); it.hasNext(); ) {
            NodeInst ni = it.next();
            if (ni.isCellInstance()) continue;
            Poly [] polys = tech.getShapeOfNode(ni);
            for(int j=0; j it = cell.getNodes(); it.hasNext(); ) {
            NodeInst ni = it.next();
            if (ni.isCellInstance()) continue;
            PrimitiveNode pn = (PrimitiveNode)ni.getProto();
            if (pn.getFunction() == PrimitiveNode.Function.NODE) {
                Layer nodeLayer = pn.getLayerIterator().next();
                PrimitiveNode newType = null;

                if (nodeLayer.getFunction() == Layer.Function.DIFFN) {
                    // make sure n-diffusion is in n-select
                    Poly [] polys = tech.getShapeOfNode(ni);
                    for (Poly poly : polys) {
                        if (merge.contains(pSelectLayer, poly)) {
                            // switch it p-active
                            newType = pDiffNode;
                            break;
                        }
                    }
                }
                if (nodeLayer.getFunction() == Layer.Function.DIFFP) {
                    // make sure p-diffusion is in p-select
                    Poly [] polys = tech.getShapeOfNode(ni);
                    for (Poly poly : polys) {
                        if (merge.contains(nSelectLayer, poly)) {
                            // switch it n-active
                            newType = nDiffNode;
                            break;
                        }
                    }
                }

                if (newType == null) continue;
                NodeInst newNi = NodeInst.newInstance(newType, ep, ni.getAnchorCenter(), ni.getXSize(), ni.getYSize(), cell);
                if (ni.getTrace() != null && ni.getTrace().length > 0)
                {
                    EPoint [] origPoints = ni.getTrace();
                    Point2D [] points = new Point2D[origPoints.length];
                    // for some reason getTrace returns points relative to center, but setTrace expects absolute coordinates
                    for (int i=0; i aIt = tech.getArcs(); aIt.hasNext(); )
		{
			ArcProto ap = aIt.next();
			if (ap.getFunction() == neededFunction) return ap;
		}
		return null;
*/

        Layer.Function fun = layer.getFunction();
        if (fun.isPoly() || fun.isMetal()) return arcsForLayer.get(layer);
        if (!fun.isDiff()) return null;

        ArrayList requiredLayers = new ArrayList();
        ArrayList requiredAbsentLayers = new ArrayList();

        // must further differentiate the active arcs...find implants
        Layer wellP = null, wellN = null;
        for(Layer l : originalMerge.getKeySet())
        {
            if (l.getFunction() == Layer.Function.WELLP) wellP = l;
            if (l.getFunction() == Layer.Function.WELLN) wellN = l;
        }

        // Active must have P-Select or N-Select
        if (pSelectLayer != null && originalMerge.intersects(pSelectLayer, poly)) {
            if (unifyActive)
                requiredLayers.add(activeLayer);
            else
                requiredLayers.add(realPActiveLayer);
            requiredLayers.add(pSelectLayer);
        }
        if (nSelectLayer != null && originalMerge.intersects(nSelectLayer, poly)) {
            if (unifyActive)
                requiredLayers.add(activeLayer);
            else
                requiredLayers.add(realNActiveLayer);
            requiredLayers.add(nSelectLayer);
        }

        // Active could either be an Active arc or a Well arc, depending on well type
        if (wellN == null || !originalMerge.intersects(wellN, poly))
            requiredAbsentLayers.add(wellN);
        if (wellN != null && originalMerge.intersects(wellN, poly))
            requiredLayers.add(wellN);

        // Active could either be an Active arc or a Well arc, depending on well type
        if (wellP == null || !originalMerge.intersects(wellP, poly))
            requiredAbsentLayers.add(wellP);
        if (wellP != null && originalMerge.intersects(wellP, poly))
            requiredLayers.add(wellP);

        // now find the arc with the desired function
        for(Iterator aIt = tech.getArcs(); aIt.hasNext(); )
        {
            ArcProto ap = aIt.next();
            List apLayers = new ArrayList();
            for (Iterator layit = ap.getLayerIterator(); layit.hasNext(); )
                apLayers.add(layit.next());
            // make sure required layers exist
            boolean failed = false;
            for (Layer l : requiredLayers) {
                if (!apLayers.contains(l)) { failed = true; break; }
            }
            if (failed) continue;
            for (Layer l : requiredAbsentLayers) {
                if (apLayers.contains(l)) { failed = true; break; }
            }
            if (failed) continue;
            return ap;
        }
        return null;
	}

	/**
	 * Method to locate a port on a node at a specific point with a specific connectivity.
	 * @param pt the center location of the desired node.
	 * @param ap the type of the arc that must connect.
	 * @param size the size of the node (if it must be created).
	 * @param newCell the cell in which to locate or place the node.
	 * @return the port on the node that is at the proper point.
	 * If there is none there, a node is created.
	 */
	private PortInst wantConnectingNodeAt(Point2D pt, ArcProto ap, double size, Cell newCell)
	{
		Rectangle2D bounds = new Rectangle2D.Double(pt.getX(), pt.getY(), 0, 0);
		for(Iterator it = newCell.searchIterator(bounds); it.hasNext(); )
		{
			Geometric geom = it.next();
			if (!(geom instanceof NodeInst)) continue;
			NodeInst ni = (NodeInst)geom;
			for(Iterator pIt = ni.getPortInsts(); pIt.hasNext(); )
			{
				PortInst pi = pIt.next();
				PortProto pp = pi.getPortProto();
				if (!pp.connectsTo(ap)) continue;
				Poly poly = pi.getPoly();
				if (poly.contains(pt)) return pi;
			}
		}
		NodeInst ni = createNode(ap.findPinProto(), pt, size, size, null, newCell);
		return ni.getOnlyPortInst();
	}

	/**
	 * Method to locate a node at a specific point with a specific type.
	 * @param pt the center location of the desired node.
	 * @param pin the type of the desired node.
	 * @param size the size of the node (if it must be created).
	 * @param newCell the cell in which to locate or place the node.
	 * @return a node of that type at that location.
	 * If there is none there, it is created.
	 */
	private NodeInst wantNodeAt(Point2D pt, NodeProto pin, double size, Cell newCell)
	{
		Rectangle2D bounds = new Rectangle2D.Double(pt.getX(), pt.getY(), 0, 0);
		for(Iterator it = newCell.searchIterator(bounds); it.hasNext(); )
		{
			Geometric geom = it.next();
			if (!(geom instanceof NodeInst)) continue;
			NodeInst ni = (NodeInst)geom;
			if (ni.getProto() != pin) continue;
			if (ni.getAnchorCenter().equals(pt)) return ni;
		}
		NodeInst ni = createNode(pin, pt, size, size, null, newCell);
		return ni;
	}

	/**
	 * Method to find the PortInst on a NodeInst that connects to a given PortProto and is closest to a given point.
	 * Because some primitive nodes (transistors) may have multiple ports that connect to each other
	 * (the poly ports) and because the system returns only one of those ports when describing the topology of
	 * a piece of geometry, it is necessary to find the closest port.
	 * @param ni the primitive NodeInst being examined.
	 * @param pp the primitive port on that node which defines the connection to the node.
	 * @param pt a point close to the desired port.
	 * @return the PortInst on the node that is electrically connected to the given primitive port, and closest to the point.
	 */
	private PortInst findPortInstClosestToPoly(NodeInst ni, PrimitivePort pp, Point2D pt)
	{
		PortInst touchingPi = ni.findPortInstFromProto(pp);
		PrimitiveNode pnp = (PrimitiveNode)ni.getProto();
		Poly touchingPoly = touchingPi.getPoly();
		double bestDist = pt.distance(new Point2D.Double(touchingPoly.getCenterX(), touchingPoly.getCenterY()));
		for(Iterator pIt = pnp.getPorts(); pIt.hasNext(); )
		{
			PrimitivePort prP = (PrimitivePort)pIt.next();
			if (prP.getTopology() == pp.getTopology())
			{
				PortInst testPi = ni.findPortInstFromProto(prP);
				Poly testPoly = testPi.getPoly();
				double dist = pt.distance(new Point2D.Double(testPoly.getCenterX(), testPoly.getCenterY()));
				if (dist < bestDist)
				{
					bestDist = dist;
					touchingPi = testPi;
				}
			}
		}
		return touchingPi;
	}

	private static class Centerline
	{
		Point2D start, end;
		EPoint startUnscaled, endUnscaled;
		boolean startHub, endHub;
		double width;
		boolean handled;
		int angle;

		Centerline(double width, Point2D start, Point2D end)
		{
			this.width = width;
			this.start = start;
			this.startUnscaled = new EPoint(start.getX() / SCALEFACTOR, start.getY() / SCALEFACTOR);
			this.endUnscaled = new EPoint(end.getX() / SCALEFACTOR, end.getY() / SCALEFACTOR);
			this.end = end;
			startHub = endHub = false;
			if (start.equals(end)) angle = -1; else
				angle = GenMath.figureAngle(end, start);
		}

		void setStart(double x, double y)
		{
			start.setLocation(x, y);
			startUnscaled = new EPoint(x / SCALEFACTOR, y / SCALEFACTOR);
		}

		void setEnd(double x, double y)
		{
			end.setLocation(x, y);
			endUnscaled = new EPoint(x / SCALEFACTOR, y / SCALEFACTOR);
		}

        Rectangle2D getBounds() {
            if (start.getX() == end.getX()) {
                // vertical
                double minX = (start.getX() < end.getX() ? start.getX() : end.getX()) - width/2.0;
                double minY = start.getY() < end.getY() ? start.getY() : end.getY();
                double maxY = start.getY() > end.getY() ? start.getY() : end.getY();
                return new Rectangle2D.Double(minX, minY, width, maxY-minY);
            }
            if (start.getY() == end.getY()) {
                // horizontal
                double minY = (start.getY() < end.getY() ? start.getY() : end.getY()) - width/2.0;
                double minX = start.getX() < end.getX() ? start.getX() : end.getX();
                double maxX = start.getX() > end.getX() ? start.getX() : end.getX();
                return new Rectangle2D.Double(minX, minY, maxX-minX, width);
            }
            return null; // non-Manhattan
        }

        public String toString()
		{
			return "CENTERLINE from (" + TextUtils.formatDouble(start.getX()/SCALEFACTOR) + "," +
				TextUtils.formatDouble(start.getY()/SCALEFACTOR) + ") to (" +
				TextUtils.formatDouble(end.getX()/SCALEFACTOR) + "," +
				TextUtils.formatDouble(end.getY()/SCALEFACTOR) + ") wid=" +
				TextUtils.formatDouble(width/SCALEFACTOR) + ", len=" +
				TextUtils.formatDouble((start.distance(end)/SCALEFACTOR));
		}
	}

	/**
	 * Method to return the port and location to use for one end of a Centerline.
	 * @param cl the Centerline to connect
	 * @param loc1 it's location (values returned through this object!)
	 * @param layer the layer associated with the Centerline.
	 * @param ap the type of arc to create.
	 * @param startSide true to consider the "start" end of the Centerline, false for the "end" end.
	 * @param newCell the Cell in which to find ports.
	 * @return the PortInst on the Centerline.
	 */
	private PortInst locatePortOnCenterline(Centerline cl, Point2D loc1, Layer layer,
		ArcProto ap, boolean startSide, Cell newCell)
	{
		PortInst piRet = null;
		boolean isHub = cl.endHub;
		gridAlignCenterline(cl, startSide);
		EPoint startPoint = cl.endUnscaled;
		if (startSide)
		{
			isHub = cl.startHub;
			startPoint = cl.startUnscaled;
		}
		if (!isHub)
		{
			List possiblePorts = findPortInstsTouchingPoint(startPoint, layer, newCell, ap);
			for(PortInst pi : possiblePorts)
			{
				Poly portPoly = pi.getPoly();
				Point2D [] points = portPoly.getPoints();
				if (points.length == 1)
				{
					Point2D iPt = GenMath.intersect(cl.startUnscaled, cl.angle, points[0], (cl.angle+900)%3600);
					if (iPt != null)
					{
						loc1.setLocation(iPt.getX(), iPt.getY());
						piRet = pi;
						break;
					}
				} else
				{
					if (portPoly.contains(startPoint))
					{
						loc1.setLocation(startPoint);
						piRet = pi;
						break;
					}
					for(int i=0; i Math.max(portLineFrom.getX(), portLineTo.getX()) ||
									interPt.getY() < Math.min(portLineFrom.getY(), portLineTo.getY()) ||
									interPt.getY() > Math.max(portLineFrom.getY(), portLineTo.getY())) interPt = null;
							}
						}
						if (interPt == null) continue;
						loc1.setLocation(interPt.getX(), interPt.getY());
						if (!portPoly.contains(loc1)) continue;
						piRet = pi;
						break;
					}
					if (piRet != null) break;
				}
			}
		}
		if (piRet == null)
		{
			// shrink the end inward by half-width
			PrimitiveNode pin = ap.findPinProto();
			int ang = GenMath.figureAngle(cl.start, cl.end);
			double xOff = GenMath.cos(ang) * cl.width/2;
			double yOff = GenMath.sin(ang) * cl.width/2;
//			double aliX = 1, aliY = 1;
//			if (alignment != null)
//			{
//				if (alignment.getWidth() > 0) aliX = scaleUp(alignment.getWidth());
//				if (alignment.getHeight() > 0) aliY = scaleUp(alignment.getHeight());
//			}
			if (startSide)
			{
				if (!isHub && cl.start.distance(cl.end) > cl.width)
				{
					//xOff = Math.floor(xOff / aliX) * aliX;
					//yOff = Math.floor(yOff / aliY) * aliY;
                    // if shortening to allow ends extend will put the arc off-grid, do not do it
                    if (xOff > 0 && (xOff % scaleUp(alignment.getWidth())) != 0) xOff = 0;
                    if (yOff > 0 && (yOff % scaleUp(alignment.getHeight())) != 0) yOff = 0;
                    cl.setStart(cl.start.getX() + xOff, cl.start.getY() + yOff);
				}
                double size = pin.getFactoryDefaultBaseDimension().getLambdaWidth();
                NodeInst ni = wantNodeAt(cl.startUnscaled, pin, size, newCell);
				loc1.setLocation(cl.startUnscaled.getX(), cl.startUnscaled.getY());
				piRet = ni.getOnlyPortInst();
			} else
			{
				if (!isHub && cl.start.distance(cl.end) > cl.width)
				{
					//xOff = Math.ceil(xOff / aliX) * aliX;
					//yOff = Math.ceil(yOff / aliY) * aliY;
                    // if shortening to allow ends extend will put the arc off-grid, do not do it
                    if (xOff > 0 && (xOff % scaleUp(alignment.getWidth())) != 0) xOff = 0;
                    if (yOff > 0 && (yOff % scaleUp(alignment.getHeight())) != 0) yOff = 0;
					cl.setEnd(cl.end.getX() - xOff, cl.end.getY() - yOff);
				}
				NodeInst ni = wantNodeAt(cl.endUnscaled, pin, cl.width / SCALEFACTOR, newCell);
				loc1.setLocation(cl.endUnscaled.getX(), cl.endUnscaled.getY());
				piRet = ni.getOnlyPortInst();
			}
		}
		return piRet;
	}

	private void gridAlignCenterline(Centerline cl, boolean startSide)
	{
//		// grid align the edges
//		double halfWidth = cl.width / 2;
//halfWidth = 0;		// is this right?
//		if (cl.start.getX() == cl.end.getX())
//		{
//			// vertical arc: make sure ends align in Y
//			int ali = (int)Math.round(alignment.getHeight() * SCALEFACTOR);
//			if (ali == 0) return;
//			if (startSide)
//			{
//				// adjust the "start" end
//				if (cl.start.getY() < cl.end.getY())
//				{
//					// start on left: compute edge below it
//					double edge = cl.start.getY() - halfWidth;
//					cl.setStart(cl.start.getX(), Math.ceil(edge / ali) * ali + halfWidth);
//				} else
//				{
//					// start on right: compute edge above it
//					double edge = cl.start.getY() + halfWidth;
//					cl.setStart(cl.start.getX(), Math.floor(edge / ali) * ali - halfWidth);
//				}
//			} else
//			{
//				// adjust the "end" end
//				if (cl.end.getY() < cl.start.getY())
//				{
//					// end on left: compute edge below it
//					double edge = cl.end.getY() - halfWidth;
//					cl.setEnd(cl.end.getX(), Math.ceil(edge / ali) * ali + halfWidth);
//				} else
//				{
//					// end on right: compute edge above it
//					double edge = cl.end.getY() + halfWidth;
//					cl.setEnd(cl.end.getX(), Math.floor(edge / ali) * ali - halfWidth);
//				}
//			}
//		} else if (cl.start.getY() == cl.end.getY())
//		{
//			// horizontal arc: make sure ends align in X
//			int ali = (int)Math.round(alignment.getWidth() * SCALEFACTOR);
//			if (ali == 0) return;
//			if (startSide)
//			{
//				// adjust the "start" end
//				if (cl.start.getX() < cl.end.getX())
//				{
//					// start on left: compute edge below it
//					double edge = cl.start.getX() - halfWidth;
//					cl.setStart(Math.ceil(edge / ali) * ali + halfWidth, cl.start.getY());
//				} else
//				{
//					// start on right: compute edge above it
//					double edge = cl.start.getX() + halfWidth;
//					cl.setStart(Math.floor(edge / ali) * ali - halfWidth, cl.start.getY());
//				}
//			} else
//			{
//				// adjust the "end" end
//				if (cl.end.getX() < cl.start.getX())
//				{
//					// end on left: compute edge below it
//					double edge = cl.end.getX() - halfWidth;
//					cl.setEnd(Math.ceil(edge / ali) * ali + halfWidth, cl.end.getY());
//				} else
//				{
//					// end on right: compute edge above it
//					double edge = cl.end.getX() + halfWidth;
//					cl.setEnd(Math.floor(edge / ali) * ali - halfWidth, cl.end.getY());
//				}
//			}
//		}
	}

	private List findPortInstsTouchingPoint(Point2D pt, Layer layer, Cell newCell, ArcProto ap)
	{
		List touchingNodes = new ArrayList();
		boolean mightCreateExports = false;
		Rectangle2D checkBounds = new Rectangle2D.Double(pt.getX(), pt.getY(), 0, 0);
		for(Iterator it = newCell.searchIterator(checkBounds); it.hasNext(); )
		{
			Geometric geom = it.next();
			if (!(geom instanceof NodeInst)) continue;
			NodeInst ni = (NodeInst)geom;
			if (ni.isCellInstance())
			{
				boolean found = false;
				for(Iterator pIt = ni.getPortInsts(); pIt.hasNext(); )
				{
					PortInst pi = pIt.next();
					Poly portPoly = pi.getPoly();
					if (portPoly.contains(pt))
					{
						touchingNodes.add(pi);
						found = true;
						break;
					}
				}
				if (found) continue;

				// remember that a cell was found...might have to create exports on it
				mightCreateExports = true;
				continue;
			}

			// for pins, must be centered over the desired point
			if (ni.getFunction().isPin())
			{
				if (!ni.getOnlyPortInst().getPortProto().connectsTo(ap)) continue;
				if (ni.getAnchorCenter().equals(pt))
				{
					touchingNodes.add(ni.getOnlyPortInst());
				}
			} else
			{
				// non-pins can have any touching and connecting layer
				Poly [] polys = tech.getShapeOfNode(ni, true, true, null);
				FixpTransform trans = ni.rotateOut();
				for(int i=0; i it = cell.searchIterator(checkBounds); it.hasNext();)
        {
            Geometric geom = it.next();
            if (!(geom instanceof NodeInst)) continue;
            NodeInst subNi = (NodeInst)geom;
            if (subNi.isCellInstance()) continue;

            Technology tech = subNi.getProto().getTechnology();
            FixpTransform trans = subNi.rotateOut();
            Poly [] polyList = tech.getShapeOfNode(subNi, true, true, null);
            for(int i=0; i it = cell.searchIterator(checkBounds); it.hasNext(); )
		{
			Geometric geom = it.next();
			if (!(geom instanceof NodeInst)) continue;
			NodeInst subNi = (NodeInst)geom;
			PortInst foundPi = null;
			if (subNi.isCellInstance())
			{
				FixpTransform transIn = subNi.rotateIn(subNi.translateIn());
				Point2D inside = new Point2D.Double();
				transIn.transform(pt, inside);
				Cell subCell = (Cell)subNi.getProto();
				PortInst pi = makePort(subCell, layer, inside);
				if (pi != null)
				{
					// already exported?
					for(Iterator eIt = pi.getNodeInst().getExports(); eIt.hasNext(); )
					{
						Export e = eIt.next();
						if (e.getOriginalPort() == pi)
						{
							foundPi = subNi.findPortInstFromProto(e);
							return foundPi;
						}
					}

					// if not already exported, make the export now
					if (foundPi == null)
					{
						Netlist nl = subCell.getNetlist();
						Network net = nl.getNetwork(pi);
						String exportName = null;
						for(Iterator nIt = net.getExportedNames(); nIt.hasNext(); )
						{
							String eName = nIt.next();
							if (eName.startsWith("E"))
							{
								boolean isFake = false;
								for(Export e : generatedExports)
								{
									if (e.getParent() == subCell && e.getName().equals(eName)) { isFake = true;   break; }
								}
								if (isFake) continue;
							}
							if (exportName == null || exportName.length() < eName.length()) exportName = eName;
						}
						boolean genFakeName = (exportName == null);
						if (genFakeName) exportName = "E";
						exportName = ElectricObject.uniqueObjectName(exportName, subCell, Export.class, true, true);
						Export e = Export.newInstance(subCell, pi, exportName, ep);
						if (genFakeName)
                            generatedExports.add(e);
						foundPi = subNi.findPortInstFromProto(e);
						return foundPi;
					}
				}
			}
		}
		return null;
	}

	/********************************************** VIA/CONTACT EXTRACTION **********************************************/

	private static class PossibleVia
	{
		PrimitiveNode pNp;
		int rotation;
		double minWidth, minHeight;
		double multicutSep1D, multicutSep2D, multicutSizeX, multicutSizeY;
		Layer [] layers;
		double [] shrinkL, shrinkR, shrinkT, shrinkB;

		PossibleVia(PrimitiveNode pNp, int numLayers)
		{
			this.pNp = pNp;
			rotation = 0;
			layers = new Layer[numLayers];
			shrinkL = new double[numLayers];
			shrinkR = new double[numLayers];
			shrinkT = new double[numLayers];
			shrinkB = new double[numLayers];
		}
	}

	/**
	 * Method to scan the geometric information for possible contacts and vias.
	 * Any vias found are created in the new cell and removed from the geometric information.
	 * @param merge the current geometry being extracted.
	 * @param originalMerge the original geometry.
	 * @param newCell the Cell where new geometry is being created.
	 * @return false if the job was aborted.
	 */
	private boolean extractVias(PolyMerge merge, PolyMerge originalMerge, Cell oldCell, Cell newCell, boolean usePureLayerNodes)
	{
		// make a list of all via/cut layers in the technology and count the number of vias/cuts
		int totalCuts = 0;
		List layers = new ArrayList();
		for (Layer layer : allCutLayers.keySet())
		{
			layers.add(layer);
			CutInfo cInfo = allCutLayers.get(layer);
			totalCuts += cInfo.getNumCuts();
		}

		// initialize list of nodes that have been created
		List contactNodes = new ArrayList();

		// examine all vias/cuts for possible contacts
		int soFar = 0;
		for (Layer layer : layers)
		{
			// compute the possible via nodes that this layer could become
			List possibleVias = findPossibleVias(layer);

			// make a list of all necessary layers
			Set layersToExamine = new TreeSet();
			for(PossibleVia pv : possibleVias)
			{
				for(int i=0; i cutsNotExtracted = new ArrayList();
			while (cInfo.getNumCuts() > 0)
			{
				PolyBase cut = cInfo.getFirstCut();
				soFar++;
				if (!recursive && (soFar % 100) == 0) Job.getUserInterface().setProgressValue(soFar * 100 / totalCuts);

				// figure out which of the layers is present at this cut point
				Rectangle2D cutBox = cut.getBox();
				if (cutBox == null)
				{
					cutBox = cut.getBounds2D();
					double centerX = cutBox.getCenterX()/SCALEFACTOR;
					double centerY = cutBox.getCenterY()/SCALEFACTOR;
					String msg = "Cannot extract nonManhattan contact cut at (" + TextUtils.formatDistance(centerX) +
						"," + TextUtils.formatDistance(centerY) + ")";
					addErrorLog(newCell, msg, new EPoint(centerX, centerY));
					cInfo.removeCut(cut);
					cutsNotExtracted.add(cut);
					continue;
				}
				Point2D ctr = new Point2D.Double(cutBox.getCenterX(), cutBox.getCenterY());
				Set layersPresent = new TreeSet();
				for(Layer l : layersToExamine)
				{
					boolean layerAtPoint = originalMerge.contains(l, ctr);
					if (layerAtPoint) layersPresent.add(geometricLayer(l));
				}
				boolean ignorePWell = false, ignoreNWell = false;
				if (pSubstrateProcess)
				{
					// P-substrate process (P-well is presumed where there is no N-Well)
					boolean foundNWell = false;
					for(Layer l : layersPresent)
					{
						if (l.getFunction() == Layer.Function.WELLN) { foundNWell = true;   break; }
					}
					if (!foundNWell) ignorePWell = true;
				}
				if (nSubstrateProcess)
				{
					// N-Substrate process (N-well is presumed where there is no P-Well)
					boolean foundPWell = false;
					for(Layer l : layersPresent)
					{
						if (l.getFunction() == Layer.Function.WELLP) { foundPWell = true;   break; }
					}
					if (!foundPWell) ignoreNWell = true;
				}

				boolean foundCut = false;
				String reason = null;
				for(PossibleVia pv : possibleVias)
				{
					// quick test to see if this via could possibly exist
					List missingLayers = null;
					for(int i=0; i();
							missingLayers.add(pv.layers[i]);
							break;
						}
					}
					if (missingLayers != null)
					{
						reason = "layers are missing:";
						for(Layer l : missingLayers) reason += " " + l.getName();
						continue;
					}
					if (DEBUGCONTACTS) System.out.println("CONSIDERING "+pv.pNp.describe(false)+" ROTATED "+pv.rotation+" AT ("+
						TextUtils.formatDouble(cutBox.getCenterX()/SCALEFACTOR)+","+
						TextUtils.formatDouble(cutBox.getCenterY()/SCALEFACTOR)+")...");

					// see if this is an active/poly layer (in which case, there can be no poly/active in the area)
					boolean activeCut = false;
					boolean polyCut = false;
					NodeLayer [] primLayers = pv.pNp.getNodeLayers();
					for(int i=0; i cutsInArea = new HashSet();
					cutsInArea.add(cut);
					Rectangle2D multiCutBounds = (Rectangle2D)cutBox.clone();
					double cutLimit = Math.ceil(Math.max(pv.multicutSep1D, pv.multicutSep2D) +
						Math.max(pv.multicutSizeX, pv.multicutSizeY)) * SCALEFACTOR;
					boolean foundMore = true;
                    double xspacing = 0, yspacing = 0;
                    while (foundMore)
					{
						foundMore = false;
                        Rectangle2D searchArea = new Rectangle2D.Double(multiCutBounds.getMinX()-cutLimit, multiCutBounds.getMinY()-cutLimit,
							multiCutBounds.getWidth() + cutLimit*2, multiCutBounds.getHeight() + cutLimit*2);
/*
                        Rectangle2D searchArea2 = new Rectangle2D.Double(searchArea.getMinX()/SCALEFACTOR, searchArea.getMinY()/SCALEFACTOR,
                                searchArea.getWidth()/SCALEFACTOR, searchArea.getHeight()/SCALEFACTOR);
                        System.out.println("Checking search area "+searchArea2);
*/
                        for(Iterator sea = new RTNode.Search(searchArea, cInfo.getRTree(), true); sea.hasNext(); )
						{
							CutBound cBound = sea.next();
							if (cutsInArea.contains(cBound.cut)) continue;
							Rectangle2D bound = cBound.getBounds();
							if (!searchArea.contains(bound.getCenterX(), bound.getCenterY())) continue;

                            // use only cuts at a spacing that matches (a multiple of) the nearest contact spacing
                            double distX = cut.getCenterX() - bound.getCenterX();
                            double distY = cut.getCenterY() - bound.getCenterY();

                            if (xspacing == 0) xspacing = distX;
                            else if (distX % xspacing != 0) continue;
                            if (yspacing == 0) yspacing = distY;
                            else if (distY % yspacing != 0) continue;

/*
                            // make sure cuts are in a contiguous array at the initial spacing
                            if (furthestX == 0) furthestX = distX;
                            if (furthestY == 0) furthestY = distY;
                            if (distX > furthestX) {
                                if (distX == furthestX + xspacing)
                                    furthestX = distX; // this is the next one on grid
                                else
                                    continue; // on grid, but not contiguous
                            }
                            if (distY > furthestY) {
                                if (distY == furthestY + yspacing)
                                    furthestY = distY; // this is the next one on grid
                                else
                                    continue; // on grid, but not contiguous
                            }

                            // record height of first column
                            if (maxColumnHeight == 0 && distX != 0) {
                                // first contact of the second column
                                maxColumnHeight = furthestY;
                            }
*/

                            double lX = Math.min(multiCutBounds.getMinX(), bound.getMinX());
							double hX = Math.max(multiCutBounds.getMaxX(), bound.getMaxX());
							double lY = Math.min(multiCutBounds.getMinY(), bound.getMinY());
							double hY = Math.max(multiCutBounds.getMaxY(), bound.getMaxY());
							Rectangle2D newMultiCutBounds = new Rectangle2D.Double(lX, lY, hX-lX, hY-lY);

							// make sure the expanded area has both metal layers in it
							boolean fits = true;
							PolyBase layerPoly = new PolyBase(newMultiCutBounds);
							for(int i=0; i rectVias = getLargestRectangleOfVias(cutsInArea, cut, xspacing, yspacing, multiCutBounds);
                    cutsInArea.clear();
                    cutsInArea.addAll(rectVias);
                    multiCutBounds = (Rectangle2D)cutBox.clone();
                    for (PolyBase via : rectVias) {
                        Rectangle2D bound = via.getBounds2D();
                        double lX = Math.min(multiCutBounds.getMinX(), bound.getMinX());
                        double hX = Math.max(multiCutBounds.getMaxX(), bound.getMaxX());
                        double lY = Math.min(multiCutBounds.getMinY(), bound.getMinY());
                        double hY = Math.max(multiCutBounds.getMaxY(), bound.getMaxY());
                        multiCutBounds = new Rectangle2D.Double(lX, lY, hX-lX, hY-lY);
                    }

                    if (DEBUGCONTACTS) System.out.println("   FOUND LARGE CONTACT WITH "+cutsInArea.size()+" CUTS, IN "+
						TextUtils.formatDouble(multiCutBounds.getMinX()/SCALEFACTOR)+"<=X<="+TextUtils.formatDouble(multiCutBounds.getMaxX()/SCALEFACTOR)+" AND "+
						TextUtils.formatDouble(multiCutBounds.getMinY()/SCALEFACTOR)+"<=Y<="+TextUtils.formatDouble(multiCutBounds.getMaxY()/SCALEFACTOR));

					// determine size of possible multi-cut contact
					double trueWidth = pv.minWidth;
					double trueHeight = pv.minHeight;
					if (pv.rotation == 90 || pv.rotation == 270)
					{
						trueWidth = pv.minHeight;
						trueHeight = pv.minWidth;
					}
					double lX = cutBox.getCenterX(), hX = cutBox.getCenterX();
					double lY = cutBox.getCenterY(), hY = cutBox.getCenterY();
					for(PolyBase cutBound : cutsInArea)
					{
						if (cutBound.getCenterX() < lX) lX = cutBound.getCenterX();
						if (cutBound.getCenterX() > hX) hX = cutBound.getCenterX();
						if (cutBound.getCenterY() < lY) lY = cutBound.getCenterY();
						if (cutBound.getCenterY() > hY) hY = cutBound.getCenterY();
					}
					lX -= trueWidth/2;    hX += trueWidth/2;
					lY -= trueHeight/2;   hY += trueHeight/2;
					Rectangle2D contactBound = new Rectangle2D.Double(lX, lY, hX-lX, hY-lY);

					// see if largest multi-cut contact fits
					Layer badLayer = doesNodeFit(pv, multiCutBounds, originalMerge, ignorePWell, ignoreNWell);
					if (badLayer == null)
					{
						// it fits: see if a the cuts fit
						double mw = contactBound.getWidth();
						double mh = contactBound.getHeight();
						if (pv.rotation == 90 || pv.rotation == 270)
						{
							mw = contactBound.getHeight();
							mh = contactBound.getWidth();
						}
						if (DEBUGCONTACTS) System.out.println("   METAL LAYERS OF LARGE CONTACT FITS...NOW CHECKING CUTS ON "+
							TextUtils.formatDouble(mw/SCALEFACTOR)+"x"+TextUtils.formatDouble(mh/SCALEFACTOR)+" NODE");
						badLayer = realizeBiggestContact(pv.pNp, Technology.NodeLayer.MULTICUT_CENTERED,
							contactBound.getCenterX(), contactBound.getCenterY(), mw, mh,
							pv.rotation*10, originalMerge, newCell, contactNodes, activeCut, polyCut, usePureLayerNodes, cutsInArea);
						if (badLayer == null)
						{
							for(PolyBase cutFound : cutsInArea)
								cInfo.removeCut(cutFound);
							soFar += cutsInArea.size() - 1;
							foundCut = true;
							break;
						}
						if (cutsInArea.size() > 1 && pv.pNp.findMulticut() != null)
						{
							Layer spreadBadLayer = realizeBiggestContact(pv.pNp, Technology.NodeLayer.MULTICUT_SPREAD,
								contactBound.getCenterX(), contactBound.getCenterY(), mw, mh,
								pv.rotation*10, originalMerge, newCell, contactNodes, activeCut, polyCut, usePureLayerNodes, cutsInArea);
							if (spreadBadLayer == null)
							{
								for(PolyBase cutFound : cutsInArea)
									cInfo.removeCut(cutFound);
								soFar += cutsInArea.size() - 1;
								foundCut = true;
								break;
							}

							spreadBadLayer = realizeBiggestContact(pv.pNp, Technology.NodeLayer.MULTICUT_CORNER,
								contactBound.getCenterX(), contactBound.getCenterY(), mw, mh,
								pv.rotation*10, originalMerge, newCell, contactNodes, activeCut, polyCut, usePureLayerNodes, cutsInArea);
							if (spreadBadLayer == null)
							{
								for(PolyBase cutFound : cutsInArea)
									cInfo.removeCut(cutFound);
								soFar += cutsInArea.size() - 1;
								foundCut = true;
								break;
							}
						}
						if (DEBUGCONTACTS) System.out.println("      LARGE CONTACT CUTS DO NOT FIT (LAYER "+badLayer.getName()+")");
					}

					// try for exact cut placement with a single contact
					if (DEBUGCONTACTS) System.out.println("   CONSIDER SMALL CONTACT IN "+
						TextUtils.formatDouble(cutBox.getMinX()/SCALEFACTOR)+"<=X<="+
						TextUtils.formatDouble(cutBox.getMaxX()/SCALEFACTOR)+" AND "+
						TextUtils.formatDouble(cutBox.getMinY()/SCALEFACTOR)+"<=Y<="+
						TextUtils.formatDouble(cutBox.getMaxY()/SCALEFACTOR));
					badLayer = doesNodeFit(pv, cutBox, originalMerge, ignorePWell, ignoreNWell);
					if (badLayer == null)
					{
						// it fits: create it
						realizeNode(pv.pNp, Technology.NodeLayer.MULTICUT_CENTERED, cutBox.getCenterX(), cutBox.getCenterY(),
							pv.minWidth, pv.minHeight, pv.rotation*10, null, originalMerge, newCell, contactNodes, usePureLayerNodes);
						cInfo.removeCut(cut);
						foundCut = true;
						break;
					}

					reason = "node " + pv.pNp.describe(false) + ", layer " + badLayer.getName() + " does not fit";
					if (pv.rotation != 0) reason += " (when rotated " + pv.rotation + ")";
				}
				if (!foundCut)
				{
					double centerX = cutBox.getCenterX()/SCALEFACTOR;
					double centerY = cutBox.getCenterY()/SCALEFACTOR;
					String msg = "Cell " + newCell.describe(false) + ": Did not extract contact " +
						cut.getLayer().getName() + " cut at (" + TextUtils.formatDouble(centerX) + "," +
						TextUtils.formatDouble(centerY) + ")";
					if (reason != null) msg += " because " + reason;
					addErrorLog(newCell, msg, new EPoint(centerX, centerY));
					cInfo.removeCut(cut);
					cutsNotExtracted.add(cut);
				}
			}
			for(PolyBase pb : cutsNotExtracted)
				cInfo.addCut(pb);
		}

		// now remove all created contacts from the original merge
		if (!startSection(oldCell, "Finished extracting " + contactNodes.size() + " vias..."))
			 return false; // aborted

		// build an R-Tree of all created nodes
		RTNode root = RTNode.makeTopLevel();
		for(NodeInst ni : contactNodes)
			root = RTNode.linkGeom(null, root, ni);

		// recursively scan the R-Tree, merging geometry on created nodes and removing them from the main merge
        if (!usePureLayerNodes)
        {
            PolyMerge subtractMerge = new PolyMerge();
		    extractContactNodes(root, merge, subtractMerge, 0, contactNodes.size());
            merge.subtractMerge(subtractMerge);
        }
        return true;
	}

    /**
     * Multi-cut vias must be rectangular - get the largest rectangle that fits in the set of vias,
     * that includes the initial via
     * @param vias set of vias
     * @param initialVia the initial via
     * @param xspacing x spacing between vias
     * @param yspacing y spacing between vias
     * @param bounds bounds of all the vias
     * @return the subset of vias that form a contiguous rectangle, containing the initial via
     */
    private Set getLargestRectangleOfVias(Set vias, PolyBase initialVia, double xspacing, double yspacing, Rectangle2D bounds)
    {
        xspacing = Math.abs(xspacing);
        yspacing = Math.abs(yspacing);
        if (xspacing == 0) xspacing = SCALEFACTOR;
        if (yspacing == 0) yspacing = SCALEFACTOR;
        int numViasWide = (int)Math.ceil(bounds.getWidth() / xspacing);
        int numViasHigh = (int)Math.ceil(bounds.getHeight() / yspacing);
        PolyBase [][] viaMap = new PolyBase[numViasWide][numViasHigh];
        int nomX = 0, nomY = 0;

        for (int x=0; x=0; y--) {
                if (viaMap[x][y] == null) break;
                colMinY = y;
            }
            if (initial) minY = colMinY; // initial column
            else if (colMinY > minY) minY = colMinY;
            initial = false;
        }

        // find maxX and minX from initial via
        int maxX = nomX, minX = nomX;
        initial = true;
        for (int y=0; y=0; x--) {
                if (viaMap[x][y] == null) break;
                colMinX = x;
            }
            if (initial) minX = colMinX; // initial row
            else if (colMinX > minX) minX = colMinX;
            initial = false;
        }

        // get rectangle
        Set rectVias = new HashSet();
        for (int x=minX; x<=maxX; x++) {
            for (int y=minY; y<=maxY; y++) {
                rectVias.add(viaMap[x][y]);
            }
        }
        return rectVias;
    }

    /**
	 * Method to create the biggest contact node in a given location.
	 * @param pNp the type of node to create.
	 * @param cutVariation the contact cut spacing rule.
	 * @param x the center X coordinate of the node.
	 * @param y the center Y coordinate of the node.
	 * @param sX the initial width of the node.
	 * @param sY the initial height of the node.
	 * @param rot the rotation of the node.
	 * @param merge the merge in which this node must reside.
	 * @param newCell the Cell in which the node will be created.
	 * @param contactNodes a list of nodes that were created.
	 * @param activeCut true if this is an active cut and must not have poly in the area.
	 * @param polyCut true if this is an poly cut and must not have active in the area.
	 * @param cutsInArea the cut polygons in the area (for exact matching).
	 * @return null if successful, otherwise the polygon that could not be matched.
	 */
	private Layer realizeBiggestContact(PrimitiveNode pNp, int cutVariation, double x, double y, double sX, double sY, int rot,
		PolyMerge merge, Cell newCell, List contactNodes, boolean activeCut, boolean polyCut, boolean usePureLayerNodes,
		Set cutsInArea)
	{
//boolean debug = pNp.getName().equals("Z-Metal-1-N-Diff-Con");
//if (debug) System.out.println("LOOKING FOR LARGEST CUT...");
		Orientation orient = Orientation.fromAngle(rot);
		if (alignment != null)
		{
			if (alignment.getWidth() > 0)
			{
				double scale = scaleUp(alignment.getWidth());
				x = Math.round(x / scale) * scale;
			}
			if (alignment.getHeight() > 0)
			{
				double scale = scaleUp(alignment.getHeight());
				y = Math.round(y / scale) * scale;
			}
		}
		EPoint ctr = new EPoint(x / SCALEFACTOR, y / SCALEFACTOR);

		// first find an X size that does not fit
		double lowXInc = 0;
		double highXInc = SCALEFACTOR*2;
		for(;;)
		{
			NodeInst ni = makeDummyNodeInst(pNp, ctr, sX+highXInc, sY, orient, cutVariation);
			PolyBase error = dummyNodeFits(ni, merge, activeCut, polyCut, cutsInArea);
//if (debug) System.out.println("  GROWING X..."+TextUtils.formatDouble((sX+highXInc)/SCALEFACTOR)+"x"+TextUtils.formatDouble(sY/SCALEFACTOR)+(error==null?" FITS":" DOES NOT FIT ("+error.getLayer().getName()+")"));
			if (error != null) break;
			lowXInc = highXInc;
			highXInc *= 2;
		}

		// now iterate to find the precise node X size
		for(;;)
		{
			if (highXInc - lowXInc <= 1) { lowXInc = Math.floor(lowXInc);   break; }
			double medInc = (lowXInc + highXInc) / 2;
			NodeInst ni = makeDummyNodeInst(pNp, ctr, sX+medInc, sY, orient, cutVariation);
			PolyBase error = dummyNodeFits(ni, merge, activeCut, polyCut, cutsInArea);
//if (debug) System.out.println("  ITERATING X..."+TextUtils.formatDouble((sX+medInc)/SCALEFACTOR)+"x"+TextUtils.formatDouble(sY/SCALEFACTOR)+(error==null?" FITS":" DOES NOT FIT ("+error.getLayer().getName()+")"));
			if (error == null) lowXInc = medInc; else
				highXInc = medInc;
		}

		// first find an Y size that does not fit
		double lowYInc = 0;
		double highYInc = SCALEFACTOR*2;
		for(;;)
		{
			NodeInst ni = makeDummyNodeInst(pNp, ctr, sX, sY+highYInc, orient, cutVariation);
			PolyBase error = dummyNodeFits(ni, merge, activeCut, polyCut, cutsInArea);
//if (debug) System.out.println("  GROWING Y..."+TextUtils.formatDouble(sX/SCALEFACTOR)+"x"+TextUtils.formatDouble((sY+highYInc)/SCALEFACTOR)+(error==null?" FITS":" DOES NOT FIT ("+error.getLayer().getName()+")"));
			if (error != null) break;
			lowYInc = highYInc;
			highYInc *= 2;
		}

		// now iterate to find the precise node Y size
		for(;;)
		{
			if (highYInc - lowYInc <= 1) { lowYInc = Math.floor(lowYInc);   break; }
			double medInc = (lowYInc + highYInc) / 2;
			NodeInst ni = makeDummyNodeInst(pNp, ctr, sX, sY+medInc, orient, cutVariation);
			PolyBase error = dummyNodeFits(ni, merge, activeCut, polyCut, cutsInArea);
//if (debug) System.out.println("  ITERATING Y..."+TextUtils.formatDouble(sX/SCALEFACTOR)+"x"+TextUtils.formatDouble((sY+medInc)/SCALEFACTOR)+(error==null?" FITS":" DOES NOT FIT ("+error.getLayer().getName()+")"));
			if (error == null) lowYInc = medInc; else
				highYInc = medInc;
		}
		if (!approximateCuts)
		{
			// make sure the basic node fits
			NodeInst ni = makeDummyNodeInst(pNp, ctr, sX+lowXInc, sY+lowYInc, orient, cutVariation);
			PolyBase error = dummyNodeFits(ni, merge, activeCut, polyCut, cutsInArea);
//if (debug) System.out.println("  CHECK FINAL..."+TextUtils.formatDouble((sX+lowXInc)/SCALEFACTOR)+"x"+TextUtils.formatDouble((sY+lowYInc)/SCALEFACTOR)+(error==null?" FITS":" DOES NOT FIT ("+error.getLayer().getName()+")"));
			if (error != null)
			{
				// incrementing both axes fails: try one at a time
				if (lowXInc > lowYInc)
				{
					ni = makeDummyNodeInst(pNp, ctr, sX+lowXInc, sY, orient, cutVariation);
					error = dummyNodeFits(ni, merge, activeCut, polyCut, cutsInArea);
//if (debug) System.out.println("  FINAL, JUST X..."+TextUtils.formatDouble((sX+lowXInc)/SCALEFACTOR)+"x"+TextUtils.formatDouble((sY)/SCALEFACTOR)+(error==null?" FITS":" DOES NOT FIT ("+error.getLayer().getName()+")"));
					if (error == null) sX += lowXInc;
				} else
				{
					ni = makeDummyNodeInst(pNp, ctr, sX, sY+lowYInc, orient, cutVariation);
					error = dummyNodeFits(ni, merge, activeCut, polyCut, cutsInArea);
//if (debug) System.out.println("  FINAL, JUST Y..."+TextUtils.formatDouble((sX)/SCALEFACTOR)+"x"+TextUtils.formatDouble((sY+lowYInc)/SCALEFACTOR)+(error==null?" FITS":" DOES NOT FIT ("+error.getLayer().getName()+")"));
					if (error == null) sY += lowYInc;
				}
			} else { sX += lowXInc;   sY += lowYInc; }
			if (error != null) return error.getLayer();
		}
		if (alignment != null)
		{
			if (alignment.getWidth() > 0)
			{
				double scale = scaleUp(alignment.getWidth()) * 2;
				sX = Math.floor(sX / scale) * scale;
			}
			if (alignment.getHeight() > 0)
			{
				double scale = scaleUp(alignment.getHeight()) * 2;
				sY = Math.floor(sY / scale) * scale;
			}
		}
		realizeNode(pNp, cutVariation, x, y, sX, sY, rot, null, merge, newCell, contactNodes, usePureLayerNodes);
		return null;
	}

	private NodeInst makeDummyNodeInst(PrimitiveNode pNp, EPoint ctr, double sX, double sY, Orientation orient, int cutVariation)
	{
		NodeInst ni = NodeInst.makeDummyInstance(pNp, ep, ctr, sX / SCALEFACTOR, sY / SCALEFACTOR, orient);
		if (ni == null) return null;
		if (cutVariation != Technology.NodeLayer.MULTICUT_CENTERED)
			ni.newVar(NodeLayer.CUT_ALIGNMENT, Integer.valueOf(cutVariation), ep);
		return ni;
	}

	private PolyBase dummyNodeFits(NodeInst ni, PolyMerge merge, boolean activeCut, boolean polyCut, Set cutsInArea)
	{
		FixpTransform trans = ni.rotateOut();
		Technology tech = ni.getProto().getTechnology();
		Poly [] polys = tech.getShapeOfNode(ni);
		double biggestArea = 0;
		Poly biggestPoly = null;
		List cutsFound = null;
		if (!approximateCuts) cutsFound = new ArrayList();
        boolean hasPplus = false;
        boolean hasNplus = false;
        boolean hasActive = false;
        for(Poly poly : polys)
		{
			Layer l = poly.getLayer();
			if (l == null) continue;
			l = geometricLayer(l);

            // ignore well layers if the process doesn't have them (in this case they are substrate layers)
            if (l.getFunction() == Layer.Function.WELLP && pSubstrateProcess) continue;
            if (l.getFunction() == Layer.Function.WELLN && nSubstrateProcess) continue;

            if (l.isDiffusionLayer()) hasActive = true;
            if (l.getFunction() == Layer.Function.IMPLANTN) hasNplus = true;
            if (l.getFunction() == Layer.Function.IMPLANTP) hasPplus = true;

            poly.setLayer(l);
			poly.transform(trans);
			if (l.getFunction().isContact())
			{
				if (!approximateCuts) cutsFound.add(poly);
				continue;
			}
			double area = poly.getArea();
			if (area > biggestArea)
			{
				biggestArea = area;
				biggestPoly = poly;
			}
			Point2D[] points = poly.getPoints();
			for(int i=0; i 0) return cutsFound.get(0);
		}

		// contact fits, now check for active or poly problems
		if (activeCut && biggestPoly != null)
		{
			// disallow poly in the contact area
			if (merge.intersects(polyLayer, biggestPoly)) return biggestPoly;
		}
		if (polyCut && biggestPoly != null)
		{
			// disallow active in the contact area
			if (pActiveLayer != null && merge.intersects(pActiveLayer, biggestPoly)) return biggestPoly;
			if (nActiveLayer != null && nActiveLayer != pActiveLayer && merge.intersects(nActiveLayer, biggestPoly)) return biggestPoly;
		}
		return null;
	}

	/**
	 * Class to define all of the cuts on a given layer.
	 */
	private static class CutInfo
	{
		private Map cutMap;
		private List justCuts;
		private RTNode cutOrg;

		public CutInfo()
		{
			cutMap = new HashMap();
			justCuts = new ArrayList();
			cutOrg = RTNode.makeTopLevel();
		}

		public List getCuts() { return justCuts; }

		public int getNumCuts() { return justCuts.size(); }

		public PolyBase getFirstCut() { return justCuts.get(0); }

		public RTNode getRTree() { return cutOrg; }

		public void sortCuts()
		{
			Collections.sort(justCuts, new CutsByLocation());
		}

		public void addCut(PolyBase cut)
		{
			CutBound cb = new CutBound(cut);
			cutOrg = RTNode.linkGeom(null, cutOrg, cb);
			cutMap.put(cut, cb);
			justCuts.add(cut);
		}

		public void removeCut(PolyBase cut)
		{
			CutBound cb = cutMap.get(cut);
			cutOrg = RTNode.unLinkGeom(null, cutOrg, cb);
			cutMap.remove(cut);
			justCuts.remove(cut);
		}
	}

	/**
	 * Class to sort CutsByLocation objects by their location.
	 */
	private static class CutsByLocation implements Comparator
	{
		public int compare(PolyBase pb1, PolyBase pb2)
		{
			double pb1x = pb1.getCenterX();
			double pb1y = pb1.getCenterY();
			double pb2x = pb2.getCenterX();
			double pb2y = pb2.getCenterY();

			if (pb1x < pb2x) return 1;
			if (pb1x > pb2x) return -1;
			if (pb1y < pb2y) return 1;
			if (pb1y > pb2y) return -1;
			return 0;
		}
	}

	/**
	 * Class to define an R-Tree leaf node for geometry in the via cuts.
	 */
	private static class CutBound implements RTBounds
	{
		private PolyBase cut;

		CutBound(PolyBase cut)
		{
			this.cut = cut;
		}

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

	/**
	 * Method to recursively scan the R-Tree of created contact nodes to remove their geometry from the main merge.
	 * @param root the current position in the scan of the R-Tree.
	 * @param merge the main merge.
	 * @param subtractMerge a merge of created contact nodes that will be subtracted from the main merge.
	 * @param soFar the number of contacts handled so far.
	 * @param totalContacts the total number of contacts to handle.
	 * @return the number of contacts handled so far.
	 */
	private int extractContactNodes(RTNode root, PolyMerge merge, PolyMerge subtractMerge, int soFar, int totalContacts)
	{
		for(int j=0; j allLayers = new ArrayList();
					for(Layer lay : subtractMerge.getKeySet())
						allLayers.add(lay);
					for(Layer lay : allLayers)
						subtractMerge.deleteLayer(lay);
				}
			} else
			{
                RTNode child = root.getChildTree(j);
				soFar = extractContactNodes(child, merge, subtractMerge, soFar, totalContacts);
			}
		}
		return soFar;
	}

	/**
	 * Method to return a list of PossibleVia objects for a given cut/via layer.
	 * @param lay the cut/via layer to find.
	 * @return a List of PossibleVia objects that use the layer as a contact.
	 */
	private List findPossibleVias(Layer lay)
	{
		List possibleVias = new ArrayList();
		for(Iterator nIt = tech.getNodes(); nIt.hasNext(); )
		{
			PrimitiveNode pNp = nIt.next();
			PrimitiveNode.Function fun = pNp.getFunction();
			if (!fun.isContact() && fun != PrimitiveNode.Function.WELL &&
				fun != PrimitiveNode.Function.SUBSTRATE) continue;

			// TODO do we really need to check each contact?
			// For some reason, the MOCMOS technology with MOCMOS foundry shows the "A-" nodes (A-Metal-1-Metal-2-Contact)
			// but these primitives are not fully in existence, and crash here
			boolean bogus = false;
			Technology.NodeLayer [] nLs = pNp.getNodeLayers();
			for(int i=0; i pvLayers = new ArrayList();
			for(int i=0; i highMetal) { int s = lowMetal;   lowMetal = highMetal;   highMetal = s; }
					if (lowMetal != highMetal-1) badContact = true;
				}
				if (badContact)
				{
					if (!bogusContacts.contains(pNp))
					{
						bogusContacts.add(pNp);
						System.out.println("Not extracting unusual via contact: " + pNp.describe(false));
					}
					continue;
				}
			}

			// create the PossibleVia to describe the geometry
			PossibleVia pv = new PossibleVia(pNp, pvLayers.size());
			for(int i=0; i
	{
        private final EditingPreferences ep;

        private ViasBySize(EditingPreferences ep) {
            this.ep = ep;
        }

		public int compare(PossibleVia pv1, PossibleVia pv2)
		{
			double area1 = 0;
			Technology.NodeLayer [] layers1 = pv1.pNp.getNodeLayers();
			double sizeX = pv1.pNp.getDefSize(ep).getLambdaX();
			double sizeY = pv1.pNp.getDefSize(ep).getLambdaY();
			for(int i=0; i pTransistors = new ArrayList();
		List nTransistors = new ArrayList();
		for(Iterator it = tech.getNodes(); it.hasNext(); )
		{
			PrimitiveNode pNp = it.next();
			if (pNp.getFunction().isPTypeTransistor() || pNp.getFunction().isNTypeTransistor())
			{
				List listToUse = pTransistors;
				if (pNp.getFunction().isNTypeTransistor()) listToUse = nTransistors;

				boolean same = true;
/*
                // JKG: took out this code because it either drops all the "special transistors"
                // that have an extra layer (OD18, VTH, VTL), or drops all the regular transistors -
                // which one it does is dependent on the order of nodes returned by tech, which is
                // apparently not consistent.
                // I don't know why this code was needed - optimization?
				if (listToUse.size() > 0)
				{
					// make sure additional transistors have the same overall structure
					Set usedLayers = new TreeSet();
					Map usageFirst = new HashMap();
					Technology.NodeLayer [] layersFirst = listToUse.get(0).getNodeLayers();
					for(int i=0; i usageNew = new HashMap();
					Technology.NodeLayer [] layersNew = pNp.getNodeLayers();
					for(int i=0; i 0)
				findTransistors(pTransistors, pActiveLayer, merge, originalMerge, newCell, usePureLayerNodes);
		} else
		{
			if (nTransistors.size() > 0)
				findTransistors(nTransistors, nActiveLayer, merge, originalMerge, newCell, usePureLayerNodes);
			if (pTransistors.size() > 0)
				findTransistors(pTransistors, pActiveLayer, merge, originalMerge, newCell, usePureLayerNodes);
		}
	}

	private void findTransistors(List transistors, Layer activeLayer, PolyMerge merge,
		PolyMerge originalMerge, Cell newCell, boolean usePureLayerNodes)
	{
		originalMerge.intersectLayers(polyLayer, activeLayer, tempLayer1);
		List polyList = getMergePolys(originalMerge, tempLayer1, null);
		if (polyList != null)
		{
			for(PolyBase poly : polyList)
			{
				// look at all of the pieces of this layer
				Rectangle2D transBox = poly.getBox();
				double cX = poly.getCenterX(), cY = poly.getCenterY();
				if (alignment != null)
				{
                    // centers can be off-grid by a factor of 2, because only the edges matter for off-grid
                    if (alignment.getWidth() > 0)
						cX = Math.round(cX / scaleUp(alignment.getWidth()/2)) * scaleUp(alignment.getWidth()/2);
					if (alignment.getHeight() > 0)
						cY = Math.round(cY / scaleUp(alignment.getHeight()/2)) * scaleUp(alignment.getHeight()/2);
				}
				if (transBox == null)
				{
					// complex polygon: extract angled or serpentine transistor
					extractNonManhattanTransistor(poly, transistors.get(0), merge, originalMerge, newCell, usePureLayerNodes);
				} else
				{
					EPoint ctr = new EPoint(cX/SCALEFACTOR, cY/SCALEFACTOR);
					Map> errorInfo = new HashMap>();
					for(PrimitiveNode transistor : transistors)
					{
						// figure out which way the poly runs in the desired transistor
						NodeInst dni = NodeInst.makeDummyInstance(transistor, ep);
						Poly [] polys = transistor.getTechnology().getShapeOfNode(dni);
						double widestPoly = 0, widestActive = 0;
						for(int i=0; i primErrorInfo = new HashMap();
						errorInfo.put(transistor, primErrorInfo);

						FixpTransform trans = ni.rotateOut();
						Technology tech = ni.getProto().getTechnology();
						Poly [] tPolys = tech.getShapeOfNode(ni);
						for(Poly tPoly : tPolys)
						{
							Layer l = tPoly.getLayer();
							if (l == null) continue;
							l = geometricLayer(l);
							Layer.Function fun = l.getFunction();
							if (fun == Layer.Function.WELLP && pSubstrateProcess) continue;
							if (fun == Layer.Function.WELLN && nSubstrateProcess) continue;
							tPoly.setLayer(l);
							tPoly.transform(trans);
							Point2D[] points = tPoly.getPoints();
							for(int i=0; i 0) continue;

						realizeNode(transistor, Technology.NodeLayer.MULTICUT_CENTERED, cX, cY,
							width, height, angle, null, merge, newCell, null, usePureLayerNodes);
						errorInfo.clear();
						break;
					}
					if (errorInfo.size() > 0)
					{
						String eMsg = "Cell " + newCell.describe(false) + ": Cannot extract Transistor at (" +
							TextUtils.formatDistance(ctr.getX()) + "," + TextUtils.formatDistance(ctr.getY()) + ")";

						// see if select/well layers failed universally
						boolean missingSubstrate = true;
						for(PrimitiveNode pnp : errorInfo.keySet())
						{
							Map primErrorInfo = errorInfo.get(pnp);
							boolean substrateHere = false;
							for(Layer lay : primErrorInfo.keySet())
							{
								if (lay.getFunction().isSubstrate()) substrateHere = true;
							}
							if (!substrateHere) missingSubstrate = false;
						}
						if (missingSubstrate)
						{
							eMsg += " because there are no well or select layers (if they are in lower-levels of hierarchy, use Network Preferences to expand them)";
	                        addErrorLog(newCell, eMsg, new EPoint(cX/SCALEFACTOR, cY/SCALEFACTOR));
						} else
						{
							// not a well/select problem: look for smallest failure
							double smallestArea = Double.MAX_VALUE;
							PrimitiveNode smallestPNP = null;
							Layer smallestLayer = null;
							Rectangle2D smallestRect = null;
							for(PrimitiveNode pnp : errorInfo.keySet())
							{
								Map primErrorInfo = errorInfo.get(pnp);
								for(Layer lay : primErrorInfo.keySet())
								{
									Rectangle2D errRect = primErrorInfo.get(lay);
									double rectSize = errRect.getWidth() * errRect.getHeight();
									if (rectSize < smallestArea)
									{
										smallestArea = rectSize;
										smallestPNP = pnp;
										smallestLayer = lay;
										smallestRect = errRect;
									}
								}
							}
							if (smallestPNP != null)
							{
								eMsg += " because" + smallestPNP.describe(false) + " needs layer " + smallestLayer.getName() + " to run from " +
									TextUtils.formatDistance(smallestRect.getMinX()/SCALEFACTOR) + "<=X<=" +
									TextUtils.formatDistance(smallestRect.getMaxX()/SCALEFACTOR) + " and " +
									TextUtils.formatDistance(smallestRect.getMinY()/SCALEFACTOR) + "<=Y<=" +
									TextUtils.formatDistance(smallestRect.getMaxY()/SCALEFACTOR);
							}
	                        addErrorLog(newCell, eMsg, new EPoint(cX/SCALEFACTOR, cY/SCALEFACTOR));
						}
					}
				}
			}
		}
		originalMerge.deleteLayer(tempLayer1);
	}

	/**
	 * Method to extract a transistor from a non-Manhattan polygon that defines the intersection of poly and active.
	 * @param poly the outline of poly/active to extract.
	 * @param transistor the type of transistor to create.
	 * @param merge the geometry collection to adjust when a transistor is extracted.
	 * @param originalMerge the original geometry collection (for examination).
	 * @param newCell the cell in which to create the extracted transistor
	 */
	private void extractNonManhattanTransistor(PolyBase poly, PrimitiveNode transistor, PolyMerge merge,
		PolyMerge originalMerge, Cell newCell, boolean usePureLayerNodes)
	{
		// determine minimum width of polysilicon
		SizeOffset so = transistor.getProtoSizeOffset();
		double minWidth = transistor.getDefHeight(ep) - so.getLowYOffset() - so.getHighYOffset();

		// reduce the geometry to a skeleton of centerlines
		List lines = findCenterlines(poly, tempLayer1, minWidth, merge, originalMerge);
		if (lines.size() == 0) return;

		// if just one line, it is simply an angled transistor
		if (lines.size() == 1)
		{
			Centerline cl = lines.get(0);
			double polySize = cl.start.distance(cl.end);
			double activeSize = cl.width;
			double cX = (cl.start.getX() + cl.end.getX()) / 2;
			double cY = (cl.start.getY() + cl.end.getY()) / 2;
			double sX = polySize + scaleUp(so.getLowXOffset() + so.getHighXOffset());
			double sY = activeSize + scaleUp(so.getLowYOffset() + so.getHighYOffset());
			realizeNode(transistor, Technology.NodeLayer.MULTICUT_CENTERED, cX, cY, sX, sY,
				cl.angle, null, merge, newCell, null, usePureLayerNodes);
			return;
		}

		// serpentine transistor: organize the lines into an array of points
		EPoint [] points = new EPoint[lines.size()+1];
		for(Centerline cl : lines)
		{
			cl.handled = false;
		}
		Centerline firstCL = lines.get(0);
		firstCL.handled = true;
		points[0] = new EPoint(firstCL.start.getX(), firstCL.start.getY());
		points[1] = new EPoint(firstCL.end.getX(), firstCL.end.getY());
		int pointsSeen = 2;
		while (pointsSeen < points.length)
		{
			boolean added = false;
			for(Centerline cl : lines)
			{
				if (cl.handled) continue;
				EPoint start = new EPoint(cl.start.getX(), cl.start.getY());
				EPoint end = new EPoint(cl.end.getX(), cl.end.getY());
				if (start.equals(points[0]))
				{
					// insert "end" point at start
					for(int i=pointsSeen; i>0; i--)
						points[i] = points[i-1];
					points[0] = end;
					pointsSeen++;
					cl.handled = true;
					added = true;
					break;
				}
				if (end.equals(points[0]))
				{
					// insert "start" point at start
					for(int i=pointsSeen; i>0; i--)
						points[i] = points[i-1];
					points[0] = start;
					pointsSeen++;
					cl.handled = true;
					added = true;
					break;
				}
				if (start.equals(points[pointsSeen-1]))
				{
					// add "end" at the end
					points[pointsSeen++] = end;
					cl.handled = true;
					added = true;
					break;
				}
				if (end.equals(points[pointsSeen-1]))
				{
					// add "start" at the end
					points[pointsSeen++] = start;
					cl.handled = true;
					added = true;
					break;
				}
			}
			if (!added) break;
		}

		// make sure all points are handled
		if (pointsSeen != points.length) return;

		// compute information about the transistor and create it
		double lX = points[0].getX(), hX = points[0].getX();
		double lY = points[0].getY(), hY = points[0].getY();
		for(int i=1; i hX) hX = points[i].getX();
			if (points[i].getY() < lY) lY = points[i].getY();
			if (points[i].getY() > hY) hY = points[i].getY();
		}
		double cX = (lX + hX) / 2;
		double cY = (lY + hY) / 2;
		for(int i=0; i extendableLayers = new ArrayList();
		for (Layer layer : merge.getKeySet())
		{
			ArcProto ap = arcsForLayer.get(layer);
			if (ap == null) continue;
			extendableLayers.add(layer);
		}

		// gather everything to extend
		int totExtensions = 0;
		Map> geomToExtend = new HashMap>();
		int soFar = 0;
		for (Layer layer : extendableLayers)
		{
			List polyList = getMergePolys(merge, layer, null);
			geomToExtend.put(layer, polyList);
			totExtensions += polyList.size();
			soFar++;
			if (!recursive) Job.getUserInterface().setProgressValue(soFar * 100 / extendableLayers.size());
		}
		if (!recursive) Job.getUserInterface().setProgressValue(0);

		soFar = 0;
		for (Layer layer : extendableLayers)
		{
			ArcProto ap = arcsForLayer.get(layer);
			if (ap == null) continue;
			double wid = ap.getDefaultLambdaBaseWidth(ep);
			double arcLayerWidth = ap.getDefaultInst(ep).getCoordExtendOverMin().add(ap.getLayerExtend(layer)).multiply(2).getLambda();
			List polyList = geomToExtend.get(layer);
			for(PolyBase poly : polyList)
			{
				soFar++;
				if (!recursive) Job.getUserInterface().setProgressValue(soFar * 100 / totExtensions);

				// find out what this polygon touches
				Map netsThatTouch = getNetsThatTouch(poly, newCell, justExtend);
				if (netsThatTouch == null) continue;

				// make a list of port/arc ends that touch this polygon
				List objectsToConnect = new ArrayList();
				for(Network net : netsThatTouch.keySet())
				{
					Object entry = netsThatTouch.get(net);
					if (entry != null) objectsToConnect.add(entry);
				}

				// if only 1 object touches the polygon, see if it can be "wired" to cover
				if (objectsToConnect.size() == 1)
				{
					// touches just 1 object: see if that object can be extended to cover the polygon
					extendObject((ElectricObject)objectsToConnect.get(0), poly, layer, ap, merge, originalMerge, newCell, usePureLayerNodes);
					continue;
				}

				// if two objects touch the polygon, see if an arc can connect them
				if (!justExtend && objectsToConnect.size() == 2)
				{
					ElectricObject obj1 = (ElectricObject)objectsToConnect.get(0);
					ElectricObject obj2 = (ElectricObject)objectsToConnect.get(1);
					if (obj1 instanceof ArcInst)
					{
						PortInst pi = findArcEnd((ArcInst)obj1, poly);
						if (pi == null)
						{
							findArcEnd((ArcInst)obj1, poly);
							continue;
						}
						obj1 = pi;
					}
					if (obj2 instanceof ArcInst)
					{
						PortInst pi = findArcEnd((ArcInst)obj2, poly);
						if (pi == null)
						{
							findArcEnd((ArcInst)obj2, poly);
							continue;
						}
						obj2 = pi;
					}
					PortInst pi1 = (PortInst)obj1;
					PortInst pi2 = (PortInst)obj2;

					// see if the ports can connect in a line
					Poly poly1 = pi1.getPoly();
					Poly poly2 = pi2.getPoly();
					Rectangle2D polyBounds1 = poly1.getBounds2D();
					Rectangle2D polyBounds2 = poly2.getBounds2D();
					if (polyBounds1.getMinX() <= polyBounds2.getMaxX() && polyBounds1.getMaxX() >= polyBounds2.getMinX())
					{
						// vertical connection
						double xpos = polyBounds1.getCenterX();
						if (xpos < polyBounds2.getMinX()) xpos = polyBounds2.getMinX();
						if (xpos > polyBounds2.getMaxX()) xpos = polyBounds2.getMaxX();
						if (alignment != null && alignment.getWidth() > 0)
						{
							xpos = Math.round(xpos / scaleUp(alignment.getWidth())) * scaleUp(alignment.getWidth());
							if (xpos < polyBounds2.getMinX() || xpos > polyBounds2.getMaxX()) continue;
						}
						Point2D pt1 = new Point2D.Double(xpos, polyBounds1.getCenterY());
						Point2D pt2 = new Point2D.Double(xpos, polyBounds2.getCenterY());

						MutableBoolean headExtend = new MutableBoolean(true), tailExtend = new MutableBoolean(true);
						originalMerge.arcPolyFits(layer, pt1, pt2, arcLayerWidth, headExtend, tailExtend);
						realizeArc(ap, pi1, pi2, pt1, pt2, wid, !headExtend.booleanValue(), !tailExtend.booleanValue(), usePureLayerNodes, merge);
						continue;
					}
					if (polyBounds1.getMinY() <= polyBounds2.getMaxY() && polyBounds1.getMaxY() >= polyBounds2.getMinY())
					{
						// horizontal connection
						double ypos = polyBounds1.getCenterY();
						if (ypos < polyBounds2.getMinY()) ypos = polyBounds2.getMinY();
						if (ypos > polyBounds2.getMaxY()) ypos = polyBounds2.getMaxY();
						if (alignment != null && alignment.getHeight() > 0)
						{
							ypos = Math.round(ypos / scaleUp(alignment.getHeight())) * scaleUp(alignment.getHeight());
							if (ypos < polyBounds2.getMinY() || ypos > polyBounds2.getMaxY()) continue;
						}
						Point2D pt1 = new Point2D.Double(polyBounds1.getCenterX(), ypos);
						Point2D pt2 = new Point2D.Double(polyBounds2.getCenterX(), ypos);

						MutableBoolean headExtend = new MutableBoolean(true), tailExtend = new MutableBoolean(true);
						originalMerge.arcPolyFits(layer, pt1, pt2, arcLayerWidth, headExtend, tailExtend);
						realizeArc(ap, pi1, pi2, pt1, pt2, wid, !headExtend.booleanValue(), !tailExtend.booleanValue(), usePureLayerNodes, merge);
						continue;
					}

					// see if a bend can be made through the polygon
					Point2D pt1 = new Point2D.Double(polyBounds1.getCenterX(), polyBounds1.getCenterY());
					Point2D pt2 = new Point2D.Double(polyBounds2.getCenterX(), polyBounds2.getCenterY());
					Point2D corner1 = new Point2D.Double(polyBounds1.getCenterX(), polyBounds2.getCenterY());
					Point2D corner2 = new Point2D.Double(polyBounds2.getCenterX(), polyBounds1.getCenterY());
					Point2D containsIt = null;
					if (poly.contains(corner1)) containsIt = corner1; else
						if (poly.contains(corner2)) containsIt = corner2;
					if (containsIt != null)
					{
						PrimitiveNode np = ap.findPinProto();
						NodeInst ni = createNode(np, containsIt, np.getDefWidth(ep), np.getDefHeight(ep), null, newCell);
						PortInst pi = ni.getOnlyPortInst();

						MutableBoolean headExtend = new MutableBoolean(true), tailExtend = new MutableBoolean(true);
						originalMerge.arcPolyFits(layer, pt1, containsIt, arcLayerWidth, headExtend, tailExtend);
						realizeArc(ap, pi1, pi, pt1, containsIt, wid, !headExtend.booleanValue(), !tailExtend.booleanValue(), usePureLayerNodes, merge);

						headExtend.setValue(true);   tailExtend.setValue(true);
						originalMerge.arcPolyFits(layer, pt2, containsIt, arcLayerWidth, headExtend, tailExtend);
						realizeArc(ap, pi2, pi, pt2, containsIt, wid, !headExtend.booleanValue(), !tailExtend.booleanValue(), usePureLayerNodes, merge);
					}
				}
			}
		}
	}

	/**
	 * Method to see if a polygon can be covered by adding an arc to an Arc or Port.
	 * @param obj the object that is being extended (either an ArcInst or a PortInst).
	 * @param poly the polygon that is being covered.
	 * @param layer the layer of the polygon.
	 * @param ap the ArcProto to use when covering the polygon.
	 * @param merge the merge area being replaced.
	 * @param originalMerge the original merge area being covered.
	 * @param newCell the Cell in which to place the new arc.
	 */
	private void extendObject(ElectricObject obj, PolyBase poly, Layer layer, ArcProto ap, PolyMerge merge,
		PolyMerge originalMerge, Cell newCell, boolean usePureLayerNodes)
	{
		// can only handle rectangles now
		Rectangle2D polyBounds = poly.getBox();
		if (polyBounds == null)
		{
			Rectangle2D totalBounds = poly.getBounds2D();
			if (originalMerge.contains(layer, totalBounds))
				polyBounds = totalBounds;
		}
		if (polyBounds == null) return;

		// find the port that is being extended
		Point2D polyCtr = new Point2D.Double(polyBounds.getCenterX(), polyBounds.getCenterY());
		if (alignment != null)
		{
			double x = polyCtr.getX();
			double y = polyCtr.getY();
			if (alignment.getWidth() > 0)
				x = Math.round(x / scaleUp(alignment.getWidth())) * scaleUp(alignment.getWidth());
			if (alignment.getHeight() > 0)
				y = Math.round(y / scaleUp(alignment.getHeight())) * scaleUp(alignment.getHeight());
			polyCtr.setLocation(x, y);
		}
		if (obj instanceof ArcInst)
		{
			ArcInst ai = (ArcInst)obj;
			double headDist = polyCtr.distance(ai.getHeadLocation());
			double tailDist = polyCtr.distance(ai.getTailLocation());
			if (headDist < tailDist) obj = ai.getHeadPortInst(); else
				obj = ai.getTailPortInst();
		}
		PortInst pi = (PortInst)obj;

		// prepare to cover the polygon
		Poly portPoly = pi.getPoly();
		Rectangle2D portRect = portPoly.getBounds2D();
		portRect.setRect(scaleUp(portRect.getMinX()), scaleUp(portRect.getMinY()),
			scaleUp(portRect.getWidth()), scaleUp(portRect.getHeight()));
		PrimitiveNode np = ap.findPinProto();

		// is the port inside of the area to be covered?
		if (polyCtr.getY() >= portRect.getMinY() && polyCtr.getY() <= portRect.getMaxY() &&
			polyCtr.getX() >= portRect.getMinX() && polyCtr.getX() <= portRect.getMaxX())
		{
			// decide whether to extend horizontally or vertically
			if (polyBounds.getWidth() > polyBounds.getHeight())
			{
				// wider area, make horizontal arcs
				double endExtension = polyBounds.getHeight() / 2;
				Point2D pinPt1 = new Point2D.Double((polyBounds.getMaxX() - endExtension) / SCALEFACTOR,
					polyCtr.getY() / SCALEFACTOR);
				Point2D pinPt2 = new Point2D.Double((polyBounds.getMinX() + endExtension) / SCALEFACTOR,
					polyCtr.getY() / SCALEFACTOR);
				Point2D objPt = new Point2D.Double(portRect.getCenterX() / SCALEFACTOR, polyCtr.getY() / SCALEFACTOR);
				double size = Math.min(polyBounds.getWidth(), polyBounds.getHeight()) / SCALEFACTOR;
				NodeInst ni1 = createNode(np, pinPt1, size, size, null, newCell);
				NodeInst ni2 = createNode(np, pinPt2, size, size, null, newCell);
				realizeArc(ap, ni1.getOnlyPortInst(), pi, pinPt1, objPt,
					polyBounds.getHeight() / SCALEFACTOR, false, false, usePureLayerNodes, merge);
				realizeArc(ap, ni2.getOnlyPortInst(), pi, pinPt2, objPt,
					polyBounds.getHeight() / SCALEFACTOR, false, false, usePureLayerNodes, merge);
			} else
			{
				// taller area, make vertical arcs
				double endExtension = polyBounds.getWidth() / 2;
				Point2D pinPt1 = new Point2D.Double(polyCtr.getX() / SCALEFACTOR,
					(polyBounds.getMaxY() - endExtension) / SCALEFACTOR);
				Point2D pinPt2 = new Point2D.Double(polyCtr.getX() / SCALEFACTOR,
					(polyBounds.getMinY() + endExtension) / SCALEFACTOR);
				Point2D objPt = new Point2D.Double(polyCtr.getX() / SCALEFACTOR, portRect.getCenterY() / SCALEFACTOR);
				double size = Math.min(polyBounds.getWidth(), polyBounds.getHeight()) / SCALEFACTOR;
				NodeInst ni1 = createNode(np, pinPt1, size, size, null, newCell);
				NodeInst ni2 = createNode(np, pinPt2, size, size, null, newCell);
				realizeArc(ap, ni1.getOnlyPortInst(), pi, pinPt1, objPt,
					polyBounds.getWidth() / SCALEFACTOR, false, false, usePureLayerNodes, merge);
				realizeArc(ap, ni2.getOnlyPortInst(), pi, pinPt2, objPt,
					polyBounds.getWidth() / SCALEFACTOR, false, false, usePureLayerNodes, merge);
			}
			return;
		}

		// can we extend vertically
		if (polyCtr.getX() >= portRect.getMinX() && polyCtr.getX() <= portRect.getMaxX())
		{
			// going up to the poly or down?
			Point2D objPt = new Point2D.Double(polyCtr.getX(), portRect.getCenterY());
			Point2D objPtNormal = new Point2D.Double(polyCtr.getX() / SCALEFACTOR, portRect.getCenterY() / SCALEFACTOR);
			Point2D pinPt = null;
			Point2D pinPtNormal = null;
			boolean endExtend = true;
			double endExtension = polyBounds.getWidth() / 2;
			if (polyBounds.getHeight() < polyBounds.getWidth())
			{
				// arc is so short that it will stick out with end extension
				endExtend = false;
				endExtension = 0;
			}
			if (polyCtr.getY() > portRect.getCenterY())
			{
				// going up to the poly
				pinPt = new Point2D.Double(polyCtr.getX(), polyBounds.getMaxY() - endExtension);
				pinPtNormal = new Point2D.Double(polyCtr.getX() / SCALEFACTOR,
					(polyBounds.getMaxY() - endExtension) / SCALEFACTOR);
			} else
			{
				// going down to the poly
				pinPt = new Point2D.Double(polyCtr.getX(), polyBounds.getMinY() + endExtension);
				pinPtNormal = new Point2D.Double(polyCtr.getX() / SCALEFACTOR,
					(polyBounds.getMinY() + endExtension) / SCALEFACTOR);
			}
			MutableBoolean headExtend = new MutableBoolean(endExtend), tailExtend = new MutableBoolean(endExtend);
			double wid = polyBounds.getWidth();
			if (originalMerge.arcPolyFits(layer, pinPt, objPt, wid, headExtend, tailExtend))
			{
				double size = Math.min(polyBounds.getWidth(), polyBounds.getHeight()) / SCALEFACTOR;
				NodeInst ni1 = createNode(np, pinPtNormal, size, size, null, newCell);
				realizeArc(ap, ni1.getOnlyPortInst(), pi, pinPtNormal, objPtNormal, wid / SCALEFACTOR,
					!headExtend.booleanValue(), !tailExtend.booleanValue(), usePureLayerNodes, merge);
			}
			return;
		}

		// can we extend horizontally
		if (polyCtr.getY() >= portRect.getMinY() && polyCtr.getY() <= portRect.getMaxY())
		{
			// going left to the poly or right?
			Point2D objPt = new Point2D.Double(portRect.getCenterX(), polyCtr.getY());
			Point2D objPtNormal = new Point2D.Double(portRect.getCenterX() / SCALEFACTOR, polyCtr.getY() / SCALEFACTOR);
			Point2D pinPt = null;
			Point2D pinPtNormal = null;
			boolean endExtend = true;
			double endExtension = polyBounds.getHeight() / 2;
			if (polyBounds.getWidth() < polyBounds.getHeight())
			{
				// arc is so short that it will stick out with end extension
				endExtend = false;
				endExtension = 0;
			}
			if (polyCtr.getX() > portRect.getCenterX())
			{
				// going right to the poly
				pinPt = new Point2D.Double(polyBounds.getMaxX() - endExtension, polyCtr.getY());
				pinPtNormal = new Point2D.Double((polyBounds.getMaxX() - endExtension) / SCALEFACTOR,
					polyCtr.getY() / SCALEFACTOR);
			} else
			{
				// going left to the poly
				pinPt = new Point2D.Double(polyBounds.getMinX() + endExtension, polyCtr.getY());
				pinPtNormal = new Point2D.Double((polyBounds.getMinX() + endExtension) / SCALEFACTOR,
					polyCtr.getY() / SCALEFACTOR);
			}
			MutableBoolean headExtend = new MutableBoolean(endExtend), tailExtend = new MutableBoolean(endExtend);
			double wid = polyBounds.getHeight();
			if (originalMerge.arcPolyFits(layer, pinPt, objPt, wid, headExtend, tailExtend))
			{
				double size = Math.min(polyBounds.getWidth(), polyBounds.getHeight()) / SCALEFACTOR;
				NodeInst ni1 = createNode(np, pinPtNormal, size, size, null, newCell);
				realizeArc(ap, ni1.getOnlyPortInst(), pi, pinPtNormal, objPtNormal,
					wid / SCALEFACTOR, !headExtend.booleanValue(), !tailExtend.booleanValue(), usePureLayerNodes, merge);
			}
		}
	}

	private PortInst findArcEnd(ArcInst ai, PolyBase poly)
	{
		// see if one end of the arc touches the poly
		Point2D head = ai.getHeadLocation();
		Point2D tail = ai.getTailLocation();
		int ang = GenMath.figureAngle(tail, head);
		int angPlus = (ang + 900) % 3600;
		double width = ai.getLambdaBaseWidth() / 2;

		// see if the head end touches
		Point2D headButFarther = new Point2D.Double(head.getX() + width * GenMath.cos(ang), head.getY() + width * GenMath.sin(ang));
		if (poly.contains(headButFarther)) return ai.getHeadPortInst();
		Point2D headOneSide = new Point2D.Double(head.getX() + width * GenMath.cos(angPlus), head.getY() + width * GenMath.sin(angPlus));
		if (poly.contains(headOneSide)) return ai.getHeadPortInst();
		Point2D headOtherSide = new Point2D.Double(head.getX() + width * GenMath.cos(angPlus), head.getY() + width * GenMath.sin(angPlus));
		if (poly.contains(headOtherSide)) return ai.getHeadPortInst();

		// see if the tail end touches
		Point2D tailButFarther = new Point2D.Double(tail.getX() - width * GenMath.cos(ang), tail.getY() - width * GenMath.sin(ang));
		if (poly.contains(tailButFarther)) return ai.getTailPortInst();
		Point2D tailOneSide = new Point2D.Double(tail.getX() - width * GenMath.cos(angPlus), tail.getY() - width * GenMath.sin(angPlus));
		if (poly.contains(tailOneSide)) return ai.getTailPortInst();
		Point2D tailOtherSide = new Point2D.Double(tail.getX() - width * GenMath.cos(angPlus), tail.getY() - width * GenMath.sin(angPlus));
		if (poly.contains(tailOtherSide)) return ai.getTailPortInst();

		return null;
	}

	private boolean polysTouch(PolyBase poly1, PolyBase poly2)
	{
		Point2D [] points1 = poly1.getPoints();
		Point2D [] points2 = poly2.getPoints();
		if (points1.length > points2.length)
		{
			Point2D [] swapPts = points1;   points1 = points2;   points2 = swapPts;
			PolyBase swapPoly = poly1;   poly1 = poly2;   poly2 = swapPoly;
		}

		// check every vertex in poly1 to see if any are in poly2
		for(int i=0; i getNetsThatTouch(PolyBase poly, Cell newCell, boolean justExtend)
	{
		// scale the polygon back
		Point2D [] points = poly.getPoints();
		PolyBase.Point [] newPoints = new PolyBase.Point[points.length];
		for(int i=0; i netsThatTouch = new TreeMap();

		// find nodes that touch
		Rectangle2D bounds = newPoly.getBounds2D();
		Point2D centerPoint = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
		Netlist nl = newCell.getNetlist();
		for(Iterator it = newCell.searchIterator(bounds); it.hasNext(); )
		{
			Geometric geom = it.next();
			if (!(geom instanceof NodeInst)) continue;
			NodeInst ni = (NodeInst)geom;
			if (ni.isCellInstance()) continue;
			FixpTransform trans = ni.rotateOut();
			Technology tech = ni.getProto().getTechnology();
			Poly [] nodePolys = tech.getShapeOfNode(ni, true, true, null);
			for(int i=0; i 2 || (numNets > 1 && justExtend)) return null;
						break;
					}
				}
			}
		}

		// find arcs that touch (only include if no nodes are on the network)
		for(Iterator it = newCell.searchIterator(bounds); it.hasNext(); )
		{
			Geometric geom = it.next();
			if (!(geom instanceof ArcInst)) continue;
			ArcInst ai = (ArcInst)geom;
			Technology tech = ai.getProto().getTechnology();
			Poly [] polys = tech.getShapeOfArc(ai);
			for(int i=0; i 2 || (numNets > 1 && justExtend)) return null;
						break;
					}
				}
			}
		}
		return netsThatTouch;
	}

	/********************************************** MISCELLANEOUS EXTRACTION HELPERS **********************************************/

	/**
	 * Method to find a list of centerlines that skeletonize a polygon.
	 * @param poly the Poly to skeletonize.
	 * @param layer the layer on which the polygon resides.
	 * @param minWidth the minimum width of geometry on the layer.
	 * @param merge
	 * @param originalMerge
	 * @return a List of Centerline objects that describe a single "bone" of the skeleton.
	 */
	private List findCenterlines(PolyBase poly, Layer layer, double minWidth, PolyMerge merge, PolyMerge originalMerge)
	{
		// the list of centerlines
		List validCenterlines = new ArrayList();

		// make a layer that describes the polygon
		merge.deleteLayer(tempLayer1);
		merge.addPolygon(tempLayer1, poly);

		List polysToAnalyze = new ArrayList();
		polysToAnalyze.add(poly);
		for(int loop=1; ; loop++)
		{
			// decompose all polygons
			boolean foundNew = false;
			for(PolyBase aPoly : polysToAnalyze)
			{
				// first make a list of all parallel wires in the polygon
				aPoly.setLayer(layer);
				List centerlines = gatherCenterlines(aPoly, merge, originalMerge);
				if (centerlines == null)
				{
					merge.subtract(tempLayer1, aPoly);
					continue;
				}

				// now pull out the relevant ones
				double lastWidth = -1;
				boolean lastWidthNonManhattan = false;
				for(Centerline cl : centerlines)
				{
					if (cl.width < minWidth) continue;

					if (alignment != null)
					{
/*
                        if (cl.start.getX() == cl.end.getX())
						{
							// vertical line: align in X
							if (alignment.getWidth() > 0)
							{
								double aliX = scaleUp(alignment.getWidth());
								double newX = Math.round(cl.start.getX() / aliX) * aliX;
								cl.width -= Math.abs(newX - cl.start.getX());
								cl.width = Math.floor(cl.width / aliX) * aliX;
								cl.setStart(newX, cl.start.getY());
								cl.setEnd(newX, cl.end.getY());
							}
						} else if (cl.start.getY() == cl.end.getY())
						{
							// horizontal line: align in Y
							if (alignment.getHeight() > 0)
							{
								double aliY = scaleUp(alignment.getHeight());
								double newY = Math.round(cl.start.getY() / aliY) * aliY;
								cl.width -= Math.abs(newY - cl.start.getY());
								cl.width = Math.floor(cl.width / aliY) * aliY;
								cl.setStart(cl.start.getX(), newY);
								cl.setEnd(cl.end.getX(), newY);
							}
						}
*/
					}

					// make the polygon to describe the centerline
					double length = cl.start.distance(cl.end);
					if (length < DBMath.getEpsilon()) continue;
					Poly clPoly = Poly.makeEndPointPoly(length, cl.width, cl.angle,
						cl.start, 0, cl.end, 0, Poly.Type.FILLED);

					// see if this centerline actually covers new area
					if (!merge.intersects(tempLayer1, clPoly)) continue;

					// if wider than long, this cannot be the first centerline
					if (validCenterlines.size() == 0)
					{
						//double len = cl.start.distance(cl.end);
						//if (cl.width > len) continue;
					}

					// for non-Manhattan centerlines, do extra work to ensure uniqueness
					boolean isNonManhattan = false;
					if (cl.startUnscaled.getX() != cl.endUnscaled.getX() &&
						cl.startUnscaled.getY() != cl.endUnscaled.getY())
					{
						boolean duplicate = false;
						for(Centerline oCl : validCenterlines)
						{
							if (cl.startUnscaled.equals(oCl.startUnscaled) && cl.endUnscaled.equals(oCl.endUnscaled))
							{
								duplicate = true;   break;
							}
							if (cl.startUnscaled.equals(oCl.endUnscaled) && cl.endUnscaled.equals(oCl.startUnscaled))
							{
								duplicate = true;   break;
							}
						}
						if (duplicate) continue;
						isNonManhattan = true;
					}

					// if narrower centerlines have already been added, stop now
					if (lastWidth < 0)
					{
						lastWidth = cl.width;
						lastWidthNonManhattan = isNonManhattan;
					}
					if (Math.abs(cl.width - lastWidth) > 1)
					{
						if (lastWidthNonManhattan != isNonManhattan) break;
						double smallest = Math.min(cl.width, lastWidth);
						if (smallest != 0 && Math.max(cl.width, lastWidth) / smallest > 1.2) break;
					}

					// add this to the list of valid centerlines
					validCenterlines.add(cl);
					merge.subtract(tempLayer1, clPoly);
					foundNew = true;
				}
			}
			if (!foundNew) break;

			// now analyze the remaining geometry in the polygon
			polysToAnalyze = getMergePolys(merge, tempLayer1, null);
			if (polysToAnalyze == null) break;
		}
		merge.deleteLayer(tempLayer1);

		if (DEBUGCENTERLINES)
		{
			System.out.println("MERGED:");
			for(int i=0; i extraCenterlines = new ArrayList();

        // now extend centerlines so they meet
		Centerline [] both = new Centerline[2];
		for(int i=0; i maxCLX || maxOCLX < minCLX || minOCLY > maxCLY || maxOCLY < minCLY) continue;

				Point2D intersect = GenMath.intersect(cl.start, cl.angle, oCl.start, oCl.angle);
				if (intersect == null) continue;
				both[0] = cl;   both[1] = oCl;
				for(int b=0; b<2; b++)
				{
					Point2D newStart = both[b].start, newEnd = both[b].end;
					double distToStart = newStart.distance(intersect);
					double distToEnd = newEnd.distance(intersect);

					// see if intersection is deeply inside of the segment
					boolean makeT = insideSegment(newStart, newEnd, intersect);
					if (makeT)
					{
						int minDistToEnd = (int)Math.min(distToStart, distToEnd);
						if (minDistToEnd <= both[b].width/2) makeT = false;
					}

                    boolean internalIntersect = false;
                    Line2D lineSeg = new Line2D.Double(newStart, newEnd);
                    if (lineSeg.ptSegDist(intersect) == 0)
                        internalIntersect = true;

                    // if intersection is off-grid, rather than having two arcs join at this point,
                    // which can cause off-grid errors, make a pure layer node that is the intersection area,
                    // and connect to that on the edges. The important difference is a pure-layer node has
                    // the entire node as the port, rather than an arc pin, so the connection points can be on grid.
                    boolean offgrid = false;
                    if (alignment != null && alignment.getWidth() > 0 && (intersect.getX() % scaleUp(alignment.getWidth()) != 0)) offgrid = true;
                    if (alignment != null && alignment.getHeight() > 0 && (intersect.getY() % scaleUp(alignment.getHeight()) != 0)) offgrid = true;

					// adjust the centerline to end at the intersection point
					double extendStart = 0, extendEnd = 0, extendAltStart = 0, extendAltEnd = 0, betterExtension = 0;
					Point2D altNewStart = new Point2D.Double(0,0), altNewEnd = new Point2D.Double(0,0);
					if (distToStart < distToEnd)
					{
						betterExtension = newStart.distance(intersect);
						altNewStart.setLocation(newStart);   altNewEnd.setLocation(intersect);
						newStart = intersect;
						extendAltEnd = extendStart = both[b].width / 2;
                        if (offgrid && !makeT) {
                            if (internalIntersect) {
                                // adjust closer end point out of intersection area
                                double diffX = altNewStart.getX() - intersect.getX();
                                double diffY = altNewStart.getY() - intersect.getY();
                                newStart = new Point2D.Double(intersect.getX() - diffX, intersect.getY() - diffY);
                                altNewEnd.setLocation(intersect.getX() + diffX, intersect.getY() + diffY);
                            } else {
                                newStart.setLocation(altNewStart);
                            }
                            extendAltEnd = extendStart = Math.abs(betterExtension);
                        }
                        else if (!makeT && betterExtension < extendStart) {
                            // wire will not have ends extended, add in extra wire to account for non-end extension
                            Centerline newCl = new Centerline(both[b].width, altNewStart, intersect);
                            newCl.startHub = false;
                            newCl.endHub = true;
                            if (newCl.start.distance(newCl.end) > 0)
                                extraCenterlines.add(newCl);
                        }
                    } else // case: distToEnd <= distToStart
					{
						betterExtension = newEnd.distance(intersect);
						altNewStart.setLocation(intersect);   altNewEnd.setLocation(newEnd);
						newEnd = intersect;
						extendAltStart = extendEnd = both[b].width / 2;
                        if (offgrid && !makeT) {
                            if (internalIntersect) {
                                double diffX = altNewEnd.getX() - intersect.getX();
                                double diffY = altNewEnd.getY() - intersect.getY();
                                newEnd = new Point2D.Double(intersect.getX() - diffX, intersect.getY() - diffY);
                                altNewStart.setLocation(intersect.getX() + diffX, intersect.getY() + diffY);
                            } else {
                                newEnd.setLocation(altNewEnd);
                            }
                            extendAltStart = extendEnd = Math.abs(betterExtension);
                        }
                        else if (!makeT && betterExtension < extendEnd) {
                            // wire will not have ends extended, add in extra wire to account for non-end extension
                            Centerline newCl = new Centerline(both[b].width, intersect, altNewEnd);
                            newCl.startHub = true;
                            newCl.endHub = false;
                            if (newCl.start.distance(newCl.end) > 0)
                                extraCenterlines.add(newCl);
                        }
					}
					Poly extended = Poly.makeEndPointPoly(newStart.distance(newEnd), both[b].width, both[b].angle,
						newStart, extendStart, newEnd, extendEnd, Poly.Type.FILLED);
					if (!originalMerge.contains(layer, extended))
					{
						if (extendStart > 0) extendStart = betterExtension;
						if (extendEnd > 0) extendEnd = betterExtension;
						extended = Poly.makeEndPointPoly(newStart.distance(newEnd), both[b].width, both[b].angle,
							newStart, extendStart, newEnd, extendEnd, Poly.Type.FILLED);
                    }
					if (originalMerge.contains(layer, extended))
					{
						both[b].setStart(newStart.getX(), newStart.getY());
						both[b].setEnd(newEnd.getX(), newEnd.getY());
						if (extendStart != 0) both[b].startHub = true;
						if (extendEnd != 0) both[b].endHub = true;
						if (makeT)
						{
							// too much shrinkage: split the centerline
							Centerline newCL = new Centerline(both[b].width, altNewStart, altNewEnd);
							if (extendAltStart != 0) newCL.startHub = true;
							if (extendAltEnd != 0) newCL.endHub = true;
							validCenterlines.add(newCL);
							continue;
						}
					} else {
                        //System.out.println("Merge does not contain arc");
                    }
				}
			}
        }
        validCenterlines.addAll(extraCenterlines);

        if (DEBUGCENTERLINES)
		{
			System.out.println("FINAL: ");
			for(Centerline cl : validCenterlines) System.out.println("    "+cl);
		}
		return validCenterlines;
	}

	/**
	 * Method to tell whether a point is inside of a line segment.
	 * It is assumed that the point is on the line defined by the segment.
	 * @param start one point on the segment.
	 * @param end another point on the segment.
	 * @param pt the point in question.
	 * @return true if the point in question is inside of the line segment.
	 */
	private boolean insideSegment(Point2D start, Point2D end, Point2D pt)
	{
		if (pt.getX() < Math.min(start.getX(),end.getX()) || pt.getX() > Math.max(start.getX(),end.getX()) ||
			pt.getY() < Math.min(start.getY(),end.getY()) || pt.getY() > Math.max(start.getY(),end.getY()))
				return false;
		return true;
	}

	/**
	 * Method to gather all of the Centerlines in a polygon.
	 * @param poly the Poly to analyze.
	 * @param merge the merge at this point (with extracted geometry removed).
	 * @param originalMerge the original collection of geometry.
	 * @return a List of Centerlines in the polygon.
	 */
	private List gatherCenterlines(PolyBase poly, PolyMerge merge, PolyMerge originalMerge)
	{
		// first make a list of all parallel wires in the polygon
		List centerlines = new ArrayList();
		Point2D [] points = poly.getPoints();
		if (DEBUGCENTERLINES)
		{
			System.out.print("POLYGON ON LAYER "+poly.getLayer().getName()+":");
			for(int i=0; i> linesAtAngle = new TreeMap>();
		for(int i=0; i= 1800) angle -= 1800;
			Integer iAngle = new Integer(angle);
			List linesSoFar = linesAtAngle.get(iAngle);
			if (linesSoFar == null)
			{
				linesSoFar = new ArrayList();
				linesAtAngle.put(iAngle, linesSoFar);
			}
			linesSoFar.add(new Integer(i));
		}

		// now see how many holes there are (by counting colinear segments)
		int colinearSegs = 0;
		for(Integer iAangle : linesAtAngle.keySet())
		{
			// get list of all line segments that are parallel to each other
			List linesAtThisAngle = linesAtAngle.get(iAangle);
			if (linesAtThisAngle == null) continue;
			for(int ai=0; ai points.length;
//System.out.println("   so have "+points.length+" points with "+colinearSegs+" colinear segments"+ (tooManyHoles?" TOO MANY":""));
//		if (tooManyHoles) return null;

		// preallocate
		Point2D [] corners = new Point2D[4];
		corners[0] = new Point2D.Double(0, 0);
		corners[1] = new Point2D.Double(0, 0);
		corners[2] = new Point2D.Double(0, 0);
		corners[3] = new Point2D.Double(0, 0);
		Point2D [] possibleStart = new Point2D[4];
		Point2D [] possibleEnd = new Point2D[4];

		// now scan all sets of parallel lines
		for(Integer iAangle : linesAtAngle.keySet())
		{
			// get list of all line segments that are parallel to each other
			List linesAtThisAngle = linesAtAngle.get(iAangle);
			if (linesAtThisAngle == null) continue;
			int angle = iAangle.intValue();
			for(int ai=0; ai= Math.max(oLastDist, oThisDist) ||
						Math.min(oLastDist, oThisDist) >= Math.max(lastDist, thisDist)) continue;
					if (DEBUGCENTERLINES)
						System.out.println("PARALLEL LINES ("+TextUtils.formatDouble(lastPt.getX()/SCALEFACTOR)+","+TextUtils.formatDouble(lastPt.getY()/SCALEFACTOR)+
							") to ("+TextUtils.formatDouble(thisPt.getX()/SCALEFACTOR)+","+TextUtils.formatDouble(thisPt.getY()/SCALEFACTOR)+")"+
							" and ("+TextUtils.formatDouble(oLastPt.getX()/SCALEFACTOR)+","+TextUtils.formatDouble(oLastPt.getY()/SCALEFACTOR)+
							") to ("+TextUtils.formatDouble(oThisPt.getX()/SCALEFACTOR)+","+TextUtils.formatDouble(oThisPt.getY()/SCALEFACTOR)+")");

					// find the overlap
					if (lastDist > thisDist)
					{
						double swap = lastDist;    lastDist = thisDist;   thisDist = swap;
						Point2D swapPt = lastPtCL; lastPtCL = thisPtCL;   thisPtCL = swapPt;
					}
					if (oLastDist > oThisDist)
					{
						double swap = oLastDist;    oLastDist = oThisDist;   oThisDist = swap;
						Point2D swapPt = oLastPtCL; oLastPtCL = oThisPtCL;   oThisPtCL = swapPt;
					}
					Point2D start, finish, oStart, oFinish;
					if (lastDist < oLastDist)
					{
						start = oLastPtCL;
						oStart = lastPtCL;
					} else
					{
						start = lastPtCL;
						oStart = oLastPtCL;
					}
					if (thisDist > oThisDist)
					{
						finish = oThisPtCL;
						oFinish = thisPtCL;
					} else
					{
						finish = thisPtCL;
						oFinish = oThisPtCL;
					}

					// make a list of the centerline extent possibilities
					possibleStart[0] = oStart;   possibleEnd[0] = oFinish;
					if (start.distance(oStart) < finish.distance(oFinish))
					{
						possibleStart[1] = oStart;   possibleEnd[1] = finish;
						possibleStart[2] = start;   possibleEnd[2] = oFinish;
					} else
					{
						possibleStart[1] = start;   possibleEnd[1] = oFinish;
						possibleStart[2] = oStart;   possibleEnd[2] = finish;
					}
					possibleStart[3] = start;   possibleEnd[3] = finish;

					// try all possible spans
					double width = oneSide.distance(otherSide);
					for(int p=0; p<4; p++)
					{
						double length = possibleStart[p].distance(possibleEnd[p]);
						Poly clPoly = Poly.makeEndPointPoly(length, width, angle,
							possibleStart[p], 0, possibleEnd[p], 0, Poly.Type.FILLED);
						if (originalMerge.contains(poly.getLayer(), clPoly))
						{
							// if the width is much greater than the length, rotate the centerline 90 degrees
/*
							if (width > length*2)
							{
								Point2D [] pts = clPoly.getPoints();
								Point2D [] edgeCtrs = new Point2D[pts.length];
								double bestDist = Double.MAX_VALUE;
								int bestPt = -1;
								for(int e=0; e peX) psX = Math.floor(psX / xGrid) * xGrid; else
									psX = Math.ceil(psX / xGrid) * xGrid;
							}
							if (!isOnGrid(psY, yGrid))
							{
								if (psY > peY) psY = Math.floor(psY / yGrid) * yGrid; else
									psY = Math.ceil(psY / yGrid) * yGrid;
							}
							if (!isOnGrid(peX, xGrid))
							{
								if (peX > psX) peX = Math.floor(peX / xGrid) * xGrid; else
									peX = Math.ceil(peX / xGrid) * xGrid;
							}
							if (!isOnGrid(peY, yGrid))
							{
								if (peY > psY) peY = Math.floor(peY / yGrid) * yGrid; else
									peY = Math.ceil(peY / yGrid) * yGrid;
							}
*/

							// create the centerline
							Point2D ps = new Point2D.Double(psX, psY);
							Point2D pe = new Point2D.Double(peX, peY);
							Centerline newCL = new Centerline(width, ps, pe);
                            if (newCL.angle >= 0) {
                                // check for redundant centerlines, favor centerlines that extend to external geometry
                                boolean comparisonDone = false;
                                for (int ci=0; ci < centerlines.size(); ci++) {
                                    Centerline acl = centerlines.get(ci);
                                    // same bounds
                                    if (acl.getBounds().equals(newCL.getBounds())) {

                                        // check if they are perpendicular - if not, they are exactly redundant
                                        Centerline horCL = null, verCL = null;
                                        if (acl.start.getX() == acl.end.getX()) verCL = acl;
                                        if (acl.start.getY() == acl.end.getY()) horCL = acl;
                                        if (newCL.start.getX() == newCL.end.getX()) verCL = newCL;
                                        if (newCL.start.getY() == newCL.end.getY()) horCL = newCL;
                                        if (horCL == null || verCL == null) {
                                            comparisonDone = true;
                                            break; // not perpendicular
                                        }

                                        Rectangle2D bounds = acl.getBounds();
                                        int favorHor = 0, favorVer = 0;

                                        // check horizontal extensions
                                        Rectangle2D boundsE = new Rectangle2D.Double(bounds.getX(), bounds.getY(),
                                                bounds.getWidth()+1.0/SCALEFACTOR, bounds.getHeight());
                                        Rectangle2D boundsW = new Rectangle2D.Double(bounds.getX()-1.0/SCALEFACTOR, bounds.getY(),
                                                bounds.getWidth()+1.0/SCALEFACTOR, bounds.getHeight());
                                        if (originalMerge.contains(poly.getLayer(), new PolyBase(boundsE)))
                                            favorHor++;
                                        if (originalMerge.contains(poly.getLayer(), new PolyBase(boundsW)))
                                            favorHor++;

                                        // check vertical extensions
                                        Rectangle2D boundsN = new Rectangle2D.Double(bounds.getX(), bounds.getY(),
                                                bounds.getWidth(), bounds.getHeight()+1.0/SCALEFACTOR);
                                        Rectangle2D boundsS = new Rectangle2D.Double(bounds.getX(), bounds.getY()-1.0/SCALEFACTOR,
                                                bounds.getWidth(), bounds.getHeight()+1.0/SCALEFACTOR);
                                        if (originalMerge.contains(poly.getLayer(), new PolyBase(boundsN)))
                                            favorVer++;
                                        if (originalMerge.contains(poly.getLayer(), new PolyBase(boundsS)))
                                            favorVer++;

                                        if (favorHor > favorVer) {
                                            if (centerlines.contains(verCL)) {
                                                if (DEBUGCENTERLINES)
                                                    System.out.println("***REMOVE "+verCL.toString()+" WHICH IS SUBOPTIMAL");
                                                centerlines.remove(verCL);
                                            }
                                            if (!centerlines.contains(horCL)) {
                                                if (DEBUGCENTERLINES) System.out.println("  MAKE "+newCL.toString());
                                                centerlines.add(horCL);
                                            }
                                            comparisonDone = true;
                                        }
                                        if (favorVer > favorHor) {
                                            if (centerlines.contains(horCL)) {
                                                if (DEBUGCENTERLINES)
                                                    System.out.println("***REMOVE "+horCL.toString()+" WHICH IS SUBOPTIMAL");
                                                centerlines.remove(horCL);
                                            }
                                            if (!centerlines.contains(verCL)) {
                                                if (DEBUGCENTERLINES) System.out.println("  MAKE "+newCL.toString());
                                                centerlines.add(verCL);
                                            }
                                            comparisonDone = true;
                                        }
                                        break;
                                    }
                                }
                                if (!comparisonDone) {
                                    centerlines.add(newCL);
                                    if (DEBUGCENTERLINES) System.out.println("  MAKE "+newCL.toString());
                                }
                            }
							break;
						}
					}
				}
			}
		}

		// sort the parallel wires by length
		Collections.sort(centerlines, new ParallelWiresByLength());
		if (DEBUGCENTERLINES)
		{
			System.out.println("SORTED BY LENGTH:");
			for(Centerline cl : centerlines)
				System.out.println("    "+cl.toString());
		}

		// remove redundant centerlines
		PolyMerge reCheck = new PolyMerge();
		for(int i=0; i
	{
		public int compare(Centerline cl1, Centerline cl2)
		{
			if (cl1.width < cl2.width) return 1;
			if (cl1.width > cl2.width) return -1;
			double cll1 = cl1.start.distance(cl1.end);
			double cll2 = cl2.start.distance(cl2.end);
			if (cll1 > cll2) return -1;
			if (cll1 < cll2) return 1;
			return 0;
		}
	}

	/**
	 * Class to sort Centerline objects by their length (and within that, by their width).
	 */
	private static class ParallelWiresByLength implements Comparator
	{
		public int compare(Centerline cl1, Centerline cl2)
		{
			double cll1 = cl1.start.distance(cl1.end);
			double cll2 = cl2.start.distance(cl2.end);
			if (cll1 > cll2) return -1;
			if (cll1 < cll2) return 1;
			if (cl1.width < cl2.width) return -1;
			if (cl1.width > cl2.width) return 1;
			return 0;
		}
	}

	/**
	 * Method to scan the geometric information and convert it all to pure layer nodes in the new cell.
	 * When all other extractions are done, this is done to preserve any geometry that is unaccounted-for.
	 */
	private void convertAllGeometry(PolyMerge merge, PolyMerge originalMerge, Cell newCell, boolean usePureLayerNodes)
	{
		MutableInteger numIgnored = new MutableInteger(0);
		for (Layer layer : merge.getKeySet())
		{
			ArcProto ap = arcsForLayer.get(layer);
			List polyList = getMergePolys(merge, layer, numIgnored);

            if (layer.getFunction().isSubstrate() || layer.getFunction().isImplant()) {
                // just dump back in original layer, as it doesn't connect to anything anyway
                polyList = new ArrayList(originalMerge.getObjects(layer, false, false));
            }

            // implant layers may be large rectangles
            /*
            if (layer.getFunction().isSubstrate())
			{
				// make a list of rectangles that are proper approximations of polygons
				List rectangleList = new ArrayList();
				for(int i=polyList.size()-1; i >= 0; i--)
				{
					PolyBase poly = polyList.get(i);
					Rectangle2D polyBounds = poly.getBounds2D();
					if (originalMerge.contains(layer, polyBounds))
					{
						rectangleList.add(polyBounds);
						polyList.remove(i);
					}
				}

				// iterate over the list of rectangles and aggregate into larger rectangles
				for(;;)
				{
					Rectangle2D aggregateBounds = null;
					List aggregatedList = new ArrayList();
					for(Rectangle2D polyBounds : rectangleList)
					{
						if (aggregateBounds == null) aggregateBounds = polyBounds; else
						{
							Rectangle2D unionBounds = new Rectangle2D.Double();
							Rectangle2D.union(aggregateBounds, polyBounds, unionBounds);
							if (!originalMerge.contains(layer, unionBounds)) continue;
							aggregateBounds = unionBounds;
						}
						aggregatedList.add(polyBounds);
					}
					if (aggregateBounds == null) break;

					// valid rectangle: cover it all with a large pure layer rectangle
					PrimitiveNode pNp = layer.getPureLayerNode();
					double centerX = aggregateBounds.getCenterX() / SCALEFACTOR;
					double centerY = aggregateBounds.getCenterY() / SCALEFACTOR;
					Point2D center = new Point2D.Double(centerX, centerY);
					createNode(pNp, center, aggregateBounds.getWidth() / SCALEFACTOR,
						aggregateBounds.getHeight() / SCALEFACTOR, null, newCell);
					for(Rectangle2D polyBounds : aggregatedList)
						rectangleList.remove(polyBounds);
				}
			}
			*/

            for(PolyBase poly : polyList)
			{
                if (usePureLayerNodes) ap = null;
				// special case: a rectangle on a routable layer: make it an arc
                if (ap != null)
				{
					Rectangle2D polyBounds = poly.getBox();
					if (polyBounds == null)
					{
						Rectangle2D totalBounds = poly.getBounds2D();
						if (originalMerge.contains(layer, totalBounds))
							polyBounds = totalBounds;
					}
					if (polyBounds != null)
					{
						double width = polyBounds.getWidth();
						double height = polyBounds.getHeight();
						double actualWidth = 0;
						if (ENFORCEMINIMUMSIZE) actualWidth = ap.getDefaultLambdaBaseWidth(ep) * SCALEFACTOR;
						if (width >= actualWidth && height >= actualWidth)
						{
							PrimitiveNode np = ap.findPinProto();
							Point2D end1 = null, end2 = null;
							double size = 0;
							if (width > height)
							{
								// make a horizontal arc
								end1 = new Point2D.Double((polyBounds.getMinX()), polyBounds.getCenterY());
								end2 = new Point2D.Double((polyBounds.getMaxX()), polyBounds.getCenterY());
								size = height;
							} else
							{
								// make a vertical arc
								end1 = new Point2D.Double(polyBounds.getCenterX(), (polyBounds.getMinY()));
								end2 = new Point2D.Double(polyBounds.getCenterX(), (polyBounds.getMaxY()));
								size = width;
							}
							MutableBoolean headExtend = new MutableBoolean(false), tailExtend = new MutableBoolean(false);
							if (originalMerge.arcPolyFits(layer, end1, end2, size, headExtend, tailExtend))
							{
                                // scale everything
                                end1 = new Point2D.Double(end1.getX() / SCALEFACTOR, end1.getY() / SCALEFACTOR);
                                end2 = new Point2D.Double(end2.getX() / SCALEFACTOR, end2.getY() / SCALEFACTOR);
                                size = size / SCALEFACTOR;
                                // make arc
                                NodeInst ni1 = createNode(np, end1, size, size, null, newCell);
								NodeInst ni2 = createNode(np, end2, size, size, null, newCell);
								realizeArc(ap, ni1.getOnlyPortInst(), ni2.getOnlyPortInst(), end1, end2,
									size, !headExtend.booleanValue(), !tailExtend.booleanValue(), usePureLayerNodes, merge);
							} else {
                                System.out.println("Arc "+layer.getName()+" did not fit at "+polyBounds.getMinX()/SCALEFACTOR+","+polyBounds.getMinY()/SCALEFACTOR);
                            }
							continue;
						}
					}
				}

				// just generate a pure-layer node
				List niList = makePureLayerNodeFromPoly(poly, newCell, originalMerge);
                // make well or implant hard to select
                if (poly.getLayer().getFunction().isSubstrate() || poly.getLayer().getFunction().isImplant()) {
                    for (NodeInst ni : niList) ni.setHardSelect();
                }

                if (niList == null) continue;

				// connect to the rest if possible
				if (ap != null) for(NodeInst ni : niList)
				{
					PortInst fPi = ni.getOnlyPortInst();
					Poly fPortPoly = fPi.getPoly();
					Rectangle2D polyBounds = poly.getBounds2D();
					Rectangle2D searchBound = new Rectangle2D.Double(polyBounds.getMinX()/SCALEFACTOR, polyBounds.getMinY()/SCALEFACTOR,
						polyBounds.getWidth()/SCALEFACTOR, polyBounds.getHeight()/SCALEFACTOR);
					PortInst bestTPi = null;
					double bestLen = Double.MAX_VALUE;
					Point2D bestFLoc = null;
					for(Iterator it = newCell.searchIterator(searchBound); it.hasNext(); )
					{
						Geometric geom = it.next();
						if (!(geom instanceof NodeInst)) continue;
						NodeInst oNi = (NodeInst)geom;
						if (oNi == ni) continue;
						if (oNi.isCellInstance()) continue;
						if (oNi.getProto().getTechnology() != tech) continue;
						for(Iterator pIt = oNi.getPortInsts(); pIt.hasNext(); )
						{
							PortInst tPi = pIt.next();
							PortProto pp = tPi.getPortProto();
							if (!pp.connectsTo(ap)) continue;
							EPoint tLoc = tPi.getPoly().getCenter();
							EPoint tLocScaled = new EPoint(scaleUp(tLoc.getX()), scaleUp(tLoc.getY()));
							Point2D fLoc = fPortPoly.closestPoint(tLoc);
							EPoint fLocScaled = new EPoint(scaleUp(fLoc.getX()), scaleUp(fLoc.getY()));
							double len = scaleUp(fLoc.distance(tLoc));
							int angle = GenMath.figureAngle(tLoc, fLoc);
							Poly conPoly = Poly.makeEndPointPoly(len, 1, angle, fLocScaled, 0, tLocScaled, 0, Poly.Type.FILLED);
							if (originalMerge.contains(layer, conPoly))
							{
								if (len < bestLen)
								{
									bestLen = len;
									bestTPi = tPi;
									bestFLoc = fLoc;
								}
							}
						}
					}
					if (bestTPi != null)
					{
						Poly tPortPoly = bestTPi.getPoly();
						Point2D tLoc = tPortPoly.closestPoint(tPortPoly.getCenter());
						ArcInst ai = realizeArc(ap, fPi, bestTPi, bestFLoc, tLoc, 0, false, false, usePureLayerNodes, merge);
						if (ai != null) ai.setFixedAngle(false);
					} else
					{
						// Approximating the error with center
						addErrorLog(newCell, "Unable to connect unextracted polygon",
							new EPoint(searchBound.getCenterX(), searchBound.getCenterY()));
					}
				}
			}
		}

		// also throw in unextracted contact cuts
		for (Layer layer : allCutLayers.keySet())
		{
			CutInfo cInfo = allCutLayers.get(layer);
			for(PolyBase poly : cInfo.getCuts())
				makePureLayerNodeFromPoly(poly, newCell, originalMerge);
		}

		if (numIgnored.intValue() > 0)
			System.out.println("WARNING: Ignored " + numIgnored.intValue() +
				" tiny polygons (use Network Preferences to control tiny polygon limit)");
	}

    /**
     * Method to add back in original routing layers as pure layer nodes
     * @param oldCell the original imported cell
     * @param newCell the new cell
     * @param merge the remaining layers to create
     * @param originalMerge the original set of layers
     */
    private void addInRoutingLayers(Cell oldCell, Cell newCell, PolyMerge merge, PolyMerge originalMerge, boolean usePureLayerNodes)
    {
        Map> newNodes = new HashMap>();

        // create new nodes as copy of old nodes
        for (Iterator nit = oldCell.getNodes(); nit.hasNext(); )
        {
            NodeInst ni = nit.next();
            NodeProto np = ni.getProto();
            if (!(np instanceof PrimitiveNode)) continue;
            PrimitiveNode pn = (PrimitiveNode)np;
            if (pn.getFunction() == PrimitiveNode.Function.NODE)
            {
                Layer layer = pn.getLayerIterator().next();
                Layer.Function fun = layer.getFunction();
                if (fun.isPoly() || fun.isMetal() || fun.isDiff())
                {
                    List newNis = new ArrayList();
                    // create same node in new cell
                    if (ni.getTrace() != null && ni.getTrace().length > 0)
                    {
                        EPoint [] origPoints = ni.getTrace();
                        PolyBase.Point [] points = new PolyBase.Point[origPoints.length];
                        // for some reason getTrace returns points relative to center, but setTrace expects absolute coordinates
                        for (int i=0; i set = ((PolySweepMerge)thisMerge).getPolyPartition(layer);
                            for(PolyBase simplePoly : set)
                            {
                                PolyBase simplePolyScaledUp = scaleUpPoly(simplePoly);
                                pn = getActiveNodeType(layer, simplePolyScaledUp, originalMerge, pn);
                                layer = pn.getLayerIterator().next();Rectangle2D polyBounds = simplePoly.getBounds2D();
                                NodeInst newNi = NodeInst.makeInstance(pn, ep, simplePoly.getCenter(), polyBounds.getWidth(), polyBounds.getHeight(),
                                                                       newCell);
                                if (newNi == null) continue;
                                newNis.add(newNi);
                            }
                        } else {
                            PolyBase polyScaledUp = scaleUpPoly(poly);
                            pn = getActiveNodeType(layer, polyScaledUp, originalMerge, pn);
                            NodeInst newNi = NodeInst.makeInstance(pn, ep, ni.getAnchorCenter(), ni.getXSize(), ni.getYSize(), newCell);
                            if (newNi != null) {
                                newNis.add(newNi);
                                newNi.setTrace(points);
                            }
                        }
                    } else {
                        PolyBase poly = new PolyBase(ni.getBounds());
                        PolyBase polyScaledUp = scaleUpPoly(poly);
                        pn = getActiveNodeType(layer, polyScaledUp, originalMerge, pn);
                        NodeInst newNi = NodeInst.makeInstance(pn, ep, ni.getAnchorCenter(), ni.getXSize(), ni.getYSize(),
                                                               newCell, ni.getOrient(), null);
                        if (newNi != null)
                            newNis.add(newNi);
                    }
                    // subtract out layers
                    layer = pn.getLayerIterator().next();
                    for (NodeInst newNi : newNis) {
                        Poly [] polys = tech.getShapeOfNode(newNi);
                        for (Poly p : polys)
                        {
                            if (p.getLayer() == layer)
                                removePolyFromMerge(merge, layer, p);
                        }
                    }
                    // add to map of created nodeinsts
                    List list = newNodes.get(layer);
                    if (list == null)
                    {
                        list = new ArrayList();
                        newNodes.put(layer, list);
                    }
                    list.addAll(newNis);
                }
            }
        }
        // now stitch new nodes together
        for (Layer layer : newNodes.keySet())
        {
            Layer.Function fun = layer.getFunction();
            ArcProto ap = Generic.tech().universal_arc;
            if (fun.isPoly() || fun.isMetal() || fun.isDiff()) ap = arcsForLayer.get(layer);

            List nodes = newNodes.get(layer);
            int i=0, j=1;
            // check nodes against each other
            for (i=0; i overlappingEdges = new ArrayList();
                    List intersection = poly1.getIntersection(poly2, overlappingEdges);

                    Point2D connectionPoint = null;
                    if (intersection.size() > 0)
                    {
                        // areas intersect, use center point of first common area
                        PolyBase pint = intersection.get(0);
                        connectionPoint = pint.getCenter();
                        // round center point
                        if (alignment != null)
                        {
                            double x = connectionPoint.getX();
                            double y = connectionPoint.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();
                            if (pint.contains(x, y))
                                connectionPoint = new Point2D.Double(x, y);
                        }
                    }
                    else if (overlappingEdges.size() > 0)
                    {
                        // areas do not intersect, but share common edges. Use center point of first edge
                        Line2D line = overlappingEdges.get(0);
                        double x = (line.getX1()+line.getX2())/2.0;
                        double y = (line.getY1()+line.getY2())/2.0;
                        if (alignment != null) {
                            double newx = x, newy = y;
                            if ((line.getY1() == line.getY2()) && !isOnGrid(x, alignment.getWidth())) {
                                newx = Math.round(x/alignment.getWidth()) * alignment.getWidth();
                            }
                            if ((line.getX1() == line.getX2()) && !isOnGrid(y, alignment.getHeight())) {
                                newy = Math.round(y/alignment.getHeight()) * alignment.getHeight();
                            }
                            if (line.ptSegDist(newx, newy) == 0) {
                                x = newx;
                                y = newy;
                            }
                        }
                        connectionPoint = new Point2D.Double(x, y);
                    }
                    if (connectionPoint != null)
                    {
                        // check on arc, we need to decide if diffusion arc is really diffusion arc or well arc
                        // scale up poly so units match originalMerge units
                        PolyBase poly1Scaled = scaleUpPoly(poly1);

                        if (fun.isDiff()) {
                            ap = findArcProtoForPoly(layer, poly1Scaled, originalMerge);
                        }
                        if (ap == null)
                        	ap = Generic.tech().universal_arc;

                        double width = ap.getDefaultLambdaBaseWidth(ep);
                        ArcProto arcProto = ap;
                        if (arcProto != Generic.tech().universal_arc)
                        {
                            // scale up to check merge
                            Point2D connPointScaled = new Point2D.Double(scaleUp(connectionPoint.getX()), scaleUp(connectionPoint.getY()));
                            Poly test1 = Poly.makeEndPointPoly(0.0, scaleUp(width), 0, connPointScaled, scaleUp(width/2.0), connPointScaled,
                                    scaleUp(width/2.0), Poly.Type.FILLED);
                            // if on grid and merge contains, make arc
                            if (isOnGrid(test1) && originalMerge.contains(layer, test1))
                            {
                                realizeArc(arcProto, ni1.getOnlyPortInst(), ni2.getOnlyPortInst(), connectionPoint,
                                        connectionPoint, width, false, false, usePureLayerNodes, merge);
                                continue;
                            }
                            arcProto = Generic.tech().universal_arc;
                            width = 0;
                            //System.out.println("Using universal arc to connect "+ni1.describe(false)+" at "+connectionPoint+" to "+ni2.describe(false));
                        }
                        realizeArc(arcProto, ni1.getOnlyPortInst(), ni2.getOnlyPortInst(), connectionPoint,
                                connectionPoint, width, false, false, usePureLayerNodes, merge);
                    }
                }
            }
        }
    }

    private PrimitiveNode getActiveNodeType(Layer activeLayer, PolyBase poly, PolyMerge merge, PrimitiveNode defaultNode)
    {
        if (unifyActive || ignoreActiveSelectWell) return defaultNode;
        if (defaultNode != pDiffNode && defaultNode != nDiffNode && defaultNode != diffNode) return defaultNode;

        if (activeLayer.getFunction() == Layer.Function.DIFFN) {
            // make sure n-diffusion is in n-select
            if (merge.contains(pSelectLayer, poly)) {
                // switch it p-active
                return pDiffNode;
            }
        }
        if (activeLayer.getFunction() == Layer.Function.DIFFP) {
            // make sure p-diffusion is in p-select
            if (merge.contains(nSelectLayer, poly)) {
                // switch it n-active
                return nDiffNode;
            }
        }
        return defaultNode;
    }

    private boolean isOnGrid(Poly poly)
    {
        if (alignment != null)
        {
            for (Point2D p : poly.getPoints())
            {
                if (alignment.getWidth() > 0)
                    if (!isOnGrid(p.getX(), alignment.getWidth())) return false;
                if (alignment.getHeight() > 0)
                    if (!isOnGrid(p.getY(), alignment.getHeight())) return false;
            }
        }
        return true;
    }

    /**
     * Returns a new poly that is a scaled up version of the given poly
     * @param poly the poly
     * @return a new, scaled up poly
     */
    private PolyBase scaleUpPoly(PolyBase poly) {
        PolyBase.Point [] points = new PolyBase.Point[poly.getPoints().length];
        for(int p=0; p makePureLayerNodeFromPoly(PolyBase poly, Cell cell, PolyMerge originalMerge)
	{
		Layer layer = poly.getLayer();

		// if an active layer, make sure correct N/P is used
		if (unifyActive && (layer == pActiveLayer || layer == nActiveLayer))
		{
			Rectangle2D rect = poly.getBounds2D();
			rect = new Rectangle2D.Double(rect.getMinX()/SCALEFACTOR-1, rect.getMinY()/SCALEFACTOR-1,
				rect.getWidth()/SCALEFACTOR+2, rect.getHeight()/SCALEFACTOR+2);
			int nType = 0, pType = 0;
			for(Iterator it = cell.searchIterator(rect); it.hasNext(); )
			{
				Geometric geom = it.next();
				if (geom instanceof NodeInst)
				{
					NodeInst ni = (NodeInst)geom;
					if (ni.isCellInstance()) continue;
					Poly [] polys = ni.getProto().getTechnology().getShapeOfNode(ni);
					for(int i=0; i nType) layer = realPActiveLayer; else
				layer = realNActiveLayer;
			if (layer.getPureLayerNode() == null) layer = poly.getLayer();
		}

		PrimitiveNode pNp = layer.getPureLayerNode();
		if (pNp == null)
		{
			System.out.println("CANNOT FIND PURE LAYER NODE FOR LAYER " + layer.getName());
			return null;
		}
		List createdNodes = new ArrayList();
		if (poly.getBox() == null)
		{
			// irregular shape: break it up with simpler polygon merging algorithm
			GeometryHandler thisMerge = GeometryHandler.createGeometryHandler(GeometryHandler.GHMode.ALGO_SWEEP, 1);
			thisMerge.add(layer, poly);
			thisMerge.postProcess(true);

            Collection set = ((PolySweepMerge)thisMerge).getPolyPartition(layer);
			for(PolyBase simplePoly : set)
			{
				Rectangle2D polyBounds = simplePoly.getBounds2D();
				NodeInst ni = makeAlignedPoly(polyBounds, layer, originalMerge, pNp, cell);
				if (ni == null) continue;
				createdNodes.add(ni);
			}
			return createdNodes;
		}
		Rectangle2D polyBounds = poly.getBounds2D();
		NodeInst ni = makeAlignedPoly(polyBounds, layer, originalMerge, pNp, cell);
		if (ni != null)
			createdNodes.add(ni);
//		double centerX = polyBounds.getCenterX() / SCALEFACTOR;
//		double centerY = polyBounds.getCenterY() / SCALEFACTOR;
//		Point2D center = new Point2D.Double(centerX, centerY);
//
//		// compute any trace information if the shape is non-Manhattan
//		EPoint [] newPoints = null;
////		if (poly.getBox() == null)
////		{
////			// store the trace
////			Point2D [] points = poly.getPoints();
////			newPoints = new EPoint[points.length];
////			for(int i=0; i 0)
			{
                // centers can be off-grid, only edges matter
                double aliX = Math.round(centerX / (alignment.getWidth()/2)) * (alignment.getWidth()/2);
				if (aliX != centerX)
				{
					double newWidth = width + Math.abs(aliX-centerX)*2;
					Poly rectPoly = new Poly(scaleUp(aliX), scaleUp(centerY), scaleUp(newWidth), scaleUp(height));
					if (!originalMerge.contains(layer, rectPoly))
					{
						if (aliX > centerX) aliX -= alignment.getWidth(); else
							aliX += alignment.getWidth();
						newWidth = width + Math.abs(aliX-centerX)*2;
						rectPoly = new Poly(scaleUp(aliX), scaleUp(centerY), scaleUp(newWidth), scaleUp(height));
						if (!originalMerge.contains(layer, rectPoly)) return null;
					}
					centerX = aliX;
					width = newWidth;
				}
			}
			if (alignment.getHeight() > 0)
			{
                // centers can be off-grid, only edges matter
				double aliY = Math.round(centerY / (alignment.getHeight()/2)) * (alignment.getHeight()/2);
				if (aliY != centerY)
				{
					double newHeight = height + Math.abs(aliY-centerY)*2;
					Poly rectPoly = new Poly(scaleUp(centerX), scaleUp(aliY), scaleUp(width), scaleUp(newHeight));
					if (!originalMerge.contains(layer, rectPoly))
					{
						if (aliY > centerY) aliY -= alignment.getHeight(); else
							aliY += alignment.getHeight();
						newHeight = height + Math.abs(aliY-centerY)*2;
						rectPoly = new Poly(scaleUp(centerX), scaleUp(aliY), scaleUp(width), scaleUp(newHeight));
						if (!originalMerge.contains(layer, rectPoly)) return null;
					}
					centerY = aliY;
					height = newHeight;
				}
			}
		}
		Point2D center = new Point2D.Double(centerX, centerY);
		NodeInst ni = createNode(pNp, center, width, height, null, cell);
		return ni;
	}

	/**
	 * Method to clean-up exports.
     * @param oldCell the original cell (unextracted)
     * @param newCell the new cell
	 */
	private void cleanupExports(Cell oldCell, Cell newCell)
	{
		// first restore original exports (which were on pure-layer nodes and must now be placed back)
		for(Export e : exportsToRestore)
		{
			EPoint loc = e.getPoly().getCenter();
			boolean found = false;
			Rectangle2D bounds = new Rectangle2D.Double(loc.getX(), loc.getY(), 0, 0);
			for(Iterator it = newCell.searchIterator(bounds); it.hasNext(); )
			{
				Geometric geom = it.next();
				if (!(geom instanceof NodeInst)) continue;
				NodeInst ni = (NodeInst)geom;
				for(Iterator pIt = ni.getPortInsts(); pIt.hasNext(); )
				{
					PortInst pi = pIt.next();
					PortProto pp = pi.getPortProto();
					if (!sameConnection(e, pp)) continue;
					EPoint pLoc = pi.getPoly().getCenter();
					if (loc.equals(pLoc))
					{
						Export.newInstance(newCell, pi, e.getName(), ep);
						found = true;
						break;
					}
				}
			}
			if (!found)
			{
				// did not reexport: create the pin and export...let it get auto-routed in
				PrimitiveNode pnUse = null;
				for(Iterator it = tech.getNodes(); it.hasNext(); )
				{
					PrimitiveNode pn = it.next();
					if (!pn.getFunction().isPin()) continue;
					if (!sameConnection(e, pn.getPort(0))) continue;
					pnUse = pn;
					break;
				}
				if (pnUse == null) continue;
				NodeInst ni = NodeInst.makeInstance(pnUse, ep, loc, pnUse.getDefWidth(ep), pnUse.getDefHeight(ep), newCell);
				Export.newInstance(newCell, ni.getOnlyPortInst(), e.getName(), ep);
			}
		}

		// now handle exported pins
		for(ExportedPin exportedPin : pinsForLater)
		{
			for(Iterator eIt = exportedPin.ni.getExports(); eIt.hasNext(); )
			{
				Export e = eIt.next();
				ArcProto[] possibleCons = e.getBasePort().getConnections();
				ArcProto ap = possibleCons[0];
				Layer layer = ap.getLayer(0);
				PortInst pi = makePort(newCell, layer, exportedPin.location);
				if (pi != null)
				{
					// found location of export: create export if not already done
					Export found = newCell.findExport(e.getName());
					if (found != null) continue;
				}

				// must create export stand-alone (not really a good idea, but cannot find anything to connect to it)
				double sX = exportedPin.ni.getXSize();
				double sY = exportedPin.ni.getYSize();
				Point2D instanceAnchor = new Point2D.Double(0, 0);
				exportedPin.trans.transform(exportedPin.ni.getAnchorCenter(), instanceAnchor);
				NodeInst newNi = NodeInst.makeInstance(exportedPin.ni.getProto(), ep, instanceAnchor, sX, sY, newCell);
				if (newNi == null)
				{
					addErrorLog(newCell, "Problem creating new instance of " + exportedPin.ni.getProto() + " for export",
						new EPoint(sX, sY));
					continue;
				}
				PortInst newPi = newNi.findPortInstFromProto(e.getOriginalPort().getPortProto());
				Export.newInstance(newCell, newPi, e.getName(), ep);
			}
		}

		// finally rename auto-generated export names to be more sensible
		for(Export e : generatedExports)
		{
			Cell cell = e.getParent();
			Netlist nl = cell.getNetlist();
			Network net = nl.getNetwork(e, 0);
			String exportName = null;
			for(Iterator nIt = net.getExportedNames(); nIt.hasNext(); )
			{
				String eName = nIt.next();
				if (eName.startsWith("E"))
				{
					boolean isTemp = false;
					for(Export e2 : generatedExports)
					{
						if (e2.getParent() == cell && e2.getName().equals(eName)) { isTemp = true;   break; }
					}
					if (isTemp) continue;
					exportName = eName;
					break;
				}
			}
			if (exportName != null)
			{
				exportName = ElectricObject.uniqueObjectName(exportName, cell, Export.class, true, true);
				e.rename(exportName);
			}
		}
	}

	/**
	 * Method to compare two ports to see that they connect to the same set of arcs.
	 * @param pp1 the first port.
	 * @param pp2 the second port.
	 * @return true if they connect to the same arcs.
	 */
	private boolean sameConnection(PortProto pp1, PortProto pp2)
	{
		ArcProto [] arcs1 = pp1.getBasePort().getConnections();
		ArcProto [] arcs2 = pp2.getBasePort().getConnections();
		if (arcs1 == arcs2) return true;
		boolean [] found = new boolean[arcs2.length];
		for(int i=0; i= arcs2.length) return false;
		}
		for(int j=0; j realizedNodes, boolean usePureLayerNodes)
	{
		Orientation orient = Orientation.fromAngle(angle);
		double cX = centerX / SCALEFACTOR;
		double cY = centerY / SCALEFACTOR;
		NodeInst ni = NodeInst.makeInstance(pNp, ep, new Point2D.Double(cX, cY),
			width / SCALEFACTOR, height / SCALEFACTOR, newCell, orient, null);
		if (ni == null) return;
		if (cutVariation != Technology.NodeLayer.MULTICUT_CENTERED)
			ni.newVar(NodeLayer.CUT_ALIGNMENT, Integer.valueOf(cutVariation), ep);
		if (points != null)
		{
			ni.setTrace(points);
			if (DEBUGSTEPS)
			{
				for(int i=1; i 0)
				width = Math.floor(width / alignment.getWidth()) * alignment.getWidth();
		}
		if (width == 0) ap = Generic.tech().universal_arc;
		ArcInst ai = ArcInst.makeInstanceBase(ap, ep, width, pi1, pi2, pt1, pt2, null);
		if (ai == null) return null;

		// special case: wide diffusion arcs on transistors should be shortened so they don't extend to other side
		if (ap.getFunction().isDiffusion() && ai.getLambdaBaseWidth() > ap.getDefaultLambdaBaseWidth(ep))
		{
			if (ai.getHeadPortInst().getNodeInst().getFunction().isTransistor()) noHeadExtend = true;
			if (ai.getTailPortInst().getNodeInst().getFunction().isTransistor()) noTailExtend = true;
		}

		if (noHeadExtend) ai.setHeadExtended(false);
		if (noTailExtend) ai.setTailExtended(false);
		EPoint head = ai.getHeadLocation();
		EPoint tail = ai.getTailLocation();
		if (head.getX() != tail.getX() && head.getY() != tail.getY())
			ai.setFixedAngle(false);

		// remember this arc for debugging
		if (DEBUGSTEPS)
		{
			Poly arcPoly = ai.makeLambdaPoly(ai.getGridBaseWidth(), Poly.Type.CLOSED);
			addedRectangles.add(ERectangle.fromLambda(arcPoly.getBounds2D()));
		}

		// now remove the generated layers from the Merge
		Poly [] polys = tech.getShapeOfArc(ai);
		for(int i=0; i getMergePolys(PolyMerge merge, Layer layer, MutableInteger numIgnored)
	{
		List polyList = merge.getMergedPoints(layer, true);
		if (polyList == null) return polyList;
		List properPolyList = new ArrayList();
		for(PolyBase poly : polyList)
		{
			// reduce common points
			Point2D [] origPoints = poly.getPoints();
			Point2D [] points = new Point2D[origPoints.length];
			int len = origPoints.length;
			for (int i=0; i 1 && points[0].distance(points[len-1]) < CLOSEDIST) len--;

			// ignore polygons with no size
			if (len <= 2) continue;

			// anything smaller than the minimum number of grid units is ignored
			double area = poly.getArea();
			if (area < smallestPoly)
			{
				if (numIgnored != null) numIgnored.increment();
				continue;
			}

			properPolyList.add(poly);
		}
		return properPolyList;
	}

	/**
	 * Class for showing progress of extraction in a modeless dialog
	 */
	private static class ShowExtraction extends EDialog
	{
		private List> addedBatchRectangles;
		private List> addedBatchLines;
		private List addedBatchNames;
		private int batchPosition;
		private JLabel comingUp;

		private ShowExtraction(Frame parent, List> addedBatchRectangles,
			List> addedBatchLines, List addedBatchNames)
		{
			super(parent, false);
			this.addedBatchRectangles = addedBatchRectangles;
			this.addedBatchLines = addedBatchLines;
			this.addedBatchNames = addedBatchNames;

			getContentPane().setLayout(new GridBagLayout());
			setTitle("Extraction Progress");
			setName("");
			addWindowListener(new WindowAdapter()
			{
				public void windowClosing(WindowEvent evt) { closeDialog(); }
			});

			GridBagConstraints gbc;
			comingUp = new JLabel("Next step:");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 0;
			gbc.gridwidth = 2;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 4, 4, 4);
			getContentPane().add(comingUp, gbc);

			JButton prev = new JButton("Prev");
			gbc = new GridBagConstraints();
			gbc.gridx = 0;   gbc.gridy = 1;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 4, 4, 4);
			prev.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt) { advanceDisplay(false); }
			});
			getContentPane().add(prev, gbc);

			JButton next = new JButton("Next");
			gbc = new GridBagConstraints();
			gbc.gridx = 1;   gbc.gridy = 1;
			gbc.anchor = GridBagConstraints.WEST;
			gbc.insets = new Insets(4, 4, 4, 4);
			next.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent evt) { advanceDisplay(true); }
			});
			getContentPane().add(next, gbc);

			batchPosition = -1;
			advanceDisplay(true);

			getRootPane().setDefaultButton(next);
			finishInitialization();
		}

		protected void escapePressed() { closeDialog(); }

		private void advanceDisplay(boolean forward)
		{
			if (forward)
			{
				batchPosition++;
				if (batchPosition >= addedBatchNames.size()) batchPosition = addedBatchNames.size()-1;
			} else
			{
				batchPosition--;
				if (batchPosition < 0) batchPosition = 0;
			}
			comingUp.setText("Batch " + (batchPosition+1) + ": " + addedBatchNames.get(batchPosition));
			pack();

			UserInterface ui = Job.getUserInterface();
			EditWindow_ wnd = ui.getCurrentEditWindow_();
			wnd.clearHighlighting();
			Cell cell = wnd.getCell();

			// highlight
			List rects = addedBatchRectangles.get(batchPosition);
			for(ERectangle er : rects)
				wnd.addHighlightArea(er, cell);
			List lines = addedBatchLines.get(batchPosition);
			for(ERectangle er : lines)
				wnd.addHighlightLine(new Point2D.Double(er.getMinX(), er.getMinY()),
                                     new Point2D.Double(er.getMaxX(), er.getMaxY()), cell, false, false);
			wnd.finishedHighlighting();
		}
	}
}