unit CADEngine;

{$mode objfpc}{$H+}

interface

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

Type
  TVCurl=Class(TObject)
    Name:String;
    Shell:TShell;
    DisplayList:TStringList;
    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;
    X,Y,A,S:Extended;
    DisplayList:TStringList;
    OffScreen:Boolean;
    TooSmall:Boolean;
    Shell:TShell;
    OType:Longint; // 0 - just object, 1 - textout, 2 - binary link
    PortList:TStringList;
    NonPortConnectionList:TStringList;
    GroupList:TStringList;
    RelatedCAList:TStringList;
    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;
    GroupList:TStringList;
  end;

  TVConnection=Class(TObject)
    Name:String;
    StartObj,EndObj:String;
    Style:String;
    X,Y:Extended;
    DisplayList:TStringList;
    OffScreen:Boolean;
    TooSmall:Boolean;
    Shell:TShell;
    OType:Longint; // 0 - just object, 1 - textout, 2 - binary link
    PortList:TStringList;
    GroupList:TStringList;
    Constructor Create;
    Destructor Destroy;
  End;

  TVCallout=Class(TObject)
    Name:String;
    RelatedObj:String;
    Style:String;
    X,Y:Extended;
    W,H:Extended;
    Source:String;
    Text:String;
    DisplayList:TStringList;
    OffScreen:Boolean;
    TooSmall:Boolean;
    Shell:TShell;
    OType:Longint; // 0 - just object, 1 - textout, 2 - binary link
    PortList:TStringList;
    GroupList:TStringList;
    ActionList:TStringList;
    Constructor Create;
    Destructor Destroy;
  End;

  TVGroup=Class(TObject)
    Name:String;
    Glue:Boolean;
    Active:Boolean;
    Visible:Boolean;
    DisplayList:TStringList; //group is logical union of objects - no other drawing commands allowed in it's display list
    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=Class(TObject)
    ShowObjectNames:Boolean;
    ShowShells:Boolean;
    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 objects
    View:TStringList; // Master display list used for visualization      - sequence of drawing
    Drawer:TDrawer;
    Clip:Boolean;
    ClipMax:Boolean;
    ClipSize:Longint;
    MaxDisplayedDepth:Longint;
    ScreenShell:TShell;
    Constructor Create;
    Destructor Destroy;
    Procedure SaveFile(FileName:String);
    Procedure LoadFile(FileName:string);
    Procedure UpdateVisibility;
    Procedure Clear;
    Procedure ExecCommand(Command:String;Acc:TAcceleratedCommand;Depth:Longint);
    Function BuildCurlsUnder(X,Y:Extended):TStringList;
    Function BuildObjectsUnder(X,Y:Extended):TStringList;
    Function BuildConnectionsUnder(X,Y:Extended):TStringList;
    Function BuildCalloutsUnder(X,Y:Extended):TStringList;
  end;

Var
  ICommands:TStringList;
//  StrToLinesBuffer:TStringList;

Procedure InitModule;
Function ExecCommand(Command:String):String;
Function RenderShell(Scene:TVScene;CommandList:TStrings;M:TMatrix2D):TShell;
Procedure RenderObjectShell(Scene:TVScene;Obj:TVObject);
Procedure UpdateConnectionDisplayList(Scene:TVScene;Connection:TVConnection);
Procedure UpdateCalloutDisplayList(Scene:TVScene;Callout:TVCallout);
Procedure MoveObjectTo(Scene:TVScene;Obj:TVObject;X,Y:Extended;GroupList:String);
Procedure MoveCalloutTo(Scene:TVScene;Callout:TVCallout;X,Y:Extended;GroupList:String);

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)
}

Procedure RenderConnDisplayList(Scene:TVScene;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 RenderShell(Scene:TVScene;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': 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;
      'bmp':    Begin
               Xws:=GetNextToken(Buf,ParsedBuf,',');
               Buf:=ParsedBuf;
               Yws:=GetNextToken(Buf,ParsedBuf,',');
               Xw:=StrToFloat(Xws);Yw:=StrToFloat(Yws);
             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:=Scene.VTemplateList.IndexOf(FName);
               If N>=0 Then Begin
                 TemplateDisplayList:=(Scene.VTemplateList.Objects[N] As TVTemplate).DisplayList;
                 SubShell:=RenderShell(Scene,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 RenderObjectShell(Scene:TVScene;Obj:TVObject);
Var Sm,Rm,Scm,tmpm,TransM:TMatrix2D;
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);
  tmpm:=MulMat2D(Sm,Rm);
  TransM:=MulMat2D(Tmpm,Scm);
  Obj.Shell:=RenderShell(Scene,Obj.DisplayList,TransM);
End;

Procedure UpdateConnectionDisplayList(Scene:TVScene;Connection:TVConnection);
Var I,Index:Longint;
    AccObject:TAccObject;
    Col,BGCol,Width,LineStyle:String;
    NonAccCommand:String;
    ConnPort:TVConnectionPort;
    Obj:TVObject; Port:TVPort;
    E:TMatrix2D;
Begin
  Col:=GetValByName(Connection.Style,'Color');
  BGCol:=GetValByName(Connection.Style,'BGColor');
  Width:=GetValByName(Connection.Style,'Width');
  LineStyle:=GetValByName(Connection.Style,'LineStyle');
  AccObject:=TAccObject.Create;
  AccObject.AcceleratedCommand:=AccelerateCommand('SetColor '+Col);
  Connection.DisplayList.AddObject('SetColor '+Col,AccObject);
  AccObject:=TAccObject.Create;
  AccObject.AcceleratedCommand:=AccelerateCommand('SetBGColor '+BGCol);
  Connection.DisplayList.AddObject('SetBGColor '+BGCol,AccObject);
  AccObject:=TAccObject.Create;
  AccObject.AcceleratedCommand:=AccelerateCommand('SetWidth '+Width);
  Connection.DisplayList.AddObject('SetWidth '+Width,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:=Scene.VObjectList.IndexOf(ConnPort.ConnectedObject);
      If Index<0 Then Exit;
      Obj:=Scene.VObjectList.Objects[Index] As TVObject;
      AccObject:=TAccObject.Create;
      If ConnPort.ConnectedPort='' Then NonAccCommand:='MoveTo '+FloatToStr(Obj.X)+','+FloatToStr(Obj.Y)
      Else Begin
        Port:=Obj.PortList.Objects[Obj.PortList.IndexOf(ConnPort.ConnectedPort)] As TVPort;
        NonAccCommand:='MoveTo '+FloatToStr(Port.AX)+','+FloatToStr(Port.AY)
      end;
      AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
      Connection.DisplayList.AddObject(NonAccCommand,AccObject);
    End;
    If ConnPort.PortType='End' Then Begin
      Index:=Scene.VObjectList.IndexOf(ConnPort.ConnectedObject);
      If Index<0 Then Exit;
      Obj:=Scene.VObjectList.Objects[Index] As TVObject;
      AccObject:=TAccObject.Create;
      If ConnPort.ConnectedPort='' Then NonAccCommand:='LineTo '+FloatToStr(Obj.X)+','+FloatToStr(Obj.Y)
      Else Begin
        Port:=Obj.PortList.Objects[Obj.PortList.IndexOf(ConnPort.ConnectedPort)] As TVPort;
        NonAccCommand:='LineTo '+FloatToStr(Port.AX)+','+FloatToStr(Port.AY)
      End;
      AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
      Connection.DisplayList.AddObject(NonAccCommand,AccObject);
    End;
  End;
  E:=GenShift(0,0);
  Connection.Shell:=RenderShell(Scene,Connection.DisplayList,E);
end;

Procedure UpdateCalloutDisplayList(Scene:TVScene;Callout:TVCallout);   //пока неправильно вообще
Var I,N,Index:Longint;
    AccObject:TAccObject;
    Source,Col,BGCol,Width,LineStyle,BindMode,FontName,FontColor,FontHeight,BIUS,Transparent:String;
    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;  }
  Col:=GetValByName(Callout.Style,'Color');
  BGCol:=GetValByName(Callout.Style,'BGColor');
  Width:=GetValByName(Callout.Style,'Width');
//  W:=StrToFloat(Width)*Scene.Scale;
  LineStyle:=GetValByName(Callout.Style,'LineStyle');
  FontName:=GetValByName(Callout.Style,'FontName');
  FontColor:=GetValByName(Callout.Style,'FontColor');
  FontHeight:=GetValByName(Callout.Style,'FontHeight');
  BIUS:=GetValByName(Callout.Style,'BIUS');
  BindMode:=GetValByName(Callout.Style,'BindMode');
  Transparent:=GetValByName(Callout.Style,'Transparent');
  If Callout.RelatedObj<>'' Then Begin
      N:=Scene.VObjectList.IndexOf(Callout.RelatedObj);
      If N>=0 Then Begin
        Obj:=Scene.VObjectList.Objects[N] As TVObject;
        Case 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(Transparent)='yes' Then Begin
  End Else Begin
    AccObject:=TAccObject.Create;
    NonAccCommand:='SetColor '+Col;
    AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
    Callout.DisplayList.AddObject(NonAccCommand,AccObject);

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

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

    AccObject:=TAccObject.Create;
    If 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 '+FontName+','+(FontHeight)+','+(FontColor)+','+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);

  AccObject:=TAccObject.Create;
//  NonAccCommand:='TextRect '+FloatToStr(Callout.X)+','+FloatToStr(Callout.Y)+','+FloatToStr(Callout.X+Callout.W)+','+FloatToStr(Callout.Y-Callout.H)+','+Callout.Text;
  If 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;
  AccObject.AcceleratedCommand:=AccelerateCommand(NonAccCommand);
  Callout.DisplayList.AddObject(NonAccCommand,AccObject);
  E:=GenShift(0,0);
  Callout.Shell:=RenderShell(Scene,Callout.DisplayList,E);
end;

Procedure MoveObjectTo(Scene:TVScene;Obj:TVObject;X,Y:Extended;GroupList:String);
Var I,J,K,N,Index: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;
Begin
  Dx:=X-Obj.X;
  Dy:=Y-Obj.Y;
  Obj.X:=X;
  Obj.Y:=Y;
  //группы 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:=Scene.VGroupList.IndexOf(Obj.GroupList[K]);
      If N>=0 Then Group:=Scene.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:=Scene.VObjectList.IndexOf(Group.DisplayList[J]);
            If Index<0 Then Continue; //не объект, а связь
            Obj2:=Scene.VObjectList.Objects[Index] As TVObject;
            If Obj=Obj2 Then Continue;
            MoveObjectTo(Scene,Obj2,Obj2.X+Dx,Obj2.Y+Dy,GroupList+','+Group.Name);
          end;
      end;
    end;
  //обновляем связанные выноски
  RenderObjectShell(Scene,Obj);
  For K:=0 To Obj.RelatedCAList.Count-1 Do Begin
    Callout:=Obj.RelatedCAList.Objects[K] As TVCallout;
    UpdateCalloutDisplayList(Scene,Callout);
  end;
  //удаляем команды старой отрисовки портов
  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;
  // пересчитать абс коорд портов
  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);
  TransM:=MulMat2D(Tmpm,Scm);
  Obj.Shell:=RenderShell(Scene,Obj.DisplayList,TransM);
  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
        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;
      //Добавление портов в DisplayList
      AccObject:=TAccObject.Create;
      AccObject.AcceleratedCommand:=AccelerateCommand('Unscaled '+FloatToStr(Port.Ax)+','+FloatToStr(Port.Ay)+',box,3');
      Obj.DisplayList.AddObject('Unscaled '+FloatToStr(Port.Ax)+','+FloatToStr(Port.Ay)+',box,3',AccObject);
    End;


  // найти подключенные без портов связи
  If Obj.NonPortConnectionList.Count>0 Then
    For I:=0 To Obj.NonPortConnectionList.Count-1 Do Begin
      Index:=Scene.VConnectionList.IndexOf(Obj.NonPortConnectionList[I]);
      If Index>=0 Then Begin
        Conn:=Scene.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(Scene,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;
  // найти подключенные к портам связи
  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:=Scene.VConnectionList.IndexOf(Port.Connection);
        Conn:=Scene.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(Scene,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 MoveCalloutTo(Scene:TVScene;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(Scene,Callout);
  Callout.Shell:=RenderShell(Scene,Callout.DisplayList,E);
  Scene.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:=Scene.VGroupList.IndexOf(Callout.GroupList[K]);
      If N>=0 Then Group:=Scene.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:=Scene.VObjectList.IndexOf(Group.DisplayList[J]);
            If Index<0 Then Continue; //не объект, а связь
            Callout2:=Scene.VObjectList.Objects[Index] As TVCallout;
            If Callout=Callout2 Then Continue;
            MoveCalloutTo(Scene,Callout2,Callout2.X+Dx,Callout2.Y+Dy,GroupList+','+Group.Name);
          end;
      end;
    end;
 {
  //удаляем команды старой отрисовки портов
  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;
  // пересчитать абс коорд портов
  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);
  TransM:=MulMat2D(Tmpm,Scm);
  Obj.Shell:=RenderShell(Scene,Obj.DisplayList,TransM);
  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
        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;
      //Добавление портов в DisplayList
      AccObject:=TAccObject.Create;
      AccObject.AcceleratedCommand:=AccelerateCommand('Unscaled '+FloatToStr(Port.Ax)+','+FloatToStr(Port.Ay)+',box,3');
      Obj.DisplayList.AddObject('Unscaled '+FloatToStr(Port.Ax)+','+FloatToStr(Port.Ay)+',box,3',AccObject);
    End;


  // найти подключенные без портов связи
  If Obj.NonPortConnectionList.Count>0 Then
    For I:=0 To Obj.NonPortConnectionList.Count-1 Do Begin
      Index:=Scene.VConnectionList.IndexOf(Obj.NonPortConnectionList[I]);
      If Index>=0 Then Begin
        Conn:=Scene.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(Scene,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;
  // найти подключенные к портам связи
  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:=Scene.VConnectionList.IndexOf(Port.Connection);
        Conn:=Scene.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(Scene,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;

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;
  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
  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(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;

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;
        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:=RenderShell(Self,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;
        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;
          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.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:=RenderShell(Self,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;
        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;
              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;
              UpdateConnectionDisplayList(Self,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;
              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:=RenderShell(Self,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);
              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;

Procedure TVScene.SaveFile(FileName:String);
Var FileContent: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;
      FileContent.Add('Curl name:'+Curl.Name);
      FileContent.Add('Lines:'+IntToStr(Curl.DisplayList.Count));
      FileContent.AddStrings(Curl.DisplayList);
    end;
  If VObjectList.Count>0 Then
    For I:=0 To VObjectList.Count-1 Do Begin
      VObject:=VObjectList.Objects[I] As TVObject;
      FileContent.Add('Object name:'+VObject.Name);
      FileContent.Add('X:'+FloatToStr(VObject.X));
      FileContent.Add('Y:'+FloatToStr(VObject.Y));
      FileContent.Add('A:'+FloatToStr(VObject.A));
      FileContent.Add('S:'+FloatToStr(VObject.S));
      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;
      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;
      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;
      FileContent.Add('NonPortConnectionList lines:'+IntToStr(VObject.NonPortConnectionList.Count));
      If VObject.NonPortConnectionList.Count>0 Then
      FileContent.AddStrings(VObject.NonPortConnectionList);
      FileContent.Add('GroupList lines:'+IntToStr(VObject.GroupList.Count));
      If VObject.GroupList.Count>0 Then
      FileContent.AddStrings(VObject.GroupList);
      FileContent.Add('ActionList lines:'+IntToStr(VObject.ActionList.Count));
      If VObject.ActionList.Count>0 Then
      FileContent.AddStrings(VObject.ActionList);

    end;

    If VConnectionList.Count>0 Then
      For I:=0 To VConnectionList.Count-1 Do Begin
        VConnection:=VConnectionList.Objects[I] As TVConnection;
        FileContent.Add('Connection name:'+VConnection.Name);
        FileContent.Add('X:'+FloatToStr(VConnection.X));
        FileContent.Add('Y:'+FloatToStr(VConnection.Y));
        FileContent.Add('StartObj:'+VConnection.StartObj);
        FileContent.Add('EndObj:'+VConnection.EndObj);
        FileContent.Add('Style:'+VConnection.Style);
        FileContent.Add('GroupList lines:'+IntToStr(VConnection.GroupList.Count));
        If VConnection.GroupList.Count>0 Then
          FileContent.AddStrings(VConnection.GroupList);

        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;
            FileContent.Add('PortType='+ConnPort.PortType+' AR='+ConnPort.AR+' X='+
               FloatToStr(ConnPort.X)+' Y='+FloatToStr(ConnPort.Y)+' Conn='+ConnPort.ConnectedObject+':'+ConnPort.ConnectedPort);
          end;
      end;
    If VCalloutList.Count>0 Then
         For I:=0 To VCalloutList.Count-1 Do Begin
           VCallout:=VCalloutList.Objects[I] As TVCallout;
           FileContent.Add('Callout name:'+VCallout.Name);
           FileContent.Add('X:'+FloatToStr(VCallout.X));
           FileContent.Add('Y:'+FloatToStr(VCallout.Y));
           FileContent.Add('W:'+FloatToStr(VCallout.W));
           FileContent.Add('H:'+FloatToStr(VCallout.H));

           FileContent.Add('RelatedObj:'+VCallout.RelatedObj);
           FileContent.Add('Style:'+VCallout.Style);
           FileContent.Add('Source:'+VCallout.Source);
        {   If VCallout.Source='' Then FileContent.Add('Text:'+VCallout.Text)
           Else FileContent.Add('Text:');}
           FileContent.Add('Text:'+VCallout.Text);

           FileContent.Add('GroupList lines:'+IntToStr(VCallout.GroupList.Count));
           If VCallout.GroupList.Count>0 Then
             FileContent.AddStrings(VCallout.GroupList);

           FileContent.Add('PortList lines:'+IntToStr(VCallout.PortList.Count));
           If VCallout.PortList.Count>0 Then
             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;
           FileContent.Add('ActionList lines:'+IntToStr(VCallout.ActionList.Count));
           If VCallout.ActionList.Count>0 Then
           FileContent.AddStrings(VCallout.ActionList);

         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;
    ObjDiam:Extended;
    SelHandle:TVSelHandle;
Begin
  If Self.VCurlList.Count>0 Then
      For I:=0 To Self.VCurlList.Count-1 Do Begin
        Curl:=Self.VCurlList.Objects[I] As TVCurl;
        If {Углы (или оболочка?)экрана не попадают в оболочку и оболочка не попадает в экран}
           ((Not PointInWrect(ScreenShell.X1,ScreenShell.Y1,Curl.Shell.X1,Curl.Shell.Y1,Curl.Shell.X2,Curl.Shell.Y2)) And
            (Not PointInWrect(ScreenShell.X2,ScreenShell.Y2,Curl.Shell.X1,Curl.Shell.Y1,Curl.Shell.X2,Curl.Shell.Y2)) And
            (Not PointInWrect(ScreenShell.X1,ScreenShell.Y2,Curl.Shell.X1,Curl.Shell.Y1,Curl.Shell.X2,Curl.Shell.Y2)) And
            (Not PointInWrect(ScreenShell.X2,ScreenShell.Y1,Curl.Shell.X1,Curl.Shell.Y1,Curl.Shell.X2,Curl.Shell.Y2))) And
           // надо не левый-нижний-правый верхний, а оболочку, а то при повороте врать будет
           ((Not PointInWrect(Curl.Shell.X1,Curl.Shell.Y1,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
            (Not PointInWrect(Curl.Shell.X1,Curl.Shell.Y2,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
            (Not PointInWrect(Curl.Shell.X2,Curl.Shell.Y1,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
            (Not PointInWrect(Curl.Shell.X2,Curl.Shell.Y2,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)))
          Then Curl.OffScreen:=True Else Curl.OffScreen:=False;
          If (Curl.Shell.X1=Math.maxExtended) Or
             (Curl.Shell.X1=-Math.maxExtended) Or
             (Curl.Shell.Y1=Math.maxExtended) Or
             (Curl.Shell.Y1=-Math.maxExtended) Or
             (Curl.Shell.X2=Math.maxExtended) Or
             (Curl.Shell.X2=-Math.maxExtended) Or
             (Curl.Shell.Y2=Math.maxExtended) Or
             (Curl.Shell.Y2=-Math.maxExtended)
          Then Begin
            Curl.TooSmall:=True;
          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;
      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 {Углы (или оболочка?)экрана не попадают в оболочку и оболочка не попадает в экран}
         ((Not PointInWrect(ScreenShell.X1,ScreenShell.Y1,Obj.Shell.X1,Obj.Shell.Y1,Obj.Shell.X2,Obj.Shell.Y2)) And
          (Not PointInWrect(ScreenShell.X2,ScreenShell.Y2,Obj.Shell.X1,Obj.Shell.Y1,Obj.Shell.X2,Obj.Shell.Y2)) And
          (Not PointInWrect(ScreenShell.X1,ScreenShell.Y2,Obj.Shell.X1,Obj.Shell.Y1,Obj.Shell.X2,Obj.Shell.Y2)) And
          (Not PointInWrect(ScreenShell.X2,ScreenShell.Y1,Obj.Shell.X1,Obj.Shell.Y1,Obj.Shell.X2,Obj.Shell.Y2))) And
         // надо не левый-нижний-правый верхний, а оболочку, а то при повороте врать будет
         ((Not PointInWrect(Obj.Shell.X1,Obj.Shell.Y1,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
          (Not PointInWrect(Obj.Shell.X1,Obj.Shell.Y2,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
          (Not PointInWrect(Obj.Shell.X2,Obj.Shell.Y1,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
          (Not PointInWrect(Obj.Shell.X2,Obj.Shell.Y2,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)))
        Then Obj.OffScreen:=True Else Obj.OffScreen:=False;
      If (Obj.Shell.X1=Math.maxExtended) Or
        (Obj.Shell.X1=-Math.maxExtended) Or
        (Obj.Shell.Y1=Math.maxExtended) Or
        (Obj.Shell.Y1=-Math.maxExtended) Or
        (Obj.Shell.X2=Math.maxExtended) Or
        (Obj.Shell.X2=-Math.maxExtended) Or
        (Obj.Shell.Y2=Math.maxExtended) Or
        (Obj.Shell.Y2=-Math.maxExtended)
      Then Begin
        Obj.TooSmall:=True;
      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;
    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;

Function TVScene.BuildCurlsUnder(X,Y:Extended):TStringList;
Var I,K:Longint;
   ResultList:TStringList;
   Curl:TVCurl;
   ActiveObject:Boolean;
Begin
    ResultList:=TStringList.Create;
    If VCurlList.Count>0 Then Begin
      For I:=0 To VCurlList.Count-1 Do Begin
        Curl:=VCurlList.Objects[I] As TVCurl;
{        //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;}
        If PointInWRect(X,Y,Curl.Shell.X1,Curl.Shell.Y1,Curl.Shell.X2,Curl.Shell.Y2) Then
          ResultList.AddObject(Curl.Name,Curl);
      End;
    End;
    Result:=ResultList;
end;

Function TVScene.BuildObjectsUnder(X,Y:Extended):TStringList;
Var I,K:Longint;
   ResultList:TStringList;
   Obj:TVObject;
   ActiveObject:Boolean;
Begin
    ResultList:=TStringList.Create;
    If VObjectList.Count>0 Then Begin
      For I:=0 To VObjectList.Count-1 Do Begin
        Obj:=VObjectList.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;
    End;
    Result:=ResultList;
end;

Function TVScene.BuildConnectionsUnder(X,Y:Extended):TStringList;
Var I,K:Longint;
   ResultList:TStringList;
   Conn:TVConnection;
   ActiveObject:Boolean;
Begin
  // Соединение по-хорошему надо отслежимвать не через оболочку, а воспроизводя DisplayList
    ResultList:=TStringList.Create;
    If VConnectionList.Count>0 Then Begin
      For I:=0 To VConnectionList.Count-1 Do Begin
        Conn:=VConnectionList.Objects[I] As TVConnection;
        //Dont check objects, belongs to disabled group
        ActiveObject:=True;
        If Conn.GroupList.Count>0 Then
          For K:=0 To Conn.GroupList.Count-1 Do Begin
            If Not (Conn.GroupList.Objects[K] As TVGroup).Active Then ActiveObject:=False;
          end;
        If Not ActiveObject Then Continue;
        If PointInWRect(X,Y,Conn.Shell.X1,Conn.Shell.Y1,Conn.Shell.X2,Conn.Shell.Y2) Then
          ResultList.AddObject(Conn.Name,Conn);
      End;
    End;
    Result:=ResultList;
end;

Function TVScene.BuildCalloutsUnder(X,Y:Extended):TStringList;
Var I,K:Longint;
   ResultList:TStringList;
   Callout:TVCallout;
   ActiveObject:Boolean;
Begin
    ResultList:=TStringList.Create;
    If VCalloutList.Count>0 Then Begin
      For I:=0 To VCalloutList.Count-1 Do Begin
        Callout:=VCalloutList.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;
    End;
    Result:=ResultList;
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 PointInWrect(ScreenShell.X1,ScreenShell.Y1,Connection.Shell.X1,Connection.Shell.Y1,Connection.Shell.X2,Connection.Shell.Y2)) And
            (Not PointInWrect(ScreenShell.X2,ScreenShell.Y2,Connection.Shell.X1,Connection.Shell.Y1,Connection.Shell.X2,Connection.Shell.Y2)) And
            (Not PointInWrect(ScreenShell.X1,ScreenShell.Y2,Connection.Shell.X1,Connection.Shell.Y1,Connection.Shell.X2,Connection.Shell.Y2)) And
            (Not PointInWrect(ScreenShell.X2,ScreenShell.Y1,Connection.Shell.X1,Connection.Shell.Y1,Connection.Shell.X2,Connection.Shell.Y2))) And
           // надо не левый-нижний-правый верхний, а оболочку, а то при повороте врать будет
           ((Not PointInWrect(Connection.Shell.X1,Connection.Shell.Y1,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
            (Not PointInWrect(Connection.Shell.X2,Connection.Shell.Y2,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
            (Not PointInWrect(Connection.Shell.X1,Connection.Shell.Y2,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
            (Not PointInWrect(Connection.Shell.X2,Connection.Shell.Y1,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)))
          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;
      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;
      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,ZeroAcc,Depth);
          end Else Begin
            DebugC:=DisplayList[J];
            DebugAcc:=(DisplayList.Objects[J] As TAccObject).AcceleratedCommand;
            If Pos('Unscaled',DisplayList[J])=1 Then Begin
              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 1',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 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;
      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
          //выноска не рисуется, если оно ведёт к невидимому объекту (отключенный слой)
{          N:=VObjectList.IndexOf(Callout.RelatedObj);
          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 PointInWrect(ScreenShell.X1,ScreenShell.Y1,Callout.Shell.X1,Callout.Shell.Y1,Callout.Shell.X2,Callout.Shell.Y2)) And
            (Not PointInWrect(ScreenShell.X2,ScreenShell.Y2,Callout.Shell.X1,Callout.Shell.Y1,Callout.Shell.X2,Callout.Shell.Y2)) And
            (Not PointInWrect(ScreenShell.X1,ScreenShell.Y2,Callout.Shell.X1,Callout.Shell.Y1,Callout.Shell.X2,Callout.Shell.Y2)) And
            (Not PointInWrect(ScreenShell.X2,ScreenShell.Y1,Callout.Shell.X1,Callout.Shell.Y1,Callout.Shell.X2,Callout.Shell.Y2))) And
           // надо не левый-нижний-правый верхний, а оболочку, а то при повороте врать будет
           ((Not PointInWrect(Callout.Shell.X1,Callout.Shell.Y1,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
            (Not PointInWrect(Callout.Shell.X2,Callout.Shell.Y2,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
            (Not PointInWrect(Callout.Shell.X1,Callout.Shell.Y2,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)) And
            (Not PointInWrect(Callout.Shell.X2,Callout.Shell.Y1,ScreenShell.X1,ScreenShell.Y1,ScreenShell.X2,ScreenShell.Y2)))
          Then Exit;
        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(Cmd,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.

