<> <> <> <> <> <> <> <> <<>> <> <> <> <<>> 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; <> <<>> <> <<>> 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: BOOL _ FALSE] = { 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: BOOL _ TRUE] = { 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]; <> 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 [BOOL _ FALSE] = 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.STREAM _ IO.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]; <> 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: ROPE _ IO.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: REAL _ NARROW[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: REAL _ NARROW[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: INT _ NARROW[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: REAL _ NARROW[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: REAL _ NARROW[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: REAL _ NARROW[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: UserInputProc ~ { IF PBasics.IsBound[LOOPHOLE[GeometricLogicOps.SetToFlatPath]] THEN { DoCombine: PROC [nextD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { <> 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; }; <> linesD: SliceDescriptor _ GGSliceOps.WalkSegments[nextD.slice, IsLineSegment]; <> IF GGSliceOps.IsCompleteParts[nextD] AND GGSliceOps.IsCompleteParts[linesD] AND IsClosed[nextD.slice] THEN { <> 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: ATOM _ NARROW[event.rest.first]; -- what GeometricLogic operation to perform deleteAtom: ATOM _ NARROW[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"]; }; <> RegisterAction: PROC [atom: ATOM, eventProc: GGUserInput.UserInputProc, argType: GGUserInput.ArgumentType, causeMouseEventsToComplete: BOOL _ TRUE, ensureUnique: BOOL _ TRUE] = GGUserInput.RegisterAction; RegisterEventProcs: PROC = { <> 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.