![JAR search and dependency download from the Maven repository](/logo.png)
com.sun.electric.tool.routing.InteractiveRouter Maven / Gradle / Ivy
/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: InteractiveRouter.java
*
* Copyright (c) 2003, 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.routing;
import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.geometry.PolyMerge;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.ElectricObject;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.SizeOffset;
import com.sun.electric.technology.technologies.Artwork;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.user.CircuitChangeJobs;
import com.sun.electric.tool.user.Highlight;
import com.sun.electric.tool.user.Highlighter;
import com.sun.electric.tool.user.ui.EditWindow;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.EDimension;
import com.sun.electric.util.math.GenMath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* An Interactive Router has several methods that build on Router
* methods to provide interactive control to user. It also
* provides methods for highlighting routes to provide visual
* feedback to the user. Finally, non-interactive routing is done only
* from PortInst to PortInst, whereas interactive routing can start and
* end on any arc, and can end in space.
*
* Note: 'Interactive' is somewhat of a misnomer, as it would imply
* the route can be incrementally built or changed by the user. In
* reality, it is expected that the route simply be rebuilt whenever
* the user input changes, until the user decides that the route is acceptable,
* at which point the route can be made.
*
* User: gainsley
*/
public abstract class InteractiveRouter extends Router {
/** for highlighting the start of the route */ private List startRouteHighlights = new ArrayList();
/** if start has been called */ private boolean started;
/** EditWindow we are routing in */ private EditWindow wnd;
/** Need fat wires */ private final EditingPreferences ep;
/** last bad object routed from: prevent too many error messages */ private ElectricObject badStartObject;
/** last bad object routing to: prevent too many error messages */ private ElectricObject badEndObject;
public InteractiveRouter(EditingPreferences ep) {
verbose = true;
started = false;
badStartObject = badEndObject = null;
wnd = null;
tool = Routing.getRoutingTool();
this.ep = ep;
}
public String toString() { return "Interactive Router"; }
protected abstract boolean planRoute(Route route, Cell cell, RouteElementPort endRE,
Point2D startLoc, Point2D endLoc, Point2D clicked, PolyMerge stayInside, VerticalRoute vroute,
boolean contactsOnEndObject, boolean extendArcHead, boolean extendArcTail, Rectangle2D contactArea);
// ----------------------- Interactive Route Control --------------------------
/**
* This stores the currently highlighted objects to highlight
* in addition to route highlighting. If routing it cancelled,
* it also restores the original highlighting.
*/
public void startInteractiveRoute(EditWindow wnd) {
this.wnd = wnd;
// copy current highlights
startRouteHighlights.clear();
for (Highlight h : wnd.getHighlighter().getHighlights()) {
startRouteHighlights.add(h);
}
wnd.clearHighlighting();
started = true;
}
/**
* Cancels interactive routing and restores original highlights
*/
public void cancelInteractiveRoute() {
// restore original highlights
Highlighter highlighter = wnd.getHighlighter();
highlighter.clear();
highlighter.setHighlightList(startRouteHighlights);
highlighter.finished();
wnd = null;
started = false;
}
/**
* Make a route between startObj and endObj in the EditWindow_.
* Uses the point where the user clicked as a parameter to set the route.
* @param wnd the EditWindow_ the user is editing
* @param cell the cell in which to create the route
* @param startObj a PortInst or ArcInst from which to start the route
* @param endObj a PortInst or ArcInst to end the route on. May be null
* if the user is drawing to empty space.
* @param clicked the point where the user clicked
*/
public void makeRoute(EditWindow wnd, Cell cell, ElectricObject startObj, ElectricObject endObj, Point2D clicked) {
if (!started) startInteractiveRoute(wnd);
// plan the route
EditingPreferences ep = wnd.getEditingPreferences();
Route route = planRoute(cell, startObj, endObj, clicked, null, ep, true, true, null, null);
// restore highlights at start of planning, so that
// they will correctly show up if this job is undone.
wnd.clearHighlighting();
wnd.getHighlighter().setHighlightList(startRouteHighlights);
// create route
createRoute(route, cell);
started = false;
}
/**
* Make a vertical route. Will add in contacts in startPort's technology
* to be able to connect to endPort. The added contacts will be placed on
* top of startPort. The final contact will be able to connect to arc.
* @param wnd the EditWindow_ the user is editing
* @param startPort the start of the route
* @param arc the arc type that the last contact will be able to connect to
* @return true on success
*/
public boolean makeVerticalRoute(EditWindow wnd, PortInst startPort, ArcProto arc) {
EditingPreferences ep = wnd.getEditingPreferences();
// do nothing if startPort can already connect to arc
if (startPort.getPortProto().connectsTo(arc)) return true;
if (!started) startInteractiveRoute(wnd);
Point2D startLoc = new Point2D.Double(startPort.getPoly().getCenterX(), startPort.getPoly().getCenterY());
Poly poly = getConnectingSite(startPort, startLoc, ep, -1);
RouteElementPort startRE = RouteElementPort.existingPortInst(startPort, poly, ep);
Route route = new Route();
route.add(startRE); route.setStart(startRE);
VerticalRoute vroute = VerticalRoute.newRoute(startPort.getPortProto(), arc);
if (!vroute.isSpecificationSucceeded()) {
cancelInteractiveRoute();
return false;
}
ArcProto startArc = vroute.getStartArc();
ArcProto endArc = vroute.getEndArc();
ContactSize sizer = new ContactSize(startPort, null, startLoc, startLoc, startLoc, startArc, endArc, false, ep);
Rectangle2D contactArea = sizer.getContactSize();
EDimension contactSize = new EDimension(contactArea.getWidth(), contactArea.getHeight());
int startAngle = sizer.getStartAngle();
double startArcWidth = sizer.getStartWidth();
Cell cell = wnd.getCell();
Route vertRoute = vroute.buildRoute(cell, startLoc, contactSize, startAngle, null, ep);
for (RouteElement re : vertRoute) {
if (!route.contains(re)) route.add(re);
route.setEnd(vertRoute.getEnd());
}
// create arc between startRE and start of vertical route
if (route.replacePin(startRE, vertRoute.getStart(), null, ep)) {
route.remove(startRE);
if (route.getStart() == startRE) route.setStart(vertRoute.getStart());
} else {
addConnectingArc(route, cell, startRE, vertRoute.getStart(), startLoc, startLoc, startArc,
startArcWidth, startAngle, true, true, null, null);
}
// restore highlights at start of planning, so that
// they will correctly show up if this job is undone.
wnd.finishedHighlighting();
wnd.getHighlighter().setHighlightList(startRouteHighlights);
new MakeVerticalRouteJob(this, route, cell, true);
started = false;
return true;
}
private static class MakeVerticalRouteJob extends Router.CreateRouteJob {
protected MakeVerticalRouteJob(Router router, Route route, Cell cell, boolean verbose) {
super(router.toString(), route, cell, verbose, Routing.getRoutingTool());
}
/** Implemented doIt() method to perform Job */
@Override
public boolean doIt() throws JobException {
if (!super.doIt()) return false;
EditingPreferences ep = getEditingPreferences();
RouteElementPort startRE = route.getStart();
if (startRE.getAction() == RouteElement.RouteElementAction.existingPortInst) {
// if this is a pin, replace it with the first contact in vertical route
PortInst pi = startRE.getPortInst();
NodeInst ni = pi.getNodeInst();
if (ni.getProto().getFunction().isPin()) {
CircuitChangeJobs.Reconnect re = CircuitChangeJobs.Reconnect.erasePassThru(ni, false, true, getEditingPreferences());
if (re != null) re.reconnectArcs(ep);
if (!ni.hasExports()) ni.kill();
}
}
return true;
}
}
// -------------------------- Highlight Route Methods -------------------------
/**
* Make a route and highlight it in the window.
* @param cell the cell in which to create the route
* @param startObj a PortInst or ArcInst from which to start the route
* @param endObj a PortInst or ArcInst to end the route on. May be null
* if the user is drawing to empty space.
* @param clicked the point where the user clicked
*/
public void highlightRoute(EditWindow wnd, Cell cell, ElectricObject startObj, ElectricObject endObj, Point2D clicked) {
if (!started) startInteractiveRoute(wnd);
// highlight route
EditingPreferences ep = wnd.getEditingPreferences();
Route route = planRoute(cell, startObj, endObj, clicked, null, ep, true, true, null, null);
highlightRoute(wnd, route, cell);
}
/**
* Highlight a route in the window
* @param route the route to be highlighted
*/
public void highlightRoute(EditWindow wnd, Route route, Cell cell) {
if (!started) startInteractiveRoute(wnd);
wnd.clearHighlighting();
//wnd.getHighlighter().setHighlightList(startRouteHighlights);
for (RouteElement e : route) {
e.addHighlightArea(wnd.getHighlighter());
}
wnd.finishedHighlighting();
}
// -------------------- Internal Router Wrapper of Router ---------------------
/**
* Plan a route from startObj to endObj, taking into account
* where the user clicked in the cell.
* @param cell the cell in which to create the arc
* @param startObj a PortInst or ArcInst from which to start the route
* @param endObj a PortInst or ArcInst to end the route on. May be null
* if the user is drawing to empty space.
* @param clicked the point where the user clicked
* @param stayInside the area in which to route (null if not applicable).
* @param ep EditingPreferences
* @param extendArcHead true to use default arc extension; false to force no arc extension. (head connects to startObj).
* @param extendArcTail true to use default arc extension; false to force no arc extension. (tail connects to endObj).
* @param contactArea
* @param alignment edge alignment factors (null for no alignment).
* @return a List of RouteElements denoting route
*/
public Route planRoute(Cell cell, ElectricObject startObj, ElectricObject endObj, Point2D clicked, PolyMerge stayInside,
EditingPreferences ep, boolean extendArcHead, boolean extendArcTail, Rectangle2D contactArea, EDimension alignment)
{
Route route = new Route(); // hold the route
if (cell == null) return route;
if (startObj == endObj) return route; // can't route to yourself
RouteElementPort startRE = null; // denote start of route
RouteElementPort endRE = null; // denote end of route
// first, convert NodeInsts to PortInsts, if it is one.
// Now we don't have to worry about NodeInsts.
startObj = filterRouteObject(startObj, clicked);
endObj = filterRouteObject(endObj, clicked);
// get the port types at each end so we can build electrical route
PortProto startPort = getRoutePort(startObj, ep);
if (startPort == null) return route; // start cannot be null
PortProto endPort;
if (endObj == null) {
// end object is null, we are routing to a pin. Figure out what arc to use
ArcProto useArc = getArcToUse(startPort, null);
if (useArc == null) return route;
PrimitiveNode pn = useArc.findOverridablePinProto(ep);
if (pn == null) // something wrong with technology. No pin found
{
System.out.println("No primitive node found for arc '" + useArc.getName() + "'");
return route; // error
}
endPort = pn.getPort(0);
} else {
endPort = getRoutePort(endObj, ep);
}
// now determine the electrical route. We need to know what
// arcs will be used (and their widths, eventually) to determine
// the Port Poly size for contacts
VerticalRoute vroute = VerticalRoute.newRoute(startPort, endPort);
if (!vroute.isSpecificationSucceeded()) return new Route();
// arc width of arcs that will connect to startObj, endObj will determine
// valid attachment points of arcs
ArcProto startArc = vroute.getStartArc();
ArcProto endArc = vroute.getEndArc();
double startArcWidth = 0;
double endArcWidth = 0;
// if (!fatWires)
{
// determine arc sizes and
// start and end points based off of arc sizes and startObj and endObj port sizes
startArcWidth = getArcWidthToUse(startObj, startArc, 0, true, ep);
endArcWidth = (endObj == null) ? startArcWidth : getArcWidthToUse(endObj, endArc, 0, true, ep);
if (startArc == endArc) {
if (startArcWidth > endArcWidth) endArcWidth = startArcWidth;
if (endArcWidth > startArcWidth) startArcWidth = endArcWidth;
}
}
// if extension not suppressed, use defaults from arcs
if (extendArcHead) extendArcHead = startArc.getDefaultInst(ep).isHeadExtended() || endArc.getDefaultInst(ep).isHeadExtended();
if (extendArcTail) extendArcTail = startArc.getDefaultInst(ep).isTailExtended() || endArc.getDefaultInst(ep).isTailExtended();
// get valid connecting sites for start and end objects based on the objects
// themselves, the point the user clicked, and the width of the wire that will
// attach to each
Poly startPoly = getConnectingSite(startObj, clicked, ep, startArcWidth);
Poly endPoly = getConnectingSite(endObj, clicked, ep, endArcWidth);
Poly startPolyFull = getConnectingSite(startObj, clicked, ep, -1);
Poly endPolyFull = getConnectingSite(endObj, clicked, ep, -1);
// Now we can figure out where on the start and end objects the connecting
// arc(s) should connect
Point2D startPoint = new Point2D.Double(0, 0);
Point2D endPoint = new Point2D.Double(0,0);
getConnectingPoints(startObj, endObj, clicked, startPoint, endPoint, startPoly, endPoly,
startArc, endArc, alignment, ep);
PortInst existingStartPort = null;
PortInst existingEndPort = null;
// favor contact cuts on arcs
boolean contactsOnEndObject = true;
// plan start of route
if (startObj instanceof PortInst) {
// portinst: just wrap in RouteElement
existingStartPort = (PortInst)startObj;
startRE = RouteElementPort.existingPortInst(existingStartPort, startPoly, ep);
}
if (startObj instanceof ArcInst) {
// arc: figure out where on arc to start
ArcInst ai = (ArcInst)startObj;
startRE = findArcConnectingPoint(route, ai, startPoint, stayInside, alignment);
if (startRE.getPortInst() == ai.getHeadPortInst() && extendArcHead) extendArcHead = ai.isHeadExtended();
if (startRE.getPortInst() == ai.getTailPortInst() && extendArcHead) extendArcHead = ai.isTailExtended();
if (startRE.isBisectArcPin()) contactsOnEndObject = false;
}
if (startRE == null) {
if (startObj != badStartObject)
System.out.println(" Can't route from "+startObj+", no ports");
badStartObject = startObj;
return route;
}
// plan end of route
if (endObj != null) {
// we have somewhere to route to
if (endObj instanceof PortInst) {
// portinst: just wrap in RouteElement
existingEndPort = (PortInst)endObj;
endRE = RouteElementPort.existingPortInst(existingEndPort, endPoly, ep);
}
if (endObj instanceof ArcInst) {
// arc: figure out where on arc to end
// use startRE location when possible if connecting to arc
ArcInst ai = (ArcInst)endObj;
endRE = findArcConnectingPoint(route, ai, endPoint, stayInside, alignment);
if (endRE.getPortInst() == ai.getHeadPortInst() && extendArcTail) extendArcTail = ai.isHeadExtended();
if (endRE.getPortInst() == ai.getTailPortInst() && extendArcTail) extendArcTail = ai.isTailExtended();
if (endRE.isBisectArcPin()) contactsOnEndObject = true;
}
if (endRE == null) {
if (endObj != badEndObject)
System.out.println(" Can't route to "+endObj+", no ports");
badEndObject = endObj;
endObj = null;
}
}
if (endObj == null) {
// nowhere to route to, must make new pin to route to
// first we need to determine what pin to make based on
// start object
ArcProto useArc = null;
if (startObj instanceof PortInst) {
PortInst startPi = (PortInst)startObj;
useArc = getArcToUse(startPi.getPortProto(), null);
}
if (startObj instanceof ArcInst) {
ArcInst startAi = (ArcInst)startObj;
useArc = startAi.getProto();
}
// make new pin to route to
PrimitiveNode pn = useArc.findOverridablePinProto(ep);
SizeOffset so = pn.getProtoSizeOffset();
endRE = RouteElementPort.newNode(cell, pn, pn.getPort(0), endPoint,
pn.getDefWidth(ep)-so.getHighXOffset()-so.getLowXOffset(),
pn.getDefHeight(ep)-so.getHighYOffset()-so.getLowYOffset(), ep);
}
// favor contact on bisected arc
if (startRE.isBisectArcPin()) contactsOnEndObject = false;
if (endRE != null && endRE.isBisectArcPin()) contactsOnEndObject = true;
// special check: if both are existing port instances and are same port, do nothing
if ((existingEndPort != null) && (existingEndPort == existingStartPort)) return new Route();
Point2D cornerLoc = getCornerLocation(startPoint, endPoint, clicked, startArc, endArc,
contactsOnEndObject, stayInside, contactArea, startPolyFull, endPolyFull, ep);
int startAngle = GenMath.figureAngle(startPoint, cornerLoc);
int endAngle = GenMath.figureAngle(endPoint, cornerLoc);
// figure out sizes to use
ContactSize sizer = new ContactSize(startObj, endObj, startPoint, endPoint, cornerLoc, startArc, endArc, false, ep);
contactArea = sizer.getContactSize();
startAngle = sizer.getStartAngle();
endAngle = sizer.getEndAngle();
startArcWidth = sizer.getStartWidth();
endArcWidth = sizer.getEndWidth();
// add startRE and endRE to route
route.add(startRE);
route.setStart(startRE);
route.add(endRE);
route.setEnd(endRE);
// create vertical route if needed
if (startArc != endArc) {
// build vertical route
EDimension contactSize = new EDimension(contactArea.getWidth(), contactArea.getHeight());
Route vertRoute = vroute.buildRoute(cell, cornerLoc, contactSize, startAngle, stayInside, ep);
for (RouteElement re : vertRoute) {
if (!route.contains(re)) route.add(re);
}
// replace endpoints if possible (remove redundant pins), startRE
if (route.replacePin(startRE, vertRoute.getStart(), stayInside, ep)) {
route.remove(startRE);
if (route.getStart() == startRE) route.setStart(vertRoute.getStart());
} else {
// create arc between startRE and start of vertical route
addConnectingArc(route, cell, startRE, vertRoute.getStart(), startPoint, cornerLoc, startArc,
startArcWidth, startAngle, extendArcHead, extendArcTail, stayInside, alignment);
}
// replace endpoints if possible (remove redundant pins), endRE
if (route.replacePin(endRE, vertRoute.getEnd(), stayInside, ep)) {
route.remove(endRE);
if (route.getEnd() == endRE) route.setEnd(vertRoute.getEnd());
} else {
// create arc between endRE and end of vertical route
addConnectingArc(route, cell, endRE, vertRoute.getEnd(), endPoint, cornerLoc, endArc,
endArcWidth, endAngle, extendArcHead, extendArcTail, stayInside, alignment);
}
}
else {
// startRE and endRE can be connected with an arc. If one of them is a bisectArcPin,
// and can be replaced by the other, just replace it and we're done.
if (route.replaceBisectPin(startRE, endRE)) {
route.remove(startRE);
return route;
} else if (route.replaceBisectPin(endRE, startRE)) {
route.remove(endRE);
route.setEnd(startRE);
return route;
}
// check single arc case
int angle = DBMath.figureAngle(startPoint, endPoint);
int angleIncrement = startArc.getAngleIncrement(ep);
if (angleIncrement == 0 || (angle % (10*angleIncrement)) == 0) {
// single arc
addConnectingArc(route, cell, startRE, endRE, startPoint, endPoint, startArc,
startArcWidth, startAngle, extendArcHead, extendArcTail, stayInside, alignment);
} else {
PrimitiveNode pn = startArc.findOverridablePinProto(ep);
SizeOffset so = pn.getProtoSizeOffset();
double defwidth = pn.getDefWidth(ep)-so.getHighXOffset()-so.getLowXOffset();
double defheight = pn.getDefHeight(ep)-so.getHighYOffset()-so.getLowYOffset();
RouteElementPort pinRE = RouteElementPort.newNode(cell, pn, pn.getPort(0), cornerLoc,
defwidth, defheight, ep);
route.add(pinRE);
addConnectingArc(route, cell, startRE, pinRE, startPoint, cornerLoc, startArc,
startArcWidth, startAngle, extendArcHead, extendArcTail, stayInside, alignment);
addConnectingArc(route, cell, endRE, pinRE, endPoint, cornerLoc, endArc,
endArcWidth, endAngle, extendArcHead, extendArcTail, stayInside, alignment);
}
}
return route;
}
// -------------------- Internal Router Utility Methods --------------------
/**
* If routeObj is a NodeInst, first thing we do is get the nearest PortInst
* to where the user clicked, and use that instead.
* @param routeObj the route object (possibly a NodeInst).
* @param clicked where the user clicked
* @return the PortInst on the NodeInst closest to where the user clicked,
* or just the routeObj back if it is not a NodeInst.
*/
protected static ElectricObject filterRouteObject(ElectricObject routeObj, Point2D clicked) {
if (routeObj instanceof NodeInst) {
return ((NodeInst)routeObj).findClosestPortInst(clicked);
}
if (routeObj instanceof Export) {
Export exp = (Export)routeObj;
return exp.getOriginalPort();
}
return routeObj;
}
/**
* Get the PortProto associated with routeObj (it should be either
* a ArcInst or a PortInst, otherwise this will return null).
* @param routeObj the route object
* @param ep EditingPreferences
* @return the PortProto for this route object
*/
protected static PortProto getRoutePort(ElectricObject routeObj, EditingPreferences ep) {
assert(!(routeObj instanceof NodeInst));
if (routeObj instanceof ArcInst) {
ArcInst ai = (ArcInst)routeObj;
PrimitiveNode pn = ai.getProto().findOverridablePinProto(ep);
return pn.getPort(0);
}
if (routeObj instanceof PortInst) {
PortInst pi = (PortInst)routeObj;
return pi.getPortProto();
}
return null;
}
/**
* Get the connecting points for the start and end objects of the route. This fills in
* the two Point2D's startPoint and endPoint. These will be the end points of an arc that
* connects to either startObj or endObj.
* @param startObj the start route object
* @param endObj the end route object
* @param clicked where the user clicked
* @param startPoint point inside startPoly on startObj to connect arc to
* @param endPoint point inside endPoly on endObj to connect arc to
* @param startPoly valid port site on startObj
* @param endPoly valid port site on endObj
* @param startArc the arc type to connect to startObj
* @param endArc the arc type to connect to endObj
* @param ep EditingPreferences.
*/
protected static void getConnectingPoints(ElectricObject startObj, ElectricObject endObj, Point2D clicked,
Point2D startPoint, Point2D endPoint, Poly startPoly, Poly endPoly,
ArcProto startArc, ArcProto endArc, EDimension alignment, EditingPreferences ep) {
if (alignment == null) alignment = ep.getAlignmentToGrid();
if ((startPoly.getBox() == null && startPoly.getPoints().length != 2) ||
(endPoly != null && endPoly.getBox() == null && endPoly.getPoints().length != 2)) {
// special case: one of the polys is not a rectangle
startPoint.setLocation(startPoly.closestPoint(clicked));
if (endPoly == null) {
endPoint.setLocation(getClosestOrthogonalPoint(startPoint, clicked));
} else {
endPoint.setLocation(endPoly.closestPoint(clicked));
}
return;
}
// default is center point, aligned to grid
Rectangle2D startBounds = new Rectangle2D.Double(startPoly.getBounds2D().getX(), startPoly.getBounds2D().getY(),
startPoly.getBounds2D().getWidth(), startPoly.getBounds2D().getHeight());
getAlignedCenter(startBounds, alignment, startPoint);
boolean manhattan = (startArc.getAngleIncrement(ep) == 90);
// if startObj is arc instance
if (startObj instanceof ArcInst) {
ArcInst ai = (ArcInst)startObj;
if (manhattan && (ai.getHeadLocation().getX() == ai.getTailLocation().getX() || ai.getHeadLocation().getY() == ai.getTailLocation().getY()))
{
// if nothing to connect to, clicked will determine connecting point on startPoly
// endPoint will be location of new pin
double x = getClosestValue(startBounds.getMinX(), startBounds.getMaxX(), clicked.getX(), alignment.getWidth());
double y = getClosestValue(startBounds.getMinY(), startBounds.getMaxY(), clicked.getY(), alignment.getHeight());
startPoint.setLocation(x, y);
} else
{
startPoint.setLocation(GenMath.closestPointToLine(ai.getHeadLocation(), ai.getTailLocation(), clicked));
manhattan = false;
}
}
if (endPoly == null) {
// if arc, find place to connect to. Otherwise use the center point (default)
int angleIncrement = endArc.getAngleIncrement(ep);
endPoint.setLocation(getClosestAngledPoint(startPoint, clicked, angleIncrement, alignment));
//endPoint.setLocation(getClosestOrthogonalPoint(startPoint, clicked));
// however, if this is an Artwork technology, just put end point at mouse
if (startArc.getTechnology() == Artwork.tech())
endPoint.setLocation(clicked);
return;
}
Rectangle2D endBounds = new Rectangle2D.Double(endPoly.getBounds2D().getX(), endPoly.getBounds2D().getY(),
endPoly.getBounds2D().getWidth(), endPoly.getBounds2D().getHeight());
getAlignedCenter(endBounds, alignment, endPoint);
if (endObj instanceof ArcInst) {
ArcInst ai = (ArcInst)endObj;
if (manhattan && (ai.getHeadLocation().getX() == ai.getTailLocation().getX() || ai.getHeadLocation().getY() == ai.getTailLocation().getY()))
{
// if nothing to connect to, clicked will determine connecting point on startPoly
// endPoint will be location of new pin
double x = getClosestValue(endBounds.getMinX(), endBounds.getMaxX(), clicked.getX(), alignment.getWidth());
double y = getClosestValue(endBounds.getMinY(), endBounds.getMaxY(), clicked.getY(), alignment.getHeight());
endPoint.setLocation(x, y);
} else
{
endPoint.setLocation(GenMath.closestPointToLine(ai.getHeadLocation(), ai.getTailLocation(), startPoint));
// see if that intersection point is actually on the segment
if (endPoint.getX() < Math.min(ai.getHeadLocation().getX(), ai.getTailLocation().getX()) ||
endPoint.getX() > Math.max(ai.getHeadLocation().getX(), ai.getTailLocation().getX()) ||
endPoint.getY() < Math.min(ai.getHeadLocation().getY(), ai.getTailLocation().getY()) ||
endPoint.getY() > Math.max(ai.getHeadLocation().getY(), ai.getTailLocation().getY()))
{
if ((startPoint.getX() < Math.min(ai.getHeadLocation().getX(), ai.getTailLocation().getX()) ||
startPoint.getX() > Math.max(ai.getHeadLocation().getX(), ai.getTailLocation().getX())) &&
startPoint.getY() >= Math.min(ai.getHeadLocation().getY(), ai.getTailLocation().getY()) &&
startPoint.getY() <= Math.max(ai.getHeadLocation().getY(), ai.getTailLocation().getY()))
{
Point2D intPt = GenMath.intersect(ai.getHeadLocation(), ai.getAngle(), startPoint, 0);
endPoint.setLocation(intPt);
} else if ((startPoint.getY() < Math.min(ai.getHeadLocation().getY(), ai.getTailLocation().getY()) ||
startPoint.getY() > Math.max(ai.getHeadLocation().getY(), ai.getTailLocation().getY())) &&
startPoint.getX() >= Math.min(ai.getHeadLocation().getX(), ai.getTailLocation().getX()) &&
startPoint.getX() <= Math.max(ai.getHeadLocation().getX(), ai.getTailLocation().getX()))
{
Point2D intPt = GenMath.intersect(ai.getHeadLocation(), ai.getAngle(), startPoint, 900);
endPoint.setLocation(intPt);
} else
{
double x = getClosestValue(endBounds.getMinX(), endBounds.getMaxX(), clicked.getX(), alignment.getWidth());
double y = getClosestValue(endBounds.getMinY(), endBounds.getMaxY(), clicked.getY(), alignment.getHeight());
endPoint.setLocation(x, y);
}
}
manhattan = false;
}
}
double lowerBoundX = Math.max(startBounds.getMinX(), endBounds.getMinX());
double upperBoundX = Math.min(startBounds.getMaxX(), endBounds.getMaxX());
boolean overlapX = lowerBoundX <= upperBoundX;
double lowerBoundY = Math.max(startBounds.getMinY(), endBounds.getMinY());
double upperBoundY = Math.min(startBounds.getMaxY(), endBounds.getMaxY());
boolean overlapY = lowerBoundY <= upperBoundY;
if (ep.isFatWires()) {
Rectangle2D startObjBounds = new Rectangle2D.Double(startPoly.getBounds2D().getX(), startPoly.getBounds2D().getY(),
startPoly.getBounds2D().getWidth(), startPoly.getBounds2D().getHeight());
Rectangle2D endObjBounds = new Rectangle2D.Double(endPoly.getBounds2D().getX(), endPoly.getBounds2D().getY(),
endPoly.getBounds2D().getWidth(), endPoly.getBounds2D().getHeight());
//Rectangle2D startObjBounds = getBounds(startObj);
//Rectangle2D endObjBounds = getBounds(endObj);
boolean objsOverlap = false;
if (startObjBounds != null && endObjBounds != null) {
if (startArc == endArc && startObjBounds.intersects(endObjBounds))
objsOverlap = true;
}
// grid align if needed
Point2D startCenter = new Point2D.Double(startBounds.getCenterX(), startBounds.getCenterY());
Point2D endCenter = new Point2D.Double(endBounds.getCenterX(), endBounds.getCenterY());
gridAlignWithinBounds(startCenter, startBounds, alignment);
gridAlignWithinBounds(endCenter, endBounds, alignment);
boolean startObjInEndObjInX = false;
boolean startObjInEndObjInY = false;
boolean endObjInStartObjInX = false;
boolean endObjInStartObjInY = false;
// special case: start object is completely inside end object in one dimension
if (startObjBounds.getMinX() >= endObjBounds.getMinX() && startObjBounds.getMaxX() <= endObjBounds.getMaxX()) {
startObjInEndObjInX = true;
}
if (startObjBounds.getMinY() >= endObjBounds.getMinY() && startObjBounds.getMaxY() <= endObjBounds.getMaxY()) {
startObjInEndObjInY = true;
}
// special case: end object is completely inside start object in one dimension
if (endObjBounds.getMinX() >= startObjBounds.getMinX() && endObjBounds.getMaxX() <= startObjBounds.getMaxX()) {
endObjInStartObjInX = true;
}
if (endObjBounds.getMinY() >= startObjBounds.getMinY() && endObjBounds.getMaxY() <= startObjBounds.getMaxY()) {
endObjInStartObjInY = true;
}
// normally we route center to center (for PortInsts) in fat wiring mode,
// but if the objects overlap (both ports and objects themselves, then we will route directly
if (!objsOverlap || !overlapX) {
// center X on both objects (if they are port instances)
if ((startObj instanceof PortInst) && !endObjInStartObjInX) {
startBounds.setRect(startCenter.getX(), startBounds.getY(), 0, startBounds.getHeight());
}
if ((endObj instanceof PortInst) && !startObjInEndObjInX) {
endBounds.setRect(endCenter.getX(), endBounds.getY(), 0, endBounds.getHeight());
}
}
if (!objsOverlap || !overlapY) {
// center Y on both objects (if they are port instances)
if ((startObj instanceof PortInst) && !endObjInStartObjInY) {
startBounds.setRect(startBounds.getX(), startCenter.getY(), startBounds.getWidth(), 0);
}
if ((endObj instanceof PortInst) && !startObjInEndObjInY) {
endBounds.setRect(endBounds.getX(), endCenter.getY(), endBounds.getWidth(), 0);
}
}
// recalculate overlaps in case bounds have changed
lowerBoundX = Math.max(startBounds.getMinX(), endBounds.getMinX());
upperBoundX = Math.min(startBounds.getMaxX(), endBounds.getMaxX());
overlapX = lowerBoundX <= upperBoundX;
lowerBoundY = Math.max(startBounds.getMinY(), endBounds.getMinY());
upperBoundY = Math.min(startBounds.getMaxY(), endBounds.getMaxY());
overlapY = lowerBoundY <= upperBoundY;
}
if (startPoly.getPoints().length == 2 && endPoly.getPoints().length == 2) {
// lines, allow special case where lines can be non-Manhattan
Point2D [] points1 = startPoly.getPoints();
Point2D [] points2 = endPoly.getPoints();
Point2D intersection = getIntersection(points1, points2);
if (intersection != null)
{
if (Job.getDebug())
{
System.out.println("===========================================================");
System.out.println("Start Poly: "+points1[0]+", "+points1[1]);
System.out.println("End Poly: "+points2[0]+", "+points2[1]);
System.out.println("Intersection Point: "+intersection);
System.out.println("===========================================================");
}
startPoint.setLocation(intersection);
endPoint.setLocation(intersection);
return;
}
}
// check if bounds share X and Y space (Manhattan rectangular polygons only)
if (manhattan)
{
if (lowerBoundX <= upperBoundX) {
double x = getClosestValue(lowerBoundX, upperBoundX, clicked.getX(), alignment.getWidth());
startPoint.setLocation(x, startPoint.getY());
endPoint.setLocation(x, endPoint.getY());
} else {
// otherwise, use closest point in bounds to the other port
// see which one is higher in X...they don't overlap, so any X coordinate in bounds is comparable
if (startBounds.getMinX() > endBounds.getMaxX()) {
startPoint.setLocation(startBounds.getMinX(), startPoint.getY());
endPoint.setLocation(endBounds.getMaxX(), endPoint.getY());
} else {
startPoint.setLocation(startBounds.getMaxX(), startPoint.getY());
endPoint.setLocation(endBounds.getMinX(), endPoint.getY());
}
}
// if bounds share y-space, use closest y within that space to clicked point
if (lowerBoundY <= upperBoundY) {
double y = getClosestValue(lowerBoundY, upperBoundY, clicked.getY(), alignment.getHeight());
startPoint.setLocation(startPoint.getX(), y);
endPoint.setLocation(endPoint.getX(), y);
} else {
// otherwise, use closest point in bounds to the other port
// see which one is higher in Y...they don't overlap, so any Y coordinate in bounds is comparable
if (startBounds.getMinY() > endBounds.getMaxY()) {
startPoint.setLocation(startPoint.getX(), startBounds.getMinY());
endPoint.setLocation(endPoint.getX(), endBounds.getMaxY());
} else {
startPoint.setLocation(startPoint.getX(), startBounds.getMaxY());
endPoint.setLocation(endPoint.getX(), endBounds.getMinY());
}
}
}
// if (alignment.getWidth() > 0 && alignment.getHeight() > 0) {
// long sX = (long)(startPoint.getX() / alignment.getWidth());
// long sY = (long)(startPoint.getY() / alignment.getHeight());
// long eX = (long)(endPoint.getX() / alignment.getWidth());
// long eY = (long)(endPoint.getY() / alignment.getHeight());
// if (sX * alignment.getWidth() != startPoint.getX() ||
// eX * alignment.getWidth() != endPoint.getX() ||
// sY * alignment.getHeight() != startPoint.getY() ||
// eY * alignment.getHeight() != endPoint.getY()) {
// System.out.println("Start point (" + startPoint.getX() + "," + startPoint.getY() + ") and/or end point (" +
// endPoint.getX() + "," + endPoint.getY() + ") not aligned to alignment: (" + alignment.getWidth() +
// "," + alignment.getHeight() + ")");
// }
// }
}
/**
* Grid align the point within the confine of bounds. This assumes the point is already within the bounds.
* @param pt the point to align
* @param bounds the bounds within to align
* @param alignment the alignment
*/
private static void gridAlignWithinBounds(Point2D pt, Rectangle2D bounds, EDimension alignment)
{
if (alignment == null) return;
double x = pt.getX();
double y = pt.getY();
if (alignment.getWidth() > 0) {
double xfloor = Math.floor(x/alignment.getWidth()) * alignment.getWidth();
double xceil = Math.ceil(x/alignment.getWidth()) * alignment.getWidth();
if (xfloor >= bounds.getMinX() && xfloor <= bounds.getMaxX()) {
x = xfloor;
} else if (xceil >= bounds.getMinX() && xceil <= bounds.getMaxX()) {
x = xceil;
}
}
if (alignment.getHeight() > 0) {
double yfloor = Math.floor(y/alignment.getHeight()) * alignment.getHeight();
double yceil = Math.ceil(y/alignment.getHeight()) * alignment.getHeight();
if (yfloor >= bounds.getMinY() && yfloor <= bounds.getMaxY()) {
y = yfloor;
} else if (yceil >= bounds.getMinY() && yceil <= bounds.getMaxY()) {
y = yceil;
}
}
pt.setLocation(x, y);
}
/**
* Method to find the center of a Rectangle, grid aligned, and to grid-align the rectangle.
* @param bounds the rectangle to evaluate and align.
* @param alignment the alignment in X and Y.
* @param ctr the center point is stored here.
*/
private static void getAlignedCenter(Rectangle2D bounds, EDimension alignment, Point2D ctr)
{
double cX = bounds.getCenterX();
double cY = bounds.getCenterY();
if (alignment != null)
{
if (alignment.getWidth() > 0)
{
double xx = cX / alignment.getWidth();
long xxL = Math.round(xx);
if (xx != xxL)
{
double newX = xxL * alignment.getWidth();
if (newX >= bounds.getMinX() && newX <= bounds.getMaxX()) cX = newX;
}
double lX = Math.ceil(bounds.getMinX() / alignment.getWidth()) * alignment.getWidth();
double hX = Math.floor(bounds.getMaxX() / alignment.getWidth()) * alignment.getWidth();
if (lX <= bounds.getMaxX() && hX >= bounds.getMinX())
bounds.setRect(lX, bounds.getMinY(), hX-lX, bounds.getHeight());
}
if (alignment.getHeight() > 0)
{
double yy = cY / alignment.getHeight();
long yyL = Math.round(yy);
if (yy != yyL)
{
double newY = yyL * alignment.getHeight();
if (newY >= bounds.getMinY() && newY <= bounds.getMaxY()) cY = newY;
}
double lY = Math.ceil(bounds.getMinY() / alignment.getHeight()) * alignment.getHeight();
double hY = Math.floor(bounds.getMaxY() / alignment.getHeight()) * alignment.getHeight();
if (lY <= bounds.getMaxY() && hY >= bounds.getMinY())
bounds.setRect(bounds.getMinX(), lY, bounds.getWidth(), hY-lY);
}
}
ctr.setLocation(cX, cY);
}
/**
* Get the intersection point of the two line segments, or null if none
* @param points1 An array of two points that define line 1
* @param points2 An array of two points that define line 2
* @return the intersection point, or null if none
*/
public static Point2D getIntersection(Point2D [] points1, Point2D [] points2) {
// Checking if line1 is not a singular point
if (DBMath.areEquals(points1[0], points1[1]) || DBMath.areEquals(points2[0], points2[1]))
{
if (Job.getDebug())
System.out.println("Line is a singular point in InteractiveRouter.getIntersection");
return null;
}
Line2D line1 = new Line2D.Double(points1[0], points1[1]);
Line2D line2 = new Line2D.Double(points2[0], points2[1]);
if (!line1.intersectsLine(line2))
return null;
double [] co1 = getLineCoeffs(line1);
double [] co2 = getLineCoeffs(line2);
// determinant = A1*B2 - A2*B1
double det = co1[0]*co2[1] - co2[0]*co1[1];
if (det == 0)
{
// lines are parallel. Since they already passed the "intersection" test, they must meet at an end
if (points1[0].getX() == points2[0].getX() && points1[0].getY() == points2[0].getY()) return points1[0];
if (points1[0].getX() == points2[1].getX() && points1[0].getY() == points2[1].getY()) return points1[0];
if (points1[1].getX() == points2[0].getX() && points1[1].getY() == points2[0].getY()) return points1[1];
if (points1[1].getX() == points2[1].getX() && points1[1].getY() == points2[1].getY()) return points1[1];
return null;
}
// x = (B2*C1 - B1*C2) / determinant
double x = (co2[1]*co1[2] - co1[1]*co2[2])/det;
// y = (A1*C2 - A2*C1) / determinant
double y = (co1[0]*co2[2] - co2[0]*co1[2])/det;
return new Point2D.Double(x, y);
}
/**
* Get the coefficients of the line of the form Ax + By = C.
* Can't use y = Ax + B because it does not allow x = A type equations.
* @param line the line
* @return an array of the values A,B,C
*/
private static double [] getLineCoeffs(Line2D line) {
double A = line.getP2().getY() - line.getP1().getY();
double B = line.getP1().getX() - line.getP2().getX();
double C = A * line.getP1().getX() + B * line.getP1().getY();
return new double [] {A,B,C};
}
/**
* Get bounds of primitive instance. Returns null if object is not an instance of a primitive.
* @param obj the object
* @return the bounds of the instance
*/
protected static Rectangle2D getBounds(ElectricObject obj) {
if (obj instanceof ArcInst) {
ArcInst ai = (ArcInst)obj;
return ai.getBounds();
}
if (obj instanceof PortInst) {
PortInst pi = (PortInst)obj;
obj = pi.getNodeInst();
}
if (obj instanceof NodeInst) {
NodeInst ni = (NodeInst)obj;
NodeProto np = ni.getProto();
if (np instanceof PrimitiveNode) {
return ni.getBounds();
}
}
return null;
}
/**
* Get the connecting site of the electric object.
*
* - For NodeInsts, this is the nearest portinst to "clicked", which is then subject to:
*
- For PortInsts, this is the nearest site of a multi-site port, or just the entire port
*
- For ArcInsts, this is a poly composed of the head location and the tail location
*
* See NodeInst.getShapeOfPort() for more details.
* @param obj the object to get a connection site for
* @param clicked used to find the nearest portinst on a nodeinst, and nearest
* site on a multi-site port
* @param arcWidth contacts port sites are restricted by the size of arcs connecting
* to them, such that the arc width does extend beyond the contact edges.
* @return a poly describing where something can connect to
*/
protected static Poly getConnectingSite(ElectricObject obj, Point2D clicked, EditingPreferences ep, double arcWidth) {
assert(clicked != null);
if (obj instanceof NodeInst) {
PortInst pi = ((NodeInst)obj).findClosestPortInst(clicked);
if (pi == null) return null;
obj = pi;
}
if (obj instanceof PortInst) {
PortInst pi = (PortInst)obj;
NodeInst ni = pi.getNodeInst();
PortProto pp = pi.getPortProto();
if (ni.isCellInstance()) {
return ni.getShapeOfPort(pp); // this is for multi-site ports
// return ni.getShapeOfPort(pp, clicked); // this is for multi-site ports
} else {
return ni.getShapeOfPortForWiringTool(pp, clicked, ep, arcWidth); // this is for multi-site ports
}
}
if (obj instanceof ArcInst) {
// make poly out of possible connecting points on arc
ArcInst arc = (ArcInst)obj;
Poly poly = new Poly(arc.getHeadLocation(), arc.getTailLocation());
return poly;
}
return null;
}
protected static Rectangle2D getLayerArea(ElectricObject obj, Layer layer) {
if (obj instanceof PortInst) {
PortInst pi = (PortInst)obj;
NodeInst ni = pi.getNodeInst();
NodeProto np = ni.getProto();
while (np instanceof Cell) {
// TODO: add code to extract initial exported primitive object,
// and create affine transform to translate extracted poly back up to current level
return null;
}
if (np instanceof PrimitiveNode) {
PrimitiveNode pn = (PrimitiveNode)np;
// if pin, use area of arcs connected to it
if (pn.getFunction().isPin()) {
// determine size by arcs connected to it
Rectangle2D horiz = null;
Rectangle2D vert = null;
for (Iterator it = pi.getConnections(); it.hasNext(); ) {
Connection conn = it.next();
ArcInst ai = conn.getArc();
if (ai.getProto().getLayerIterator().next() != layer) continue;
int angle = ai.getDefinedAngle();
if (angle % 1800 == 0) {
if (horiz == null)
horiz = ai.getBounds();
else
horiz = horiz.createUnion(ai.getBounds());
}
if ((angle + 900) % 1800 == 0) {
if (vert == null)
vert = ai.getBounds();
else
vert = vert.createUnion(ai.getBounds());
}
}
return horiz.createIntersection(vert);
}
// if contact, use size of contact
if (pn.getFunction().isContact()) {
Poly p = ni.getBaseShape();
return p.getBounds2D();
}
}
}
if (obj instanceof ArcInst) {
ArcInst ai = (ArcInst)obj;
return ai.getBounds();
}
return null;
}
/**
* If drawing to/from an ArcInst, we may connect to some
* point along the arc. This may bisect the arc, in which case
* we delete the current arc, add in a pin at the appropriate
* place, and create 2 new arcs to the old arc head/tail points.
* The bisection point depends on where the user point, but it
* is always a point on the arc.
*
* Note that this method adds the returned RouteElement to the
* route, and updates the route if the arc is bisected.
* This method should NOT add the returned RouteElement to the route.
* @param route the route so far
* @param arc the arc to draw from/to
* @param connectingPoint point on or near arc
* @param stayInside the area in which to route (null if not applicable).
* @param alignment if non-null, alignment to align to
* @return a RouteElement holding the new pin at the bisection
* point, or a RouteElement holding an existingPortInst if
* drawing from either end of the ArcInst.
*/
protected RouteElementPort findArcConnectingPoint(Route route, ArcInst arc, Point2D connectingPoint, PolyMerge stayInside, EDimension alignment) {
EPoint head = arc.getHeadLocation();
EPoint tail = arc.getTailLocation();
RouteElementPort headRE = RouteElementPort.existingPortInst(arc.getHeadPortInst(), head, ep);
RouteElementPort tailRE = RouteElementPort.existingPortInst(arc.getTailPortInst(), tail, ep);
if (head.equals(connectingPoint)) return headRE;
if (tail.equals(connectingPoint)) return tailRE;
// otherwise, we must be bisecting the arc
return bisectArc(route, arc, connectingPoint, stayInside, alignment);
}
/**
* Splits an arc at bisectPoint and updates the route to reflect the change.
* This method should NOT add the returned RouteElement to the route.
*
* @param route the current route
* @param arc the arc to split
* @param bisectPoint point on arc from which to split it
* @param stayInside the area in which to route (null if not applicable).
* @param alignment alignment to align to, if non-null
* @return the RouteElement from which to continue the route
*/
protected RouteElementPort bisectArc(Route route, ArcInst arc, Point2D bisectPoint, PolyMerge stayInside, EDimension alignment) {
Cell cell = arc.getParent();
EPoint head = arc.getHeadLocation();
EPoint tail = arc.getTailLocation();
// determine pin type to use if bisecting arc
PrimitiveNode pn = arc.getProto().findOverridablePinProto(ep);
SizeOffset so = pn.getProtoSizeOffset();
double width = pn.getDefWidth(ep)-so.getHighXOffset()-so.getLowXOffset();
double height = pn.getDefHeight(ep)-so.getHighYOffset()-so.getLowYOffset();
// make new pin
RouteElementPort newPinRE = RouteElementPort.newNode(cell, pn, pn.getPort(0),
bisectPoint, width, height, ep);
newPinRE.setBisectArcPin(true);
// make dummy end pins
RouteElementPort headRE = RouteElementPort.existingPortInst(arc.getHeadPortInst(), head, ep);
RouteElementPort tailRE = RouteElementPort.existingPortInst(arc.getTailPortInst(), tail, ep);
headRE.setShowHighlight(false);
tailRE.setShowHighlight(false);
// put name on longer arc
String name1 = null;
String name2 = null;
String nameToUse = arc.getName();
if (arc.getNameKey().isTempname()) nameToUse = null;
if (head.distance(bisectPoint) > tail.distance(bisectPoint))
name1 = nameToUse; else
name2 = nameToUse;
// add two arcs to rebuild old startArc
boolean extendArcFromHeadSide = getExtendArcEnd(newPinRE, bisectPoint, arc.getLambdaBaseWidth(), arc.getProto(),
arc.getAngle(), arc.isTailExtended(), alignment);
boolean extendArcFromTailSide = getExtendArcEnd(newPinRE, bisectPoint, arc.getLambdaBaseWidth(), arc.getProto(),
arc.getAngle(), arc.isHeadExtended(), alignment);
RouteElement newHeadArcRE = RouteElementArc.newArc(cell, arc.getProto(), arc.getLambdaBaseWidth(), headRE, newPinRE,
head, bisectPoint, name1, arc.getTextDescriptor(ArcInst.ARC_NAME), arc, arc.isHeadExtended(), extendArcFromHeadSide, stayInside);
RouteElement newTailArcRE = RouteElementArc.newArc(cell, arc.getProto(), arc.getLambdaBaseWidth(), newPinRE, tailRE,
bisectPoint, tail, name2, arc.getTextDescriptor(ArcInst.ARC_NAME), arc, extendArcFromTailSide, arc.isTailExtended(), stayInside);
newHeadArcRE.setShowHighlight(false);
newTailArcRE.setShowHighlight(false);
// delete old arc
RouteElement deleteArcRE = RouteElementArc.deleteArc(arc, ep);
// add new stuff to route
route.add(deleteArcRE);
route.add(headRE);
route.add(tailRE);
route.add(newHeadArcRE);
route.add(newTailArcRE);
return newPinRE;
}
protected static Point2D getCornerLocation(Point2D startLoc, Point2D endLoc, Point2D clicked, ArcProto startArc, ArcProto endArc,
boolean contactsOnEndObj, PolyMerge stayInside, Rectangle2D contactArea,
Poly startPolyFull, Poly endPolyFull, EditingPreferences ep) {
// connection point specified by contactArea
if (contactArea != null) {
return new Point2D.Double(contactArea.getCenterX(), contactArea.getCenterY());
}
// if startArc and endArc are the same, see if we can connect directly with whatever angle increment is on the arc
boolean singleArc = false;
if (startArc == endArc) {
int inc = 10*startArc.getAngleIncrement(ep);
if (inc == 0) singleArc = true; else
{
int ang = GenMath.figureAngle(startLoc, endLoc);
if ((ang % inc) == 0) singleArc = true;
}
}
else {
// see if start and end line up in X or Y
if (DBMath.areEquals(startLoc.getX(), endLoc.getX()) || DBMath.areEquals(startLoc.getY(), endLoc.getY())) singleArc = true;
}
// if single arc, corner location is either start or end location
if (singleArc) {
if (contactsOnEndObj)
return new Point2D.Double(endLoc.getX(), endLoc.getY());
else
return new Point2D.Double(startLoc.getX(), startLoc.getY());
}
Point2D cornerLoc = null;
Point2D pin1 = new Point2D.Double(startLoc.getX(), endLoc.getY());
Point2D pin2 = new Point2D.Double(endLoc.getX(), startLoc.getY());
int clickedQuad = findQuadrant(endLoc, clicked);
int pin1Quad = findQuadrant(endLoc, pin1);
int pin2Quad = findQuadrant(endLoc, pin2);
int oppositeQuad = (clickedQuad + 2) % 4;
// presume pin1 by default
cornerLoc = pin1;
if (pin2Quad == clickedQuad)
{
cornerLoc = pin2; // same quad as pin2, use pin2
} else if (pin1Quad == clickedQuad)
{
cornerLoc = pin1; // same quad as pin1, use pin1
} else if (pin1Quad == oppositeQuad)
{
cornerLoc = pin2; // near to pin2 quad, use pin2
}
// special case - if poly sites overlap and corner is within that area, use that corner
if (startPolyFull != null && endPolyFull != null && startPolyFull.intersects(endPolyFull)) {
boolean usepin1 = false, usepin2 = false;
if (startPolyFull.contains(pin1) && endPolyFull.contains(pin1)) usepin1 = true;
if (startPolyFull.contains(pin2) && endPolyFull.contains(pin2)) usepin2 = true;
if (usepin1 && !usepin2) cornerLoc = pin1;
if (usepin2 && !usepin1) cornerLoc = pin2;
}
ArcProto useArc = startArc;
if (!contactsOnEndObj) useArc = endArc;
if (stayInside != null && useArc != null)
{
// make sure the bend stays inside of the merge area
double pinSize = useArc.getDefaultLambdaBaseWidth(ep);
Layer pinLayer = useArc.getLayerIterator().next();
Rectangle2D pin1Rect = new Rectangle2D.Double(pin1.getX()-pinSize/2, pin1.getY()-pinSize/2, pinSize, pinSize);
Rectangle2D pin2Rect = new Rectangle2D.Double(pin2.getX()-pinSize/2, pin2.getY()-pinSize/2, pinSize, pinSize);
if (stayInside.contains(pinLayer, pin1Rect)) cornerLoc = pin1; else
if (stayInside.contains(pinLayer, pin2Rect)) cornerLoc = pin2;
}
return cornerLoc;
}
protected static void updateContactArea(Rectangle2D contactArea, RouteElementPort re, Point2D cornerLoc, double arcWidth, int arcAngle) {
if (arcAngle % 1800 == 0) {
// horizontal arc
if (contactArea.getHeight() < arcWidth) {
contactArea.setRect(contactArea.getX(), contactArea.getCenterY()-arcWidth/2, contactArea.getWidth(), arcWidth);
}
}
if ((arcAngle + 900) % 1800 == 0) {
// vertical arc
if (contactArea.getWidth() < arcWidth) {
contactArea.setRect(contactArea.getCenterX()-arcWidth/2, contactArea.getY(), arcWidth, contactArea.getHeight());
}
}
}
protected static void addConnectingArc(Route route, Cell cell, RouteElementPort startRE, RouteElementPort endRE,
Point2D startPoint, Point2D endPoint,
ArcProto arc, double width, int arcAngle, boolean extendArcHead,
boolean extendArcTail, PolyMerge stayInside, EDimension alignment) {
if (extendArcHead) extendArcHead = getExtendArcEnd(startRE, startPoint, width, arc, arcAngle, extendArcHead, alignment);
if (extendArcTail) extendArcTail = getExtendArcEnd(endRE, endPoint, width, arc, arcAngle, extendArcTail, alignment);
RouteElementArc reArc = RouteElementArc.newArc(cell, arc, width, startRE, endRE, startPoint, endPoint,
null, null, null, extendArcHead, extendArcTail, stayInside);
reArc.setArcAngle(arcAngle);
route.add(reArc);
}
protected static boolean getExtendArcEnd(RouteElementPort re, Point2D point, double arcWidth, ArcProto arc, int arcAngle, boolean defExtends, EDimension alignment) {
NodeProto np = re.getNodeProto();
if (np == null) return defExtends;
if (np instanceof PrimitiveNode) {
PrimitiveNode pn = (PrimitiveNode)np;
if (pn.getFunction().isContact()) {
EDimension size = re.getNodeSize();
if (arcAngle % 1800 == 0) {
if (arcWidth > size.getWidth()) return false;
}
if ((arcAngle+900) % 1800 == 0) {
if (arcWidth > size.getHeight()) return false;
}
}
}
if (alignment != null) {
if (arcAngle == -1) arcAngle = 0;
if (arcAngle % 1800 == 0) {
// horizontal
if (isNumberAligned(point.getX(), alignment.getWidth()) &&
!isNumberAligned(point.getX() + arcWidth/2, alignment.getWidth()))
return false;
if (!isNumberAligned(point.getX(), alignment.getWidth()) &&
isNumberAligned(point.getX() + arcWidth/2, alignment.getWidth()))
return true;
}
if ((arcAngle+900) % 1800 == 0) {
// vertical
if (isNumberAligned(point.getY(), alignment.getHeight()) &&
!isNumberAligned(point.getY() + arcWidth/2, alignment.getHeight()))
return false;
if (!isNumberAligned(point.getY(), alignment.getHeight()) &&
isNumberAligned(point.getY() + arcWidth/2, alignment.getHeight()))
return true;
}
}
return defExtends;
}
private static boolean isNumberAligned(double number, double alignment) {
if (number % alignment == 0) return true;
return false;
}
// ------------------------- Spatial Dimension Calculations -------------------------
/**
* Get closest value to clicked within a range from min to max
*/
protected static double getClosestValue(double min, double max, double clicked, double alignment) {
if (alignment > 0) {
double newMax = Math.floor(max / alignment) * alignment;
if (newMax > max) newMax = max;
if (newMax < min) newMax = min;
max = newMax;
double newMin = Math.ceil(min / alignment) * alignment;
if (newMin > max) newMin = max;
if (newMin < min) newMin = min;
min = newMin;
clicked = Math.round(clicked / alignment) * alignment;
}
if (clicked >= max) {
return max;
} else if (clicked <= min) {
return min;
} else {
return clicked;
}
}
/**
* Gets the closest orthogonal point from the startPoint to the clicked point.
* This is used when the user clicks in space and the router draws only a single
* arc towards the clicked point in one dimension.
* @param startPoint start point of the arc
* @param clicked where the user clicked
* @return an end point to draw to from start point to make a single arc segment.
*/
protected static Point2D getClosestOrthogonalPoint(Point2D startPoint, Point2D clicked) {
Point2D newPoint;
if (Math.abs(startPoint.getX() - clicked.getX()) < Math.abs(startPoint.getY() - clicked.getY())) {
// draw horizontally
newPoint = new Point2D.Double(startPoint.getX(), clicked.getY());
} else {
// draw vertically
newPoint = new Point2D.Double(clicked.getX(), startPoint.getY());
}
return newPoint;
}
/**
* Method to find the closest point to the clicked point when routed from a starting point.
* This point will be at an angle from the starting point which is a multiple of
* a specified angle increment.
* @param startPoint the starting point of the route.
* @param clicked the location of the user's click.
* @param angleIncrement the increment of possible angles.
* @param alignment the grid alignment to use.
* @return the point to use for the end of the wire.
*/
public static Point2D getClosestAngledPoint(Point2D startPoint, Point2D clicked, int angleIncrement, EDimension alignment) {
angleIncrement = Math.abs(angleIncrement) * 10;
// if any angle is possible, just select the clicked point
if (angleIncrement == 0) return clicked;
// special case for Manhattan computation
if (angleIncrement == 900) return getClosestOrthogonalPoint(startPoint, clicked);
// find closest two angles
int angle = DBMath.figureAngle(startPoint, clicked);
int nearest1 = (angle / angleIncrement) * angleIncrement;
while (nearest1 < 0) nearest1 += 3600;
if (nearest1 >= 3600) nearest1 %= 3600;
int nearest2 = (angle < 0) ? nearest1 - angleIncrement : nearest1 + angleIncrement;
while (nearest2 < 0) nearest2 += 3600;
if (nearest2 >= 3600) nearest2 %= 3600;
// find two possible points for the two angles
double distance = clicked.distance(startPoint);
Point2D p1 = new Point2D.Double(distance * DBMath.cos(nearest1), distance * DBMath.sin(nearest1));
Point2D p2 = new Point2D.Double(distance * DBMath.cos(nearest2), distance * DBMath.sin(nearest2));
// of the two angles, take closest to clicked point
double xFinal, yFinal;
double x = clicked.getX() - startPoint.getX();
double y = clicked.getY() - startPoint.getY();
int nearest;
if (p2.distance(x, y) < p1.distance(x, y)) {
xFinal = DBMath.round(p2.getX() + startPoint.getX());
yFinal = DBMath.round(p2.getY() + startPoint.getY());
nearest = nearest2;
} else {
xFinal = DBMath.round(p1.getX() + startPoint.getX());
yFinal = DBMath.round(p1.getY() + startPoint.getY());
nearest = nearest1;
}
Point2D result = new Point2D.Double(xFinal, yFinal);
// grid align the result if it is on a Manhattan line
int direction = -1;
if (nearest == 0 || nearest == 1800) direction = 0;
if (nearest == 900 || nearest == 2700) direction = 1;
if (direction >= 0) DBMath.gridAlign(result, alignment, direction); else
{
// special case for off-angle arcs
if (alignment.getWidth() > 0 && alignment.getHeight() > 0)
{
long ix = Math.round(startPoint.getX() / alignment.getWidth());
long iy = Math.round(startPoint.getY() / alignment.getHeight());
double fx = ix * alignment.getWidth(), fy = iy * alignment.getHeight();
if (DBMath.areEquals(fx, startPoint.getX()) && DBMath.areEquals(fy, startPoint.getY()))
{
// starting point is on grid, see if ending point can be grid-aligned
int gridAngle = DBMath.figureAngle(alignment.getWidth(), alignment.getHeight());
if ((nearest / gridAngle) * gridAngle == nearest)
{
// angle of route can be grid-aligned
DBMath.gridAlign(result, alignment);
}
}
}
}
return result;
}
protected boolean withinBounds(double point, double bound1, double bound2) {
double min, max;
if (bound1 < bound2) {
min = bound1; max = bound2;
} else {
min = bound2; max = bound1;
}
return ((point >= min) && (point <= max));
}
/**
* Returns true if point is on the line segment, false otherwise.
*/
protected boolean onSegment(Point2D point, Line2D line) {
double minX, minY, maxX, maxY;
Point2D head = line.getP1();
Point2D tail = line.getP2();
if (head.getX() < tail.getX()) {
minX = head.getX(); maxX = tail.getX();
} else {
minX = tail.getX(); maxX = head.getX();
}
if (head.getY() < tail.getY()) {
minY = head.getY(); maxY = tail.getY();
} else {
minY = tail.getY(); maxY = head.getY();
}
if ((point.getX() >= minX) && (point.getX() <= maxX) &&
(point.getY() >= minY) && (point.getY() <= maxY))
return true;
return false;
}
/**
* Determines what route quadrant a point is compared to a reference point.
* A route can be drawn vertically or horizontally so this
* method will return a number between 0 and 3, inclusive,
* where quadrants are defined based on the angle relationship
* of the reference point to the point. Imagine a circle with reference point as
* the center and point a point on the circumference of the
* circle. Then theta is the angle described by the arc reference point->point,
* and quadrants are defined as:
*
* quadrant : angle (theta)
*
0 : -45 degrees to 45 degrees
*
1 : 45 degrees to 135 degrees
*
2 : 135 degrees to 225 degrees
*
3 : 225 degrees to 315 degrees (-45 degrees)
*
* @param refPoint reference point
* @param pt variable point
* @return which quadrant point is in.
*/
protected static int findQuadrant(Point2D refPoint, Point2D pt) {
// find angle
double angle = Math.atan((pt.getY()-refPoint.getY())/(pt.getX()-refPoint.getX()));
if (pt.getX() < refPoint.getX()) angle += Math.PI;
if ((angle > -Math.PI/4) && (angle <= Math.PI/4))
return 0;
else if ((angle > Math.PI/4) && (angle <= Math.PI*3/4))
return 1;
else if ((angle > Math.PI*3/4) &&(angle <= Math.PI*5/4))
return 2;
else
return 3;
}
}