/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: LayoutLib.java * * Copyright (c) 2003 Sun Microsystems and Static Free Software * * Electric(tm) is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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.generator.layout; import com.sun.electric.database.EditingPreferences; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import com.sun.electric.database.geometry.DBMath; import com.sun.electric.database.geometry.EPoint; import com.sun.electric.database.geometry.Orientation; import com.sun.electric.database.geometry.Poly; import com.sun.electric.database.hierarchy.Cell; import com.sun.electric.database.hierarchy.Export; import com.sun.electric.database.hierarchy.Library; import com.sun.electric.database.prototype.NodeProto; import com.sun.electric.database.prototype.PortCharacteristic; import com.sun.electric.database.prototype.PortProto; 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.NodeInst; import com.sun.electric.database.topology.PortInst; import com.sun.electric.technology.ArcProto; import com.sun.electric.technology.Layer; import com.sun.electric.technology.SizeOffset; import com.sun.electric.technology.Technology; import com.sun.electric.tool.JobException; import com.sun.electric.tool.Job; import com.sun.electric.tool.io.FileType; import com.sun.electric.tool.io.input.LibraryFiles; import com.sun.electric.tool.io.output.Output; import com.sun.electric.tool.user.dialogs.OpenFile; import com.sun.electric.tool.user.IconParameters; /* * The LayoutLib class provides an assortment of methods that I * found to be useful for programatic layout generation. */ public class LayoutLib { // ---------------------------- public data ------------------------------ /** Use the default size. When a width or height argument has this * value the object should be created with its default * dimension. Note that -DEF_SIZE is also a legal * constant. Negative dimensions specify mirroring for certain * methods. */ public static final double DEF_SIZE = Double.POSITIVE_INFINITY; public static enum Corner { TL, TR, BL, BR; } /** * Open a library for reading. If a library is * already open then return it. Otherwise look for the library * file named libFileName and open that library. * * @param libFileName the fully qualified path name of the Library * file on disk * @return the open Library or null if it can't be found */ public static Library openLibForRead(String libFileName) { URL libFileURL = TextUtils.makeURLToFile(libFileName); String libName = TextUtils.getFileNameWithoutExtension(libFileURL); Library lib = Library.findLibrary(libName); FileType type = OpenFile.getOpenFileType(libFileName, FileType.DEFAULTLIB); IconParameters iconParameters = IconParameters.makeInstance(true); if (lib==null) { lib = LibraryFiles.readLibrary(libFileURL, null, type, false, iconParameters); } Job.error(lib==null, "can't open Library for reading: "+libFileName); return lib; } /** * Open a library for modification. If a library named libName is * already open then return it. Otherwise look for the file named: * libFileName and open that library. Finally, if all else fails * create a new Library and return it. * * @param libName the name of the Library * file on disk * @return the desired library */ // This doesn't work anymore. // public static Library openLibForModify(String libName, String libFileName) { // // return an open Library if it exists // Library lib = Library.findLibrary(libName); // if (lib!=null) return lib; // // // open a Library file if it exists // URL libFileURL = TextUtils.makeURLToFile(libFileName); // lib = Input.readLibrary(libFileURL, OpenFile.Type.ELIB); // if (lib!=null) return lib; // // // create a new Library // lib = Library.newInstance(libName, libFileURL); // // error(lib==null, "can't open Library for modify: "+libName); // return lib; // } public static Library openLibForWrite(String libName) { // return an open Library if it exists Library lib = Library.findLibrary(libName); if (lib!=null) return lib; // create a new Library lib = Library.newInstance(libName, null); URL libURL = TextUtils.makeURLToFile(libName); lib.setLibFile(libURL); Job.error(lib==null, "can't open Library for modify: "+libName); return lib; } /** * Write a library in JELIB format. * @param lib the library to be written. */ public static void writeLibrary(Library lib, int backupScheme) throws JobException { Output.writeLibrary(lib, FileType.JELIB, false, false, false, backupScheme); } /** * Get the width of an ArcInst. The getArcInstWidth method differs * from ArcInst.getLambdaFullWidth() in that it subtracts off the "width * offset". Hence, getArcInstWidth returns a width that matches * that reported by the GUI. * * @param ai the ArcInst whose width is reported * @return the width of the ArcInst. */ public static double getArcInstWidth(ArcInst ai) { double w = ai.getLambdaBaseWidth(); return DBMath.round(w); } /** Get the default width of a NodeProto. The getNodeProtoWidth * method differs from NodeProto.getDefWidth in that it subtracts * off the "width offset". Hence getNodeProtoWidth returns a width * that matches that reported by the GUI. * * @param np the NodeProto we want the width of. * @return the width of the NodeProto. */ public static double getNodeProtoWidth(NodeProto np) { SizeOffset so = np.getProtoSizeOffset(); double w = np.getDefWidth() - so.getLowXOffset() - so.getHighXOffset(); return DBMath.round(w); } /** Get the default height of a NodeProto. The getNodeProtoHeight * method differs from NodeProto.getDefHeight in that it subtracts * off the "height offset". Hence getNodeProtoHeight returns a * height that matches that reported by the GUI. * * @param np the NodeProto we want the height of * @return the height of the NodeProto */ public static double getNodeProtoHeight(NodeProto np) { SizeOffset so = np.getProtoSizeOffset(); double h = np.getDefHeight() - so.getLowYOffset() - so.getHighYOffset(); return DBMath.round(h); } private static void prln(String msg) {System.out.println(msg);} /** * Find the width of the widest wire connected hierarchically to port. * * @param port the PortInst to check for attached wires. * @return the width of the widest wire. This width excludes the * "width offset" so it matches the width reported by the GUI. * If no wire is attached to port then return DEF_SIZE. */ public static double widestWireWidth(PortInst port) { // NodeInst ni = port.getNodeInst(); PortProto pp = port.getPortProto(); double maxWid = -1; for (Iterator<ArcInst> arcs=getArcInstsOnPortInst(port); arcs.hasNext();) { ArcInst ai = arcs.next(); //prln(" arc width: "+getArcInstWidth(ai)); maxWid = Math.max(maxWid, getArcInstWidth(ai)); } if (pp instanceof Export) { double lowerMax = widestWireWidth(((Export)pp).getOriginalPort()); if (lowerMax!=DEF_SIZE) maxWid = Math.max(maxWid, lowerMax); } if (maxWid < 0) return DEF_SIZE; return DBMath.round(maxWid); } /** Return a list of ArcInsts attached to PortInst, pi. * @param pi PortInst on which to find attached ArcInsts. */ public static Iterator<ArcInst> getArcInstsOnPortInst(PortInst pi) { ArrayList<ArcInst> arcs = new ArrayList<ArcInst>(); // NodeInst ni = pi.getNodeInst(); for (Iterator<Connection> it=pi.getConnections(); it.hasNext();) { Connection c = it.next(); arcs.add(c.getArc()); } // for (Iterator it=ni.getConnections(); it.hasNext();) { // Connection c = it.next(); // if (c.getPortInst()==pi) arcs.add(c.getArc()); // } return arcs.iterator(); } /** The center returned by bounds might have a slight amount of rounding * error. Compensate for this by always rounding coordinates to a 103-4 * lambda grid when reading and writing the database. */ public static double roundCenterX(PortInst pi) { return pi.getCenter().getX(); // return DBMath.round(pi.getBounds().getCenterX()); new way avoids rounding here } public static double roundCenterY(PortInst pi) { return pi.getCenter().getY(); // return DBMath.round(pi.getBounds().getCenterY()); } public static Rectangle2D calculateNodeInst(NodeProto np, double x, double y, double width, double height) { if (np instanceof Cell) { width = (width<0 ? -1 : 1) * np.getDefWidth(); height = (height<0 ? -1 : 1) * np.getDefHeight(); } else { SizeOffset so = np.getProtoSizeOffset(); // Take the default width or height if that's what the user wants. // Otherwise adjust the user-specified width or height by the // SizeOffset. double signW = width<0 ? -1 : 1; if (width==DEF_SIZE || width==-DEF_SIZE) { width = signW * np.getDefWidth(); } else { double hi = so.getHighXOffset(); double lo = so.getLowXOffset(); Job.error(lo!=hi, "asymmetric X offset"); width = signW * (Math.abs(width) + hi+lo); } double signH = height<0 ? -1 : 1; if (height==DEF_SIZE || height==-DEF_SIZE) { height = signH * np.getDefHeight(); } else { double hi = so.getHighYOffset(); double lo = so.getLowYOffset(); Job.error(lo!=hi, "asymmetric Y offset"); height = signH * (Math.abs(height) + hi+lo); } } // round all dimensions to a 10e-4 lambda grid x = DBMath.round(x); y = DBMath.round(y); width = DBMath.round(width); height = DBMath.round(height); return new Rectangle2D.Double(x, y, width, height); } /** * Create a new NodeInst. The following geometric transformations * are performed upon the NodeProto in order to arrive at the * final position of the NodeInst in the coordinate space of the * parent: * <ol> * <li> Scale the NodeProto in x and y so that it has dimensions * |width| and |height|. All scaling is performed about the * NodeProto's origin, (0, 0). * <li> If width<0 then mirror the preceding result about the * y-axis. * <li> If height<0 then mirror the preceding result about the * x-axis. * <li> Rotating the preceding result clockwise by angle degrees. * <li> Translate the preceding result by (x, y). Note that the * NodeProto's origin always ends up at (x, y) in the * coordinate space of the parent. * </ol> * * The newNodeInst method differs from NodeInst.newInstance in the * following ways: * * <ul> * <li>The "size offset" is added to the width and height * arguments before the object is created. The result is a * NodeInst that the GUI reports has dimensions: width x height. * <li>The angle is in units of degrees but is rounded to the * nearest tenth degree. * <li>If np is a Cell then the width and height are taken from * the Cell's defaults. The width and height arguments only * specify mirroring. * <li>If np is a Cell then rotation and mirroring are performed * relative to the Cell's origin. * <li>If np is a Cell then the NodeInst is positioned using the * Cell's origin. That is, the resulting NodeInst will map the * Cell's origin to (x, y) in the coordinate space of the parent. * <li>If the width or height arguments are equal to DEF_SIZE then * the NodeInst is created using the NodeProto's default * dimensions. Eventually I will change this to <i>minimum</i> * dimensions. * </ul> * @param np the NodeProto to instantiate * @param width the desired width of the NodeInst * @param height the desired height of the NodeInst * @param x the desired x-coordinate of the NodeProto's origin in * the coordinate space of the parent. If x is negative then the * NodeInst mirrors about the y-axis. * @param y the desired y-coordinate of the NodeProto's origin in * the coordinate space of the parent. If y is negative then the * NodeInst mirrors about the x-axis. * @param angle the angle, in degrees, of rotation about the * NodeProto's origin. * @param parent the Cell that will contain the NodeInst. * @return the new NodeInst. */ public static NodeInst newNodeInst(NodeProto np, double x, double y, double width, double height, double angle, Cell parent) { Rectangle2D rect = calculateNodeInst(np, x, y, width, height); return newNodeInst(np, rect, angle, parent); } public static NodeInst newNodeInst(NodeProto np, Rectangle2D rect, double angle, Cell parent) { double x = rect.getX(); double y = rect.getY(); double width = rect.getWidth(); double height = rect.getHeight(); Orientation orient = Orientation.fromJava((int)Math.round(angle*10), width < 0, height < 0); NodeInst ni = NodeInst.newInstance(np, new Point2D.Double(x, y), Math.abs(width), Math.abs(height), parent, orient, null); Job.error(ni==null, "newNodeInst failed"); // adjust position so that translation is Cell-Center relative if (np instanceof Cell) { Point2D ref = getPosition(ni); ni.move(x-ref.getX(), y-ref.getY()); } return ni; } /** * Modify the position of a NodeInst. The size and position of a * NodeInst in the coordinate space of its parent cell is * determined by 5 parameters: x, y, width, height, and angle, as * described in the JavaDoc for newNodeInst. The modNodeInst * method modifies those parameters. * <p>The modNodeInst method differs from NodeInst.modifyInstance * in the following ways: * <ul> * <li>If ni is an instance of a Cell then mirroring, rotation, * and positioning are performed relative to the Cell's origin * <li>The arguments dw and dh, are added to the absolute values * of the NodeInst's x-size and y-size. * <li>The arguments mirrorAboutXAxis and mirrorAboutYAxis are * used to mirror the NodeInst about the x and y axes. * </ul> * @param ni the NodeInst to modify * @param dx the amount by which to change the x-coordinate of the * NodeInst's position * @param dy the amount by which to change the y-coordinate of the * NodeInst's position * @param dw the amount by which to change to absolute value of * the NodeInst's width * @param dh the amount by which to change to absolute value of * the NodeInst's height. * @param mirrorAboutYAxis if true then toggle the mirroring of * the NodeInst about the y-axis * @param mirrorAboutXAxis if true then toggle the mirroring of * the NodeInst about the x-axis * @param dAngle the amount by which to change the NodeInst's angle */ public static void modNodeInst(NodeInst ni, double dx, double dy, double dw, double dh, boolean mirrorAboutYAxis, boolean mirrorAboutXAxis, double dAngle) { // boolean oldMirX = ni.isMirroredAboutXAxis(); // boolean oldMirY = ni.isMirroredAboutYAxis(); // double oldXS = ni.getXSize() * (oldMirY ? -1 : 1); // double oldYS = ni.getYSize() * (oldMirX ? -1 : 1); double newX = getPosition(ni).getX() + dx; double newY = getPosition(ni).getY() + dy; // double newW = Math.max(ni.getXSize() + dw, 0); // double newH = Math.max(ni.getYSize() + dh, 0); // // boolean newMirX = oldMirX ^ mirrorAboutXAxis; // boolean newMirY = oldMirY ^ mirrorAboutYAxis; // // double newXS = newW * (newMirY ? -1 : 1); // double newYS = newH * (newMirX ? -1 : 1); Orientation dOrient = Orientation.fromJava((int)Math.rint(dAngle*10), mirrorAboutYAxis, mirrorAboutXAxis); ni.modifyInstance(0, 0, dw, dh, dOrient); // ni.modifyInstance(0, 0, newXS-oldXS, newYS-oldYS, // (int)Math.rint(dAngle*10)); ni.move(newX-getPosition(ni).getX(), newY-getPosition(ni).getY()); } /** * Get the position of a NodeInst. In the coordinate space of the * NodeInst's parent, get the x and y-coordinates of the origin of * the NodeInst's NodeProto. * @param ni the NodeInst we want the position of * @return the x and y-coordinates of the origin of the * NodeInst's NodeProto */ public static Point2D getPosition(NodeInst ni) { NodeProto np = ni.getProto(); Point2D p; if (np instanceof Cell) { AffineTransform xForm = ni.transformOut(); p = xForm.transform(new Point2D.Double(0, 0), null); } else { p = ni.getAnchorCenter(); } double x = DBMath.round(p.getX()); double y = DBMath.round(p.getY()); return new Point2D.Double(x,y); } /** * Create a new ArcInst. This differs from ArcInst.newInstance in that * the "width-offset" is added to the width parameter. The result is an * ArcInst that the GUI reports is width wide. * @param ap the ArcProto to instantiate * @param width the desired width of the ArcInst * @param head the head PortInst * @param hX the x-coordinate of the head PortInst * @param hY the y-coordinate of the head PortInst * @param tail the tail PortInst * @param tX the x-coordinate of the tail PortInst * @param tY the y-coordinate of the tail PortInst * @return the new ArcInst */ public static ArcInst newArcInst(ArcProto ap, double width, PortInst head, double hX, double hY, PortInst tail, double tX, double tY) { // Take the default width if that's what the user wants. // Otherwise adjust the user-specified width or height by the // SizeOffset. if (width==DEF_SIZE) { width = ap.getDefaultLambdaBaseWidth(); // width = ap.getDefaultLambdaFullWidth(); // } else { // width += ap.getLambdaWidthOffset(); } hX = DBMath.round(hX); hY = DBMath.round(hY); tX = DBMath.round(tX); tY = DBMath.round(tY); width = DBMath.round(width); ArcInst ai = ArcInst.newInstanceBase(ap, width, // ArcInst ai = ArcInst.newInstanceFull(ap, width, head, tail, new Point2D.Double(hX, hY), new Point2D.Double(tX, tY), null, 0); Job.error(ai==null, "newArcInst failed"); ai.setFixedAngle(true); return ai; } /** * Create a new ArcInst. This differs from ArcInst.newInstance in that * the "width-offset" is added to the width parameter. The result is an * ArcInst that the GUI reports is width wide. * * <p> Connect the new ArcInst to the centers of the PortInsts. * If the centers don't share an X or y-coordinate then connect * the head and the tail using two ArcInsts. The ArcInst attached * to the head is horizontal and the ArcInst attached to the tail * is vertical. * @param ap the head PortInst * @param width the desired width of the ArcInst * @param head the head ArcInst * @param tail the tail ArcInst * @return the ArcInst connected to the tail. */ public static ArcInst newArcInst(ArcProto ap, double width, PortInst head, PortInst tail) { EPoint headP = head.getCenter(); double hX = headP.getX(); // roundCenterX(head); double hY = headP.getY(); // roundCenterY(head); EPoint tailP = tail.getCenter(); double tX = tailP.getX(); // roundCenterX(tail); double tY = tailP.getY(); // roundCenterY(tail); ArcInst ai; if (hX==tX || hY==tY) { // no jog necessary ai = newArcInst(ap, width, head, hX, hY, tail, tX, tY); } else { Cell parent = head.getNodeInst().getParent(); EditingPreferences ep = parent.getEditingPreferences(); NodeProto pinProto = ap.findOverridablePinProto(ep); PortInst pin = newNodeInst(pinProto, tX, hY, DEF_SIZE, DEF_SIZE, 0, parent).getOnlyPortInst(); // debug EPoint pinP = pin.getCenter(); double newX = pinP.getX(); // roundCenterX(pin); double newY = pinP.getY(); // roundCenterY(pin); if (newX!=tX || newY!=hY) { Rectangle2D r = head.getBounds(); double loy = r.getMinY(); double hiy = r.getMaxY(); System.out.println(loy+" "+hiy); Job.error(true, "center moved"); } ai = newArcInst(ap, width, head, pin); ai.setFixedAngle(true); ai = newArcInst(ap, width, pin, tail); } ai.setFixedAngle(true); return ai; } /** * Create an export for a particular layer. * * <p> At the coordinates <code>(x, y)</code> create a NodeInst of * a layer-pin for the layer <code>ap</code>. Export that * layer-pin's PortInst. * * <p> Attach an ArcInst of ArcProto ap to the layer-pin. The * ArcInst is useful because Electric uses the widest ArcInst on a * PortInst as a hint for the width to use for all future * arcs. Because Electric doesn't use the size of layer-pins as * width hints, the layer-pin is created in it's default size. * * <p> <code>newExport</code> seems very specialized, but it's * nearly the only one I use when generating layout. * @param cell the Cell to which to add the Export. * @param name the name of the Export. * @param role the Export's type. * @param ap the ArcProto indicating the layer on which to create * the Export. * @param w width of the ArcInst serving as a hint. * @param x the x-coordinate of the layer pin. * @param y the y-coordinate of the layer pin. */ public static Export newExport(Cell cell, String name, PortCharacteristic role, ArcProto ap, double w, double x, double y) { EditingPreferences ep = cell.getEditingPreferences(); NodeProto np = ap.findOverridablePinProto(ep); Job.error(np==null, "LayoutLib.newExport: This layer has no layer-pin"); double defSz = LayoutLib.DEF_SIZE; NodeInst ni = LayoutLib.newNodeInst(np, x, y, defSz, defSz, 0, cell); LayoutLib.newArcInst(ap, w, ni.getOnlyPortInst(), ni.getOnlyPortInst()); Export e = Export.newInstance(cell, ni.getOnlyPortInst(), name); e.setCharacteristic(role); return e; } public static Rectangle2D roundBounds(Rectangle2D r) { double w = DBMath.round(r.getWidth()); double h = DBMath.round(r.getHeight()); double x = DBMath.round(r.getX()); double y = DBMath.round(r.getY()); return new Rectangle2D.Double(x,y,w,h); } /** * Get the essential or regular bounds. If NodeInst * <code>node</code> has an Essential Bounds then return * it. Otherwise return the regular bounds. * @param node the NodeInst. * @return the Rectangle2D representing the bounds. */ public static Rectangle2D getBounds(NodeInst node) { Rectangle2D bounds = node.findEssentialBounds(); if (bounds==null) bounds = node.getBounds(); return roundBounds(bounds); } /** * Get the essential or regular bounds. If Cell c has an * Essential Bounds then return it. Otherwise return the regular * bounds. * @param c the Cell. * @return the Rectangle2D representing the bounds. */ public static Rectangle2D getBounds(Cell c) { Rectangle2D bounds = c.findEssentialBounds(); if (bounds==null) bounds = c.getBounds(); return roundBounds(bounds); } // --------------------- Abutment methods --------------------------------- // There are too many abutment methods. I need to figure out how // to eliminate some. /** * Move NodeInst so it's left edge is at <code>leftX</code> and * the y-coordinate of it's origin is at * <code>originY</code>. Don't alter the NodeInst's scale or * rotation. * @param node the NodeInst * @param leftX desired x-coordinate of left edge of <code>node</code>. * @param originY desired y-coordinate of <code>node</code>'s origin */ public static void abutLeft(NodeInst node, double leftX, double originY) { double cY = getPosition(node).getY(); Rectangle2D bd = node.findEssentialBounds(); Job.error(bd==null, "can't abut NodeInsts that don't have essential-bounds"); LayoutLib.modNodeInst(node, leftX-bd.getX(), originY-cY, 0, 0, false, false, 0); } /** * Abut an array of NodeInsts left to right. Move the 0th NodeInst * so it's left edge is at <code>leftX</code> and it the * y-coordinate of its origin is at <code>originY</code>. Abut the * remaining nodes left to right. Don't alter any NodeInst's * scale or rotation. * @param leftX desired x-coordinate of left edge of 0th NodeInst. * @param originY desired y-coordinate of all NodeInst origins * @param nodeInsts the ArrayList of NodeInsts. */ public static void abutLeftRight(double leftX, double originY, Collection<NodeInst> nodeInsts) { NodeInst prev = null; for (NodeInst ni : nodeInsts) { if (prev==null) { abutLeft(ni, leftX, originY); } else { abutLeftRight(prev, ni); } prev = ni; } } /** * Abut two NodeInsts left to right. Move <code>rightNode</code> * so its left edge coincides with <code>leftNode</code>'s right * edge, and the y-coordinate of <code>rightNode</code>'s is equal * to the y-coordinate of <code>leftNode</code>'s origin. Don't * move <code>leftNode</code>. Don't alter any node's scale or * rotation. * @param leftNode the NodeInst that doesn't move. * @param rightNode the NodeInst that is moved to butt against * leftNode. */ public static void abutLeftRight(NodeInst leftNode, NodeInst rightNode) { abutLeft(rightNode, getBounds(leftNode).getMaxX(), getPosition(leftNode).getY()); } /** * Abut an array of NodeInsts left to right. Don't move the 0th * node. Abut remaining nodes left to right. Don't alter any * NodeInst's scale or rotation. * @param nodeInsts the ArrayList of NodeInsts */ public static void abutLeftRight(Collection<NodeInst> nodeInsts) { NodeInst prev = null; for (NodeInst ni : nodeInsts) { if (prev!=null) abutLeftRight(prev, ni); prev = ni; } } /** Move a NodeInst so it's bottom edge is at <code>botY</code>. * * <p>Place <code>node</code>'s origin at * <code>originX</code>. Don't alter <code>node</code>'s scale or * rotation. * @param node the NodeInst to move * @param originX desired x-coordinate of NodeInst's origin * @param botY desired y-coordinate of bottom edge of NodeInst */ public static void abutBottom(NodeInst node, double originX, double botY) { double cX = getPosition(node).getX(); Rectangle2D eb = node.findEssentialBounds(); Job.error(eb==null, "can't abut a NodeInst that doesn't have Essential Bounds"); LayoutLib.modNodeInst(node, originX-cX, botY-eb.getMinY(), 0, 0, false, false, 0); } /** * Abut two NodeInsts bottom to top. Move <code>topNode</code> so * its bottom edge coincides with <code>bottomNode</code>'s top * edge, and the y-coordinate of <code>topNode</code>'s origin is * equal to the y-coorinate of <code>bottomNode</code>'s * origin. Don't move <code>bottomNode</code>. Don't alter any * node's scale or rotation. */ public static void abutBottomTop(NodeInst bottomNode, double space, NodeInst topNode) { abutBottom(topNode, getPosition(bottomNode).getX(), getBounds(bottomNode).getMaxY()+space); } /** * Abut a list of NodeInsts bottom to top. Move 0th NodeInst so * it's bottom edge is at botY and it's origin has the * x-coordinate, originX. Abut remaining nodes bottom to top. * Don't alter any NodeInst's scale or rotation. * @param originX desired x-coordinate of all NodeInst reference points. * Lambda units. * @param botY desired y-coordinate of bottom edge of first NodeInst. * @param nodeInsts Collection of NodeInsts to abut. */ public static void abutBottomTop(double originX, double botY, Collection<NodeInst> nodeInsts, double space) { NodeInst prev = null; for (NodeInst ni : nodeInsts) { if (prev==null){ abutBottom(ni, originX, botY); } else { abutBottomTop(prev, space, ni); } prev = ni; } } /** * Abut a list of NodeInsts bottom to top. Don't alter position * of 0th node. Abut the remaining nodes bottom to top. Don't * alter any NodeInst's scale or rotation. * @param nodeInsts the list of NodeInsts to abut. */ public static void abutBottomTop(Collection<NodeInst> nodeInsts, double space) { NodeInst prev = null; for (NodeInst ni : nodeInsts) { if (prev!=null) abutBottomTop(prev, space, ni); prev = ni; } } private static Point2D getPointAtCorner(NodeInst ni, Corner cnr) { Rectangle2D b = ni.findEssentialBounds(); double x = (cnr==Corner.TL || cnr==Corner.BL) ? b.getMinX() : b.getMaxX(); double y = (cnr==Corner.BL || cnr==Corner.BR) ? b.getMinY() : b.getMaxY(); return new Point2D.Double(x, y); } public static void alignCorners(NodeInst niFixed, Corner cnrFixed, NodeInst niMove, Corner cnrMove, double offsetX, double offsetY) { Point2D ptFixed = getPointAtCorner(niFixed, cnrFixed); Point2D ptMove = getPointAtCorner(niMove, cnrMove); double dx = ptFixed.getX() - ptMove.getX() + offsetX; double dy = ptFixed.getY() - ptMove.getY() + offsetY; niMove.move(dx, dy); } /** * Get the bounding box for the layer in the Cell. * Note this does not include geometry from sub-cells. * @param cell the cell to examine * @param function the layer's function * @return a bounding box around all instances of the layer in the cell */ public static Rectangle2D getBounds(Cell cell, Layer.Function function) { Rectangle2D bounds = null; Technology tech = cell.getTechnology(); Layer.Function.Set thisFunction = new Layer.Function.Set(function); // get layer from nodes for (Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); ) { NodeInst ni = it.next(); AffineTransform trans = ni.rotateOut(); Poly [] polys = tech.getShapeOfNode(ni, false, true, thisFunction); if (polys == null) continue; for (int i=0; i<polys.length; i++) { if (polys[i] == null) continue; if (polys[i].getLayer().getFunction() == function) { polys[i].transform(trans); if (bounds == null) bounds = polys[i].getBox(); else bounds = bounds.createUnion(polys[i].getBox()); } } } // get layer from arcs for (Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); ) { ArcInst ai = it.next(); Poly [] polys = tech.getShapeOfArc(ai, thisFunction); if (polys == null) continue; for (int i=0; i<polys.length; i++) { if (polys[i] == null) continue; if (polys[i].getLayer().getFunction() == function) { if (bounds == null) bounds = polys[i].getBox(); else bounds = bounds.createUnion(polys[i].getBox()); } } } return bounds; } }