ImplicitDesignImpl.mesa
Copyright Ó 1985, 1990 by Xerox Corporation. All rights reserved.
Bloomenthal, February 26, 1993 4:22 pm PST
DIRECTORY CedarProcess, Commander, CommanderOps, Controls, Convert, Draw2d, FileNames, G3dBasic, G3dDraw, G3dShape, G3dTool, G3dVector, Icons, Imager, ImplicitDebug, ImplicitDesign, ImplicitMinimal, IO, PFS, ProcessProps, Real, Rope, VFonts;
ImplicitDesignImpl: CEDAR PROGRAM
IMPORTS CedarProcess, Commander, CommanderOps, Controls, Convert, Draw2d, FileNames, G3dDraw, G3dShape, G3dTool, G3dVector, Icons, Imager, ImplicitDebug, ImplicitMinimal, IO, PFS, ProcessProps, Real, Rope, VFonts
EXPORTS ImplicitDesign
~ BEGIN
Types
ForkableProc:  TYPE ~ CedarProcess.ForkableProc;
CommandProc:  TYPE ~ Commander.CommandProc;
ButtonList:   TYPE ~ Controls.ButtonList;
ClickProc:   TYPE ~ Controls.ClickProc;
Box:     TYPE ~ G3dBasic.Box;
Triple:    TYPE ~ G3dBasic.Triple;
Tool:     TYPE ~ ImplicitDesign.Tool;
ToolRep:    TYPE ~ ImplicitDesign.ToolRep;
ROPE:    TYPE ~ Rope.ROPE;
ImplicitDesign Tool
icon:     Icons.IconFlavor ¬ Icons.NewIconFromFile["ImplicitSurfaces.icon", 0];
gtool:     Tool;
MakeTool: PUBLIC PROC [
name:    ROPE,
function:   ImplicitMinimal.Function,
start:    ImplicitDesign.StartProc ¬ NIL,
ops:    G3dTool.Operations ¬ G3dTool.allOps,
extraButtons:  ButtonList ¬ NIL,
extraControls: Controls.ControlList ¬ NIL,
controlSizes:  Controls.ControlSizes ¬ [],
useArcBalls:  BOOL ¬ TRUE,
arcBallSize:  INT ¬ 136,
client:    G3dTool.Client ¬ [],
graphicsHeight: INT ¬ 475,
background:  Triple ¬ [0.3, 0.3, 1.0],
noOpen:   BOOL ¬ FALSE,
camera:   G3dTool.CameraRep ¬ [[0.0, 2.0, 0.0], [], 1.0, 60.0],
nViews:   INT ¬ 1,
toolSettings:  ToolRep ¬ [],
tool3dSettings: G3dTool.ToolRep ¬ []]
RETURNS [t: Tool]
~ {
AddButton: PROC [name: ROPE, proc: ClickProc, guarded: BOOL ¬ FALSE] ~ {
extraButtons ¬ CONS[Controls.ClickButton[name, proc, t, 1,,,,,, guarded], extraButtons];
};
cmd: Commander.Handle ¬ NARROW[ProcessProps.GetProp[$CommanderHandle]];
ref: REF ANY ¬ CommanderOps.GetProp[cmd, $Tool];
calledByMakeSurface: BOOL ¬ ref # NIL;
showTool: BOOL ¬ CommanderOps.GetProp[cmd, $ShowTool] # NIL;
t ¬ IF calledByMakeSurface THEN NARROW[ref] ELSE NEW[ToolRep ¬ toolSettings];
IF NOT calledByMakeSurface THEN { -- call originated with ImplicitDesign command
FOR l: ButtonList ¬ extraButtons, l.rest WHILE l # NIL DO
l.first.row ¬ MAX[2, l.first.row];
ENDLOOP;
AddButton["Triangles", TriangulateButton];
AddButton["Flat", FlatnessButton];
AddButton["Size", SizeButton];
AddButton["Level", LevelButton];
AddButton["Grid", GridButton];
};
IF function # NIL THEN t.function ¬ function;
t.client ¬ client;
t.startProc ¬ start;
gtool ¬ t;
t.tool3d ¬ G3dTool.MakeTool[
name: name,
nViews: nViews,
ops: ops,
extraButtons: extraButtons,
extraControls: extraControls,
controlSizes: controlSizes,
client: [data: t, draw: Draw, stop: Stop, destroy: Destroy],
useArcBalls: useArcBalls,
arcBallSize: arcBallSize,
graphicsHeight: graphicsHeight,
background: background,
icon: icon,
camera: camera,
toolSettings: tool3dSettings];
ImplicitDebug.SetDebug[[out: t.tool3d.cmd.out]];
SetToolFromLog[t, name];
};
Destroy: Controls.DestroyProc ~ {
tool: Tool ¬ NARROW[clientData];
IF tool.client.destroy # NIL THEN tool.client.destroy[viewer, reason, tool.client.data];
};
Tool Control
ToolBusy: PUBLIC PROC [tool: Tool] RETURNS [b: BOOL] ~ {b ¬ G3dTool.ToolBusy[tool.tool3d]};
MaybeFork: PUBLIC PROC [tool: Tool, proc: ForkableProc, clientData: REF ¬ NIL]
RETURNS [forked: BOOL]
~ {
forked ¬ G3dTool.MaybeFork[tool.tool3d, proc, IF clientData = NIL THEN tool ELSE clientData];
};
Stop: G3dTool.StopProc ~ {StopTool[NARROW[clientData], reason]};
StopTool: PUBLIC PROC [tool: Tool, reason: ROPE ¬ NIL, waitTilAborted: BOOL ¬ FALSE] ~ {
G3dTool.Stop[tool.tool3d, reason, waitTilAborted];
IF tool.client.stop # NIL THEN tool.client.stop[tool.client.data, reason];
};
Triangulation
Reset: PUBLIC PROC [tool: Tool] ~ {
G3dTool.DeleteShape[tool.tool3d, "Imp"];
Repaint[tool];
};
TriangulateButton: Controls.ClickProc ~ {
[] ¬ MaybeFork[NARROW[clientData], ForkTriangulate];
};
ForkTriangulate: ForkableProc ~ {Triangulate[NARROW[data]]};
Triangulate: PUBLIC PROC [tool: Tool] ~ {
G3dTool.StartTimer[tool.tool3d];
Reset[tool];
ImplicitDebug.SetDebug[[out: tool.tool3d.cmd.out]];
DoTriangulate[tool];
};
DoTriangulate: PROC [t: Tool] ~ {
Status: ImplicitMinimal.TriangleProc ~ {
CedarProcess.CheckAbort[];
t.triangle ¬ [vertices[i1].point, vertices[i2].point, vertices[i3].point];
Repaint[t, $NewTriangle];
};
adaptive: REAL ¬ RealFns.CosDeg[t.flatness];
s: G3dShape.Shape;
start: Triple ¬ IF t.startProc # NIL
THEN t.startProc[t.client.data]
ELSE ImplicitMinimal.FindStart[[], t.function, t.size, t.level, t.client.data
! ImplicitMinimal.Error => {TSWrite[t, Rope.Concat[reason, "\n"]]; GOTO Bad}];
TSWrite[t, IO.PutFR["start: (%g, %g, %g), ", IO.real[start.x], IO.real[start.y], IO.real[start.z]]];
s ¬ ImplicitMinimal.ShapeFromFunction[
t.function, t.level, t.size, t.client.data, start, Status
! ImplicitMinimal.Error => {TSWrite[t, Rope.Concat[reason, "\n"]]; GOTO Bad}];
IF s = NIL
THEN {TSWrite[t, "no shape\n"]; RETURN}
ELSE {
edges: G3dShape.EdgeSequence ¬ s.edges ¬ G3dShape.MakeEdges[s];
gTimes2: INT ¬ 2+s.edges.length-INT[s.vertices.length+s.surfaces.length];
s.name ¬ "Imp";
s.selected ¬ TRUE;
G3dTool.AddShape[t.tool3d, s];
TSWrite[t, IO.PutFR["%g points, %g triangles (%7.2f secs), ",
IO.int[s.vertices.length],
IO.int[s.surfaces.length],
IO.real[G3dTool.ElapsedTime[t.tool3d]]]];
IF gTimes2 MOD 2 = 0
THEN TSWrite[t, IO.PutFR1["genus = %g\n", IO.int[gTimes2/2]]]
ELSE TSWrite[t, IO.PutFLR["error: 2*genus = 2+E-F-V = 2+%g-%g-%g = %g\n", LIST[
IO.int[s.edges.length], IO.int[s.surfaces.length],
IO.int[s.vertices.length], IO.int[gTimes2]]]];
TSWrite[t, UpdateLog[t, t.tool3d.name, ParametersMessage[t]]];
};
EXITS Bad => NULL;
};
SizeButton: Controls.ClickProc ~ {
t: Tool ¬ NARROW[clientData];
t.size ¬ GetReal[t, "TrackSize", t.size];
};
LevelButton: Controls.ClickProc ~ {
t: Tool ¬ NARROW[clientData];
t.level ¬ GetReal[t, "Threshold", t.level];
};
Discretization
GridButton: Controls.ClickProc ~ {
Output one byte (normalized between min and max function values over range of shape)
per grid location, where the grid range is the bounding box of shape and grid spacing is
tool.size. Output is to the given file, which contains a brief header and then the bytes,
looping first in z, then y, then x.
tool: Tool ¬ NARROW[clientData];
IF tool.tool3d.shapes = NIL
THEN TSWrite[tool, "no shape to gridify\n"]
ELSE {
DoWithValue: PROC [action: PROC [value: REAL]] ~ {
FOR k: INT IN [0..nz) DO
z: REAL ¬ box.min.z+tool.size*(REAL[k]-1.5);
FOR j: INT IN [0..ny) DO
y: REAL ¬ box.min.y+tool.size*(REAL[j]-1.5);
FOR i: INT IN [0..nx) DO
x: REAL ¬ box.min.x+tool.size*(REAL[i]-1.5);
action[tool.function[[x, y, z], tool.client.data]];
ENDLOOP;
ENDLOOP;
ENDLOOP;
};
MinMax: PROC [value: REAL] ~ {min ¬ MIN[min, value]; max ¬ MAX[max, value]};
Output: PROC [value: REAL] ~ {IO.PutByte[out, BYTE[Real.Round[(value-min)*scale]]]};
out: IO.STREAM;
min: REAL ¬ Real.PlusInfinity;
max, scale: REAL ¬ Real.MinusInfinity;
box: Box ¬ G3dShape.BoundingBox[tool.tool3d.shapes[0]];
objectSize: Triple ¬ G3dVector.Sub[box.max, box.min];
halfGrid: REAL ¬ 0.5*tool.size;
# cells is objectSize/tool.size, which results in 1+objectSize/tool.size samples;
however, samples are shifted -tool.size/2 so the first cell fully encloses the object;
this requires an additional, final cell, so total # samples is 2+objectSize/tool.size.
Actually, for some discontinuous tilers, gradients are computed and thus require
an additional cell in all six directions, so # samples is 4+objectSize/tool.size.
nx: INT ¬ 4+Real.Ceiling[objectSize.x/tool.size];
ny: INT ¬ 4+Real.Ceiling[objectSize.y/tool.size];
nz: INT ¬ 4+Real.Ceiling[objectSize.z/tool.size];
prompt: ROPE ¬ IO.PutFLR[
"(%g, %g, %g) grid (%g elements), filename (<CR> to abort): ", LIST[
IO.int[nx], IO.int[ny], IO.int[nz], IO.int[nx*ny*nz]]];
name: ROPE ¬ Controls.TypescriptRead[tool.tool3d.typescript, prompt];
IF Rope.IsEmpty[name] THEN RETURN;
IF Rope.Fetch[name, 0] # '/ THEN name ¬ Rope.Concat[tool.tool3d.directory, name];
out ¬ PFS.StreamOpen[PFS.PathFromRope[name], $create];
DoWithValue[MinMax];
scale ¬ 255.0/(max-min);
IO.PutF[out, "-- %g discrete implicit function values from ImplicitDesign: %g\n",
IO.int[nx*ny*nz], IO.rope[tool.tool3d.name]];
IO.PutF[out, "-- initial function range from %g to %g mapped to 0 to 255\n",
IO.real[min], IO.real[max]];
IO.PutF[out, "-- isovalue is %g, which maps to %g\n",
IO.real[tool.level], IO.real[(tool.level-min)*scale]];
IO.PutFL[out, "-- surface bounds: (%5.3f, %5.3f, %5.3f) to (%5.3f, %5.3f, %5.3f)\n",
LIST[IO.real[box.min.x], IO.real[box.min.y], IO.real[box.min.z],
  IO.real[box.max.x], IO.real[box.max.y], IO.real[box.max.z]]];
IO.PutFL[out, "-- grid bounds: (%5.3f, %5.3f, %5.3f) to (%5.3f, %5.3f, %5.3f)\n", LIST[
IO.real[box.min.x-halfGrid], IO.real[box.min.y-halfGrid], IO.real[box.min.z-halfGrid],
IO.real[box.max.x+halfGrid], IO.real[box.max.y+halfGrid], IO.real[box.max.z+halfGrid]]];
IO.PutF1[out, "-- fixed cubical grid spacing is %g\n", IO.real[tool.size]];
IO.PutF[out, "-- x, y, z resolution: %g, %g, %g\n", IO.int[nx], IO.int[ny], IO.int[nz]];
IO.PutRope[out, "-- z is the outer loop, y the middle loop, x the inner loop\n"];
DoWithValue[Output];
IO.Close[out];
};
};
Display
Repaint: PUBLIC PROC [tool: Tool, whatChanged: REF ANY ¬ NIL] ~ {
G3dTool.Repaint[tool.tool3d, whatChanged];
};
Draw: G3dTool.DrawProc ~ {
DrawClient: PROC ~ {
IF t.client.draw # NIL
THEN t.client.draw[context, viewer, whatChanged, view, vp, v, tool, t.client.data];
};
t: Tool ¬ NARROW[clientData];
IF v # NIL AND v.index = 0
THEN
SELECT whatChanged FROM
$Client => {Draw2d.Clear[context]; DrawClient[]};
$NewTriangle => {
G3dDraw.SetColor[context, [0.0, 0.7, 0.7]];
G3dDraw.Triangle[context, t.triangle.p1, t.triangle.p2, t.triangle.p3, view, vp];
};
ENDCASE => DrawClient[]
ELSE SELECT whatChanged FROM
NIL, $IPOut, $NoClear, $Camera, $Scene, $Client => DrawClient[];
ENDCASE;
};
Support
GetReal: PROC [t: Tool, prompt: ROPE, value: REAL] RETURNS [REAL] ~ {
RETURN[Controls.GetReal[t.tool3d.typescript, prompt, value]];
};
Eq: PROC [r1, r2: ROPE] RETURNS [b: BOOL] ~ {b ¬ Rope.Equal[r1, r2, FALSE]};
TSWrite: PROC [t: Tool, rope: ROPE] ~ {Controls.TypescriptWrite[t.tool3d.typescript, rope]};
Logging
logName: ROPE ¬ "ImplicitSurfaces.params";
ParametersMessage: PUBLIC PROC [t: Tool] RETURNS [ROPE] ~ {
RETURN[IO.PutFR["level: %g, size: %g", IO.real[t.level], IO.real[t.size]]];
};
FullFileName: PROC [t: Tool, name: ROPE] RETURNS [PFS.PATH] ~ {
RETURN[PFS.PathFromRope[Rope.Concat[t.tool3d.directory, name]]];
};
UpdateLog: PUBLIC PROC [tool: Tool, key, entry: ROPE] RETURNS [err: ROPE ¬ NIL] ~ {
in, out: IO.STREAM;
inRope: ROPE ¬ NIL;
found: BOOL ¬ FALSE;
inRope ¬ PFS.RopeOpen[FullFileName[tool, logName] ! PFS.Error => CONTINUE].rope;
IF inRope # NIL THEN in ¬ IO.RIS[inRope];
out ¬ PFS.StreamOpen[FullFileName[tool, logName], create];
IF in # NIL THEN WHILE NOT IO.EndOf[in] DO
line: ROPE ¬ IO.GetLineRope[in];
word: ROPE ¬ Rope.Substr[line,, Rope.SkipTo[line,, ": \t"]];
IF Eq[word, key]
THEN {
found ¬ TRUE;
IO.PutF[out, "%g: %g\n", IO.rope[key], IO.rope[entry]];
}
ELSE IO.PutF1[out, "%g\n", IO.rope[line]];
ENDLOOP;
IF NOT found THEN IO.PutF[out, "%g: %g\n", IO.rope[key], IO.rope[entry]];
IO.Close[out];
};
GetLogEntry: PUBLIC PROC [tool: Tool, key: ROPE] RETURNS [r: ROPE ¬ NIL] ~ {
in: IO.STREAM;
IO.PutF1[tool.tool3d.cmd.out, "full file name = %g\n",
IO.rope[PFS.RopeFromPath[FullFileName[tool, logName]]]];
in ¬ PFS.StreamOpen[FullFileName[tool, logName] ! PFS.Error => CONTINUE];
IF in # NIL THEN WHILE NOT IO.EndOf[in] DO
line: ROPE ¬ IO.GetLineRope[in];
IF Eq[Rope.Substr[line,, Rope.SkipTo[line,, ": \t"]], key] THEN RETURN[line];
ENDLOOP;
};
SetToolFromLog: PROC [t: Tool, name: ROPE] ~ {
GetArg: PROC [key: ROPE] RETURNS [rope: ROPE ¬ NIL] ~ {
i: INT ¬ Rope.Find[entry, key,, FALSE];
IF i # -1 THEN {
ii: INT ¬ Rope.SkipOver[entry, i+Rope.Length[key], ": \t"];
rope ¬ Rope.Substr[entry, ii, Rope.SkipTo[entry, ii, ",: \t\n"]-ii];
};
};
GetInt: PROC [key: ROPE, defValue: INT] RETURNS [r: INT] ~ {
r ¬ Convert.IntFromRope[GetArg[key] ! Convert.Error => {r ¬ defValue; CONTINUE}];
};
GetReal: PROC [key: ROPE, defValue: REAL] RETURNS [r: REAL] ~ {
r ¬ Convert.RealFromRope[GetArg[key] ! Convert.Error => {r ¬ defValue; CONTINUE}];
};
entry: ROPE ¬ GetLogEntry[t, name];
IF entry = NIL THEN RETURN;
t.level ¬ GetReal["level", t.level];
t.size ¬ GetReal["size", t.size];
};
Registration and Dispatching
Registration: TYPE ~ RECORD [
command:   ROPE,         -- name of CommandTool command
function:    ROPE,         -- name of implicit function
action:    CommandProc,      -- proc to be called
doc:     ROPE         -- documentation of function
];
registry:  LIST OF Registration ¬ NIL;
RegistrationMatch: PROC [item: Registration, command, function: ROPE] RETURNS [BOOL] ~ {
RETURN[Eq[item.command, command] AND Eq[item.function, function]];
};
Register: PUBLIC PROC [
function: ROPE,
action: CommandProc,
doc: ROPE,
command: ROPE ¬ NIL]
~ {
item: Registration;
IF command = NIL THEN command ¬ "ImplicitDesign";
item ¬ [command, function, action, doc];
IF registry = NIL
THEN registry ¬ LIST[item]
ELSE FOR l: LIST OF Registration ¬ registry, l.rest WHILE l # NIL DO
IF RegistrationMatch[l.first, command, function] THEN {l.first ¬ item; EXIT};
IF l.rest = NIL THEN {l.rest ¬ LIST[item]; EXIT};
ENDLOOP;
Commander.Register[command, DesignCmd]; -- try that, Unix
};
ToolOptions: PUBLIC PROC [command: ROPE] RETURNS [toolOptions: ROPE ¬ NIL] ~ {
width: INT ¬ 2*VFonts.StringWidth["\t"];  -- VFonts says 12; but should be 24
FOR l: LIST OF Registration ¬ registry, l.rest WHILE l # NIL DO
IF Eq[l.first.command, command] THEN {
colonTabs: ROPE ¬ ":";
nTabs: INT ¬ 2+MAX[81-VFonts.StringWidth[Rope.Concat[l.first.function, ":"]], 0]/width;
THROUGH [1..nTabs] DO colonTabs ¬ Rope.Concat[colonTabs, "\t"]; ENDLOOP;
toolOptions ¬ Rope.Cat[toolOptions, "\n\t", l.first.function, colonTabs, l.first.doc];
};
ENDLOOP;
};
ExecuteOption: PUBLIC CommandProc ~ {
argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd];
IF argv.argc > 1 THEN SELECT TRUE FROM
Eq[argv[1], "reset"] => {
FOR l: LIST OF Registration ¬ registry, l.rest WHILE l # NIL DO
Commander.Register[l.first.command, NIL];
ENDLOOP;
registry ¬ NIL;
};
ENDCASE => {
command: ROPE ¬ FileNames.GetShortName[cmd.command];
FOR l: LIST OF Registration ¬ registry, l.rest WHILE l # NIL DO
IF RegistrationMatch[l.first, command, argv[1]] THEN {
[result, msg] ¬ l.first.action[cmd];
RETURN;
};
ENDLOOP;
RETURN[$Failure, "No such option."];
};
};
DesignCmd: PUBLIC CommandProc ~ {
argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd];
command: ROPE ¬ FileNames.GetShortName[cmd.command];
IF argv.argc < 2 THEN RETURN[$Failure, Rope.Concat[command, " <option | ?>"]];
IF Eq[argv[1], "?"]
THEN RETURN[, Rope.Cat[command, " <option>, options include:", ToolOptions[command]]]
ELSE [result, msg] ¬ ExecuteOption[cmd];
};
Initialization
Commander.Register["ImplicitDesign", DesignCmd, "ImplicitDesign <option | ?>"];
Reset:\t\t\t\tremove current options";
END.