File: SVEventImplA.mesa
Author: Eric Bier in October 1982
Copyright © 1984 by Xerox Corporation. All rights reserved.
Last edited by Bier on May 7, 1987 3:26:22 pm PDT
Contents: Code to respond to button presses made in an SVViewer
DIRECTORY
AtomButtons, AtomButtonsTypes, BasicTime, CodeTimer, Containers, CoordSys, DisplayListToTree, Feedback, FileNames, FS, GraphicsButton, Imager, IO, Menus, Random, Rope, SlackProcess, SV2d, SV3d, SVBasicTypes, SVCastRays, SVDrawMonitor, SVEditUser, SVEvent, SVFiles, SVGravity, SVInterfaceTypes, SVModelTypes, SVRayTypes, SVScene, SVSceneTypes, SVSelections, SVSessionLog, SVState, SVUserInput, SVUtility, SVViewersOnScene, SVViewerTool, SVWindow, TFO3d, ViewerClasses, ViewerOps, ViewerTools;
SVEventImplA: CEDAR PROGRAM
IMPORTS AtomButtons, BasicTime, SVCastRays, CodeTimer, CoordSys, DisplayListToTree, Feedback, FileNames, FS, GraphicsButton, IO, Random, Rope, SlackProcess, SVEditUser, SVEvent, SVFiles, SVGravity, SVScene, SVSelections, SVSessionLog, SVState, SVUserInput, SVUtility, SVViewersOnScene, SVWindow, TFO3d, ViewerOps, ViewerTools
EXPORTS SVEvent =
BEGIN
Slice: TYPE = SVSceneTypes.Slice;
BoundBox: TYPE = SVBasicTypes.BoundBox;
Camera: TYPE = SVModelTypes.Camera;
Color: TYPE = Imager.Color;
CoordSysGenerator: TYPE = SVScene.CoordSysGenerator;
CoordSystem: TYPE = SVModelTypes.CoordSystem;
CSGTree: TYPE = SVRayTypes.CSGTree;
DisplayContextProc: TYPE = SVInterfaceTypes.DCProc;
DrawStyle: TYPE = SVModelTypes.DrawStyle;
EditToolData: TYPE = SVInterfaceTypes.EditToolData;
FeatureData: TYPE = SVInterfaceTypes.FeatureData;
FeedbackData: TYPE = AtomButtonsTypes.FeedbackData;
FileCamera: TYPE = SVSceneTypes.FileCamera;
GravityType: TYPE = SVInterfaceTypes.GravityType;
MasterObject: TYPE = SVSceneTypes.MasterObject;
Matrix4by4: TYPE = SV3d.Matrix4by4;
MouseButton: TYPE = Menus.MouseButton;
Point2d: TYPE = SV2d.Point2d;
Point3d: TYPE = SV3d.Point3d;
Primitive: TYPE = SVRayTypes.Primitive;
Scene: TYPE = SVSceneTypes.Scene;
TwoState: TYPE = AtomButtons.TwoState;
Vector3d: TYPE = SV3d.Vector3d;
Viewer: TYPE = ViewerClasses.Viewer;
SVData: TYPE = SVInterfaceTypes.SVData;
Gravity Line Operations
GravityChoiceChange: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
info: AtomButtons.EnumTypeRef ← svData.hitTest.gravityTypeMenu;
name: Rope.ROPE;
IF event.rest.first = $FlipForward THEN
AtomButtons.TimeToFlipThru[LIST[$FlipForward, info]]
ELSE
AtomButtons.TimeToFlipThru[LIST[$FlipBackward, info]];
name ← info.flipLabel.name;
SELECT TRUE FROM
Rope.Equal[name, "StrictDistance", TRUE] => svData.hitTest.gravityType ← strictDistance;
Rope.Equal[name, "PreferLines", TRUE] => svData.hitTest.gravityType ← linesPreferred;
Rope.Equal[name, "PreferPoints", TRUE] => svData.hitTest.gravityType ← pointsPreferred;
ENDCASE => ERROR;
UpdateGravityChoice[svData];
};
UpdateGravityChoice: PROC [clientData: REF ANY] = {
svData: SVData ← NARROW[clientData];
SELECT AtomButtons.GetButtonState[svData.hitTest.gravButton] FROM
on => SELECT svData.hitTest.gravityType FROM
strictDistance => SVWindow.SetCursorLooks[strictDistance, svData];
linesPreferred => SVWindow.SetCursorLooks[linesPreferred, svData];
pointsPreferred => SVWindow.SetCursorLooks[pointsPreferred, svData];
ENDCASE => ERROR; -- SHOULD NOT USE off STATE
off => SVWindow.SetCursorLooks[off, svData];
ENDCASE => ERROR;
};
GravityExtentChange: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
ged: SVInterfaceTypes.GravityExtentData ←
NARROW[GraphicsButton.GetValue[svData.hitTest.gravityExtentButton].buttonData];
extent: REAL ← ged.extent;
success: BOOLTRUE;
SELECT event.rest.first FROM
$ValueUp => {
IF extent < 18432.0 THEN extent ← extent*2.0
ELSE {
Feedback.PutF[svData.feedback, oneLiner, "Can't extend gravity further than 256 inches."];
Feedback.Blink[svData.feedback];
success ← FALSE;
};
};
$ValueDown => extent ← extent/2.0;
ENDCASE => extent ← SVEditUser.GetDefaultGravityExtent[];
IF success THEN {
SetGravityExtent[svData, extent/72.0];
};
};
SetGravityExtent: PUBLIC PROC [svData: SVData, inches: REAL] = {
screenDots: REAL;
graphicsState: AtomButtonsTypes.GraphicsState ← svData.hitTest.gravityExtentButton;
ged: SVInterfaceTypes.GravityExtentData;
screenDots ← inches*72.0;
Feedback.PutF[svData.feedback, oneLiner, "Gravity extent is now %g inches.", [real[inches]] ];
ged ← NEW[SVInterfaceTypes.GravityExtentDataObj ← [extent: screenDots]];
GraphicsButton.SetButtonValueAndPaint[graphicsState, svData, ged];
svData.hitTest.tolerance ← screenDots;
svData.hitTest.criticalR ← screenDots;
svData.hitTest.innerR ← screenDots/2.0;
};
ToggleGravity: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
stateInfo: TwoState ← svData.hitTest.gravButton;
AtomButtons.SwitchState[stateInfo];
UpdateGravityChoice[svData];
};
ToggleMidpoints: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
stateInfo: TwoState ← svData.hitTest.midpointButton;
AtomButtons.SwitchState[stateInfo];
SVWindow.RestoreScreenAndInvariants[paintAction: $None, svData: svData];
};
Alignment Lines
InitializeAlignments: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
};
MISCELLANEOUS
Painter: PUBLIC PROC [proc: DisplayContextProc, svData: SVData ← NIL] = {
}; -- end of Painter
UpdateHeader: PUBLIC PROC [pictureFile: Rope.ROPE, svData: SVData] = {
headerRope: Rope.ROPE;
aisName: Rope.ROPE;
viewName: Rope.ROPE ← svData.camera.viewName;
IF pictureFile = NIL THEN pictureFile ← svData.scene.name;
headerRope ← Rope.Cat[viewName, " View of Solid Scene: ", pictureFile];
aisName ← Rope.Concat[SVFiles.FilenameMinusExtension[pictureFile], ".ais"];
svData.outer.name ← headerRope;
ViewerOps.PaintViewer[svData.outer, caption, FALSE, NIL];
};
NewVersion: PUBLIC PROC [viewer: Viewer] = {
svData: SVData ← NARROW[viewer.data];
scene: Scene ← svData.scene;
scene.dirty ← TRUE;
svData.scene.tree.outOfDate ← TRUE;
svData.outer.newVersion ← TRUE;
UpdateHeader[scene.name, svData];
};
SceneOldVersion: PUBLIC PROC [svData: SVData] = {
Tell EditTool that scene is no longer dirty. EditTool will call back all affected viewers using OldVersion (see below).
scene: Scene ← svData.scene;
SVViewersOnScene.SceneOldVersion[svData, scene];
};
OldVersion: PUBLIC PROC [viewer: Viewer] = {
svData: SVData ← NARROW[viewer.data];
scene: Scene ← svData.scene;
scene.dirty ← FALSE;
svData.scene.tree.outOfDate ← TRUE; -- OldVersion may result from loading a new scene, or creating an empty scene. In either case, the ray tracing tree is out of date. Set flag just in case.
svData.outer.newVersion ← FALSE;
UpdateHeader[scene.name, svData];
};
DRAW SOMETHING IN A SOLID VIEWER (Fetches a display context first)
EraseAndDrawScene: PUBLIC PROC [svData: SVData] = {
SVWindow.RestoreScreenAndInvariants[$PaintEntireScene, svData];
};
MENU PROCS
DrawSceneButton: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
SVWindow.RestoreScreenAndInvariants[$PaintEntireScene, svData];
};
Clear: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
Creates a new empty scene for the viewer to view.
editToolData: EditToolData ← svData.editToolData;
oldScene: Scene ← svData.scene;
newScene: Scene;
oldScreenCS: CoordSystem;
fileCamera: FileCamera;
cameraName: Rope.ROPE;
drawStyle: DrawStyle;
oldScreenCS ← svData.camera.screenCS;
newScene ← svData.scene ← SVScene.CreateScene["NoName.pic"];
SVSelections.KillSkitterAndSelections[editToolData];
UpdateHeader[newScene.name, svData];
SVEvent.Selected[event, svData];
IF newScene.cameraOrder = NIL THEN fileCamera ← newScene.cameras.first
ELSE {
success: BOOL;
cameraName ← newScene.cameraOrder.first;
[fileCamera, success] ← SVScene.FindFileCameraFromName[cameraName, newScene];
IF NOT success THEN {
fileCamera ← newScene.cameras.first;
Feedback.Append[svData.feedback, Rope.Cat["Camera ", cameraName, " not found"], oneLiner];
Feedback.Blink[svData.feedback];
};
};
drawStyle ← SVEvent.CurrentStyle[svData];
svData.camera ← SVScene.CreateCamera[newScene.coordSysRoot, CoordSys.CopyCoordSysFromAnyTree[oldScreenCS, "SCREEN", NIL, NIL], drawStyle];
SVScene.StuffCameraFromFileCamera[svData.camera, fileCamera, svData.camera.style, newScene];
SVViewersOnScene.NotifyOfEmpty[svData: svData, from: oldScene, to: svData.scene];
SceneOldVersion[svData];
This must be done after NotifyOfEmpty since SVViewersOnScene expects to register to the newly created NoName scene before it resets the [New Version] headers.
SVWindow.RestoreScreenAndInvariants[$PaintEntireScene, svData];
};
Restore: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
editToolData: EditToolData ← svData.editToolData;
success: BOOL;
wdir: Rope.ROPE;
scene: Scene ← svData.scene;
wdir ← editToolData.originalWorkingDirectory;
[scene, success] ← SVViewersOnScene.LoadScene[svData, scene.name, wdir];
Reload this scene from its backing file.
IF success THEN {
svData.scene ← scene;
SVSelections.KillSkitterAndSelections[editToolData];
SceneOldVersion[svData];
UpdateHeader[scene.name, svData];
EraseAndDrawScene[svData];
SVEvent.Selected[event, svData];
};
}; -- end of Reset
Save: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
success: BOOL;
sceneName: Rope.ROPE;
scene: Scene ← svData.scene;
sceneName ← FileNames.StripVersionNumber[scene.name];
success ← SVViewersOnScene.SaveScene[svData, sceneName];
IF success THEN {
SVEvent.Selected[event, svData];
SceneOldVersion[svData];
UpdateHeader[scene.name, svData];
};
}; -- end of Save
Get: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
editToolData: EditToolData ← NARROW[svData.editToolData];
success: BOOL;
picName: Rope.ROPENARROW[event.rest.first];
fullName: Rope.ROPE;
wdir: Rope.ROPE;
scene: Scene;
oldScreenCS: CoordSystem ← svData.camera.screenCS;
TIMING VARIABLES
startTime: BasicTime.GMT;
endTime: BasicTime.GMT;
totalTime: INT;
timeRope: Rope.ROPE;
drawStyle: DrawStyle;
IF Rope.Length[picName] = 0 THEN RETURN;
wdir ← editToolData.originalWorkingDirectory;
success ← TRUE;
[fullName,,] ← FS.ExpandName[picName, wdir
! FS.Error => IF error.group = user THEN {
success ← FALSE;
CONTINUE;
}
];
IF NOT success THEN RETURN;
START TIMING
startTime ← BasicTime.Now[];
[scene, success] ← SVViewersOnScene.LoadScene[svData, fullName, wdir];
IF success THEN {
fileCamera: FileCamera;
cameraName: Rope.ROPE;
END TIMING
endTime ← BasicTime.Now[];
totalTime ← BasicTime.Period[startTime, endTime];
timeRope ← IO.PutFR[" Done in time (%r)", [integer[totalTime]]];
Feedback.Append[svData.feedback, timeRope, oneLiner];
IF scene.cameraOrder = NIL THEN fileCamera ← scene.cameras.first
ELSE {
cameraName ← scene.cameraOrder.first;
[fileCamera, success] ← SVScene.FindFileCameraFromName[cameraName, scene];
IF NOT success THEN {
fileCamera ← scene.cameras.first;
Feedback.Append[svData.feedback, Rope.Cat["Camera ", cameraName, " not found"], oneLiner];
Feedback.Blink[svData.feedback];
};
};
svData.scene ← scene;
SVSelections.KillSkitterAndSelections[editToolData];
drawStyle ← SVEvent.CurrentStyle[svData];
svData.camera ← SVScene.CreateCamera[scene.coordSysRoot, CoordSys.CopyCoordSysFromAnyTree[oldScreenCS, "SCREEN", NIL, NIL], drawStyle];
SVScene.StuffCameraFromFileCamera[svData.camera, fileCamera, svData.camera.style, scene];
SceneOldVersion[svData];
UpdateHeader[fullName, svData];
EraseAndDrawScene[svData];
SVEvent.Selected[event, svData];
};
}; -- end of Get
Store: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
editToolData: EditToolData ← NARROW[svData.editToolData];
wdir: Rope.ROPE;
success: BOOL;
picName: Rope.ROPENARROW[event.rest.first];
fullName: Rope.ROPE;
scene: Scene ← svData.scene;
IF Rope.Length[picName] = 0 THEN RETURN;
wdir ← editToolData.originalWorkingDirectory;
success ← TRUE;
[fullName,,] ← FS.ExpandName[picName, wdir
! FS.Error => IF error.group = user THEN {
success ← FALSE;
CONTINUE;
}
];
IF NOT success THEN RETURN;
success ← SVViewersOnScene.StoreScene[svData, scene, fullName];
IF success THEN {
UpdateHeader[fullName, svData];
SVEvent.Selected[event, svData];
SceneOldVersion[svData];
UpdateHeader[scene.name, svData];
aisName ← Rope.Concat[SVFiles.FilenameMinusExtension[fullName], ".ais"];
};
}; -- end of Store
Split: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
Create a new viewer onto this scene. Update the ring of links joining all viewers onto this scene,
scene: Scene ← svData.scene;
SVViewersOnScene.Split[svData, scene];
};
Script Menu
in SVEventImplA.OpenScript
OpenScript: PUBLIC Menus.ClickProc = {
svData: SVData ← NARROW[clientData];
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
SVSessionLog.OpenScript[name, svData];
IF svData.debug.logStream#NIL THEN [] ← SlackProcess.EnableSessionLogging[svData.slackHandle];
};
AppendToScript: PUBLIC Menus.ClickProc = {
svData: SVData ← NARROW[clientData];
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
SVSessionLog.AppendScript[name, svData];
IF svData.debug.logStream#NIL THEN [] ← SlackProcess.EnableSessionLogging[svData.slackHandle];
};
CloseScript: PUBLIC Menus.ClickProc = {
svData: SVData ← NARROW[clientData];
[] ← SlackProcess.DisableSessionLogging[svData.slackHandle];
SVSessionLog.CloseScript[svData];
};
PlaybackScript: PUBLIC Menus.ClickProc = {
svData: SVData ← NARROW[clientData];
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
SVSessionLog.PlaybackFromFile[name, svData];
};
FastPlayScript: PUBLIC Menus.ClickProc = {
svData: SVData ← NARROW[clientData];
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
SVUserInput.EventNotify[svData, LIST[$DisableRefresh]];
SVSessionLog.PlaybackFromFile[name, svData];
SVUserInput.EventNotify[svData, LIST[$EnableRefresh]];
SVUserInput.EventNotify[svData, LIST[$Refresh, $DoNotClearFeedback]];
};
RayCast: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
editToolData: EditToolData ← NARROW[svData.editToolData];
scene: Scene ← svData.scene;
camera: Camera ← svData.camera;
feedback: FeedbackData ← svData.feedback;
tree: CSGTree;
startTime: BasicTime.GMT;
afterTreeTime: BasicTime.GMT;
endTime: BasicTime.GMT;
totalTime: INT;
msgRope, wdir: Rope.ROPE;
aisFileRope: Rope.ROPE;
success: BOOL;
maxRed, maxGreen, maxBlue, maxBlack: NAT;
aisFileRope ← Rope.Concat[SVFiles.FilenameMinusExtension[scene.name], ".ais"];
wdir ← editToolData.originalWorkingDirectory;
success ← TRUE;
[aisFileRope,,] ← FS.ExpandName[aisFileRope, wdir
! FS.Error => IF error.group = user THEN {
Feedback.Append[svData.feedback, Rope.Concat["RayCast: ", error.explanation], oneLiner];
success ← FALSE;
CONTINUE;
}
];
IF NOT success THEN RETURN;
startTime ← BasicTime.Now[];
tree ← DisplayListToTree.AssemblyToTree[scene.assembly, scene, camera];
afterTreeTime ← BasicTime.Now[];
totalTime ← BasicTime.Period[from: startTime, to: afterTreeTime];
Feedback.PutF[svData.feedback, oneLiner, "Building tree took (%r)",[integer[totalTime]]];
IF mouseButton = red OR mouseButton = blue THEN {
[success, maxRed, maxGreen, maxBlue, maxBlack] ← SVCastRays.DrawTree[tree, scene.lightSources, camera, aisFileRope, mouseButton = blue, SVEvent.RayCastProgress, svData, feedback];
[success, maxRed, maxGreen, maxBlue, maxBlack] ← SVCastRays.DrawTree[tree, scene.lightSources, camera, aisFileRope, TRUE, SVEvent.RayCastProgress, svData, feedback];
}
ELSE { -- yellow bug
cameraPoint: Point2d;
[cameraPoint, ----] ← SVSelections.GetPositionSkitter[];
[success, maxRed, maxGreen, maxBlue, maxBlack] ← SVCastRays.DrawTreeWithStartLine[cameraPoint[2], tree, scene.lightSources, camera, aisFileRope, mouseButton = blue, SVEvent.RayCastProgress, svData, feedback];
};
IF NOT success THEN {
Feedback.PutF[feedback, end, "...Raycast aborted."];
RETURN;
};
endTime ← BasicTime.Now[];
totalTime ← BasicTime.Period[from: afterTreeTime, to: endTime];
Feedback.PutF[svData.feedback, begin, " Raycast took (%r).",[integer[totalTime]]];
msgRope ← IO.PutFR[" Max values were: red: (%g) green: (%g) blue: (%g) black: (%g)\n",
[integer[maxRed]], [integer[maxGreen]], [integer[maxBlue]], [integer[maxBlack]]];
Feedback.AppendTypescript[svData.feedback, msgRope, end];
SVEvent.DrawBlackAndWhite[event, svData];
}; -- end of RayCast
StopRays: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
camera: Camera ← svData.camera;
camera.abort ← TRUE;
};
ARay: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
scene: Scene ← svData.scene;
camera: Camera ← svData.camera;
outHandle: IO.STREAM ← Feedback.GetTypescriptStream[$Solidviews];
Get x and y from appropriate slots. Interpret this as camera coordinates and draw a cross.
x, y: REAL;
tree: CSGTree ← DisplayListToTree.AssemblyToTree[scene.assembly, scene, camera];
color: Color;
[x, y] ← SVUtility.ReadTwoReals[svData.textSection.xyz];
color ← SVCastRays.SingleRay[[x, y], tree, scene.lightSources, camera, svData.feedback, TRUE];
Feedback.PutFTypescript[svData.feedback, begin, "[%g,%g]: ",[real[x]], [real[y]]];
TFO3d.FileoutColor[outHandle, color];
Feedback.PutFTypescript[svData.feedback, end, "\n"];
}; -- end of ARay
DrawCoordSystems: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
SVWindow.RestoreScreenAndInvariants[$DrawCoordSystems, svData];
};
DrawBoundBoxes: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
viewer is Container
SVWindow.RestoreScreenAndInvariants[$DrawBoundBoxes, svData];
}; -- end of DrawBoundBoxes
DrawBoundSpheres: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
SVWindow.RestoreScreenAndInvariants[$DrawBoundSpheres, svData];
}; -- end of DrawBoundSpheres
DrawPt: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
camera: Camera ← svData.camera;
get x and y from appropriate slots. Interpret this as camera coordinates and draw a cross.
SVWindow.RestoreScreenAndInvariants[$DrawPt, svData];
};
CrossHairs: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
viewer is the crossHairs button
SVWindow.RestoreScreenAndInvariants[$DrawCrossHairs, svData];
}; -- end of CrossHairs
TestGravity: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
Within the bounds of the viewer, randomly choose mouse positions. See if that mouse position is in range of any object. If so, draw a dot at that point. Repeat until 100 points have been drawn.
xRandomStream, yRandomStream: Random.RandomStream;
testPoint: Point2d;
pointCount: INTNARROW[event.rest.first, REF INT]^;
x, y: INT;
totalCount, hitCount, diffCount: NAT ← 0;
surfacePtWORLD: Point3d;
camera: Camera ← svData.camera;
scene: Scene ← svData.scene;
screenCS: CoordSystem ← camera.screenCS;
feature: FeatureData;
gravityType: GravityType ← SVState.GetGravityType[svData];
xRandomStream ← Random.Create[svData.actionArea.cw];
yRandomStream ← Random.Create[svData.actionArea.ch];
UNTIL totalCount >= pointCount DO
x ← Random.NextInt[xRandomStream];
y ← Random.NextInt[yRandomStream];
testPoint ← [x, y];
testPoint ← CoordSys.ScreenToCamera[testPoint, screenCS];
svData.refresh.spotPoint ← testPoint;
[surfacePtWORLD, ----, feature] ← SVGravity.RayMap[testPoint, svData.hitTest.criticalR, NIL, svData.hitTest.sceneBag, svData];
totalCount ← totalCount + 1;
IF feature = NIL THEN {
SVWindow.RestoreScreenAndInvariants[paintAction: $PaintSpot, svData: svData];
}
ELSE {
hitCount ← hitCount + 1;
svData.refresh.hitPoint ← surfacePtWORLD;
SVWindow.RestoreScreenAndInvariants[paintAction: $PaintHitLine, svData: svData];
};
ENDLOOP;
Feedback.PutF[svData.feedback, oneLiner, "Tested %g total points. %g hits.", [integer[totalCount]], [integer[hitCount]]];
}; -- end TestGravity
ResetStatistics: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
CodeTimer.ResetTable[CodeTimer.GetTable[$Solidviews]];
};
PrintStatistics: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = {
f: IO.STREAM ← Feedback.GetTypescriptStream[$Solidviews];
CodeTimer.PrintTable[f, CodeTimer.GetTable[$Solidviews]];
};
END.