DIRECTORY Convert USING [RopeFromInt], Graph USING [CaretIndex, ColorIndex, Entity, EntityGroupList, EntityHashSize, EntityRec, EntityGroup, EntityGroupRec, EntityList, FontIndex, GraphHandle, HashIndex, NestedEntitiesRec, NumberOfColors, NumberOfFonts, PaintAction, ROPE, Text, TextRec, Texts, UnitLineWidth, ValueList, Viewer, XY], GraphCarets USING [Move, TurnOff, TurnOn], GraphConvert USING [RopeOfSlope, VLFromSDL, VLsFromValues], GraphCleanUp USING [CleanUpVL], GraphPrivate USING [AddEntityButton, AddGroupButton, AddTextButton, GraphAtomProc, PaintAll, PaintEntity, PaintGrids, PaintTarget, PaintText, systemColor], GraphUtil USING [Almost, AppendEGL, AppendEntityList, BlinkMsg, EntityFromId, GetIntField, GetRealField, InitSegAll, InitSegEnd, LengthOfVL, NewEntityId, NewGroupId, NewTextId, RaiseError, RealToScreenI, SetColor, SetIntField, SetSegments, TextFromId, UpdateSegAll, UpdateSegEnd], Imager USING [Error], ImagerFont USING [Find, Font, Scale], IO USING [int, PutFR], Rope USING [Cat, Concat], VFonts USING [EstablishFont], ViewerTools USING [GetContents, SetContents]; GraphUpdate: CEDAR PROGRAM IMPORTS Convert, GraphCarets, GraphCleanUp, GraphConvert, GraphPrivate, GraphUtil, Imager, ImagerFont, IO, Rope, VFonts, ViewerTools EXPORTS GraphPrivate = { OPEN Graph, GraphPrivate, GraphUtil; Update: PUBLIC GraphAtomProc = { IF handle # NIL THEN IF handle.controller # NIL THEN { OPEN handle; msg: ROPE; i: INT; -- r: REAL; SELECT atom FROM $Divisions => UpdateDivisions[handle]; $Bounds => UpdateBounds[handle]; $Carets => UpdateCarets[handle]; $Targets => UpdateTargets[handle]; $Grids => UpdateGrids[handle]; $Color => { [msg, i] _ GetIntField[controller.colorIndex]; IF msg = NIL THEN IF i IN ColorIndex THEN UpdateColor[handle, i] ELSE BlinkMsg[Rope.Cat[ "Index must be in [0..", Convert.RopeFromInt[NumberOfColors], ")"]]; }; $Font => { [msg, i]_ GetIntField[controller.fontIndex]; IF msg = NIL THEN IF i IN FontIndex THEN UpdateFont[handle, i] ELSE BlinkMsg[Rope.Cat[ "Index must be in [0..", Convert.RopeFromInt[NumberOfFonts], ")"]]; }; $Text => { [msg, i] _ GetIntField[controller.textId]; IF msg = NIL THEN { text: Text _ TextFromId[handle.allTexts, i]; UpdateText[handle, text, i]; -- text = nil => add it. }; }; $Entity, $EntityAndValues => { [msg, i] _ GetIntField[controller.entityId]; IF msg = NIL THEN { entity: Entity _ EntityFromId[entityHash, i]; UpdateEntity[handle, entity, i, atom = $EntityAndValues]; }; }; $XYValues => { msg: ROPE; i: INT; [msg, i] _ GetIntField[controller.idOfy]; IF msg = NIL THEN { entity: Entity _ EntityFromId[entityHash, i]; IF entity = NIL THEN BlinkMsg["No curve with this id. To add an entity, please use the Curve entry on the Spec menu."] ELSE UpdateXYValues[handle, entity]; }; }; ENDCASE => RaiseError[$UnknownAtom, "in Update"]; }; }; -- Update UpdateDivisions: PROC[handle: GraphHandle] = { OPEN handle; graph.auto[divisions] _ controller.auto[divisions]; graph.division[x] _ UpdateFromIntField[controller.divX, graph.division[x]]; graph.division[y] _ UpdateFromIntField[controller.divY, graph.division[x]]; IF graph.division[x] < 1 OR graph.division[y] < 1 THEN BlinkMsg["x or y divisions < 1."] ELSE IF chart.viewer # NIL THEN PaintAll[handle, TRUE]; -- paint everything. }; -- SetDivisionFields UpdateBounds: PROC[handle: GraphHandle] = { OPEN handle; graph.auto[bounds] _ controller.auto[bounds]; graph.bounds _ [ xmin: UpdateFromRealField[controller.xmin, graph.bounds.xmin], ymin: UpdateFromRealField[controller.ymin, graph.bounds.ymin], xmax: UpdateFromRealField[controller.xmax, graph.bounds.xmax], ymax: UpdateFromRealField[controller.ymax, graph.bounds.ymax] ]; IF NOT graph.auto[bounds] AND (graph.bounds.xmin >= graph.bounds.xmax OR graph.bounds.ymin >= graph.bounds.ymax) THEN BlinkMsg["Illegal bounds. (xmin >= xmax or ymin >= ymax.)"] ELSE IF chart.viewer # NIL THEN PaintAll[handle, TRUE]; }; -- UpdateBounds UpdateCarets: PROC[handle: GraphHandle] = { OPEN handle; UpdateCaret: PROC[index: CaretIndex] = { vx: Viewer _ controller.caretPlace[index][x]; vy: Viewer _ controller.caretPlace[index][y]; rx: REAL _ UpdateFromRealField[vx, graph.caret[index].place.x]; ry: REAL _ UpdateFromRealField[vy, graph.caret[index].place.y]; sx: INTEGER _ RealToScreenI[handle, rx, x]; sy: INTEGER _ RealToScreenI[handle, ry, y]; graph.caret[index]^ _ [on: controller.caretOn[index], place: [rx, ry]]; IF chart.viewer # NIL THEN { GraphCarets.Move[handle, sx, sy, index]; IF graph.caret[index].on THEN GraphCarets.TurnOn[handle, index] ELSE GraphCarets.TurnOff[handle, index]; }; }; -- UpdateCaret FOR i: CaretIndex IN CaretIndex DO UpdateCaret[primary] ENDLOOP; ViewerTools.SetContents[controller.slope, IF (graph.showSlope _ controller.slopeOn) THEN GraphConvert.RopeOfSlope[graph.caret[primary].place, graph.caret[secondary].place] ELSE NIL]; }; -- UpdateCarets UpdateTargets: PROC[handle: GraphHandle] = { OPEN handle; UpdateTarget: PROC[xy: XY] = { IF chart.viewer # NIL AND graph.target[xy].on THEN PaintTarget[handle, erase, xy]; graph.target[xy].value _ UpdateFromRealField[ controller.targetValue[xy], graph.target[xy].value]; graph.target[xy].width _ UpdateFromRealField[ controller.targetWidth[xy], graph.target[xy].width]; graph.target[xy].colorIndex _ UpdateFromIntField[ controller.targetColor[xy], graph.target[xy].colorIndex]; IF graph.target[xy].colorIndex NOT IN ColorIndex THEN { graph.target[xy].colorIndex _ 13; BlinkMsg["Color index out of range. Default is used."]; }; IF (graph.target[xy].on _ controller.targetOn[xy]) THEN IF chart.viewer # NIL THEN PaintTarget[handle, paint, xy]; }; UpdateTarget[x]; UpdateTarget[y]; }; -- UpdateTargets UpdateGrids: PROC[handle: GraphHandle] = { OPEN handle; UpdateGrid: PROC[wasOn, isOn: BOOL, xy: XY] = { IF wasOn # isOn THEN { IF chart.viewer # NIL THEN { IF wasOn THEN PaintGrids[handle, erase, xy]; PaintGrids[handle, paint, xy]; -- paint grids or ticks. }; graph.grids[xy] _ isOn; }; }; UpdateGrid[graph.grids[x], controller.gridOn[x], x]; UpdateGrid[graph.grids[y], controller.gridOn[y], y]; }; -- UpdateGrids UpdateColor: PROC[handle: GraphHandle, index: ColorIndex] = { OPEN handle; GetRGB: PROC[v: Viewer, r: REAL] RETURNS [rgb: REAL] = { rgb _ UpdateFromRealField[v, r]; IF rgb > 1.0 OR rgb < 0.0 THEN { rgb _ MAX[0.0, MIN[1.0, rgb]]; BlinkMsg["color value limited by [0..1]."]; }; }; graph.color[index].R _ GetRGB[controller.red, graph.color[index].R]; graph.color[index].G _ GetRGB[controller.green, graph.color[index].G]; graph.color[index].B _ GetRGB[controller.blue, graph.color[index].B]; systemColor[index] _ SetColor[index, graph.color[index]]; }; -- UpdateColor UpdateFont: PROC[handle: GraphHandle, index: FontIndex] = { OPEN handle; PaintTextsOfThisFont: PROC [action: PaintAction] = { IF chart.viewer # NIL THEN FOR ts: Texts _ graph.texts, ts.rest UNTIL ts = NIL DO text: Text _ ts.first; IF text.fontIndex = index THEN PaintText[handle, action, text]; ENDLOOP; }; -- PaintTextsOfThisFont msg: ROPE; vFontSize: INT; pFontScale: REAL; vFont, pFont: ImagerFont.Font _ NIL; fontFamily: ROPE _ ViewerTools.GetContents[controller.fontFamily]; IF fontFamily = NIL THEN {BlinkMsg["Specify font family."]; RETURN}; [msg, vFontSize] _ GetIntField[controller.vFontSize]; IF msg # NIL THEN RETURN; [msg, pFontScale] _ GetRealField[controller.pFontScale]; IF msg # NIL THEN RETURN; vFont _ VFonts.EstablishFont[fontFamily, vFontSize, controller.boldOn, controller.italicOn, TRUE]; -- true means default on failure. pFont _ ImagerFont.Scale[ ImagerFont.Find[Rope.Cat[ "Xerox/PressFonts/", fontFamily, Rope.Concat[IF controller.boldOn THEN "-b" ELSE "-m", IF controller.italicOn THEN "ir" ELSE "rr"]]], pFontScale ! Imager.Error => {pFont _ NIL; CONTINUE}]; IF pFont = NIL THEN BlinkMsg["Can't find font."] ELSE { PaintTextsOfThisFont[erase]; graph.font[index] _ [fontFamily, controller.boldOn, controller.italicOn, vFontSize, pFontScale]; imagerFonts[screen][index] _ vFont; imagerFonts[interpress][index] _ pFont; PaintTextsOfThisFont[paint]; }; }; -- UpdateFont UpdateText: PROC[handle: GraphHandle, text: Text, id: INT] = { OPEN handle; rope: ROPE _ ViewerTools.GetContents[controller.textContent]; msg: ROPE _ NIL; add: BOOL _ text = NIL; int: INT; GetTextPlace: PROC [v: Viewer, original: REAL] RETURNS [r: REAL] = { r _ UpdateFromRealField[v, original]; IF r > 3.0 OR r < - 3.0 THEN { r _ MAX[-3.0, MIN[3.0, r]]; BlinkMsg["Text coordinates limited by [-3..3]"]; }; }; -- GetTextPlace IF rope = NIL THEN {BlinkMsg["name field is empty."]; RETURN}; IF add THEN text _ NEW[TextRec _ []] ELSE IF chart.viewer # NIL THEN PaintText[handle, erase, text]; int _ UpdateFromIntField[controller.textFont, text.fontIndex]; IF int NOT IN FontIndex THEN msg _ "Illegal font index." ELSE text.fontIndex _ int; int _ UpdateFromIntField[controller.textColor, text.colorIndex]; IF int NOT IN ColorIndex THEN msg _ msg.Cat[" Illegal color index."] ELSE text.colorIndex _ int; IF msg # NIL THEN {BlinkMsg[msg]; text _ NIL; RETURN}; text^ _ [ text: rope, place: [GetTextPlace[controller.textPlaceX, text.place.x], GetTextPlace[controller.textPlaceY, text.place.y]], fontIndex: text.fontIndex, colorIndex: text.colorIndex, rotation: UpdateFromRealField[controller.textRotation, text.rotation], justifX: controller.justifX, justifY: controller.justifY, id: IF add THEN NewTextId[handle, id] ELSE id ]; IF add THEN SetIntField[controller.textId, text.id]; IF chart.viewer # NIL THEN PaintText[handle, paint, text]; IF add THEN { handle.allTexts _ CONS[text, handle.allTexts]; graph.texts _ CONS[text, graph.texts]; AddTextButton[handle, text]; }; }; -- UpdateText UpdateEntity: PROC [handle: GraphHandle, entity: Entity, id: INT, valuesToo: BOOL _ FALSE] = { OPEN handle; msg: ROPE _ NIL; index: INT _ 0; add: BOOL _ entity = NIL; plotted: BOOL _ IF add THEN FALSE ELSE (EntityFromId[graph.entityList, entity.id] # NIL); plotExits: BOOL _ chart.viewer # NIL; vlx, vly, lastX, lastY: ValueList; lenX, lenY: INT; group: EntityGroup _ NIL; index _ UpdateFromIntField[controller.entityColor, entity.colorIndex]; IF index NOT IN ColorIndex THEN {BlinkMsg[" Illegal color index."]; RETURN}; IF add AND NOT valuesToo THEN { BlinkMsg["Click with ctrl-shift at the Curve entry, if you really want to add a new curve."]; RETURN; }; IF valuesToo THEN { [vlx, vly, lastX, lastY, msg] _ GraphConvert.VLsFromValues[handle]; IF msg # NIL THEN { lenX _ LengthOfVL[vlx]; lenY _ LengthOfVL[vly]; IF lenX # lenY THEN msg _ IO.PutFR[ "number of x values = %g, but number of y values = %g.", IO.int[lenX], IO.int[lenY]] ELSE IF NOT add THEN IF lenX # entity.group.length THEN msg _ IO.PutFR[ "There should be %g values on this curve, but only %g are specified.", IO.int[entity.group.length], IO.int[lenX]]; }; IF msg # NIL THEN {BlinkMsg[msg]; RETURN} }; IF add THEN { -- allocate entity (and perhaps its group too). iHash: HashIndex; FOR egl: EntityGroupList _ entityGroupList, egl.rest UNTIL egl = NIL DO eg: EntityGroup _ egl.first; IF AlmostSameVL[vlx, IF eg.x.segments = NIL THEN eg.x.oldValues ELSE GraphConvert.VLFromSDL[eg.x.segments]] THEN { group _ eg; EXIT}; ENDLOOP; IF group = NIL THEN { -- create a new group, add it to entityGroupList, and init x fields. group _ NEW[EntityGroupRec _ [ x: NEW[EntityRec _ [ name: "X", oldValues: vlx, lastValue: lastX, id: (lastEntityId _ lastEntityId + 1) ]], ys: NEW[NestedEntitiesRec _ []], id: NewGroupId[handle, 0], length: lenX ]]; group.ys.comment _ Convert.RopeFromInt[group.id]; entityGroupList _ AppendEGL[entityGroupList, CONS[group, NIL]]; InitSegEnd[group.x]; }; entity _ NEW[EntityRec _ [ name: ViewerTools.GetContents[controller.entityName], comment: ViewerTools.GetContents[controller.entityCmt], colorIndex: index, mark: controller.mark, width: UpdateFromRealField[controller.entityWidth, entity.width], oldValues: vly, group: group, parent: group.ys, id: NewEntityId[handle, id], lastValue: lastY ]]; InitSegAll[entity]; graph.entityList _ AppendEntityList[graph.entityList, CONS[entity, NIL]]; group.ys.entityList _ AppendEntityList[group.ys.entityList, CONS[entity, NIL]]; iHash _ entity.id MOD EntityHashSize; entityHash[iHash] _ CONS[entity, entityHash[iHash]]; SetIntField[controller.entityId, entity.id]; SetIntField[controller.idOfy, entity.id]; AddGroupButton[handle, group]; AddEntityButton[handle, entity]; PaintEntity[handle, paint, entity, TRUE]; } ELSE { -- not adding a new entity sameX, sameY: BOOL; entity^ _ [ name: ViewerTools.GetContents[controller.entityName], comment: ViewerTools.GetContents[controller.entityCmt], colorIndex: index, mark: controller.mark, width: UpdateFromRealField[controller.entityWidth, entity.width], oldValues: entity.oldValues, segments: entity.segments, group: entity.group, id: NewEntityId[handle, id] ]; IF NOT plotted THEN { graph.entityList _ AppendEntityList[graph.entityList, CONS[entity, NIL]]; IF entity.group.x.segments = NIL THEN InitSegEnd[entity.group.x]; InitSegAll[entity]; }; sameX _ AlmostSameVL[vlx, GraphConvert.VLFromSDL[entity.group.x.segments]]; sameY _ AlmostSameVL[vly, GraphConvert.VLFromSDL[entity.segments]]; IF sameX THEN { IF NOT sameY THEN { IF plotExits AND plotted THEN PaintEntity[handle, erase, entity, TRUE]; UpdateSegAll[entity, vly]; }; } ELSE { -- x changed, whether same y or not. UpdateSegEnd[entity.group.x, vlx]; FOR el: EntityList _ handle.graph.entityList, el.rest UNTIL el = NIL DO SetSegments[el.first]; ENDLOOP; }; vlx _ GraphCleanUp.CleanUpVL[vlx]; vly _ GraphCleanUp.CleanUpVL[vly]; lastX _ GraphCleanUp.CleanUpVL[lastX]; lastY _ GraphCleanUp.CleanUpVL[lastY]; }; }; -- UpdateEntity UpdateXYValues: PROC [handle: GraphHandle, entity: Entity] = { xvl, yvl, lastX, lastY: ValueList; msg: ROPE; [xvl, yvl, lastX, lastY, msg] _ GraphConvert.VLsFromValues[handle]; IF msg # NIL THEN BlinkMsg[msg] ELSE { lenX: INT _ LengthOfVL[xvl]; lenY: INT _ LengthOfVL[yvl]; IF lenX # lenY THEN BlinkMsg[IO.PutFR[ "number of x values = %g, but number of y values = %g.", IO.int[lenX], IO.int[lenY]]] ELSE IF lenX # entity.group.length THEN BlinkMsg[IO.PutFR[ "number of values should be %g but there are only %g.", IO.int[entity.group.length], IO.int[lenX]]] ELSE { OPEN handle; -- length of x, y, and group x are the same. sameX, sameY: BOOL; plotExits: BOOL _ chart.viewer # NIL; plotted: BOOL _ EntityFromId[graph.entityList, entity.id] # NIL; IF NOT plotted THEN { graph.entityList _ AppendEntityList[graph.entityList, CONS[entity, NIL]]; IF entity.group.x.segments = NIL THEN InitSegEnd[entity.group.x]; InitSegAll[entity]; }; sameX _ AlmostSameVL[xvl, GraphConvert.VLFromSDL[entity.group.x.segments]]; sameY _ AlmostSameVL[yvl, GraphConvert.VLFromSDL[entity.segments]]; IF sameX THEN { IF NOT sameY THEN { IF plotExits AND plotted THEN PaintEntity[handle, erase, entity, TRUE]; UpdateSegAll[entity, yvl]; }; } ELSE { -- x changed, whether same y or not. UpdateSegEnd[entity.group.x, xvl]; FOR el: EntityList _ handle.graph.entityList, el.rest UNTIL el = NIL DO SetSegments[el.first]; ENDLOOP; }; IF plotExits THEN { IF sameX THEN PaintEntity[handle, paint, entity, TRUE] ELSE PaintAll[handle]; }; }; }; xvl _ GraphCleanUp.CleanUpVL[xvl]; yvl _ GraphCleanUp.CleanUpVL[yvl]; lastX _ GraphCleanUp.CleanUpVL[lastX]; lastY _ GraphCleanUp.CleanUpVL[lastY]; }; -- UpdateXYValues AlmostSameVL: PROC [vla, vlb: ValueList] RETURNS [BOOL] = { va: ValueList _ vla; vb: ValueList _ vlb; DO IF va = NIL THEN { IF vb = NIL THEN RETURN[TRUE] ELSE RETURN[FALSE]; } ELSE IF vb = NIL THEN RETURN[FALSE]; IF NOT Almost[va.first, vb.first] THEN RETURN[FALSE]; va _ va.rest; vb _ vb.rest; ENDLOOP; }; -- AlmostSameVL UpdateFromIntField: PROC [field: Viewer, origianl: INT _ 0, showMsg: BOOL _ TRUE] RETURNS [int: INT _ 0] = { msg: ROPE; [msg, int] _ GetIntField[field, showMsg]; IF msg # NIL THEN int _ origianl; }; -- UpdateFromIntField UpdateFromRealField: PROC [field: Viewer, origianl: REAL _ 0.0, showMsg: BOOL _ TRUE] RETURNS [real: REAL _ 0.0] = { msg: ROPE; [msg, real] _ GetRealField[field, showMsg]; IF msg # NIL THEN real _ origianl; }; -- UpdateFromRealField }. LOG. SChen, created at October 9, 1985 8:57:21 pm PDT. ÒGraphUpdate.mesa, Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: Sweetsun Chen, November 15, 1985 6:16:10 pm PST If handle = nil or controller = nil then noop; otherwise: updates internal values based on users input on panel and, if chart.viewer # nil, also updates display. $CurveGroup => { [msg, i] _ GetIntField[controller.entityId]; IF msg = NIL THEN { eg: EntityGroup _ EntityGroupFromId[handle.entityGroupList, i]; UpdateEntityGroup[handle, eg, i]; }; }; $Crosssection => { [msg, r] _ GetRealField[controller.entityId]; IF msg = NIL THEN UpdateCrosssection[controller, x]; }; no fields on panel have to do with blinking. UpdateX: PROC [handle: GraphHandle] = { OPEN handle; -- proc no use for now. length, pos: INT; vList, last: ValueList; [vList, last, length, pos] _ GraphConvert.ValueListFromRope[ ViewerTools.GetContents[controller.xValues]]; IF pos # 0 THEN RETURN; IF vList = NIL THEN {BlinkMsg["The values field is empty."]; RETURN}; IF length # entityGroupList.first.length THEN { BlinkMsg[Rope.Cat["There should be ", Convert.RopeFromInt[entityGroupList.first.length], "numbers in the values field."]]; RETURN}; UpdateSegEnd[entityGroupList.first.x, vList]; IF chart.viewer # NIL THEN PaintAll[handle]; }; -- UpdateX rules: if entity # nil: if not valuesToo, update all fields except the values. if valuesToo, and if values don't have syntax errors, then update values too. if x's and y's are "almost" the same as original values, don't change values. if xs are "almost" the same as original values, but not ys, then update y values. if ys are "almost" the same as original values, but not xs, then update x values. if both xs and ys are different from the original values, but the length is not changed, then update both xs and ys. if both xs and ys are different from the original values, and the length is changed, then error. if entity = nil, if not valuesToo, error. if valuesToo, then if values don't have syntax errors, add the entity to a group of the same x (otherwise to a new group) and intialize its values. vList: ValueList; eGroup: EntityGroup; [msg, groupId] _ GetIntField[controller.entityGroupId, "group id"]; IF msg # NIL THEN RETURN; eGroup _ EntityGroupFromId[handle.entityGroupList, groupId]; IF eGroup = NIL THEN { BlinkMsg[Rope.Cat["There is no group with id = ", Convert.RopeFromInt[groupId], "."]]; RETURN; }; [vList, length, pos] _ GraphConvert.ValueListFromRope[ ViewerTools.GetContents[controller.entityValues]]; IF pos # 0 THEN RETURN; IF vList = NIL THEN {BlinkMsg["Error in the values field."]; RETURN}; IF length # entityGroupList.first.length THEN { BlinkMsg[Rope.Cat["There should be ", Convert.RopeFromInt[entityGroupList.first.length], "numbers in the values field."]]; RETURN}; some checking: 1. check color index 2. enforce adding entity thru spec menu 3. check values group.x.group _ group; -- not for now. group.x.parent _ group.ys; -- not for now. UpdateEntityGroup: PROC[handle: GraphHandle, eg: EntityGroup] = { OPEN handle; }; -- UpdateEntityGroup UpdateCrosssection: PROC[handle: GraphHandle, x: REAL] = { OPEN handle; rope, tRope: ROPE; ok: BOOL; y: REAL; FOR egl: EntityGroupList _ graph.entityGroupList, egl.rest UNTIL egl = NIL DO xEntity: Entity _ egl.first.x; rope _ IF rope = NIL THEN "[" ELSE rope.Concat[", ["]; FOR el: EntityList _ egl.first.entityList, el.rest UNTIL el = NIL DO IF el.first = xEntity THEN LOOP; [ok, y] _ Crosssection[xEntity.newValues, el.first, x]; tRope _ IF ok THEN Convert.RopeFromReal[y] ELSE "(*)"; rope _ IF rope = NIL THEN tRope ELSE rope.Cat[" ", tRope]; ENDLOOP; rope.Concat["]"]; ENDLOOP; ViewerTools.SetContent[controller.yIds, rope]; }; -- UpdateCrosssection caller should make sure that field is not nil. caller should make sure that field is not nil. Ê´˜JšœÏmœ1™Nšœ™Icode™/—J˜šÏk ˜ Jšœžœ˜JšœžœÙžœ:žœ˜¦Jšœ žœ˜*Jšœ žœ)˜;Jšœ žœ ˜Jšœ žœ‰˜›Jšœ žœ‰˜˜Jšœžœ ˜Jšœ žœ˜%Jšžœžœ˜Jšœžœ˜Jšœžœ˜Jšœ žœ˜-—J˜šœ žœž˜Jšžœ`žœ˜„Jšžœžœ ˜=J˜—šœžœ˜ ™9J™g—šžœ žœžœžœžœžœžœ˜CJšœžœžœÏcÐikŸ˜šžœž˜Jšœ&˜&Jšœ ˜ Jšœ ˜ Jšœ"˜"Jšœ˜šœ ˜ Jšœ.˜.šžœžœž˜Jšžœžœ žœ˜.šžœ˜JšœD˜D——J˜—˜ Jšœ,˜,šžœžœžœ˜Jšžœžœ žœ˜,šžœ˜JšœC˜C——J˜—šœ ˜ Jšœ*˜*šžœžœžœ˜Jšœ,˜,JšœŸ˜5J˜—J˜—šœ˜Jšœ,˜,šžœžœžœ˜Jšœ-˜-Jšœ9˜9J˜—J˜—šœ˜Jšœžœžœ˜Jšœ)˜)šžœžœžœ˜Jšœ-˜-Jšžœ žœžœb˜vJšžœ ˜$J˜—Jšœ˜—™Jšœ,™,šžœžœžœ™Jšœ?™?Jšœ!™!J™—J™—šœ™Jšœ-™-Jšžœžœžœ#™4J™—Jšžœ*˜1—J˜—JšœŸ ˜ J˜šÏnœžœžœ˜;Jšœ3˜3JšœK˜KJšœK˜KJšžœžœžœ"˜XJš žœžœžœžœžœŸ˜LJšœŸ˜—J˜š¡ œžœžœ˜8Jšœ-˜-šœ˜Jšœ>˜>Jšœ>˜>Jšœ>˜>Jšœ=˜=J˜—Jš žœžœžœ)žœ(žœ<˜±Jš žœžœžœžœžœ˜7JšœŸ˜—J˜š¡ œžœžœ˜8š¡ œžœ˜(Jšœ-˜-Jšœ-˜-Jšœžœ7˜?Jšœžœ7˜?Jšœžœ ˜+Jšœžœ ˜+JšœG˜Gšžœžœžœ˜Jšœ(˜(Jšžœžœ"˜?Jšžœ$˜(J™,J˜—JšœŸ˜—J˜Jšžœžœ žœžœ˜@J˜šœ)˜)šžœ(ž˜.JšœR˜R—Jšžœžœ˜ —JšœŸ˜—J˜š¡ œžœžœ˜9š¡ œžœžœ˜Jšžœžœžœžœ ˜Ršœ-˜-Jšœ4˜4—šœ-˜-Jšœ4˜4—šœ1˜1Jšœ9˜9—šžœžœžœ žœ˜7Jšœ!˜!J˜7J˜—šžœ1ž˜7Jšžœžœžœ ˜:—J˜—Jšœ˜Jšœ˜JšœŸ˜—J˜š¡ œžœžœ˜7š¡ œžœžœžœ˜/šžœžœ˜šžœžœžœ˜Jšžœžœ˜,JšœŸ˜7J˜—J˜J˜—J˜—Jšœ4˜4Jšœ4˜4JšœŸ˜—J˜š¡ œžœ-žœ˜Jš ¡œžœžœžœžœ˜8Jšœ ˜ šžœ žœ žœ˜ Jšœžœžœ ˜J˜+J˜—J˜—JšœD˜DJšœF˜FJšœE˜EJšœ9˜9JšœŸ˜—J˜š¡ œžœ,žœ˜Hš¡œžœ˜4š žœžœžœžœ"žœžœž˜QJ˜Jšžœžœ!˜?Jšžœ˜—JšœŸ˜—Jšœžœ˜ Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜$Jšœ žœ2˜BJšžœžœžœ$žœ˜DJšœ5˜5Jšžœžœžœžœ˜Jšœ8˜8Jšžœžœžœžœ˜Jšœ\žœŸ!˜„šœ˜šœ˜Jšœ˜Jšœ ˜ šœ žœžœžœ˜5Jšžœžœžœ ˜.——Jšœ ˜ Jšœžœžœ˜+—Jšžœ žœžœ˜0šžœ˜J˜Jšœ`˜`J˜#J˜'J˜J˜—JšœŸ ˜—J˜š¡ œžœ&žœžœ˜KJšœžœ3˜=Jšœžœžœ˜Jšœžœ žœ˜Jšœžœ˜ š ¡ œžœžœžœžœ˜DJšœ%˜%šžœ žœ žœ˜Jšœžœžœ ˜J˜0J˜—JšœŸ˜—Jšžœžœžœ$žœ˜>Jšžœžœžœ˜$Jšžœžœžœžœ ˜?Jšœ>˜>Jšžœžœžœ žœ˜8Jšžœ˜Jšœ@˜@Jšžœžœžœ žœ'˜DJšžœ˜Jš žœžœžœžœžœ˜6˜ J˜ šœ:˜:Jšœ3˜3—Jšœ˜Jšœ˜JšœF˜FJšœ˜Jšœ˜Jšœžœžœžœ˜-J˜—Jšžœžœ)˜4Jšžœžœžœ ˜:šžœžœ˜ Jšœžœ˜.Jšœžœ˜&J˜J˜—JšœŸ ˜—J˜š¡œžœžœ Ÿ™LJšœ žœ™Jšœ™šœ<™Jšœ"˜"Jšœžœ˜ JšœC˜CJšžœžœžœ˜šžœ˜Jšœžœ˜Jšœžœ˜šžœ žœ žœ˜&Jšœ8˜8Jšžœ žœ ˜—šžœžœžœ žœ˜:Jšœ7˜7Jšœžœ ˜+—šžœžœ˜JšŸ,˜,Jšœžœ˜Jšœ žœžœ˜%Jšœ žœ/žœ˜@šžœžœ žœ˜Jšœ6žœ žœ˜IJšžœžœžœ˜AJ˜J˜—JšœK˜KJšœC˜Cšžœžœ˜šžœžœžœ˜Jšžœ žœ žœ$žœ˜GJšœ˜J˜—J˜—šžœŸ$˜+Jšœ"˜"šžœ3žœžœž˜GJ˜Jšžœ˜—J˜—šžœ žœ˜Jšžœžœ$žœ˜6Jšžœ˜J˜—J˜—J˜—J˜"J˜"J˜&J˜&JšœŸ˜J˜—š¡ œžœžœžœ˜;Jšœ˜J˜šž˜šžœžœžœ˜Jš žœžœžœžœžœ˜Jšžœžœžœ˜J˜—Jš žœžœžœžœžœžœ˜$Jš žœžœžœžœžœ˜5J˜ J˜ Jšžœ˜—JšœŸ˜J˜—š¡œžœ+žœ™NJšœŸ™—J˜š¡œžœžœžœ™GJšœ žœ™Jšœžœ™ Jšœžœ™šžœ8žœžœž™MJšœ™Jš œžœžœžœžœ™6šžœ0žœžœž™DJšžœžœžœ™ Jšœ7™7Jšœžœžœžœ™6Jš œžœžœžœžœ™:Jšžœ™—J™Jšžœ™—Jšœ.™.JšœŸ™—J˜š¡œžœžœžœžœžœžœ ˜lJ™.Jšœžœ˜ Jšœ)˜)Jšžœžœžœ˜!JšœŸ˜—J˜š¡œžœžœžœžœžœžœ ˜tJ™.Jšœžœ˜ Jšœ+˜+Jšžœžœžœ˜"JšœŸ˜——J˜J˜šžœ˜J˜1——…—>æbl