DIRECTORY CedarProcess, Convert, FileNames, G2dBasic, G3dBasic, G3dIO, G3dMatrix, FS, IO, Process, Rope, Tioga, TiogaAccess; G3dIOImpl: CEDAR MONITOR IMPORTS CedarProcess, Convert, FileNames, FS, IO, Process, Rope, TiogaAccess EXPORTS G3dIO ~ BEGIN IntegerSequence: TYPE ~ G3dBasic.IntegerSequence; IntegerSequenceRep: TYPE ~ G3dBasic.IntegerSequenceRep; NatSequence : TYPE ~ G3dBasic.NatSequence; NatSequenceRep: TYPE ~ G3dBasic.NatSequenceRep; Pair: TYPE ~ G3dBasic.Pair; PairSequence: TYPE ~ G3dBasic.PairSequence; PairSequenceRep: TYPE ~ G3dBasic.PairSequenceRep; Quad: TYPE ~ G3dBasic.Quad; RealSequence: TYPE ~ G3dBasic.RealSequence; RealSequenceRep: TYPE ~ G3dBasic.RealSequenceRep; SurfaceSequence: TYPE ~ G3dBasic.SurfaceSequence; Triple: TYPE ~ G3dBasic.Triple; TripleSequence: TYPE ~ G3dBasic.TripleSequence; TripleSequenceRep: TYPE ~ G3dBasic.TripleSequenceRep; SurfaceSequenceRep: TYPE ~ G3dBasic.SurfaceSequenceRep; ErrorType: TYPE ~ G3dIO.ErrorType; FieldRep: TYPE ~ G3dIO.FieldRep; FieldSequence: TYPE ~ G3dIO.FieldSequence; FieldSequenceRep: TYPE ~ G3dIO.FieldSequenceRep; FieldType: TYPE ~ G3dIO.FieldType; Line: TYPE ~ G3dIO.Line; LineRep: TYPE ~ G3dIO.LineRep; Matrix: TYPE ~ G3dMatrix.Matrix; STREAM: TYPE ~ IO.STREAM; ROPE: TYPE ~ Rope.ROPE; Writer: TYPE ~ TiogaAccess.Writer; Error: PUBLIC ERROR [reason, lineText: ROPE, lineNumber: NAT] = CODE; ErrorReport: PUBLIC PROC [errorType: ErrorType, line: Line ¬ NIL, keyWord: ROPE ¬ NIL] ~ { lineNumber: INTEGER ¬ IF errorType # notFound THEN LineNumber[line] ELSE 0; text: ROPE ¬ IF errorType # notFound THEN line.rope ELSE NIL; reason: ROPE ¬ SELECT errorType FROM notFound => Rope.Concat["KeyWord not found: ", keyWord], format => IO.PutFR["Bad format, line %g: %g", IO.int[lineNumber], IO.rope[line.rope]], convert => IO.PutFR["Bad conversion, line %g: %g", IO.int[lineNumber], IO.rope[line.rope]], ENDCASE => NIL; Error[reason, text, lineNumber]; }; GetNonBlankLine: PROC [stream: STREAM, line: Line ¬ NIL] RETURNS [Line] ~ { DO line ¬ GetLine[stream, line]; IF line.type # blank THEN EXIT; ENDLOOP; RETURN[line]; }; GetLineAfterKey: PROC [ stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE, line: Line ¬ NIL] RETURNS [Line] ~ { [] ¬ FindKeyWord[stream, keyWord, circularSearch, , line]; RETURN[GetLine[stream, line]]; }; BlankLine: PROC [line: ROPE] RETURNS [BOOL] ~ { RETURN[Rope.SkipOver[line, 0, " \t"] = Rope.Length[line]]; }; Equal: PROC [r1, r2: ROPE] RETURNS [BOOL] ~ {RETURN[Rope.Equal[r1, r2, FALSE]]}; nScratchLines: NAT ~ 40; scratchLines: ARRAY [0..nScratchLines) OF Line ¬ ALL[NIL]; ObtainLine: PUBLIC ENTRY PROC RETURNS [line: Line] ~ { FOR i: NAT IN [0..nScratchLines) DO line: Line ~ scratchLines[i]; IF line # NIL THEN { scratchLines[i] ¬ NIL; line.index ¬ line.length ¬ 0; line.rope ¬ NIL; line.stream ¬ NIL; RETURN[line]; }; ENDLOOP; RETURN[NEW[LineRep]]; }; ReleaseLine: PUBLIC ENTRY PROC [line: Line] ~ { FOR i: NAT IN [0..nScratchLines) DO IF scratchLines[i] = NIL THEN { scratchLines[i] ¬ line; RETURN; }; ENDLOOP; }; LineNumber: PUBLIC PROC [line: Line] RETURNS [nLine: INTEGER ¬ 0] ~ { index: INT ¬ IO.GetIndex[line.stream]; IF line = NIL OR line.stream = NIL THEN RETURN[-1]; IO.SetIndex[line.stream, 0]; DO test: ROPE ¬ IO.GetLineRope[line.stream ! IO.EndOfStream, IO.Error => {nLine ¬ -1; EXIT}]; IF Rope.Equal[line.rope, test] THEN EXIT; nLine ¬ nLine+1; ENDLOOP; IO.SetIndex[line.stream, index]; }; NextKeyWord: PUBLIC PROC [ stream: STREAM, circularSearch: BOOL ¬ FALSE, maxNLinesToTest: CARDINAL ¬ LAST[CARDINAL]] RETURNS [key: ROPE] ~ { line: Line ¬ ObtainLine[]; nTested: CARDINAL ¬ 0; loopIndex: INT; length: INTEGER; eof, startedAtZero: BOOL ¬ FALSE; sIndexStart: INT ¬ IO.GetIndex[stream]; DO loopIndex ¬ IO.GetIndex[stream]; IF nTested >= maxNLinesToTest OR (startedAtZero AND loopIndex > sIndexStart) THEN GOTO notFound; line ¬ GetLine[stream, line ! IO.EndOfStream, IO.Error => {eof ¬ TRUE; CONTINUE}]; nTested ¬ nTested+1; IF eof THEN { IF NOT circularSearch OR startedAtZero THEN GOTO notFound; IO.SetIndex[stream, 0]; startedAtZero ¬ TRUE; LOOP; }; IF line.type = blank THEN LOOP; length ¬ Rope.Length[key ¬ GetWord[line]]; IF Rope.Fetch[key, length-1] = '~ THEN {key ¬ Rope.Substr[key, 0, length-1]; EXIT}; IF Equal[GetWord[line], "~"] THEN EXIT; REPEAT notFound => { IO.SetIndex[stream, sIndexStart]; ErrorReport[notFound, NIL, "no keyWord"]; }; ENDLOOP; ReleaseLine[line]; IO.SetIndex[stream, loopIndex]; }; FindKeyWord: PUBLIC PROC [ stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE, maxNLinesToTest: CARDINAL ¬ LAST[CARDINAL], line: Line ¬ NIL] RETURNS [Line] ~ { word: ROPE; nTested: CARDINAL ¬ 0; length: INTEGER ¬ Rope.Length[keyWord]; eof, startedAtZero: BOOL ¬ FALSE; sIndexStart: INT ¬ IO.GetIndex[stream]; DO IF nTested = maxNLinesToTest OR (startedAtZero AND IO.GetIndex[stream] > sIndexStart) THEN GOTO notFound; line ¬ GetLine[stream, line ! IO.EndOfStream, IO.Error => {eof ¬ TRUE; CONTINUE}]; nTested ¬ nTested+1; IF eof THEN { IF NOT circularSearch OR startedAtZero THEN GOTO notFound; IO.SetIndex[stream, 0]; startedAtZero ¬ TRUE; LOOP; }; SELECT Rope.Length[word ¬ GetWord[line]] FROM length => { IF NOT Equal[keyWord, word] THEN LOOP; IF Equal[GetWord[line], "~"] THEN EXIT; }; length+1 => { IF NOT Equal[keyWord, Rope.Substr[word, 0, length]] THEN LOOP; IF Rope.Fetch[word, length] = '~ THEN EXIT; }; ENDCASE => NULL; REPEAT notFound => { IO.SetIndex[stream, sIndexStart]; ErrorReport[notFound, NIL, keyWord]; }; ENDLOOP; RETURN[line]; }; InitializeFields: PUBLIC PROC [keyLine: Line] RETURNS [fields: FieldSequence] ~ { nFields: INTEGER ¬ 0; fields ¬ NEW[FieldSequenceRep[(NWordsInRope[keyLine.rope]-1)/2]]; -- allocate fields FOR n: NAT IN[0..fields.maxLength) DO -- set id and type of each field rope: ROPE ¬ GetWord[keyLine]; length: INTEGER ¬ Rope.Length[rope]; IF rope = NIL OR Rope.Equal[Rope.Substr[rope, 0, 2], "--"] THEN EXIT; IF Rope.Equal[rope, ","] THEN length ¬ Rope.Length[rope ¬ GetWord[keyLine]]; IF Rope.Fetch[rope, length-1] = ': THEN length ¬ length-1 ELSE IF NOT Rope.Equal[GetWord[keyLine], ":"] THEN ErrorReport[format, keyLine]; fields[n] ¬ NEW[FieldRep]; nFields ¬ nFields+1; fields[n].id ¬ Rope.Substr[rope, 0, length]; rope ¬ GetWord[keyLine]; length ¬ Rope.Length[rope]; IF Rope.Fetch[rope, length-1] = ', THEN rope ¬ Rope.Substr[rope, 0, length-1]; fields[n].type ¬ SELECT TRUE FROM Equal[rope, "integer"] => integer, Equal[rope, "real"] => real, Equal[rope, "pair"] => pair, Equal[rope, "triple"] => triple, Equal[rope, "nats"] => nats, ENDCASE => none; ENDLOOP; fields.length ¬ nFields; }; NumberOfLinesToConvert: PUBLIC PROC [stream: STREAM] RETURNS [INTEGER] ~ { GoodLine: PROC RETURNS [BOOL] ~ { DO line ¬ GetNonBlankLine[stream, line]; SELECT line.type FROM key => RETURN[FALSE]; comment => LOOP; ENDCASE => { rope: ROPE ¬ GetWord[line]; RETURN[rope = NIL OR NOT Equal["~", rope]]; }; ENDLOOP; }; line: Line ¬ ObtainLine[]; nLines: INTEGER ¬ 0; sIndexSave: INT ¬ IO.GetIndex[stream]; -- start data conversion from here DO IF GoodLine[ ! IO.EndOfStream => GOTO eof] THEN nLines ¬ nLines+1 ELSE EXIT; REPEAT eof => NULL; ENDLOOP; IO.SetIndex[stream, sIndexSave]; -- reset for upcoming conversions ReleaseLine[line]; RETURN[nLines]; }; NWordsInRope: PUBLIC PROC [line: ROPE] RETURNS [INTEGER] ~ { nWords, index: INTEGER ¬ 0; length: INTEGER ¬ Rope.Length[line]; IF line = NIL THEN RETURN[0]; index ¬ Rope.SkipOver[line, 0, " \t"]; WHILE index < length DO nWords ¬ nWords+1; index ¬ Rope.SkipOver[line, Rope.SkipTo[line, index, " \t"], " \t"]; ENDLOOP; RETURN[nWords]; }; GetStream: PUBLIC PROC [fileName: ROPE] RETURNS [stream: STREAM] ~ { stream ¬ FS.StreamOpen[FileNames.ResolveRelativePath[fileName] ! FS.Error => CONTINUE]; }; GetLine: PUBLIC PROC [stream: STREAM, line: Line ¬ NIL] RETURNS [Line] ~ { Process.CheckForAbort[]; IF line = NIL THEN line ¬ ObtainLine[]; line.rope ¬ NIL; line.index ¬ 0; line.type ¬ blank; line.rope ¬ IO.GetLineRope[stream]; line.length ¬ Rope.Length[line.rope]; line.stream ¬ stream; IF NOT BlankLine[line.rope] THEN { rope: ROPE ¬ GetWord[line]; IF Rope.Equal["--", Rope.Substr[rope, 0, 2]] THEN line.type ¬ comment ELSE IF Rope.Fetch[rope, Rope.Length[rope]-1] = '~ THEN line.type ¬ key ELSE line.type ¬ IF Equal["~", GetWord[line]] THEN key ELSE data; line.index ¬ 0; }; RETURN[line]; }; GetDataLine: PUBLIC PROC [stream: STREAM, line: Line ¬ NIL] RETURNS [Line] ~ { DO line ¬ GetLine[stream, line]; IF line.type = data THEN RETURN[line]; ENDLOOP; }; GetWord: PUBLIC PROC [line: Line] RETURNS [word: ROPE] ~ { index: INTEGER; IF line = NIL THEN RETURN[NIL]; IF line.rope = NIL OR line.length = 0 OR line.index = line.length THEN RETURN[NIL]; IF line.index = 0 THEN line.index ¬ Rope.SkipOver[line.rope, 0, " \t"]; index ¬ Rope.SkipTo[line.rope, line.index, " \t"]; word ¬ Rope.Substr[line.rope, line.index, index-line.index]; line.index ¬ Rope.SkipOver[line.rope, index, " \t"]; }; GetInteger: PUBLIC PROC [line: Line] RETURNS [INTEGER] ~ { word: ROPE ¬ GetWord[line]; IF word = NIL THEN ErrorReport[format, line]; RETURN[Convert.IntFromRope[word ! Convert.Error => ErrorReport[convert, line]]]; }; GetReal: PUBLIC PROC [line: Line] RETURNS [REAL] ~ { word: ROPE ¬ GetWord[line]; IF word = NIL THEN ErrorReport[format, line]; RETURN[Convert.RealFromRope[word ! Convert.Error => ErrorReport[convert, line]]]; }; GetPair: PUBLIC PROC [line: Line] RETURNS [Pair] ~ { p: ARRAY [0..2) OF REAL; FOR n: NAT IN[0..2) DO word: ROPE ¬ GetWord[line]; IF word = NIL THEN ErrorReport[format, line]; p[n] ¬ Convert.RealFromRope[word ! Convert.Error => ErrorReport[convert, line]]; ENDLOOP; RETURN[[p[0], p[1]]]; }; GetTriple: PUBLIC PROC [line: Line] RETURNS [Triple] ~ { p: ARRAY [0..3) OF REAL; FOR n: NAT IN[0..3) DO word: ROPE ¬ GetWord[line]; IF word = NIL THEN ErrorReport[format, line]; p[n] ¬ Convert.RealFromRope[word ! Convert.Error => ErrorReport[convert, line]]; ENDLOOP; RETURN[[p[0], p[1], p[2]]]; }; GetNats: PUBLIC PROC [line: Line] RETURNS [nats: NatSequence] ~ { maxLength: NAT ¬ NWordsInRope[Rope.Substr[line.rope, line.index]]; nats ¬ NEW[NatSequenceRep[maxLength]]; FOR n: NAT IN [0..maxLength) DO word: ROPE ¬ GetWord[line]; IF Rope.Length[word] > 1 AND Rope.Equal[Rope.Substr[word, 0, 2], "--"] THEN EXIT; nats[n] ¬ Convert.IntFromRope[word ! Convert.Error => ErrorReport[convert, line]]; nats.length ¬ nats.length+1; ENDLOOP; }; maxNTriples: CARDINAL ~ G2dBasic.maxNTriples; ReadRope: PUBLIC PROC [stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE] RETURNS [rope: ROPE] ~ { line: Line ¬ FindKeyWord[stream, keyWord, circularSearch, , ObtainLine[]]; rope ¬ Rope.Substr[line.rope, line.index, Rope.Length[line.rope]-line.index]; ReleaseLine[line]; }; ReadInteger: PUBLIC PROC [stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE] RETURNS [n: INTEGER] ~ { line: Line ¬ ObtainLine[]; n ¬ GetInteger[GetLineAfterKey[stream, keyWord, circularSearch, line]]; ReleaseLine[line]; }; ReadReal: PUBLIC PROC [stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE] RETURNS [r: REAL] ~ { line: Line ¬ ObtainLine[]; r ¬ GetReal[GetLineAfterKey[stream, keyWord, circularSearch, line]]; ReleaseLine[line]; }; ReadPair: PUBLIC PROC [stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE] RETURNS [p: Pair] ~ { line: Line ¬ ObtainLine[]; p ¬ GetPair[GetLineAfterKey[stream, keyWord, circularSearch, line]]; ReleaseLine[line]; }; ReadTriple: PUBLIC PROC [stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE] RETURNS [t: Triple] ~ { line: Line ¬ ObtainLine[]; t ¬ GetTriple[GetLineAfterKey[stream, keyWord, circularSearch, line]]; ReleaseLine[line]; }; ReadIntegerSequence: PUBLIC PROC [ stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE, nElements: INT ¬ 0] RETURNS [IntegerSequence] ~ { line: Line ¬ FindKeyWord[stream, keyWord, circularSearch, , ObtainLine[]]; nLines: INT ¬ IF nElements # 0 THEN nElements ELSE NumberOfLinesToConvert[stream]; integers: IntegerSequence ¬ NEW[IntegerSequenceRep[nLines]]; integers.length ¬ nLines; FOR n: INT IN [0..nLines) DO line ¬ GetDataLine[stream, line ! IO.EndOfStream => GOTO eof]; integers[n] ¬ GetInteger[line]; REPEAT eof => NULL; ENDLOOP; ReleaseLine[line]; RETURN[integers]; }; ReadRealSequence: PUBLIC PROC [ stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE, nElements: INT ¬ 0] RETURNS [RealSequence] ~ { line: Line ¬ FindKeyWord[stream, keyWord, circularSearch, , ObtainLine[]]; nLines: INT ¬ IF nElements # 0 THEN nElements ELSE NumberOfLinesToConvert[stream]; reals: RealSequence ¬ NEW[RealSequenceRep[nLines]]; reals.length ¬ nLines; FOR n: INT IN [0..nLines) DO line ¬ GetDataLine[stream ! IO.EndOfStream => GOTO eof]; reals[n] ¬ GetReal[line]; REPEAT eof => NULL; ENDLOOP; ReleaseLine[line]; RETURN[reals]; }; ReadPairSequence: PUBLIC PROC [ stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE, nElements: INT ¬ 0] RETURNS [PairSequence] ~ { line: Line ¬ FindKeyWord[stream, keyWord, circularSearch, , ObtainLine[]]; nLines: INT ¬ IF nElements # 0 THEN nElements ELSE NumberOfLinesToConvert[stream]; pairs: PairSequence ¬ NEW[PairSequenceRep[nLines]]; pairs.length ¬ nLines; FOR n: INT IN [0..nLines) DO line ¬ GetDataLine[stream, line ! IO.EndOfStream => GOTO eof]; pairs[n] ¬ GetPair[line]; REPEAT eof => NULL; ENDLOOP; ReleaseLine[line]; RETURN[pairs]; }; ReadTripleSequence: PUBLIC PROC [ stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE, nElements: INT ¬ 0] RETURNS [TripleSequence] ~ { line: Line ¬ FindKeyWord[stream, keyWord, circularSearch, , ObtainLine[]]; indexed: BOOL ¬ Rope.Find[line.rope, "index",, FALSE] # -1; nLines: INT ¬ MIN[maxNTriples, IF nElements # 0 THEN nElements ELSE NumberOfLinesToConvert[stream]]; triples: TripleSequence ¬ NEW[TripleSequenceRep[nLines]]; triples.length ¬ nLines; FOR n: INT IN [0..nLines) DO line ¬ GetDataLine[stream, line ! IO.EndOfStream => GOTO eof]; IF indexed THEN [] ¬ GetWord[line]; triples[n] ¬ GetTriple[line]; REPEAT eof => NULL; ENDLOOP; ReleaseLine[line]; RETURN[triples]; }; ReadSurfaceSequence: PUBLIC PROC [ stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE, nElements: INT ¬ 0] RETURNS [SurfaceSequence] ~ { line: Line ¬ FindKeyWord[stream, keyWord, circularSearch, , ObtainLine[]]; indexed: BOOL ¬ Rope.Find[line.rope, "index",, FALSE] # -1; nLines: INT ¬ IF nElements # 0 THEN nElements ELSE NumberOfLinesToConvert[stream]; nats: SurfaceSequence ¬ NEW[SurfaceSequenceRep[nLines]]; nats.length ¬ nLines; FOR n: INT IN [0..nLines) DO line ¬ GetDataLine[stream, line]; IF indexed THEN [] ¬ GetWord[line]; nats[n].vertices ¬ GetNats[line]; ENDLOOP; ReleaseLine[line]; RETURN[nats]; }; ReadFields: PUBLIC PROC [ stream: STREAM, keyWord: ROPE, circularSearch: BOOL ¬ FALSE, nElements: INT ¬ 0] RETURNS [fields: FieldSequence] ~ { SkipTriple: PROC [l: Line] ~ {[] ¬ GetWord[l]; [] ¬ GetWord[l]; [] ¬ GetWord[l]}; line: Line ¬ FindKeyWord[stream, keyWord, circularSearch, , ObtainLine[]]; -- keyword line f: FieldSequence ¬ fields ¬ InitializeFields[line]; nLines: INTEGER ¬ IF nElements#0 THEN nElements ELSE NumberOfLinesToConvert[stream]; nTripleLines: INTEGER ¬ MIN[maxNTriples, nLines]; FOR n: INT IN [0..fields.length) DO -- allocate sequences fields[n].sequence ¬ SELECT fields[n].type FROM integer => NEW[IntegerSequenceRep[nLines]], real => NEW[RealSequenceRep[nLines]], pair => NEW[PairSequenceRep[nLines]], triple => NEW[TripleSequenceRep[nTripleLines]], nats => NEW[SurfaceSequenceRep[nLines]], ENDCASE => NIL; ENDLOOP; FOR n: NAT IN[0..fields.length) DO -- assign sequence length SELECT fields[n].type FROM integer => NARROW[fields[n].sequence, IntegerSequence].length ¬ nLines; real => NARROW[fields[n].sequence, RealSequence].length ¬ nLines; pair => NARROW[fields[n].sequence, PairSequence].length ¬ nLines; triple => NARROW[fields[n].sequence, TripleSequence].length ¬ nTripleLines; nats => NARROW[fields[n].sequence, SurfaceSequence].length ¬ nLines; ENDCASE => NULL; ENDLOOP; FOR n: NAT IN [0..nLines) DO -- line by line data conversions CedarProcess.CheckAbort[]; line ¬ GetDataLine[stream, line ! IO.EndOfStream => GOTO eof]; FOR nn: NAT IN [0..fields.length) DO SELECT fields[nn].type FROM integer => { s: IntegerSequence ¬ NARROW[fields[nn].sequence]; s[n] ¬ GetInteger[line]; }; real => { s: RealSequence ¬ NARROW[fields[nn].sequence]; s[n] ¬ GetReal[line]; }; pair => { s: PairSequence ¬ NARROW[fields[nn].sequence]; s[n] ¬ GetPair[line]; }; triple => { s: TripleSequence ¬ NARROW[fields[nn].sequence]; IF n < maxNTriples THEN s[n] ¬ GetTriple[line] ELSE SkipTriple[line]; }; nats => { s: SurfaceSequence ¬ NARROW[fields[nn].sequence]; s[n].vertices ¬ GetNats[line]; }; ENDCASE => NULL; ENDLOOP; REPEAT eof => NULL; ENDLOOP; ReleaseLine[line]; }; ReadMatrix: PUBLIC PROC [stream: STREAM] RETURNS [m: Matrix] ~ { m _ NEW[G3dMatrix.MatrixRep]; FOR row: INT IN [0 .. 4) DO FOR col: INT IN [0 .. 4) DO m[row][col] _ IO.GetReal[stream]; ENDLOOP; ENDLOOP; }; WritePair: PUBLIC PROC [stream: STREAM, p: Pair] ~ { WriteJustified[stream, p.x]; WriteJustified[stream, p.y]; IO.PutRope[stream, "\t"]; }; WriteTriple: PUBLIC PROC [stream: STREAM, t: Triple] ~ { WriteJustified[stream, t.x]; WriteJustified[stream, t.y]; WriteJustified[stream, t.z]; IO.PutRope[stream, "\t"]; }; WriteJustified: PUBLIC PROC [stream: STREAM, r: REAL] ~ { IF r >= 0.0 THEN IO.PutRope[stream, " "]; IO.PutF1[stream, "%6.5f ", IO.real[r]] }; WriteFields: PUBLIC PROC [stream: STREAM, keyWord: ROPE, fields: FieldSequence] ~ { RopeFromFieldType: PROC [type: FieldType] RETURNS [ROPE] ~ { RETURN [SELECT type FROM integer => "integer", real => "real", pair => "pair", triple => "triple", nats => "nats", ENDCASE => NIL]; }; IF fields # NIL THEN { nElements: INTEGER ¬ 0; IO.PutF1[stream, "\n%g~ ", IO.rope[keyWord]]; FOR n: NAT IN [0..fields.length) DO nElements ¬ MAX[nElements, SELECT fields[n].type FROM integer => NARROW[fields[n].sequence, IntegerSequence].length, real => NARROW[fields[n].sequence, RealSequence].length, pair => NARROW[fields[n].sequence, PairSequence].length, triple => NARROW[fields[n].sequence, TripleSequence].length, nats => NARROW[fields[n].sequence, SurfaceSequence].length, ENDCASE => nElements]; ENDLOOP; FOR n: NAT IN [0..fields.length) DO IO.PutF[stream, "%g: %g ", IO.rope[fields[n].id], IO.rope[RopeFromFieldType[fields[n].type]]]; ENDLOOP; IO.PutRope[stream, "\n\n"]; FOR n: NAT IN [0..nElements) DO FOR nn: NAT IN [0..fields.length) DO SELECT fields[nn].type FROM integer => { s: IntegerSequence ¬ NARROW[fields[nn].sequence]; IO.PutF1[stream, "%5g ", IO.int[INT[s[n]]]]; }; real => { s: RealSequence ¬ NARROW[fields[nn].sequence]; IO.PutF1[stream, "%9g ", IO.real[s[n]]]; }; pair => { s: PairSequence ¬ NARROW[fields[nn].sequence]; IO.PutF[stream, "%9g %9g\t\t ", IO.real[s[n].x], IO.real[s[n].y]]; }; triple => { s: TripleSequence ¬ NARROW[fields[nn].sequence]; IO.PutF[stream, "%9g %9g %9g\t\t ", IO.real[s[n].x], IO.real[s[n].y], IO.real[s[n].z]]; }; nats => { t: SurfaceSequence ¬ NARROW[fields[nn].sequence]; s: NatSequence ¬ t[n].vertices; FOR nnn: NAT IN [0..s.length) DO IO.PutF1[stream, "%5g ", IO.int[INT[s[nnn]]]]; ENDLOOP; }; ENDCASE => NULL; ENDLOOP; IO.PutRope[stream, "\n"]; ENDLOOP; }; }; WriteMatrix: PUBLIC PROC [stream: STREAM, m: Matrix] ~ { FOR row: INT IN [0 .. 4) DO FOR col: INT IN [0 .. 4) DO IO.PutF1[stream," %g", IO.real[m[row][col]]]; ENDLOOP; IO.PutRope[stream,"\n"]; ENDLOOP; }; ReadPointsPolygons: PUBLIC PROC [fileName: ROPE] RETURNS [points: TripleSequence, polygons: SurfaceSequence, errorMessage: ROPE] ~ { stream: IO.STREAM ¬ NIL; points ¬ NIL; polygons ¬ NIL; IF (stream ¬ FS.StreamOpen[fileName ! FS.Error => CONTINUE]) = NIL THEN { errorMessage ¬ "Can't open file."; RETURN; }; points ¬ ReadTripleSequence[stream, "Vertices", TRUE]; polygons ¬ ReadSurfaceSequence[stream, "Polygons", TRUE]; IO.Close[stream]; }; WritePointsPolygons: PUBLIC PROC [ fileName: ROPE, points: TripleSequence, polygons: SurfaceSequence] ~ { stream: STREAM ¬ FS.StreamOpen[fileName, $create]; IO.PutRope[stream, "Vertices~ xyzCoords: triple\n\n"]; FOR n: NAT IN [0..points.length) DO WriteTriple[stream, points[n]]; IO.PutRope[stream, "\n"]; ENDLOOP; IO.PutRope[stream, "\n\nPolygons~ vertices: nats\n\n"]; FOR n: NAT IN [0..polygons.length) DO nats: NatSequence ~ polygons[n].vertices; FOR i: NAT IN [0..nats.length) DO IO.PutF1[stream, "%g\t", IO.int[nats[i]]]; ENDLOOP; IO.PutRope[stream, "\n"]; ENDLOOP; IO.Close[stream]; }; PrintPair: PUBLIC PROC [stream: STREAM, pair: Pair, name: ROPE ¬ NIL] ~ { IF stream = NIL THEN RETURN; IF name = NIL THEN stream.PutF["%f\t%f\n", IO.real[pair.x], IO.real[pair.y]] ELSE stream.PutF["%g\t[%6.3f\t%6.3f]\n", IO.rope[name], IO.real[pair.x], IO.real[pair.y]]; }; PrintTriple: PUBLIC PROC [stream: STREAM, triple: Triple, name: ROPE ¬ NIL] ~ { IF stream = NIL THEN RETURN; IF name = NIL THEN IO.PutF[stream, "%f\t%f\t%f\n", IO.real[triple.x], IO.real[triple.y], IO.real[triple.z]] ELSE IO.PutFL[stream, "%g\t[%6.3f\t%6.3f\t%6.3f]\n", LIST[IO.rope[name], IO.real[triple.x], IO.real[triple.y], IO.real[triple.z]]]; }; PrintQuad: PUBLIC PROC [stream: STREAM, quad: Quad, name: ROPE ¬ NIL] ~ { IF stream = NIL THEN RETURN; IF name # NIL THEN IO.PutFL[stream, "%g: [%f\t%f\t%f\t%f]\n", LIST[IO.rope[name], IO.real[quad.x], IO.real[quad.y], IO.real[quad.z], IO.real[quad.w]]] ELSE IO.PutFL[stream, "%6.3f\t%6.3f\t%6.3f\t%6.3f\n", LIST[IO.real[quad.x], IO.real[quad.y], IO.real[quad.z], IO.real[quad.w]]]; }; PrintMatrix: PUBLIC PROC [stream: STREAM, matrix: Matrix, name: ROPE ¬ NIL] ~ { IF stream = NIL OR matrix = NIL THEN RETURN; IF name # NIL THEN IO.PutF1[stream, "%g\n", IO.rope[name]]; FOR i: NAT IN [0..3] DO IO.PutFL[stream, "%6.3f\t%6.3f\t%6.3f\t%6.3f\n", LIST[IO.real[matrix[i][0]], IO.real[matrix[i][1]], IO.real[matrix[i][2]], IO.real[matrix[i][3]]]]; ENDLOOP; }; WriteCreate: PUBLIC PROC [title: ROPE] RETURNS [w: Writer] ~ { w ¬ TiogaAccess.Create[]; WriteNode[w, title, "bi", TRUE]; TiogaAccess.Nest[w, 1]; WriteNode[w, IO.PutFR1["Created %g", IO.time[]], "i", TRUE, $code]; WriteNode[w, "", "", TRUE]; }; WriteNode: PUBLIC PROC [ w: Writer, node: ROPE, looks: ROPE ¬ NIL, comment: BOOL ¬ FALSE, format: ATOM ¬ NIL] ~ { tc: TiogaAccess.TiogaChar ¬ [0, '\000, ALL[FALSE], NIL, comment, FALSE, 0, NIL]; FOR n: INT IN [0..Rope.Length[looks]) DO -- set the looks c: CHAR ¬ Rope.Fetch[looks, n]; IF c IN Tioga.Look THEN tc.looks[c] ¬ TRUE; ENDLOOP; tc.format ¬ format; FOR n: INT IN [0..Rope.Length[node]) DO -- write the node tc.char ¬ Rope.Fetch[node, n]; TiogaAccess.Put[w, tc]; ENDLOOP; tc.endOfNode ¬ TRUE; TiogaAccess.Put[w, tc]; }; WritePartialNode: PUBLIC PROC [ w: Writer, node: ROPE, looks: ROPE ¬ NIL, comment: BOOL ¬ FALSE] ~ { tc: TiogaAccess.TiogaChar ¬ [0, '\000, ALL[FALSE], NIL, comment, FALSE, 0, NIL]; FOR n: INT IN [0..Rope.Length[looks]) DO -- set the looks c: CHAR ¬ Rope.Fetch[looks, n]; IF c IN Tioga.Look THEN tc.looks[c] ¬ TRUE; ENDLOOP; FOR n: INT IN [0..Rope.Length[node]) DO -- write the node tc.char ¬ Rope.Fetch[node, n]; TiogaAccess.Put[w, tc]; ENDLOOP; }; WriteKey: PUBLIC PROC [w: Writer, key: ROPE, val1, val2, val3: IO.Value ¬ [null[]]] ~ { WritePartialNode[w, key, "b"]; SELECT TRUE FROM val1 # [null[]] AND val2 # [null[]] AND val3 # [null[]] => WritePartialNode[w, IO.PutFR[" %g %g %g ", val1, val2, val3]]; val1 # [null[]] AND val2 # [null[]] => WritePartialNode[w, IO.PutFR[" %g %g ", val1, val2]]; val1 # [null[]] => WritePartialNode[w, IO.PutFR1[" %g ", val1]]; ENDCASE; WriteNodeEnd[w]; }; WriteNodeEnd: PUBLIC PROC [w: Writer] ~ { tc: TiogaAccess.TiogaChar ¬ [0, '\000, ALL[FALSE], NIL, FALSE, FALSE, 0, NIL]; tc.endOfNode ¬ TRUE; TiogaAccess.Put[w, tc]; }; END. .. %Œ G3dIOImpl.mesa Copyright Ó 1985, 1992 by Xerox Corporation. All rights reserved. Bloomenthal, July 22, 1992 1:49 pm PDT Glassner, July 5, 1989 6:38:15 pm PDT Ken Fishkin, August 20, 1992 4:23 pm PDT Type Declarations Errors Private Support Procedures Support Procedures Return the next keyWord. The file index points to the beginning of the key line. Return the line beginning with keyWord followed by "~". line is returned with its index set to after the "~". The file index points to the beginning of the line following the key line. Allocate fields, set their id's and types, but not their sequences. Get Procedures Return the next whitespace-delimited word in line. maxLength: NAT _ NWordsInRope[Rope.Substr[line.rope, line.index, line.length-line.index]]; Read Procedures NB: note that G3dMatrices are 4 by 4 by definition. Write Procedures NB: note that G3dMatrices are 4 by 4 by definition. Points/Polys Procedures Printing Procedures TiogaAccess Old Code VertexValidities: TYPE ~ RECORD [normal, color, texture, transmittance: BOOL _ FALSE]; VertexProc: TYPE ~ PROC [ vertexIndex, nVertices: CARDINAL, point, normal, color: Triple _ [0.0, 0.0, 0.0], texture: Pair _ [0.0, 0.0], transmittance: REAL _ 1.0, validities: VertexValidities _ []] RETURNS [continue: BOOL _ TRUE]; Return FALSE if the client's vertex array or sequence is about to overflow. ShapeRep: TYPE ~ RECORD [ type: {patch, poly} _ poly, -- patch or polygon? insideVisible: BOOL _ FALSE, -- true iff backfacing visible vertexValidities: VertexValidities _ [], -- what's in the file for a vertex? polygons: SurfaceSequence _ NIL, -- the polygons faceNormals: TripleSequence _ NIL, -- polygon normals faceColors: TripleSequence _ NIL, -- polygon colors faceTransmits: RealSequence _ NIL, -- polygon transmits props: PropList _ NIL -- for unconventional file formats ]; ShapeFromStream: PUBLIC PROC [ stream: STREAM, vertexProc: VertexProc, checkNVertices: BOOL _ FALSE] RETURNS [s: Shape] ~ { Get ready: AddToMap: PROC [index: CARDINAL, number: INTEGER] ~ { IF map = NIL THEN map _ NEW[IntegerSequenceRep[100]]; IF index >= map.maxLength THEN map _ G2dBasic.LengthenIntegerSequence[map, MAX[1.3, REAL[index+1]/map.maxLength]]; map[index] _ number; map.length _ MAX[map.length, index+1]; }; Eq: PROC [r1, r2: ROPE] RETURNS [BOOL] ~ {RETURN[Rope.Equal[r1, r2, FALSE]]}; CheckHeader: PROC [key: ROPE] ~ { -- hint about shape type, nVertices, nPolygons? streamIndex: INT _ IO.GetIndex[stream]; -- need to reset if key found header: Line _ ObtainLine[]; IF (header _ FindKeyWord[stream, key,, 10 ! Error => CONTINUE]) # NIL THEN { rope, previous: ROPE; DO ENABLE Convert.Error => CONTINUE; Seq: PROC [r:ROPE] RETURNS [b:BOOL] ~ {b_Eq[Rope.Substr[rope,,Rope.Size[r]],r]}; IF (rope _ GetWord[header]) = NIL THEN EXIT; SELECT TRUE FROM Seq["Polygon"] => s.type _ poly; Seq["Bezier"] => s.type _ patch; Seq["InsideVisible"] => s.insideVisible _ TRUE; Seq["CountFromOne"] => countFromOne _ TRUE; Seq["vertices"] => nVertices _ Convert.IntFromRope[previous]; Seq["polygons"] => nPolygons _ Convert.IntFromRope[previous]; Seq["patches"] => nPolygons _ Convert.IntFromRope[previous]; ENDCASE; previous _ rope; ENDLOOP; IO.SetIndex[stream, streamIndex]; }; ReleaseLine[header]; }; line: Line; fields: FieldSequence; vertexOverflow, countFromOne: BOOL _ FALSE; map, ids: IntegerSequence _ NIL; nVertices, nPolygons, vertexIndex: CARDINAL _ 0; IF stream = NIL THEN RETURN; s _ NEW[ShapeRep]; CheckHeader["ShapeHeader"]; CheckHeader["SurfaceType"]; Read Vertices: line _ FindKeyWord[stream, "Vertices", FALSE,, ObtainLine[]]; IF nVertices = 0 AND checkNVertices THEN nVertices _ NumberOfLinesToConvert[stream]; fields _ InitializeFields[line]; ids _ NEW[IntegerSequenceRep[fields.length]]; FOR n: NAT IN [0..fields.length) DO -- what's a vertex? SELECT TRUE FROM Eq[fields[n].id, "normalVec"] => s.vertexValidities.normal _ TRUE; Eq[fields[n].id, "rgbColor"] => s.vertexValidities.color _ TRUE; Eq[fields[n].id, "textureCoords"] => s.vertexValidities.texture _ TRUE; ENDCASE; ENDLOOP; FOR n: NAT IN [0..fields.length) DO ids[n] _ SELECT TRUE FROM Eq[fields[n].id, "index"] => 0, Eq[fields[n].id, "xyzCoords"] => 1, Eq[fields[n].id, "normalVec"] => 2, Eq[fields[n].id, "rgbColor"] => 3, Eq[fields[n].id, "textureCoords"] => 4, Eq[fields[n].id, "transmittance"] => 5, ENDCASE => -1; ENDLOOP; DO streamIndex: INT _ IO.GetIndex[stream]; -- need to reset if line is a key line _ GetLine[stream, line ! IO.EndOfStream => GOTO eof]; SELECT line.type FROM data => { transmittance: REAL; texture: Pair; point, normal, color: Triple; FOR n: NAT IN [0..fields.length) DO SELECT ids[n] FROM 0 => AddToMap[GetInteger[line], vertexIndex]; 1 => point _ GetTriple[line]; 2 => normal _ GetTriple[line]; 3 => color _ GetTriple[line]; 4 => texture _ GetPair[line]; 5 => transmittance _ GetReal[line]; ENDCASE; ENDLOOP; IF NOT vertexProc[vertexIndex, nVertices, point, normal, color, texture, transmittance, s.vertexValidities] THEN { vertexOverflow _ TRUE; EXIT; }; vertexIndex _ vertexIndex+1; }; key => { IO.SetIndex[stream, streamIndex]; EXIT; }; ENDCASE; REPEAT eof => NULL; ENDLOOP; ReleaseLine[line]; Read Polygons: fields _ ReadFields[stream, IF s.type = poly THEN "Polygons" ELSE "Patches",, nPolygons]; FOR n: NAT IN [0..fields.length) DO PutProp: PROC ~ {s.props _ Atom.PutPropOnList[ s.props, Convert.AtomFromRope[fields[n].id], fields[n].sequence]}; SELECT fields[n].type FROM integer => IF NOT Eq[fields[n].id, "index"] THEN PutProp[]; -- (ignore indices) real => IF Eq[fields[n].id, "transmittance"] THEN s.faceTransmits _ NARROW[fields[n].sequence] ELSE PutProp[]; triple => SELECT TRUE FROM Eq[fields[n].id, "rgbColor"] => s.faceColors _ NARROW[fields[n].sequence]; Eq[fields[n].id, "normalVec"] => s.faceNormals _ NARROW[fields[n].sequence]; ENDCASE => PutProp[]; nats => IF Eq[fields[n].id, "vertices"] THEN s.polygons _ NARROW[fields[n].sequence] ELSE PutProp[]; ENDCASE => PutProp[]; ENDLOOP; Tests: IF vertexOverflow THEN { -- cull smashed polygons n: NAT _ 0; DO poly: NatSequence _ s.polygons[n]; FOR nn: NAT IN [0..poly.length) DO IF poly[nn] >= vertexIndex THEN { -- overwrite this polygon s.polygons.length _ s.polygons.length-1; IF n # s.polygons.length THEN s.polygons[n] _ s.polygons[s.polygons.length]; EXIT; }; REPEAT FINISHED => n _ n+1; ENDLOOP; IF n >= s.polygons.length THEN RETURN; ENDLOOP; }; SELECT TRUE FROM map # NIL => -- take care of strangely indexed vertices: FOR n: NAT IN [0..s.polygons.length) DO poly: NatSequence _ s.polygons[n]; FOR nn: NAT IN [0..poly.length) DO poly[nn] _ map[poly[nn]]; ENDLOOP; ENDLOOP; countFromOne => FOR n: NAT IN [0..s.polygons.length) DO poly: NatSequence _ s.polygons[n]; FOR nn: NAT IN [0..poly.length) DO poly[nn] _ poly[nn]-1; -- vertices are indexed from 0 ENDLOOP; ENDLOOP; ENDCASE; FOR n: NAT IN [0..s.polygons.length) DO -- check polygon validity: poly: NatSequence _ s.polygons[n]; FOR nn: NAT IN [0..poly.length) DO IF poly[nn] >= vertexIndex THEN Error[IO.PutFR["polygon %g refers to non-existent vertex", IO.int[n]]]; ENDLOOP; ENDLOOP; }; NumberOfLinesToConvert: PROC [stream: STREAM, nElementsPerLine: INTEGER] RETURNS [INTEGER] ~ { nElements: INTEGER _ 0; indexSave: INT _ IO.GetIndex[stream]; -- start data conversion from here DO line: ROPE _ IO.GetLineRope[stream ! IO.EndOfStream, IO.Error => GOTO eof]; index: INTEGER _ Rope.SkipOver[line, 0, " \t"]; IF index = Rope.Length[line] THEN LOOP; -- blank line? SELECT Rope.Fetch[line, index] FROM '-, '. => IF Rope.Fetch[line, index+1] NOT IN ['0..'9] THEN EXIT; '0, '1, '2, '3, '4, '5, '6, '7, '8, '9 => NULL; ENDCASE => EXIT; IF NWordsInRope[line] # nElementsPerLine THEN EXIT; nElements _ nElements+1; REPEAT eof => NULL; ENDLOOP; IO.SetIndex[stream, indexSave]; -- reset for upcoming conversions RETURN[nElements]; }; NumberOfLinesToConvert: PROC [stream: STREAM, nElementsPerLine: INTEGER] RETURNS [INTEGER] ~ { WordType: TYPE ~ {number, none, other}; TestWord: PROC RETURNS [WordType] ~ { WHILE index < refText.length DO c: CHAR _ refText[index]; index _ index+1; SELECT c FROM Ascii.SP, Ascii.TAB => EXIT; '., '0, '1, '2, '3, '4, '5, '6, '7, '8, '9 => NULL; ENDCASE => RETURN[other]; ENDLOOP; RETURN[number]; }; SkipWhiteAndTestWord: PROC RETURNS [WordType] ~ { c: CHAR; DO IF index = refText.length THEN RETURN[none]; c _ refText[index]; index _ index+1; IF c # Ascii.SP AND c # Ascii.TAB THEN EXIT; ENDLOOP; RETURN[SELECT c FROM '., '-, '0, '1, '2, '3, '4, '5, '6, '7, '8, '9 => TestWord[], ENDCASE => other ]; }; GoodLine: PROC RETURNS [BOOL] ~ { nNumbersInLine: INTEGER _ 0; index _ refText.length _ 0; [] _ Rope.AppendChars[refText, GetNonBlankLine[stream].rope]; DO SELECT SkipWhiteAndTestWord[] FROM none => RETURN[nNumbersInLine = nElementsPerLine]; number => nNumbersInLine _ nNumbersInLine+1; ENDCASE => RETURN[FALSE]; ENDLOOP; }; sIndexSave: INT _ IO.GetIndex[stream]; -- start data conversion from here index, nLines: INTEGER _ 0; refText: TEXT _ RefText.ObtainScratch[1000]; DO IF GoodLine[ ! IO.EndOfStream => GOTO eof] THEN nLines _ nLines+1 ELSE EXIT; REPEAT eof => NULL; ENDLOOP; RefText.ReleaseScratch[refText]; IO.SetIndex[stream, sIndexSave]; -- reset for upcoming conversions RETURN[nLines]; }; NatLine: PROC [line: Line] RETURNS [BOOL] ~ { DO word: ROPE _ GetWord[line]; IF word = NIL THEN RETURN[TRUE]; refText _ Rope.ToRefText[GetWord[line]]; FOR n: NAT IN [0..refText.length) DO IF refText[n] NOT IN ['0..'9] THEN RETURN[FALSE]; ENDLOOP; ENDLOOP; }; Ê+¤•NewlineDelimiter –"cedarcode" style™™Jšœ Ïeœ6™BJ™&J™%J™(J˜JšÏk œGžœžœ$˜|J˜—šÑbln œžœž˜Jšžœ#žœžœ˜LJšžœ˜ J˜—Jšœž˜headšÏl™Jšœžœ˜3Jšœžœ˜7Jšœžœ˜,Jšœžœ˜1Jšœž œ˜ Jšœžœ˜-Jšœžœ˜2Jšœž œ˜ Jšœžœ˜-Jšœžœ˜2Jšœžœ˜2Jšœžœ˜#Jšœžœ˜1Jšœžœ˜6Jšœžœ˜7Jšœžœ˜%Jšœ žœ˜$Jšœžœ˜,Jšœžœ˜1Jšœžœ˜%Jšœ žœ˜Jšœ žœ˜"Jšœžœ˜$Jšžœžœžœžœ˜Jšžœžœžœ˜Jšœ žœ˜&—š ™Jš Ïnœžœžœžœžœžœ˜E—š ™š ¡ œž œ%žœ žœžœ˜ZJš œ žœžœžœžœ˜KJš œžœžœžœ žœžœ˜=šœžœžœ ž˜$J˜8Jšœ žœ"žœžœ˜VJšœÏsœ¢žœ¢œ¢œ¢œ¢žœžœ˜[Jšžœžœ˜—J˜ ˜J˜——š ¡œžœ žœžœžœ ˜Kšž˜J˜Jšžœžœžœ˜Jšžœ˜—Jšžœ˜ J˜J˜—š¡œžœ˜Jšœžœ˜Jšœ žœ˜Jšœžœžœ˜Jšœ žœ˜Jšžœ˜J˜J˜:Jšžœ˜J˜J˜—š ¡ œžœžœžœžœ˜/Jšžœ4˜:J˜J˜—Jš¡œžœ žœžœžœžœžœ˜P—š ™Jšœžœ˜š œžœžœžœžœ˜:J˜—š¡ œžœž œžœ˜6šžœžœžœž˜#J˜šžœžœžœ˜Jšœžœ˜J˜Jšœ žœ˜Jšœžœ˜Jšžœ˜ J˜—Jšžœ˜—Jšžœžœ ˜J˜J˜—š¡ œžœž œ˜/šžœžœžœž˜#šžœžœžœ˜J˜Jšžœ˜J˜—Jšžœ˜—J˜J˜—š¡ œž œžœ žœ ˜EJšœžœžœ˜&Jš žœžœžœžœžœžœ˜3Jšžœ˜šž˜Jš œžœžœžœžœžœ˜ZJšžœžœžœ˜)J˜Jšžœ˜—Jšžœ˜ J˜J˜—š¡ œžœžœ˜Jšœžœ˜Jšœžœžœ˜Jšœžœžœžœ˜+Jšžœžœ˜J˜J™QJ˜Jšœ žœ˜Jšœ žœ˜Jšœžœ˜Jšœžœžœ˜!Jšœ žœžœ˜'šž˜Jšœ žœ˜ šžœžœžœ˜LJšžœžœ ˜—Jš œžœžœžœžœ˜RJ˜šžœžœ˜ Jš žœžœžœžœžœ ˜:Jšžœ˜Jšœžœ˜Jšžœ˜J˜—Jšžœžœžœ˜J˜*Jšžœ žœ'žœ˜SJšžœžœžœ˜'šž˜˜ Jšžœ˜!Jšœžœ˜)J˜——Jšžœ˜—J˜Jšžœ˜J˜J˜—š¡ œžœžœ˜Jšœžœ˜Jšœ žœ˜Jšœžœžœ˜Jšœžœžœžœ˜+Jšœ žœ˜Jšžœ˜J˜J™\J™]Jšœžœ˜ Jšœ žœ˜Jšœžœ˜'Jšœžœžœ˜!Jšœ žœžœ˜'šž˜šžœžœžœžœ ˜UJšžœžœ ˜—Jš œžœžœžœžœ˜RJ˜šžœžœ˜ Jš žœžœžœžœžœ ˜:Jšžœ˜Jšœžœ˜Jšžœ˜J˜—šžœ#ž˜-˜ Jšžœžœžœžœ˜&Jšžœžœžœ˜'J˜—˜ Jšžœžœ.žœžœ˜>Jšžœžœžœ˜+J˜—Jšžœž˜—šž˜˜ Jšžœ˜!Jšœžœ ˜$J˜——Jšžœ˜—Jšžœ˜ J˜J˜—š¡œž œžœ˜QJ™CJšœ žœ˜Jšœ žœ6Ïc˜Tš žœžœžœžœ£ ˜KJšœžœ˜Jšœžœ˜$Jš žœžœžœ+žœžœ˜EJšžœžœ0˜Mšžœ ˜"Jšžœ˜Jšžœžœžœ#žœ˜P—Jšœ žœ ˜J˜J˜,J˜J˜Jšžœ!žœ'˜Nšœžœžœž˜!J˜"J˜J˜J˜!J˜Jšžœ ˜—Jšžœ˜—J˜J˜J˜—š ¡œž œ žœžœžœ˜Jš¡œžœžœžœ˜!šž˜J˜%šžœ ž˜Jšœžœžœ˜Jšœ žœ˜šžœ˜ Jšœžœ˜Jšžœžœžœžœ˜+J˜——Jšžœ˜—J˜—J˜Jšœžœ˜Jšœ žœžœ£"˜Nšž˜šžœ žœžœ˜*Jšžœ˜Jšžœžœ˜ —šž˜Jšœžœ˜ —Jšžœ˜—Jšžœ&£!˜IJ˜Jšžœ ˜J˜J˜—š ¡ œž œžœžœžœ˜Jšœžœ žœ˜—J˜J˜—š ¡œžœžœ žœžœžœ ˜JJ˜Jšžœžœžœ˜'Jšœ žœ˜J˜J˜Jšœ žœ˜#J˜%J˜šžœžœžœ˜"Jšœžœ˜šžœ*˜,Jšžœ˜šžœžœ+˜2Jšžœ˜Jšžœ žœžœžœ˜A——J˜J˜—Jšžœ˜ J˜J˜—š ¡ œžœžœ žœžœžœ ˜Nšž˜J˜Jšžœžœžœ˜&Jšžœ˜—J˜J˜—š¡œž œžœžœ˜:J™2Jšœžœ˜Jš žœžœžœžœžœ˜Jšžœ žœžœžœžœžœžœ˜SJšžœžœ1˜GJ˜2J˜J˜Jšžœžœ˜Jšžœ˜—J˜Jšžœ ˜˜J˜——š¡œž œ˜Jš œžœ žœžœžœ žœ˜PJšžœ˜J˜J˜JJš œžœžœžœ žœ ˜RJšœžœ˜3J˜šžœžœžœ ž˜Jšœžœžœ˜8J˜Jšžœžœ˜Jšžœ˜—J˜Jšžœ˜˜J˜——š¡œž œ˜Jš œžœ žœžœžœ žœ˜PJšžœ˜J˜J˜JJš œžœžœžœ žœ ˜RJšœžœ˜3J˜šžœžœžœ ž˜Jšœ"žœžœ˜>J˜Jšžœžœ˜Jšžœ˜—J˜Jšžœ˜˜J˜——š¡œž œ˜!Jš œžœ žœžœžœ žœ˜PJšžœ˜J˜J˜JJšœ žœ"žœ˜;šœžœžœžœ˜/Jšžœ žœ!˜4—Jšœžœ˜9J˜šžœžœžœ ž˜Jšœ"žœžœ˜>Jšžœ žœ˜#J˜Jšžœžœ˜Jšžœ˜—J˜Jšžœ ˜˜J˜——š¡Ðbnœž œ˜"Jš œžœ žœžœžœ žœ˜PJšžœ˜J˜J˜JJšœ žœ"žœ˜;Jš œžœžœžœ žœ ˜RJšœžœ˜8J˜šžœžœžœ ž˜J˜!Jšžœ žœ˜#J˜!Jšžœ˜—J˜Jšžœ˜ J˜J˜—š¡ œžœžœ˜Jš œžœ žœžœžœ žœ˜PJšžœ˜J˜Jš¡ œžœA˜QJšœJ£˜ZJ˜3Jš œžœžœ žœ žœ ˜TJšœžœžœ˜1š žœžœžœžœ£˜?šœžœž˜/Jšœ žœ˜+Jšœ žœ˜&Jšœ žœ˜&Jšœ žœ"˜0Jšœ žœ˜)Jšžœžœ˜—Jšžœ˜—š žœžœžœžœ£˜Bšžœž˜Jšœ žœ6˜GJšœžœ3˜AJšœžœ3˜AJšœ žœ;˜KJšœžœ6˜DJšžœžœ˜—Jšžœ˜—š žœžœžœ žœ £ ˜EJ˜Jšœ"žœžœ˜>šžœžœžœž˜$šžœž˜˜ Jšœžœ˜1J˜J˜—˜ Jšœžœ˜.J˜J˜—˜ Jšœžœ˜.J˜J˜—˜ Jšœžœ˜0Jšžœžœžœ˜EJ˜—˜ Jšœžœ˜1J˜J˜—Jšžœžœ˜—Jšžœ˜—šž˜Jšœžœ˜ —Jšžœ˜J˜—J˜—Icode˜š¡ œž œ žœžœ˜AL™3L˜Lšœžœ˜šžœžœžœ ž˜šžœžœžœ ž˜Lšœžœ˜!Lšžœ˜—Lšžœ˜—L˜—J˜—š ™š¡ œž œ žœ˜4J˜J˜Jšžœ˜J˜J˜—š¡ œž œ žœ˜8J˜J˜J˜Jšžœ˜J˜J˜—š¡œž œ žœžœ˜9Jšžœ žœžœž˜)Jšžœžœ ˜&J˜J˜—š¡ œž œ žœ žœ˜Sš¡œžœžœžœ˜<šžœžœž˜J˜J˜J˜J˜J˜Jšžœžœ˜—J˜—šžœ žœžœ˜Jšœ žœ˜Jšžœžœ˜-šžœžœžœž˜#šœ žœ žœž˜5Jšœ žœ-˜>Jšœžœ*˜9Jšœžœ*˜9Jšœ žœ,˜=Jšœžœ-˜J˜Jšœžœ˜ Jšœ˜Jšœ žœžœžœ ˜CJšœžœ˜J˜J˜—š¡ œž œ˜J˜ Jšœžœ˜ Jšœžœžœ˜Jšœ žœžœ˜Jšœžœžœ˜J˜Jš œ'žœžœžœ žœžœ˜Pš žœžœžœžœ£˜9Jšœžœ˜Jšžœžœ žœžœ˜+Jšžœ˜—J˜š žœžœžœžœ£˜:J˜J˜Jšžœ˜—Jšœžœ˜J˜J˜J˜—š¡œž œ˜J˜ Jšœžœ˜ Jšœžœžœ˜Jšœ žœžœ˜J˜Jš œ'žœžœžœ žœžœ˜Pš žœžœžœžœ£˜9Jšœžœ˜Jšžœžœ žœžœ˜+Jšžœ˜—š žœžœžœžœ£˜:J˜J˜Jšžœ˜—J˜J˜—š¡œž œžœžœ˜WJ˜šžœžœž˜šœžœžœ˜:Jšœžœ(˜>—šœžœ˜&Jšœžœ˜5—˜Jšœžœ˜-—Jšžœ˜—J˜J˜J˜—š¡ œž œ˜)Jš œ'žœžœžœžœžœžœ˜NJšœžœ˜J˜J˜—J˜—Jšžœ˜J˜š ™š œžœžœ)žœžœ™WJ™—šœžœžœ™Jšœž œ™"J™0J™ Jšœžœ™J™'Jšžœ žœžœ™"Jšœ¢œ?™KJ™—šœ žœžœ™Jšœ#£™7Jšœžœžœ£™AJšœ-£#™PJšœ!žœ£™8Jšœ"žœ£™:Jšœ!žœ£™8Jšœ žœ£™:Jšœžœ£"™AJ™J™—š¡œžœžœ™Jšœžœ™J™Jšœžœžœ™Jšžœ ™J™šÏb ™ š¡œžœ žœ žœ™5Jšžœžœžœžœ™5šžœžœ™$Jšœ&žœžœ™M—J™Jšœ žœ™&J™—Jš¡œžœ žœžœžœžœžœ™Mš¡ œžœžœ£/™QJšœ žœžœ£™EJ™šžœ3žœžœžœ™LJšœžœ™šž™Jšžœžœ™!Jš ¡œžœžœžœžœ.™PJšžœžœžœžœ™,šžœžœž™J™"J™#Jšœ+žœ™0Jšœ&žœ™+J™?J™@J™?Jšžœ™—J™Jšžœ™—Jšžœ™!J™—J™J™—J™ J™Jšœžœžœ™+Jšœžœ™ Jšœ#žœ™0Jšžœ žœžœžœ™Jšœžœ ™J™J™—š¥™Jšœ'žœ™=Jšžœžœžœ,™TJ™ Jšœžœ$™-š žœžœžœžœ£™8šžœžœž™Jšœ>žœ™CJšœ=žœ™BJšœBžœ™GJšžœ™—Jšžœ™—šžœžœžœž™#šœ žœžœž™J™!J™$J™$J™$J™'J™'Jšžœ™—Jšžœ™—šž™Jšœ žœžœ£!™IJšœžœžœ™:šžœ ž™™ Jšœžœ™J™J™šžœžœžœž™#šžœž™J™-J™J™J™J™J™#Jšžœ™—Jšžœ™—šžœžœežœ™rJšœžœ™Jšžœ™J™—J™J™—™Jšžœ™!Jšžœ™J™—Jšžœ™—šž™Jšœžœ™ —Jšžœ™—J™—š¥™Jšœžœžœ žœ™Yšžœžœžœž™#š¡œžœ!™.Jšœ,žœ™B—šžœž™™ Jšžœžœžœ £™D—™šžœ"™$Jšžœžœ™1Jšžœ ™——™ šžœžœž™Jšœ/žœ™JJšœ1žœ™LJšžœ™——™šžœ™Jšžœžœ™,Jšžœ ™——šžœ™ J™ ——Jšžœ™——š¥™šžœžœ£™3Jšœžœ™ šž™J™"šžœžœžœž™"šžœžœ£™;J™(Jšžœžœ/™LJšžœ™J™—šž™Jšžœ ™—Jšžœ™—Jšžœžœžœ™&Jšžœ™—J™—šžœžœž™šœžœ£+™:šžœžœžœž™'J™"šžœžœžœž™"J™Jšžœ™—Jšžœ™——™šžœžœžœž™'J™"šžœžœžœž™"Jšœ£™6Jšžœ™—Jšžœ™——Jšžœ™—š žœžœžœžœ£™BJ™"šžœžœžœž™"šžœž™Jšœžœ3žœ ™G—Jšžœ™—Jšžœ™——J™J™—š¡œžœ žœžœ™HJšžœžœ™Jšœ žœ™Jšœ žœžœ£"™MJ™šž™Jš œžœžœžœžœ žœ™KJšœžœ!™/Jšžœžœžœ£™9šžœž™#Jš œ žœžœžœ žœžœ™BJšœ*žœ™/Jšžœžœ™—Jšžœ'žœžœ™3J™šž™Jšœžœ™ —Jšžœ™J™—Jšžœ%£!™HJšžœ ™J™J™—š¡œžœ žœžœ™HJšžœžœ™Jšœ žœ™'š¡œžœžœ™%šžœž™Jšœžœ™J™šžœž™ Jšœžœžœžœ™Jšœ.žœ™3Jšžœžœ™—Jšžœ™—Jšžœ ™J™—š¡œžœžœ™1Jšœžœ™šž™Jšžœžœžœ™,J™J™Jš žœ žœžœ žœžœžœ™,Jšžœ™—šžœžœž™J™=Jšžœ ™J™—J™—š¡œžœžœžœ™!Jšœžœ™J™J™=šž™šžœž™"Jšœžœ$™2J™,Jšžœžœžœ™—Jšžœ™—J™—Jšœ žœžœ£"™NJšœžœ™Jšœ žœ™,J™šž™šžœ žœžœ™*Jšžœ™Jšžœžœ™ —šž™Jšœžœ™ —Jšžœ™J™—J™ Jšžœ&£!™IJšžœ ™J™J™—š¡œžœžœžœ™-šž™Jšœžœ™Jš žœžœžœžœžœ™ J™(šžœžœžœž™$Jš žœ žœžœ žœžœžœ™1Jšžœ™—Jšžœ™—J™—J™——…—\ü®,