File: SVEventImplC.mesa
Last edited by Bier on September 24, 1987 1:40:32 pm PDT
Copyright © 1984 by Xerox Corporation. All rights reserved.
Contents: Procedures for responding to button clicks made in a solidviewer.
Bloomenthal, June 16, 1987 7:52:46 pm PDT
DIRECTORY
Ascii, BasicTime, ColorTool, Feedback, GGCoreOps, Imager, ImagerColor, ImagerColorFns, ImagerColorPrivate, ImagerInterpress, IO, NamedColors, PrincOpsUtils, Rope, SV2d, SV3d, SVArtwork, SVAssembly, SVBasicTypes, SVCaret, SVCastRays, SVCoordSys, SVEditUser, SVEvent, SVInterfaceTypes, SVMasterObject, SVMasterObjectTypes, SVMatrix3d, SVModelTypes, SVPreprocess3d, SVRay, SVRefresh, SVScene, SVSceneToTree, SVSceneTypes, SVSelect, SVSelections, SVState, SVTransforms, SVUserInput, SVUtility, SVVector3d, SVViewersOnScene, SVViewerTools, SVWindow, TIPUser, ViewerClasses, ViewerTools;
SVEventImplC:
CEDAR PROGRAM
IMPORTS Ascii, BasicTime, ColorTool, GGCoreOps, SVCoordSys, SVSceneToTree, Feedback, Imager, ImagerColor, ImagerColorFns, ImagerColorPrivate, ImagerInterpress, IO, SVMatrix3d, NamedColors, PrincOpsUtils, SVPreprocess3d, Rope, SVArtwork, SVAssembly, SVCaret, SVCastRays, SVEditUser, SVEvent, SVRay, SVRefresh, SVScene, SVSelect, SVSelections, SVState, SVTransforms, SVUtility, SVVector3d, SVViewersOnScene, SVViewerTools, SVWindow, ViewerTools
EXPORTS SVEvent =
BEGIN
Artwork: TYPE = SVModelTypes.Artwork;
ArtworkToolData: TYPE = SVInterfaceTypes.ArtworkToolData;
Slice: TYPE = SVSceneTypes.Slice;
SliceList: TYPE = SVSceneTypes.SliceList;
BoundBox: TYPE = SVBasicTypes.BoundBox;
Camera: TYPE = SVModelTypes.Camera;
Classification: TYPE = SVSceneTypes.Classification;
Color: TYPE = Imager.Color;
CoordSystem: TYPE = SVModelTypes.CoordSystem;
CSGTree: TYPE = SVSceneTypes.CSGTree;
CylinderRec: TYPE = SVMasterObjectTypes.CylinderRec;
EditToolData: TYPE = SVInterfaceTypes.EditToolData;
FeedbackData: TYPE = Feedback.FeedbackData;
FileCamera: TYPE = SVSceneTypes.FileCamera;
FrameBox: TYPE = SVModelTypes.FrameBox;
MasterObject: TYPE = SVSceneTypes.MasterObject;
Matrix4by4: TYPE = SV3d.Matrix4by4;
Point2d: TYPE = SV2d.Point2d;
Point3d: TYPE = SV3d.Point3d;
Primitive: TYPE = SVSceneTypes.Primitive;
Ray: TYPE = SVSceneTypes.Ray;
Scene: TYPE = SVSceneTypes.Scene;
SearchDepth: TYPE = SVSceneTypes.SearchDepth;
Selection: TYPE = SVInterfaceTypes.Selection;
SelectionGenerator: TYPE = SVInterfaceTypes.SelectionGenerator;
Shape: TYPE = SVSceneTypes.Shape;
SliceDescriptor: TYPE = SVSceneTypes.SliceDescriptor;
SliceDescriptorGenerator: TYPE = SVSceneTypes.SliceDescriptorGenerator;
Skitter: TYPE = SVSceneTypes.Skitter;
SkitterMode: TYPE = SVSceneTypes.SkitterMode;
Sphere: TYPE = SV3d.Sphere;
ToolData: TYPE = SVSceneTypes.ToolData;
TrigLine: TYPE = SV2d.TrigLine;
Vector3d: TYPE = SV3d.Vector3d;
SVData: TYPE = SVInterfaceTypes.SVData;
Stroke Color Menu
StrokeColorFromColorTool:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
IF ColorToolIsBound[svData] THEN StrokeColorAux[ColorTool.GetColor[], svData];
};
StrokeColorToColorTool:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
IF ColorToolIsBound[svData]
THEN {
color: Imager.Color;
sliceDescGen: SliceDescriptorGenerator ← SVSelect.SelectedSlices[svData.scene, normal];
sliceD: SliceDescriptor ← SVSelect.NextSliceDescriptor[sliceDescGen];
IF sliceD=
NIL
THEN {
Feedback.Append[svData.feedback, "Select exactly one segment for sending color to ColorTool", oneLiner];
}
ELSE {
color ← SVAssembly.GetStrokeColor[sliceD.slice, sliceD.parts];
IF color#NIL THEN ColorTool.SetColor[color, NIL] ELSE Feedback.Append[svData.feedback, ". . . Object has no stroke color", oneLiner];
};
};
};
PrintStrokeColor:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
FetchStrokeColor:
PROC [sliceD: SliceDescriptor]
RETURNS [color: Imager.ConstantColor] = {
color ← NARROW[SVAssembly.GetStrokeColor[sliceD.slice, sliceD.parts].color];
};
PrintColorAux[svData.scene, svData.feedback, "Stroke", FetchStrokeColor];
};
StrokeColorNone:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
StrokeColorAux[NIL, svData];
};
StrokeColorGray:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
whiteness: REAL ← NARROW[event.rest.first, REF REAL]^;
color: Imager.Color ← ImagerColor.ColorFromGray[1.0 - whiteness];
StrokeColorAux[color, svData];
};
StrokeColorFromSelectedName:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
name: Rope.ROPE ← IF event.rest = NIL THEN ViewerTools.GetSelectionContents[] ELSE NARROW[event.rest.first];
color: Imager.Color ← ParseColorName[name, svData.feedback];
IF color#NIL THEN StrokeColorAux[color, svData];
};
StrokeColorFromSelectedRGB:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
name: Rope.ROPE ← IF event.rest = NIL THEN ViewerTools.GetSelectionContents[] ELSE NARROW[event.rest.first];
color: Imager.Color ← ImagerColor.ColorFromRGB[RGBFromRope[name ! RGBFromRopeError => GOTO SyntaxError]];
StrokeColorAux[color, svData];
EXITS
SyntaxError => {Feedback.Append[svData.feedback, "RGB Syntax is R: [0.0..1.0] G: [0.0..1.0] B: [0.0..1.0]", oneLiner]; Feedback.Blink[svData.feedback];};
};
StrokeColorAux:
PROC [color: Imager.Color, svData: SVData] = {
sliceDescGen: SliceDescriptorGenerator ← SVSelect.SelectedSlices[svData.scene, normal];
FOR sliceD: SliceDescriptor ← SVSelect.NextSliceDescriptor[sliceDescGen], SVSelect.NextSliceDescriptor[sliceDescGen]
UNTIL sliceD=
NIL
DO
SVAssembly.SetStrokeColor[sliceD.slice, sliceD.parts, color];
ENDLOOP;
SVWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, svData: svData, remake: none, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
ColorToolIsBound:
PROC [svData: SVData]
RETURNS [
BOOL ←
FALSE] =
TRUSTED {
IF PrincOpsUtils.IsBound[LOOPHOLE[ColorTool.GetRGBValue]] THEN RETURN[TRUE];
Feedback.Append[svData.feedback, "Please start ColorTool and retry this operation", oneLiner];
};
SetDefaultStrokeColor:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
sliceD: SliceDescriptor;
sliceDescGen: SliceDescriptorGenerator ← SVSelect.SelectedSlices[svData.scene, normal];
IF sliceDescGen=NIL OR (sliceD ← SVSelect.NextSliceDescriptor[sliceDescGen])=NIL OR SVSelect.NextSliceDescriptor[sliceDescGen]#NIL THEN Feedback.Append[svData.feedback, "Select exactly one object for setting default line color", oneLiner]
ELSE {
isProcessBlack: Rope.ROPE;
red, green, blue: REAL;
color: Imager.ConstantColor ← NARROW[SVAssembly.GetStrokeColor[sliceD.slice, sliceD.parts]];
IF color#
NIL
THEN {
[red,green,blue] ← GGCoreOps.ExtractRGB[color];
IF color = Imager.black THEN isProcessBlack ← " (process black)"
ELSE IF ImagerColorPrivate.GrayFromColor[color]=1.0 THEN isProcessBlack ← " (CMY black)"
ELSE isProcessBlack ← "";
Feedback.Append[
svData.feedback,
IO.PutFR["Default Stroke Color: R: %1.3f G: %1.3f B: %1.3f CNS: %g%g", [real[red]], [real[green]], [real[blue]], [rope[NamedColors.HSLToRope[ImagerColorFns.HSLFromRGB[[red,green,blue]]]]], [rope[isProcessBlack]] ],
oneLiner];
}
ELSE Feedback.Append[svData.feedback, "Default Stroke Color: None", oneLiner];
svData.defaults.strokeColor ← color;
};
};
ShowDefaultStrokeColor:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
isProcessBlack: Rope.ROPE;
red, green, blue: REAL;
color: Imager.ConstantColor ← NARROW[svData.defaults.strokeColor];
IF color#
NIL
THEN {
[red,green,blue] ← GGCoreOps.ExtractRGB[color];
IF color = Imager.black THEN isProcessBlack ← " (process black)"
ELSE IF ImagerColorPrivate.GrayFromColor[color]=1.0 THEN isProcessBlack ← " (CMY black)"
ELSE isProcessBlack ← "";
Feedback.Append[
svData.feedback,
IO.PutFR["Default Stroke Color: R: %1.3f G: %1.3f B: %1.3f CNS: %g%g", [real[red]], [real[green]], [real[blue]], [rope[NamedColors.HSLToRope[ImagerColorFns.HSLFromRGB[[red,green,blue]]]]], [rope[isProcessBlack]] ],
oneLiner];
}
ELSE Feedback.Append[svData.feedback, "Default Stroke Color: None", oneLiner];
};
Fill Color Menu
AreaColorFromColorTool:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
IF ColorToolIsBound[svData] THEN AreaColorAux[ColorTool.GetColor[], svData];
};
AreaColorToColorTool:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
FetchFillColor:
PROC [sliceD: SliceDescriptor]
RETURNS [color: Imager.ConstantColor] = {
color ← NARROW[SVAssembly.GetFillColor[sliceD.slice, sliceD.parts].color];
};
IF ColorToolIsBound[svData]
THEN {
color: Imager.Color;
sliceDescGen: SliceDescriptorGenerator ← SVSelect.SelectedSlices[svData.scene, normal];
sliceD: SliceDescriptor ← SVSelect.NextSliceDescriptor[sliceDescGen];
IF sliceD=
NIL
THEN {
Feedback.Append[svData.feedback, "Select exactly one slice for sending color to ColorTool", oneLiner];
}
ELSE {
color ← FetchFillColor[sliceD];
IF color#NIL THEN ColorTool.SetColor[color, NIL] ELSE Feedback.Append[svData.feedback, ". . . Object has no fill color", oneLiner];
};
};
};
PrintAreaColor:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
FetchFillColor:
PROC [sliceD: SliceDescriptor]
RETURNS [color: Imager.ConstantColor] = {
color ← NARROW[SVAssembly.GetFillColor[sliceD.slice, sliceD.parts].color];
};
PrintColorAux[svData.scene, svData.feedback, "Fill", FetchFillColor];
};
ColorFetchProc: TYPE = PROC [sliceD: SliceDescriptor] RETURNS [color: Imager.ConstantColor];
PrintColorAux:
PROC [scene: Scene, feedback: FeedbackData, opName: Rope.
ROPE, colorFetch: ColorFetchProc] = {
sliceD: SliceDescriptor;
isProcessBlack: Rope.ROPE;
sliceDescGen: SliceDescriptorGenerator ← SVSelect.SelectedSlices[scene, normal];
IF sliceDescGen=NIL OR (sliceD ← SVSelect.NextSliceDescriptor[sliceDescGen])=NIL OR SVSelect.NextSliceDescriptor[sliceDescGen]#NIL THEN Feedback.PutF[feedback, oneLiner, "Select exactly one object for Print%gColor", [rope[opName]]]
ELSE {
red, green, blue: REAL;
color: Imager.ConstantColor ← colorFetch[sliceD];
IF color#
NIL
THEN {
[red,green,blue] ← GGCoreOps.ExtractRGB[color];
IF color = Imager.black THEN isProcessBlack ← " (process black)"
ELSE IF ImagerColorPrivate.GrayFromColor[color]=1.0 THEN isProcessBlack ← " (CMY black)"
ELSE isProcessBlack ← "";
Feedback.Append[
feedback,
IO.PutFR["R: %1.3f G: %1.3f B: %1.3f CNS: %g%g", [real[red]], [real[green]], [real[blue]], [rope[NamedColors.HSLToRope[ImagerColorFns.HSLFromRGB[[red,green,blue]]]]], [rope[isProcessBlack]] ],
oneLiner];
}
ELSE Feedback.PutF[feedback, oneLiner, "No %g Color", [rope[opName]]];
};
};
ParseColorName:
PROC [name: Rope.
ROPE, feedback: FeedbackData]
RETURNS [color: Imager.Color ←
NIL] ~ {
IF Rope.Match["*/*", name]
THEN {
--try for a registered name
color ← ImagerColor.Find[name];
IF color=NIL THEN color ← ImagerColor.Find[Rope.Cat["Xerox/Research/", name]];
IF color=NIL THEN GOTO UnregisteredName;
}
ELSE color ← ImagerColor.ColorFromRGB[ImagerColorFns.RGBFromHSL[NamedColors.RopeToHSL[name !
NamedColors.UndefinedName => GOTO UndefinedName;
NamedColors.BadGrammar => GOTO BadGrammar;
]]];
EXITS
UnregisteredName => {Feedback.Append[feedback, "Hierarchical name not registered", oneLiner]; Feedback.Blink[feedback];};
UndefinedName => {Feedback.Append[feedback, "Undefined Color Name", oneLiner]; Feedback.Blink[feedback];};
BadGrammar => {Feedback.Append[feedback, "Bad Color Name", oneLiner]; Feedback.Blink[feedback];};
};
AreaColorAux:
PROC [color: Imager.Color, svData: SVData] = {
artwork: Artwork ← SVArtwork.CreateColorArtwork[color, chalk];
sliceDescGen: SliceDescriptorGenerator ← SVSelect.SelectedSlices[svData.scene, normal];
FOR sliceD: SliceDescriptor ← SVSelect.NextSliceDescriptor[sliceDescGen], SVSelect.NextSliceDescriptor[sliceDescGen]
UNTIL sliceD=
NIL
DO
SVAssembly.SetFillColor[sliceD.slice, sliceD.parts, artwork];
ENDLOOP;
SVWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, svData: svData, remake: none, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
AreaColorNone:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
AreaColorAux[NIL, svData];
};
AreaColorGray:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
whiteness: REAL ← NARROW[event.rest.first, REF REAL]^;
color: Imager.Color ← ImagerColor.ColorFromGray[1.0 - whiteness];
AreaColorAux[color, svData];
};
AreaColorFromSelectedName:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
name: Rope.ROPE ← IF event.rest = NIL THEN ViewerTools.GetSelectionContents[] ELSE NARROW[event.rest.first];
color: Imager.Color ← ParseColorName[name, svData.feedback];
IF color#NIL THEN AreaColorAux[color, svData];
};
AreaColorFromSelectedRGB:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
name: Rope.ROPE ← IF event.rest = NIL THEN ViewerTools.GetSelectionContents[] ELSE NARROW[event.rest.first];
color: Imager.Color ← ImagerColor.ColorFromRGB[RGBFromRope[name ! RGBFromRopeError => GOTO SyntaxError]];
AreaColorAux[color, svData];
EXITS
SyntaxError => {Feedback.Append[svData.feedback, "RGB Syntax is R: [0.0..1.0] G: [0.0..1.0] B: [0.0..1.0]", oneLiner]; Feedback.Blink[svData.feedback];};
};
RGBFromRopeError: SIGNAL = CODE;
RGBFromRope:
PROC [name: Rope.
ROPE]
RETURNS [rgb: ImagerColor.
RGB] = {
ENABLE IO.Error, IO.EndOfStream => GOTO RGBError;
Check:
PROC [x:
REAL] = {
IF x NOT IN [0.0..1.0] THEN SIGNAL RGBFromRopeError;
};
rs: IO.STREAM ← IO.RIS[name];
IF Ascii.Upper[rs.GetChar[]]#'R THEN GOTO RGBError;
IF rs.GetChar[]#': THEN GOTO RGBError;
rgb.R ← rs.GetReal[]; [] ← rs.SkipWhitespace[];
IF Ascii.Upper[rs.GetChar[]]#'G THEN GOTO RGBError;
IF rs.GetChar[]#': THEN GOTO RGBError;
rgb.G ← rs.GetReal[]; [] ← rs.SkipWhitespace[];
IF Ascii.Upper[rs.GetChar[]]#'B THEN GOTO RGBError;
IF rs.GetChar[]#': THEN GOTO RGBError;
rgb.B ← rs.GetReal[];
Check[rgb.R]; Check[rgb.G]; Check[rgb.B];
EXITS
RGBError => SIGNAL RGBFromRopeError;
};
SetDefaultFillColor:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
sliceD: SliceDescriptor;
sliceDescGen: SliceDescriptorGenerator ← SVSelect.SelectedSlices[svData.scene, normal];
IF sliceDescGen=NIL OR (sliceD ← SVSelect.NextSliceDescriptor[sliceDescGen])=NIL OR SVSelect.NextSliceDescriptor[sliceDescGen]#NIL THEN Feedback.Append[svData.feedback, "Select exactly one filled object for setting default fill color", oneLiner]
ELSE {
isProcessBlack: Rope.ROPE;
red, green, blue: REAL;
artwork: Artwork;
artwork ← SVAssembly.GetFillColor[sliceD.slice, sliceD.parts];
IF artwork#
NIL
AND artwork.class = justColor
THEN {
color: Imager.ConstantColor ← NARROW[artwork.color];
[red,green,blue] ← GGCoreOps.ExtractRGB[color];
IF color = Imager.black THEN isProcessBlack ← " (process black)"
ELSE IF ImagerColorPrivate.GrayFromColor[color]=1.0 THEN isProcessBlack ← " (CMY black)"
ELSE isProcessBlack ← "";
Feedback.Append[
svData.feedback,
IO.PutFR["Default Fill Color: R: %1.3f G: %1.3f B: %1.3f CNS: %g%g", [real[red]], [real[green]], [real[blue]], [rope[NamedColors.HSLToRope[ImagerColorFns.HSLFromRGB[[red,green,blue]]]]], [rope[isProcessBlack]] ],
oneLiner];
}
ELSE Feedback.Append[svData.feedback, "Default Fill Color: None", oneLiner];
svData.defaults.fillColor ← artwork;
};
};
ShowDefaultFillColor:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
isProcessBlack: Rope.ROPE;
red, green, blue: REAL;
color: Imager.ConstantColor ← NARROW[svData.defaults.fillColor.color];
IF color#
NIL
THEN {
[red,green,blue] ← GGCoreOps.ExtractRGB[color];
IF color = Imager.black THEN isProcessBlack ← " (process black)"
ELSE IF ImagerColorPrivate.GrayFromColor[color]=1.0 THEN isProcessBlack ← " (CMY black)"
ELSE isProcessBlack ← "";
Feedback.Append[
svData.feedback,
IO.PutFR["Default Fill Color: R: %1.3f G: %1.3f B: %1.3f CNS: %g%g", [real[red]], [real[green]], [real[blue]], [rope[NamedColors.HSLToRope[ImagerColorFns.HSLFromRGB[[red,green,blue]]]]], [rope[isProcessBlack]] ],
oneLiner];
}
ELSE Feedback.Append[svData.feedback, "Default Fill Color: None", oneLiner];
};
Select Menu
SelectAll:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
editToolData: EditToolData ← svData.editToolData;
scene: Scene ← svData.scene;
SVSelect.DeselectAll[svData.scene, normal];
SVSelect.SelectAll[svData.scene, normal];
SVViewersOnScene.PaintSceneAllViewers[paintAction: $SelectionChanged, editToolData: editToolData, scene: scene, edited: FALSE];
};
Units Menu
pointsPerIn: REAL = 72.0;
pointsPerCm: REAL = 72.0/2.54;
cmPerIn: REAL = 2.54;
ScaleUnitFromSegment:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
};
ScaleUnitFromValue:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
};
ScaleUnitFromSelection:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
};
InchScaleUnit:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
ScaleUnitAux[svData, pointsPerIn]; -- one inch in screen dots
};
ScaleUnitAux:
PROC [svData: SVData, distance:
REAL] = {
SVState.SetScaleUnit[svData, distance]; -- in screen dots
PrintScaleUnit[event: NIL, svData: svData];
SVWindow.RestoreScreenAndInvariants[paintAction: $NewAlignmentsSelected, svData: svData, remake: alignBag, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE];
};
CentimeterScaleUnit:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
ScaleUnitAux[svData, pointsPerCm];
};
PointsScaleUnit:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
ScaleUnitAux[svData, 1];
};
PrintScaleUnit:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
inches: REAL ← SVState.GetScaleUnit[svData];
Feedback.PutF[svData.feedback, oneLiner, "Current scale is %g points = %g inches = %g centimeters", [real[inches*pointsPerIn]], [real[inches]], [real[inches/cmPerIn]] ];
};
TIP Table Functions
IPSnapShot:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
This command is executed in the middle of a dragging operation to make an interpress master of the current state of the screen. Luckily, all dragging operations use the same painting commands, namely:
SVWindow.RestoreScreenAndInvariants[paintAction: $DuringMotion, svData: svData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE];
We wish to have the same effect as these commands except written to an interpress master. We will create an interpress master named snapshot.ip and use SVRefresh.SnapShot to draw the scene into it.
DoMakeInterpress:
PROC [dc: Imager.Context] = {
ENABLE
UNWIND => {
svData.camera.displayStyle ← tempStyle;
};
DoItInterpress:
PROC = {
Imager.ScaleT[dc, metersPerPixel];
svData.camera.displayStyle ← print;
SVRefresh.SnapShot[dc, svData];
svData.camera.displayStyle ← tempStyle;
};
Imager.DoSave[dc, DoItInterpress];
};
ipRef: ImagerInterpress.Ref;
fullName: Rope.ROPE;
success: BOOL;
metersPerPixel: REAL = 0.0254/72.0;
startTime: BasicTime.GMT;
endTime: BasicTime.GMT;
totalTime: INT;
msgRope: Rope.ROPE;
tempStyle: SVModelTypes.DisplayStyle ← svData.camera.displayStyle;
[fullName, success] ← SVUtility.GetInterpressFileName["snapshot.ip", svData.currentWDir, svData.feedback];
IF NOT success THEN RETURN;
ipRef ← ImagerInterpress.Create[fullName];
msgRope ← IO.PutFR["Writing to IP file: %g . . . ", [rope[fullName]]];
Feedback.Append[svData.feedback, msgRope, begin];
startTime ← BasicTime.Now[];
ImagerInterpress.DoPage[ipRef, DoMakeInterpress, 1.0];
ImagerInterpress.Close[ipRef];
endTime ← BasicTime.Now[];
totalTime ← BasicTime.Period[startTime, endTime];
msgRope ← IO.PutFR[" Done in time (%r)", [integer[totalTime]]];
Feedback.Append[svData.feedback, msgRope, end];
};
JackPivotX:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
JackPivotAux[event, svData, 1];
};
JackPivotY:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
JackPivotAux[event, svData, 2];
};
JackPivotZ:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
JackPivotAux[event, svData, 3];
};
JackPivotAux:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData, axis: [1..3]] = {
Rotate the distinguished target coincident with respect to itself by degrees. This makes sense for both hook and coordsys targets.
editToolData: EditToolData ← svData.editToolData;
scene: Scene ← svData.scene;
degrees: REAL ← NARROW[event.rest.first, REF REAL]^;
targetSel: Selection;
coincident: Slice;
targetSel ← SVSelections.TopTarget[];
IF targetSel = NIL THEN RETURN;
coincident ← targetSel.coincident;
Erases the target shape. For a jack, this leaves nothing. For another object, this leaves the object.
SVTransforms.Rotate[coincident, scene, axis, degrees, coincident.coordSys, TRUE];
SVViewersOnScene.PaintSceneAllViewers[paintAction: $SelectionChanged, editToolData: editToolData, scene: scene, edited: TRUE];
};
SourcePivotX:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
SourcePivotAux[event, svData, 1];
};
SourcePivotY:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
SourcePivotAux[event, svData, 2];
};
SourcePivotZ:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
SourcePivotAux[event, svData, 3];
};
SourcePivotAux:
PUBLIC
PROC [event:
LIST
OF
REF
ANY, svData: SVData, axis: [1..3]] = {
Rotate the distinguished source indirect with respect to the distinguished source coincident. If the selection is not a hook jack, then indirect and coincident are identical.
editToolData: EditToolData ← svData.editToolData;
scene: Scene ← svData.scene;
degrees: REAL ← NARROW[event.rest.first, REF REAL]^;
sourceSel: Selection;
jack, indirect: Slice;
sourceSel ← SVSelections.TopMovee[];
IF sourceSel = NIL THEN RETURN;
SELECT sourceSel.referentType FROM
hook => {
jack ← sourceSel.coincident;
indirect ← sourceSel.indirect;
};
coordSys => {
jack ← sourceSel.coincident;
indirect ← sourceSel.coincident;
};
ENDCASE => ERROR;
SVViewersOnScene.SceneNewVersion[editToolData.currentSVData];
SVTransforms.Rotate[indirect, scene, axis, degrees, jack.coordSys, TRUE];
svData.refresh.addedObject ← indirect;
SVViewersOnScene.PaintSceneAllViewers[$ObjectChangedBoundBoxProvided, editToolData, scene];
};
ArrowShoot:
PUBLIC PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
Cast a ray from the distinguished source to each of the targets. Create a hook at each intersection point, aligned with the surface normal at that point.
source, target: Slice;
sourceSel, firstTargetSel: Selection;
editToolData: EditToolData;
sourceSel ← SVSelections.PopMovee[];
IF sourceSel = NIL THEN RETURN;
source ← sourceSel.coincident;
svData ← sourceSel.svData;
editToolData ← svData.editToolData;
firstTargetSel ← SVSelections.PopTarget[];
IF firstTargetSel = NIL THEN RETURN;
FOR targetSel: Selection ← firstTargetSel, SVSelections.NextTarget[]
UNTIL targetSel =
NIL
DO
IF targetSel.svData # sourceSel.svData
THEN {
Feedback.Append[svData.feedback, "Can't shoot arrows between viewers.", oneLiner];
Feedback.Blink[svData.feedback];
LOOP;
};
target ← targetSel.coincident;
ArrowShootAux[source.coordSys, target.coordSys, svData];
ENDLOOP;
SVViewersOnScene.SceneNewVersion[editToolData.currentSVData];
SVViewersOnScene.PaintSceneAllViewers[$PaintEntireScene, svData.editToolData, svData.scene];
};
ArrowShootAux:
PROC [sourceCS, targetCS: CoordSystem, svData: SVData] = {
Cast a ray from source to target. Create a hook at each intersection point, aligned with the surface normal at that point.
sOriginWorld, tOriginWorld: Point3d;
tree: CSGTree;
ray: Ray;
class: Classification;
t: REAL;
worldDirection, primitiveNormal, worldNormal: Vector3d;
selectionMat: Matrix4by4;
surfacePtInWorld: Point3d;
primitive: Primitive;
assembly: Slice;
scene: Scene;
camera: Camera;
skitter: Skitter ← svData.editToolData.skitter;
scene ← svData.scene;
camera ← svData.camera;
tree ← SVSceneToTree.AssemblyToTree[scene.assembly, scene, camera];
[] ← SVPreprocess3d.PreprocessForCatScan[tree, camera];
sOriginWorld ← SVMatrix3d.OriginOfMatrix[SVCoordSys.WRTWorld[sourceCS]];
tOriginWorld ← SVMatrix3d.OriginOfMatrix[SVCoordSys.WRTWorld[targetCS]];
ray ← SVRay.GetRayFromPool[];
worldDirection ← SVVector3d.Sub[tOriginWorld, sOriginWorld];
SVRay.StuffWorldRay[ray, sOriginWorld, worldDirection, camera];
class ← SVCastRays.RayCastBoundingSpheres[ray, tree.son];
FOR i:
NAT
IN [1..class.count]
DO
t ← class.params[i]; -- the parameter of the ray intersection
primitiveNormal ← class.normals[i];
primitive ← class.primitives[i];
worldNormal ← SVMatrix3d.UpdateVectorWithInverse[primitive.worldWRTPrim, primitiveNormal];
surfacePtInWorld[1] ← sOriginWorld[1] + t*worldDirection[1];
surfacePtInWorld[2] ← sOriginWorld[2] + t*worldDirection[2];
surfacePtInWorld[3] ← sOriginWorld[3] + t*worldDirection[3];
assembly ← NARROW[primitive.assembly];
selectionMat ← MakeAlignedMat[worldNormal, surfacePtInWorld, assembly.coordSys];
SVCaret.SetAssemblyAndPrimitive[skitter, assembly, primitive];
SVCaret.PositionFromMatrix[skitter, [0,0], selectionMat];
SVSelections.SetModeSkitter[svData, surface];
SVEvent.SkitterMakes[NIL, svData];
ENDLOOP;
SVCastRays.ReturnClassToPool[class];
SVRay.ReturnRayToPool[ray];
};
Sign:
PROC [r:
REAL]
RETURNS [
INT] = {
IF r = 0.0 THEN RETURN[2];
IF r < 0.0 THEN RETURN[-1]
ELSE RETURN[1];
};
AntiParallel:
PRIVATE
PROC [v1, v2: Vector3d]
RETURNS [
BOOL] = {
RETURN[Sign[v1[1]] = -Sign[v2[1]]
OR
Sign[v1[2]] = -Sign[v2[2]] OR
Sign[v1[3]] = -Sign[v2[3]] ];
};
MakeAlignedMat:
PRIVATE
PROC [worldNormal: Vector3d, surfacePtInWorld: Point3d, cs: CoordSystem]
RETURNS [mat: Matrix4by4] = {
Create a Matrix4by4 with origin at surfacePtInWorld whose z axis is parallel to worldNormal and whose x zxis is orthogonal to both worldNormal and the y axis of cs in WORLD coordinates. Assume that SVCoordSys.WRTWorld[cs] is accurate
yAxisOfCS: Vector3d ← SVMatrix3d.YAxisOfMatrix[SVCoordSys.WRTWorld[cs]];
xAxis: Vector3d;
IF SVVector3d.Parallel[yAxisOfCS, worldNormal]
THEN {
xAxis ← SVMatrix3d.XAxisOfMatrix[SVCoordSys.WRTWorld[cs]];
IF AntiParallel[yAxisOfCS, worldNormal] THEN xAxis ← SVVector3d.Negate[xAxis];
Allows positioning code to distinguish between top surfaces and bottom surfaces.
}
ELSE xAxis ← SVVector3d.CrossProduct[yAxisOfCS, worldNormal];
mat ← SVMatrix3d.MakeMatFromZandXAxis[worldNormal, xAxis, surfacePtInWorld];
};
MoveUntilTouch:
PUBLIC PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
Consider a line from the distinguished source to the distinguished target. Using translation along the ray direction (only) move the distinguish source towards the distinguished target until it touches something other than itself. If the distinguished source already touches something else, leave it alone.
source, target: Slice;
sourceSel, targetSel: Selection;
editToolData: EditToolData;
sourceSel ← SVSelections.PopMovee[];
IF sourceSel = NIL THEN RETURN;
source ← sourceSel.coincident;
svData ← sourceSel.svData;
editToolData ← svData.editToolData;
targetSel ← SVSelections.PopTarget[];
IF targetSel = NIL THEN RETURN;
IF targetSel.svData # sourceSel.svData
THEN {
Feedback.Append[svData.feedback, "Can't skewer between viewers.", oneLiner];
Feedback.Blink[svData.feedback];
RETURN;
};
target ← targetSel.coincident;
MoveUntilTouchAux[source, target, svData];
SVViewersOnScene.SceneNewVersion[editToolData.currentSVData];
SVViewersOnScene.PaintSceneAllViewers[$PaintEntireScene, svData.editToolData, svData.scene];
};
MoveUntilTouchAux:
PROC [source, target: Slice, svData: SVData] = {
Cast a set of rays parallel to the line from source to target and starting on a plane normal to that line a bounding sphere's radius behind source. Using translation along the ray direction (only) move the source until it touches something (anything).
sOriginWorld, tOriginWorld: Point3d;
tree, sourceTree: CSGTree;
worldDirection: Vector3d;
scene: Scene;
camera: Camera;
boundSphere: Sphere;
R: REAL;
basisMat: Matrix4by4;
scene ← svData.scene;
camera ← svData.camera;
sourceTree ← SVSceneToTree.AssemblyToTree[source, scene, camera];
boundSphere ← SVPreprocess3d.PreprocessForCatScan[sourceTree, camera];
R ← boundSphere.radius;
tree ← SVSceneToTree.AssemblyToTree[scene.assembly, scene, camera];
[] ← SVPreprocess3d.PreprocessForCatScan[tree, camera];
sOriginWorld ← SVMatrix3d.OriginOfMatrix[SVCoordSys.WRTWorld[source.coordSys]];
tOriginWorld ← SVMatrix3d.OriginOfMatrix[SVCoordSys.WRTWorld[target.coordSys]];
worldDirection ← SVVector3d.Sub[tOriginWorld, sOriginWorld];
Shoot many rays in this direction starting from the plane of points x satisfying (x-sOriginWorld).w = 0. First, let's find two basis vectors for this plane. They are the x and y axes of the basis matrix.
basisMat ← SVMatrix3d.MakeHorizontalMatFromZAxis[worldDirection, sOriginWorld];
MoveUntilTouchAuxAux[source, basisMat, worldDirection, R, scene, camera, tree];
}; -- end of MoveUntilTouchAux
MoveUntilTouchAuxAux:
PROC [source: Slice, basisMat: Matrix4by4, worldDirection: Vector3d,
R:
REAL, scene: Scene, camera: Camera, tree: CSGTree] = {
ray: Ray;
minDeltaT, lastSourceT, firstOtherT: REAL;
class: Classification;
success, someData: BOOL;
basisBasePt, worldBasePt: Point3d;
moveVector: Vector3d;
raysPerHalfSide: NAT = 20;
raysPerHalfSideF: REAL = 20.0;
ray ← SVRay.GetRayFromPool[];
someData ← FALSE;
FOR i:
INT
IN [-raysPerHalfSide..raysPerHalfSide-1]
DO
basisBasePt[1] ← (i+0.5)*R/raysPerHalfSideF;
FOR j:
INT
IN [-raysPerHalfSide..raysPerHalfSide-1]
DO
basisBasePt[2] ← (j+0.5)*R/raysPerHalfSideF;
basisBasePt[3] ← 0.0;
worldBasePt ← SVMatrix3d.Update[basisBasePt, basisMat];
SVRay.StuffWorldRay[ray, worldBasePt, worldDirection, camera];
class ← SVCastRays.RayCastBoundingSpheres[ray, tree.son, FALSE];
[lastSourceT, firstOtherT, success] ← SourceAndOtherT[class, source];
IF
NOT success
THEN {
SVCastRays.ReturnClassToPool[class];
LOOP;
};
IF firstOtherT < lastSourceT
THEN {
Feedback.AppendRaw[$Solidviews, "Source is already touching.", oneLiner];
Feedback.BlinkRaw[$Solidviews];
RETURN;
};
IF
NOT someData
THEN {
minDeltaT ← firstOtherT-lastSourceT;
someData ← TRUE;
}
ELSE minDeltaT ← MIN[minDeltaT, firstOtherT-lastSourceT];
SVCastRays.ReturnClassToPool[class];
ENDLOOP; -- j
ENDLOOP; -- i
IF
NOT someData
THEN {
Feedback.AppendRaw[$Solidviews, "No Obstacles Found. Object NOT moved.", oneLiner];
Feedback.BlinkRaw[$Solidviews];
RETURN;
};
moveVector ← SVVector3d.Scale[worldDirection, minDeltaT];
SVTransforms.Translate[source, scene, moveVector];
SVRay.ReturnRayToPool[ray];
};
SourceAndOtherT:
PROC [class: Classification, source: Slice]
RETURNS [lastSourceT, firstOtherT:
REAL, success:
BOOL ←
FALSE] = {
sourceFound, otherFound: BOOL;
thisAssembly: Slice;
sourceFound ← FALSE;
otherFound ← FALSE;
IF class.count = 0 THEN RETURN [0,0,FALSE];
FOR i:
NAT
IN [1..class.count]
DO
thisAssembly ← NARROW[class.primitives[i].assembly];
IF SVSceneToTree.IsSuccessorOf[thisAssembly, source]
THEN {
lastSourceT ← class.params[i];
sourceFound ← TRUE;
}
ELSE
IF
NOT otherFound
THEN {
firstOtherT ← class.params[i];
otherFound ← TRUE;
};
ENDLOOP;
success ← sourceFound AND otherFound;
};
Skewer:
PUBLIC PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
Shoot a ray from the distinguished source to the distinguished target. Using translation along the ray direction (only) bring all intersected objects together so they are touching at the ray intersection points.
source, target: Slice;
sourceSel, firstTargetSel: Selection;
editToolData: EditToolData;
sourceSel ← SVSelections.PopMovee[];
IF sourceSel = NIL THEN RETURN;
source ← sourceSel.coincident;
svData ← sourceSel.svData;
editToolData ← svData.editToolData;
firstTargetSel ← SVSelections.PopTarget[];
IF firstTargetSel = NIL THEN RETURN;
IF firstTargetSel.svData # sourceSel.svData
THEN {
Feedback.Append[svData.feedback, "Can't skewer between viewers.", oneLiner];
Feedback.Blink[svData.feedback];
RETURN;
};
target ← firstTargetSel.coincident;
SkewerAux[source, target, svData];
SVViewersOnScene.SceneNewVersion[editToolData.currentSVData];
SVViewersOnScene.PaintSceneAllViewers[$PaintEntireScene, svData.editToolData, svData.scene];
};
SkewerAux:
PROC [source, target: Slice, svData: SVData] = {
Cast a ray from source to target. Using translation along the ray direction (only) bring all intersected objects together so they are touching at the ray intersection points. The skewer extends from the earliest point hit on the source object (by the infinite line) to the last point hit on the target object. For now, a kludge: Cast a ray from 30% behind the source to 30% after the target.
sOriginWorld, tOriginWorld: Point3d;
tree: CSGTree;
ray: Ray;
class: Classification;
t: REAL;
directionWorld: Vector3d;
surfacePtInWorld, lastSurfacePtInWorld: Point3d;
primitive: Primitive;
assembly, lastAssembly: Slice;
moveVector, totalVector, localDiff: Vector3d;
scene: Scene;
camera: Camera;
sourceCS, targetCS: CoordSystem;
sourceCS ← source.coordSys;
targetCS ← target.coordSys;
scene ← svData.scene;
camera ← svData.camera;
tree ← SVSceneToTree.AssemblyToTree[scene.assembly, scene, camera];
[] ← SVPreprocess3d.PreprocessForCatScan[tree, camera];
sOriginWorld ← SVMatrix3d.OriginOfMatrix[SVCoordSys.WRTWorld[sourceCS]];
tOriginWorld ← SVMatrix3d.OriginOfMatrix[SVCoordSys.WRTWorld[targetCS]];
ray ← SVRay.GetRayFromPool[];
direction
World ← SVVector3d.Sub[tOrigin
World, sOrigin
World];
Here comes the kludge
sOriginWorld ← SVVector3d.Sub[sOriginWorld, SVVector3d.Scale[directionWorld, 0.3]];
direction
World ← SVVector3d.Scale[direction
World, 1.6];
End of kludge.
SVRay.StuffWorldRay[ray, sOriginWorld, directionWorld, camera];
class ← SVCastRays.RayCastBoundingSpheres[ray, tree.son, FALSE];
SVCastRays.SortClassByPrimitive[class];
IF class.count = 0
THEN {
SVCastRays.ReturnClassToPool[class];
SVRay.ReturnRayToPool[ray];
RETURN;
};
lastAssembly ← NARROW[class.primitives[1].assembly];
t ← class.params[1];
lastSurfacePtInWorld[1] ← sOriginWorld[1] + t*directionWorld[1];
lastSurfacePtInWorld[2] ← sOriginWorld[2] + t*directionWorld[2];
lastSurfacePtInWorld[3] ← sOriginWorld[3] + t*directionWorld[3];
totalVector ← [0,0,0];
FOR i:
NAT
IN [2..class.count]
DO
t ← class.params[i]; -- the parameter of the ray intersection
IF t > 1.0 THEN EXIT; -- Compression occurs only BETWEEN source and target.
primitive ← class.primitives[i];
surfacePtInWorld[1] ← sOriginWorld[1] + t*directionWorld[1];
surfacePtInWorld[2] ← sOriginWorld[2] + t*directionWorld[2];
surfacePtInWorld[3] ← sOriginWorld[3] + t*directionWorld[3];
assembly ← NARROW[primitive.assembly];
IF lastAssembly # assembly
THEN {
localDiff ← SVVector3d.Sub[lastSurfacePtInWorld, surfacePtInWorld];
moveVector ← SVVector3d.Add[localDiff, totalVector];
totalVector ← SVVector3d.Add[totalVector, localDiff];
SVTransforms.Translate[assembly, scene, moveVector];
};
lastAssembly ← assembly;
lastSurfacePtInWorld ← surfacePtInWorld;
ENDLOOP;
SVCastRays.ReturnClassToPool[class];
SVRay.ReturnRayToPool[ray];
};
AddCylinder:
PUBLIC PROC [event:
LIST
OF
REF
ANY, svData: SVData] = {
Add a cylinder whose radius is taken from the editTool and whose height is just large enough so it stretches from the distinguished source to the distinguished target.
sourceSel, firstTargetSel: Selection;
sourceCS, targetCS: CoordSystem;
editToolData: EditToolData;
radius: REAL;
scene: Scene;
sourceSel ← SVSelections.PopMovee[];
IF sourceSel = NIL THEN RETURN;
firstTargetSel ← SVSelections.PopTarget[];
IF firstTargetSel = NIL THEN RETURN;
IF
sourceSel.svData # firstTargetSel.svData
THEN {
Feedback.Append[svData.feedback, "Can't add cylinder between viewers.", oneLiner];
Feedback.Blink[svData.feedback];
RETURN;
};
svData ← sourceSel.svData;
scene ← svData.scene;
editToolData ← svData.editToolData;
sourceCS ← sourceSel.coincident.coordSys;
radius ← SVViewerTools.GetReal[editToolData.cylinderSection.radius, 20];
FOR targetSel: Selection ← firstTargetSel, SVSelections.NextTarget[]
UNTIL targetSel =
NIL
DO
targetCS ← targetSel.coincident.coordSys;
AddCylinderAux[sourceCS, targetCS, radius, editToolData, scene];
ENDLOOP;
SVViewersOnScene.SceneNewVersion[editToolData.currentSVData];
SVViewersOnScene.PaintSceneAllViewers[$PaintEntireScene, editToolData, svData.scene];
};
AddCylinderAux:
PROC [sourceCS, targetCS: CoordSystem, radius:
REAL, editToolData: EditToolData, scene: Scene] = {
Add a cylinder whose radius is taken from the editTool and whose height is just large enough so it stretches from sourceCS to targetCS.
cylMO: MasterObject;
moFound: BOOL;
cylinderRec: CylinderRec;
sOriginWorld, tOriginWorld, midPointWorld: Point3d;
cylWorld, superWorld, cylSuper: Matrix4by4;
cylinderY: Vector3d;
height: REAL;
newAssembly, superAssembly: Slice;
success: BOOL;
addSucceeds: BOOL ← TRUE;
cylName: Rope.ROPE;
[cylMO, moFound] ← SVScene.FindObjectFromName["cylinder", scene];
IF NOT moFound THEN ERROR;
cylinderRec ← NARROW[cylMO.mainBody];
sOriginWorld ← SVMatrix3d.OriginOfMatrix[SVCoordSys.WRTWorld[sourceCS]];
tOriginWorld ← SVMatrix3d.OriginOfMatrix[SVCoordSys.WRTWorld[targetCS]];
midPointWorld ← SVVector3d.Add[sOriginWorld, tOriginWorld];
midPointWorld ← SVVector3d.Scale[midPointWorld, 0.5];
cylinderY ← SVVector3d.Sub[tOriginWorld, sOriginWorld];
height ← SVVector3d.Magnitude[cylinderY];
cylWorld ← MakeHorizontalMatFromYAxis[cylinderY, midPointWorld];
radius ← radius/cylinderRec.radius; height ← height/cylinderRec.height;
cylName ← ViewerTools.GetContents[editToolData.sceneSection.new];
cylName ← SVCoordSys.UniqueNameFrom[cylName, scene.coordSysRoot];
[superAssembly, success] ← SVEditUser.GetParent[editToolData];
IF NOT success THEN RETURN;
superWorld ← SVCoordSys.WRTWorld[superAssembly.coordSys];
cylSuper ← SVMatrix3d.AInTermsOfB[cylWorld, superWorld];
[newAssembly, ----, addSucceeds] ← SVAssembly.CreatePrimitive[cylName, "cylinder", scene];
IF NOT addSucceeds THEN RETURN;
addSucceeds ← SVAssembly.AddPrimitive[newAssembly, [radius, height, radius], superAssembly, cylSuper, scene];
}; -- end of AddCylinderAux
MakeHorizontalMatFromYAxis:
PROC [yAxis: Vector3d, origin: Point3d]
RETURNS [mat: Matrix4by4] = {
Uses yAxis as it is. Finds a horizontal x axis orthogonal to zAxis. If yAxis is vertical, then there are infinitely many. Chooses [1,0,0] in this case.
xAxis: Vector3d;
IF yAxis[1] = 0 AND yAxis[3] = 0 THEN xAxis ← [1,0,0]
ELSE xAxis ← SVVector3d.CrossProduct[[0,1,0], yAxis];
mat ← SVMatrix3d.MakeMatFromYandXAxis[yAxis, xAxis, origin];
};
END.