DIRECTORY Args, Commander, Controls, Draw2d, FileNames, FS, G2dBasic, G2dContour, G3dBasic, G3dControl, G3dCurve, G3dDraw, G3dMatrix, G3dPlane, G3dSpline, G3dTool, G3dTube, G3dVector, G3dView, Icons, IO, ImagerInterpress, Menus, MessageWindow, Real, Rope, ViewerOps; G3dTubeCmdImpl: CEDAR PROGRAM IMPORTS Args, Controls, Draw2d, FileNames, FS, G2dContour, G3dControl, G3dCurve, G3dDraw, G3dMatrix, G3dPlane, G3dSpline, G3dTool, G3dTube, G3dVector, G3dView, Icons, IO, ImagerInterpress, MessageWindow, Real, Rope, ViewerOps ~ BEGIN ClickProc: TYPE ~ Controls.ClickProc; Control: TYPE ~ Controls.Control; ControlList: TYPE ~ Controls.ControlList; Mouse: TYPE ~ Controls.Mouse; OuterData: TYPE ~ Controls.OuterData; Zip: TYPE ~ Draw2d.Zip; Contour: TYPE ~ G2dContour.Contour; Pair: TYPE ~ G3dBasic.Pair; Triple: TYPE ~ G3dBasic.Triple; Quad: TYPE ~ G3dBasic.Quad; Hold: TYPE ~ G3dControl.Hold; Matrix: TYPE ~ G3dMatrix.Matrix; Plane: TYPE ~ G3dPlane.Plane; RenderTool: TYPE ~ G3dTool.Tool; SplineRep: TYPE ~ G3dSpline.SplineRep; STREAM: TYPE ~ IO.STREAM; ROPE: TYPE ~ Rope.ROPE; Details: TYPE ~ G3dTube.Details; DetailType: TYPE ~ G3dTube.DetailType; RadiusMode: TYPE ~ G3dTube.RadiusMode; Tube: TYPE ~ G3dTube.Tube; TubeProc: TYPE ~ G3dTube.TubeProc; TubeRep: TYPE ~ G3dTube.TubeRep; Pendings: TYPE ~ REF PendingsRep; PendingsRep: TYPE ~ RECORD [ epsilon: BOOL ¬ FALSE, size: BOOL ¬ FALSE, r: BOOL ¬ FALSE, taper: BOOL ¬ FALSE, tens: BOOL ¬ FALSE, tw0: BOOL ¬ FALSE, tw1: BOOL ¬ FALSE, cres: BOOL ¬ FALSE, holdPosition: BOOL ¬ FALSE, holdTangent: BOOL ¬ FALSE, holdRoll: BOOL ¬ FALSE, shape: BOOL ¬ FALSE, skin: BOOL ¬ FALSE ]; Pick: TYPE ~ REF PickRep; PickRep: TYPE ~ RECORD [ type: {point, segment, subTree} ¬ point, pos, tan: Triple ¬ G3dBasic.origin, tube: Tube ¬ NIL, t: REAL ¬ 1.0, selected0, selected1: Tube ¬ NIL, dividePending: BOOL ¬ FALSE, plane: Plane ¬ [0.0, 0.0, 0.0, 0.0], mouse: Mouse ]; ActionType: TYPE ~ {none, select, camera, spline, shape, button}; Tool: TYPE ~ REF ToolRep; ToolRep: TYPE ~ RECORD [ controls: ControlList ¬ NIL, -- list of the following controls: size: Control ¬ NIL, -- size of tube r0: Control ¬ NIL, -- radius 0 r1: Control ¬ NIL, -- radius 1 tw0: Control ¬ NIL, -- twist 0 tw1: Control ¬ NIL, -- twist 1 tens0: Control ¬ NIL, -- tension 0 tens1: Control ¬ NIL, -- tension 1 cres: Control ¬ NIL, -- circ. resolution epsilon: Control ¬ NIL, -- spline flatness value taper: Control ¬ NIL, -- tube taper hold: Hold ¬ NIL, -- handle on a point and its tangent lim: Control ¬ NIL, -- vector limit renderTool: RenderTool ¬ NIL, details: Details ¬ NIL, pendings: Pendings ¬ NIL, noDraw: BOOL ¬ FALSE, vectors: BOOL ¬ TRUE, neverMoused: BOOL ¬ TRUE, mouse: Mouse ¬ [[0, 0], none, left], mousePrev: Mouse ¬ [[0, 0], none, left], pick: Pick ¬ NIL, action: ActionType ¬ none, radiusMode: RadiusMode ¬ linear, round: BOOL ¬ FALSE, xformIO: BOOL ¬ FALSE, -- view-xform points when writing file ground: Plane ¬ [], -- something new frill: Tube ¬ NIL, -- the frilled tube tube: Tube ¬ NIL -- all the tube splines ]; TubeDesignCmd: Commander.CommandProc ~ { NewControl: PROC [name: ROPE, min, max, init: REAL, lin: BOOL ¬ TRUE, precision: NAT ¬ 3] RETURNS [c: Control] ~ { c ¬ Controls.NewControl[ name, vSlider, t, min, max, init, TubeControl,, precision,,,,,,,,, IF lin THEN lin ELSE exp]; }; err: ROPE; t: Tool ~ NEW[ToolRep]; SELECT Args.NArgs[cmd] FROM 0 => t.tube ¬ ResetTube[]; 1 => IF (err ¬ TubeFromFile[t, Args.GetRope[cmd]]) # NIL THEN RETURN[$Failure, err]; ENDCASE => RETURN[$Failure, designUsage]; t.details ¬ NEW[G3dTube.DetailsRep]; t.pendings ¬ NEW[PendingsRep]; t.hold ¬ G3dControl.InitHold[TubeControl, t]; t.tw0 ¬ NewControl["tw0", -1000., 1000., 0.0]; t.tw1 ¬ NewControl["tw1", -1000., 1000., 0.0]; t.tens0 ¬ NewControl["tens0", -3.0, 3.0, 0.0]; t.tens1 ¬ NewControl["tens1", -3.0, 3.0, 0.0]; t.r0 ¬ NewControl["r0", 0.0, 0.5, 0.0]; t.r1 ¬ NewControl["r1", 0.0, 0.5, 0.0]; t.size ¬ NewControl["size", 0.0, 4.0, 1.0]; t.epsilon ¬ NewControl["eps", 0.0, 0.2, 0.03, FALSE]; t.taper ¬ NewControl["taper", -3.0, 3.0, 0.0, FALSE]; t.cres ¬ NewControl["cres", 2.0, 25.0, 6.0, TRUE, 0]; t.lim ¬ NewControl["lim", 0.0, 1000.0, 500.0, TRUE, 0]; t.pendings ¬ NEW[PendingsRep ¬ [shape: TRUE]]; MakePick[t]; SetPick[t]; Moused[t, [[0, 0], up, left], $Buttons]; t.renderTool ¬ G3dTool.MakeTool[ name: Rope.Concat["3dTube ", IF t.tube # NIL THEN t.tube.name ELSE NIL], extraButtons: LIST[ Controls.ClickButton["Tube Ops", TubeOps, t], Controls.ClickButton["IO Ops", IOOps, t], Controls.ClickButton["Draw Ops", DrawOps, t]], extraControls: LIST[ t.hold.x, t.hold.y, t.hold.z, t.hold.pitch, t.hold.yaw, -- t.hold.roll, -- t.hold.mag, t.tw0, t.tw1, t.r0, t.r1, t.tens0, t.tens1, t.lim, t.epsilon, t.taper, t.size, t.cres], controlSizes: [20, 200, 55, 20, 55, 150, 150], client: [data: t, mouse: MouseProc, draw: DrawProc, destroy: DestroyProc], ops: [FALSE, FALSE, FALSE, FALSE, FALSE, FALSE], useArcBalls: FALSE, arcBallSize: 80, icon: icon]; }; ClearPendings: PROC [pendings: Pendings] ~ { pendings.epsilon ¬ pendings.size ¬ pendings.r ¬ pendings.tw0 ¬ pendings.tw1 ¬ pendings.cres ¬ pendings.holdPosition ¬ pendings.holdTangent ¬ pendings.shape ¬ pendings.skin ¬ FALSE; }; TubeOps: ClickProc ~ { t: Tool ¬ NARROW[clientData]; SELECT Controls.PopUpRequest[["Tube Ops"], LIST[ -- 1 -- ["New Tube", "reset the current tube to the default tube"], -- 2 -- ["Select All", "select the entire tube"], -- 3 -- ["Branch", "add a branch to the current tube at the selected point"], -- 4 -- ["Delete", "delete the current selected branch"], -- 5 -- Controls.BoolRequest[t.round, "Round ends"], -- 6 -- ["Wt Rad", "select different radius weighting scheme"], -- 7 -- ["Radius Mode", "cycle through radius mode"], -- 8 -- Controls.BoolRequest[t.xformIO, "Xform points"]]] FROM 1 => ReplaceTube[t, ResetTube[]]; 2 => G3dTube.SelectAll[t.tube]; 3 => Branch[t]; 4 => Delete[t]; 5 => { t.round ¬ NOT t.round; t.pendings.shape ¬ TRUE; }; 6 => WeightRadii[t]; 7 => { t.radiusMode ¬ SELECT Controls.MultiRequest["Radius mode is ", LIST[ ["Linear", t.radiusMode = linear], ["Square", t.radiusMode = square]]] FROM 1 => linear, 2 => square, ENDCASE => linear; }; 8 => t.xformIO ¬ NOT t.xformIO; ENDCASE; IF Controls.GetPopUpButton[] = right THEN CheckPendingAndRePaint[t]; }; DrawProc: G3dTool.DrawProc ~ { t: Tool ~ NARROW[clientData]; IF NOT t.noDraw THEN { IF whatChanged # $Camera THEN Draw2d.Clear[context]; IF t.details[spline] AND t.details[ends] THEN G3dTube.DrawSplineEnds[context, t.tube, view]; IF t.details[enabled] THEN G3dTube.DrawTube[context, t.tube, t.details, view, vp] ELSE IF t.details[spline] THEN G3dTube.DrawSplines[context, t.tube, view, vp]; IF t.details[pick] AND t.pick # NIL AND t.pick.tube # NIL THEN { IF NOT t.neverMoused THEN { pairClip: G3dView.PairClip ~ G3dView.TransformAndClipInZ[t.pick.pos, view]; Imager.SetStrokeWidth[context, 1.0]; G3dDraw.Vector[context, t.pick.pos, t.pick.tan, view, vp]; IF NOT pairClip.clipped THEN { Draw2d.Circle[context, pairClip.pair, 3.0, TRUE]; IF NOT t.pick.t IN (0.0..1.0) THEN Draw2d.Mark[context, pairClip.pair, asterisk]; }; }; }; }; }; DrawOps: ClickProc ~ { t: Tool ¬ NARROW[clientData]; SELECT Controls.PopUpRequest[["LF Ops"], LIST[ -- 1 -- Controls.BoolRequest[t.vectors, "Vectors"], -- 2 -- Controls.BoolRequest[t.details[skeleton], "Skeleton"], -- 3 -- Controls.BoolRequest[t.details[spline], "Splines"], -- 4 -- Controls.BoolRequest[t.details[ends], "Ends"], -- 5 -- Controls.BoolRequest[t.details[pick], "Pick"], -- 6 -- Controls.BoolRequest[t.details[label], "Label"], -- 7 -- Controls.BoolRequest[t.details[autoSimplify], "Auto"], -- 8 -- Controls.BoolRequest[t.details[contours], "Contours"], -- 9 -- Controls.BoolRequest[t.details[lines], "Lines"], -- 10 -- Controls.BoolRequest[t.details[xSections], "XSections"], -- 11 -- Controls.BoolRequest[t.details[points], "Points"], -- 12 -- Controls.BoolRequest[t.details[normals], "Normals"], -- 13 -- Controls.BoolRequest[t.details[curvature], "Curvature"], -- 14 -- Controls.BoolRequest[t.details[velocity], "Velocity"], -- 15 -- Controls.BoolRequest[t.details[acceleration], "Acceleration"]]] FROM 1 => t.vectors ¬ NOT t.vectors; 2 => TogDetail[t, skeleton]; 3 => TogDetail[t, spline]; 4 => TogDetail[t, ends]; 5 => TogDetail[t, pick]; 6 => TogDetail[t, label]; 7 => TogDetail[t, autoSimplify]; 8 => TogDetail[t, contours]; 9 => TogDetail[t, lines]; 10 => TogDetail[t, xSections]; 11 => TogDetail[t, points]; 12 => TogDetail[t, normals]; 13 => TogDetail[t, curvature]; 14 => TogDetail[t, velocity]; 15 => TogDetail[t, acceleration]; ENDCASE; IF Controls.GetPopUpButton[] = right THEN CheckPendingAndRePaint[t, TRUE]; }; TogDetail: PROC [t: Tool, detailType: DetailType] ~ { button: Controls.MouseButton ¬ Controls.GetPopUpButton[]; m: Mouse ~ [[0, 0], up, button]; wasShape: BOOL ~ t.details[shape]; wasSkin: BOOL ~ t.details[skin]; t.details[detailType] ¬ NOT t.details[detailType]; t.details[shape] ¬ t.details[xSections] OR t.details[curvature] OR t.details[velocity] OR t.details[acceleration]; t.details[skin] ¬ t.details[contours] OR t.details[lines] OR t.details[normals]; Moused[t, m, $Buttons]; IF wasShape # t.details[shape] THEN t.pendings.shape ¬ t.details[shape]; IF wasSkin # t.details[skin] THEN t.pendings.skin ¬ t.details[skin]; IF detailType # autoSimplify AND button = right THEN CheckPendingAndRePaint[t, TRUE]; }; Moused: PROC [ t: Tool, mouse: Mouse, whatChanged: REF ANY, msg: ROPE ¬ NIL] ~ { c: Control ¬ SELECT whatChanged FROM t.hold.pitch, t.hold.yaw, t.hold.roll, t.hold.mag, t.hold.x, t.hold.y, t.hold.z, t.epsilon, t.taper, t.size, t.r0, t.r1, t.tw0, t.tw1, t.cres => NARROW[whatChanged], ENDCASE => NIL; t.mousePrev ¬ t.mouse; t.mouse ¬ mouse; t.action ¬ SELECT whatChanged FROM $Buttons => button, $Select => select, t.hold.pitch, t.hold.yaw, t.hold.roll, t.hold.mag, t.hold.x, t.hold.y, t.hold.z => spline, t.epsilon, t.taper => spline, $Pull, t.size, t.r0, t.r1, t.tw0, t.tw1, t.cres => shape, ENDCASE => none; t.details[enabled] ¬ NOT t.details[autoSimplify] OR t.action = shape OR t.mouse.state = up; IF NOT t.noDraw THEN SELECT whatChanged FROM t.size => t.pendings.size ¬ TRUE; t.r0, t.r1 => t.pendings.r ¬ TRUE; t.tw0 => t.pendings.tw0 ¬ TRUE; t.tw1 => t.pendings.tw1 ¬ TRUE; t.tens0, t.tens1 => t.pendings.tens ¬ TRUE; t.cres => t.pendings.cres ¬ TRUE; t.epsilon => t.pendings.epsilon ¬ TRUE; t.taper => t.pendings.taper ¬ TRUE; $Pull, t.hold.x, t.hold.y, t.hold.z => t.pendings.holdPosition ¬ TRUE; t.hold.pitch, t.hold.yaw, t.hold.mag => t.pendings.holdTangent ¬ TRUE; t.hold.roll => t.pendings.holdRoll ¬ TRUE; ENDCASE; IF t.action = spline AND t.pick.dividePending THEN DivideSpline[t]; IF t.renderTool # NIL THEN { IF msg = NIL THEN Controls.TypescriptClear[t.renderTool.typescript] ELSE Controls.TypescriptWrite[t.renderTool.typescript, msg]; }; }; DivideSpline: PROC [t: Tool] ~ { DividePick[t.pick]; Controls.SetSliderDialValue[t.hold.mag, G3dVector.Length[t.pick.tube.v1]]; }; CheckPendingAndRePaint: PROC [t: Tool, forceReDraw: BOOL ¬ FALSE] ~ { IF t.pick.tube = NIL THEN t.pendings.holdPosition ¬ t.pendings.holdTangent ¬ t.pendings.holdRoll ¬ FALSE; t.pendings.shape ¬ t.pendings.shape OR t.pendings.holdPosition OR t.pendings.holdTangent OR t.pendings.holdRoll; t.pendings.skin ¬ t.pendings.skin OR t.pendings.shape OR t.pendings.r OR t.pendings.tens OR t.pendings.tw0 OR t.pendings.tw1 OR t.pendings.taper OR t.pendings.epsilon OR t.pendings.cres; IF t.pendings.holdPosition THEN ChangePosition[t.pick, [t.hold.x.value, t.hold.y.value, t.hold.z.value]]; IF t.pendings.holdTangent THEN ChangeTangent[t.pick, t.hold]; IF t.pendings.holdRoll THEN RollSubTree[t.pick, Controls.GetSliderDialDeltaValue[t.hold.roll]]; IF t.pendings.size OR t.pendings.taper THEN G3dTube.ReScale[t.tube, t.size.value, t.taper.value]; IF t.pendings.epsilon THEN G3dTube.PropagateEpsilon[t.tube, t.epsilon.value]; IF t.pendings.cres THEN G3dTube.PropagateCircleRes[t.tube, Real.Round[t.cres.value]]; IF t.pendings.tens THEN ChangeTension[t.pick, t.tens0.value, t.tens1.value]; IF t.pendings.shape OR t.pendings.skin THEN G3dTube.InvalidateTubeAttributes[t.tube]; IF t.details[skin] OR t.details[shape] THEN { IF t.pendings.r THEN ChangeRadii[t.pick, t.r0.value, t.r1.value, t.size.value, t.epsilon.value]; IF t.pendings.tw0 THEN { ChangeTw0[t.pick, t.tw0.value, t.tw0.valuePrev]; IF t.pick.selected1 # NIL THEN Controls.SetSliderDialValue[t.tw1, t.pick.selected1.tw1]; }; IF t.pendings.tw1 THEN ChangeTw1[t.pick, t.tw1.value, t.tw1.valuePrev]; IF t.pendings.shape OR t.pendings.skin THEN MakeDetails[t]; }; IF forceReDraw THEN t.noDraw ¬ FALSE; IF t.vectors THEN G3dTool.Repaint[t.renderTool, t]; ClearPendings[t.pendings]; }; TubeControl: Controls.ControlProc ~ { t: Tool ~ NARROW[control.clientData]; Moused[t, control.mouse, control]; IF NOT t.noDraw AND (control.mouse.button = right OR control.whatChanged = $TypedIn) THEN CheckPendingAndRePaint[t]; }; MouseProc: Controls.MouseProc ~ { t: Tool ~ NARROW[clientData]; whatChanged: REF ANY ¬ $Select; view: Matrix ¬ GetView[viewer, t]; Moused[t, mouse, whatChanged]; t.neverMoused ¬ FALSE; IF mouse.state = up THEN RETURN; IF -- mouse.state = down OR -- NOT mouse.controlKey THEN { ScreenPick[t, mouse, view, t.pick]; G3dControl.SetHold[t.hold, t.pick.pos, t.pick.tan]; SetPick[t]; }; IF mouse.controlKey THEN { p: Pair ¬ [mouse.pos.x, mouse.pos.y]; plane: Quad ¬ [t.pick.plane.x, t.pick.plane.y, t.pick.plane.z, t.pick.plane.w]; q: Triple ¬ G3dMatrix.TripleFromScreenAndPlane[p, plane, view]; Controls.SetSliderDialValue[t.hold.x, q.x]; Controls.SetSliderDialValue[t.hold.y, q.y]; Controls.SetSliderDialValue[t.hold.z, q.z]; whatChanged ¬ t.hold.x -- $Pull -- ; } ELSE IF t.mouse.button = left THEN { point: Triple ¬ G3dSpline.Position[t.pick.tube.spline, t.pick.t]; message: ROPE ¬ IO.PutFLR["%g%g (%6.3f, %6.3f, %6.3f)", LIST[ IO.rope[IF t.tube.name # NIL THEN t.tube.name ELSE "Tube"], IO.rope[G3dTube.Where[t.pick.tube, t.tube]], IO.real[point.x], IO.real[point.y], IO.real[point.z]]]; MessageWindow.Append[message, TRUE]; }; CheckPendingAndRePaint[t]; }; SetPick: PROC [t: Tool] ~ { IF t.pick.selected0 # NIL THEN { Controls.SetSliderDialValue[t.r0, t.pick.selected0.r0]; Controls.SetSliderDialValue[t.tw0, t.pick.selected0.tw0]; Controls.SetSliderDialValue[t.tens0, t.pick.selected0.tens0]; }; IF t.pick.selected1 # NIL THEN { Controls.SetSliderDialValue[t.r1, t.pick.selected1.r1]; Controls.SetSliderDialValue[t.tw1, t.pick.selected1.tw1]; Controls.SetSliderDialValue[t.tens1, t.pick.selected1.tens1]; }; }; MakeDetails: PROC [t: Tool] ~ { G3dTube.Make[t.tube, t.details[skin], t.round, NIL, t.epsilon.value]; }; ResetTube: PROC RETURNS [tube: Tube] ~ { tube ¬ NEW[TubeRep ¬ [p0: [-0.5,0.,0.], p1: [0.5,0.,0.], v0: [1.,0.,1.], v1: [1.,0.,1.]]]; G3dTube.SetSpline[tube]; tube.name ¬ "Tube"; }; RenameViewer: PROC [t: Tool] ~ { IF Rope.Equal[t.tube.name, "Tube", FALSE] THEN RETURN; t.renderTool.outer.name ¬ Rope.Concat["3dTube ", t.tube.name]; t.renderTool.outer.label ¬ t.tube.name; ViewerOps.PaintViewer[t.renderTool.outer, caption]; }; ReplaceTube: PROC [t: Tool, tube: Tube] ~ { t.tube ¬ tube; IF tube.name = NIL THEN tube.name ¬ "Tube"; RenameViewer[t]; t.pendings ¬ NEW[PendingsRep ¬ [shape: TRUE]]; MakePick[t]; SetPick[t]; Moused[t, [[0, 0], up, left], $Buttons]; CheckPendingAndRePaint[t, TRUE]; }; Branch: PROC [t: Tool] ~ { IF t.pick.t = 0.0 AND t.pick.tube.prev = NIL THEN RETURN; IF t.pick.dividePending THEN DivideSpline[t]; G3dTube.AddBranch[t.pick.tube, G3dTube.NewBranch[t.pick.tube]]; CheckPendingAndRePaint[t, TRUE]; }; Delete: PROC [t: Tool] ~ { G3dTube.Delete[t.pick.selected0]; t.pick.selected0 ¬ t.pick.selected1 ¬ NIL; G3dTube.UnSelectAll[t.tube]; IF NOT G3dTube.Find[t.pick.tube, t.tube] THEN t.pick.tube ¬ NIL; CheckPendingAndRePaint[t, TRUE]; }; WeightRadii: PROC [t: Tool] ~ { G3dTube.SetDescendantRadii[t.tube, t.radiusMode]; t.pendings.skin ¬ TRUE; CheckPendingAndRePaint[t, TRUE]; }; FromControl: PROC [control: Control, t: REAL ¬ 0.0] RETURNS [contour: Contour] ~ { ips: G2dBasic.IntegerPairSequence ¬ Controls.GetContour[control]; IF ips # NIL THEN { offset: Pair ¬ [0.5*(control.w-1), 0.5*(control.h-1)]; scale: Pair ¬ [IF offset.x>0 THEN 1./offset.x ELSE 1., IF offset.y>0 THEN 1./offset.y ELSE 1.]; contour ¬ G2dContour.FromIntegerPairs[ips, Controls.IsContourClosed[control], t]; RETURN[G2dContour.Thin[G2dContour.Scale[G2dContour.Offset[contour, offset], scale]]]; }; }; GetContour: PROC [t: Tool] ~ { c: Control ~ Controls.LastControlMoused[]; IF c # NIL AND c.type = contour AND NOT c.viewer.destroyed THEN { G3dTube.AddContour[t.tube, G2dContour.Orient[FromControl[c, t.pick.t]]]; Moused[t, [[0, 0], up, left], $Buttons, "Contour received\n"]; t.pendings.skin ¬ TRUE; CheckPendingAndRePaint[t, TRUE]; } ELSE Controls.TypescriptWrite[t.renderTool.typescript, "1st middle-click a contour viewer\n"]; }; GetTube: PROC [t: Tool] ~ { tube: Tube ~ G3dTube.LastTubePicked[FALSE]; IF tube # NIL THEN ReplaceTube[t, tube] ELSE Controls.TypescriptWrite[t.renderTool.typescript, "1st click tube viewer\n"]; }; DestroyProc: Controls.DestroyProc ~ { t: Tool ~ NARROW[clientData]; MessageWindow.Clear[]; IF G3dTube.LastTubePicked[FALSE] = t.tube THEN G3dTube.SetLastTubePicked[NIL]; }; IOOps: ClickProc ~ { t: Tool ¬ NARROW[clientData]; SELECT Controls.PopUpRequest[["Misc Ops"], LIST[ -- 1 -- ["Read Tube", "read a tube from a file"], -- 2 -- ["Get Tube", "get a tube from another viewer"], -- 3 -- ["Get Contour", "get a contour from a viewer"], -- 4 -- ["Write Tube", "write a tube to a file"], -- 5 -- ["Write Curve", "write a curve to a file"], -- 6 -- ["Write Interpress", "write an Interpress file of the current image"], -- 7 -- ["Shape-Out", "write a shape file consisting of points and polygons"]]] FROM 1 => ReadTube[t]; 2 => GetTube[t]; 3 => GetContour[t]; 4 => WriteTube[t]; 5 => WriteCurve[t]; 6 => WriteInterpress[t]; 7 => WritePointsPolys[t]; ENDCASE; }; GetView: PROC [v: Controls.Viewer ¬ NIL, t: Tool] RETURNS [m: Matrix] ~ { m ¬ G3dTool.GetViewTransform[v, t.renderTool]; }; WriteTube: PROC [t: Tool] ~ { fileName: ROPE ¬ Controls.TypescriptReadFileName[t.renderTool.typescript]; IF fileName # NIL THEN { m: Matrix ~ IF t.xformIO THEN GetView[, t] ELSE NIL; t.tube.name ¬ G3dTube.ExtractTubeName[fileName]; G3dTube.TubeToFile[t.tube, fileName, m]; RenameViewer[t]; }; }; WritePointsPolys: PROC [t: Tool] ~ { nPoints, nPolys: INT; m: Matrix ~ IF t.xformIO THEN GetView[, t] ELSE NIL; fileName: ROPE ¬ Controls.TypescriptReadFileName[t.renderTool.typescript]; IF fileName = NIL THEN RETURN; [nPoints, nPolys] ¬ G3dTube.WritePointsPolys[t.tube, fileName, m]; Controls.TypescriptWrite[t.renderTool.typescript, IO.PutFR["%g points and %g polys written\n", IO.int[nPoints], IO.int[nPolys]]]; }; WriteInterpress: PROC [t: Tool] ~ { fileName: ROPE ¬ Controls.TypescriptReadFileName[t.renderTool.typescript]; IF fileName # NIL THEN { ref: ImagerInterpress.Ref ¬ ImagerInterpress.Create[fileName]; G3dTube.WriteIP[ref, t.tube, t.details, G3dTool.GetViewTransform[, t.renderTool]]; ImagerInterpress.Close[ref]; }; }; WriteCurve: PROC [t: Tool] ~ { fileName: ROPE ¬ Controls.TypescriptReadFileName[t.renderTool.typescript]; IF fileName # NIL THEN { out: STREAM ¬ FS.StreamOpen[fileName, $create]; c: G3dCurve.Curve ¬ G3dTube.CurveFromTube[t.tube]; name: ROPE ¬ IF t.tube.name # NIL THEN Rope.Concat["from ", t.tube.name] ELSE NIL; IF t.xformIO THEN G3dCurve.Transform[c, GetView[, t]]; G3dCurve.Write[out, c, name]; }; }; ReadTube: PROC [tool: Tool] ~ { name: ROPE ¬ Controls.TypescriptReadFileName[tool.renderTool.typescript]; tool.tube ¬ G3dTube.TubeFromFile[name]; IF tool.tube # NIL THEN ReplaceTube[tool, tool.tube] ELSE Blink["Couldn't read tube"]; }; TubeFromFile: PROC [t: Tool, name: ROPE] RETURNS [error: ROPE] ~ { r: ROPE ¬ FileNames.ResolveRelativePath[name]; stream: STREAM ¬ FS.StreamOpen[FS.ExpandName[r].fullFName ! FS.Error => GOTO noOpen]; t.tube ¬ G3dTube.ReadTube[stream]; IO.Close[stream]; IF t.tube = NIL THEN RETURN["No tube in file."]; t.tube.name ¬ G3dTube.ExtractTubeName[r]; EXITS noOpen => error ¬ IO.PutFR1["Couldn't open %g.\n", IO.rope[name]]; }; WriteTubeInfo: PROC [tube: Tube, o: OuterData] ~ { nPoints, nPolys, minCres, maxCres: INTEGER; [nPoints, nPolys, minCres, maxCres] ¬ G3dTube.Info[tube]; Controls.TypescriptWrite[o.typescript, IO.PutFLR["%g points, %g polygons, cres: %g-%g\n", LIST[IO.int[nPoints], IO.int[nPolys], IO.int[minCres], IO.int[maxCres]]]]; }; MakePick: PROC [t: Tool] ~ { t.pick ¬ NEW[PickRep ¬ [tube: t.tube]]; t.pendings.holdPosition ¬ t.pendings.holdTangent ¬ t.pendings.holdRoll ¬ FALSE; SetPoint[t]; IF t.hold.x.viewer # NIL THEN G3dControl.SetHold[t.hold, t.pick.pos, t.pick.tan]; }; ScreenPick: PROC [tool: Tool, mouse: Mouse, view: Matrix, pick: Pick] ~ { IF pick = NIL THEN RETURN; pick.mouse ¬ mouse; [[pick.tube, pick.t]] ¬ G3dTube.NearestTube2d[tool.tube, [mouse.pos.x, mouse.pos.y], mouse. state= down, view]; SELECT TRUE FROM mouse.button = right => { pick.type ¬ subTree; SetSubTree[tool]; }; mouse.shiftKey => { pick.type ¬ segment; SetSelection[tool]; }; ENDCASE => { pick.type ¬ point; SetPoint[tool]; }; }; UpdatePick: PROC [t: Tool] ~ { pick: Pick ¬ t.pick; IF pick.tube = NIL THEN RETURN; G3dTube.SetLastTubePicked[pick.tube]; pick.dividePending ¬ pick.t IN (0.0..1.0); pick.tan ¬ G3dSpline.Tangent[pick.tube.spline, pick.t]; pick.pos ¬ --pick.origPos ¬-- G3dSpline.Position[pick.tube.spline, pick.t]; IF t.renderTool # NIL THEN { nrm: Quad ¬ G3dMatrix.TransformH[[0,0,1], GetView[, t]]; pick.plane ¬ G3dPlane.FromPointAndNormal[t.pick.pos, [nrm.x, nrm.y, nrm.z], TRUE]; }; }; SetPoint: PROC [tool: Tool] ~ { IF tool.pick.tube = NIL THEN RETURN; IF tool.pick.t = 0.0 AND tool.pick.tube.prev # NIL THEN { tool.pick.t ¬ 1.0; tool.pick.tube ¬ tool.pick.tube.prev; }; IF tool.pick.selected0 # NIL THEN tool.pick.selected0.selected ¬ FALSE; IF tool.pick.selected1 # NIL THEN tool.pick.selected1.selected ¬ FALSE; tool.pick.selected0 ¬ tool.pick.selected1 ¬ NIL; UpdatePick[tool]; }; SetSelection: PROC [tool: Tool] ~ { pick: Pick ¬ tool.pick; pick.selected1 ¬ pick.tube; IF pick.mouse.button = middle OR pick.selected0 = NIL THEN pick.selected0 ¬ pick.selected1; pick.t ¬ IF pick.t > 0.5 THEN 1.0 ELSE 0.0; FOR t: Tube ¬ pick.selected0, t.next WHILE t # NIL DO -- ensure selected0 before selected1 IF t = pick.selected1 THEN EXIT; REPEAT FINISHED => { temp: Tube ¬ pick.selected0; pick.selected0 ¬ pick.selected1; pick.selected1 ¬ temp; }; ENDLOOP; G3dTube.UnSelectAll[G3dTube.First[pick.tube]]; -- set selected splines FOR t: Tube ¬ pick.selected0, t.next WHILE t # NIL AND t # pick.selected1.next DO t.selected ¬ TRUE; ENDLOOP; UpdatePick[tool]; }; SetSubTree: PROC [tool: Tool] ~ { tool.pick.selected0 ¬ tool.pick.selected1 ¬ tool.pick.tube; tool.pick.t ¬ IF tool.pick.t > 0.5 THEN 1.0 ELSE 0.0; G3dTube.UnSelectAll[G3dTube.First[tool.pick.tube]]; G3dTube.SelectAll[tool.pick.tube]; UpdatePick[tool]; }; DividePick: PROC [pick: Pick] ~ { IF pick.tube # NIL AND pick.dividePending AND pick.t IN (0.0..1.0) THEN { tubeProc: TubeProc ~ {tube.prev ¬ newTube}; newTube: Tube ¬ NEW[TubeRep ¬ [ spline: NEW[SplineRep], xSpline: NEW[SplineRep], prev: pick.tube, selected: pick.tube.selected, next: pick.tube.next, branches: pick.tube.branches, p1: pick.tube.p1, r1: pick.tube.r1, tw1: pick.tube.tw1]]; G3dTube.DivideContours[pick.tube, pick.tube, newTube, pick.t]; pick.tube.next ¬ newTube; pick.tube.branches ¬ NIL; G3dTube.ApplyToBranches[newTube, tubeProc, FALSE]; pick.tube.p1 ¬ newTube.p0 ¬ pick.pos --pick.origPos--; pick.tube.r1 ¬ newTube.r0 ¬ 0.5*(pick.tube.r0+newTube.r1); pick.tube.tw1 ¬ newTube.tw0 ¬ 0.5*(pick.tube.tw0+newTube.tw1); [] ¬ G3dSpline.Reparameterize[pick.tube.spline, pick.t, 1.0, newTube.spline]; [] ¬ G3dSpline.Reparameterize[pick.tube.spline, 0.0, pick.t, pick.tube.spline]; pick.tube.v0 ¬ G3dSpline.Tangent[pick.tube.spline, 0.0]; pick.tube.v1 ¬ G3dSpline.Tangent[pick.tube.spline, 1.0]; newTube.v0 ¬ G3dSpline.Tangent[newTube.spline, 0.0]; newTube.v1 ¬ G3dSpline.Tangent[newTube.spline, 1.0]; pick.t ¬ 1.0; pick.tan ¬ pick.tube.v1; }; pick.dividePending ¬ FALSE; }; NewSpline: PROC [pick: Pick] ~ { tubeProc: TubeProc ~ {G3dTube.SetSpline[tube]}; [] ¬ tubeProc[pick.tube]; G3dTube.ApplyToBranches[pick.tube, tubeProc]; }; RollSubTree: PROC [pick: Pick, rollAngle: REAL] ~ { IF G3dTube.NBranches[pick.tube] = 0 THEN RETURN; IF pick.t = 0.0 THEN G3dTube.Rotate[pick.tube, [pick.tube.p0, pick.tube.v0], rollAngle, TRUE, TRUE] ELSE { v: Triple ¬ IF pick.tube.next#NIL THEN pick.tube.next.v0 ELSE pick.tube.branches[0].v0; G3dTube.Rotate[pick.tube, [pick.tube.p1, v], rollAngle, TRUE, FALSE]; }; NewSpline[pick]; }; ChangeTangent: PROC [pick: Pick, hold: Hold] ~ { IF pick.tube # NIL THEN { tangent: Triple ¬ G3dControl.VectorFromHold[hold]; SELECT pick.type FROM point, segment => { IF pick.t = 0.0 THEN pick.tube.v0 ¬ tangent ELSE { tubeProc: TubeProc ~ { IF tube # NIL THEN tube.v0 ¬ G3dVector.Mul[tangent, G3dVector.Length[tube.v0]*iV1]; }; iV1: REAL ~ IF G3dVector.Null[pick.tube.v1] THEN 0.0 ELSE 1.0/G3dVector.Length[pick.tube.v1]; pick.tube.v1 ¬ tangent; IF iV1 # 0.0 THEN G3dTube.ApplyToBranches[pick.tube, tubeProc]; }; }; subTree => { axis: Triple ¬ G3dVector.Cross[tangent, pick.tan]; base: Triple ¬ IF pick.t = 0.0 THEN pick.tube.p0 ELSE pick.tube.p1; angle: REAL ¬ G3dVector.AngleBetween[tangent, pick.tan, , TRUE]; G3dTube.Rotate[pick.tube, [base, axis], angle, TRUE, pick.t = 0.0]; }; ENDCASE; pick.tan ¬ tangent; NewSpline[pick]; }; }; ChangePosition: PROC [pick: Pick, --change-- position: Triple] ~ { pick.pos ¬ position; IF pick.tube = NIL THEN RETURN; IF pick.t = 0.0 THEN pick.tube.p0 ¬ pick.pos ELSE { pick.tube.p1 ¬ pick.pos; FOR n: NAT IN [0..G3dTube.NBranches[pick.tube]) DO pick.tube.branches[n].p0 ¬ pick.pos; ENDLOOP; IF pick.tube.next # NIL THEN pick.tube.next.p0 ¬ pick.pos; }; NewSpline[pick]; }; ChangeRadii: PROC [pick: Pick, r0, r1, scale, epsilon: REAL] ~ { numberSelected: INTEGER ~ NumberSelected[pick]; IF numberSelected > 0 THEN { r: REAL ¬ r0; dr: REAL ~ (r1-r)/REAL[numberSelected]; IF pick.selected0.prev # NIL THEN pick.selected0.prev.r1 ¬ r; FOR t: Tube ¬ pick.selected0, t.next WHILE t # NIL AND t # pick.selected1.next DO t.r0 ¬ r; r ¬ r+dr; t.r1 ¬ r; ENDLOOP; IF pick.selected1.next # NIL THEN pick.selected1.next.r0 ¬ r; RemakeSelected[pick, scale, epsilon]; }; }; ChangeTension: PROC [pick: Pick, tens0, tens1: REAL] ~ { numberSelected: INTEGER ~ NumberSelected[pick]; IF numberSelected > 0 THEN { tens: REAL ¬ tens0; dtens: REAL ~ (tens1-tens0)/REAL[numberSelected]; FOR t: Tube ¬ pick.selected0, t.next WHILE t # NIL AND t # pick.selected1.next DO t.tens0 ¬ tens; tens ¬ tens+dtens; t.tens1 ¬ tens; G3dTube.SetSpline[t]; ENDLOOP; }; }; ChangeTw0: PROC [pick: Pick, tw0, tw0Prev: REAL] ~ { nBefore: INTEGER ~ BeforeSelected[pick]; change: REAL ~ tw0-tw0Prev; IF nBefore > 0 THEN { dchange: REAL ¬ 0.0; ddchange: REAL ¬ change/REAL[nBefore]; FOR t: Tube ¬ G3dTube.First[t], t.next WHILE t # pick.selected0 DO t.tw0 ¬ t.tw0+dchange; dchange ¬ dchange+ddchange; t.tw1 ¬ t.tw1+dchange; ENDLOOP; }; FOR t: Tube ¬ pick.selected0, t.next WHILE t # NIL DO t.tw0 ¬ t.tw0+change; t.tw1 ¬ t.tw1+change; ENDLOOP; }; ChangeTw1: PROC [pick: Pick, tw1, tw1Prev: REAL] ~ { nSelected: INTEGER ~ NumberSelected[pick]; change: REAL ~ tw1-tw1Prev; IF nSelected > 0 THEN { dchange: REAL ¬ 0.0; ddchange: REAL ¬ change/REAL[nSelected]; FOR t: Tube ¬ pick.selected0, t.next WHILE t # pick.selected1.next DO t.tw0 ¬ t.tw0+dchange; dchange ¬ dchange+ddchange; t.tw1 ¬ t.tw1+dchange; ENDLOOP; }; IF pick.selected1 # NIL THEN FOR t: Tube ¬ pick.selected1.next, t.next WHILE t # NIL DO t.tw0 ¬ t.tw0+change; t.tw1 ¬ t.tw1+change; ENDLOOP; }; NumberSelected: PROC [pick: Pick] RETURNS [INTEGER] ~ { n: INTEGER ¬ 0; IF pick.selected0 = NIL OR pick.selected1 = NIL THEN RETURN[0]; FOR t: Tube ¬ pick.selected0, t.next WHILE t # NIL AND t # pick.selected1.next DO n ¬ n+1; ENDLOOP; RETURN[n]; }; BeforeSelected: PROC [pick: Pick] RETURNS [INTEGER] ~ { n: INTEGER ¬ 0; FOR t: Tube ¬ pick.selected0.prev, t.prev WHILE t # NIL DO n ¬ n+1; ENDLOOP; RETURN[n]; }; AfterSelected: PROC [pick: Pick] RETURNS [INTEGER] ~ { n: INTEGER ¬ 0; FOR t: Tube ¬ pick.selected1.next, t.next WHILE t # NIL DO n ¬ n+1; ENDLOOP; RETURN[n]; }; RemakeSelected: PROC [pick: Pick, scale, epsilon: REAL] ~ { t0: Tube ¬ IF pick.selected0.prev # NIL THEN pick.selected0.prev ELSE pick.selected0; t1: Tube ¬ IF pick.selected1.next # NIL THEN pick.selected1.next ELSE pick.selected1; FOR t: Tube ¬ t0, t.next WHILE t # t1.next AND t # NIL DO t.epsilon ¬ epsilon; t.scale ¬ scale; G3dTube.MakeSectionXSections[t]; ENDLOOP; }; Blink: PROC [rope: ROPE] ~ { MessageWindow.Append[rope, TRUE]; MessageWindow.Blink[]; }; icon: Icons.IconFlavor ¬ Icons.NewIconFromFile["G3dUser.icons", 5]; designUsage: ROPE ~ "tube : Model tubular structures."; G3dTool.Register["Tube", TubeDesignCmd, designUsage]; END. ~ G3dTubeCmdImpl.mesa Copyright Σ 1985, 1992 by Xerox Corporation. All rights reserved. Bloomenthal, October 20, 1992 4:33 pm PDT Types Controls: Viewing: State: Geometry: Initialization Tube Ops Drawing User Interaction t.noDraw _ t.mouse.state = up AND (c = NIL OR c.whatChanged # $TypedIn) AND t.action # button AND t.action # select AND NOT t.details[autoSimplify]; G3dTool.Repaint[t.renderTool, t]; MouseProc: Controls.MouseProc ~ { t: Tool ~ NARROW[clientData]; t.neverMoused _ FALSE; Moused[t, mouse, $Select]; IF t.mouse.state = up THEN RETURN; ScreenPick[t.tube, t.mouse, t.renderTool.view, t.pick]; G3dControl.SetHold[t.hold, t.pick.pos, t.pick.tan]; IF t.mouse.button = left THEN { IF t.mouse.state = down THEN { point: Triple _ G3dSpline.Position[t.pick.tube.spline, t.pick.t]; message: ROPE _ IO.PutFR["%g%g (%6.3f, %6.3f, %6.3f)", IO.rope[IF t.tube.name # NIL THEN t.tube.name ELSE "Tube"], IO.rope[G3dTube.Where[t.pick.tube, t.tube]], IO.real[point.x], IO.real[point.y], IO.real[point.z]]; MessageWindow.Append[message, TRUE]; }; }; SetPick[t]; G3dTool.Repaint[t.renderTool, clientData]; }; adjust controls to selected splines settings: Tube Modification IO Pick Procedures Compute the plane perpendicular to the view direction and passing through pick.pos: Find the 3d vector that presently points along z-axis after the view transform. Thus, transform the z-axis by the inverse of the view transformation. Note, the view transformation being considered includes the viewport. To transform a vector, premultiply by the matrix's inverse (i.e., inverse of the inverse). pick.tube.v1 _ newTube.v0 _ [ 0.25*(3.0*(newTube.p1.x-pick.tube.p0.x)-pick.tube.v0.x-newTube.v1.x), 0.25*(3.0*(newTube.p1.y-pick.tube.p0.y)-pick.tube.v0.y-newTube.v1.y), 0.25*(3.0*(newTube.p1.z-pick.tube.p0.z)-pick.tube.v0.z-newTube.v1.z)]; If T = new vector, V0 = beginning vector of outgoing tube, and V1 = end of incoming tube, then T|V0|/|T| is a vector in direction T with length of V0. We would like V0 length to increase, however, by |T|/|V1|. So, V0 should be assigned T|V0||T|/|T||V1| = T|V0|/|V1|. pick.pos _ G3dVector.Add[pick.origPos, change]; Miscellaneous Start Code Κλ•NewlineDelimiter –"cedarcode" style™™Jšœ Οeœ6™BJ™)J˜JšΟk œ˜ŠJ˜—šΠblœžœž˜JšžœΪ˜αJ˜—Jšœž˜headšΟl™Jšœ žœ˜'Jšœ žœ˜$Jšœžœ˜+Jšœ žœ˜ Jšœ žœ˜'Jšœžœ˜Jšœ žœ˜&Jšœžœ˜Jšœ žœ˜"Jšœžœ˜Jšœ žœ˜!Jšœ žœ˜#Jšœ žœ˜!Jšœ žœ˜"Jšœ žœ˜(Jšžœžœžœžœ˜šžœžœžœ˜J˜—Jšœ žœ˜#šœ žœ˜(J˜—Jšœ žœ˜(Jšœ žœ˜Jšœ žœ˜$šœ žœ˜#J˜—Jšœ žœžœ ˜$šœžœžœ˜Jšœžœžœ˜Jšœ žœžœ˜Jšœžœžœ˜Jšœ žœžœ˜Jšœ žœžœ˜Jšœ žœžœ˜Jšœ žœžœ˜Jšœž œžœ˜Jšœžœžœ˜Jšœžœžœ˜Jšœžœžœ˜Jšœ žœžœ˜Jšœ žœž˜J˜J˜—Jšœžœžœ ˜šœ žœžœ˜J˜.J˜(Jšœžœ˜Jšœžœ˜Jšœžœ˜#Jšœžœžœ˜J˜*J˜J˜J˜—šœ žœ1˜CJ˜—Jšœž œžœ ˜šœžœžœ˜™ JšœžœΟc"˜EJšœžœ‘˜,Jšœžœ‘ ˜'Jšœžœ‘ ˜'Jšœžœ‘ ˜&Jšœžœ‘ ˜&Jšœžœ‘ ˜*Jšœžœ‘˜*Jšœžœ‘˜0Jšœžœ‘˜7Jšœžœ‘˜+Jšœžœ‘$˜?Jšœžœ‘˜+—™Jšœžœ˜ —™Jšœžœ˜Jšœžœ˜Jšœ žœžœ˜Jšœ žœžœ˜Jšœžœžœ˜J˜(J˜+Jšœžœ˜J˜Jšœ žœ˜#Jšœ žœžœ˜Jšœ žœžœ‘'˜D—™ Jšœ‘˜,Jšœžœ‘˜/Jšœžœ‘˜1—J˜——š ™šΟb œ˜(š!Οn œΟsž€œ€žœ€œ€œ€œ€žœ€œ€ž€œ€žœ€œ €ž€œ˜YJšžœ˜˜Jšœ!€œ €ž€œ€ž€œ€ž€œ˜]—Jšœ˜—Jšœžœ˜ Jšœ žœ ˜šžœž˜J˜Jš œžœ.žœžœžœ˜TJšžœžœ˜)—Jšœ žœ˜$Jšœ žœ˜J˜-J˜/J˜/J˜.J˜.J˜)J˜)J˜,Jšœ.žœ˜5Jšœ/žœ˜6Jšœ-žœ˜6Jšœ/žœ˜8Jšœ žœžœ˜.J˜ J˜ J˜(˜ Jš œžœ žœžœ žœžœ˜Hšœžœ˜Jšœ-˜-Jšœ)˜)Jšœ.˜.—šœžœ˜Jšœ8‘œ ˜VJ˜+Jšœ+˜+—J˜.JšœJ˜JJš œžœžœžœžœžœžœ˜0Jšœ žœ˜J˜Jšœ ˜ —J˜J˜—š£ œžœ˜,J˜MJ˜OJšœžœ˜J˜——š ™š£œ˜Jšœ žœ ˜šžœ%žœ˜0J˜CJ˜1J˜MJ˜9J˜4J˜?J˜5Jšœ9˜9—šž˜Jšœ!˜!J˜J˜J˜˜Jšœ žœ ˜Jšœžœ˜J˜—J˜šœ˜šœžœ*žœ˜DJšœ"˜"Jšœ#˜#Jšžœžœ ˜1J˜——Jšœžœ ˜Jšžœ˜—Jšžœ"žœ˜DJ˜——š ™š’œ˜Jšœ žœ ˜šžœžœ žœ˜Jšžœžœ˜4šžœžœž˜-Jšœ.˜.—šžœ˜Jšžœ7˜;Jšžœžœžœ0˜N—š žœžœ žœžœžœžœ˜@šžœžœžœ˜JšœK˜KJ˜$J˜:šžœžœžœ˜Jšœ+žœ˜1Jšžœžœ žœ žœ/˜QJ˜—J˜—J˜—J˜—J˜J˜—š£œ˜Jšœ žœ ˜šžœ#žœ˜.Jš‘œ,˜4Jš‘œ7˜?Jš‘œ4˜˜B—Jšžœžœ5˜LJšžœžœžœ*˜Ušžœžœžœ˜-šžœž˜J˜K—šžœžœ˜J˜0Jšžœžœžœ:˜XJ˜—Jšžœžœ1˜GJšžœžœžœ˜;J˜—Jšžœ žœ žœ˜%Jšžœ žœ"˜3J˜J˜J˜—šΠbn œ˜%Jšœ žœ˜%J˜"šžœžœ žœžœ ˜TJšžœ˜—J˜J˜—š’ œ˜!Jšœ žœ ˜Jšœ žœžœ ˜J˜"J˜Jšœžœ˜Jšžœžœžœ˜ š žœ‘Πcs‘œžœžœ˜:J˜#J˜3J˜ J˜—šžœ˜šžœ˜J˜%J˜OJ˜?J˜+J˜+J˜+J˜$J˜—šžœžœ˜%J˜Ašœ žœžœ&žœ˜=Jš žœžœžœžœ žœ ˜;Jšžœ*˜,Jšžœžœžœ˜7—Jšœžœ˜%J˜——Jšœ!™!Jšœ˜J˜J˜—š’ œ™!Jšœ žœ ™Jšœžœ™J™Jšžœžœžœ™"J™7J™3šžœžœ™šžœžœ™J™Ašœ žœžœ$™6Jš žœžœžœžœ žœ ™;Jšžœ*™,Jšžœžœžœ™6—Jšœžœ™%J™—J™—J™ Jšœ*™*J™J™—š£œžœ˜J™-šžœžœžœ˜ J˜7J˜9J˜=J˜—šžœžœžœ˜ J˜7J˜9J˜=J˜—J˜——š ™š£ œžœ˜Jšœ/žœ˜EJ˜J˜—š£ œž œ˜(JšœžœP˜ZJ˜J˜J˜J˜—š£ œžœ˜ Jšžœ!žœžœžœ˜6J˜>J˜'J˜3J˜J˜—š£ œžœ˜+J˜Jšžœ žœžœ˜+J˜Jšœ žœžœ˜.J˜ J˜ J˜(Jšœžœ˜ J˜J˜—š₯œžœ˜Jš žœžœžœžœžœ˜9Jšžœžœ˜-J˜?Jšœžœ˜ J˜J˜—š₯œžœ˜J˜!Jšœ&žœ˜*J˜Jšžœžœ#žœžœ˜@Jšœžœ˜ J˜J˜—š₯ œžœ˜J˜1Jšœžœ˜Jšœžœ˜ J˜J˜—š£ œžœžœžœ˜RJ˜Ašžœžœžœ˜J˜6Jš œžœ žœ žœžœ žœ žœ˜_J˜QJšžœO˜UJ˜—J˜J˜—š₯ œžœ˜J˜*š žœžœžœžœžœ˜:šžœ˜J˜HJ˜>Jšœžœ˜Jšœžœ˜ J˜—šž˜J˜Y——J˜J˜—š₯œžœ˜Jšœ$žœ˜+šžœž˜ Jšžœ˜JšžœN˜R—J˜J˜—š£ œ˜%Jšœ žœ ˜J˜Jšžœžœ žœžœ˜NJ˜——š ™š£œ˜Jšœ žœ ˜šžœ%žœ˜0J˜1J˜7J˜7J˜1J˜3J˜NJ˜O—šž˜J˜J˜J˜J˜J˜J˜Jšœ˜Jšžœ˜—J˜J˜—š£œžœžœ žœ˜IJ˜.J˜J˜—š£ œžœ˜Jšœ žœ<˜Jšžœ žœžœ˜Jš œ žœ žœžœžœ˜4J˜0J˜(J˜J˜—J˜J˜—š£œžœ˜$Jšœžœ˜Jš œ žœ žœžœžœ˜4Jšœ žœ<˜JJšžœ žœžœžœ˜J˜Bšœ1˜1Jšžœ*žœžœ˜O—J˜J˜—š£œžœ˜#Jšœ žœ<˜Jšžœ žœžœ˜J˜>JšœR˜RJ˜J˜—J˜J˜—š£ œžœ˜Jšœ žœ<˜Jšžœ žœžœ˜Jšœžœžœ˜/J˜2Jš œžœžœžœžœ#žœžœ˜RJšžœ žœ%˜6J˜J˜—J˜J˜—š₯œžœ˜Jšœžœ?˜IJ˜'Jšžœ žœžœ˜VJ˜J˜—š ₯ œžœžœžœ žœ˜BJšœžœ'˜.Jš œžœžœ žœžœ žœ ˜UJ˜"Jšžœ˜Jšžœ žœžœžœ˜0J˜)šž˜Jšœžœžœ ˜B—J˜J˜—š£ œžœ˜2Jšœ#žœ˜+J˜9šœ&žœ0˜YJš žœžœžœžœžœ˜J—J˜——š ™š£œžœ˜Jšœ žœ˜'JšœIžœ˜OJ˜ Jšžœžœžœ4˜QJ˜J˜—š₯ œžœ9˜IJšžœžœžœžœ˜J˜˜J˜W—šžœžœž˜˜J˜Jšœ˜J˜—˜J˜Jšœ˜J˜—šžœ˜ J˜Jšœ˜J˜——J˜J˜—š£ œžœ˜J˜Jšžœ žœžœžœ˜J˜%Jšœžœ ˜*J˜7Jšœ ‘œ‘œ.˜Kšžœžœžœ˜J™SJ™OJ™EJ™EJ™ZJ˜8JšœLžœ˜RJ˜—J˜J˜—š£œžœ˜Jšžœžœžœžœ˜$šžœžœžœžœ˜9J˜J˜%J˜—Jšžœžœžœ žœ˜GJšžœžœžœ žœ˜GJšœ,žœ˜0Jšœ˜J˜J˜—š£ œžœ˜#J˜J˜šžœžœž˜5Jšžœ!˜%—Jšœ žœžœžœ˜+š žœ"žœžœžœ‘$˜ZJšžœžœžœ˜ šž˜šžœ˜ J˜J˜ J˜J˜——Jšžœ˜—Jšœ/‘˜Hš žœ"žœžœžœž˜QJšœ žœ˜Jšžœ˜—Jšœ˜J˜J˜—š£ œžœ˜!J˜;Jšœžœžœžœ˜5J˜3J˜"J˜J˜J˜—š£ œžœ˜!š žœ žœžœžœžœ žœ˜IJš£œ#˜+šœžœ ˜Jšœžœ ˜Jšœ žœ ˜J˜J˜J˜J˜J˜J˜J˜—Jšœ>˜>J˜Jšœžœ˜Jšœ+žœ˜2J˜6J˜:J˜>J˜MJ˜OJ˜8J˜8J˜4J˜4™J™EJ™EJ™F—J˜ J˜J˜—Jšœžœ˜J˜J˜—š₯ œžœ˜ Jš£œ'˜/J˜J˜-J˜J˜—š£ œžœžœ˜3Jšžœ"žœžœ˜0šžœ ˜JšžœDž œ˜Sšžœ˜Jš œ žœžœžœžœ˜WJšœ8žœžœ˜EJ˜——J˜J˜J˜—š£ œžœ˜0šžœ žœžœ˜J˜2šžœ ž˜˜šžœ ˜Jšžœ˜šžœ˜J™GJ™NJ™:J™8š£œ˜šžœžœžœ ˜J˜6—J˜—Jš œžœžœžœžœ$˜]J˜Jšžœ žœ.˜?J˜——J˜—˜ J˜2Jšœžœžœžœ˜CJšœžœ/žœ˜@Jšœ/žœ˜CJ˜—Jšžœ˜—J˜J˜J˜—J˜J˜—š£œžœ.˜BJ˜J™/Jšžœ žœžœžœ˜šžœ ˜Jšžœ˜šžœ˜J˜šžœžœžœ#ž˜2J˜$Jšžœ˜—Jšžœžœžœ˜:J˜——J˜J˜J˜—š£ œžœ&žœ˜@Jšœžœ˜/šžœžœ˜Jšœžœ˜ Jšœžœ žœ˜'Jšžœžœžœ˜=š žœ"žœžœžœž˜QJ˜ J˜ J˜ Jšžœ˜—Jšžœžœžœ˜=J˜%J˜—J˜J˜—š£ œžœžœ˜8Jšœžœ˜/šžœžœ˜Jšœžœ ˜Jšœžœžœ˜1š žœ"žœžœžœž˜QJ˜J˜J˜J˜Jšžœ˜—J˜—J˜J˜—š£ œžœžœ˜4Jšœ žœ˜(Jšœžœ˜šžœ žœ˜Jšœ žœ˜Jšœ žœ žœ ˜&šžœ$žœž˜BJ˜J˜J˜Jšžœ˜—J˜—šžœ"žœžœž˜5J˜J˜Jšžœ˜—J˜J˜—š£ œžœžœ˜4Jšœ žœ˜*Jšœžœ˜šžœžœ˜Jšœ žœ˜Jšœ žœ žœ ˜(šžœ"žœž˜EJ˜J˜J˜Jšžœ˜—J˜—šžœž˜šžœ'žœžœž˜:J˜J˜Jšžœ˜——J˜J˜—š£œžœžœžœ˜7Jšœžœ˜Jš žœžœžœžœžœžœ˜?š žœ"žœžœžœž˜QJ˜Jšžœ˜—Jšžœ˜ J˜J˜—š£œžœžœžœ˜7Jšœžœ˜Jš žœ'žœžœžœ žœ˜LJšžœ˜ J˜J˜—š£ œžœžœžœ˜6Jšœžœ˜Jš žœ'žœžœžœ žœ˜LJšžœ˜ J˜J˜—š£œžœžœ˜;Jš œ žœžœžœžœ˜UJš œ žœžœžœžœ˜Uš žœžœ žœžœž˜9J˜J˜J˜ Jšžœ˜—J˜——š  ™ š£œžœžœ˜Jšœžœ˜!J˜J˜——š  ™ ˜CJ˜—šœ žœ1˜BJ˜—J˜5J˜—Jšžœ˜J˜—…—rμ›U