Implements the new DXF Tokenizer

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@1458 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
sekelsenmat
2011-01-25 14:13:33 +00:00
parent 8d6d07fe81
commit d67eefba96
6 changed files with 398 additions and 154 deletions

View File

@ -0,0 +1,42 @@
unit dxftokentotree;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, ComCtrls,
dxfvectorialreader;
procedure ConvertDXFTokensToTreeView(ATokens: TDXFTokens; ATreeView: TTreeView);
procedure ConvertDXFTokensToTreeNodes(ATokens: TDXFTokens; ATreeNodes: TTreeNodes; ABaseNode: TTreeNode);
implementation
procedure ConvertDXFTokensToTreeView(ATokens: TDXFTokens; ATreeView: TTreeView);
begin
ATreeView.Items.Clear;
ConvertDXFTokensToTreeNodes(ATokens, ATreeView.Items, ATreeView.Items.GetFirstNode);
end;
procedure ConvertDXFTokensToTreeNodes(ATokens: TDXFTokens;
ATreeNodes: TTreeNodes; ABaseNode: TTreeNode);
var
AToken: TDXFToken;
NodeStr: string;
NewNode: TTreeNode;
i: Integer;
begin
if ATokens = nil then Exit;
for i := 0 to ATokens.Count - 1 do
begin
AToken := TDXFToken(ATokens.Items[i]);
NodeStr := Format('(%d %s)', [AToken.GroupCode, AToken.StrValue]);
NewNode := ATreeNodes.AddChild(ABaseNode, NodeStr);
ConvertDXFTokensToTreeNodes(AToken.Childs, NewNode.TreeNodes, NewNode);
end;
end;
end.

View File

@ -17,7 +17,7 @@ SECTION_NAME
ENDSEC ENDSEC
0 0
after all section end there is: after all sections there is:
EOF EOF
@ -37,6 +37,31 @@ type
{ Used by tcutils.SeparateString } { Used by tcutils.SeparateString }
T10Strings = array[0..9] of shortstring; T10Strings = array[0..9] of shortstring;
TDXFToken = class;
TDXFTokens = TFPList;// TDXFToken;
TDXFToken = class
GroupCode: Integer;
StrValue: string;
FloatValue: double;
IntValue: Integer;
Childs: TDXFTokens;
constructor Create;
Destructor Destroy; override;
end;
{ TDXFTokenizer }
TDXFTokenizer = class
public
Tokens: TDXFTokens;
constructor Create;
Destructor Destroy; override;
procedure ReadFromStrings(AStrings: TStrings);
function IsENTITIES_Subsection(AStr: string): Boolean;
end;
{ TvDXFVectorialReader } { TvDXFVectorialReader }
TvDXFVectorialReader = class(TvCustomVectorialReader) TvDXFVectorialReader = class(TvCustomVectorialReader)
@ -44,13 +69,15 @@ type
LineStartX, LineStartY, LineStartZ: Double; LineStartX, LineStartY, LineStartZ: Double;
LineEndX, LineEndY, LineEndZ: Double; LineEndX, LineEndY, LineEndZ: Double;
function SeparateString(AString: string; ASeparator: Char): T10Strings; function SeparateString(AString: string; ASeparator: Char): T10Strings;
function ReadSection(AStrings: TStrings; var AIndex: Integer; AData: TvVectorialDocument): Boolean; procedure ReadENTITIES(ATokens: TDXFTokens; AData: TvVectorialDocument);
function ReadENTITIES(AStrings: TStrings; var AIndex: Integer; AData: TvVectorialDocument): Boolean; function ReadENTITIES_LINE(AStrings: TStrings; var AIndex: Integer; AData: TvVectorialDocument): Boolean;
function ReadENTITIES_LINE(AStrings: TStrings; var AIndex: Integer; AData: TvVectorialDocument): Boolean;
function GetCoordinate(AStr: shortstring): Integer; function GetCoordinate(AStr: shortstring): Integer;
function GetCoordinateValue(AStr: shortstring): Double; function GetCoordinateValue(AStr: shortstring): Double;
public public
{ General reading methods } { General reading methods }
Tokenizer: TDXFTokenizer;
constructor Create;
Destructor Destroy; override;
procedure ReadFromStrings(AStrings: TStrings; AData: TvVectorialDocument); override; procedure ReadFromStrings(AStrings: TStrings; AData: TvVectorialDocument); override;
end; end;
@ -59,23 +86,182 @@ implementation
{$define FPVECTORIALDEBUG} {$define FPVECTORIALDEBUG}
const const
{ Coordinate constants } // Group Codes for ENTITIES
DXF_ENTITIES_TYPE = 0;
DXF_ENTITIES_HANDLE = 5;
DXF_ENTITIES_APPLICATION_GROUP = 102;
DXF_ENTITIES_AcDbEntity = 100;
DXF_ENTITIES_MODEL_OR_PAPER_SPACE = 67; // default=0=model, 1=paper
DXF_ENTITIES_VISIBILITY = 60; // default=0 = Visible, 1 = Invisible
INT_COORDINATE_NONE = 0; { TDXFToken }
INT_COORDINATE_X = 1;
INT_COORDINATE_Y = 2;
INT_COORDINATE_Z = 3;
{ GCode constants } constructor TDXFToken.Create;
begin
inherited Create;
STR_GCODE_LINEAR_MOVE = 'G01'; Childs := TDXFTokens.Create;
STR_GCODE_STEPPER_MOVE = 'S01'; end;
STR_GCODE_2DBEZIER_MOVE = 'B02';
STR_GCODE_3DBEZIER_MOVE = 'B03';
STR_GCODE_DRILL_UP = 'P01';
STR_GCODE_DRILL_DOWN = 'P02';
{ TvAvisoCNCGCodeReader } destructor TDXFToken.Destroy;
begin
Childs.Free;
inherited Destroy;
end;
{ TDXFTokenizer }
constructor TDXFTokenizer.Create;
begin
inherited Create;
Tokens := TDXFTokens.Create;
end;
destructor TDXFTokenizer.Destroy;
begin
Tokens.Free;
inherited Destroy;
end;
procedure TDXFTokenizer.ReadFromStrings(AStrings: TStrings);
var
i: Integer;
StrSectionGroupCode, StrSectionName: string;
IntSectionGroupCode: Integer;
CurTokenBase, NextTokenBase, ENTITIESTokenBase: TDXFTokens;
NewToken: TDXFToken;
ParserState: Integer;
begin
// Tokens.ForEachCall(); deletecallback
Tokens.Clear;
CurTokenBase := Tokens;
NextTokenBase := Tokens;
i := 0;
ParserState := 0;
while i < AStrings.Count - 1 do
begin
CurTokenBase := NextTokenBase;
// Now read and process the section name
StrSectionGroupCode := AStrings.Strings[i];
IntSectionGroupCode := StrToInt(Trim(StrSectionGroupCode));
StrSectionName := AStrings.Strings[i+1];
NewToken := TDXFToken.Create;
NewToken.GroupCode := IntSectionGroupCode;
NewToken.StrValue := StrSectionName;
// Waiting for a section
if ParserState = 0 then
begin
if (StrSectionName = 'SECTION') then
begin
ParserState := 1;
NextTokenBase := NewToken.Childs;
end
else
begin
raise Exception.Create(Format(
'TDXFTokenizer.ReadFromStrings: Expected SECTION, but got: %s', [StrSectionname]));
end;
end
// Processing the section name
else if ParserState = 1 then
begin
if (StrSectionName = 'HEADER') or
(StrSectionName = 'CLASSES') or
(StrSectionName = 'TABLES') or
(StrSectionName = 'BLOCKS') or
(StrSectionName = 'OBJECTS') or
(StrSectionName = 'THUMBNAILIMAGE') then
begin
ParserState := 2;
end
else if (StrSectionName = 'ENTITIES') then
begin
ParserState := 3;
ENTITIESTokenBase := CurTokenBase;
end
else if (StrSectionName = 'EOF') then
begin
Exit;
end
else
begin
raise Exception.Create(Format(
'TDXFTokenizer.ReadFromStrings: Invalid section name: %s', [StrSectionname]));
end;
end
// Reading a generic section
else if ParserState = 2 then
begin
if StrSectionName = 'ENDSEC' then
begin
ParserState := 0;
NextTokenBase := Tokens;
end;
end
// Reading the ENTITIES section
else if ParserState = 3 then
begin
if IsENTITIES_Subsection(StrSectionName) then
begin
CurTokenBase := ENTITIESTokenBase;
NextTokenBase := NewToken.Childs;
end
end;
CurTokenBase.Add(NewToken);
Inc(i, 2);
end;
end;
function TDXFTokenizer.IsENTITIES_Subsection(AStr: string): Boolean;
begin
Result :=
(AStr = '3DFACE') or
(AStr = '3DSOLID') or
(AStr = 'ACAD_PROXY_ENTITY') or
(AStr = 'ARC') or
(AStr = 'ATTDEF') or
(AStr = 'ATTRIB') or
(AStr = 'BODY') or
(AStr = 'CIRCLE') or
(AStr = 'DIMENSION') or
(AStr = 'ELLIPSE') or
(AStr = 'HATCH') or
(AStr = 'IMAGE') or
(AStr = 'INSERT') or
(AStr = 'LEADER') or
(AStr = 'LINE') or
(AStr = 'LWPOLYLINE') or
(AStr = 'MLINE') or
(AStr = 'MTEXT') or
(AStr = 'OLEFRAME') or
(AStr = 'OLE2FRAME') or
(AStr = 'POINT') or
(AStr = 'POLYLINE') or
(AStr = 'RAY') or
(AStr = 'REGION') or
(AStr = 'SEQEND') or
(AStr = 'SHAPE') or
(AStr = 'SOLID') or
(AStr = 'SPLINE') or
(AStr = 'TEXT') or
(AStr = 'TOLERANCE') or
(AStr = 'TRACE') or
(AStr = 'VERTEX') or
(AStr = 'VIEWPORT') or
(AStr = 'XLINE');
end;
{ TvDXFVectorialReader }
{@@ {@@
Reads a string and separates it in substring Reads a string and separates it in substring
@ -110,116 +296,44 @@ begin
end; end;
end; end;
{@@ procedure TvDXFVectorialReader.ReadENTITIES(ATokens: TDXFTokens; AData: TvVectorialDocument);
returns If an end of file marker was found
}
function TvDXFVectorialReader.ReadSection(
AStrings: TStrings; var AIndex: Integer; AData: TvVectorialDocument): Boolean;
var var
DestX, DestY, DestZ: Double; i: Integer;
StrSectionNum, StrSectionName: string; CurToken: TDXFToken;
IntSectionNum, i: Integer;
begin begin
Result := False; for i := 0 to ATokens.Count - 1 do
// Check if there is minimal space for a section
if AIndex+5 > AStrings.Count then
begin begin
{$ifdef FPVECTORIALDEBUG} CurToken := TDXFToken(ATokens.Items[i]);
WriteLn('Not enough space for a section'); if CurToken.StrValue = 'ELLIPSE' then
{$endif}
Exit(True);
end;
// Check of the EOF marker
StrSectionName := Trim(AStrings.Strings[AIndex+1]);
if StrSectionName = 'EOF' then
begin
{$ifdef FPVECTORIALDEBUG}
WriteLn('EOF found');
{$endif}
Exit(True);
end;
// Now read and process the section name
StrSectionNum := AStrings.Strings[AIndex+2];
IntSectionNum := StrToInt(Trim(StrSectionNum));
StrSectionName := AStrings.Strings[AIndex+3];
{$ifdef FPVECTORIALDEBUG}
WriteLn('TvDXFVectorialReader.ReadSection ' + StrSectionName);
{$endif}
if (StrSectionName = 'HEADER') or
(StrSectionName = 'CLASSES') or
(StrSectionName = 'TABLES') or
(StrSectionName = 'BLOCKS') or
(StrSectionName = 'OBJECTS') or
(StrSectionName = 'THUMBNAILIMAGE') then
begin
// We don't care about contents here, so let's just find the last section and get out of here.
for i := AIndex + 4 to AStrings.Count - 1 do
begin begin
if AStrings.Strings[i] = 'ENDSEC' then // ...
begin end
AIndex := i + 1; else if CurToken.StrValue = 'LINE' then
Exit; begin
end; // Initial values
LineStartX := 0;
LineStartY := 0;
LineStartZ := 0;
LineEndX := 0;
LineEndY := 0;
LineEndZ := 0;
// Read the data of the line
// Inc(AIndex, 2);
// while not ReadENTITIES_LINE(AStrings, AIndex, AData) do ;
// And now write it
{$ifdef FPVECTORIALDEBUG}
WriteLn(Format('Adding Line from %f,%f to %f,%f', [LineStartX, LineStartY, LineEndX, LineEndY]));
{$endif}
// AData.StartPath(LineStartX, LineStartY);
// AData.AddLineToPath(LineEndX, LineEndY);
// AData.EndPath();
end
else if CurToken.StrValue = 'TEXT' then
begin
// ...
end; end;
// If we reached here, the section in incomplete
raise Exception.Create('TvDXFVectorialReader.ReadSection: ENDSEC was not found in the SECTION');
end
else if StrSectionName = 'ENTITIES' then
begin
AIndex := AIndex + 4;
while not ReadENTITIES(AStrings, AIndex, AData) do ;
end;
{else
begin
end;}
end;
function TvDXFVectorialReader.ReadENTITIES(AStrings: TStrings;
var AIndex: Integer; AData: TvVectorialDocument): Boolean;
var
StrSectionNum, StrSectionName: string;
IntSectionNum, i: Integer;
begin
Result := False;
// Now read and process the item name
StrSectionName := AStrings.Strings[AIndex+1];
{$ifdef FPVECTORIALDEBUG}
WriteLn('TvDXFVectorialReader.ReadENTITIES ', StrSectionName);
{$endif}
if StrSectionName = 'ENDSEC' then
begin
Inc(AIndex, 2);
Exit(True);
end
else if StrSectionName = 'LINE' then
begin
// Initial values
LineStartX := 0;
LineStartY := 0;
LineStartZ := 0;
LineEndX := 0;
LineEndY := 0;
LineEndZ := 0;
// Read the data of the line
Inc(AIndex, 2);
while not ReadENTITIES_LINE(AStrings, AIndex, AData) do ;
// And now write it
{$ifdef FPVECTORIALDEBUG}
WriteLn(Format('Adding Line from %f,%f to %f,%f', [LineStartX, LineStartY, LineEndX, LineEndY]));
{$endif}
AData.StartPath(LineStartX, LineStartY);
AData.AddLineToPath(LineEndX, LineEndY);
AData.EndPath();
end; end;
end; end;
@ -232,11 +346,11 @@ var
begin begin
Result := False; Result := False;
// Now read and process the item name { // Now read and process the item name
StrSectionNum := AStrings.Strings[AIndex]; StrSectionNum := AStrings.Strings[AIndex];
StrSectionValue := AStrings.Strings[AIndex+1]; StrSectionValue := AStrings.Strings[AIndex+1];
if (StrSectionValue = 'LINE') or if IsENTITIES_Subsection(StrSectionValue) or
(StrSectionValue = 'ENDSEC') then (StrSectionValue = 'ENDSEC') then
begin begin
Exit(True); Exit(True);
@ -256,26 +370,40 @@ begin
21: LineEndY := FloatSectionValue; 21: LineEndY := FloatSectionValue;
31: LineEndZ := FloatSectionValue; 31: LineEndZ := FloatSectionValue;
end; end;
end; end;}
end; end;
function TvDXFVectorialReader.GetCoordinate(AStr: shortstring): Integer; function TvDXFVectorialReader.GetCoordinate(AStr: shortstring): Integer;
begin begin
Result := INT_COORDINATE_NONE; { Result := INT_COORDINATE_NONE;
if AStr = '' then Exit if AStr = '' then Exit
else if AStr[1] = 'X' then Result := INT_COORDINATE_X else if AStr[1] = 'X' then Result := INT_COORDINATE_X
else if AStr[1] = 'Y' then Result := INT_COORDINATE_Y else if AStr[1] = 'Y' then Result := INT_COORDINATE_Y
else if AStr[1] = 'Z' then Result := INT_COORDINATE_Z; else if AStr[1] = 'Z' then Result := INT_COORDINATE_Z;}
end; end;
function TvDXFVectorialReader.GetCoordinateValue(AStr: shortstring): Double; function TvDXFVectorialReader.GetCoordinateValue(AStr: shortstring): Double;
begin begin
Result := 0.0; Result := 0.0;
if Length(AStr) <= 1 then Exit; { if Length(AStr) <= 1 then Exit;
Result := StrToFloat(Copy(AStr, 2, Length(AStr) - 1)); Result := StrToFloat(Copy(AStr, 2, Length(AStr) - 1));}
end;
constructor TvDXFVectorialReader.Create;
begin
inherited Create;
Tokenizer := TDXFTokenizer.Create;
end;
destructor TvDXFVectorialReader.Destroy;
begin
Tokenizer.Free;
inherited Destroy;
end; end;
{@@ {@@
@ -286,10 +414,18 @@ procedure TvDXFVectorialReader.ReadFromStrings(AStrings: TStrings;
AData: TvVectorialDocument); AData: TvVectorialDocument);
var var
i: Integer; i: Integer;
CurToken, CurTokenFirstChild: TDXFToken;
begin begin
i := 0; Tokenizer.ReadFromStrings(AStrings);
while i < AStrings.Count - 1 do
if ReadSection(AStrings, i, AData) then Break; for i := 0 to Tokenizer.Tokens.Count - 1 do
begin
CurToken := TDXFToken(Tokenizer.Tokens.Items[i]);
CurTokenFirstChild := TDXFToken(CurToken.Childs.Items[0]);
if CurTokenFirstChild.StrValue = 'ENTITIES' then
ReadENTITIES(CurToken.Childs, AData);
end;
end; end;
initialization initialization

View File

@ -1,11 +1,11 @@
object frmFPVViewer: TfrmFPVViewer object frmFPVViewer: TfrmFPVViewer
Left = 234 Left = 186
Height = 433 Height = 441
Top = 172 Top = 137
Width = 342 Width = 336
Caption = 'frmFPVViewer' Caption = 'frmFPVViewer'
ClientHeight = 433 ClientHeight = 441
ClientWidth = 342 ClientWidth = 336
LCLVersion = '0.9.31' LCLVersion = '0.9.31'
object editFileName: TFileNameEdit object editFileName: TFileNameEdit
Left = 8 Left = 8
@ -44,21 +44,15 @@ object frmFPVViewer: TfrmFPVViewer
Left = 8 Left = 8
Height = 18 Height = 18
Top = 79 Top = 79
Width = 61 Width = 58
Caption = 'Scale by:' Caption = 'Scale by:'
ParentColor = False ParentColor = False
end end
object imageView: TImage
Left = 11
Height = 296
Top = 128
Width = 325
end
object Label2: TLabel object Label2: TLabel
Left = 8 Left = 8
Height = 18 Height = 18
Top = 104 Top = 104
Width = 82 Width = 76
Caption = 'Start Pos X:' Caption = 'Start Pos X:'
ParentColor = False ParentColor = False
end end
@ -80,8 +74,48 @@ object frmFPVViewer: TfrmFPVViewer
Left = 152 Left = 152
Height = 18 Height = 18
Top = 104 Top = 104
Width = 79 Width = 73
Caption = 'Start Pos Y:' Caption = 'Start Pos Y:'
ParentColor = False ParentColor = False
end end
object Button1: TButton
Left = 112
Height = 25
Top = 41
Width = 128
Caption = 'View DXF Tokens'
OnClick = Button1Click
TabOrder = 5
end
object notebook: TNotebook
Left = 0
Height = 313
Top = 128
Width = 336
PageIndex = 1
TabOrder = 6
TabStop = True
object Page1: TPage
ClientWidth = 336
ClientHeight = 313
object imageView: TImage
Left = 8
Height = 296
Top = 8
Width = 325
end
end
object Page2: TPage
ClientWidth = 336
ClientHeight = 313
object DXFTreeView: TTreeView
Left = 8
Height = 313
Top = 0
Width = 321
DefaultItemHeight = 19
TabOrder = 0
end
end
end
end end

View File

@ -6,7 +6,7 @@ interface
uses uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, EditBtn, Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, EditBtn,
StdCtrls, Spin, ExtCtrls; StdCtrls, Spin, ExtCtrls, ComCtrls;
type type
@ -14,15 +14,21 @@ type
TfrmFPVViewer = class(TForm) TfrmFPVViewer = class(TForm)
btnVisualize: TButton; btnVisualize: TButton;
Button1: TButton;
editFileName: TFileNameEdit; editFileName: TFileNameEdit;
imageView: TImage; imageView: TImage;
Label2: TLabel; Label2: TLabel;
Label3: TLabel; Label3: TLabel;
notebook: TNotebook;
Page1: TPage;
Page2: TPage;
spinStartX: TSpinEdit; spinStartX: TSpinEdit;
spinStartY: TSpinEdit; spinStartY: TSpinEdit;
spinScale: TFloatSpinEdit; spinScale: TFloatSpinEdit;
Label1: TLabel; Label1: TLabel;
DXFTreeView: TTreeView;
procedure btnVisualizeClick(Sender: TObject); procedure btnVisualizeClick(Sender: TObject);
procedure Button1Click(Sender: TObject);
private private
{ private declarations } { private declarations }
public public
@ -37,7 +43,8 @@ implementation
uses uses
fpvectorial, cdrvectorialreader, svgvectorialwriter, pdfvectorialreader, fpvectorial, cdrvectorialreader, svgvectorialwriter, pdfvectorialreader,
dxfvectorialreader, dxfvectorialreader,
fpvtocanvas; fpvtocanvas,
dxftokentotree;
{$R *.lfm} {$R *.lfm}
@ -50,6 +57,8 @@ begin
// First check the in input // First check the in input
//if not CheckInput() then Exit; //if not CheckInput() then Exit;
notebook.PageIndex := 0;
Vec := TvVectorialDocument.Create; Vec := TvVectorialDocument.Create;
try try
Vec.ReadFromFile(editFileName.FileName, vfDXF); Vec.ReadFromFile(editFileName.FileName, vfDXF);
@ -61,5 +70,26 @@ begin
end; end;
end; end;
procedure TfrmFPVViewer.Button1Click(Sender: TObject);
var
Reader: TvDXFVectorialReader;
Vec: TvVectorialDocument;
begin
// First check the in input
//if not CheckInput() then Exit;
notebook.PageIndex := 1;
Reader := TvDXFVectorialReader.Create;
Vec := TvVectorialDocument.Create;
try
Reader.ReadFromFile(editFileName.FileName, Vec);
ConvertDXFTokensToTreeView(Reader.Tokenizer.Tokens, DXFTreeView);
finally
Reader.Free;
Vec.Free;
end;
end;
end. end.

View File

@ -3,9 +3,6 @@
<ProjectOptions> <ProjectOptions>
<Version Value="9"/> <Version Value="9"/>
<General> <General>
<Flags>
<AlwaysBuild Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/> <SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/> <MainUnit Value="0"/>
<ResourceType Value="res"/> <ResourceType Value="res"/>
@ -37,7 +34,7 @@
<PackageName Value="LCL"/> <PackageName Value="LCL"/>
</Item1> </Item1>
</RequiredPackages> </RequiredPackages>
<Units Count="2"> <Units Count="3">
<Unit0> <Unit0>
<Filename Value="fpvviewer.lpr"/> <Filename Value="fpvviewer.lpr"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
@ -50,6 +47,11 @@
<ResourceBaseClass Value="Form"/> <ResourceBaseClass Value="Form"/>
<UnitName Value="fpvv_mainform"/> <UnitName Value="fpvv_mainform"/>
</Unit1> </Unit1>
<Unit2>
<Filename Value="dxftokentotree.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="dxftokentotree"/>
</Unit2>
</Units> </Units>
</ProjectOptions> </ProjectOptions>
<CompilerOptions> <CompilerOptions>

View File

@ -7,7 +7,7 @@ uses
cthreads, cthreads,
{$ENDIF}{$ENDIF} {$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset Interfaces, // this includes the LCL widgetset
Forms, fpvv_mainform Forms, fpvv_mainform, dxftokentotree
{ you can add units after this }; { you can add units after this };
{$R *.res} {$R *.res}