GGEventImplG.mesa
Copyright Ó 1986, 1987, 1988, 1989 by Xerox Corporation. All rights reserved.
Bier, November 20, 1991 10:13 am PST
Kurlander, July 21, 1987 10:52:27 am PDT
Eisenman, August 11, 1987 5:36:57 pm PDT
Pier, November 30, 1990 1:22 pm PST
Maureen Stone, October 5, 1987 11:15:23 am PDT
Doug Wyatt, December 7, 1989 3:14:41 pm PST
Contents: stuff implemented in DCedar but not PCedar (yet)
EdgeFit operations extracted from GGEventImplC.mesa
SetCombine extracted from GGEventImplA.mesa
DIRECTORY
CedarProcess USING [CheckAbort, SetPriority],
Feedback USING [Append, PutF],
FeedbackOps USING [GetLabel],
FitBasic USING [Handle],
FitState USING [AddSample, Create, CurrentJoints, CurrentSamples, ResetData],
FitStateUtils USING [ForAllSamples, SampleProc],
GeometricLogicOps USING [ANDs, MaskFill, MINUSs, NOTs, ORs, PathActionProc, Scale, ScaleRec, Set, SetToFlatPath, XORs],
GGBasicTypes USING [Point],
GGFromImager USING [Capture],
GGHistory USING [NewCapture, PushCurrent],
GGInterfaceTypes USING [GGData],
GGModelTypes USING [BoundBox, Scene, Slice, SliceDescriptor, Traj, TrajData],
GGOutline USING [CreateOutline, HasHoles],
GGParent USING [FirstChild],
GGParseIn USING [ReadWNAT, ReadWReal, ReadWRope, SyntaxError],
GGRefresh USING [InterpressEntireScene],
GGRefreshTypes,
GGScene USING [AddSlice, BoundBoxOfSelected, DeleteAllSelected, MergeScenes, WalkSelectedSlices],
GGSegment USING [MakeBezier, MakeLine],
GGSegmentTypes USING [Segment],
GGSelect USING [DeselectAll, SelectAll],
GGSliceOps USING [BuildPath, GetBoundBox, GetFillColor, GetType, IsCompleteParts, WalkSegments],
GGState USING [GetDefaultFillColor],
GGTraj USING [AddSegment, CloseByDistorting, CreateTraj],
GGUserInput USING [ArgumentType, RegisterAction, UserInputProc],
GGWindow USING [RestoreScreenAndInvariants],
Imager USING [ClipRectangle, Color, Context, MaskFill, PathProc, SetColor, SetStrokeWidth, TranslateT],
ImagerBox USING [Rectangle, RectangleFromBox],
ImagerPixel USING [MakePixelMap],
ImagerSample USING [Fill, Get, ObtainScratchMap, ReleaseScratchMap, SampleMap],
ImagerSmoothContext USING [Create, SetOutputBuffer],
ImagerTransformation USING [Transformation],
IO USING [PutFR, RIS, STREAM],
LSPiece USING [Metrics, MetricsRec],
Outline USING [GetOutline, Handle, OutlineEdge, Rec],
PBasics USING [IsBound],
PiecewiseFit USING [Cubic2Proc, Data, DataRec, GrowSpline],
PotentialKnots USING [CircleTangents, DynPoly],
Real USING [Ceiling, Round],
Rope USING [ROPE],
SF USING [Vec];
GGEventImplG: CEDAR PROGRAM
IMPORTS CedarProcess, Feedback, FeedbackOps, FitState, FitStateUtils, GeometricLogicOps, GGFromImager, GGHistory, GGOutline, GGParent, GGParseIn, GGRefresh, GGScene, GGSegment, GGSelect, GGSliceOps, GGState, GGTraj, GGUserInput, GGWindow, Imager, ImagerBox, ImagerPixel, ImagerSample, ImagerSmoothContext, IO, Outline, PBasics, PiecewiseFit, PotentialKnots, Real
EXPORTS GGInterfaceTypes = BEGIN
BoundBox: TYPE = GGModelTypes.BoundBox;
GGData: TYPE = GGInterfaceTypes.GGData;
Point: TYPE = GGBasicTypes.Point;
RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj;
ROPE: TYPE = Rope.ROPE;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGSegmentTypes.Segment;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
Traj: TYPE = GGModelTypes.Traj;
TrajData: TYPE = GGModelTypes.TrajData;
Transformation: TYPE = ImagerTransformation.Transformation;
UserInputProc: TYPE = GGUserInput.UserInputProc;
EdgeFit (formerly in GGEventImplC)
EdgeFit Menu
EdgeData: TYPE = REF EdgeDataObj;
EdgeDataObj: TYPE = RECORD [
sampleMap: ImagerSample.SampleMap,
trans: Point,
edgeCount: CARD,
height: INTEGER,
ggData: GGData
];
BadThreshold: SIGNAL = CODE;
CheckThreshold: PROC [r: REAL] RETURNS [REAL] = {
IF r NOT IN (0.0 .. 1.0) THEN SIGNAL BadThreshold;
RETURN[r];
};
GetSample: PROC [client: REF, x,y: INT] RETURNS [INT] = {
edgeData: EdgeData ← NARROW[client];
sample: WORD ← ImagerSample.Get[edgeData.sampleMap, [f: x, s: edgeData.height-y]];
RETURN[sample];
};
FeedBackEdge: PROC [client: REF, x0,y0,x1,y1: REAL] RETURNS [abort: BOOLFALSE] = {
edgeData: EdgeData ← NARROW[client];
ggData: GGData ← edgeData.ggData;
ggData.refresh.spotPoint ← [x0+edgeData.trans.x, y0+edgeData.trans.y]; --back to ggworld coordinates
ggData.refresh.hitPoint ← [x1+edgeData.trans.x, y1+edgeData.trans.y];
edgeData.edgeCount ← edgeData.edgeCount+1;
CedarProcess.CheckAbort[];
IF edgeData.edgeCount MOD 20 = 0 THEN Feedback.PutF[ggData.router, middle, $Feedback, "."];
GGWindow.RestoreScreenAndInvariants[paintAction: $FitFeedback, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
DoFit: PROC[ggData: GGData, makeCurves: BOOLTRUE] = {
thisTraj: Traj;
fit: FitBasic.Handle;
lastPt: Point ← [0,0];
h: Outline.Handle;
edgeData: EdgeData;
contourCount: NAT ← 0;
NewPoint: PROC [x,y: REAL] = {
pt: Point ← [x+edgeData.trans.x, y+edgeData.trans.y];
FitState.AddSample[fit, pt.x, pt.y];
};
NewCubic: PiecewiseFit.Cubic2Proc = {
IF thisTraj = NIL THEN thisTraj ← GGTraj.CreateTraj[bezier.b0];
CedarProcess.CheckAbort[];
Feedback.PutF[ggData.router, middle, $Feedback, "."];
[] ← GGTraj.AddSegment[thisTraj, hi, GGSegment.MakeBezier[bezier.b0, bezier.b1, bezier.b2, bezier.b3, NIL], lo];
};
ContourFromSelection: PROC = {
selectedBox: BoundBox ← GGScene.BoundBoxOfSelected[ggData.scene, normal];
rect: ImagerBox.Rectangle ← ImagerBox.RectangleFromBox[[selectedBox.loX, selectedBox.loY, selectedBox.hiX, selectedBox.hiY] ]; --gg type to Imager type
size: SF.Vec ~ [s: Real.Ceiling[rect.h], f: Real.Ceiling[rect.w]];
context: Imager.Context ← ImagerSmoothContext.Create[size: size];
sampleMap: ImagerSample.SampleMap ← ImagerSample.ObtainScratchMap[box: [max: size], bitsPerSample: 8];
ImagerSample.Fill[sampleMap, [max: size], 255];
ImagerSmoothContext.SetOutputBuffer[context: context, pixelMap: ImagerPixel.MakePixelMap[s0: sampleMap], components: LIST[$Intensity]];
Imager.TranslateT[context, [-rect.x, -rect.y]]; --move lower left corner to origin
Imager.ClipRectangle[context,rect]; --clip in Gargoyle world coordinates
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Fit: rendering selected region for thresholding"];
GGRefresh.InterpressEntireScene[dc: context, ggData: ggData]; -- not really Interpress. Means just draw the objects.
edgeData ← NEW[EdgeDataObj ← [sampleMap, [rect.x, rect.y], 0, 0, ggData]];
h ← NEW[Outline.Rec];
h.tValue ← ggData.fitData.threshold*255.0;
IF h.tValue=Real.Round[h.tValue] THEN h.tValue ← h.tValue+0.0001; -- can't use integer thresholds
h.border ← 255;
h.xStart ← 0;
h.yStart ← 0;
h.xPixels ← size.f;
h.yPixels ← size.s;
edgeData.height ← h.yPixels-1;
h.get ← GetSample;
h.newEdge ← FeedBackEdge;
h.client ← edgeData;
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Fit: finding edges in selected region"];
contourCount ← Outline.OutlineEdge[h];
ImagerSample.ReleaseScratchMap[map: sampleMap]; sampleMap ← NIL; context ← NIL;
};
CedarProcess.SetPriority[background];
GGHistory.NewCapture["Fit", ggData]; -- capture scene BEFORE UPDATE
ContourFromSelection[]; --local proc rather than inline for readability
Feedback.PutF[ggData.router, begin, $Feedback, "Fit: %g contours found. Begin fitting", [integer[contourCount]]];
BEGIN
lineLength: REAL ← 10; --minimum line length to trigger corner check in DynPoly
thisOutline: Slice;
data: PiecewiseFit.Data;
metrics: LSPiece.Metrics ← NEW[LSPiece.MetricsRec ← [
maxItr: ggData.fitData.maxIterations, maxDev: ggData.fitData.tolerance, sumErr: 0, deltaT: 0]];
checkSamples: PROC RETURNS[ok: BOOLEAN] = {
count: NAT ← 0;
proc: FitStateUtils.SampleProc = {count ← count+1};
FitStateUtils.ForAllSamples[fit.slist, proc];
IF count > 3 THEN RETURN[TRUE] ELSE RETURN[FALSE];
};
checkJoints: PROC RETURNS[ok: BOOLEAN] = {
count: NAT ← 0;
proc: FitStateUtils.SampleProc = {
IF s.jointType#none THEN count ← count+1};
FitStateUtils.ForAllSamples[fit.slist, proc];
IF count > 2 THEN RETURN[TRUE] ELSE RETURN[FALSE];
};
fit ← FitState.Create[];
fit.closed ← TRUE;
fit.minDist ← ggData.fitData.minDistance;
FOR i: CARD IN [0..contourCount) DO
thisTraj ← NIL;
Outline.GetOutline[h.data, NewPoint, i]; -- make a FitState.Handle
IF NOT checkSamples[] THEN { -- do this here as minDistance filter affects total
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Fit: less than 3 samples in outline. Skipping outline"];
LOOP;
};
PotentialKnots.DynPoly[fit, ggData.fitData.polyPenalty, lineLength, ggData.fitData.angle]; -- set potential knots
IF makeCurves THEN {
IF NOT checkJoints[] THEN {
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Fit: less than 2 potential knots in outline. Skipping outline"];
LOOP;
};
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Fit: potential knots found. Start curve finding"];
PotentialKnots.CircleTangents[fit, ggData.fitData.angle]; --set the tangents at the potential knots
data ← NEW[PiecewiseFit.DataRec ← [
samples: FitState.CurrentSamples[fit],
closed: TRUE,
joints: NIL, tangents: NIL]];
[data.joints, data.tangents] ← FitState.CurrentJoints[fit];
PiecewiseFit.GrowSpline[data, metrics, NewCubic];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Fit: new curve outline found"];
}
ELSE {
lastPoint: Point ← [0,0];
sampleProc: FitStateUtils.SampleProc = {
IF s.jointType#none THEN {
p: Point ← [s.xy.x, s.xy.y];
IF thisTraj=NIL THEN thisTraj ← GGTraj.CreateTraj[p]
ELSE {
newLine: Segment ← GGSegment.MakeLine[lastPoint, p, NIL];
[] ← GGTraj.AddSegment[thisTraj, hi, newLine, lo];
};
lastPoint ← p;
};
};
IF NOT checkJoints[] THEN {
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Fit: less than 2 vertices in outline. Skipping outline"];
LOOP;
};
FitStateUtils.ForAllSamples[fit.slist, sampleProc];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Fit: new Polygon found"];
};
GGTraj.CloseByDistorting[thisTraj, lo]; --no distortion should be necessary
thisOutline ← GGOutline.CreateOutline[thisTraj, NIL];
GGScene.AddSlice[ggData.scene, thisOutline, -1];
Paint the object for feedback
ggData.refresh.addedObject ← thisOutline;
ggData.refresh.startBoundBox^ ← GGSliceOps.GetBoundBox[thisOutline]^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
FitState.ResetData[fit, samples]; --cleanup after fit
FitState.ResetData[fit, links];
ENDLOOP;
END;
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
CedarProcess.SetPriority[normal];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Fit: completed. %g outlines added to scene", [integer[contourCount]]];
GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE];
};
FitIsLoaded: PROC [ggData: GGData] RETURNS [BOOLFALSE] = TRUSTED {
IF PBasics.IsBound[LOOPHOLE[Outline.GetOutline]] AND PBasics.IsBound[LOOPHOLE[ImagerSmoothContext.Create]] THEN RETURN[TRUE];
Feedback.Append[ggData.router, oneLiner, $Complaint, "Fit failed: please install Fit and ImagerSmooth, then retry this operation"];
};
FitLines: UserInputProc = {
IF FitIsLoaded[ggData] THEN DoFit[ggData, FALSE];
};
FitCurves: UserInputProc = {
IF FitIsLoaded[ggData] THEN DoFit[ggData, TRUE];
};
SetFitParameters: UserInputProc = {
ENABLE GGParseIn.SyntaxError=> GOTO SyntaxError;
tolerance, minDistance, threshold, angle, polyPenalty: REAL;
maxIterations: NAT;
s: IO.STREAMIO.RIS[NARROW[event.rest.first]]; -- same form as ShowFitParameters
GGParseIn.ReadWRope[s, "Fit: threshold:"];
threshold ← GGParseIn.ReadWReal[s];
GGParseIn.ReadWRope[s, "minDistance:"];
minDistance ← GGParseIn.ReadWReal[s];
GGParseIn.ReadWRope[s, "polyPenalty:"];
polyPenalty ← GGParseIn.ReadWReal[s];
GGParseIn.ReadWRope[s, "tolerance:"];
tolerance ← GGParseIn.ReadWReal[s];
GGParseIn.ReadWRope[s, "angle:"];
angle ← GGParseIn.ReadWReal[s];
GGParseIn.ReadWRope[s, "iterations:"];
maxIterations ← GGParseIn.ReadWNAT[s];
Here if you didn't get a syntax error
ggData.fitData.threshold ← CheckThreshold[threshold ! BadThreshold => GOTO Abort];
ggData.fitData.minDistance ← minDistance;
ggData.fitData.polyPenalty ← polyPenalty;
ggData.fitData.tolerance ← tolerance;
ggData.fitData.angle ← angle;
ggData.fitData.maxIterations ← maxIterations;
ShowFitParameters[ggData, NIL];
EXITS
Abort => {
Feedback.Append[ggData.router, oneLiner, $Complaint, "Fit failed: fit threshold must be greater than 0.0 and less than 1.0"];
};
SyntaxError => {
Feedback.Append[ggData.router, oneLiner, $Complaint, "Fit failed: syntax error while parsing Fit parameters: use exact format from ShowFitParameters"];
};
};
ShowFitParameters: UserInputProc = {
rope: ROPEIO.PutFR["Fit: threshold: %g minDistance: %g polyPenalty: %g", [real[ggData.fitData.threshold]], [real[ggData.fitData.minDistance]], [real[ggData.fitData.polyPenalty]] ];
Feedback.PutF[ggData.router, oneLiner, $Show, "%g tolerance: %g angle: %g iterations: %g", [rope[rope]], [real[ggData.fitData.tolerance]], [real[ggData.fitData.angle]], [integer[ggData.fitData.maxIterations]]];
};
reallyBigReal: REAL = 1.0e37;
SetFitTolerance: UserInputProc = {
r: REALNARROW[event.rest.first, REF REAL]^;
IF r>reallyBigReal THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "SetFit failed: select a reasonable value for fit tolerance"];
RETURN;
};
ggData.fitData.tolerance ← r;
ShowFitParameters[ggData, NIL];
};
SetFitMinDistance: UserInputProc = {
r: REALNARROW[event.rest.first, REF REAL]^;
IF r>reallyBigReal THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "SetFit failed: select a reasonable value for fit min distance"];
RETURN;
};
ggData.fitData.minDistance ← r;
ShowFitParameters[ggData, NIL];
};
SetFitIterations: UserInputProc = {
i: INTNARROW[event.rest.first, REF INT]^;
IF i=LAST[INT] THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "SetFit failed: select a reasonable value for fit iterations"];
RETURN;
};
ggData.fitData.maxIterations ← i;
ShowFitParameters[ggData, NIL];
};
SetFitThreshold: UserInputProc = {
threshold: REALNARROW[event.rest.first, REF REAL]^;
ggData.fitData.threshold ← CheckThreshold[threshold ! BadThreshold => GOTO Abort];
ShowFitParameters[ggData, NIL];
EXITS
Abort => {
Feedback.Append[ggData.router, oneLiner, $Complaint, "SetFit failed: fit threshold must be greater than 0.0 and less than 1.0"];
};
};
SetFitAngle: UserInputProc = {
r: REALNARROW[event.rest.first, REF REAL]^;
IF r>reallyBigReal THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "SetFit failed: select a reasonable value for fit angle"];
RETURN;
};
ggData.fitData.angle ← r;
ShowFitParameters[ggData, NIL];
};
SetFitPolyPenalty: UserInputProc = {
r: REALNARROW[event.rest.first, REF REAL]^;
IF r>reallyBigReal THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "SetFit failed: select a reasonable value for fit poly penalty"];
RETURN;
};
ggData.fitData.polyPenalty ← r;
ShowFitParameters[ggData, NIL];
};
SetCombine (formerly in GGEventImplA)
SetCombine: UserInputProc ~ {
IF PBasics.IsBound[LOOPHOLE[GeometricLogicOps.SetToFlatPath]] THEN {
DoCombine: PROC [nextD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
SliceDescriptorWalkProc. Called for each selected slice.
IsClosed: PROC [slice: Slice] RETURNS [BOOL] = {
firstChild: Slice;
RETURN[GGSliceOps.GetType[slice]#$Outline OR GGOutline.HasHoles[slice] OR (GGSliceOps.GetType[(firstChild ← GGParent.FirstChild[slice, first])]=$Traj AND NARROW[firstChild.data, TrajData].role#open)];
};
IsLineSegment: PROC [seg: Segment, transform: Transformation] RETURNS [keep: BOOL] = {
keep ← seg.class.type=$Line;
};
nextD should be a complete descriptor for closed objects containing only $Line segments. We verify:
linesD: SliceDescriptor ← GGSliceOps.WalkSegments[nextD.slice, IsLineSegment];
linesD is a descriptor for all the straight line segments in nextD.slice
IF GGSliceOps.IsCompleteParts[nextD] AND GGSliceOps.IsCompleteParts[linesD] AND IsClosed[nextD.slice] THEN {
i.e. every part was selected and every segment is a line segment of a closed shape
SlicePath: Imager.PathProc = {
GGSliceOps.BuildPath[nextD.slice, NIL, NIL, moveTo, lineTo, curveTo, conicTo, arcTo];
};
nextSet ← GeometricLogicOps.MaskFill[SlicePath, FALSE, compositeScale];
opSet ← IF opSet=NIL THEN nextSet ELSE
SELECT opAtom FROM
$NOTs => GeometricLogicOps.NOTs[nextSet],
$ANDs => GeometricLogicOps.ANDs[opSet, nextSet],
$ORs => GeometricLogicOps.ORs[opSet, nextSet],
$MINUSs => GeometricLogicOps.MINUSs[opSet, nextSet],
$XORs => GeometricLogicOps.XORs[opSet, nextSet],
ENDCASE => ERROR;
lastD ← nextD;
}
ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "Combine failed: only complete closed objects composed solely of straight line segments are combined"];
};
lastD: SliceDescriptor; -- describes the last legitimate slice participating in Combine
compositeScale: GeometricLogicOps.Scale ← NEW[GeometricLogicOps.ScaleRec];
nextSet, opSet: GeometricLogicOps.Set;
opAtom: ATOMNARROW[event.rest.first]; -- what GeometricLogic operation to perform
deleteAtom: ATOMNARROW[event.rest.rest.first]; -- optional delete of selected objects
bBox: BoundBox;
GGHistory.NewCapture["Geometric combine", ggData]; -- capture scene BEFORE UPDATE
bBox ← GGScene.BoundBoxOfSelected[scene: ggData.scene, selectClass: normal, sliceLevel: TRUE];
compositeScale^ ← [minX: bBox.loX, minY: bBox.loY, maxX: bBox.hiX, maxY: bBox.hiY];
ggData.refresh.startBoundBox^ ← bBox^;
[] ← GGScene.WalkSelectedSlices[ggData.scene, first, DoCombine, normal, $Outline];
[] ← GGScene.WalkSelectedSlices[ggData.scene, first, DoCombine, normal, $Box];
IF opSet#NIL THEN {
SceneAction: PROC [context: Imager.Context] = {
MaskSetProc: GeometricLogicOps.PathActionProc = { -- PROC [path: Imager.PathProc]
Imager.MaskFill[context: context, path: path, oddWrap: FALSE];
};
Imager.SetStrokeWidth[context, 0.0];
Imager.SetColor[context, IF fillColor#NIL THEN fillColor ELSE GGState.GetDefaultFillColor[ggData] ];
GeometricLogicOps.SetToFlatPath[opSet, MaskSetProc];
};
fillColor: Imager.Color ← GGSliceOps.GetFillColor[lastD.slice, NIL].color;
scene: Scene ← GGFromImager.Capture[action: SceneAction, camera: ggData.camera]; -- get a scene containing only the combined object
IF deleteAtom=$DeleteSelected THEN [] ← GGScene.DeleteAllSelected[ggData.scene] ELSE GGSelect.DeselectAll[ggData.scene, normal];
GGSelect.SelectAll[scene, normal];
ggData.scene ← GGScene.MergeScenes[ggData.scene, scene]; -- is this all it takes ??
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Combine: combined object added to scene"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE];
}
ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "Combine failed: no legitimate selections for Combine"];
}
ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "Combine failed: please install GeometricLogicOps, then retry this operation"];
};
Registration
RegisterAction: PROC [atom: ATOM, eventProc: GGUserInput.UserInputProc, argType: GGUserInput.ArgumentType, causeMouseEventsToComplete: BOOLTRUE, ensureUnique: BOOLTRUE] = GGUserInput.RegisterAction;
RegisterEventProcs: PROC = {
EdgeFit Menu
RegisterAction[$FitCurves, FitCurves, none];
RegisterAction[$ShowFitParameters, ShowFitParameters, none];
RegisterAction[$FitLines, FitLines, none];
RegisterAction[$FitTolerance, SetFitTolerance, refReal];
RegisterAction[$SetFitParameters, SetFitParameters, rope];
RegisterAction[$SetFitPolyPenalty, SetFitPolyPenalty, refReal];
RegisterAction[$SetFitAngle, SetFitAngle, refReal];
RegisterAction[$SetFitThreshold, SetFitThreshold, refReal];
RegisterAction[$SetFitIterations, SetFitIterations, refInt];
RegisterAction[$SetFitMinDistance, SetFitMinDistance, refReal];
RegisterAction[$SetCombine, SetCombine, none];
};
RegisterEventProcs[];
END.