SchCDUtilsImpl.mesa
Copyright Ó 1985, 1986, 1987 by Xerox Corporation. All rights reserved.
Jean-Marc Frailong December 7, 1987 8:49:00 pm PST
Don Curry January 22, 1988 2:50:01 pm PST
DIRECTORY
Basics,
CD, CDBasics, CDCells, CDDirectory, CDInstances, CDLayers, CDOps, CDPanelFonts, CDProperties, CDRects, CDSatellites, CDSequencer, CDTexts,
Core, CoreOps, CoreGeometry,
ExtractOps,
GList, IO, Rope, RopeList,
SchCDUtils,
Sisyph,
TerminalIO;
SchCDUtilsImpl: CEDAR PROGRAM
IMPORTS Basics, CD, CDBasics, CDCells, CDDirectory, CDInstances, CDLayers, CDOps, CDPanelFonts, CDProperties, CDRects, CDSatellites, CDSequencer, CDTexts, CoreOps, CoreGeometry, ExtractOps, GList, IO, Rope, RopeList, Sisyph, TerminalIO
EXPORTS SchCDUtils
~ BEGIN
Create icon from schematic
NewShell: PUBLIC PROC [name: Rope.ROPE, pins: ARRAY CoreGeometry.Side OF LIST OF Rope.ROPE, design: CD.Design, pinLayer: CD.Layer ← CD.commentLayer, boxLayer: CD.Layer ← CD.commentLayer] RETURNS [shell: SchCDUtils.Shell] ~ {
Build a shell structure based on parameters and defaults extracted from the design.
pinWidth: CD.Number = CDLayers.LayerWidth[design, pinLayer];
shell ← NEW [SchCDUtils.ShellRep ← [
name: name,
pinLayer: pinLayer,
boxLayer: boxLayer,
boxWidth: CDLayers.LayerWidth[design, boxLayer],
pinRect: [x: pinWidth, y: 4*pinWidth],
guard: 4,
grid: CDOps.GetGrid[design],
font: CDPanelFonts.CurrentFont[design],
pins: pins]]};
ShellObject: PUBLIC PROC [shell: SchCDUtils.Shell] RETURNS [obj: CD.Object] ~ {
Create a ChipNDale object for this shell. This function is extremely tricky due to the geometry manipulation (for which CD does not really help..). If you want to modify it, get brand new pad, pencil and eraser for a few drawings...
The following basic invariants should help the intrepid:
- the outermost left/bottom point of pins are grid aligned (+pin width on top & right)
- BuildSideGeometry creates the instances in such a way that the first pin has its lower-left point at (0,0) and that texts may be edited by CD and still stick to the pin
- CD texts need some massaging so that a horizontal text is exactly on the middle of the horizontal axis (c.f. fontCorrection...)
- the numerous orientations used are critical as they allow easier positionning & permit the correct edition of texts
GridUp: PROC [x: CD.Number] RETURNS [v: CD.Number] ~
Round a number up to the grid
{v ← IF x>=0 THEN ((x+grid-1)/grid)*grid ELSE (x/grid)*grid};
GridDown: PROC [x: CD.Number] RETURNS [v: CD.Number] ~
Round a number down to the grid
{v ← IF x>=0 THEN (x/grid)*grid ELSE ((x-grid+1)/grid)*grid};
GridRectUp: PROC [r: CD.Rect] RETURNS [v: CD.Rect] ~
Return a gridded rectangle surrounding the argument
{v ← [x1: GridDown[r.x1], x2: GridUp[r.x2], y1: GridDown[r.y1], y2: GridUp[r.y2]]};
MakeTitle: PROC [text: Rope.ROPE, font: CDTexts.CDFont] RETURNS [inst: CD.Instance, size: CD.Position] ~ {
Create the instance for the title centered around the origin and return an inflated size
fName: Rope.ROPE = font.supposedName;
obj: CD.Object;
IF Rope.IsEmpty[text] THEN RETURN [NIL, [0,0]];
IF Rope.Find[fName, "I", Rope.Length[fName]-1, FALSE]=-1 THEN
font ← CDTexts.MakeFont[fName.Concat["I"], font.scaleI];
obj ← CDTexts.Create[text, font];
size ← CDBasics.SizeOfRect[obj.bbox];
inst ← CDInstances.NewInst[obj, [[-size.x/2, font.origin.y - font.height/2], original]];
size.x ← GridUp[(3*size.x)/2]; -- increase a bit the area taken by the title
size.y ← GridUp[2*size.y]}; -- increase a bit the area taken by the title
BuildSideGeometry: PROC [names: LIST OF Rope.ROPE, pinOrient, textOrient: CD.Orientation, textOffset: CD.Position] RETURNS [iList: CD.InstanceList, size: CD.Position]~ {
Create geometry (pins and names) along an edge of the icon. The size is extended to the grid size.
pin: CD.Object = CDRects.CreateRect[shell.pinRect, shell.pinLayer]; -- all pins are the same
step: CD.Position = CDBasics.OrientedSize[[GridUp[shell.font.height], 0], pinOrient]; -- between pins
pinPos: CD.Position ← [0, 0];
iList ← NIL;
WHILE names#NIL DO
pinInst: CD.Instance = CDInstances.NewInst[pin, [pinPos, pinOrient]];
textInst: CD.Instance = CDInstances.NewInst[CDTexts.Create[names.first, shell.font], [CDBasics.AddPoints[pinPos, textOffset], textOrient]];
pinPos ← CDBasics.AddPoints[pinPos, step];
CDSatellites.Associate[master: pinInst, text: textInst];
iList ← CONS[pinInst, CONS[textInst, iList]];
names ← names.rest;
ENDLOOP;
IF iList#NIL
THEN size ← CDBasics.SizeOfRect[GridRectUp[CDInstances.BoundingRectO[iList]]]
ELSE size ← [0, 0]};
AddBoxSide: PROC [length: CD.Number, at: CD.Position, orient: CD.Orientation] ~ {
CDProperties.PutInstanceProp[
CDCells.IncludeOb[cell: obj, ob: CDRects.CreateRect[[shell.boxWidth, length], shell.boxLayer], trans: [at, orient], mode: dontResize].newInst,
Sisyph.mode.extractProcProp,
$ExtractNull]};
AddInstances: PROC [insts: CD.InstanceList, moveBy: CD.Position] ~ {
Move instances and insert them into the cell
WHILE insts#NIL DO
insts.first.trans.off ← CDBasics.AddPoints[insts.first.trans.off, moveBy];
[] ← CDCells.IncludeInstance[cell: obj, inst: insts.first, mode: dontResize];
insts ← insts.rest;
ENDLOOP};
Center: PROC [mid, left, right, tot: CD.Number] RETURNS [v: CD.Number] ~ {
Center a block between two others taking the height of texts... (grumble)
v ← GridUp[(tot+left-mid-right+shell.font.height)/2]};
grid: CD.Number = shell.grid;
fontCorrection: CD.Number = shell.font.origin.y - shell.font.height/2; -- CDTexts are crazy...
guard: CD.Number = shell.guard+shell.boxWidth; -- the real guard
margin: CD.Number = GridUp[2*(shell.pinRect.y+shell.boxWidth)]; -- left/right safety margin
iLeft, iRight, iTop, iBottom: CD.InstanceList; -- list of instances for pins on all 4 sides
sLeft, sRight, sTop, sBottom: CD.Position; -- bbox for the 4 sides
title: CD.Instance; titleSize: CD.Position;
box: CD.Rect;
width, height: CD.Number;
Create the object into which instances will be later added
obj ← CDCells.CreateEmptyCell[];
Compute the instances for the 4 sides and the title (not well placed yet)
[iTop, sTop] ← BuildSideGeometry[names: shell.pins[top],
pinOrient: original, textOrient: rotate270,
textOffset: [fontCorrection, -guard]];
[iBottom, sBottom] ← BuildSideGeometry[names: shell.pins[bottom],
pinOrient: rotate180X, textOrient: rotate90X,
textOffset: [fontCorrection, guard]];
[iRight, sRight] ← BuildSideGeometry[names: shell.pins[right],
pinOrient: rotate90X, textOrient: mirrorX,
textOffset: [-guard, fontCorrection]];
[iLeft, sLeft] ← BuildSideGeometry[names: shell.pins[left],
pinOrient: rotate90, textOrient: original,
textOffset: [guard, fontCorrection]];
[title, titleSize] ← MakeTitle[shell.name, shell.font];
Compute a reasonable size for the nice box and prepare reposition.
width ← sLeft.x+sRight.x+MAX[sBottom.x, sTop.x, titleSize.x];
height ← MAX[sLeft.y+margin, sRight.y+margin, sBottom.y+sTop.y+titleSize.y];
box ← [ -- touching all pins on the inside
x1: IF iLeft=NIL THEN 0 ELSE shell.pinRect.y,
x2: shell.pinRect.x + (IF iRight=NIL THEN width ELSE width-shell.pinRect.y),
y1: IF iBottom=NIL THEN 0 ELSE shell.pinRect.y,
y2: shell.pinRect.x + (IF iTop=NIL THEN height ELSE height-shell.pinRect.y)];
Move the sides at their final locations, add box and title if present
AddInstances[iBottom, [Center[sBottom.x, sLeft.x, sRight.x, width], box.y1]];
AddInstances[iTop, [Center[sTop.x, sLeft.x, sRight.x, width], box.y2]];
AddInstances[iLeft, [box.x1, Center[sLeft.y, 0, 0, height]]];
AddInstances[iRight, [box.x2, Center[sRight.y, 0, 0, height]]];
AddBoxSide[box.y2-box.y1, [box.x1, box.y1], original]; -- left
AddBoxSide[box.y2-box.y1, [box.x2, box.y2], rotate180]; -- right
AddBoxSide[box.x2-box.x1, [box.x2, box.y1], rotate90]; -- bot
AddBoxSide[box.x2-box.x1, [box.x1, box.y2], rotate270]; -- top
IF title#NIL THEN -- reposition the title in the center of the inside of the icon
AddInstances[LIST[title], [(width+sLeft.x-sRight.x)/2, (height+sBottom.y-sTop.y)/2]];
Update the object
CDCells.ToSequenceMode[obj];
[] ← CDCells.ResizeCell[design: NIL, cell: obj]};
CellTypePins: PUBLIC PROC
[cell: Core.CellType] RETURNS [pins: ARRAY CoreGeometry.Side OF LIST OF Rope.ROPE] ~ {
Return list of publics on each side, sorted in increasing coordinates. A name may appear on multiple sides depending on the source.
EachWire: CoreOps.EachWireProc ~ {
EachPin: CoreGeometry.EachPinProc ~ {
elemsOn[side] ← CONS [NEW[ElemRep ← [name, min]], elemsOn[side]];
};
name: Rope.ROPE = CoreOps.GetShortWireName[wire];
IF Rope.IsEmpty[name]
THEN TerminalIO.PutRope["*** Warning: Unnamed public.\n"]
ELSE [] ← CoreGeometry.EnumerateSides[Sisyph.mode.decoration, cell, wire, EachPin];
RETURN[FALSE, FALSE];
};
Increasing: GList.CompareProc ~ { -- Inverted order because of later list copy !!!
RETURN [Basics.CompareInt[NARROW [ref2, Elem].at, NARROW [ref1, Elem].at]]};
Elem: TYPE ~ REF ElemRep;
Elems: TYPE ~ LIST OF Elem;
ElemRep: TYPE ~ RECORD [name: Rope.ROPE, at: INT];
elemsOn: ARRAY CoreGeometry.Side OF Elems ← ALL [NIL];
[] ← CoreOps.VisitWireSeq[cell.public, EachWire]; -- build list for each side
FOR side: CoreGeometry.Side IN CoreGeometry.Side DO -- sort each side & build icon
list: Elems ← NARROW[GList.Sort[elemsOn[side], Increasing]];
pins[side] ← NIL;
WHILE list#NIL DO
pins[side] ← CONS [list.first.name, pins[side]];
list ← list.rest;
ENDLOOP;
ENDLOOP;
};
NewerCellTypePins: PUBLIC PROC [cell: Core.CellType] RETURNS [pins: ARRAY CoreGeometry.Side OF LIST OF Rope.ROPE] ~ {
Returns publics on each side sorted in alphabetic order.
A name may appear on multiple sides depending on the source.
EachWire: CoreOps.EachWireProc ~ {
EachPin: CoreGeometry.EachPinProc ~ {pins[side] ← CONS [name, pins[side]]};
name: Rope.ROPE = CoreOps.GetShortWireName[wire];
IF Rope.IsEmpty[name]
THEN TerminalIO.PutRope["*** Warning: Unnamed public.\n"]
ELSE {
[] ← CoreGeometry.EnumerateSides[Sisyph.mode.decoration, cell, wire, EachPin];
RETURN[FALSE, FALSE]}}; -- use highest level name only
[] ← CoreOps.VisitWireSeq[cell.public, EachWire]; -- build list for each side
FOR side: CoreGeometry.Side IN CoreGeometry.Side DO -- sort and remove multiple entries
pins[side] ← RopeList.Sort[pins[side], RopeList.IgnoreCase];
FOR list: LIST OF Rope.ROPE ← pins[side], list.rest WHILE list#NIL DO
WHILE list.rest#NIL AND list.first.Equal[list.rest.first] DO
list.rest ← list.rest.rest;
ENDLOOP;
ENDLOOP;
ENDLOOP};
OldCellTypePins: PROC
[cell: Core.CellType] RETURNS [pins: ARRAY CoreGeometry.Side OF LIST OF Rope.ROPE] ~ {
Return list of publics on each side, sorted in increasing coordinates. A public having pin geometry on multiple places get allocated to a single side & position arbitrarily. Unnamed publics are blissfully ignored...
EachWire: CoreOps.EachWireProc ~ {
EachPin: CoreGeometry.EachPinProc ~ {
elemsOn[side] ← CONS [NEW[ElemRep ← [name, min]], elemsOn[side]];
quit ← TRUE}; -- enumerate only the first pin of each wire...
name: Rope.ROPE = CoreOps.GetShortWireName[wire];
IF Rope.IsEmpty[name] THEN RETURN; -- ignore unnamed wires
[] ← CoreGeometry.EnumerateSides[decoration, cell, wire, EachPin]};
Increasing: GList.CompareProc ~ { -- Inverted order because of later list copy !!!
RETURN [Basics.CompareInt[NARROW [ref2, Elem].at, NARROW [ref1, Elem].at]]};
Elem: TYPE ~ REF ElemRep;
Elems: TYPE ~ LIST OF Elem;
ElemRep: TYPE ~ RECORD [name: Rope.ROPE, at: INT];
decoration: CoreGeometry.Decoration = Sisyph.mode.decoration;
elemsOn: ARRAY CoreGeometry.Side OF Elems ← ALL [NIL];
[] ← CoreOps.VisitWireSeq[cell.public, EachWire]; -- build list for each side
FOR side: CoreGeometry.Side IN CoreGeometry.Side DO -- sort each side & build icon
list: Elems ← NARROW[GList.Sort[elemsOn[side], Increasing]];
pins[side] ← NIL;
WHILE list#NIL DO
pins[side] ← CONS [list.first.name, pins[side]];
list ← list.rest;
ENDLOOP;
ENDLOOP};
CreateIconCommand: PROC [comm: CDSequencer.Command] ~ {
User interface to create icon from schematic
schName, shortName, iconName: Rope.ROPE;
icon: CD.Object;
cell: Core.CellType;
shell: SchCDUtils.Shell;
nms: ARRAY CoreGeometry.Side OF LIST OF Rope.ROPE;
selected: CD.Instance = CDOps.TheInstance[comm.design, "CreateIcon\n"];
Get name of source and target
IF selected=NIL THEN RETURN; -- nothing to do
schName ← CDDirectory.Name[selected.ob, comm.design];
IF schName.IsEmpty[] THEN
{TerminalIO.PutF["*** Selected schematic has no name.\n"]; RETURN};
IF NOT Rope.Match["*.sch", schName] THEN TerminalIO.PutF["*** Convention for schematics is to suffix them with '.sch'.\n"];
shortName ← TerminalIO.RequestRope["Type icon short name: "];
IF shortName.IsEmpty[] THEN
shortName ← IF Rope.Match["*.sch", schName] THEN schName.Substr[0, schName.Length[]-4] ELSE schName;
iconName ← shortName.Concat[".icon"];
IF CDDirectory.Fetch[comm.design, iconName]#NIL THEN { -- maybe offer replace ?
TerminalIO.PutF["*** The icon %g already exists!\n", IO.rope[iconName]];
RETURN};
Get the source cell type
cell ← ExtractOps.ExtractCDInstanceCellTypeAndReport[selected, comm.design, Sisyph.mode];
IF cell=NIL THEN RETURN; -- message already printed, service of ExtractOps
Get the names
nms ← CellTypePins[cell];
Build the object, instantiate it & return
shell ← NewShell[shortName, nms, comm.design];
shell.grid ← CDOps.GetGrid[comm.design, comm]; -- default might not be perfect...
icon ← ShellObject[shell];
CDProperties.PutObjectProp[icon, Sisyph.mode.extractProcProp, $SisyphExtractCellIcon];
CDProperties.PutObjectProp[icon, $IconFor, schName];
CDCells.SetSimplificationTreshhold[cell: icon, val: 30, inPixels: TRUE];
IF NOT CDDirectory.Include[comm.design, icon, iconName] THEN ERROR; -- Abnormal
[] ← CDOps.IncludeObjectI[comm.design, icon, comm.pos];
TerminalIO.PutF["%g generated\n", IO.rope[iconName]]};
Big wires versus small wires
IsWire: PROC [inst: CD.Instance] RETURNS [BOOL] ~ {
Return TRUE if the object of the instance is a schematics wire
RETURN [inst.ob.layer=CD.commentLayer AND CDRects.IsBareRect[inst.ob]]};
FiddlePos: PROC [inst: CD.Instance, ir: CD.Rect] = {
MoreZero: PROC [a, b: INT] RETURNS [INT𡤀] = {
IF a>=0 AND b>=0 THEN RETURN [MIN[a, b]];
IF a<=0 AND b<=0 THEN RETURN [MAX[a, b]];
};
r: CD.Rect ← CDInstances.InstRectI[inst];
inst.trans.off.x ← inst.trans.off.x + MoreZero[ir.x1-r.x1, ir.x2-r.x2];
inst.trans.off.y ← inst.trans.off.y + MoreZero[ir.y1-r.y1, ir.y2-r.y2]};
SwitchWireSizeCommand: PROC [comm: CDSequencer.Command] ~ {
The command to enlarge/shrink all selected wires
shrunk: INT ← 0;
enlarged: INT ← 0;
smallW: CD.Number = CDLayers.LayerWidth[comm.design, CD.commentLayer];
largeW: CD.Number = 3*smallW;
TerminalIO.PutRope["switch size of selected wires\n"];
FOR list: CD.InstanceList ← CDOps.InstList[comm.design], list.rest WHILE list#NIL DO
inst: CD.Instance ← list.first; -- instance may be modified !
IF inst.selected AND IsWire[inst] THEN {
size: CD.Position = CD.InterestSize[inst.ob];
delta: CD.Number;
SELECT MIN [size.x, size.y] FROM
smallW => delta ← smallW;
largeW => delta ← -smallW;
ENDCASE => delta ← 0;
IF delta#0 THEN { -- modify the instance
goal: CD.Rect = CDBasics.Extend[CDInstances.InstRectI[inst], delta];
oldRect: CD.Rect = CDInstances.InstRectO[inst];
inst.ob ← CDRects.CreateRect[CDBasics.AddPoints[size, [2*delta, 2*delta]], inst.ob.layer];
inst.trans.off ← CDBasics.SubPoints[inst.trans.off, [delta, delta]];
FiddlePos[inst, goal]; -- move instance to fit goal: transf are the plague of CD
CDOps.Redraw[comm.design, CDBasics.Surround[oldRect, CDInstances.InstRectO[inst]]];
IF delta>0 THEN enlarged ← enlarged + 1 ELSE shrunk ← shrunk + 1 } };
ENDLOOP;
TerminalIO.PutF
["Enlarged %g wire(s), shrunk %g wire(s)\n", IO.int[enlarged], IO.int[shrunk]]};
Initialization
CDSequencer.ImplementCommand[$CreateIconCommand, CreateIconCommand];
CDSequencer.ImplementCommand[$SwitchWireSize, SwitchWireSizeCommand];
END.