unit CADEngine;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Graphics, ShlepsUtils, TPG, Math, Process, Pipes;

Type
  TVCurl=Class(TObject)
    Name:String;
    Shell:TShell;
    DisplayList:TStringList;
    Saveable:Boolean;
    OffScreen:Boolean;
    TooSmall:Boolean;
    Constructor Create;
    Destructor Destroy;
  End;

  // порты у объекта
  // порты у коннектора
  // начало конец [надпись надпись....] [излом излом излом....]

  TVPort=Class(TObject)
    Name:String;
    AR:String;
    X,Y,AX,AY:Extended;
    Connection:String;
    ConnectionPort:String;
  end;

  TVTemplate=Class(TObject)
    Name:String;
    DisplayList:TStringList;
    Shell:TShell;
    OType:Longint; // 0 - just object, 1 - textout, 2 - binary link
    PortList:TStringList;
    Constructor Create;
    Destructor Destroy;
  End;

  TVObject=Class(TOBject)
    Name:String;
    Style:String;
    X,Y,A,S,X2,Y2,W,H:Extended;
    Color,BGColor,Width,Template:String;
    Value:Extended;
    Source:String;
    DisplayList:TStringList;
    Saveable:Boolean;
    OffScreen:Boolean;
    TooSmall:Boolean;
    Shell:TShell;
    PortList:TStringList;
    NonPortConnectionList:TStringList;
    GroupList:TStringList;
    RelatedCAList:TStringList;   // dynamic, updated in unit1.CalloutGUI2Scene
                                 // CreateRelCASpeedButtonClick LoadDocument
    ActionList:TStringList;
    Constructor Create;
    Destructor Destroy;
  End;

  TVConnectionPort=Class(TObject)
    PortType:String; // Start "End" Text Point
    AR:String;
    X,Y:Extended;AX,AY:Extended;
    ConnectedObject:String;
    ConnectedPort:String;
  end;

  TVConnection=Class(TObject)
    Name:String;
    Style:String;
    StartObj,EndObj:String;
    StartPort,EndPort:String;
    StartStyle,EndStyle:String;
    Color,BGColor,Width:String;
    DisplayList:TStringList;
    Value:Extended;
    Saveable:Boolean;
    OffScreen:Boolean;
    TooSmall:Boolean;
    Shell:TShell;
    OType:Longint; // 0 - just object, 1 - textout, 2 - binary link
    PortList:TStringList;   // to be deleted?
    GroupList:TStringList;
    Constructor Create;
    Destructor Destroy;
  End;

  TVCallout=Class(TObject)
    Name:String;
    Style:String;
    X,Y,W,H:Extended;
    Content:String;
    LineStyle,Color,BGColor,Width,Transparent:String;
    FontName,FontHeight,FontColor,BIUS:String;
    BindMode,RelatedObj:String;
    Source:String;
    Text:String;
    DisplayList:TStringList;
    Saveable:Boolean;
    OffScreen:Boolean;
    TooSmall:Boolean;
    Shell:TShell;
    PortList:TStringList;
    GroupList:TStringList;
    ActionList:TStringList;
    Constructor Create;
    Destructor Destroy;
  End;

  TVGroup=Class(TObject)
    Name:String;
    Glue:Boolean;            //group members moved together
    Active:Boolean;          //group members respond to mouse
    Visible:Boolean;         //layer-like groups
    DisplayList:TStringList; //group is just list of Objects Connection and Callouts
                             //Curls are not allowed.
                             //No Display commands here - just names of items
                             //dynamic, LoadFile
    Constructor Create;
    Destructor Destroy;
  End;

  TVSelHandle=Class(TObject)
    Name:String;
    Style:String;
    Xw,Yw:Extended;
    X,Y:Longint;
    //LT MT RT
    //LM MM RM
    //LB MB RB

    OffScreen:Boolean;
    TooSmall:Boolean;
    Shell:TShell;
  End;

  { TVScene }

  TVScene=Class(TObject)
    ShowObjectNames:Boolean;
    ShowPortNames:Boolean;
    ShowShells:Boolean;
    SelHandleSize:Longint;
    PortSize:Longint;
    VObjectList:TStringList; // List of Objects
    VGroupList:TStringList; // List of groups of display commands - group is that moved together
    VTemplateList:TStringList; // List of models - what's the difference between curl and template???
    VCurlList:TStringList; // List of uncompleted objects
    VConnectionList:TStringList; // List of connections between objects
    VCalloutList:TStringList; // List of text comments for objects
    VHandleList:TStringList; // List of handles for selected items
    View:TStringList; // Master display list used for visualization
                      // sequence of drawing of curls and objects
    VisibleList:TStringList;
    SelectedList:TStringList;
    Drawer:TDrawer;
    Clip:Boolean;
    ClipMax:Boolean;
    ClipSize:Longint;
    MaxDisplayedDepth:Longint;
    ScreenShell:TShell;
    ActiveGroup:TVGroup;
    Constructor Create;
    Destructor Destroy;
    Procedure SaveFile(FileName:String);
    Procedure LoadFile(Var FileContent:TStringList);
    Procedure LoadOldFile(FileName:string);
    Procedure UpdateVisibility;
    Procedure Clear;
    Procedure ExecCommand(Command:String;Acc:TAcceleratedCommand;Depth:Longint);
    Function GetTypeByName(Name:String):String;
    Function GetNewName(List:TStringList;Prefix:String):String;

    Function RenderShell(CommandList:TStrings;M:TMatrix2D):TShell;
    Procedure RenderObjectShell(Obj:TVObject);

    Procedure ObjectAttr2Style(Obj:TVObject);
    Procedure ObjectStyle2Attr(Obj:TVObject);
    Procedure UpdateObjectDisplayList(Obj:TVObject);
    Procedure UpdateObjectPorts(Obj:TVObject);
    Procedure MoveObjectTo(Obj:TVObject;X,Y:Extended;GroupList:String);

    Procedure CalloutAttr2Style(Callout:TVCallout);
    Procedure CalloutStyle2Attr(Callout:TVCallout);
    Procedure UpdateCalloutDisplayList(Callout:TVCallout);
    Procedure MoveCalloutTo(Callout:TVCallout;X,Y:Extended;GroupList:String);

    Procedure DeleteObject(Obj:TVObject);
    Procedure DeleteCallout(Callout:TVCallout);
    Procedure DeleteConnection(Connection:TVConnection);

    Procedure ConnectionAttr2Style(Conn:TVConnection);
    Procedure ConnectionStyle2Attr(Conn:TVConnection);
    Procedure UpdateConnectionDisplayList(Connection:TVConnection);

    Function SaveCurl(VCurl:TVCurl):TStringList;
    Function LoadCurl(Base:Longint;Add:Boolean;Var FileContent:TStringList):TVCurl;
    Function SaveGroup(VGroup:TVGroup):TStringList;
    Function LoadGroup(Base:Longint;Add:Boolean;DX,DY:Extended;Var FileContent,RenList:TStringList):TVGroup;
    Function SaveObject(VObject:TVObject):TStringList;
    Function LoadObject(Base:Longint;Add:Boolean;DX,DY:Extended;Var FileContent,RenList:TStringList):TVObject;
    Function SaveConnection(VConnection:TVConnection):TStringList;
    Function LoadConnection(Base:Longint;Add:Boolean;DX,DY:Extended;Var FileContent,RenList:TStringList):TVConnection;
    Function SaveCallout(VCallout:TVCallout):TStringList;
    Function LoadCallout(Base:Longint;Add:Boolean;DX,DY:Extended;Var FileContent,RenList:TStringList):TVCallout;
    Function SaveItem(ItemName:String):TStringList;
    Procedure ExportCommands(Var Buf:TStringList);
    Function BuildUnderMouse(X,Y:Extended;SamePlaceZone:Longint):TStringList;
    Procedure CreateSelHandles(ItemName:String);
    Procedure DeleteSelHandles(ItemName:String);
    Procedure UpdateSelHandles(ItemName:String);

    Procedure CreateGroup(GroupName:String);
    Procedure DeleteGroup(GroupName:String);
    Procedure EraseGroup(GroupName:String);
    Procedure SetActiveGroup(GroupName:String);
    Procedure AddItemToGroup(GroupName,ItemName:String);
    Procedure DelItemFromGroup(GroupName,ItemName:String);
  End;

Var
  ICommands:TStringList;
//  StrToLinesBuffer:TStringList;

Procedure InitModule;
Function ExecCommand(Command:String):String;
Function ShellsAreCrossed(Shell1,Shell2:TShell):Boolean;
Function InfiniteShell(Shell:TShell):Boolean;


implementation
Uses Dispatcher;

Function GetResponce(CLI:String;Var ErrCode:Longint):String;
Var I,Avail:Longint;
  Process:TProcess; ProcessOut:TInputPipeStream;
  CB:String;b,pb,Buf,RequestedOP,AttributeName,AttributeType:String;
Begin
    //    StopWaitingResponce:=False;
        Process:=TProcess.Create(Nil);
        Process.Options:=[poUsePipes,poNoConsole,poStderrToOutPut];
        Process.ShowWindow:=swoShow;
        Process.CommandLine:=CLI;
        Try
          Buf:='';
          Process.Execute;
          ProcessOut:=Process.Output;
          Repeat
            Avail:=ProcessOut.NumBytesAvailable;
            If Avail>0 Then Begin
              If Avail>254 Then Repeat
                SetLength(cb,254);
                ProcessOut.Read(CB[1],254);
                Buf:=Buf+CB;
                Avail:=Avail-254;
              Until Avail<=254;
              SetLength(CB,Avail);
              ProcessOut.Read(CB[1],Avail);
              Buf:=Buf+CB;
            End;
         //   Application.ProcessMessages;
          Until ((Not Process.Active) And (ProcessOut.NumBytesAvailable=0)) {Or (StopWaitingResponce)};
        Except LogMessage(0,'Error running '+CLI);End;
        Process.CloseOutput;
    {    If StopWaitingResponce Then Begin
          Result:='cancelled';
          ErrCode:=-1;
        End Else Begin}
          Result:=(Buf);
          ErrCode:=Process.ExitCode;
{        end;}
        Process.Free;
End;
{ Scene.Add('bg:group');
  Scene.Add('1:group');
  Scene.Add('2:group');
  Scene.Add('ld130:vobject');

  Vobject.create(ld130,displaylist=u20)
}

Function TVScene.GetTypeByName(Name:String):String;
Var I:Longint;
Begin
  If VCurlList.IndexOf(Name)>=0 Then Begin Result:='curl';Exit;End;
  If VObjectList.IndexOf(Name)>=0 Then Begin Result:='object';Exit;End;
  If VConnectionList.IndexOf(Name)>=0 Then Begin Result:='connection';Exit;End;
  If VCalloutList.IndexOf(Name)>=0 Then Begin Result:='callout';Exit;End;
  If VTemplateList.IndexOf(Name)>=0 Then Begin Result:='template';Exit;End;
  If VGroupList.IndexOf(Name)>=0 Then Begin Result:='group';Exit;End;
  Result:='none';
End;

Function TVScene.GetNewName(List:TStringList;Prefix:String):String;
Var CName:String;Numerator:Longint;
Begin
  Numerator:=List.Count;
  Repeat
    If List=VCurlList Then CName:=Prefix+'Curl'+IntToStr(Numerator);
    If List=VObjectList Then CName:=Prefix+'Object'+IntToStr(Numerator);
    If List=VConnectionList Then CName:=Prefix+'Connection'+IntToStr(Numerator);
    If List=VTemplateList Then CName:=Prefix+'Template'+IntToStr(Numerator);
    If List=VGroupList Then CName:=Prefix+'Group'+IntToStr(Numerator);
    If List=VCalloutList Then CName:=Prefix+'Callout'+IntToStr(Numerator);
    Inc(Numerator);
  Until (VCurlList.IndexOf(CName)<0)And
        (VObjectList.IndexOf(CName)<0)And
        (VConnectionList.IndexOf(CName)<0)And
        (VTemplateList.IndexOf(CName)<0)And
        (VGroupList.IndexOf(CName)<0)And
        (VCalloutList.IndexOf(CName)<0);
  Result:=CName;
  // старый именователь
  // соотвестствующий список пуст - начинаем с 0
  // иначе - берем имя последнего и прибавляем 1
  // не учитывает переименование в нестандартные имена
{  If Scene.VCurlList.Count=0 Then CName:='curl0'
  Else CName:=((Scene.VCurlList.Objects[Scene.VCurlList.Count-1]) As TVCurl).Name;
  TI:=IntToStr(StrToInt(Copy(CName,5,Length(CName)-4))+1);
  Scene.VCurlList.AddObject('curl'+TI,Curl);
  Curl.Name:='curl'+TI;}
End;

{
Procedure TVScene.RenderConnDisplayList(Connection:TVConnection);
//по имени или по хэндлу?
Var I:Longint;
    AccObject:TAccObject;
    Color:String;
    Width:String;
    LineStyle:String;
Begin
// Convert  Connection.Style to Color Width Style;
{   AccObject:=TAccObject.Create;
   AccObject.AcceleratedCommand:=AccelerateCommand('SetColor '+Color);
   Connection.DisplayList.AddObject('SetColor '+ColorToString(BorderColorButton.ButtonColor),AccObject);
   AccObject:=TAccObject.Create;
   AccObject.AcceleratedCommand:=AccelerateCommand('SetBGColor '+ColorToString(BGColorButton.ButtonColor));
   Connection.DisplayList.AddObject('SetBGColor '+ColorToString(BGColorButton.ButtonColor),AccObject);
   AccObject:=TAccObject.Create;
   AccObject.AcceleratedCommand:=AccelerateCommand('SetWidth '+IntToStr(WidthButton1.Tag));
   Connection.DisplayList.AddObject('SetWidth 2',AccObject);
   AccObject:=TAccObject.Create;

   NonAccCommand:='MoveTo '+FloatToStr(Xw1)+','+FloatToStr(Yw1);
   AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
   Connection.DisplayList.AddObject(NonAccCommand,AccObject);
   AccObject:=TAccObject.Create;
   NonAccCommand:='LineTo '+FloatToStr(Xw2)+','+FloatToStr(Yw2);


   AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
   Connection.DisplayList.AddObject(NonAccCommand,AccObject);
   E:=GenShift(0,0);
   Connection.Shell:=RenderShell(Scene,Connection.DisplayList,E);
 }
End;
 }

 Function ShellsAreCrossed(Shell1,Shell2:TShell):Boolean;
 Var S1Xmin,S1XMax,S1Ymin,S1Ymax:Extended;
     S2Xmin,S2XMax,S2Ymin,S2Ymax:Extended;
     XpCross,YpCross:Boolean;
 Begin
   S1Xmin:=Min(Shell1.X1,Shell1.X2);
   S1XMax:=Max(Shell1.X1,Shell1.X2);
   S1Ymin:=Min(Shell1.Y1,Shell1.Y2);
   S1Ymax:=Max(Shell1.Y1,Shell1.Y2);
   S2Xmin:=Min(Shell2.X1,Shell2.X2);
   S2XMax:=Max(Shell2.X1,Shell2.X2);
   S2Ymin:=Min(Shell2.Y1,Shell2.Y2);
   S2Ymax:=Max(Shell2.Y1,Shell2.Y2);
   XPCross:=((S1Xmin<=S2Xmin) And (S2Xmin<=S1Xmax)) Or ((S2Xmin<=S1Xmin) And (S1Xmin<=S2Xmax));
   YPCross:=((S1Ymin<=S2Ymin) And (S2Ymin<=S1Ymax)) Or ((S2Ymin<=S1Ymin) And (S1Ymin<=S2Ymax));
   Result:=XPCross And YpCross;
 end;

 Function InfiniteShell(Shell:TShell):Boolean;
 Begin
   Result:=
    (Shell.X1=Math.maxExtended) Or
    (Shell.X1=-Math.maxExtended+Math.minExtended) Or
    (Shell.Y1=Math.maxExtended) Or
    (Shell.Y1=-Math.maxExtended+Math.minExtended) Or
    (Shell.X2=Math.maxExtended) Or
    (Shell.X2=-Math.maxExtended+Math.minExtended) Or
    (Shell.Y2=Math.maxExtended) Or
    (Shell.Y2=-Math.maxExtended+Math.minExtended);
 end;

Function TVScene.RenderShell(CommandList:TStrings;M:TMatrix2D):TShell;
Var Cmd,Buf,ParsedBuf,fname:String;
    FBuf:TStringList;
    Color:TColor;
    Xs,Ys,Rs:Longint;xws,yws,Rws:String;
    X2s,Y2s:Longint;x2ws,y2ws:String;
    DisplayList:TStringList;
    Angle:Extended;AngleS:String;
    PicScale:Extended;PicScaleS:String;
    Xw,Yw,Rw:Extended;
    X2w,Y2w:Extended;
    R1,R2:TRect;BM:TBitmap;
    SubDrawer:TDrawer;
    I:Longint;Sm,Rm,Scm,tmpm:TMatrix2D;
    FontFace,FontSize,FontColor,BIUS:String;
    TemplateDisplayList:TStringList;
    ShellX1,ShellX2,ShellY1,ShellY2:Extended;
    SubShell:TShell;
    K,N:Longint;E:TMatrix2D;
    p1,p2:TPoint2D;
Begin
  ShellX1:=Math.maxExtended;
  ShellX2:=-Math.maxExtended;//+Math.minExtended;
  ShellY1:=Math.MaxExtended;
  ShellY2:=-Math.maxExtended;//+Math.minExtended;
  E:=GenShift(0,0);
  If CommandList.Count>0 Then
    For K:=0 To CommandList.Count-1 Do Begin
      Buf:=CommandList[K];
      Cmd:=GetNextToken(Buf,ParsedBuf,' ');
      Buf:=ParsedBuf;
      Case LowerCase(Cmd) Of
      'ellipse','rect','roundrect','textrect','bmp','subpicture': Begin
               Xws:=GetNextToken(Buf,ParsedBuf,',');
               Buf:=ParsedBuf;
               Yws:=GetNextToken(Buf,ParsedBuf,',');
               Buf:=ParsedBuf;
               X2ws:=GetNextToken(Buf,ParsedBuf,',');
               Buf:=ParsedBuf;
               Y2ws:=GetNextToken(Buf,ParsedBuf,',');
               Xw:=StrToFloat(Xws);Yw:=StrToFloat(Yws);
               P1[1]:=Xw;P1[2]:=Yw;P1[3]:=1;
               P2:=MulPoint2D(P1,M);
               If P2[1]<ShellX1 Then ShellX1:=P2[1];
               If P2[1]>ShellX2 Then ShellX2:=P2[1];
               If P2[2]<ShellY1 Then ShellY1:=P2[2];
               If P2[2]>ShellY2 Then ShellY2:=P2[2];
               X2w:=StrToFloat(X2ws);Y2w:=StrToFloat(Y2ws);
               P1[1]:=X2w;P1[2]:=Y2w;P1[3]:=1;
               P2:=MulPoint2D(P1,M);
               If P2[1]<ShellX1 Then ShellX1:=P2[1];
               If P2[1]>ShellX2 Then ShellX2:=P2[1];
               If P2[2]<ShellY1 Then ShellY1:=P2[2];
               If P2[2]>ShellY2 Then ShellY2:=P2[2];
             End;
      'lineto','moveto': Begin
               Xws:=GetNextToken(Buf,ParsedBuf,',');
               Yws:=ParsedBuf;
               Xw:=StrToFloat(Xws);Yw:=StrToFloat(Yws);
               P1[1]:=Xw;P1[2]:=Yw;P1[3]:=1;
               P2:=MulPoint2D(P1,M);
               If P2[1]<ShellX1 Then ShellX1:=P2[1];
               If P2[1]>ShellX2 Then ShellX2:=P2[1];
               If P2[2]<ShellY1 Then ShellY1:=P2[2];
               If P2[2]>ShellY2 Then ShellY2:=P2[2];
             End;
      'circle': Begin
               Xws:=GetNextToken(Buf,ParsedBuf,',');
               Buf:=ParsedBuf;
               Yws:=GetNextToken(Buf,ParsedBuf,',');
               Rws:=ParsedBuf;
               Xw:=StrToFloat(Xws);Yw:=StrToFloat(Yws);
               Rw:=StrToFloat(Rws);
               P1[1]:=Xw;P1[2]:=Yw;P1[3]:=1;
               P2:=MulPoint2D(P1,M);
               //probably wrong cause R was not scaled
               If (P2[1]-Rw)<ShellX1 Then ShellX1:=(P2[1]-Rw);
               If (P2[1]+Rw)>ShellX2 Then ShellX2:=(P2[1]+Rw);
               If (P2[2]-Rw)<ShellY1 Then ShellY1:=(P2[2]-Rw);
               If (P2[2]+Rw)>ShellY2 Then ShellY2:=(P2[2]+Rw);
             end;
      'drawtemplate': Begin
               FName:=GetNextToken(Buf,ParsedBuf,' ');
               Buf:=ParsedBuf;
               Xws:=GetNextToken(Buf,ParsedBuf,',');
               Buf:=ParsedBuf;
               Yws:=GetNextToken(Buf,ParsedBuf,',');
               Buf:=ParsedBuf;
               AngleS:=GetNextToken(Buf,ParsedBuf,',');
               PicScaleS:=ParsedBuf;
               Xw:=StrToFloat(Xws);Yw:=StrToFloat(Yws);
               Angle:=StrToFloat(AngleS);
               PicScale:=StrToFloat(PicScaleS);
               Sm:=GenShift(Xw,Yw);
               Rm:=GenRotate(xw,yw,Angle/180*pi);
               Scm:=GenScale(xw,yw,PicScale,PicScale);
               tmpm:=MulMat2D(Sm,Rm);
               tmpm:=MulMat2D(Tmpm,Scm);
               tmpm:=MulMat2D(Tmpm,M);
               N:=VTemplateList.IndexOf(FName);
               If N>=0 Then Begin
                 TemplateDisplayList:=(VTemplateList.Objects[N] As TVTemplate).DisplayList;
                 SubShell:=RenderShell(TemplateDisplayList,tmpm);
                 If SubShell.X1<ShellX1 Then ShellX1:=SubShell.X1;
                 If SubShell.X2>ShellX2 Then ShellX2:=SubShell.X2;
                 If SubShell.Y1<ShellY1 Then ShellY1:=SubShell.Y1;
                 If SubShell.Y2>ShellY2 Then ShellY2:=SubShell.Y2;
               end;
             End;
      //'unscaled' не должны учитываться при расчёте оболочки, чтении и сохранении дисплейных списков
    end;
  end;
  Result.X1:=ShellX1;
  Result.X2:=ShellX2;
  Result.Y1:=ShellY1;
  Result.Y2:=ShellY2;
end;

Procedure TVScene.RenderObjectShell(Obj:TVObject);
Var Sm,Rm,Scm,tmpm,TransM:TMatrix2D;
Begin

  If Obj.A<700 Then Begin
    Sm:=GenShift(Obj.X,Obj.Y);
    Rm:=GenRotate(Obj.X,Obj.Y,Obj.A/180*pi);
    Scm:=GenScale(Obj.X,Obj.Y,Obj.S,Obj.S);
  end Else Begin
   { If Obj.A=(720+4*360) Then Begin
      Sm:=GenShift(Obj.X,Obj.Y);
      Rm:=GenRotate(Obj.X,Obj.Y,0);
      Scm:=GenScale(Obj.X,Obj.Y,1,1);
    End Else Begin}
      Sm:=GenShift(0,0);
      Rm:=GenRotate(0,0,0);
      Scm:=GenScale(0,0,1,1);
{    end;}
  End;
  tmpm:=MulMat2D(Sm,Rm);
  TransM:=MulMat2D(Tmpm,Scm);
  Obj.Shell:=RenderShell(Obj.DisplayList,TransM);
 End;

Procedure TVScene.ConnectionAttr2Style(Conn:TVConnection);
Begin
    Conn.Style:=' Color='+Conn.Color+
                ' BGColor='+Conn.BGColor+
                ' Width='+Conn.Width+
                ' StartObj='+Conn.StartObj+
                ' EndObj='+Conn.EndObj;
    If (Conn.StartStyle<>'flat') And (Conn.StartStyle<>'') Then Conn.Style:=Conn.Style+' StartStyle='+Conn.StartStyle;
    If (Conn.EndStyle<>'flat') And (Conn.EndStyle<>'') Then Conn.Style:=Conn.Style+' EndStyle='+Conn.EndStyle;
    If Conn.Value<>0 Then Conn.Style:=Conn.Style+' Value='+FloatToStr(Conn.Value);
    If Conn.StartPort<>'' Then Conn.Style:=Conn.Style+' StartPort='+Conn.StartPort;
    If Conn.EndPort<>'' Then Conn.Style:=Conn.Style+' EndPort='+Conn.EndPort;
End;

Procedure TVScene.ConnectionStyle2Attr(Conn:TVConnection);
Var Xw1,Yw1,Xw2,Yw2,W,H:Extended;
   Color,BGColor,Width,Vals:String;
Begin
    Conn.Color:=GetValByName(Conn.Style,'Color');
    If Conn.Color='' Then Conn.Color:='clBlack';
    Conn.BGColor:=GetValByName(Conn.Style,'BGColor');
    If Conn.BGColor='' Then Conn.BGColor:='clNone';
    Conn.Width:=GetValByName(Conn.Style,'Width');
    If Conn.Width='' Then Conn.Width:='1';
    Conn.StartObj:=GetValByName(Conn.Style,'StartObj');
    Conn.EndObj:=GetValByName(Conn.Style,'EndObj');
    Conn.StartPort:=GetValByName(Conn.Style,'StartPort');
    Conn.EndPort:=GetValByName(Conn.Style,'EndPort');
    Conn.StartStyle:=GetValByName(Conn.Style,'StartStyle');
    Conn.EndStyle:=GetValByName(Conn.Style,'EndStyle');
    Vals:=GetValByName(Conn.Style,'Value');
    If Vals='' Then Vals:='0';
    TryStrToFloat(Vals,Conn.Value);
End;


Procedure TVScene.UpdateConnectionDisplayList(Connection:TVConnection);
Var I,J,Index,BackCount,ForwardCount:Longint;
    AccObject:TAccObject;
    NonAccCommand:String;
    ConnPort,ConnPort2,ConnPort3:TVConnectionPort;
    Obj:TVObject; Port:TVPort;
    E:TMatrix2D;
    X,Y,A,L:Extended;
Begin
  If Connection.DisplayList.Count>0 Then
    For I:=Connection.DisplayList.Count-1 DownTo 0 Do Begin
      AccObject:=Connection.DisplayList.Objects[I] As TAccObject;
      AccObject.Free;
    End;
  Connection.DisplayList.Clear;
  NonAccCommand:='SetColor '+Connection.Color;
  AccObject:=TAccObject.Create;
  AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
  Connection.DisplayList.AddObject(NonAccCommand,AccObject);
  NonAccCommand:='SetBGColor '+Connection.BGColor;
  AccObject:=TAccObject.Create;
  AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
  Connection.DisplayList.AddObject(NonAccCommand,AccObject);
  NonAccCommand:='SetWidth '+Connection.Width;
  AccObject:=TAccObject.Create;
  AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
  Connection.DisplayList.AddObject(NonAccCommand,AccObject);
  If Connection.PortList.Count=0 Then Exit;
  For I:=0 To Connection.PortList.Count-1 Do Begin
    ConnPort:=Connection.PortList.Objects[I] As TVConnectionPort;
    If ConnPort.PortType='Start' Then Begin
      Index:=VObjectList.IndexOf(ConnPort.ConnectedObject);
      If Index<0 Then Exit;
      Obj:=VObjectList.Objects[Index] As TVObject;
      If ConnPort.ConnectedPort='' Then Begin
        ConnPort.AX:=Obj.X;
        ConnPort.AY:=Obj.Y;
      end Else Begin
        Index:=Obj.PortList.IndexOf(ConnPort.ConnectedPort);
        If Index<0 Then Exit;
        Port:=Obj.PortList.Objects[Index] As TVPort;

        ConnPort.AX:=Port.AX;
        ConnPort.AY:=Port.AY;
      end;
      Case Connection.StartStyle of
      'box','circle','cross','plus': Begin
                 NonAccCommand:='UnScaled '+FloatToStr(ConnPort.AX)+','+FloatToStr(ConnPort.AY)+','+Connection.StartStyle+',5';
                 AccObject:=TAccObject.Create;
                 AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
                 Connection.DisplayList.AddObject(NonAccCommand,AccObject);
               End;
      'arrow': Begin
              J:=I;
              Repeat
                Inc(J);
                ConnPort2:=Connection.PortList.Objects[J] As TVConnectionPort;
              Until (ConnPort2.PortType='Node') Or (ConnPort2.PortType='End') Or (J>=Connection.PortList.Count);
       //       If ConnPort.AY=ConnPort2.AY Then Begin
       //       End Else Begin
                A:=ArcTan2((ConnPort.AY-ConnPort2.AY),(ConnPort.AX-ConnPort2.AX));
                L:=Sqrt((ConnPort.AX-ConnPort2.AX)*(ConnPort.AX-ConnPort2.AX)+
                        (ConnPort.AY-ConnPort2.AY)*(ConnPort.AY-ConnPort2.AY));
                X:=ConnPort.AX-L*Cos(A+15/180*Pi)/10;
                Y:=ConnPort.AY-L*Sin(A+15/180*Pi)/10;
         //     end;
              NonAccCommand:='MoveTo '+FloatToStr(X)+','+FloatToStr(Y);
              AccObject:=TAccObject.Create;
              AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
              Connection.DisplayList.AddObject(NonAccCommand,AccObject);
              NonAccCommand:='LineTo '+FloatToStr(ConnPort.AX)+','+FloatToStr(ConnPort.AY);
              AccObject:=TAccObject.Create;
              AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
              Connection.DisplayList.AddObject(NonAccCommand,AccObject);
              X:=ConnPort.AX-L*Cos(A-15/180*Pi)/10;
              Y:=ConnPort.AY-L*Sin(A-15/180*Pi)/10;
              NonAccCommand:='LineTo '+FloatToStr(X)+','+FloatToStr(Y);
              AccObject:=TAccObject.Create;
              AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
              Connection.DisplayList.AddObject(NonAccCommand,AccObject);
                     End;
      End;
   //   NonAccCommand:='MoveTo '+FloatToStr(Port.AX)+','+FloatToStr(Port.AY);
      AccObject:=TAccObject.Create;
      If ConnPort.ConnectedPort='' Then Begin
        NonAccCommand:='MoveTo '+FloatToStr(Obj.X)+','+FloatToStr(Obj.Y);
        ConnPort.AX:=Obj.X;
        ConnPort.AY:=Obj.Y;
      end Else Begin
        Port:=Obj.PortList.Objects[Obj.PortList.IndexOf(ConnPort.ConnectedPort)] As TVPort;
        NonAccCommand:='MoveTo '+FloatToStr(Port.AX)+','+FloatToStr(Port.AY);
        ConnPort.AX:=Port.AX;
        ConnPort.AY:=Port.AY;
      end;
      AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
      Connection.DisplayList.AddObject(NonAccCommand,AccObject);
    End;
    //относительные узлы относительно оболочки или начала-конца?
    If ConnPort.PortType='Node' Then Begin
      If ConnPort.AR='R' Then Begin
        ConnPort.AX:=ConnPort.X*(Connection.Shell.X2-Connection.Shell.X1)+Connection.Shell.X1;
        ConnPort.AY:=ConnPort.Y*(Connection.Shell.Y2-Connection.Shell.Y1)+Connection.Shell.Y1;
      end;
      AccObject:=TAccObject.Create;
      NonAccCommand:='LineTo '+FloatToStr(ConnPort.AX)+','+FloatToStr(ConnPort.AY);
      AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
      Connection.DisplayList.AddObject(NonAccCommand,AccObject);
    End;
    //правильнее текстовые порты размещать после остальных
    If ConnPort.PortType='Text' Then Begin
      If ConnPort.AR='R' Then Begin
        //порт с надписью не может быть первым или последним, иначе выход за границы диапазона
        BackCount:=0;ForwardCount:=0;
        Repeat
          Inc(BackCount);
          ConnPort2:=Connection.PortList.Objects[I-BackCount] As TVConnectionPort;
        Until (ConnPort2.PortType='Start')Or(ConnPort2.PortType='Node');
        Repeat
          Inc(ForwardCount);
          ConnPort3:=Connection.PortList.Objects[I+ForwardCount] As TVConnectionPort;
        Until (ConnPort3.PortType='End')Or(ConnPort3.PortType='Node');
        ConnPort.AX:=ConnPort2.AX+(ConnPort3.AX-ConnPort2.AX)*BackCount/(BackCount+ForwardCount);
        ConnPort.AY:=ConnPort2.AY+(ConnPort3.AY-ConnPort2.AY)*BackCount/(BackCount+ForwardCount);
      end;
      If ConnPort.ConnectedPort='' Then Begin
        //размер по умолчанию
        AccObject:=TAccObject.Create;
        NonAccCommand:='SetFont default,10,clblack,';
        AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
        Connection.DisplayList.AddObject(NonAccCommand,AccObject);
        AccObject:=TAccObject.Create;
        NonAccCommand:='TextOut '+FloatToStr(ConnPort.AX)+','+FloatToStr(ConnPort.AY)+','+ConnPort.ConnectedObject;
        AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
        Connection.DisplayList.AddObject(NonAccCommand,AccObject);
      End Else Begin
        //здесь дожно быть применение форматирования, сохранённого в поле
        AccObject:=TAccObject.Create;
        NonAccCommand:='SetScaledFont default,10,1,clblack,';
        AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
        Connection.DisplayList.AddObject(NonAccCommand,AccObject);
        AccObject:=TAccObject.Create;
        NonAccCommand:='TextRect '+FloatToStr(ConnPort.AX)+','+FloatToStr(ConnPort.AY)+',0,0,'+ConnPort.ConnectedObject;
  //      Canvas.TextRect(Rect(Xs,Ys,Xs+Canvas.GetTextWidth(FName),Ys+Canvas.GetTextHeight(FName)),Xs,Ys,FName,Canvas.TextStyle);
        AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
        Connection.DisplayList.AddObject(NonAccCommand,AccObject);
      End;
    End;

    If ConnPort.PortType='End' Then Begin
      Index:=VObjectList.IndexOf(ConnPort.ConnectedObject);
      If Index<0 Then Exit;
      Obj:=VObjectList.Objects[Index] As TVObject;
      AccObject:=TAccObject.Create;
      If ConnPort.ConnectedPort='' Then Begin
        ConnPort.AX:=Obj.X;
        ConnPort.AY:=Obj.Y;
      end Else Begin
        Index:=Obj.PortList.IndexOf(ConnPort.ConnectedPort);
        If Index<0 Then Begin LogMessage(10,'update conn displayList: Port not Found '+ConnPort.ConnectedPort);exit;End;
        Port:=Obj.PortList.Objects[Index] As TVPort;
        ConnPort.AX:=Port.AX;
        ConnPort.AY:=Port.AY;
      End;
      NonAccCommand:='LineTo '+FloatToStr(ConnPort.AX)+','+FloatToStr(ConnPort.AY);
      AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
      Connection.DisplayList.AddObject(NonAccCommand,AccObject);
      Case Connection.EndStyle of
      'box','circle','cross','plus': Begin
               NonAccCommand:='UnScaled '+FloatToStr(ConnPort.AX)+','+FloatToStr(ConnPort.AY)+','+Connection.EndStyle+',5';
               AccObject:=TAccObject.Create;
               AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
               Connection.DisplayList.AddObject(NonAccCommand,AccObject);
             End;
      'arrow': Begin
        J:=I;
        Repeat
          Dec(J);
          ConnPort2:=Connection.PortList.Objects[J] As TVConnectionPort;
        Until (ConnPort2.PortType='Node') Or (ConnPort2.PortType='Start')Or(J<=0);
   //     If ConnPort.AY=ConnPort2.AY Then Begin
   //     End Else Begin
          A:=ArcTan2((ConnPort.AY-ConnPort2.AY),(ConnPort.AX-ConnPort2.AX));
          L:=Sqrt((ConnPort.AX-ConnPort2.AX)*(ConnPort.AX-ConnPort2.AX)+
                  (ConnPort.AY-ConnPort2.AY)*(ConnPort.AY-ConnPort2.AY));
          X:=ConnPort.AX-L*Cos(A+15/180*Pi)/10;
          Y:=ConnPort.AY-L*Sin(A+15/180*Pi)/10;
  //      end;
        NonAccCommand:='MoveTo '+FloatToStr(X)+','+FloatToStr(Y);
        AccObject:=TAccObject.Create;
        AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
        Connection.DisplayList.AddObject(NonAccCommand,AccObject);
        NonAccCommand:='LineTo '+FloatToStr(ConnPort.AX)+','+FloatToStr(ConnPort.AY);
        AccObject:=TAccObject.Create;
        AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
        Connection.DisplayList.AddObject(NonAccCommand,AccObject);
        X:=ConnPort.AX-L*Cos(A-15/180*Pi)/10;
        Y:=ConnPort.AY-L*Sin(A-15/180*Pi)/10;
        NonAccCommand:='LineTo '+FloatToStr(X)+','+FloatToStr(Y);
        AccObject:=TAccObject.Create;
        AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
        Connection.DisplayList.AddObject(NonAccCommand,AccObject);
               End;
      End;
    End;
  End;
  E:=GenShift(0,0);
  Connection.Shell:=RenderShell(Connection.DisplayList,E);
end;


Procedure TVScene.ObjectAttr2Style(Obj:TVObject);
Begin
    Obj.Style:=
    'X='+FloatToStr(Obj.X)+
    ' Y='+FloatToStr(Obj.Y)+
    ' A='+FloatToStr(Obj.A)+
    ' S='+FloatToStr(Obj.S);
    If Obj.Template<>'' Then Obj.Style:=Obj.Style+' Template='+Obj.Template;
    If Obj.A>=720 Then
      Obj.Style:=Obj.Style+' Color='+Obj.Color+
                           ' BGColor='+Obj.BGColor+
                           ' Width='+Obj.Width+
                           ' W='+FloatToStr(Obj.W)+
                           ' H='+FloatToStr(Obj.H);
    If Obj.Source<>'' Then Obj.Style:=Obj.Style+' Source="'+Obj.Source+'"';
    If Obj.Value<>0 Then Obj.Style:=Obj.Style+' Value='+FloatToStr(Obj.Value);
End;

Procedure TVScene.ObjectStyle2Attr(Obj:TVObject);
Var Xw1,Yw1,Xw2,Yw2,W,H:Extended;
    Xs,Ys,X2s,Y2s,Ast,Vals,Ss,Ws,Hs,Color,BGColor,Width:String;
Begin
  Xs:=GetValByName(Obj.Style,'X');
  Ys:=GetValByName(Obj.Style,'Y');
  If Xs='' Then Obj.X:=0 // default value
  Else Obj.X:=StrToFloat(Xs);
  If Ys='' Then Obj.Y:=0 // default value
  Else Obj.Y:=StrToFloat(Ys);
  Ast:=GetValByName(Obj.Style,'A');
  If Not TryStrToFloat(Ast,Obj.A) Then Obj.A:=0;
  If (Obj.A=720) Or (Obj.A=720+360) Or (Obj.A=720+2*360) Or (Obj.A=720+3*360) Or (Obj.A=720+4*360) Then Begin
    //special objects, needs W H               ---- or X2 Y2
    Obj.Color:=GetValByName(Obj.Style,'Color');
    If Obj.Color='' Then Obj.Color:='clBlack';
    Obj.BGColor:=GetValByName(Obj.Style,'BGColor');
    If Obj.BGColor='' Then Obj.BGColor:='clGray';
    Obj.Width:=GetValByName(Obj.Style,'Width');
    If Obj.Width='' Then Obj.Width:='1';
    Ws:=GetValByName(Obj.Style,'W');
    If Ws<>'' Then Begin Obj.X2:=0; Obj.W:=StrToFloat(Ws);End;
    Hs:=GetValByName(Obj.Style,'H');
    If Hs<>'' Then Begin Obj.Y2:=0; Obj.H:=StrToFloat(Hs);End;
{    X2s:=GetValByName(Obj.Style,'X2');
    If X2s<>'' Then Obj.X2:=StrToFloat(X2s);
    Y2s:=GetValByName(Obj.Style,'Y2');
    If Y2s<>'' Then Obj.Y2:=StrToFloat(Y2s);}
  end;
  Ss:=GetValByName(Obj.Style,'S');
  If Ss='' Then Ss:='1';
  TryStrToFloat(Ss,Obj.S);
  Vals:=GetValByName(Obj.Style,'Value');
  If Vals='' Then Vals:='0';
  Obj.Value:=StrToFloat(Vals);
  Obj.Source:=GetValByName(Obj.Style,'Source');
  Obj.Template:=GetValByName(Obj.Style,'Template');
End;


Procedure TVScene.UpdateObjectDisplayList(Obj:TVObject);
Var I,ModCommandNumber:Longint;
    Xw1s,Yw1s,Xw2s,Yw2s,Buf,ParsedBuf,Cmd:String;
    Xw1,Yw1,Xw2,Yw2,XB:Extended;
    AccObject:TAccObject;
    NonAccCommand:String;
    E:TMatrix2D;
Begin
  // как быть с портами ??? проверить правильность проложение и наличия портов после редактирования фигур
  If (Obj.A=720) Or (Obj.A=720+360) Or (Obj.A=720+2*360) Or (Obj.A=720+3*360) Or (Obj.A=720+4*360) Then Begin
  // объект - фигура
    If Obj.DisplayList.Count>0 Then
      For I:=Obj.DisplayList.Count-1 DownTo 0 Do Begin
        AccObject:=Obj.DisplayList.Objects[I] As TAccObject;
        AccObject.Free;
      end;
    Obj.DisplayList.Clear;
    Xw1:=Obj.X;Yw1:=Obj.Y;
    If Obj.W=0 Then Xw2:=Obj.X2 Else Xw2:=Xw1+Obj.W;
    If Obj.H=0 Then Yw2:=Obj.Y2 Else Yw2:=Yw1+Obj.H;
    NonAccCommand:='SetColor '+Obj.Color;
    AccObject:=TAccObject.Create;
    AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
    Obj.DisplayList.AddObject(NonAccCommand,AccObject);
    NonAccCommand:='SetBGColor '+Obj.BGColor;
    AccObject:=TAccObject.Create;
    AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
    Obj.DisplayList.AddObject(NonAccCommand,AccObject);
    NonAccCommand:='SetWidth '+Obj.Width;
    AccObject:=TAccObject.Create;
    AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
    Obj.DisplayList.AddObject(NonAccCommand,AccObject);

    If (Obj.A=720) Then NonAccCommand:='Rect '+FloatToStr(Xw1)+','+FloatToStr(Yw1)+','+FloatToStr(Xw2)+','+FloatToStr(Yw2);
    If (Obj.A=720+360) Then NonAccCommand:='RoundRect '+FloatToStr(Xw1)+','+FloatToStr(Yw1)+','+FloatToStr(Xw2)+','+FloatToStr(Yw2);
    If (Obj.A=720+2*360) Then NonAccCommand:='Ellipse '+FloatToStr(Xw1)+','+FloatToStr(Yw1)+','+FloatToStr(Xw2)+','+FloatToStr(Yw2);
    If (Obj.A=720+3*360) Then NonAccCommand:='Bmp '+FloatToStr(Xw1)+','+FloatToStr(Yw1)+','+FloatToStr(Xw2)+','+FloatToStr(Yw2)+','+Obj.Source;
    If (Obj.A=720+4*360) Then NonAccCommand:='SubPicture '+FloatToStr(Xw1)+','+FloatToStr(Yw1)+','+FloatToStr(Xw2)+','+FloatToStr(Yw2)+','+Obj.Source;
    AccObject:=TAccObject.Create;
    AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
    Obj.DisplayList.AddObject(NonAccCommand,AccObject);
  end Else Begin
    If Obj.Template<>'' Then Begin
      Obj.DisplayList.Clear;
      NonAccCommand:='DrawTemplate '+Obj.Template+' 0,0,0,1';
      AccObject:=TAccObject.Create;
      AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
      Obj.DisplayList.AddObject(NonAccCommand,AccObject);
    end;
  end;
  RenderObjectShell(Obj);
  UpdateObjectPorts(Obj);
End;

Procedure TVScene.CalloutAttr2Style(Callout:TVCallout);
Begin
    Callout.Style:=
    'X='+FloatToStr(Callout.X)+
    ' Y='+FloatToStr(Callout.Y)+
    ' W='+FloatToStr(Callout.W)+
    ' H='+FloatToStr(Callout.H)+
    ' Content='+Callout.Content+
    ' Color='+Callout.Color+
    ' BGColor='+Callout.BGColor+
    ' Width='+Callout.Width+
    ' FontName='+Callout.FontName+
    ' FontColor='+Callout.FontColor+
    ' FontHeight='+Callout.FontHeight+
    ' BIUS='+Callout.BIUS+
    ' Transparent='+Callout.Transparent+
    ' BindMode='+Callout.BindMode;
    If Callout.RelatedObj<>'' Then Callout.Style:=Callout.Style+' RelatedObj='+Callout.RelatedObj;
End;

Procedure TVScene.CalloutStyle2Attr(Callout:TVCallout);
Var Xw1,Yw1,Xw2,Yw2,W,H:Extended;
    Xs,Ys,X2s,Y2s,Ast,Ss,Ws,Hs,Color,BGColor,Width:String;
    RelObj,OldRelObj:String;
    Obj:TVObject;
    K,L:Longint;
Begin
  Xs:=GetValByName(Callout.Style,'X');
  Ys:=GetValByName(Callout.Style,'Y');
  Ws:=GetValByName(Callout.Style,'W');
  Hs:=GetValByName(Callout.Style,'H');
  If Xs='' Then Callout.X:=0 // default value
  Else Callout.X:=StrToFloat(Xs);
  If Ys='' Then Callout.Y:=0 // default value
  Else Callout.Y:=StrToFloat(Ys);
  If Ws='' Then Callout.W:=1 // default value
  Else Callout.W:=StrToFloat(Ws);
  If Hs='' Then Callout.H:=1 // default value
  Else Callout.H:=StrToFloat(Hs);
  Callout.Width:=GetValByName(Callout.Style,'Width');
  If Callout.Width='' Then Callout.Width:='0';
  Callout.Content:=GetValByName(Callout.Style,'Content');
  If Callout.Content='' Then Callout.Content:='Text';
  Callout.Color:=GetValByName(Callout.Style,'Color');
  If Callout.Color='' Then Callout.Color:='clBlack';
  Callout.BGColor:=GetValByName(Callout.Style,'BGColor');
  If Callout.BGColor='' Then Callout.BGColor:='clGray';
  Callout.Transparent:=GetValByName(Callout.Style,'Transparent');
  If Callout.Transparent='' Then Callout.Transparent:='no';

  Callout.LineStyle:=GetValByName(Callout.Style,'LineStyle');

  Callout.FontName:=GetValByName(Callout.Style,'FontName');
  If Callout.FontName='' Then Callout.FontName:='default';
  Callout.FontColor:=GetValByName(Callout.Style,'FontColor');
  If Callout.FontColor='' Then Callout.FontColor:='clBlack';
  Callout.FontHeight:=GetValByName(Callout.Style,'FontHeight');
  If Callout.FontHeight='' Then Callout.FontHeight:='10';
  Callout.BIUS:=GetValByName(Callout.Style,'BIUS');
  Callout.BindMode:=GetValByName(Callout.Style,'BindMode');
//  If Callout.BindMode='' Then Callout.BindMode:= ;

  OldRelObj:=Callout.RelatedObj;
  Callout.RelatedObj:=GetValByName(Callout.Style,'RelatedObj');
  If Callout.RelatedObj<>OldRelObj Then Begin
    //изменили объект-владелец - надо обновитиь встречную ссылку в самом объекте
     K:=VObjectList.IndexOf(Callout.RelatedObj);
     If K>=0 Then Begin
       //add link to this callout to object
       Obj:=VObjectList.Objects[K] As TVObject;
       If Obj.RelatedCAList.IndexOf(Callout.Name)<0 Then Obj.RelatedCAList.AddObject(Callout.Name,Callout);
     end;
     K:=VObjectList.IndexOf(OldRelObj);
     If K>=0 Then Begin
       //removelink to this callout from old object
       Obj:=VObjectList.Objects[K] As TVObject;
       L:=Obj.RelatedCAList.IndexOf(Callout.Name);
       If L>=0 Then Obj.RelatedCAList.Delete(I);
     end;

  end;
End;

Procedure TVScene.UpdateCalloutDisplayList(Callout:TVCallout);
//пока неправильно вообще
Var I,N,Index:Longint;
    AccObject:TAccObject;
    W:Extended;
    NonAccCommand:String;
    ConnPort:TVConnectionPort;
    Obj:TVObject; Port:TVPort;
    E:TMatrix2D;
    BX,BY:Extended;
    FileBuf:TStringList;
    ErrCode:Longint;
Begin
  Callout.DisplayList.Clear;
{  If Callout.Source<>'' Then Begin
    StrToLinesBuffer.Clear;
    StrToLinesBuffer.Text:=GetResponce(Callout.Source,ErrCode);
    Callout.Text:=StrToLinesBuffer.CommaText;}
{    FileBuf:=TStringList.Create;
    Try
    FileBuf.LoadFromFile(Callout.Source);
    Callout.Text:=FileBuf.CommaText;
    finally
      FileBuf.Free;
    end;
  End;  }
  If Callout.RelatedObj<>'' Then Begin
      N:=VObjectList.IndexOf(Callout.RelatedObj);
      If N>=0 Then Begin
        Obj:=VObjectList.Objects[N] As TVObject;
        Case Callout.BindMode Of
        'Base': Begin
                  BX:=Obj.X;BY:=Obj.Y;
                End;
        'LT': Begin
                  BX:=Obj.Shell.X1;BY:=Obj.Shell.Y2;
              End;
        'RT': Begin
                  BX:=Obj.Shell.X2;BY:=Obj.Shell.Y2;
              End;
        'LB': Begin
                  BX:=Obj.Shell.X1;BY:=Obj.Shell.Y1;
              End;
        'RB': Begin
                  BX:=Obj.Shell.X2;BY:=Obj.Shell.Y1;
              End;
        'Shell': Begin
                  BX:=Obj.Shell.X1;BY:=Obj.Shell.Y2;
                  Callout.W:=Abs(Obj.Shell.X2-Obj.Shell.X1);
                  Callout.H:=Abs(Obj.Shell.Y2-Obj.Shell.Y1);
              End;
         Else Begin
                BX:=0;BY:=0;
              End;
         end;
      end Else Begin
        BX:=0;BY:=0;
      end;
    end Else Begin
      BX:=0;BY:=0;
    end;
  If LowerCase(Callout.Transparent)='yes' Then Begin
  End Else Begin
    AccObject:=TAccObject.Create;
    NonAccCommand:='SetColor '+Callout.Color;
    AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
    Callout.DisplayList.AddObject(NonAccCommand,AccObject);

    AccObject:=TAccObject.Create;
    NonAccCommand:='SetBGColor '+(Callout.BGColor);
    AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
    Callout.DisplayList.AddObject(NonAccCommand,AccObject);

    AccObject:=TAccObject.Create;
    NonAccCommand:='SetWidth '+Callout.Width;
    AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
    Callout.DisplayList.AddObject(NonAccCommand,AccObject);

    AccObject:=TAccObject.Create;
    If Callout.BindMode='Shell' Then
         NonAccCommand:='Rect '+FloatToStr(BX)+','+FloatToStr(BY)+','+FloatToStr(BX+Callout.W)+','+FloatToStr(BY-Callout.H)
    Else NonAccCommand:='Rect '+FloatToStr(BX+Callout.X)+','+FloatToStr(BY+Callout.Y)+','+FloatToStr(BX+Callout.X+Callout.W)+','+FloatToStr(BY+Callout.Y-Callout.H);
    AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
    Callout.DisplayList.AddObject(NonAccCommand,AccObject);
  End;
  AccObject:=TAccObject.Create;
  NonAccCommand:='SetScaledFont '+Callout.FontName+','+(Callout.FontHeight)+','+(Callout.FontColor)+','+Callout.bius;
  AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
  Callout.DisplayList.AddObject(NonAccCommand,AccObject);

  AccObject:=TAccObject.Create;
  NonAccCommand:='SetTextStyle Transparent';
  AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
  Callout.DisplayList.AddObject(NonAccCommand,AccObject);


  Case Callout.Content of
  'Pix': Begin
    AccObject:=TAccObject.Create;
  //  NonAccCommand:='Bmp '+FloatToStr(Callout.X)+','+FloatToStr(Callout.Y)+','+FloatToStr(Callout.X+Callout.W)+','+FloatToStr(Callout.Y-Callout.H)+','+Callout.Text;
    If Callout.BindMode='Shell' Then
         NonAccCommand:='Bmp '+FloatToStr(BX)+','+FloatToStr(BY-Callout.H)+','+FloatToStr(BX+Callout.W)+','+FloatToStr(BY)+','+Callout.Source
    Else NonAccCommand:='Bmp '+FloatToStr(BX+Callout.X)+','+FloatToStr(BY+Callout.Y-Callout.H)+','+FloatToStr(BX+Callout.X+Callout.W)+','+FloatToStr(BY+Callout.Y)+','+Callout.Source;

         End;
  'Vect': Begin
    AccObject:=TAccObject.Create;
    If Callout.BindMode='Shell' Then
         NonAccCommand:='SubPicture '+FloatToStr(BX)+','+FloatToStr(BY-Callout.H)+','+FloatToStr(BX+Callout.W)+','+FloatToStr(BY)+','+Callout.Source
    Else NonAccCommand:='SubPicture '+FloatToStr(BX+Callout.X)+','+FloatToStr(BY+Callout.Y-Callout.H)+','+FloatToStr(BX+Callout.X+Callout.W)+','+FloatToStr(BY+Callout.Y)+','+Callout.Source;
       End;
  Else Begin //text
    AccObject:=TAccObject.Create;
  //  NonAccCommand:='TextRect '+FloatToStr(Callout.X)+','+FloatToStr(Callout.Y)+','+FloatToStr(Callout.X+Callout.W)+','+FloatToStr(Callout.Y-Callout.H)+','+Callout.Text;
    If Callout.BindMode='Shell' Then
         NonAccCommand:='TextRect '+FloatToStr(BX)+','+FloatToStr(BY)+','+FloatToStr(BX+Callout.W)+','+FloatToStr(BY-Callout.H)+','+Callout.Text
    Else NonAccCommand:='TextRect '+FloatToStr(BX+Callout.X)+','+FloatToStr(BY+Callout.Y)+','+FloatToStr(BX+Callout.X+Callout.W)+','+FloatToStr(BY+Callout.Y-Callout.H)+','+Callout.Text;
       End;
  end;
  AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
  Callout.DisplayList.AddObject(NonAccCommand,AccObject);
  E:=GenShift(0,0);
  Callout.Shell:=RenderShell(Callout.DisplayList,E);
end;

Procedure TVScene.UpdateObjectPorts(Obj:TVObject);
Var I,J,K:Longint;
    AccObject:TAccObject;
    Port:TVPort;
Begin
//удаляем команды старой отрисовки портов
For K:=Obj.DisplayList.Count-1 DownTo 0 Do Begin
  AccObject:=Obj.DisplayList.Objects[K] As TAccObject;
  If Pos('UnScaled ',Obj.DisplayList[K])=1 Then Begin
    Obj.DisplayList.Delete(K);
    AccObject.Free;
  End;
end;
// пересчитать абс коорд портов
If Obj.PortList.Count>0 Then
  For J:=0 To Obj.PortList.Count-1 Do Begin
    Port:=Obj.PortList.Objects[J] As TVPort;
    If Port.AR='A' Then Begin
      //Надо ли сдвигать порты при сдвиге объекта, если они заданы абсолютно?
//        Port.X:=(Port.AX-Obj.Shell.X1)/(Obj.Shell.X2-Obj.Shell.X1);
//        Port.Y:=(Port.AY-Obj.Shell.Y1)/(Obj.Shell.Y2-Obj.Shell.Y1);
    End Else Begin
      If Not(InfiniteShell(Obj.Shell)) Then Begin
        Port.AX:=Obj.Shell.X1+Port.X*(Obj.Shell.X2-Obj.Shell.X1);
        Port.AY:=Obj.Shell.Y1+Port.Y*(Obj.Shell.Y2-Obj.Shell.Y1);
      End;
    End;
    //Добавление портов в DisplayList
    AccObject:=TAccObject.Create;
    AccObject.AcceleratedCommand:=AccelerateCommand('UnScaled '+FloatToStr(Port.Ax)+','+FloatToStr(Port.Ay)+',portbox,'+IntToStr(PortSize));
    Obj.DisplayList.AddObject('UnScaled '+FloatToStr(Port.Ax)+','+FloatToStr(Port.Ay)+',portbox,'+IntToStr(PortSize),AccObject);
  End;
End;

Procedure TVScene.MoveObjectTo(Obj:TVObject;X,Y:Extended;GroupList:String);
Var I,J,K,N,Index,ModCommandNumber:Longint;
    Obj2:TVObject;
    Conn:TVConnection;
    Port:TVPort;
    AccObject:TAccObject;
    Callout:TVCallout;
    E,Sm,Rm,Scm,tmpm,TransM:TMatrix2D;
    GroupName:String;
    Group:TVGroup;
    Dx,Dy:Extended;
    Xw1s,Yw1s,Xw2s,Yw2s,Buf,ParsedBuf,Cmd,NonAccCommand:String;
    Xw1,Xw2,Yw1,Yw2,XB:Extended;
Begin
  Dx:=X-Obj.X;
  Dy:=Y-Obj.Y;
  Obj.X:=X;
  Obj.Y:=Y;
  If Obj.A>700 Then Begin
             // объект - фигура
               If Obj.DisplayList.Count>0 Then
                 For I:=0 To Obj.DisplayList.Count-1 Do Begin
                   Buf:=Obj.DisplayList[I];
                   Cmd:=GetNextToken(Buf,ParsedBuf,' ');
                   Buf:=ParsedBuf;
                   If (Cmd='Rect') Or (Cmd='RoundRect') Or (Cmd='Ellipse') Or (Cmd='Bmp') Or (Cmd='SubPicture') Then Begin
                     ModCommandNumber:=I;
                     Xw1s:=GetNextToken(Buf,ParsedBuf,',');
                     Buf:=ParsedBuf;
                     Yw1s:=GetNextToken(Buf,ParsedBuf,',');
                     Buf:=ParsedBuf;
                     Xw2s:=GetNextToken(Buf,ParsedBuf,',');
                     Buf:=ParsedBuf;
                     Yw2s:=GetNextToken(Buf,ParsedBuf,',');
                     Xw1:=StrToFloat(Xw1s);
                     Yw1:=StrToFloat(Yw1s);
                     Xw2:=StrToFloat(Xw2s);
                     Yw2:=StrToFloat(Yw2s);
                     If Xw1>Xw2 Then Begin
                       XB:=Xw1;Xw1:=Xw2;Xw2:=XB
                     End;
                     If Yw1>Yw2 Then Begin
                       XB:=Yw1;Yw1:=Yw2;Yw2:=XB
                     End;
                    // Obj.X:=Xw1+Dx;Obj.Y:=Yw1+Dy;
                     Break; //For
                   end;
                 end;
             AccObject:=Obj.DisplayList.Objects[ModCommandNumber] As TAccObject;
             If Obj.A=720 Then NonAccCommand:='Rect '+FloatToStr(Xw1+Dx)+','+FloatToStr(Yw1+Dy)+','+FloatToStr(Xw2+Dx)+','+FloatToStr(Yw2+Dy);
             If Obj.A=720+360 Then NonAccCommand:='RoundRect '+FloatToStr(Xw1+Dx)+','+FloatToStr(Yw1+Dy)+','+FloatToStr(Xw2+Dx)+','+FloatToStr(Yw2+Dy);
             If Obj.A=720+2*360 Then NonAccCommand:='Ellipse '+FloatToStr(Xw1+Dx)+','+FloatToStr(Yw1+Dy)+','+FloatToStr(Xw2+Dx)+','+FloatToStr(Yw2+Dy);
             If Obj.A=720+3*360 Then NonAccCommand:='Bmp '+FloatToStr(Xw1+Dx)+','+FloatToStr(Yw1+Dy)+','+FloatToStr(Xw2+Dx)+','+FloatToStr(Yw2+Dy)+','+Obj.Source;
             If Obj.A=720+4*360 Then NonAccCommand:='SubPicture '+FloatToStr(Xw1+Dx)+','+FloatToStr(Yw1+Dy)+','+FloatToStr(Xw2+Dx)+','+FloatToStr(Yw2+Dy)+','+Obj.Source;
             AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
             Obj.DisplayList.Objects[ModCommandNumber]:=AccObject;
             Obj.DisplayList.Strings[ModCommandNumber]:=NonAccCommand;
  End;
  ObjectAttr2Style(Obj);
  RenderObjectShell(Obj);
  //группы Glue
  If Obj.GroupList.Count>0 Then
    For K:=0 To Obj.GroupList.Count-1 Do Begin
      //пропускаем группы, чьи объекты уже подвинуты
      If Pos(Obj.GroupList[K],GroupList)>0 Then Continue; // проверка неаккуратная - может находить неправильные имена групп
      N:=VGroupList.IndexOf(Obj.GroupList[K]);
      If N>=0 Then Group:=VGroupList.Objects[N] As TVGroup;
      If Group.Glue Then Begin
        //двигаем все объекты группы
        If Group.DisplayList.Count>0 Then
          For J:=0 To Group.DisplayList.Count-1 Do Begin
            Index:=VObjectList.IndexOf(Group.DisplayList[J]);
            If Index<0 Then Continue; //не объект, а связь
            Obj2:=VObjectList.Objects[Index] As TVObject;
            If Obj=Obj2 Then Continue;
            MoveObjectTo(Obj2,Obj2.X+Dx,Obj2.Y+Dy,GroupList+','+Group.Name);
          end;
      end;
    end;
  //обновляем связанные выноски

  For K:=0 To Obj.RelatedCAList.Count-1 Do Begin
    Callout:=Obj.RelatedCAList.Objects[K] As TVCallout;
    UpdateCalloutDisplayList(Callout);
  end;

  UpdateObjectPorts(Obj);
  // найти подключенные без портов связи
  If Obj.NonPortConnectionList.Count>0 Then
    For I:=0 To Obj.NonPortConnectionList.Count-1 Do Begin
      Index:=VConnectionList.IndexOf(Obj.NonPortConnectionList[I]);
      If Index>=0 Then Begin
        Conn:=VConnectionList.Objects[Index] As TVConnection;
        If Conn.StartObj=Obj.Name Then Begin
          //двигаем начало
         // Conn.PortList;
          If Conn.DisplayList.Count>0 Then
            For J:=Conn.DisplayList.Count-1 Downto 0 Do Begin
              AccObject:=Conn.DisplayList.Objects[J] As TAccObject;
              AccObject.Free;
            end;
          Conn.DisplayList.Clear;
          UpdateConnectionDisplayList(Conn);
        End Else Begin
          //двигаем конец
          If Conn.DisplayList.Count>0 Then
            For J:=Conn.DisplayList.Count-1 Downto 0 Do Begin
              AccObject:=Conn.DisplayList.Objects[J] As TAccObject;
              AccObject.Free;
            end;
          Conn.DisplayList.Clear;
          UpdateConnectionDisplayList(Conn);
        End;
      End;
    End;
  // найти подключенные к портам связи
  If Obj.PortList.Count>0 Then
    For I:=0 To Obj.PortList.Count-1 Do Begin
      Port:=Obj.PortList.Objects[I] As TVPort;
      If Port.Connection<>'' Then Begin
        Index:=VConnectionList.IndexOf(Port.Connection);
        If Index<0 Then Begin Port.Connection:='';Continue;End;
        Conn:=VConnectionList.Objects[Index] As TVConnection;
        If (Conn.StartObj=Obj.Name)Or(Conn.EndObj=Obj.Name) Then Begin
          //двигаем начало
         // Conn.PortList;
          If Conn.DisplayList.Count>0 Then
            For J:=Conn.DisplayList.Count-1 Downto 0 Do Begin
              AccObject:=Conn.DisplayList.Objects[J] As TAccObject;
              AccObject.Free;
            end;
          Conn.DisplayList.Clear;
          UpdateConnectionDisplayList(Conn);
 {       End Else Begin
          //двигаем конец
          If Conn.DisplayList.Count>0 Then
            For J:=Conn.DisplayList.Count-1 Downto 0 Do Begin
              AccObject:=Conn.DisplayList.Objects[J] As TAccObject;
              AccObject.Free;
            end;
          Conn.DisplayList.Clear;
          UpdateConnectionDisplayList(Scene,Conn);}
        End;

      end;
    end;

  // пересчитать координаты узлов связи, если узлы относительные
  // пересчитать дисплейный список связи

End;


Procedure TVScene.MoveCalloutTo(Callout:TVCallout;X,Y:Extended;GroupList:String);
//здесь недописанная дичь, возможно процедура не нужна совсем
Var I,J,K,N,Index:Longint;
    Callout2:TVCallout;
    Conn:TVConnection;
    Port:TVPort;
    AccObject:TAccObject;
    Sm,Rm,Scm,tmpm,TransM:TMatrix2D;
    GroupName:String;
    Group:TVGroup;
    Dx,Dy:Extended;
Begin
  Dx:=X-Callout.X;
  Dy:=Y-Callout.Y;
  Callout.X:=X;
  Callout.Y:=Y;
  UpdateCalloutDisplayList(Callout);
  Callout.Shell:=RenderShell(Callout.DisplayList,E);
 // ExecCommand('DrawCallout '+Callout.Name,ZeroAcc,0);
  exit;

  //группы Glue
  If Callout.GroupList.Count>0 Then
    For K:=0 To Callout.GroupList.Count-1 Do Begin
      //пропускаем группы, чьи объекты уже подвинуты
      If Pos(Callout.GroupList[K],GroupList)>0 Then Continue; // проверка неаккуратная - может находить неправильные имена групп
      N:=VGroupList.IndexOf(Callout.GroupList[K]);
      If N>=0 Then Group:=VGroupList.Objects[N] As TVGroup;
      If Group.Glue Then Begin
        //двигаем все объекты группы
        If Group.DisplayList.Count>0 Then
          For J:=0 To Group.DisplayList.Count-1 Do Begin
            Index:=VObjectList.IndexOf(Group.DisplayList[J]);
            If Index<0 Then Continue; //не объект, а связь
            Callout2:=VObjectList.Objects[Index] As TVCallout;
            If Callout=Callout2 Then Continue;
            MoveCalloutTo(Callout2,Callout2.X+Dx,Callout2.Y+Dy,GroupList+','+Group.Name);
          end;
      end;
    end;
End;


Procedure TVScene.DeleteCallout(Callout:TVCallout);
Var CacheIndex,I,N:Longint;
    CalloutName,ObjName:String;
    Obj:TVobject;
    Group:TVGroup;
    CacheItem:TGraphCacheItem;
Begin
  CalloutName:=Callout.Name;
  If VCalloutList.IndexOfObject(Callout)>=0 Then
    VCalloutList.Delete(VCalloutList.IndexOfObject(Callout));
  // убрать из VisibleList
  If VisibleList.IndexOf(CalloutName)>=0 Then VisibleList.Delete(VisibleList.IndexOf(CalloutName));

  // убираем из связанного объекта
  ObjName:=Callout.RelatedObj;
  N:=VObjectList.IndexOf(ObjName);
  If N>=0 Then Begin
    Obj:=VObjectList.Objects[N] As TVObject;
    N:=Obj.RelatedCAList.IndexOf(CalloutName);
    If N>=0 Then Obj.RelatedCAList.Delete(N);
  End;
  // убрать из связанных групп
  If Callout.GroupList.Count>0 Then
    For I:=Callout.GroupList.Count-1 DownTo 0 Do Begin
      Group:=Callout.GroupList.Objects[I] As TVGroup;
      N:=Group.DisplayList.IndexOf(Callout.Name);
      Group.DisplayList.Delete(N);
    end;
  //удаление битмапа из графического кеша
  If (Callout.Content='Vect')Or(Callout.Content='Pix') Then Begin
    CacheIndex:=GraphCache.IndexOf(Callout.Source);
    If CacheIndex>=0 Then Begin
      CacheItem:=GraphCache.Objects[CacheIndex] As TGraphCacheItem;
      If CacheItem.LinkCount>0 Then Dec(CacheItem.LinkCount);
      If CacheItem.LinkCount=0 Then CacheItem.Destroy;
    End;
  End;
  Callout.Free;
end;

Procedure TVScene.DeleteConnection(Connection:TVConnection);
Var I,N:Longint;
    ConnectionName,CalloutName,ObjName:String;
    Obj:TVObject;
    Port:TVPort;
    Group:TVGroup;
Begin
  If Connection=Nil Then Exit;
  ConnectionName:=Connection.Name;
  If VConnectionList.IndexOfObject(Connection)>=0 Then
    VConnectionList.Delete(VConnectionList.IndexOfObject(Connection));
  // убрать из VisibleList
  If VisibleList.IndexOf(ConnectionName)>=0 Then VisibleList.Delete(VisibleList.IndexOf(ConnectionName));

  // убираем из связанных объектов
  N:=VObjectList.IndexOf(Connection.StartObj);
  If N>=0 Then Begin
    Obj:=VObjectList.Objects[N] As TVObject;
    If Obj.PortList.Count>0 Then
      For I:=0 To Obj.PortList.Count-1 Do Begin
        Port:=Obj.PortList.Objects[I] As TVPort;
        If Port.Connection=ConnectionName Then Begin
          Port.Connection:='';
          Port.ConnectionPort:='';
        end;
      end;
    If Obj.NonPortConnectionList.Count>0 Then Begin
      Repeat
        N:=Obj.NonPortConnectionList.IndexOf(ConnectionName);
        If N>=0 Then Obj.NonPortConnectionList.Delete(N);
       Until N<0;
    End;
  End;
  N:=VObjectList.IndexOf(Connection.EndObj);
   If N>=0 Then Begin
     Obj:=VObjectList.Objects[N] As TVObject;
     If Obj.PortList.Count>0 Then
       For I:=0 To Obj.PortList.Count-1 Do Begin
         Port:=Obj.PortList.Objects[I] As TVPort;
         If Port.Connection=ConnectionName Then Begin
           Port.Connection:='';
           Port.ConnectionPort:='';
         end;
       end;
     If Obj.NonPortConnectionList.Count>0 Then Begin
       Repeat
         N:=Obj.NonPortConnectionList.IndexOf(ConnectionName);
         If N>=0 Then Obj.NonPortConnectionList.Delete(N);
        Until N<0;
     End;
   End;

  // убрать из связанных групп
  If Connection.GroupList.Count>0 Then
    For I:=Connection.GroupList.Count-1 DownTo 0 Do Begin
      Group:=Connection.GroupList.Objects[I] As TVGroup;
      N:=Group.DisplayList.IndexOf(Connection.Name);
      Group.DisplayList.Delete(N);
    end;
  Connection.Free;
end;

Procedure TVScene.DeleteObject(Obj:TVObject);
Var I,N,CacheIndex:Longint;
    CalloutName,ObjName:String;
    Group:TVGroup;
    Connection:TVConnection;
    Callout:TVCallout;
    Port:TVPort;
    CacheItem:TGraphCacheItem;
Begin
  ObjName:=Obj.Name;
  Repeat
    N:=View.IndexOf('DrawObject '+ObjName);
    If N>=0 Then View.Delete(N);
  Until N<0;
  // убрать из VisibleList
  If VisibleList.IndexOf(ObjName)>=0 Then VisibleList.Delete(VisibleList.IndexOf(ObjName));
  // убрать из связанных групп
  If Obj.GroupList.Count>0 Then
    For I:=Obj.GroupList.Count-1 DownTo 0 Do Begin
      Group:=Obj.GroupList.Objects[I] As TVGroup;
      N:=Group.DisplayList.IndexOf(ObjName);
      Group.DisplayList.Delete(N);
    end;
  //что делать со связанными выносками? Удалять? отцеплять?
  If Obj.RelatedCAList.Count>0 Then
    For I:=Obj.RelatedCAList.Count-1 DownTo 0 Do Begin
      If Obj.RelatedCAList.Objects[I]<> Nil Then
        If Obj.RelatedCAList.Objects[I] Is TVCallout Then Begin
          Callout:=Obj.RelatedCAList.Objects[I] As TVCallout;
         DeleteCallout(Callout);
//      Obj.RelatedCAList.Delete(I);
      End;
    End;
  //соединения удалить
  If Obj.NonPortConnectionList.Count>0 Then
    For I:=Obj.NonPortConnectionList.Count-1 DownTo 0 Do Begin
      Connection:=Obj.NonPortConnectionList.Objects[I] As TVConnection;
      DeleteConnection(Connection);
    end;
  If Obj.PortList.Count>0 Then
    For I:=Obj.PortList.Count-1 DownTo 0 Do Begin
      Port:=Obj.PortList.Objects[I] As TVPort;
      If Port.Connection<>'' Then Begin
        N:=VConnectionList.IndexOf(Port.Connection);
        Connection:=VConnectionList.Objects[N] As TVConnection;
        DeleteConnection(Connection);
      end;
    end;
  //удаление битмапа из графического кеша
  If Obj.A=720+3*360 Then Begin
    CacheIndex:=GraphCache.IndexOf(Obj.Source);
    If CacheIndex>=0 Then Begin
      CacheItem:=GraphCache.Objects[CacheIndex] As TGraphCacheItem;
      If CacheItem.LinkCount>0 Then Dec(CacheItem.LinkCount);
      If CacheItem.LinkCount=0 Then CacheItem.Destroy;
    End;
  End;
  //удаление текста из текстового кеша
  If Obj.A=720+4*360 Then Begin
    CacheIndex:=GraphCache.IndexOf(Obj.Source);
    If CacheIndex>=0 Then Begin
      CacheItem:=GraphCache.Objects[CacheIndex] As TGraphCacheItem;
      If CacheItem.LinkCount>0 Then Dec(CacheItem.LinkCount);
      If CacheItem.LinkCount=0 Then CacheItem.Destroy;
    End;
  End;

  If VObjectList.IndexOfObject(Obj)>=0 Then
    VObjectList.Delete(VObjectList.IndexOfObject(Obj));
  Obj.Free;
end;

Procedure TVScene.ExportCommands(Var Buf:TStringList);
Var I:Longint;
    Obj:TVObject;
    Callout:TVCallout;
    Conn:TVConnection;
Begin
  Buf.Clear;
  If VObjectList.Count>0 Then
    For I:=0 To VObjectList.Count-1 Do Begin
      Obj:=VObjectList.Objects[I] As TVObject;
      Buf.Add('self::CAD_CreateObject:Name='+Obj.Name+' '+Obj.Style);
    end;
  If VCalloutList.Count>0 Then
    For I:=0 To VCalloutList.Count-1 Do Begin
      Callout:=VCalloutList.Objects[I] As TVCallout;
      Buf.Add('self::CAD_CreateCallout:Name='+Callout.Name+' '+Callout.Style);
    end;
  If VConnectionList.Count>0 Then
    For I:=0 To VConnectionList.Count-1 Do Begin
      Conn:=VConnectionList.Objects[I] As TVConnection;
      Buf.Add('self::CAD_CreateConnection:Name='+Conn.Name+' '+Conn.Style);
    end;

end;

Constructor TVCurl.Create;
Begin
  Inherited Create;
  DisplayList:=TStringList.Create;
End;

Destructor TVCurl.Destroy;
Var I:Longint;Acc:TAccObject;
Begin
  If DisplayList.Count>0 Then
    For I:=DisplayList.Count-1 Downto 0 Do Begin
      Acc:=DisplayList.Objects[I] As TAccObject;
      Acc.Free;
    end;
  DisplayList.Free;
  Inherited Destroy;
End;

Constructor TVConnection.Create;
Begin
  Inherited Create;
  PortList:=TStringList.Create;
  DisplayList:=TStringList.Create;
  GroupList:=TStringList.Create;
End;

Destructor TVConnection.Destroy;
Var I:Longint;Acc:TAccObject;
    Port:TVConnectionPort;
Begin
  If DisplayList.Count>0 Then
    For I:=DisplayList.Count-1 Downto 0 Do Begin
      Acc:=DisplayList.Objects[I] As TAccObject;
      Acc.Free;
    end;
  If PortList.Count>0 Then
    For I:=PortList.Count-1 Downto 0 Do Begin
      Port:=PortList.Objects[I] As TVConnectionPort;
      Port.Free;
      PortList.Delete(I);
    end;
  DisplayList.Free;
  PortList.Free;
  GroupList.Free;
  Inherited Destroy;
End;

Constructor TVTemplate.Create;
Begin
  Inherited Create;
  DisplayList:=TStringList.Create;
  PortList:=TStringList.Create;
End;

Destructor TVTemplate.Destroy;
Begin
  DisplayList.Free;
  PortList.Free;
  Inherited Destroy;
End;

Constructor TVObject.Create;
Begin
  Inherited Create;
  DisplayList:=TStringList.Create;
  PortList:=TStringList.Create;
  NonPortConnectionList:=TStringList.Create;
  GroupList:=TStringList.Create;
  RelatedCAList:=TStringList.Create;
  ActionList:=TStringList.Create;
End;

Destructor TVObject.Destroy;
Var I:Longint;Port:TVPort;
Begin
  DisplayList.Free;
  GroupList.Free;
  RelatedCAList.Free;
  If PortList.Count>0 Then
    For I:=PortList.Count-1 Downto 0 Do Begin
      Port:=PortList.Objects[I] As TVPort;
      Port.Free;
      PortList.Delete(I);
    end;
  PortList.Free;
  NonPortConnectionList.Free;
  ActionList.Free;
  Inherited Destroy;
End;

Constructor TVCallout.Create;
Begin
  Inherited Create;
  PortList:=TStringList.Create;
  DisplayList:=TStringList.Create;
  GroupList:=TStringList.Create;
  ActionList:=TStringList.Create;
End;

Destructor TVCallout.Destroy;
Var I:Longint;Acc:TAccObject;
    Port:TVConnectionPort;
Begin
  If DisplayList.Count>0 Then
    For I:=DisplayList.Count-1 Downto 0 Do Begin
      Acc:=DisplayList.Objects[I] As TAccObject;
      Acc.Free;
    end;
  If PortList.Count>0 Then
    For I:=PortList.Count-1 Downto 0 Do Begin
      Port:=PortList.Objects[I] As TVConnectionPort;
      Port.Free;
      PortList.Delete(I);
    end;
  DisplayList.Free;
  PortList.Free;
  GroupList.Free;
  ActionList.Free;
  Inherited Destroy;
End;

Constructor TVGroup.Create;
Begin
  Inherited Create;
  DisplayList:=TStringList.Create;
End;

Destructor TVGroup.Destroy;
Begin
  DisplayList.Free;
  Inherited Destroy;
End;
Constructor TVScene.Create;
Begin
  Inherited Create;
  VObjectList:=TStringList.Create; // List of Objects
  VGroupList:=TStringList.Create; // List of groups of display commands
  VTemplateList:=TStringList.Create; // List of models
  VCurlList:=TStringList.Create; // List of models
  VConnectionList:=TStringList.Create;
  VCalloutList:=TStringList.Create;
  SelectedList:=TStringList.Create;
  VisibleList:=TStringList.Create;
  VHandleList:=TStringList.Create;
  View:=TStringList.Create; // Master display list used for visualization - sequence of drawing
End;

Destructor TVScene.Destroy;
Var I:Longint;C1:TVCurl;T1:TVTemplate;O1:TVObject;G1:TVGroup;Co1:TVConnection;
    Handle:TVSelHandle;
    Callout:TVCallout;
Begin
  View.Clear;
  If VGroupList.Count>0 Then
  For I:=VGroupList.Count-1 DownTo 0 Do Begin
    G1:=VGroupList.Objects[I] As TVGroup;
    G1.Destroy;
  end;
  VGroupList.Free; // List of groups of display commands
  For I:=VObjectList.Count-1 DownTo 0 Do Begin
    O1:=VObjectList.Objects[I] As TVObject;
    O1.Destroy;
  end;
  VObjectList.Free; // List of Objects
  For I:=VTemplateList.Count-1 DownTo 0 Do Begin
    T1:=VTemplateList.Objects[I] As TVTemplate;
    T1.Destroy;
  end;
  VTemplateList.Free; // List of models
  For I:=VCurlList.Count-1 DownTo 0 Do Begin
    C1:=VCurlList.Objects[I] As TVCurl;
    C1.Destroy;
  end;
  VCurlList.Free; // List of models
  For I:=VConnectionList.Count-1 DownTo 0 Do Begin
    Co1:=VConnectionList.Objects[I] As TVConnection;
    Co1.Destroy;
  end;
  VConnectionList.Free; // List of connections
  For I:=VCalloutList.Count-1 DownTo 0 Do Begin
    Callout:=VCalloutList.Objects[I] As TVCallout;
    Callout.Destroy;
  end;
  VCalloutList.Free; // List of callouts
  For I:=VHandleList.Count-1 DownTo 0 Do Begin
    Handle:=VHandleList.Objects[I] As TVSelHandle;
    Handle.Destroy;
  end;
  VHandleList.Free; // List of handles
  View.Free; // Master display list used for visualization - sequence of drawing
  SelectedList.Free;
  VisibleList.Free;
  Inherited Destroy;
End;

Procedure TVScene.Clear;
Var I:Longint;C1:TVCurl;T1:TVTemplate;O1:TVObject;G1:TVGroup;Co1:TVConnection;
    Callout:TVCallout; SelHandle:TVSelHandle;
Begin
  View.Clear;
  If VGroupList.Count>0 Then
  For I:=VGroupList.Count-1 DownTo 0 Do Begin
    G1:=VGroupList.Objects[I] As TVGroup;
    G1.Destroy;
    VGroupList.Delete(I);
  end;
  If VObjectList.Count>0 Then
  For I:=VObjectList.Count-1 DownTo 0 Do Begin
    O1:=VObjectList.Objects[I] As TVObject;
    O1.Destroy;
    VObjectList.Delete(I);
  end;
{  If VTemplateList.Count>0 Then
  For I:=VTemplateList.Count-1 DownTo 0 Do Begin
    T1:=VTemplateList.Objects[I] As TVTemplate;
    T1.Destroy;
    VTemplateList.Delete(I);
  end;}
  If VCurlList.Count>0 Then
  For I:=VCurlList.Count-1 DownTo 0 Do Begin
    C1:=VCurlList.Objects[I] As TVCurl;
    C1.Destroy;
    VCurlList.Delete(I);
  end;
  If VConnectionList.Count>0 Then
    For I:=VConnectionList.Count-1 DownTo 0 Do Begin
      Co1:=VConnectionList.Objects[I] As TVConnection;
      Co1.Destroy;
      VConnectionList.Delete(I);
    end;
  If VCalloutList.Count>0 Then
    For I:=VCalloutList.Count-1 DownTo 0 Do Begin
      Callout:=VCalloutList.Objects[I] As TVCallout;
      Callout.Destroy;
      VCalloutList.Delete(I);
    end;
  If VHandleList.Count>0 Then
    For I:=VHandleList.Count-1 DownTo 0 Do Begin
      SelHandle:=VHandleList.Objects[I] As TVSelHandle;
      SelHandle.Destroy;
      VHandleList.Delete(I);
    end;

End;

Procedure TVScene.LoadFile(Var FileContent:TStringList);
Var Limiter:TStringList;
    FileBuf,RenList:TStringList;
    I,J,K,N,ChunkLength:Longint;
    Curl:TVCurl;VObject:TVObject;Port:TVPort;VGroup:TVGroup;
    VConnection:TVConnection;VConnectionPort:TVConnectionPort;
    VCallout:TVCallout;
    CurrentLine,Keyword,Buf,ParsedBuf:String;
    AccObject:TAccObject;
    E,Sm,Rm,Scm,tmpm,TransM:TMatrix2D;

Begin
  Clear;
  RenList:=TStringList.Create;
  Limiter:=TStringList.Create;
  For I:=0 To FileContent.Count-1 Do Begin
    If (Pos('curl name:',LowerCase(FileContent[I]))=1) or
      (Pos('object name:',LowerCase(FileContent[I]))=1) or
      (Pos('callout name:',LowerCase(FileContent[I]))=1) or
      (Pos('connection name:',LowerCase(FileContent[I]))=1) or
      (Pos('group name:',LowerCase(FileContent[I]))=1) or
      (Pos('view:',LowerCase(FileContent[I]))=1)
    Then Limiter.Add(IntToStr(I));
  end;
  Limiter.Add(IntToStr(FileContent.Count));
  For I:=0 To Limiter.Count-2 Do Begin
     LogMessage(9,(FileContent[StrToInt(Limiter[I])]));
     If (Pos('group name:',LowerCase(FileContent[StrToInt(Limiter[I])]) )=1) Then Begin
              FileBuf:=TStringList.Create;
              For J:=StrToInt(Limiter[I]) To StrToInt(Limiter[I+1])-1 Do
                FileBuf.Add(FileContent[J]);
              VGroup:=LoadGroup(0,False,0,0,FileBuf,RenList);
              FileBuf.Free;
     End;
     If (Pos('curl name:',LowerCase(FileContent[StrToInt(Limiter[I])]) )=1) Then Begin
       FileBuf:=TStringList.Create;
       For J:=StrToInt(Limiter[I]) To StrToInt(Limiter[I+1])-1 Do
         FileBuf.Add(FileContent[J]);
       Curl:=LoadCurl(0,False,FileBuf);
       FileBuf.Free;
     End;
     If (Pos('object name:',LowerCase(FileContent[StrToInt(Limiter[I])]) )=1) Then Begin
       FileBuf:=TStringList.Create;
       For J:=StrToInt(Limiter[I]) To StrToInt(Limiter[I+1])-1 Do
         FileBuf.Add(FileContent[J]);
       VObject:=LoadObject(0,False,0,0,FileBuf,RenList);
       UpdateObjectDisplayList(VObject);
       RenderObjectShell(VObject); //посчитает неправильно при загрузке
       //RenderShell? Ports?
       FileBuf.Free;
     End;
     If (Pos('connection name:',LowerCase(FileContent[StrToInt(Limiter[I])]) )=1) Then Begin
          FileBuf:=TStringList.Create;
          For J:=StrToInt(Limiter[I]) To StrToInt(Limiter[I+1])-1 Do
            FileBuf.Add(FileContent[J]);
          VConnection:=LoadConnection(0,False,0,0,FileBuf,RenList);
          FileBuf.Free;
     End;
     If (Pos('callout name:',LowerCase(FileContent[StrToInt(Limiter[I])]) )=1) Then Begin
          FileBuf:=TStringList.Create;
          For J:=StrToInt(Limiter[I]) To StrToInt(Limiter[I+1])-1 Do
            FileBuf.Add(FileContent[J]);
          VCallout:=LoadCallout(0,False,0,0,FileBuf,RenList);
          FileBuf.Free;
     End;
     If (Pos('view:',LowerCase(FileContent[StrToInt(Limiter[I])]) )=1) Then Begin
          For J:=StrToInt(Limiter[I])+2 To StrToInt(Limiter[I+1])-1 Do
            View.Add(FileContent[J]);
     End;
  end;
  Limiter.Free;
  RenList.Free;
end;


Procedure TVScene.LoadOldFile(FileName:String);
Var FileContent:TStringList;
    I,J,K,N,ChunkLength:Longint;
    Curl:TVCurl;VObject:TVObject;Port:TVPort;VGroup:TVGroup;
    VConnection:TVConnection;VConnectionPort:TVConnectionPort;
    VCallout:TVCallout;
    CurrentLine,Keyword,Buf,ParsedBuf:String;
    AccObject:TAccObject;
    E,Sm,Rm,Scm,tmpm,TransM:TMatrix2D;
    Xw1s,Xw2s,Yw1s,Yw2s:String;
    Xw1,Xw2,Yw1,Yw2,Xb:Extended;
Begin
  Clear;
  FileContent:=TStringList.Create;
  FileContent.LoadFromFile(FileName);
  If FileContent.Count=0 Then LogMessage(0,'Empty File') Else Begin
    I:=0;
    Repeat
      CurrentLine:=FileContent[I];
      //================== Curl chunk ===================
      If Pos('Curl name:',CurrentLine)=1 Then Begin
        Buf:=CurrentLine;
        KeyWord:=GetNextToken(Buf,ParsedBuf,':');
        If ParsedBuf='' Then Begin
          LogMessage(0,'Error parsing file. Empty curl name after keyword at line '+IntToStr(I)+'.');
          FileContent.Free;
          Exit;
        End;
        If I+1>=FileContent.Count Then Begin
          LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+1)+'. Curl chunk length expected');
          FileContent.Free;
          Exit;
        End;

        //create curl here
        Curl:=TVcurl.Create;
        Curl.Name:=ParsedBuf;
        Curl.Saveable:=True;
        VCurlList.AddObject(Curl.Name,Curl);
        CurrentLine:=FileContent[I+1];
        If Pos('Lines:',CurrentLine)<>1 Then Begin
          LogMessage(0,'Error parsing file. Keyword "Lines:" expected at line '+IntToStr(I+1)+'.');
          FileContent.Free;
          Exit;
        End;
        Buf:=CurrentLine;
        KeyWord:=GetNextToken(Buf,ParsedBuf,':');
        Try ChunkLength:=StrToInt(ParsedBuf)
        Except
          LogMessage(0,'Error parsing file. Not an integer after keyword "Lines:" at line '+IntToStr(I+1)+'.');
          FileContent.Free;
          Exit;
        end;
        For J:=1 To ChunkLength Do Begin
          If I+1+J>=FileContent.Count Then Begin
            LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+1+J)+'.');
            FileContent.Free;
            Exit;
          End;
          CurrentLine:=FileContent[I+1+J];
          AccObject:=TAccObject.Create;
          AccObject.AcceleratedCommand:=AccelerateCommand(CurrentLine);
          Curl.DisplayList.AddObject(CurrentLine,AccObject);
        End;
        E:=GenShift(0,0);
        Curl.Shell:=Self.RenderShell(Curl.DisplayList,E);
        I:=I+1+J;
        Continue;
      end;
      //================== Object chunk ===================
      If Pos('Object name:',CurrentLine)=1 Then Begin
        Buf:=CurrentLine;
        KeyWord:=GetNextToken(Buf,ParsedBuf,':');
        If ParsedBuf='' Then Begin
          LogMessage(0,'Error parsing file. Empty curl name after keyword at line '+IntToStr(I)+'.');
          FileContent.Free;
          Exit;
        End;
        If I+1>=FileContent.Count Then Begin
          LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+1)+'. Curl chunk length expected');
          FileContent.Free;
          Exit;
        End;

        //create Object here
        VObject:=TVObject.Create;
        VObject.Name:=ParsedBuf;
        VObject.Saveable:=True;
        VObjectList.AddObject(VObject.Name,VObject);
        CurrentLine:=FileContent[I+1];
        If Pos('X:',CurrentLine)<>1 Then Begin
                    LogMessage(0,'Error parsing file. Keyword "X:" expected at line '+IntToStr(I+1)+'.');
                    FileContent.Free;
                    Exit;
                  End;
        Buf:=CurrentLine;
        KeyWord:=GetNextToken(Buf,ParsedBuf,':');
        Try VObject.X:=StrToFloat(ParsedBuf)
        Except
          LogMessage(0,'Error parsing file. Not an float after keyword "X:" at line '+IntToStr(I+1)+'.');
          FileContent.Free;
          Exit;
        end;
        CurrentLine:=FileContent[I+2];
        If Pos('Y:',CurrentLine)<>1 Then Begin
                    LogMessage(0,'Error parsing file. Keyword "Y:" expected at line '+IntToStr(I+2)+'.');
                    FileContent.Free;
                    Exit;
                  End;
        Buf:=CurrentLine;
        KeyWord:=GetNextToken(Buf,ParsedBuf,':');
        Try VObject.Y:=StrToFloat(ParsedBuf)
        Except
          LogMessage(0,'Error parsing file. Not an float after keyword "Y:" at line '+IntToStr(I+2)+'.');
          FileContent.Free;
          Exit;
        end;
        CurrentLine:=FileContent[I+3];
        If Pos('A:',CurrentLine)<>1 Then Begin
                    LogMessage(0,'Error parsing file. Keyword "A:" expected at line '+IntToStr(I+3)+'.');
                    FileContent.Free;
                    Exit;
                  End;
        Buf:=CurrentLine;
        KeyWord:=GetNextToken(Buf,ParsedBuf,':');
        Try VObject.A:=StrToFloat(ParsedBuf)
        Except
          LogMessage(0,'Error parsing file. Not an float after keyword "A:" at line '+IntToStr(I+3)+'.');
          FileContent.Free;
          Exit;
        end;
        CurrentLine:=FileContent[I+4];
        If Pos('S:',CurrentLine)<>1 Then Begin
                    LogMessage(0,'Error parsing file. Keyword "S:" expected at line '+IntToStr(I+4)+'.');
                    FileContent.Free;
                    Exit;
                  End;
        Buf:=CurrentLine;
        KeyWord:=GetNextToken(Buf,ParsedBuf,':');
        Try VObject.S:=StrToFloat(ParsedBuf)
        Except
          LogMessage(0,'Error parsing file. Not an float after keyword "S:" at line '+IntToStr(I+4)+'.');
          FileContent.Free;
          Exit;
        end;
        I:=I+5;
        CurrentLine:=FileContent[I];
        If Pos('DisplayList lines:',CurrentLine)<>1 Then Begin
          LogMessage(0,'Error parsing file. Keyword "DisplayList lines:" expected at line '+IntToStr(I)+'.');
          FileContent.Free;
          Exit;
        End;
        Buf:=CurrentLine;
        KeyWord:=GetNextToken(Buf,ParsedBuf,':');
        Try ChunkLength:=StrToInt(ParsedBuf)
        Except
          LogMessage(0,'Error parsing file. Not an integer after keyword "DisplayList lines:" at line '+IntToStr(I)+'.');
          FileContent.Free;
          Exit;
        end;
        For J:=1 To ChunkLength Do Begin
          If I+1+J>=FileContent.Count Then Begin
            LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+1+J)+'.');
            FileContent.Free;
            Exit;
          End;
          CurrentLine:=FileContent[I+J];
          AccObject:=TAccObject.Create;
          If Pos('DrawTemplate',CurrentLine)=1 Then Begin
            Buf:=CurrentLine;
            GetNextToken(Buf,ParsedBuf,' ');
            Buf:=ParsedBuf;
            VObject.Template:=GetNextToken(Buf,ParsedBuf,' ');
          End;
          AccObject.AcceleratedCommand:=AccelerateCommand(CurrentLine);
          VObject.DisplayList.AddObject(CurrentLine,AccObject);
        End;
        I:=I+1+J;
        CurrentLine:=FileContent[I];
        If Pos('PortList lines:',CurrentLine)<>1 Then Begin
          LogMessage(0,'Error parsing file. Keyword "PortList lines:" expected at line '+IntToStr(I)+'.');
          FileContent.Free;
          Exit;
        End;
        Buf:=CurrentLine;
        KeyWord:=GetNextToken(Buf,ParsedBuf,':');
        Try ChunkLength:=StrToInt(ParsedBuf)
        Except
          LogMessage(0,'Error parsing file. Not an integer after keyword "PortList lines:" at line '+IntToStr(I)+'.');
          FileContent.Free;
          Exit;
        end;
        If ChunkLength>0 Then Begin
          For J:=1 To ChunkLength Do Begin
            If I+J>=FileContent.Count Then Begin
              LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+J)+'.');
              FileContent.Free;
              Exit;
            End;
            CurrentLine:=FileContent[I+J];
            Port:=TVPort.Create;
            Port.Name:=GetValByName(CurrentLine,'Name');
            Port.AR:=GetValByName(CurrentLine,'AR');
            If Port.AR='A' Then Begin
              Port.AX:=StrToFloat(GetValByName(CurrentLine,'X'));
              Port.AY:=StrToFloat(GetValByName(CurrentLine,'Y'));
              //пересчитать относительные?
            End Else Begin
              Port.X:=StrToFloat(GetValByName(CurrentLine,'X'));
              Port.Y:=StrToFloat(GetValByName(CurrentLine,'Y'));
              //пересчитать абсолютные можно только после расчёта оболочки
            End;
            Port.Connection:=GetValByName(CurrentLine,'Conn');
            Port.ConnectionPort:=GetValByName(CurrentLine,'Port');
            VObject.PortList.AddObject(Port.Name,Port);
          End;
          I:=I+1+J;
        End Else I:=I+1;
        CurrentLine:=FileContent[I];
        If Pos('NonPortConnectionList lines:',CurrentLine)=1 Then Begin
{          LogMessage(0,'Error parsing file. Keyword "NonPortConnectionList lines:" expected at line '+IntToStr(I)+'.');
          FileContent.Free;
          Exit;
        End;}
        Buf:=CurrentLine;
        KeyWord:=GetNextToken(Buf,ParsedBuf,':');
        Try ChunkLength:=StrToInt(ParsedBuf)
        Except
          LogMessage(0,'Error parsing file. Not an integer after keyword "NonPortConnectionList lines:" at line '+IntToStr(I)+'.');
          FileContent.Free;
          Exit;
        end;
        If ChunkLength>0 Then Begin
          For J:=1 To ChunkLength+1 Do Begin
            If I+J>=FileContent.Count Then Begin
              LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+J)+'.');
              FileContent.Free;
              Exit;
            End;
            CurrentLine:=FileContent[I+J-1];
            VObject.NonPortConnectionList.Add(CurrentLine);
          End;
          I:=I+J;
        end Else I:=I+1;
      End;
      CurrentLine:=FileContent[I];
      If Pos('GroupList lines:',CurrentLine)=1 Then Begin
{          LogMessage(0,'Error parsing file. Keyword "GroupList lines:" expected at line '+IntToStr(I)+'.');
        FileContent.Free;
        Exit;
      End;}
      Buf:=CurrentLine;
      KeyWord:=GetNextToken(Buf,ParsedBuf,':');
      Try ChunkLength:=StrToInt(ParsedBuf)
      Except
        LogMessage(0,'Error parsing file. Not an integer after keyword "GroupList lines:" at line '+IntToStr(I)+'.');
        FileContent.Free;
        Exit;
      end;
      If ChunkLength>0 Then Begin
        For J:=1 To ChunkLength+1 Do Begin
          If I+J>=FileContent.Count Then Begin
            LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+J)+'.');
            FileContent.Free;
            Exit;
          End;
          CurrentLine:=FileContent[I+J-1];
          N:=VGroupList.IndexOf(CurrentLine);
          If N>=0 Then Begin
            VGroup:=VGroupList.Objects[N] As TVGroup;
            VGroup.DisplayList.Add(VObject.Name);
            VObject.GroupList.AddObject(CurrentLine,VGroup);
          end;
        End;
        I:=I+J;
      end Else I:=I+1;
    End;
      CurrentLine:=FileContent[I];
      If Pos('ActionList lines:',CurrentLine)=1 Then Begin
{          LogMessage(0,'Error parsing file. Keyword "ActionList lines:" expected at line '+IntToStr(I)+'.');
        FileContent.Free;
        Exit;
      End;}
      Buf:=CurrentLine;
      KeyWord:=GetNextToken(Buf,ParsedBuf,':');
      Try ChunkLength:=StrToInt(ParsedBuf)
      Except
        LogMessage(0,'Error parsing file. Not an integer after keyword "ActionList lines:" at line '+IntToStr(I)+'.');
        FileContent.Free;
        Exit;
      end;
      If ChunkLength>0 Then Begin
        For J:=1 To ChunkLength Do Begin
          If I+J>=FileContent.Count Then Begin
            LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+J)+'.');
            FileContent.Free;
            Exit;
          End;
          CurrentLine:=FileContent[I+J];
          VObject.ActionList.Add(CurrentLine);
        End;
        I:=I+J;
      end Else I:=I+1;
    End;
        If VObject.A>=720 Then Begin
          For K:=0 To VObject.DisplayList.Count-1 Do
            If (Pos('Rect',VObject.DisplayList[K])=1) Or
               (Pos('RRect',VObject.DisplayList[K])=1) Or
               (Pos('Ellipse',VObject.DisplayList[K])=1) Then Begin
                     Buf:=VObject.DisplayList[K];
                     GetNextToken(Buf,ParsedBuf,' ');
                     Buf:=ParsedBuf;
                     Xw1s:=GetNextToken(Buf,ParsedBuf,',');
                     Buf:=ParsedBuf;
                     Yw1s:=GetNextToken(Buf,ParsedBuf,',');
                     Buf:=ParsedBuf;
                     Xw2s:=GetNextToken(Buf,ParsedBuf,',');
                     Yw2s:=ParsedBuf;
                     Xw1:=StrToFloat(Xw1s);
                     Yw1:=StrToFloat(Yw1s);
                     Xw2:=StrToFloat(Xw2s);
                     Yw2:=StrToFloat(Yw2s);
                     If Xw1>Xw2 Then Begin
                       XB:=Xw1;Xw1:=Xw2;Xw2:=XB
                     End;
                     If Yw1>Yw2 Then Begin
                       XB:=Yw1;Yw1:=Yw2;Yw2:=XB
                     End;
                     VObject.W:=Xw2-Xw1;
                     VObject.H:=Yw2-Yw1;
                     Break;
               end;

        end;
        If VObject.DisplayList.Count=0 Then Begin
          //Shell пустого объекта = точке вставки?
          VObject.Shell.X1:=VObject.X;
          VObject.Shell.X2:=VObject.X;
          VObject.Shell.Y1:=VObject.Y;
          VObject.Shell.Y2:=VObject.Y;
        End Else Begin
          //вот тут надо проверить, загружены ли Templatы и если надо - загрузить
          // но это произойддет в LoadDocument
          Sm:=GenShift(VObject.X,VObject.Y);
          Rm:=GenRotate(VObject.X,VObject.Y,VObject.A/180*pi);
          Scm:=GenScale(VObject.X,VObject.Y,VObject.S,VObject.S);
          tmpm:=MulMat2D(Sm,Rm);
          TransM:=MulMat2D(Tmpm,Scm);
          VObject.Shell:=Self.RenderShell(VObject.DisplayList,TransM);
          //пересчёт относительных и абсолютных координат портов
          //может только после пересчета оболочки с использованием всех загруженных шаблонов
{          If VObject.PortList.Count>0 Then
            For J:=0 To VObject.PortList.Count-1 Do Begin
              Port:=VObject.PortList.Objects[J] As TVPort;
              If Port.AR='A' Then Begin
                Port.X:=(Port.AX-VObject.Shell.X1)/(VObject.Shell.X2-VObject.Shell.X1);
                Port.Y:=(Port.AY-VObject.Shell.Y1)/(VObject.Shell.Y2-VObject.Shell.Y1);
              End Else Begin
                Port.AX:=VObject.Shell.X1+Port.X*(VObject.Shell.X2-VObject.Shell.X1);
                Port.AY:=VObject.Shell.Y1+Port.Y*(VObject.Shell.Y2-VObject.Shell.Y1);
              End;
              //Добавление портов в DisplayList
              AccObject:=TAccObject.Create;
              AccObject.AcceleratedCommand:=AccelerateCommand('Circle '+FloatToStr(Port.Ax)+','+FloatToStr(Port.Ay)+','+
              FloatToStr((VObject.Shell.X2-VObject.Shell.X1)/10));
              VObject.DisplayList.AddObject('Circle '+FloatToStr(Port.Ax)+','+FloatToStr(Port.Ay)+','+
              FloatToStr((VObject.Shell.X2-VObject.Shell.X1)/10),AccObject);
            end;}
        End;
        ObjectAttr2Style(VObject);
        ObjectStyle2Attr(VObject);
        Continue;
      end;
      //================== Connection chunk ===================
            If Pos('Connection name:',CurrentLine)=1 Then Begin
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              If ParsedBuf='' Then Begin
                LogMessage(0,'Error parsing file. Empty connection name after keyword at line '+IntToStr(I)+'.');
                FileContent.Free;
                Exit;
              End;
              If I+1>=FileContent.Count Then Begin
                LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+1)+'. Connection chunk length expected');
                FileContent.Free;
                Exit;
              End;

              //create Connection here
              VConnection:=TVConnection.Create;
              VConnection.Name:=ParsedBuf;
              VConnection.Saveable:=True;
              VConnectionList.AddObject(VConnection.Name,VConnection);
              CurrentLine:=FileContent[I+1];
              If Pos('X:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "X:" expected at line '+IntToStr(I+1)+'.');
                          FileContent.Free;
                          Exit;
              End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
{              Try VConnection.X:=StrToFloat(ParsedBuf)
              Except
                LogMessage(0,'Error parsing file. Not a float after keyword "X:" at line '+IntToStr(I+1)+'.');
                FileContent.Free;
                Exit;
              end;}
              CurrentLine:=FileContent[I+2];
              If Pos('Y:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "Y:" expected at line '+IntToStr(I+2)+'.');
                          FileContent.Free;
                          Exit;
                        End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
{              Try VConnection.Y:=StrToFloat(ParsedBuf)
              Except
                LogMessage(0,'Error parsing file. Not a float after keyword "Y:" at line '+IntToStr(I+2)+'.');
                FileContent.Free;
                Exit;
              end;}
              CurrentLine:=FileContent[I+3];
              If Pos('StartObj:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "StartObj:" expected at line '+IntToStr(I+3)+'.');
                          FileContent.Free;
                          Exit;
                        End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              If ParsedBuf<>'' Then  VConnection.StartObj:=ParsedBuf
              Else Begin
                LogMessage(0,'Error parsing file. Empty Start object at line '+IntToStr(I+3)+'.');
                FileContent.Free;
                Exit;
              end;
              CurrentLine:=FileContent[I+4];
              If Pos('EndObj:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "EndObj:" expected at line '+IntToStr(I+4)+'.');
                          FileContent.Free;
                          Exit;
              End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              If ParsedBuf<>'' Then VConnection.EndObj:=ParsedBuf
              Else Begin
                LogMessage(0,'Error parsing file. Empty End object at line '+IntToStr(I+4)+'.');
                FileContent.Free;
                Exit;
              end;
              CurrentLine:=FileContent[I+5];
              If Pos('Style:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "Style:" expected at line '+IntToStr(I+5)+'.');
                          FileContent.Free;
                          Exit;
              End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              If ParsedBuf<>'' Then  VConnection.Style:=ParsedBuf
              Else Begin
                LogMessage(0,'Error parsing file. Empty Style object at line '+IntToStr(I+5)+'.');
                FileContent.Free;
                Exit;
              end;
              I:=I+6;
              CurrentLine:=FileContent[I];
              If Pos('GroupList lines:',CurrentLine)=1 Then Begin
   {            LogMessage(0,'Error parsing file. Keyword "GroupList lines:" expected at line '+IntToStr(I)+'.');
                FileContent.Free;
                Exit;
              End;}
                Buf:=CurrentLine;
                KeyWord:=GetNextToken(Buf,ParsedBuf,':');
                Try ChunkLength:=StrToInt(ParsedBuf)
                Except
                  LogMessage(0,'Error parsing file. Not an integer after keyword "GroupList lines:" at line '+IntToStr(I)+'.');
                  FileContent.Free;
                  Exit;
                end;
                If ChunkLength>0 Then Begin
                  For J:=1 To ChunkLength+1 Do Begin
                    If I+J>=FileContent.Count Then Begin
                    LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+J)+'.');
                    FileContent.Free;
                    Exit;
                  End;
                  CurrentLine:=FileContent[I+J-1];
                  N:=VGroupList.IndexOf(CurrentLine);
                  If N>=0 Then Begin
                    VGroup:=VGroupList.Objects[N] As TVGroup;
                    VGroup.DisplayList.Add(VConnection.Name);
                    VConnection.GroupList.AddObject(CurrentLine,VGroup);
                  end;
                End;
                I:=I+J;
                end Else I:=I+1;
              End;
              CurrentLine:=FileContent[I];
              If Pos('PortList lines:',CurrentLine)<>1 Then Begin
                LogMessage(0,'Error parsing file. Keyword "PortList lines:" expected at line '+IntToStr(I)+'.');
                FileContent.Free;
                Exit;
              End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              Try ChunkLength:=StrToInt(ParsedBuf)
              Except
                LogMessage(0,'Error parsing file. Not an integer after keyword "PortList lines:" at line '+IntToStr(I)+'.');
                FileContent.Free;
                Exit;
              end;
              If ChunkLength>0 Then Begin
                For J:=1 To ChunkLength Do Begin
                  If I+1+J>=FileContent.Count Then Begin
                    LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+1+J)+'.');
                    FileContent.Free;
                    Exit;
                  End;
                  CurrentLine:=FileContent[I+J];
                  VConnectionPort:=TVConnectionPort.Create;
                  VConnectionPort.PortType:=GetValByName(CurrentLine,'PortType');
                  VConnectionPort.AR:=GetValByName(CurrentLine,'AR');
                  VConnectionPort.X:=StrToFloat(GetValByName(CurrentLine,'X'));
                  VConnectionPort.Y:=StrToFloat(GetValByName(CurrentLine,'Y'));
                  VConnectionPort.ConnectedObject:=GetNextToken(GetValByName(CurrentLine,'Conn'),
                          VConnectionPort.ConnectedPort,':');
                  VConnection.PortList.AddObject(VConnectionPort.PortType,VConnectionPort);

{   FileContent.Add('PortType='+ConnPort.PortType+' AR='+ConnPort.AR+' X='+
               FloatToStr(ConnPort.X)+' Y='+FloatToStr(ConnPort.Y)+' Conn='+ConnPort.ConnectedObject+':'+ConnPort.ConnectedPort);
}
                End;
                I:=I+1+J;
              end Else I:=I+1;
              ConnectionAttr2Style(VConnection);
              ConnectionStyle2Attr(VConnection);
              Self.UpdateConnectionDisplayList(VConnection);
              Continue;
            end;

//================== Callout chunk ===================
            If Pos('Callout name:',CurrentLine)=1 Then Begin
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              If ParsedBuf='' Then Begin
                LogMessage(0,'Error parsing file. Empty callout name after keyword at line '+IntToStr(I)+'.');
                FileContent.Free;
                Exit;
              End;
              If I+1>=FileContent.Count Then Begin
                LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+1)+'. Callout chunk length expected');
                FileContent.Free;
                Exit;
              End;

              //create Callout here
              VCallout:=TVCallout.Create;
              VCallout.Name:=ParsedBuf;
              VCallout.Saveable:=True;
              VCalloutList.AddObject(VCallout.Name,VCallout);
              CurrentLine:=FileContent[I+1];
              If Pos('X:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "X:" expected at line '+IntToStr(I+1)+'.');
                          FileContent.Free;
                          Exit;
              End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              Try VCallout.X:=StrToFloat(ParsedBuf)
              Except
                LogMessage(0,'Error parsing file. Not a float after keyword "X:" at line '+IntToStr(I+1)+'.');
                FileContent.Free;
                Exit;
              end;
              CurrentLine:=FileContent[I+2];
              If Pos('Y:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "Y:" expected at line '+IntToStr(I+2)+'.');
                          FileContent.Free;
                          Exit;
                        End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              Try VCallout.Y:=StrToFloat(ParsedBuf)
              Except
                LogMessage(0,'Error parsing file. Not a float after keyword "Y:" at line '+IntToStr(I+2)+'.');
                FileContent.Free;
                Exit;
              end;
              CurrentLine:=FileContent[I+3];
              If Pos('W:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "W:" expected at line '+IntToStr(I+3)+'.');
                          FileContent.Free;
                          Exit;
                        End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              Try VCallout.W:=StrToFloat(ParsedBuf)
              Except
                LogMessage(0,'Error parsing file. Not a float after keyword "W:" at line '+IntToStr(I+3)+'.');
                FileContent.Free;
                Exit;
              end;
              CurrentLine:=FileContent[I+4];
              If Pos('H:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "H:" expected at line '+IntToStr(I+4)+'.');
                          FileContent.Free;
                          Exit;
                        End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              Try VCallout.H:=StrToFloat(ParsedBuf)
              Except
                LogMessage(0,'Error parsing file. Not a float after keyword "H:" at line '+IntToStr(I+4)+'.');
                FileContent.Free;
                Exit;
              end;
              CurrentLine:=FileContent[I+5];
              If Pos('RelatedObj:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "RelatedObj:" expected at line '+IntToStr(I+5)+'.');
                          FileContent.Free;
                          Exit;
                        End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              VCallout.RelatedObj:=ParsedBuf;
              //здесь надо найти соответствующий объект и добавить ему в список связанных эту выноску или реализовать это в основной программе
              CurrentLine:=FileContent[I+6];
              If Pos('Style:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "Style:" expected at line '+IntToStr(I+6)+'.');
                          FileContent.Free;
                          Exit;
              End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              If ParsedBuf<>'' Then VCallout.Style:=ParsedBuf
              Else Begin
                LogMessage(0,'Error parsing file. Empty Style at line '+IntToStr(I+6)+'.');
                FileContent.Free;
                Exit;
              end;
              VCallout.Style:=ParsedBuf;
              CurrentLine:=FileContent[I+7];
              If Pos('Source:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "Source:" expected at line '+IntToStr(I+7)+'.');
                          FileContent.Free;
                          Exit;
                        End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              VCallout.Source:=ParsedBuf;
              CurrentLine:=FileContent[I+8];
              If Pos('Text:',CurrentLine)<>1 Then Begin
                          LogMessage(0,'Error parsing file. Keyword "Text:" expected at line '+IntToStr(I+7)+'.');
                          FileContent.Free;
                          Exit;
              End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              VCallout.Text:=ParsedBuf;

               Sm:=GenShift(VCallout.X,VCallout.Y);
               Rm:=GenRotate(VCallout.X,VCallout.Y,0);
               Scm:=GenScale(VCallout.X,VCallout.Y,1,1);
               tmpm:=MulMat2D(Sm,Rm);
               TransM:=MulMat2D(Tmpm,Scm);
               VCallout.Shell:=Self.RenderShell(VCallout.DisplayList,TransM);

              I:=I+9;
              CurrentLine:=FileContent[I];
              If Pos('GroupList lines:',CurrentLine)=1 Then Begin
   {            LogMessage(0,'Error parsing file. Keyword "GroupList lines:" expected at line '+IntToStr(I)+'.');
                FileContent.Free;
                Exit;
              End;}
                Buf:=CurrentLine;
                KeyWord:=GetNextToken(Buf,ParsedBuf,':');
                Try ChunkLength:=StrToInt(ParsedBuf)
                Except
                  LogMessage(0,'Error parsing file. Not an integer after keyword "GroupList lines:" at line '+IntToStr(I)+'.');
                  FileContent.Free;
                  Exit;
                end;
                If ChunkLength>0 Then Begin
                  For J:=1 To ChunkLength+1 Do Begin
                    If I+J>=FileContent.Count Then Begin
                    LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+J)+'.');
                    FileContent.Free;
                    Exit;
                  End;
                  CurrentLine:=FileContent[I+J-1];
                  N:=VGroupList.IndexOf(CurrentLine);
                  If N>=0 Then Begin
                    VGroup:=VGroupList.Objects[N] As TVGroup;
                    VGroup.DisplayList.Add(VCallout.Name);
                    VCallout.GroupList.AddObject(CurrentLine,VGroup);
                  end;
                End;
                I:=I+J;
                end Else I:=I+1;
              End;
              CurrentLine:=FileContent[I];
              If Pos('PortList lines:',CurrentLine)<>1 Then Begin
                LogMessage(0,'Error parsing file. Keyword "PortList lines:" expected at line '+IntToStr(I)+'.');
                FileContent.Free;
                Exit;
              End;
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              Try ChunkLength:=StrToInt(ParsedBuf)
              Except
                LogMessage(0,'Error parsing file. Not an integer after keyword "PortList lines:" at line '+IntToStr(I)+'.');
                FileContent.Free;
                Exit;
              end;
              If ChunkLength>0 Then Begin
                For J:=1 To ChunkLength Do Begin
                  If I+1+J>=FileContent.Count Then Begin
                    LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+1+J)+'.');
                    FileContent.Free;
                    Exit;
                  End;
                  CurrentLine:=FileContent[I+J];
                  VConnectionPort:=TVConnectionPort.Create;
                  VConnectionPort.PortType:=GetValByName(CurrentLine,'PortType');
                  VConnectionPort.AR:=GetValByName(CurrentLine,'AR');
                  VConnectionPort.X:=StrToFloat(GetValByName(CurrentLine,'X'));
                  VConnectionPort.Y:=StrToFloat(GetValByName(CurrentLine,'Y'));
                  VConnectionPort.ConnectedObject:=GetNextToken(GetValByName(CurrentLine,'Conn'),
                          VConnectionPort.ConnectedPort,':');
                  VCallout.PortList.AddObject(VConnectionPort.PortType,VConnectionPort);

{   FileContent.Add('PortType='+ConnPort.PortType+' AR='+ConnPort.AR+' X='+
               FloatToStr(ConnPort.X)+' Y='+FloatToStr(ConnPort.Y)+' Conn='+ConnPort.ConnectedObject+':'+ConnPort.ConnectedPort);
}
                End;
                I:=I+1+J;
              end Else I:=I+1;

              CurrentLine:=FileContent[I];
              If Pos('ActionList lines:',CurrentLine)=1 Then Begin
        {          LogMessage(0,'Error parsing file. Keyword "ActionList lines:" expected at line '+IntToStr(I)+'.');
                FileContent.Free;
                Exit;
              End;}
              Buf:=CurrentLine;
              KeyWord:=GetNextToken(Buf,ParsedBuf,':');
              Try ChunkLength:=StrToInt(ParsedBuf)
              Except
                LogMessage(0,'Error parsing file. Not an integer after keyword "ActionList lines:" at line '+IntToStr(I)+'.');
                FileContent.Free;
                Exit;
              end;
              If ChunkLength>0 Then Begin
                For J:=1 To ChunkLength Do Begin
                  If I+J>=FileContent.Count Then Begin
                    LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+J)+'.');
                    FileContent.Free;
                    Exit;
                  End;
                  CurrentLine:=FileContent[I+J];
                  VCallout.ActionList.Add(CurrentLine);
                End;
                I:=I+J;
              end Else I:=I+1;
            End;

// было здесь но должно быть в основной из-за оболочек объектов, которые будут только после загрузки темплейтов
//              UpdateCalloutDisplayList(Self,VCallout);
  VCallout.Width:=GetValByName(VCallout.Style,'Width');
  If VCallout.Width='' Then VCallout.Width:='0';
  VCallout.Color:=GetValByName(VCallout.Style,'Color');
  If VCallout.Color='' Then VCallout.Color:='clBlack';
  VCallout.BGColor:=GetValByName(VCallout.Style,'BGColor');
  If VCallout.BGColor='' Then VCallout.BGColor:='clGray';
  VCallout.Transparent:=GetValByName(VCallout.Style,'Transparent');
  If VCallout.Transparent='' Then VCallout.Transparent:='no';

  VCallout.LineStyle:=GetValByName(VCallout.Style,'LineStyle');

  VCallout.FontName:=GetValByName(VCallout.Style,'FontName');
  If VCallout.FontName='' Then VCallout.FontName:='default';
  VCallout.FontColor:=GetValByName(VCallout.Style,'FontColor');
  If VCallout.FontColor='' Then VCallout.FontColor:='clBlack';
  VCallout.FontHeight:=GetValByName(VCallout.Style,'FontHeight');
  If VCallout.FontHeight='' Then VCallout.FontHeight:='10';
  VCallout.BIUS:=GetValByName(VCallout.Style,'BIUS');
  VCallout.BindMode:=GetValByName(VCallout.Style,'BindMode');
            CalloutAttr2Style(VCallout);
            CalloutStyle2Attr(VCallout);
            Continue;


            end;

      //================== Group chunk ===================
                  If Pos('Group name:',CurrentLine)=1 Then Begin
                    Buf:=CurrentLine;
                    KeyWord:=GetNextToken(Buf,ParsedBuf,':');
                    If ParsedBuf='' Then Begin
                      LogMessage(0,'Error parsing file. Empty group name after keyword at line '+IntToStr(I)+'.');
                      FileContent.Free;
                      Exit;
                    End;
                    If I+1>=FileContent.Count Then Begin
                      LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+1)+'. Connection chunk length expected');
                      FileContent.Free;
                      Exit;
                    End;

                    //create Group here
                    VGroup:=TVGroup.Create;
                    VGroup.Name:=ParsedBuf;
                    VGroupList.AddObject(VGroup.Name,VGroup);
                    CurrentLine:=FileContent[I+1];

                    VGroup.Glue:=GetValByName(CurrentLine,'Glue')='1';
                    VGroup.Visible:=GetValByName(CurrentLine,'Visible')='1';
                    VGroup.Active:=GetValByName(CurrentLine,'Active')='1';

                    I:=I+2;
                    CurrentLine:=FileContent[I];
                    Continue;
                  end;

      // ====================== View chunk ===================================
      If Pos('View:',CurrentLine)=1 Then Begin
        Buf:=CurrentLine;
        KeyWord:=GetNextToken(Buf,ParsedBuf,':');
        If ParsedBuf='' Then Begin
          LogMessage(0,'Error parsing file. Empty view name after keyword at line '+IntToStr(I)+'.');
          FileContent.Free;
          Exit;
        End;
        If I+1>=FileContent.Count Then Begin
          LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+1)+'. View chunk length expected');
          FileContent.Free;
          Exit;
        End;
        CurrentLine:=FileContent[I+1];
        If Pos('Lines:',CurrentLine)<>1 Then Begin
          LogMessage(0,'Error parsing file. Keyword "Lines:" expected at line '+IntToStr(I+1)+'.');
          FileContent.Free;
          Exit;
        End;
        Buf:=CurrentLine;
        Keyword:=GetNextToken(Buf,ParsedBuf,':');
        Try ChunkLength:=StrToInt(ParsedBuf)
        Except
          LogMessage(0,'Error parsing file. Not an integer after keyword "Lines:" at line '+IntToStr(I+1)+'.');
          FileContent.Free;
          Exit;
        End;
        For J:=1 To ChunkLength Do Begin
          If I+1+J>=FileContent.Count Then Begin
            LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(I+1+J)+'.');
            FileContent.Free;
            Exit;
          End;
          CurrentLine:=FileContent[I+1+J];
          View.Add(CurrentLine);
        End;
        I:=I+1+J;
      End;
      Inc(I);
    Until I=FileContent.Count;
  end;
  FileContent.Free;
end;

Function TVScene.SaveGroup(VGroup:TVGroup):TStringList;
Var FileContent:TStringList;
    JCount,J:Longint;
    Port:TVPort;
    Buf:String;
Begin
  FileContent:=TStringList.Create;
  FileContent.Add('Group name:'+VGroup.Name);
  If VGroup.Glue Then Buf:='Glue=1' Else Buf:='Glue=0';
  If VGroup.Active Then Buf:=Buf+' Active=1' Else Buf:=Buf+' Active=0';
  If VGroup.Visible Then Buf:=Buf+' Visible=1' Else Buf:=Buf+' Visible=0';
  FileContent.Add(Buf);
  Result:=FileContent;
End;

Function TVScene.LoadGroup(Base:Longint;Add:Boolean;DX,DY:Extended;Var FileContent,RenList:TStringList):TVGroup;
Var VGroup:TVGroup;
    I,J,N,ChunkLength:Longint;
    Buf,ParsedBuf,CurrentLine,KeyWord:String;
Begin
  I:=0;
  CurrentLine:=FileContent[I];
  If Pos('Group name:',CurrentLine)=1 Then Begin
    Buf:=CurrentLine;
    KeyWord:=GetNextToken(Buf,ParsedBuf,':');
    If ParsedBuf='' Then Begin
      LogMessage(0,'Error parsing file. Empty group name after keyword at line '+IntToStr(Base+I)+'.');
      FileContent.Free;
      Exit;
    End;
    If I+1>=FileContent.Count Then Begin
      LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+1)+'. Connection chunk length expected');
      FileContent.Free;
      Exit;
    End;
                   //create Group here
    VGroup:=TVGroup.Create;
    VGroup.Name:=ParsedBuf;
    VGroupList.AddObject(VGroup.Name,VGroup);
    CurrentLine:=FileContent[I+1];
    VGroup.Glue:=GetValByName(CurrentLine,'Glue')='1';
    VGroup.Visible:=GetValByName(CurrentLine,'Visible')='1';
    VGroup.Active:=GetValByName(CurrentLine,'Active')='1';
  end;
End;


Function TVScene.SaveCurl(VCurl:TVCurl):TStringList;
Var FileContent:TStringList;
    JCount,J:Longint;
    Port:TVPort;
    Buf:String;
Begin
    FileContent:=TStringList.Create;
    FileContent.Add('Curl name:'+VCurl.Name);
    FileContent.Add('Lines:'+IntToStr(VCurl.DisplayList.Count));
    FileContent.AddStrings(VCurl.DisplayList);
    Result:=FileContent;
end;

Function TVScene.LoadCurl(Base:Longint;Add:Boolean;Var FileContent:TStringList):TVCurl;
Var Curl:TVCurl;
    AccObject:TAccObject;
    Port:TVPort;
    VGroup:TVGroup; //должна ли копия объект входить в те же группы, что оригинал?
    I,J,N,ChunkLength:Longint;
    Buf,ParsedBuf,CurrentLine,KeyWord:String;
    Sm,Rm,Scm,tmpm,TransM:TMatrix2D;
Begin
  I:=0;
  CurrentLine:=FileContent[I];
  If Pos('Curl name:',CurrentLine)=1 Then Begin
    Buf:=CurrentLine;
    KeyWord:=GetNextToken(Buf,ParsedBuf,':');
    If ParsedBuf='' Then Begin
      LogMessage(0,'Error parsing file. Empty curl name after keyword at line '+IntToStr(Base+I)+'.');
      FileContent.Free;
      Exit;
    End;
    If I+1>=FileContent.Count Then Begin
      LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+1)+'. Curl chunk length expected');
      FileContent.Free;
      Exit;
    End;

    //create curl here
    Curl:=TVcurl.Create;
    Curl.Name:=ParsedBuf;
    Curl.Saveable:=True;
    If VCurlList.IndexOf(ParsedBuf)<0 Then
      Curl.Name:=ParsedBuf
    Else Begin
      Curl.Name:=Self.GetNewName(Self.VCurlList,'')
//      RenList.Add(ParsedBuf+'='+Curl.Name);
    end;
    VCurlList.AddObject(Curl.Name,Curl);
    CurrentLine:=FileContent[I+1];
    If Pos('Lines:',CurrentLine)<>1 Then Begin
      LogMessage(0,'Error parsing file. Keyword "Lines:" expected at line '+IntToStr(Base+I+1)+'.');
      FileContent.Free;
      Exit;
    End;
    Buf:=CurrentLine;
    KeyWord:=GetNextToken(Buf,ParsedBuf,':');
    Try ChunkLength:=StrToInt(ParsedBuf)
    Except
      LogMessage(0,'Error parsing file. Not an integer after keyword "Lines:" at line '+IntToStr(Base+I+1)+'.');
      FileContent.Free;
      Exit;
    end;
    For J:=1 To ChunkLength Do Begin
      If I+1+J>=FileContent.Count Then Begin
        LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+1+J)+'.');
        FileContent.Free;
        Exit;
      End;
      CurrentLine:=FileContent[I+1+J];
      AccObject:=TAccObject.Create;
      AccObject.AcceleratedCommand:=AccelerateCommand(CurrentLine);
      Curl.DisplayList.AddObject(CurrentLine,AccObject);
    End;
    E:=GenShift(0,0);
    Curl.Shell:=RenderShell(Curl.DisplayList,E);
    I:=I+1+J;
    Self.View.Add('DrawCurl '+Curl.Name);
  end;

end;

Function TVScene.SaveObject(VObject:TVObject):TStringList;
Var FileContent:TStringList;
    JCount,J:Longint;
    Port:TVPort;
    Buf,StyleString:String;
Begin
      FileContent:=TStringList.Create;
      FileContent.Add('Object name:'+VObject.Name);
{      StyleString:='Style: '+'X='+FloatToStr(VObject.X)+' '+
                       'Y='+FloatToStr(VObject.Y)+' '+
                       'A='+FloatToStr(VObject.A)+' '+
                       'S='+FloatToStr(VObject.S)+' ';
      If VObject.A>700 Then Begin
        StyleString:=StyleString+'W='+FloatToStr(VObject.W)+' H='+FloatToStr(VObject.H)+
                     ' Width='+VObject.Width+' Color='+VObject.Color
                     +' BGColor='+VObject.BGColor;
      End;}
      FileContent.Add('Style: '+VObject.Style);
      JCount:=0;
      If VObject.DisplayList.Count>0 Then
        For J:=0 To VObject.DisplayList.Count-1 Do Begin
          If Pos('UnScaled ',VObject.DisplayList[J])=1 Then Continue;
          Inc(JCount);
        End;
      If JCount>0 Then Begin
        FileContent.Add('DisplayList lines:'+IntToStr(JCount));
        If VObject.DisplayList.Count>0 Then
          For J:=0 To VObject.DisplayList.Count-1 Do Begin
            If Pos('UnScaled ',VObject.DisplayList[J])=1 Then Continue;
            FileContent.Add(VObject.DisplayList[J]);
          End;
      end;
      If VObject.PortList.Count>0 Then Begin
        FileContent.Add('PortList lines:'+IntToStr(VObject.PortList.Count));
        If VObject.PortList.Count>0 Then Begin
          For J:=0 To VObject.PortList.Count-1 Do Begin
            Port:=VObject.PortList.Objects[J] As TVPort;
            Buf:='Name='+Port.Name+' '+'AR='+Port.AR;
            If Port.AR='A'
            Then Buf:=Buf+' X='+FloatToStr(Port.AX)+' Y='+FloatToStr(Port.AY)
            Else Buf:=Buf+' X='+FloatToStr(Port.X)+' Y='+FloatToStr(Port.Y);
            Buf:=Buf+' Conn='+Port.Connection+' Port='+Port.ConnectionPort;
            FileContent.Add(Buf);
          End;
        End;
      End;
      If VObject.NonPortConnectionList.Count>0 Then Begin
        FileContent.Add('NonPortConnectionList lines:'+IntToStr(VObject.NonPortConnectionList.Count));
        If VObject.NonPortConnectionList.Count>0 Then
          FileContent.AddStrings(VObject.NonPortConnectionList);
      end;
      If VObject.GroupList.Count>0 Then Begin
        FileContent.Add('GroupList lines:'+IntToStr(VObject.GroupList.Count));
        If VObject.GroupList.Count>0 Then
          FileContent.AddStrings(VObject.GroupList);
      End;
      If VObject.ActionList.Count>0 Then Begin
        FileContent.Add('ActionList lines:'+IntToStr(VObject.ActionList.Count));
        FileContent.AddStrings(VObject.ActionList);
      end;
      Result:=FileContent;
End;

Function TVScene.LoadObject(Base:Longint;Add:Boolean;DX,DY:Extended;Var FileContent,RenList:TStringList):TVObject;
// Base - текущее положение в тексте, от которого отсчитываются стоки чанка
// Add - флаг, True=добавление (копирование из кармана)
Var VObject:TVObject;
    AccObject:TAccObject;
    Port:TVPort;
    VGroup:TVGroup; //должна ли копия объект входить в те же группы, что оригинал?
    I,J,N,ChunkLength:Longint;
    Style,Buf,ParsedBuf,CurrentLine,KeyWord:String;
    Sm,Rm,Scm,tmpm,TransM:TMatrix2D;
Begin
  I:=0;
  While I<=FileContent.Count-1 Do Begin
    CurrentLine:=FileContent[I];
    Buf:=CurrentLine;
    KeyWord:=GetNextToken(Buf,ParsedBuf,':');
    If Pos('Object name:',CurrentLine)=1 Then Begin
      If ParsedBuf='' Then Begin
        LogMessage(0,'Error parsing file. Empty object name after keyword at line '+IntToStr(Base+I)+'.');
        FileContent.Free;
        Exit;
      End;
      If I+1>=FileContent.Count Then Begin
        LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+1)+'. Callout chunk length expected');
        FileContent.Free;
        Exit;
      End;
      //create Object here
      VObject:=TVObject.Create;
      VObject.Saveable:=True;
//      If Add Then Begin
        If VObjectList.IndexOf(ParsedBuf)<0 Then Begin
          //Если на сцене нет элемента с таким именем - оставляем имя без изменений,
          // если есть - генерируем новое имя и заносим в список переименования
          VObject.Name:=ParsedBuf;
        End Else Begin
          VObject.Name:=Self.GetNewName(Self.VObjectList,'');
          RenList.Add(ParsedBuf+'='+VObject.Name);
        end;
  //    Else VObject.Name:=ParsedBuf;    //добавить проверку на дубликат
      VObjectList.AddObject(VObject.Name,VObject);
      Inc(I);
      Continue;
    End;
    If Pos('Style:',CurrentLine)=1 Then Begin
      If ParsedBuf<>'' Then VObject.Style:=ParsedBuf
      Else Begin
        LogMessage(0,'Error parsing file. Empty Style at line '+IntToStr(Base+I)+'.');
        FileContent.Free;
        Exit;
      end;
      Inc(I);
      Continue;
    End;
    If Pos('DisplayList lines:',CurrentLine)=1 Then Begin
      Try ChunkLength:=StrToInt(ParsedBuf)
      Except
        LogMessage(0,'Error parsing file. Not an integer after keyword "DisplayList lines:" at line '+IntToStr(Base+I)+'.');
        FileContent.Free;
        Exit;
      end;
      For J:=1 To ChunkLength Do Begin
        If I+J>=FileContent.Count Then Begin
          LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+J)+'.');
          FileContent.Free;
          Exit;
        End;
        CurrentLine:=FileContent[I+J];
        AccObject:=TAccObject.Create;
        AccObject.AcceleratedCommand:=AccelerateCommand(CurrentLine);
        VObject.DisplayList.AddObject(CurrentLine,AccObject);
      End;
      I:=I+1+J;
      Continue;
    End;
    If Pos('PortList lines:',CurrentLine)=1 Then Begin
      Try ChunkLength:=StrToInt(ParsedBuf)
      Except
        LogMessage(0,'Error parsing file. Not an integer after keyword "PortList lines:" at line '+IntToStr(Base+I)+'.');
        FileContent.Free;
        Exit;
      end;
      If ChunkLength>0 Then Begin
        For J:=1 To ChunkLength Do Begin
          If I+J>=FileContent.Count Then Begin
            LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+J)+'.');
            FileContent.Free;
            Exit;
          End;
         CurrentLine:=FileContent[I+J];
         Port:=TVPort.Create;
         Port.Name:=GetValByName(CurrentLine,'Name');
         Port.AR:=GetValByName(CurrentLine,'AR');
         If Port.AR='A' Then Begin
           Port.AX:=StrToFloat(GetValByName(CurrentLine,'X'));
           Port.AY:=StrToFloat(GetValByName(CurrentLine,'Y'));
          //пересчитать относительные?
         End Else Begin
           Port.X:=StrToFloat(GetValByName(CurrentLine,'X'));
           Port.Y:=StrToFloat(GetValByName(CurrentLine,'Y'));
           //пересчитать абсолютные можно только после расчёта оболочки
         End;
         Port.Connection:=GetValByName(CurrentLine,'Conn');
         Port.ConnectionPort:=GetValByName(CurrentLine,'Port');
         VObject.PortList.AddObject(Port.Name,Port);
       End;
       I:=I+1+J;
     End Else I:=I+1;
     Continue;
   End;
   If Pos('NonPortConnectionList lines:',CurrentLine)=1 Then Begin
     Try ChunkLength:=StrToInt(ParsedBuf)
     Except
       LogMessage(0,'Error parsing file. Not an integer after keyword "NonPortConnectionList lines:" at line '+IntToStr(Base+I)+'.');
       FileContent.Free;
       Exit;
     end;
     If ChunkLength>0 Then Begin
       For J:=1 To ChunkLength Do Begin
         If I+J>=FileContent.Count Then Begin
           LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+J)+'.');
           FileContent.Free;
           Exit;
         End;
         CurrentLine:=FileContent[I+J];
         VObject.NonPortConnectionList.Add(CurrentLine);
       End;
       I:=I+J+1;
     end Else I:=I+1;
     Continue;
   End;
   If Pos('GroupList lines:',CurrentLine)=1 Then Begin
     Try ChunkLength:=StrToInt(ParsedBuf)
     Except
       LogMessage(0,'Error parsing file. Not an integer after keyword "GroupList lines:" at line '+IntToStr(Base+I)+'.');
       FileContent.Free;
       Exit;
     end;
     If ChunkLength>0 Then Begin
       For J:=1 To ChunkLength Do Begin
         If I+J>=FileContent.Count Then Begin
           LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+J)+'.');
           FileContent.Free;
           Exit;
         End;
         CurrentLine:=FileContent[I+J];
         N:=VGroupList.IndexOf(CurrentLine);
         If N>=0 Then Begin
           VGroup:=VGroupList.Objects[N] As TVGroup;
           VGroup.DisplayList.Add(VObject.Name);
           VObject.GroupList.AddObject(CurrentLine,VGroup);
         end;
       End;
       I:=I+J;
     end Else I:=I+1;
     Continue;
   End;
   If Pos('ActionList lines:',CurrentLine)=1 Then Begin
     Try ChunkLength:=StrToInt(ParsedBuf)
     Except
       LogMessage(0,'Error parsing file. Not an integer after keyword "ActionList lines:" at line '+IntToStr(Base+I)+'.');
       FileContent.Free;
       Exit;
     end;
     If ChunkLength>0 Then Begin
       For J:=1 To ChunkLength Do Begin
         If I+J>=FileContent.Count Then Begin
           LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+J)+'.');
           FileContent.Free;
           Exit;
         End;
         CurrentLine:=FileContent[I+J];
         VObject.ActionList.Add(CurrentLine);
       End;
       I:=I+J;
     end Else I:=I+1;
     Continue;
   End;
   Inc(I);
  End;
  ObjectStyle2Attr(VObject);
  VObject.X:=VObject.X+DX;
  VObject.Y:=VObject.Y+DY;
  If VObject.DisplayList.Count=0 Then Begin
    //Shell пустого объекта = точке вставки?
    VObject.Shell.X1:=VObject.X;
    VObject.Shell.X2:=VObject.X;
    VObject.Shell.Y1:=VObject.Y;
    VObject.Shell.Y2:=VObject.Y;
  End Else Begin
    UpdateObjectDisplayList(VObject);
    RenderObjectShell(VObject);    //здесь было закомментировано. можно ли сейчас считать шелл?
                              // нельзя потому что еще не загружены шаблоны
                              // тогда и координаты портов можно пересчитывать только если они прогружены
    If Add Then Begin
      If VObject.PortList.Count>0 Then
        For J:=0 To VObject.PortList.Count-1 Do Begin
          Port:=VObject.PortList.Objects[J] As TVPort;
          If Port.AR='A' Then Begin
            Port.X:=(Port.AX-VObject.Shell.X1)/(VObject.Shell.X2-VObject.Shell.X1);
            Port.Y:=(Port.AY-VObject.Shell.Y1)/(VObject.Shell.Y2-VObject.Shell.Y1);
          End Else Begin
            Port.AX:=VObject.Shell.X1+Port.X*(VObject.Shell.X2-VObject.Shell.X1);
            Port.AY:=VObject.Shell.Y1+Port.Y*(VObject.Shell.Y2-VObject.Shell.Y1);
          End;
        //Добавление портов в DisplayList
          AccObject:=TAccObject.Create;
          AccObject.AcceleratedCommand:=AccelerateCommand('UnScaled '+FloatToStr(Port.Ax)+','+FloatToStr(Port.Ay)+',portbox,'+IntToStr(PortSize));
          VObject.DisplayList.AddObject('UnScaled '+FloatToStr(Port.Ax)+','+FloatToStr(Port.Ay)+',portbox,'+IntToStr(PortSize),AccObject);
        end;
      End;
  end;
  Result:=VObject;
  If Add Then Self.View.Add('DrawObject '+VObject.Name);
End;

Function TVScene.SaveConnection(VConnection:TVConnection):TStringList;
Var FileContent:TStringList;
    JCount,J:Longint;
    ConnPort:TVConnectionPort;
    Buf:String;
Begin
      FileContent:=TStringList.Create;
      FileContent.Add('Connection name:'+VConnection.Name);
      FileContent.Add('Style:'+VConnection.Style);
      If VConnection.GroupList.Count>0 Then Begin
        FileContent.Add('GroupList lines:'+IntToStr(VConnection.GroupList.Count));
        FileContent.AddStrings(VConnection.GroupList);
      end;
      FileContent.Add('PortList lines:'+IntToStr(VConnection.PortList.Count));
      If VConnection.PortList.Count>0 Then
      For J:=0 To VConnection.PortList.Count-1 Do Begin
        ConnPort:=VConnection.PortList.Objects[J] As TVConnectionPort;
        If ConnPort.AR='A' Then
          FileContent.Add('PortType='+ConnPort.PortType+' AR='+ConnPort.AR+' X='+
          FloatToStr(ConnPort.AX)+' Y='+FloatToStr(ConnPort.AY)+' Conn='+ConnPort.ConnectedObject+':'+ConnPort.ConnectedPort)
        Else
          FileContent.Add('PortType='+ConnPort.PortType+' AR='+ConnPort.AR+' X='+
          FloatToStr(ConnPort.X)+' Y='+FloatToStr(ConnPort.Y)+' Conn='+ConnPort.ConnectedObject+':'+ConnPort.ConnectedPort);
      end;
      Result:=FileContent;
end;

Function TVScene.LoadConnection(Base:Longint;Add:Boolean;DX,DY:Extended;Var FileContent,RenList:TStringList):TVConnection;
Var VConnection:TVConnection;
    VConnectionPort:TVConnectionPort;
    VObject:TVObject;
    AccObject:TAccObject;
    Port:TVPort;
    VGroup:TVGroup; //должна ли копия объект входить в те же группы, что оригинал?
    I,J,N,ChunkLength:Longint;
    Buf,ParsedBuf,CurrentLine,KeyWord:String;
    Sm,Rm,Scm,tmpm,TransM:TMatrix2D;
Begin
    I:=0;
    While I<=FileContent.Count-1 Do Begin
      CurrentLine:=FileContent[I];
      Buf:=CurrentLine;
      KeyWord:=GetNextToken(Buf,ParsedBuf,':');
      If Pos('Connection name:',CurrentLine)=1 Then Begin
        If ParsedBuf='' Then Begin
          LogMessage(0,'Error parsing file. Empty connection name after keyword at line '+IntToStr(Base+I)+'.');
          FileContent.Free;
          Exit;
        End;
        If I+1>=FileContent.Count Then Begin
          LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+1)+'. Connection chunk length expected');
          FileContent.Free;
          Exit;
        End;
        //create Connection here
        LogMessage(10,'creating connection '+CurrentLine);
        VConnection:=TVConnection.Create;
        VConnection.Saveable:=True;
//        If Add Then Begin
          If VConnectionList.IndexOf(ParsedBuf)<0 Then Begin
            VConnection.Name:=ParsedBuf;
          End Else Begin
            VConnection.Name:=Self.GetNewName(Self.VConnectionList,'');
            RenList.Add(ParsedBuf+'='+VConnection.Name);
          End;
//        end Else VConnection.Name:=ParsedBuf;
        VConnectionList.AddObject(VConnection.Name,VConnection);
        Inc(I);
        Continue;
      End;
      If Pos('Style:',CurrentLine)=1 Then Begin
        If ParsedBuf<>'' Then VConnection.Style:=ParsedBuf
        Else Begin
          LogMessage(0,'Error parsing file. Empty Style at line '+IntToStr(Base+I)+'.');
          FileContent.Free;
          Exit;
        End;
        ConnectionStyle2Attr(VConnection);
        Inc(I);
        Continue;
      End;
      If Pos('GroupList lines:',CurrentLine)=1 Then Begin
        Try ChunkLength:=StrToInt(ParsedBuf)
        Except
          LogMessage(0,'Error parsing file. Not an integer after keyword "GroupList lines:" at line '+IntToStr(Base+I)+'.');
          FileContent.Free;
          Exit;
        end;
        If ChunkLength>0 Then Begin
          For J:=1 To ChunkLength Do Begin
            If I+J>=FileContent.Count Then Begin
              LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+J)+'.');
              FileContent.Free;
              Exit;
            End;
            CurrentLine:=FileContent[I+J];
            N:=VGroupList.IndexOf(CurrentLine);
            If N>=0 Then Begin
              VGroup:=VGroupList.Objects[N] As TVGroup;
              VGroup.DisplayList.Add(VConnection.Name);
              VConnection.GroupList.AddObject(CurrentLine,VGroup);
            end;
          End;
          I:=I+J+1;
        end Else I:=I+1;
        Continue;
      End;
      If Pos('PortList lines:',CurrentLine)=1 Then Begin
        Try ChunkLength:=StrToInt(ParsedBuf)
         Except
           LogMessage(0,'Error parsing file. Not an integer after keyword "PortList lines:" at line '+IntToStr(Base+I)+'.');
           FileContent.Free;
           Exit;
         end;
         If ChunkLength>0 Then Begin
           For J:=1 To ChunkLength Do Begin
             If I+J>=FileContent.Count Then Begin
               LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+J)+'.');
               FileContent.Free;
               Exit;
             End;
             CurrentLine:=FileContent[I+J];
             VConnectionPort:=TVConnectionPort.Create;
             VConnectionPort.PortType:=GetValByName(CurrentLine,'PortType');
             VConnectionPort.AR:=GetValByName(CurrentLine,'AR');
             //if ar=a then x+dx
             If VConnectionPort.AR='A' Then Begin
               VConnectionPort.AX:=StrToFloat(GetValByName(CurrentLine,'X'));
               VConnectionPort.AY:=StrToFloat(GetValByName(CurrentLine,'Y'));
             End Else Begin
               VConnectionPort.X:=StrToFloat(GetValByName(CurrentLine,'X'));
               VConnectionPort.Y:=StrToFloat(GetValByName(CurrentLine,'Y'));
             End;
             VConnectionPort.ConnectedObject:=GetNextToken(GetValByName(CurrentLine,'Conn'),
             VConnectionPort.ConnectedPort,':');
             If Add Then Begin
               N:=RenList.IndexOfName(VConnectionPort.ConnectedObject);
               If N>=0 Then VConnectionPort.ConnectedObject:=RenList.ValueFromIndex[N];
             End;
             VConnection.PortList.AddObject(VConnectionPort.PortType,VConnectionPort);

{   FileContent.Add('PortType='+ConnPort.PortType+' AR='+ConnPort.AR+' X='+
               FloatToStr(ConnPort.X)+' Y='+FloatToStr(ConnPort.Y)+' Conn='+ConnPort.ConnectedObject+':'+ConnPort.ConnectedPort);
}
           End;
           I:=I+1+J;
         end Else I:=I+1;
         Continue;
       End;
      Inc(I);
     end;

   If Not Add Then UpdateConnectionDisplayList(VConnection);
   Result:=VConnection;
end;

Function TVScene.SaveCallout(VCallout:TVCallout):TStringList;
Var FileContent:TStringList;
    JCount,J:Longint;
    ConnPort:TVConnectionPort;
    Buf:String;
Begin
           FileContent:=TStringList.Create;
           FileContent.Add('Callout name:'+VCallout.Name);
           FileContent.Add('Style:'+VCallout.Style);
           If VCallout.Source<>'' Then FileContent.Add('Source:'+VCallout.Source);
           If VCallout.Text<>'' Then FileContent.Add('Text:'+VCallout.Text);
           If VCallout.GroupList.Count>0 Then Begin
             FileContent.Add('GroupList lines:'+IntToStr(VCallout.GroupList.Count));
             FileContent.AddStrings(VCallout.GroupList);
           end;
           If VCallout.PortList.Count>0 Then Begin
             FileContent.Add('PortList lines:'+IntToStr(VCallout.PortList.Count));
             For J:=0 To VCallout.PortList.Count-1 Do Begin
               ConnPort:=VCallout.PortList.Objects[J] As TVConnectionPort;
               FileContent.Add('PortType='+ConnPort.PortType+' AR='+ConnPort.AR+' X='+
                  FloatToStr(ConnPort.X)+' Y='+FloatToStr(ConnPort.Y)+' Conn='+ConnPort.ConnectedObject+':'+ConnPort.ConnectedPort);
             end;
           end;
           If VCallout.ActionList.Count>0 Then Begin
             FileContent.Add('ActionList lines:'+IntToStr(VCallout.ActionList.Count));
             FileContent.AddStrings(VCallout.ActionList);
           end;
           Result:=FileContent;
End;

Function TVScene.LoadCallout(Base:Longint;Add:Boolean;DX,DY:Extended;Var FileContent,RenList:TStringList):TVCallout;
Var VCallout:TVCallout;
    AccObject:TAccObject;
    Port:TVPort;
    VConnectionPort:TVConnectionPort;
    VGroup:TVGroup; //должна ли копия объект входить в те же группы, что оригинал?
    I,J,N,ChunkLength:Longint;
    Buf,ParsedBuf,CurrentLine,KeyWord:String;
    Sm,Rm,Scm,tmpm,TransM:TMatrix2D;
Begin
  I:=0;
  While I<=FileContent.Count-1 Do Begin
    CurrentLine:=FileContent[I];
    Buf:=CurrentLine;
    KeyWord:=GetNextToken(Buf,ParsedBuf,':');
    If Pos('Callout name:',CurrentLine)=1 Then Begin
      If ParsedBuf='' Then Begin
        LogMessage(0,'Error parsing file. Empty callout name after keyword at line '+IntToStr(Base+I)+'.');
        FileContent.Free;
        Exit;
      End;
      If I+1>=FileContent.Count Then Begin
        LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+1)+'. Callout chunk length expected');
        FileContent.Free;
        Exit;
      End;
      //create Callout here
      VCallout:=TVCallout.Create;
      VCallout.Saveable:=True;
//      If Add Then Begin
      If VCalloutList.IndexOf(ParsedBuf)<0 Then Begin
        VCallout.Name:=ParsedBuf;
      End Else Begin
        VCallout.Name:=GetNewName(Self.VCalloutList,'');
        RenList.Add(ParsedBuf+'='+VCallout.Name);
      end;
      VCalloutList.AddObject(VCallout.Name,VCallout);
      VCallout.Source:='';
      VCallout.Text:='';
      Inc(I);
      Continue;
    End;
    If Pos('Style:',CurrentLine)=1 Then Begin
      If ParsedBuf<>'' Then VCallout.Style:=ParsedBuf
      Else Begin
        LogMessage(0,'Error parsing file. Empty Style at line '+IntToStr(Base+I)+'.');
        FileContent.Free;
        Exit;
      end;
      Inc(I);
      Continue;
    End;
    If Pos('RelatedObj:',CurrentLine)=1 Then Begin
      If ParsedBuf<>'' Then VCallout.RelatedObj:=ParsedBuf;
      Inc(I);
      Continue;
    End;
    If Pos('Source:',CurrentLine)=1 Then Begin
      If ParsedBuf<>'' Then VCallout.Source:=ParsedBuf;
      Inc(I);
      Continue;
    End;
    If Pos('Text:',CurrentLine)=1 Then Begin
      VCallout.Text:=ParsedBuf;
      Inc(I);
      Continue;
    End;
    If Pos('GroupList lines:',CurrentLine)=1 Then Begin
      Try ChunkLength:=StrToInt(ParsedBuf)
      Except
        LogMessage(0,'Error parsing file. Not an integer after keyword "GroupList lines:" at line '+IntToStr(Base+I)+'.');
        FileContent.Free;
        Exit;
      end;
      If ChunkLength>0 Then Begin
        For J:=1 To ChunkLength Do Begin
          If I+J>=FileContent.Count Then Begin
            LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+J)+'.');
            FileContent.Free;
            Exit;
          End;
          CurrentLine:=FileContent[I+J];
          N:=VGroupList.IndexOf(CurrentLine);
          If N>=0 Then Begin
            VGroup:=VGroupList.Objects[N] As TVGroup;
            VGroup.DisplayList.Add(VCallout.Name);
            VCallout.GroupList.AddObject(CurrentLine,VGroup);
          end;
        End;
        I:=I+J+1;
      end Else I:=I+1;
      Continue;
    End;
    If Pos('PortList lines:',CurrentLine)=1 Then Begin
      Try ChunkLength:=StrToInt(ParsedBuf)
      Except
        LogMessage(0,'Error parsing file. Not an integer after keyword "PortList lines:" at line '+IntToStr(Base+I)+'.');
        FileContent.Free;
        Exit;
      end;
      If ChunkLength>0 Then Begin
        For J:=1 To ChunkLength Do Begin
          If I+1+J>=FileContent.Count Then Begin
            LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+1+J)+'.');
            FileContent.Free;
            Exit;
          End;
          CurrentLine:=FileContent[I+J];
          VConnectionPort:=TVConnectionPort.Create;
          VConnectionPort.PortType:=GetValByName(CurrentLine,'PortType');
          VConnectionPort.AR:=GetValByName(CurrentLine,'AR');
          VConnectionPort.X:=StrToFloat(GetValByName(CurrentLine,'X'));
          VConnectionPort.Y:=StrToFloat(GetValByName(CurrentLine,'Y'));
          VConnectionPort.ConnectedObject:=GetNextToken(GetValByName(CurrentLine,'Conn'),
                  VConnectionPort.ConnectedPort,':');
          VCallout.PortList.AddObject(VConnectionPort.PortType,VConnectionPort);

{   FileContent.Add('PortType='+ConnPort.PortType+' AR='+ConnPort.AR+' X='+
       FloatToStr(ConnPort.X)+' Y='+FloatToStr(ConnPort.Y)+' Conn='+ConnPort.ConnectedObject+':'+ConnPort.ConnectedPort);
}
        End;
        I:=I+1+J;
      end Else I:=I+1;
      Continue;
    End;
    If Pos('ActionList lines:',CurrentLine)=1 Then Begin
      Try ChunkLength:=StrToInt(ParsedBuf)
      Except
        LogMessage(0,'Error parsing file. Not an integer after keyword "ActionList lines:" at line '+IntToStr(Base+I)+'.');
        FileContent.Free;
        Exit;
      end;
      If ChunkLength>0 Then Begin
        For J:=1 To ChunkLength Do Begin
          If I+J>=FileContent.Count Then Begin
            LogMessage(0,'Error parsing file. Unexpected EOF at line '+IntToStr(Base+I+J)+'.');
            FileContent.Free;
            Exit;
          End;
          CurrentLine:=FileContent[I+J];
          VCallout.ActionList.Add(CurrentLine);
        End;
        I:=I+J+1;
      end Else I:=I+1;
      Continue;
    End;
    Inc(I);
  end;
  CalloutStyle2Attr(VCallout);
  If VCallout.BindMode='' Then Begin
    VCallout.X:=VCallout.X+DX;
    VCallout.Y:=VCallout.Y+DY;
  End;
  Sm:=GenShift(VCallout.X,VCallout.Y);
  Rm:=GenRotate(VCallout.X,VCallout.Y,0);
  Scm:=GenScale(VCallout.X,VCallout.Y,1,1);
  tmpm:=MulMat2D(Sm,Rm);
  TransM:=MulMat2D(Tmpm,Scm);
  VCallout.Shell:=RenderShell(VCallout.DisplayList,TransM);
  If Add Then UpdateCalloutDisplayList(VCallout);
  Result:=VCallout;
end;

Function TVScene.SaveItem(ItemName:String):TStringList;
Var Curl:TVCurl;
    Obj:TVObject;
    Callout:TVCallout;
    Conn:TVConnection;
    Group:TVGroup;
Begin
  Case GetTypeByName(ItemName) of
  'curl': Begin
              Curl:=Self.VCurlList.Objects[Self.VCurlList.IndexOf(ItemName)] As TVCurl;
              Result:=Self.SaveCurl(Curl);
          End;
  'template': Begin End;
  'object': Begin
              Obj:=Self.VObjectList.Objects[Self.VObjectList.IndexOf(ItemName)] As TVObject;
              Result:=Self.SaveObject(Obj);
            End;
  'callout': Begin
               Callout:=Self.VCalloutList.Objects[Self.VCalloutList.IndexOf(ItemName)] As TVCallout;
               Result:=Self.SaveCallout(Callout);
             End;
  'connection':Begin
                 Conn:=Self.VConnectionList.Objects[Self.VConnectionList.IndexOf(ItemName)] As TVConnection;
                 Result:=Self.SaveConnection(Conn);
               End;
  'group': Begin
              Group:=Self.VGroupList.Objects[Self.VGroupList.IndexOf(ItemName)] As TVGroup;
              Result:=Self.SaveGroup(Group);
           End;
  End;
End;

Procedure TVScene.SaveFile(FileName:String);
Var FileContent,ChunkBuf:TStringList;
    I,J,JCount:Longint;Curl:TVCurl;
    VObject:TVObject;VConnection:TVConnection;
    VGroup:TVGroup;VCallout:TVCallout;
    Port:TVPort; ConnPort:TVConnectionPort;
    Buf:String;
Begin
  FileContent:=TStringList.Create; // WARNING - MEMORY OVERFLOW HERE! USE TFileStream
  If VGroupList.Count>0 Then
    For I:=0 To VGroupList.Count-1 Do Begin
      VGroup:=VGroupList.Objects[I] As TVGroup;
      FileContent.Add('Group name:'+VGroup.Name);
      If VGroup.Glue Then Buf:='Glue=1' Else Buf:='Glue=0';
      If VGroup.Active Then Buf:=Buf+' Active=1' Else Buf:=Buf+' Active=0';
      If VGroup.Visible Then Buf:=Buf+' Visible=1' Else Buf:=Buf+' Visible=0';
      FileContent.Add(Buf);
{      FileContent.Add('GroupList lines:'+IntToStr(VGroup.DisplayList.Count));
      If VGroup.DisplayList.Count>0 Then
        For J:=0 To VGroup.DisplayList.Count-1 Do Begin
          FileContent.Add(VGroup.DisplayList[J]);
        end;}
    end;

  If VCurlList.Count>0 Then
    For I:=0 To VCurlList.Count-1 Do Begin
      Curl:=VCurlList.Objects[I] As TVCurl;
      If Curl.Saveable Then Begin
        FileContent.Add('Curl name:'+Curl.Name);
        FileContent.Add('Lines:'+IntToStr(Curl.DisplayList.Count));
        FileContent.AddStrings(Curl.DisplayList);
      End;
    end;
  If VObjectList.Count>0 Then
    For I:=0 To VObjectList.Count-1 Do Begin
      VObject:=VObjectList.Objects[I] As TVObject;
      If VObject.Saveable Then Begin
        ChunkBuf:=SaveObject(VObject);
        FileContent.AddStrings(ChunkBuf);
        ChunkBuf.Free;
      End;
    end;
    If VConnectionList.Count>0 Then
      For I:=0 To VConnectionList.Count-1 Do Begin
        VConnection:=VConnectionList.Objects[I] As TVConnection;
        If VConnection.Saveable Then Begin
          ChunkBuf:=SaveConnection(VConnection);
          FileContent.AddStrings(ChunkBuf);
          ChunkBuf.Free;
        end;
      end;
    If VCalloutList.Count>0 Then
         For I:=0 To VCalloutList.Count-1 Do Begin
           VCallout:=VCalloutList.Objects[I] As TVCallout;
           If VCallout.Saveable Then Begin
             ChunkBuf:=SaveCallout(VCallout);
             FileContent.AddStrings(ChunkBuf);
             ChunkBuf.Free;
           end;
         end;
  If View.Count>0 Then Begin
    FileContent.Add('View: Main');
    FileContent.Add('Lines:'+IntToStr(View.Count));
    FileContent.AddStrings(View);
  end;

  FileContent.SaveToFile(FileName);
  FileContent.Free;
end;

Procedure TVScene.UpdateVisibility;
Var I:Longint;
    Obj:TVObject;
    Curl:TVCurl;
    Callout:TVCallout;
    Connection:TVConnection;
    ObjDiam:Extended;
    SelHandle:TVSelHandle;
    S:TShell;
Begin
  VisibleList.Clear;
  If Self.VCurlList.Count>0 Then
      For I:=0 To Self.VCurlList.Count-1 Do Begin
        Curl:=Self.VCurlList.Objects[I] As TVCurl;
          If ShellsAreCrossed(ScreenShell,Curl.Shell)
          Then Curl.OffScreen:=False Else Curl.OffScreen:=True;
          If InfiniteShell(Curl.Shell)
          Then Begin
            Curl.TooSmall:=False;
          end Else Begin
            If Self.ClipMax Then ObjDiam:=Max(Curl.Shell.X2-Curl.Shell.X1,Curl.Shell.Y2-Curl.Shell.Y1)
            Else ObjDiam:=Min(Curl.Shell.X2-Curl.Shell.X1,Curl.Shell.Y2-Curl.Shell.Y1);
            Curl.TooSmall:=ObjDiam/Self.Drawer.ViewScale<Self.ClipSize;
          end;
          If (Not Curl.OffScreen) And (Not Curl.TooSmall) Then VisibleList.AddObject(Curl.Name,Curl);
      end;

  If Self.VObjectList.Count>0 Then
    For I:=0 To Self.VObjectList.Count-1 Do Begin
      Obj:=Self.VObjectList.Objects[I] As TVObject;
        If ShellsAreCrossed(ScreenShell,Obj.Shell)
        Then Obj.OffScreen:=False Else Obj.OffScreen:=True;
      If InfiniteShell(Obj.Shell)
      Then Begin
        Obj.TooSmall:=False;
      end Else Begin
        If Self.ClipMax Then ObjDiam:=Max(Obj.Shell.X2-Obj.Shell.X1,Obj.Shell.Y2-Obj.Shell.Y1)
        Else ObjDiam:=Min(Obj.Shell.X2-Obj.Shell.X1,Obj.Shell.Y2-Obj.Shell.Y1);
        Obj.TooSmall:=ObjDiam/Self.Drawer.ViewScale<Self.ClipSize;
      end;
      If (Not Obj.OffScreen) And (Not Obj.TooSmall) Then VisibleList.AddObject(Obj.Name,Obj);
    end;

    If Self.VCalloutList.Count>0 Then
    For I:=0 To Self.VCalloutList.Count-1 Do Begin
      Callout:=Self.VCalloutList.Objects[I] As TVCallout;
        If ShellsAreCrossed(ScreenShell,Callout.Shell)
        Then Callout.OffScreen:=False Else Callout.OffScreen:=True;
      If InfiniteShell(Callout.Shell)
      Then Begin
        Callout.TooSmall:=False;
      end Else Begin
        If Self.ClipMax Then ObjDiam:=Max(Callout.Shell.X2-Callout.Shell.X1,Callout.Shell.Y2-Callout.Shell.Y1)
        Else Begin
          If Callout.Shell.X2=Callout.Shell.X1 Then ObjDiam:=Callout.Shell.Y2-Callout.Shell.Y1
          Else
            If Callout.Shell.Y2=Callout.Shell.Y1 Then ObjDiam:=Callout.Shell.X2-Callout.Shell.X1
            Else ObjDiam:=Min(Callout.Shell.X2-Callout.Shell.X1,Callout.Shell.Y2-Callout.Shell.Y1);
        End;
        Callout.TooSmall:=ObjDiam/Self.Drawer.ViewScale<Self.ClipSize;
      end;
        If (Not Callout.OffScreen) And (Not Callout.TooSmall) Then VisibleList.AddObject(Callout.Name,Callout);
    end;

    If Self.VConnectionList.Count>0 Then
    For I:=0 To Self.VConnectionList.Count-1 Do Begin
      Connection:=Self.VConnectionList.Objects[I] As TVConnection;
        If ShellsAreCrossed(ScreenShell,Connection.Shell)
        Then Connection.OffScreen:=False Else Connection.OffScreen:=True;
{      If InfiniteShell(Connection.Shell)
      Then Begin
        Connection.TooSmall:=False;
      end Else Begin}
        ObjDiam:=Max(Connection.Shell.X2-Connection.Shell.X1,Connection.Shell.Y2-Connection.Shell.Y1);
        Connection.TooSmall:=Abs(ObjDiam)/Self.Drawer.ViewScale<Self.ClipSize;
//      end;
        If (Not Connection.OffScreen) And (Not Connection.TooSmall) Then
          VisibleList.AddObject(Connection.Name,Connection);
    end;

    If Self.VHandleList.Count>0 Then
    For I:=0 To Self.VHandleList.Count-1 Do Begin
      SelHandle:=Self.VHandleList.Objects[I] As TVSelHandle;
      Self.Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
    end;

end;
Procedure TVScene.CreateSelHandles(ItemName:String);
Var I,J,N:Longint;
    Shell:TShell;
    Obj:TVObject;
    Callout:TVCallout;
    Curl:TVCurl;
    Conn:TVConnection;
    ConnPort:TVConnectionPort;
    Port:TVPort;
    SelHandle,SelHandle1:TVSelHandle;
    DupHandles:Boolean;
Begin
  N:=VObjectList.IndexOf(ItemName);
  If N>=0 Then Begin
    Obj:=VObjectList.Objects[N] As TVObject;
    Shell:=Obj.Shell;
    For I:=0 To Obj.PortList.Count-1 Do Begin
      Port:=Obj.PortList.Objects[I] As TVPort;
        SelHandle:=TVSelHandle.Create;
        SelHandle.Xw:=Port.AX;
        SelHandle.Yw:=Port.AY;
        Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
        SelHandle.Name:=ItemName;
        SelHandle.Style:='Box';
        VHandleList.AddObject(SelHandle.Name,SelHandle);
        Drawer.Canvas.Pen.Mode:=pmXor;
        Drawer.ExecCommand('UnScaled '+FloatToStr(SelHandle.Xw)+','+FloatToStr(SelHandle.Yw)+',box,'+IntToStr(SelHandleSize),ZeroAcc);
      End;
  end Else Begin
    N:=VCalloutList.IndexOf(ItemName);
    If N>=0 Then Begin
      Callout:=VCalloutList.Objects[N] As TVCallout;
      Shell:=Callout.Shell;
    end Else Begin
      N:=VCurlList.IndexOf(ItemName);
      If N>=0 Then Begin
        Curl:=VCurlList.Objects[N] As TVCurl;
        Shell:=Curl.Shell;
      end Else Begin
        N:=VConnectionList.IndexOf(ItemName);
        If N>=0 Then Begin
          Conn:=VConnectionList.Objects[N] As TVConnection;
          Shell:=Conn.Shell;
          For I:=0 To Conn.PortList.Count-1 Do Begin
            ConnPort:=Conn.PortList.Objects[I] As TVConnectionPort;
            If (ConnPort.PortType='Node')Or(ConnPort.PortType='Text') Then Begin
              SelHandle:=TVSelHandle.Create;
              SelHandle.Xw:=ConnPort.AX;
              SelHandle.Yw:=ConnPort.AY;
              Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
              SelHandle.Name:=ItemName;
              SelHandle.Style:='Box';
              VHandleList.AddObject(SelHandle.Name,SelHandle);
              Drawer.Canvas.Pen.Mode:=pmXor;   //
              Drawer.ExecCommand('UnScaled '+FloatToStr(SelHandle.Xw)+','+FloatToStr(SelHandle.Yw)+',box,'+IntToStr(SelHandleSize),ZeroAcc);
            End;
          End;
          Exit;
        End;
      End;
    end;
  end;

  SelHandle:=TVSelHandle.Create;
              Drawer.Canvas.Pen.Mode:=pmXor;
              SelHandle.Xw:=Shell.X1;
              SelHandle.Yw:=Shell.Y2;
              Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
              SelHandle.Name:=ItemName;
              SelHandle.Style:='LT';
              VHandleList.AddObject(SelHandle.Name,SelHandle);

              //создавать-создаём, но рисуем только если нет такого же
              DupHandles:=False;
              If VHandleList.Count>1 Then
                For J:=0 To VHandleList.Count-2 Do Begin
                  SelHandle1:=VHandleList.Objects[J] As TVSelHandle;
                  If (Abs(SelHandle.Xw-SelHandle1.Xw)/Drawer.ViewScale<2)And(Abs(SelHandle.Yw-SelHandle1.Yw)/Drawer.ViewScale<2) Then Begin
                    DupHandles:=True;Break;
                  End;
                End;
              If Not DupHandles Then
                Drawer.ExecCommand('UnScaled '+FloatToStr(SelHandle.Xw)+','+FloatToStr(SelHandle.Yw)+',box,'+IntToStr(SelHandleSize),ZeroAcc);
              SelHandle:=TVSelHandle.Create;
              SelHandle.Xw:=Shell.X2;
              SelHandle.Yw:=Shell.Y2;
              Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
              SelHandle.Name:=ItemName;
              SelHandle.Style:='RT';
              VHandleList.AddObject(SelHandle.Name,SelHandle);
              DupHandles:=False;
              If VHandleList.Count>1 Then
                For J:=0 To VHandleList.Count-2 Do Begin
                  SelHandle1:=VHandleList.Objects[J] As TVSelHandle;
                  If (Abs(SelHandle.Xw-SelHandle1.Xw)/Drawer.ViewScale<2)And(Abs(SelHandle.Yw-SelHandle1.Yw)/Drawer.ViewScale<2) Then Begin
                    DupHandles:=True;Break;
                  End;
                End;
              If Not DupHandles Then
              Drawer.ExecCommand('UnScaled '+FloatToStr(SelHandle.Xw)+','+FloatToStr(SelHandle.Yw)+',box,'+IntToStr(SelHandleSize),ZeroAcc);
              SelHandle:=TVSelHandle.Create;
              SelHandle.Xw:=Shell.X1;
              SelHandle.Yw:=Shell.Y1;
              Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
              SelHandle.Name:=ItemName;
              SelHandle.Style:='LB';
              VHandleList.AddObject(SelHandle.Name,SelHandle);
              DupHandles:=False;
              If VHandleList.Count>1 Then
                For J:=0 To VHandleList.Count-2 Do Begin
                  SelHandle1:=VHandleList.Objects[J] As TVSelHandle;
                  If (Abs(SelHandle.Xw-SelHandle1.Xw)/Drawer.ViewScale<2)And(Abs(SelHandle.Yw-SelHandle1.Yw)/Drawer.ViewScale<2) Then Begin
                    DupHandles:=True;Break;
                  End;
                End;
              If Not DupHandles Then
              Drawer.ExecCommand('UnScaled '+FloatToStr(SelHandle.Xw)+','+FloatToStr(SelHandle.Yw)+',box,'+IntToStr(SelHandleSize),ZeroAcc);
              SelHandle:=TVSelHandle.Create;
              SelHandle.Xw:=Shell.X2;
              SelHandle.Yw:=Shell.Y1;
              Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
              SelHandle.Name:=ItemName;
              SelHandle.Style:='RB';
              VHandleList.AddObject(SelHandle.Name,SelHandle);
              DupHandles:=False;
              If VHandleList.Count>1 Then
                For J:=0 To VHandleList.Count-2 Do Begin
                  SelHandle1:=VHandleList.Objects[J] As TVSelHandle;
                  If (Abs(SelHandle.Xw-SelHandle1.Xw)/Drawer.ViewScale<2)And(Abs(SelHandle.Yw-SelHandle1.Yw)/Drawer.ViewScale<2) Then Begin
                    DupHandles:=True;Break;
                  End;
                End;
              If Not DupHandles Then
                Drawer.ExecCommand('UnScaled '+FloatToStr(SelHandle.Xw)+','+FloatToStr(SelHandle.Yw)+',box,'+IntToStr(SelHandleSize),ZeroAcc);

              //----
              SelHandle:=TVSelHandle.Create;
              Drawer.Canvas.Pen.Mode:=pmXor;
              SelHandle.Xw:=(Shell.X1+Shell.X2)/2;
              SelHandle.Yw:=Shell.Y2;
              Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
              SelHandle.Name:=ItemName;
              SelHandle.Style:='MT';
              VHandleList.AddObject(SelHandle.Name,SelHandle);
              DupHandles:=False;
              If VHandleList.Count>1 Then
                For J:=0 To VHandleList.Count-2 Do Begin
                  SelHandle1:=VHandleList.Objects[J] As TVSelHandle;
                  If (Abs(SelHandle.Xw-SelHandle1.Xw)/Drawer.ViewScale<2)And(Abs(SelHandle.Yw-SelHandle1.Yw)/Drawer.ViewScale<2) Then Begin
                    DupHandles:=True;Break;
                  End;
                End;
              If Not DupHandles Then
              Drawer.ExecCommand('UnScaled '+FloatToStr(SelHandle.Xw)+','+FloatToStr(SelHandle.Yw)+',box,'+IntToStr(SelHandleSize),ZeroAcc);
              SelHandle:=TVSelHandle.Create;
              SelHandle.Xw:=(Shell.X1+Shell.X2)/2;
              SelHandle.Yw:=Shell.Y1;
              Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
              SelHandle.Name:=ItemName;
              SelHandle.Style:='MB';
              VHandleList.AddObject(SelHandle.Name,SelHandle);
              DupHandles:=False;
              If VHandleList.Count>1 Then
                For J:=0 To VHandleList.Count-2 Do Begin
                  SelHandle1:=VHandleList.Objects[J] As TVSelHandle;
                  If (Abs(SelHandle.Xw-SelHandle1.Xw)/Drawer.ViewScale<2)And(Abs(SelHandle.Yw-SelHandle1.Yw)/Drawer.ViewScale<2) Then Begin
                    DupHandles:=True;Break;
                  End;
                End;
              If Not DupHandles Then
              Drawer.ExecCommand('UnScaled '+FloatToStr(SelHandle.Xw)+','+FloatToStr(SelHandle.Yw)+',box,'+IntToStr(SelHandleSize),ZeroAcc);
              SelHandle:=TVSelHandle.Create;
              SelHandle.Xw:=Shell.X1;
              SelHandle.Yw:=(Shell.Y2+Shell.Y1)/2;
              Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
              SelHandle.Name:=ItemName;
              SelHandle.Style:='LM';
              VHandleList.AddObject(SelHandle.Name,SelHandle);
              DupHandles:=False;
              If VHandleList.Count>1 Then
                For J:=0 To VHandleList.Count-2 Do Begin
                  SelHandle1:=VHandleList.Objects[J] As TVSelHandle;
                  If (Abs(SelHandle.Xw-SelHandle1.Xw)/Drawer.ViewScale<2)And(Abs(SelHandle.Yw-SelHandle1.Yw)/Drawer.ViewScale<2) Then Begin
                    DupHandles:=True;Break;
                  End;
                End;
              If Not DupHandles Then
              Drawer.ExecCommand('UnScaled '+FloatToStr(SelHandle.Xw)+','+FloatToStr(SelHandle.Yw)+',box,'+IntToStr(SelHandleSize),ZeroAcc);
              SelHandle:=TVSelHandle.Create;
              SelHandle.Xw:=Shell.X2;
              SelHandle.Yw:=(Shell.Y2+Shell.Y1)/2;
              Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
              SelHandle.Name:=ItemName;
              SelHandle.Style:='RM';
              VHandleList.AddObject(SelHandle.Name,SelHandle);
              DupHandles:=False;
              If VHandleList.Count>1 Then
                For J:=0 To VHandleList.Count-2 Do Begin
                  SelHandle1:=VHandleList.Objects[J] As TVSelHandle;
                  If (Abs(SelHandle.Xw-SelHandle1.Xw)/Drawer.ViewScale<2)And(Abs(SelHandle.Yw-SelHandle1.Yw)/Drawer.ViewScale<2) Then Begin
                    DupHandles:=True;Break;
                  End;
                End;
              If Not DupHandles Then
              Drawer.ExecCommand('UnScaled '+FloatToStr(SelHandle.Xw)+','+FloatToStr(SelHandle.Yw)+',box,'+IntToStr(SelHandleSize),ZeroAcc);
End;

Procedure TVScene.DeleteSelHandles(ItemName:String);
Var I,J,N:Longint;
    Shell:TShell;
    Obj:TVObject;
    Callout:TVCallout;
    Curl:TVCurl;
    SelHandle,SelHandle1:TVSelHandle;
    DupHandles:Boolean;
Begin
    If VHandleList.Count>0 Then
      For I:=VHandleList.Count-1 DownTo 0 Do Begin
        SelHandle:=VHandleList.Objects[I] As TVSelHandle;
        If SelHandle.Name=ItemName Then Begin
            //нет такого - удаляем
            Drawer.Canvas.Pen.Mode:=pmXor;
            DupHandles:=False;
            If VHandleList.Count>1 Then
              For J:=VHandleList.Count-1 DownTo 0 Do Begin
                SelHandle1:=VHandleList.Objects[J] As TVSelHandle;
                If I<>J Then
                  If (Abs(SelHandle.Xw-SelHandle1.Xw)/Drawer.ViewScale<2)And(Abs(SelHandle.Yw-SelHandle1.Yw)/Drawer.ViewScale<2) Then Begin
                    DupHandles:=True;Break;
                End;
              End;
            If Not DupHandles Then
            Drawer.ExecCommand('UnScaled '+FloatToStr(SelHandle.Xw)+','+FloatToStr(SelHandle.Yw)+',box,'+IntToStr(SelHandleSize),ZeroAcc);
            VHandleList.Delete(I);
            SelHandle.Free;
          end;
      end;
end;
Procedure TVScene.UpdateSelHandles(ItemName:String);
Var I,J,N:Longint;
    Shell:TShell;
    Obj:TVObject;
    Callout:TVCallout;
    Curl:TVCurl;
    Connection:TVConnection;
    SelHandle:TVSelHandle;
    ConnPort:TVConnectionPort;
    Port:TVPort;
Begin
//обновить хэндлы выделения
  N:=VObjectList.IndexOf(ItemName);
  If N>=0 Then Begin
    Obj:=VObjectList.Objects[N] As TVObject;
    Shell:=Obj.Shell;
    N:=VObjectList.IndexOf(ItemName);
          If N>=0 Then Begin
            Obj:=VObjectList.Objects[N] As TVObject;
            Shell:=Obj.Shell;
            DeleteSelHandles(ItemName);
            CreateSelHandles(ItemName);
            Exit;
          End;
  end Else Begin
    N:=VCalloutList.IndexOf(ItemName);
    If N>=0 Then Begin
      Callout:=VCalloutList.Objects[N] As TVCallout;
      Shell:=Callout.Shell;
    end Else Begin
      N:=VCurlList.IndexOf(ItemName);
      If N>=0 Then Begin
        Curl:=VCurlList.Objects[N] As TVCurl;
        Shell:=Curl.Shell;
      end Else Begin
          N:=VConnectionList.IndexOf(ItemName);
          If N>=0 Then Begin
          Connection:=VConnectionList.Objects[N] As TVConnection;
          Shell:=Connection.Shell;
          DeleteSelHandles(ItemName);
          CreateSelHandles(ItemName);
          Exit;
        End Else Exit;
      End;
    end;
  end;
  If VHandleList.Count>0 Then
    For J:=0 To VHandleList.Count-1 Do Begin
      SelHandle:=VHandleList.Objects[J] As TVSelHandle;
      If SelHandle.Name=ItemName Then Begin
        Case SelHandle.Style Of
        'LT': Begin
                SelHandle.Xw:=Shell.X1;
                SelHandle.Yw:=Shell.Y2;
              End;
        'MT': Begin
                SelHandle.Xw:=(Shell.X1+Shell.X2)/2;
                SelHandle.Yw:=Shell.Y2;
              End;
        'RT': Begin
                SelHandle.Xw:=Shell.X2;
                SelHandle.Yw:=Shell.Y2;
              End;
        'LM': Begin
                SelHandle.Xw:=Shell.X1;
                SelHandle.Yw:=(Shell.Y1+Shell.Y2)/2;
              End;
        'RM': Begin
                SelHandle.Xw:=Shell.X2;
                SelHandle.Yw:=(Shell.Y1+Shell.Y2)/2;
              End;
        'LB': Begin
                SelHandle.Xw:=Shell.X1;
                SelHandle.Yw:=Shell.Y1;
              End;
        'MB': Begin
                SelHandle.Xw:=(Shell.X1+Shell.X2)/2;
                SelHandle.Yw:=Shell.Y1;
              End;
        'RB': Begin
                SelHandle.Xw:=Shell.X2;
                SelHandle.Yw:=Shell.Y1;
              End;
        'Box': Begin
                  //ShowMessage('Node');
{                  SelHandle.Xw:=;
                  SelHandle.Yw:=;}
          Case GetTypeByName(ItemName) Of
          'object': Begin
            For I:=0 To Obj.PortList.Count-1 Do Begin
              Port:=Obj.PortList.Objects[I] As TVPort;
              SelHandle.Xw:=Port.AX;
              SelHandle.Yw:=Port.AY;
            End;
                    End;
          'connection': Begin
            For I:=0 To Connection.PortList.Count-1 Do Begin
              ConnPort:=Connection.PortList.Objects[I] As TVConnectionPort;
              If ConnPort.PortType='Node' Then Begin
                SelHandle.Xw:=ConnPort.AX;
                SelHandle.Yw:=ConnPort.AY;
              End;
            End;
                        End;
          End;
        End;
       End;
      Drawer.WtoS(SelHandle.Xw,SelHandle.Yw,SelHandle.X,SelHandle.Y);
    End;
  End;
End;

Function TVScene.BuildUnderMouse(X,Y:Extended;SamePlaceZone:Longint):TStringList;
Var I,J,K:Longint;
   ResultList:TStringList;
   Curl:TVCurl;
   Obj:TVObject;
   Callout:TVCallout;
   Connection:TVConnection;
   ActiveObject:Boolean;
   ConnPort1,ConnPort2:TVConnectionPort;
   x1s,x2s,y1s,y2s,cxs,cys:Longint;
Begin
    Drawer.WtoS(X,Y,cxs,cys);
    ResultList:=TStringList.Create;
    If VisibleList.Count>0 Then Begin
      For I:=0 To VisibleList.Count-1 Do Begin
        If VisibleList.Objects[I] Is TVCurl Then Begin
          Curl:=VisibleList.Objects[I] As TVCurl;
          If PointInWRect(X,Y,Curl.Shell.X1,Curl.Shell.Y1,Curl.Shell.X2,Curl.Shell.Y2) Then
            ResultList.AddObject(Curl.Name,Curl);
        end;
        If VisibleList.Objects[I] Is TVObject Then Begin
          Obj:=VisibleList.Objects[I] As TVObject;
          //Dont check objects, belongs to disabled group
          ActiveObject:=True;
          If Obj.GroupList.Count>0 Then
            For K:=0 To Obj.GroupList.Count-1 Do Begin
              If Not (Obj.GroupList.Objects[K] As TVGroup).Active Then ActiveObject:=False;
            end;
          If Not ActiveObject Then Continue;
          If PointInWRect(X,Y,Obj.Shell.X1,Obj.Shell.Y1,Obj.Shell.X2,Obj.Shell.Y2) Then
            ResultList.AddObject(Obj.Name,Obj);
        End;

        If VisibleList.Objects[I] Is TVCallout Then Begin
          Callout:=VisibleList.Objects[I] As TVCallout;
          //Dont check objects, belongs to disabled group
          ActiveObject:=True;
          If Callout.GroupList.Count>0 Then
            For K:=0 To Callout.GroupList.Count-1 Do Begin
              If Not (Callout.GroupList.Objects[K] As TVGroup).Active Then ActiveObject:=False;
            end;
          If Not ActiveObject Then Continue;
          If PointInWRect(X,Y,Callout.Shell.X1,Callout.Shell.Y1,Callout.Shell.X2,Callout.Shell.Y2) Then
            ResultList.AddObject(Callout.Name,Callout);
        End;
        If VisibleList.Objects[I] Is TVConnection Then Begin
          Connection:=VisibleList.Objects[I] As TVConnection;
          //Dont check objects, belongs to disabled group
          ActiveObject:=True;
          If Connection.GroupList.Count>0 Then
            For K:=0 To Connection.GroupList.Count-1 Do Begin
              If Not (Connection.GroupList.Objects[K] As TVGroup).Active Then ActiveObject:=False;
            end;
          If Not ActiveObject Then Continue;
          If PointInWRect(X,Y,Connection.Shell.X1,Connection.Shell.Y1,Connection.Shell.X2,Connection.Shell.Y2)Or
             (Abs(Connection.Shell.X1-Connection.Shell.X2)/Drawer.ViewScale<3) Or (Abs(Connection.Shell.Y1-Connection.Shell.Y2)/Drawer.ViewScale<3) Then Begin
            // уточненная проверка поотрезочно
            If Connection.PortList.Count>1 Then Begin
              ConnPort1:=Connection.PortList.Objects[0] As TVConnectionPort;
              Drawer.WToS(ConnPort1.AX,ConnPort1.AY,x1s,y1s);
              For J:=1 To Connection.PortList.Count-1 Do Begin
                ConnPort2:=Connection.PortList.Objects[J] As TVConnectionPort;
                If ConnPort2.PortType='Text' Then Continue;
                Drawer.WToS(ConnPort2.AX,ConnPort2.AY,x2s,y2s);
                If Abs(x1s-x2s)<=SamePlaceZone Then Begin
                  //вырожденный случай - вертикальная линия
                  If ((Abs(cxs-x1s)<=SamePlaceZone)Or(Abs(cxs-x2s)<=SamePlaceZone)) And
                     ( ((y1s<=cys)And(cys<=y2s))
                       Or
                       (y2s<=cys)And(cys<=y1s))
                     Then Begin ResultList.AddObject(Connection.Name,Connection);Break;End;
                End Else Begin
                  If (Abs((cys-y1s)-(cxs-x1s)*(y2s-y1s)/(x2s-x1s))<=SamePlaceZone)
                     And
                     ( ((x1s<=cxs)And(cxs<=x2s))
                       Or
                       ((x2s<=cxs)And(cxs<=x1s))
                     )
                  Then Begin
                    ResultList.AddObject(Connection.Name,Connection);Break;
                  end;
                End;
                ConnPort1:=ConnPort2;
                x1s:=x2s;y1s:=y2s;
              end;
            end;
         //   ResultList.AddObject(Connection.Name,Connection);
          end;
        end;

{        //Dont check objects, belongs to disabled group
        ActiveObject:=True;
        If Curl.GroupList.Count>0 Then
          For K:=0 To Obj.GroupList.Count-1 Do Begin
            If Not (Obj.GroupList.Objects[K] As TVGroup).Active Then ActiveObject:=False;
          end;
        If Not ActiveObject Then Continue;}
      End;
    End;
    Result:=ResultList;
end;

Procedure TVScene.CreateGroup(GroupName:String);
Var Group:TVGroup;
    Name:String;
Begin
  If VGroupList.IndexOf(GroupName)>=0 Then Exit;
  Group:=TVGroup.Create;
  VGroupList.AddObject(GroupName,Group);
  Group.Name:=GroupName;
  Group.Visible:=True;
  Group.Active:=True;
  Group.Glue:=False;
end;

Procedure TVScene.DeleteGroup(GroupName:String);
Var Group:TVGroup;
    Name:String;
    I,N,GN:Longint;
    Obj:TVObject;Callout:TVCallout;
    Conn:TVConnection;
Begin
  N:=VGroupList.IndexOf(GroupName);
  If N<0 Then Exit;
  Group:=VGroupList.Objects[N] As TVGroup;
  If Group.DisplayList.Count>0 Then
     For I:=Group.DisplayList.Count-1 Downto 0 Do Begin
       N:=VObjectList.IndexOf(Group.DisplayList[I]);
       If N>=0 Then Begin
         Obj:=VObjectList.Objects[N] As TVObject;
         GN:=Obj.GroupList.IndexOf(Group.Name);
         If GN>=0 Then Obj.GroupList.Delete(GN);
       end;
       N:=VCalloutList.IndexOf(Group.DisplayList[I]);
       If N>=0 Then Begin
         Callout:=VCalloutList.Objects[N] As TVCallout;
         GN:=Callout.GroupList.IndexOf(Group.Name);
         If GN>=0 Then Obj.GroupList.Delete(GN);
       end;
       If N>=0 Then Begin
         Conn:=VConnectionList.Objects[N] As TVConnection;
         GN:=Conn.GroupList.IndexOf(Group.Name);
         If GN>=0 Then Conn.GroupList.Delete(GN);
       end;
     End;
  VGroupList.Delete(N);
  If Group=ActiveGroup Then ActiveGroup:=Nil;
  Group.Free;
end;

Procedure TVScene.EraseGroup(GroupName:String);
Var Group:TVGroup;
    Name:String;
    I,N,NN,GN:Longint;
    Obj:TVObject;Callout:TVCallout;
    Conn:TVConnection;
Begin
  NN:=VGroupList.IndexOf(GroupName);
  If NN<0 Then Exit;
  Group:=VGroupList.Objects[NN] As TVGroup;
  If Group.DisplayList.Count>0 Then
    Repeat
      For I:=Group.DisplayList.Count-1 Downto 0 Do Begin
        N:=VConnectionList.IndexOf(Group.DisplayList[I]);
        If N>=0 Then Begin
          Conn:=VConnectionList.Objects[N] As TVConnection;
          DeleteConnection(Conn);
          Break;
        end;
        N:=VCalloutList.IndexOf(Group.DisplayList[I]);
        If N>=0 Then Begin
          Callout:=VCalloutList.Objects[N] As TVCallout;
          DeleteCallout(Callout);
          Break;
        end;
        N:=VObjectList.IndexOf(Group.DisplayList[I]);
        If N>=0 Then Begin
          Obj:=VObjectList.Objects[N] As TVObject;
          DeleteObject(Obj);
          Break;
        end;
      End;
    Until Group.DisplayList.Count=0;
  VGroupList.Delete(NN);
  If Group=ActiveGroup Then ActiveGroup:=Nil;
  Group.Free;
end;

Procedure TVScene.SetActiveGroup(GroupName:String);
Var Group:TVGroup;
    Name:String;
    I,N,GN:Longint;
    Obj:TVObject;Callout:TVCallout;
    Conn:TVConnection;
Begin
  N:=VGroupList.IndexOf(GroupName);
  If N<0 Then ActiveGroup:=Nil
  Else ActiveGroup:=VGroupList.Objects[N] As TVGroup;
End;

Procedure TVScene.AddItemToGroup(GroupName,ItemName:String);
Var I,N:Longint;
    Group:TVGroup;
    Obj:TVObject;
    Callout:TVCallout;
    Connection:TVConnection;
Begin
  N:=VGroupList.IndexOf(GroupName);
  If N<0 Then Exit; //Error
  Group:=VGroupList.Objects[N] As TVGroup;
  I:=VObjectList.IndexOf(ItemName);
  If I>=0 Then Begin
    Obj:=VObjectList.Objects[I] As TVObject;
    If Obj.GroupList.IndexOf(GroupName)<0 Then
      Obj.GroupList.AddObject(GroupName,Group);
    If Group.DisplayList.IndexOf(Obj.Name)<0 Then Group.DisplayList.Add(ItemName);
    Exit;
  end;
  I:=VCalloutList.IndexOf(ItemName);
  If I>=0 Then Begin
    Callout:=VCalloutList.Objects[I] As TVCallout;
    If Callout.GroupList.IndexOf(GroupName)<0 Then
      Callout.GroupList.AddObject(GroupName,Group);
     If Group.DisplayList.IndexOf(Callout.Name)<0 Then Group.DisplayList.Add(ItemName);
    Exit;
  end;
  I:=VConnectionList.IndexOf(ItemName);
  If I>=0 Then Begin
    Connection:=VConnectionList.Objects[I] As TVConnection;
    If Connection.GroupList.IndexOf(GroupName)<0 Then
      Connection.GroupList.AddObject(GroupName,Group);
     If Group.DisplayList.IndexOf(Connection.Name)<0 Then Group.DisplayList.Add(ItemName);
    Exit;
  end;
End;

Procedure TVScene.DelItemFromGroup(GroupName,ItemName:String);
Var I,N:Longint;
    Group:TVGroup;
    Obj:TVObject;
    Callout:TVCallout;
    Connection:TVConnection;
Begin
  N:=VGroupList.IndexOf(GroupName);
  If N<0 Then Exit; //Error
  Group:=VGroupList.Objects[N] As TVGroup;
  I:=VObjectList.IndexOf(ItemName);
  If I>=0 Then Begin
    Obj:=VObjectList.Objects[I] As TVObject;
    If Obj.GroupList.IndexOf(GroupName)>=0 Then
      Obj.GroupList.Delete(Obj.GroupList.IndexOf(GroupName));
    If Group.DisplayList.IndexOf(Obj.Name)>=0 Then
      Group.DisplayList.Delete(Group.DisplayList.IndexOf(Obj.Name));
    Exit;
  end;
  I:=VCalloutList.IndexOf(ItemName);
  If I>=0 Then Begin
    Callout:=VCalloutList.Objects[I] As TVCallout;
    If Callout.GroupList.IndexOf(GroupName)>=0 Then
      Callout.GroupList.Delete(Callout.GroupList.IndexOf(GroupName));
     If Group.DisplayList.IndexOf(Callout.Name)<0 Then
       Group.DisplayList.Delete(Group.DisplayList.IndexOf(Callout.Name));
    Exit;
  end;
  I:=VConnectionList.IndexOf(ItemName);
  If I>=0 Then Begin
    Connection:=VConnectionList.Objects[I] As TVConnection;
    If Connection.GroupList.IndexOf(GroupName)>=0 Then
      Connection.GroupList.Delete(Connection.GroupList.IndexOf(GroupName));
     If Group.DisplayList.IndexOf(Connection.Name)>=0 Then
       Group.DisplayList.Delete(Group.DisplayList.IndexOf(Connection.Name));
    Exit;
  end;
End;

Procedure TVScene.ExecCommand(Command:String; Acc:TAcceleratedCommand; Depth:Longint);
Var I,J,N,N1:Longint; G,GX,Gr,GY,x1,x2,x3,x4,y1,y2,y3,y4,lx,ly,rx,ry:Extended;
    Cmd,TemplName,sX,sY,sA,sS,Buf,ParsedBuf:String;
    Sm,Rm,Scm,tmpm,OldTransM,NewTransM:TMatrix2D;
    DisplayList:TStringList;
    X,Y,A,S:Extended;
    SubDrawer:TDrawer;
    Curl:TVCurl; Obj,Obj1:TVObject; Templ:TVTemplate; Connection:TVConnection; Group:TVGroup; Callout:TVCallout;
    DebugC:String;DebugAcc:TAcceleratedCommand;
    //потом убрать в дравер или ещё куда
    ULx,ULy,URx,URy,LLx,LLy,LRx,LRy:Extended;
    TemplDiam:Extended;
Begin
  Buf:=Command;
  Cmd:=GetNextToken(Buf,ParsedBuf,' ');
  Buf:=ParsedBuf;
  Case Cmd Of
    'DrawCurl': Begin
      N:=VCurlList.IndexOf(Buf);
      Curl:=VCurlList.Objects[N] As TVCurl;
      If Clip Then Begin
        If Curl.TooSmall Then Exit;
        If Curl.OffScreen Then Exit;
      End;
      DisplayList:=Curl.DisplayList;
      If DisplayList.Count>0 Then
        For J:=0 To DisplayList.Count-1 Do Begin
          DebugC:=DisplayList[J];
          DebugAcc:=(DisplayList.Objects[J] As TAccObject).AcceleratedCommand;
          Drawer.ExecCommand(DebugC,DebugAcc);
        End;
      If ShowShells Then Begin
        Drawer.ExecCommand('SetColor clRed',ZeroAcc);
        Drawer.ExecCommand('SetWidth 1',ZeroAcc);
        Drawer.ExecCommand('MoveTo '+FloatToStr(Curl.Shell.X1)+','+FloatToStr(Curl.Shell.Y1),ZeroAcc);
        Drawer.ExecCommand('LineTo '+FloatToStr(Curl.Shell.X1)+','+FloatToStr(Curl.Shell.Y2),ZeroAcc);
        Drawer.ExecCommand('LineTo '+FloatToStr(Curl.Shell.X2)+','+FloatToStr(Curl.Shell.Y2),ZeroAcc);
        Drawer.ExecCommand('LineTo '+FloatToStr(Curl.Shell.X2)+','+FloatToStr(Curl.Shell.Y1),ZeroAcc);
        Drawer.ExecCommand('LineTo '+FloatToStr(Curl.Shell.X1)+','+FloatToStr(Curl.Shell.Y1),ZeroAcc);
      End;
    End;
    'DrawConnection': Begin
        N:=VConnectionList.IndexOf(Buf);
        Connection:=VConnectionList.Objects[N] As TVConnection;
        If Connection.GroupList.Count>0 Then
        For J:=0 To Connection.GroupList.Count-1 Do Begin
          Group:=Connection.GroupList.Objects[J] As TVGroup;
          If Not Group.Visible Then Exit;
        End;
        If Clip Then Begin
          //соединение не рисуется, если оно ведёт к невидимому объекту (отключенный слой)
          N:=VObjectList.IndexOf(Connection.StartObj);
          If N<0 Then Exit;
          Obj:=VObjectList.Objects[N] As TVObject;
          If Obj.TooSmall Then Exit;
          If Obj.GroupList.Count>0 Then
            For J:=0 To Obj.GroupList.Count-1 Do Begin
              Group:=Obj.GroupList.Objects[J] As TVGroup;
              If Not Group.Visible Then Exit;
            End;
          N:=VObjectList.IndexOf(Connection.EndObj);
          If N<0 Then Exit;
          Obj:=VObjectList.Objects[N] As TVObject;
          If Obj.TooSmall Then Exit;
          If Obj.GroupList.Count>0 Then
            For J:=0 To Obj.GroupList.Count-1 Do Begin
              Group:=Obj.GroupList.Objects[J] As TVGroup;
              If Not Group.Visible Then Exit;
            End;
          If Not ShellsAreCrossed(ScreenShell,Connection.Shell) Then Exit;
        End;
        DisplayList:=Connection.DisplayList;
        If DisplayList.Count>0 Then
          For J:=0 To DisplayList.Count-1 Do Begin
            DebugC:=DisplayList[J];
            DebugAcc:=(DisplayList.Objects[J] As TAccObject).AcceleratedCommand;
            Drawer.ExecCommand(DebugC,DebugAcc);
          End;
        If ShowShells Then Begin
          Drawer.ExecCommand('SetColor clRed',ZeroAcc);
          Drawer.ExecCommand('SetWidth 1',ZeroAcc);
          Drawer.ExecCommand('MoveTo '+FloatToStr(Connection.Shell.X1)+','+FloatToStr(Connection.Shell.Y1),ZeroAcc);
          Drawer.ExecCommand('LineTo '+FloatToStr(Connection.Shell.X1)+','+FloatToStr(Connection.Shell.Y2),ZeroAcc);
          Drawer.ExecCommand('LineTo '+FloatToStr(Connection.Shell.X2)+','+FloatToStr(Connection.Shell.Y2),ZeroAcc);
          Drawer.ExecCommand('LineTo '+FloatToStr(Connection.Shell.X2)+','+FloatToStr(Connection.Shell.Y1),ZeroAcc);
          Drawer.ExecCommand('LineTo '+FloatToStr(Connection.Shell.X1)+','+FloatToStr(Connection.Shell.Y1),ZeroAcc);
        End;
      End;
   'DrawObject': Begin
      N:=VObjectList.IndexOf(Buf);
      If N<0 Then Exit;
      Obj:=VObjectList.Objects[N] As TVObject;
      If Obj.GroupList.Count>0 Then
        For J:=0 To Obj.GroupList.Count-1 Do Begin
          Group:=Obj.GroupList.Objects[J] As TVGroup;
          If Not Group.Visible Then Exit;
        End;
      If Clip Then Begin
        If Obj.TooSmall Then Exit;
        If Obj.OffScreen Then Exit;
      End;
      DisplayList:=Obj.DisplayList;
      OldTransM:=Drawer.TransM;
      If Obj.A<700 Then Begin  //exclude special objects
        Sm:=GenShift(Obj.X,Obj.Y);
        Rm:=GenRotate(Obj.X,Obj.Y,Obj.A/180*pi);
        Scm:=GenScale(Obj.X,Obj.Y,Obj.S,Obj.S);
        tmpm:=MulMat2D(Sm,Rm);
        tmpm:=MulMat2D(Tmpm,Scm);
        OldTransM:=Drawer.TransM;
        NewTransM:=MulMat2D(tmpm,Drawer.TransM);
        Drawer.TransM:=NewTransM;
      End Else NewTransM:=OldTransM;

      If DisplayList.Count>0 Then
        For J:=0 To DisplayList.Count-1 Do Begin
          //Acc:=?
          If Pos('DrawTemplate',DisplayList[J])=1 Then Begin
            DebugC:=DisplayList[J];
            DebugAcc:=(DisplayList.Objects[J] As TAccObject).AcceleratedCommand;     //why was commented ???
            ExecCommand(DebugC,DebugAcc,Depth);// why was zeroacc
          end Else Begin
            DebugC:=DisplayList[J];
            DebugAcc:=(DisplayList.Objects[J] As TAccObject).AcceleratedCommand;
            If Pos('UnScaled',DisplayList[J])=1 Then Begin
//              Drawer.ExecCommand('SetWidth 0',ZeroAcc);//тонкие порты теперь вшиты в UnScaled
              Drawer.TransM:=OldTransM;
              Drawer.ExecCommand(DebugC,DebugAcc);
            End Else Begin
              Drawer.TransM:=NewTransM;
              Drawer.ExecCommand(DebugC,DebugAcc);
            End;
          End;
        End;
      Drawer.TransM:=OldTransM;

      If ShowShells Then Begin
        Drawer.ExecCommand('SetColor clRed',ZeroAcc);
        Drawer.ExecCommand('SetWidth 0',ZeroAcc);
        Drawer.ExecCommand('MoveTo '+FloatToStr(Obj.Shell.X1)+','+FloatToStr(Obj.Shell.Y1),ZeroAcc);
        Drawer.ExecCommand('LineTo '+FloatToStr(Obj.Shell.X1)+','+FloatToStr(Obj.Shell.Y2),ZeroAcc);
        Drawer.ExecCommand('LineTo '+FloatToStr(Obj.Shell.X2)+','+FloatToStr(Obj.Shell.Y2),ZeroAcc);
        Drawer.ExecCommand('LineTo '+FloatToStr(Obj.Shell.X2)+','+FloatToStr(Obj.Shell.Y1),ZeroAcc);
        Drawer.ExecCommand('LineTo '+FloatToStr(Obj.Shell.X1)+','+FloatToStr(Obj.Shell.Y1),ZeroAcc);
      End;
    End;
   'DrawTemplate': Begin
      If Depth>MaxDisplayedDepth Then Exit;
      TemplName:=GetNextToken(Buf,ParsedBuf,' ');
      Buf:=ParsedBuf;
      sX:=GetNextToken(Buf,ParsedBuf,',');
      Buf:=ParsedBuf;
      sY:=GetNextToken(Buf,ParsedBuf,',');
      Buf:=ParsedBuf;
      sA:=GetNextToken(Buf,ParsedBuf,',');
      sS:=ParsedBuf;
      X:=StrToFloat(sX);
      Y:=StrToFloat(sY);
      A:=StrToFloat(sA);
      S:=StrToFloat(sS);
      N1:=VTemplateList.IndexOf(TemplName);
      If N1<0 Then exit;
      Templ:=VTemplateList.Objects[N1] As TVTemplate;
      If Self.Clip Then Begin

        If not InfiniteShell(Templ.Shell) Then
          If Self.ClipMax Then TemplDiam:=Max(Templ.Shell.X2-Templ.Shell.X1,Templ.Shell.Y2-Templ.Shell.Y1)
          Else TemplDiam:=Min(Templ.Shell.X2-Templ.Shell.X1,Templ.Shell.Y2-Templ.Shell.Y1);
          //  If TemplDiam/S/Self.Drawer.ViewScale<Self.ClipSize then Exit;
            //  *S or /S - who knows?
      end;
      DisplayList:=Templ.DisplayList;
      Sm:=GenShift(X,Y);
      Rm:=GenRotate(X,Y,A/180*pi);
      Scm:=GenScale(X,Y,S,S);
      tmpm:=MulMat2D(Sm,Rm);
      tmpm:=MulMat2D(Tmpm,Scm);
      OldTransM:=Drawer.TransM;
      Drawer.TransM:=MulMat2D(tmpm,Drawer.TransM);
      If DisplayList.Count>0 Then
        For J:=0 To DisplayList.Count-1 Do Begin
          // Acc:=?
          If Pos('DrawTemplate',DisplayList[J])=1 Then Begin
            DebugC:=DisplayList[J];
            DebugAcc:=(DisplayList.Objects[J] As TAccObject).AcceleratedCommand;
            ExecCommand(DebugC,DebugAcc,Depth+1);
          end Else Begin
            DebugC:=DisplayList[J];
            DebugAcc:=(DisplayList.Objects[J] As TAccObject).AcceleratedCommand;
            Drawer.ExecCommand(DebugC,DebugAcc);
          end;
        End;
      Drawer.TransM:=OldTransM;
    End;
    'DrawCallout': Begin
        N:=VCalloutList.IndexOf(Buf);
        Callout:=VCalloutList.Objects[N] As TVCallout;
        If Callout.GroupList.Count>0 Then
        For J:=0 To Callout.GroupList.Count-1 Do Begin
          Group:=Callout.GroupList.Objects[J] As TVGroup;
          If Not Group.Visible Then Exit;
        End;
        If Clip Then Begin
          If Clip Then Begin
            If Callout.TooSmall Then Exit;
            If Callout.OffScreen Then Exit;
          End;
        End;

          //выноска не рисуется, если оно ведёт к невидимому объекту (отключенный слой)
          N:=VObjectList.IndexOf(Callout.RelatedObj);
          If N>=0 Then Begin
            Obj:=VObjectList.Objects[N] As TVObject;
            If Obj.TooSmall Then Exit;
            If Obj.GroupList.Count>0 Then
              For J:=0 To Obj.GroupList.Count-1 Do Begin
                Group:=Obj.GroupList.Objects[J] As TVGroup;
                If Not Group.Visible Then Exit;
              End;
          end;

        DisplayList:=Callout.DisplayList;
        If DisplayList.Count>0 Then
          For J:=0 To DisplayList.Count-1 Do Begin
            DebugC:=DisplayList[J];
            DebugAcc:=(DisplayList.Objects[J] As TAccObject).AcceleratedCommand;
            Drawer.ExecCommand(DebugC,DebugAcc);
          End;
        If ShowShells Then Begin
          Drawer.ExecCommand('SetColor clRed',ZeroAcc);
          Drawer.ExecCommand('SetWidth 1',ZeroAcc);
          Drawer.ExecCommand('MoveTo '+FloatToStr(Callout.Shell.X1)+','+FloatToStr(Callout.Shell.Y1),ZeroAcc);
          Drawer.ExecCommand('LineTo '+FloatToStr(Callout.Shell.X1)+','+FloatToStr(Callout.Shell.Y2),ZeroAcc);
          Drawer.ExecCommand('LineTo '+FloatToStr(Callout.Shell.X2)+','+FloatToStr(Callout.Shell.Y2),ZeroAcc);
          Drawer.ExecCommand('LineTo '+FloatToStr(Callout.Shell.X2)+','+FloatToStr(Callout.Shell.Y1),ZeroAcc);
          Drawer.ExecCommand('LineTo '+FloatToStr(Callout.Shell.X1)+','+FloatToStr(Callout.Shell.Y1),ZeroAcc);
        End;
      End;



  Else Drawer.ExecCommand(Command,Acc);
  End;
End;

Procedure InitModule;
Begin
{  DispatcherForm.ModulesList.Items.Add('CADEngine');
  ICommands:=TStringList.Create;}
//  StrToLinesBuffer:=TStringList.Create;
End;

Function ExecCommand(Command:String):String;
var B,PB,Obj,Cmd,Attr,AttrType,AttrName,AttrValue:String;
    oid,Path,Module,ClassPath,ClassName:String;
Begin
  Obj:=GetNextToken(Command,PB,':');
  B:=PB;
  Cmd:=GetNextToken(B,PB,':');
  Attr:=PB;
  If Obj='' Then Begin
// InitDB:<path>#:<module>:[<path to meta>]
    If LowerCase(Cmd)='drawcurl' Then Begin
      Path:=GetNextToken(Attr,PB,'#:');
      B:=PB;
      Module:=GetNextToken(B,PB,':');
      ClassPath:=PB;
      If Module='' Then Module:='0';
      If ClassPath='' Then ClassPath:=OSPath(Path+'\metadata');
      Try
//       InitDB(MainDB,Path,StrToInt(Module),ClassPath);
      Except On EConvertError Do
        LogMessage(1,'bad Module value:'+Module);
      End;
      Result:='';
    End;
  End;
End;

initialization
 // ICommands:=TStringList.Create;
  //StrToLinesBuffer:=TStringList.Create;

end.

