// // The graphics engine GLScene https://github.com/glscene // unit GLS.FileDXF; (* Support-Code to load DXF (Drawing eXchange Files) TGLFreeForm or TGLActor Components in GLScene. Note that you must manually add this unit to one of your project's uses to enable support for DXF at run-time. Turn on TwoSideLighting in your Buffer! DXF-Faces have no defined winding order *) interface uses System.Classes, System.SysUtils, GLScene.VectorTypes, GLS.PersistentClasses, GLS.ApplicationFileIO, GLScene.VectorGeometry, GLS.VectorLists, GLS.Scene, GLS.Texture, GLS.VectorFileObjects, GLS.Material, GLScene.Utils; type TGLDXFVectorFile = class(TGLVectorFile) private FSourceStream: TStream; { Load from this stream } FBuffer: String; { Buffer and current line } FLineNo: Integer; { current Line number - for error messages } FEof: Boolean; { Stream done? } FBufPos: Integer; { Position in the buffer } HasPushedCode: Boolean; PushedCode: Integer; FLayers: TStringList; FBlocks: TStringList; FLastpercentdone: BYTE; protected procedure PushCode(code: Integer); function GetCode: Integer; procedure SkipTable; procedure SkipSection; // procedure DoProgress (Stage: TGLProgressStage; PercentDone: single; RedrawNow: Boolean; const Msg: string); function NeedMesh(basemesh: TGLBaseMesh; layer: STRING): TGLMeshObject; function NeedFaceGroup(m: TGLMeshObject; fgmode: TGLFaceGroupMeshMode; fgmat: String): TFGVertexIndexList; procedure NeedMeshAndFaceGroup(basemesh: TGLBaseMesh; layer: STRING; fgmode: TGLFaceGroupMeshMode; fgmat: STRING; var m: TGLMeshObject; var fg: TFGVertexIndexList); function ReadLine: STRING; // Read a single line of text from the source stream, set FEof to true when done. function ReadInt: Integer; function ReadDouble: double; procedure ReadTables; procedure ReadLayer; procedure ReadLayerTable; procedure ReadBlocks; procedure ReadInsert(basemesh: TGLBaseMesh); procedure ReadEntity3Dface(basemesh: TGLBaseMesh); procedure ReadEntityPolyLine(basemesh: TGLBaseMesh); procedure ReadEntities(basemesh: TGLBaseMesh); public class function Capabilities: TGLDataFileCapabilities; override; procedure LoadFromStream(aStream: TStream); override; end; implementation procedure BuildNormals(m: TGLMeshObject); FORWARD; const DXFcolorsRGB: ARRAY [1 .. 255] OF LONGINT = ($FF0000, $FFFF00, $00FF00, $00FFFF, $0000FF, $FF00FF, $000000, $000000, $000000, $FF0000, $FF8080, $A60000, $A65353, $800000, $804040, $4D0000, $4D2626, $260000, $261313, $FF4000, $FF9F80, $A62900, $A66853, $802000, $805040, $4D1300, $4D3026, $260A00, $261813, $FF8000, $FFBF80, $A65300, $A67C53, $804000, $806040, $4D2600, $4D3926, $261300, $261D13, $FFBF00, $FFDF80, $A67C00, $A69153, $806000, $807040, $4D3900, $4D4326, $261D00, $262113, $FFFF00, $FFFF80, $A6A600, $A6A653, $808000, $808040, $4D4D00, $4D4D26, $262600, $262613, $BFFF00, $DFFF80, $7CA600, $91A653, $608000, $708040, $394D00, $434D26, $1D2600, $212613, $80FF00, $BFFF80, $53A600, $7CA653, $408000, $608040, $264D00, $394D26, $132600, $1D2613, $40FF00, $9FFF80, $29A600, $68A653, $208000, $508040, $134D00, $304D26, $0A2600, $182613, $00FF00, $80FF80, $00A600, $53A653, $008000, $408040, $004D00, $264D26, $002600, $132613, $00FF40, $80FF9F, $00A629, $53A668, $008020, $408050, $004D13, $264D30, $00260A, $132618, $00FF80, $80FFBF, $00A653, $53A67C, $008040, $408060, $004D26, $264D39, $002613, $13261D, $00FFBF, $80FFDF, $00A67C, $53A691, $008060, $408070, $004D39, $264D43, $00261D, $132621, $00FFFF, $80FFFF, $00A6A6, $53A6A6, $008080, $408080, $004D4D, $264D4D, $002626, $132626, $00BFFF, $80DFFF, $007CA6, $5391A6, $006080, $407080, $00394D, $26434D, $001D26, $132126, $0080FF, $80BFFF, $0053A6, $537CA6, $004080, $406080, $00264D, $26394D, $001326, $131D26, $0040FF, $809FFF, $0029A6, $5368A6, $002080, $405080, $00134D, $26304D, $000A26, $131826, $0000FF, $8080FF, $0000A6, $5353A6, $000080, $404080, $00004D, $26264D, $000026, $131326, $4000FF, $9F80FF, $2900A6, $6853A6, $200080, $504080, $13004D, $30264D, $0A0026, $181326, $8000FF, $BF80FF, $5300A6, $7C53A6, $400080, $604080, $26004D, $39264D, $130026, $1D1326, $BF00FF, $DF80FF, $7C00A6, $9153A6, $600080, $704080, $39004D, $43264D, $1D0026, $211326, $FF00FF, $FF80FF, $A600A6, $A653A6, $800080, $804080, $4D004D, $4D264D, $260026, $261326, $FF00BF, $FF80DF, $A6007C, $A65391, $800060, $804070, $4D0039, $4D2643, $26001D, $261321, $FF0080, $FF80BF, $A60053, $A6537C, $800040, $804060, $4D0026, $4D2639, $260013, $26131D, $FF0040, $FF809F, $A60029, $A65368, $800020, $804050, $4D0013, $4D2630, $26000A, $261318, $545454, $767676, $989898, $BBBBBB, $DDDDDD, $FFFFFF); const BufSize = 65536; { Load input data in chunks of BufSize Bytes. } LineLen = 100; { Allocate memory for the current line in chunks } function RGB2BGR(bgr: LONGINT): LONGINT; begin result := ((bgr SHR 16) and $FF) or (bgr AND $FF00) or ((bgr SHL 16) and $FF0000) end; function StreamEOF(S: TStream): Boolean; begin { Is the stream at its end? } result := (S.Position >= S.Size); end; class function TGLDXFVectorFile.Capabilities: TGLDataFileCapabilities; begin result := [dfcRead]; end; function TGLDXFVectorFile.ReadLine: STRING; var j: Integer; FLine: STRING; NewlineChar: CHAR; procedure FillBuffer; var l: Integer; begin l := FSourceStream.Size - FSourceStream.Position; if l > BufSize then l := BufSize; SetLength(FBuffer, l); FSourceStream.Read(FBuffer[1], l); FBufPos := 1; end; begin Inc(FLineNo); if FBufPos < 1 then FillBuffer; j := 1; while True do begin if FBufPos > Length(FBuffer) then begin if StreamEOF(FSourceStream) then begin FEof := True; break; end else FillBuffer end else begin case FBuffer[FBufPos] of #10, #13: begin NewlineChar := FBuffer[FBufPos]; Inc(FBufPos); if FBufPos > Length(FBuffer) then if StreamEOF(FSourceStream) then break else FillBuffer; if ((FBuffer[FBufPos] = #10) or (FBuffer[FBufPos] = #13)) and (FBuffer[FBufPos] <> NewlineChar) then Inc(FBufPos); break; end; else if j > Length(FLine) then SetLength(FLine, Length(FLine) + LineLen); if FBuffer[FBufPos] = #9 then FLine[j] := #32 else FLine[j] := FBuffer[FBufPos]; Inc(FBufPos); Inc(j); end; end; end; SetLength(FLine, j - 1); ReadLine := Trim(FLine); end; { procedure TGLDXFVectorFile.DoProgress (Stage: TGLProgressStage; PercentDone: single; RedrawNow: Boolean; const Msg: string); var perc:BYTE; begin // If the following line stops your compiler, just comment this function if @owner.OnProgress<>NIL then begin perc:=round(percentdone); if (perc<>Flastpercentdone) or (msg<>'') or redrawnow then owner.OnProgress (owner,stage,perc,redrawnow,msg); Flastpercentdone:=perc; end; end; } procedure TGLDXFVectorFile.PushCode(code: Integer); begin PushedCode := code; HasPushedCode := True; end; function TGLDXFVectorFile.GetCode: Integer; var S: STRING; begin if HasPushedCode then begin GetCode := PushedCode; HasPushedCode := FALSE; end else begin S := ReadLine; result := StrToIntDef(S, -1); if result = -1 then raise Exception.create('Invalid DXF Code ' + S + ' on Line #' + IntToStr(FLineNo)); end; end; function TGLDXFVectorFile.ReadDouble: double; var S: String; c: CHAR; begin c := FormatSettings.DecimalSeparator; FormatSettings.DecimalSeparator := '.'; S := Trim(ReadLine); result := GLStrToFloatDef(S); FormatSettings.DecimalSeparator := c; end; function TGLDXFVectorFile.ReadInt: Integer; var S: String; begin S := Trim(ReadLine); result := StrToInt(S); end; procedure TGLDXFVectorFile.SkipSection; var S: String; code: Integer; begin repeat code := GetCode; S := ReadLine; until (code = 0) and (S = 'ENDSEC'); end; procedure TGLDXFVectorFile.SkipTable; var S: String; code: Integer; begin repeat code := GetCode; S := ReadLine; until (code = 0) and (S = 'ENDTAB'); end; procedure TGLDXFVectorFile.ReadLayer; var layername, color: String; code: Integer; begin color := '1'; repeat code := GetCode; case code of 0: ; 2: layername := ReadLine; 70: ReadLine; // freeze and lock flags 62: color := ReadLine; else ReadLine; end; until code = 0; PushCode(0); FLayers.AddObject(layername, POINTER(StrToIntDef(color, 1))); end; procedure TGLDXFVectorFile.ReadLayerTable; var S: STRING; code: Integer; begin repeat code := GetCode; S := ReadLine; if (code = 0) and (S = 'LAYER') then ReadLayer; until (code = 0) and (S = 'ENDTAB'); end; procedure TGLDXFVectorFile.ReadTables; var S: String; code: Integer; begin repeat code := GetCode; S := ReadLine; if (code = 0) and (S = 'TABLE') then begin code := GetCode; S := ReadLine; if (code = 2) then if S = 'LAYER' then ReadLayerTable else SkipTable; // LTYPE, STYLE, UCS, and more currently skipped end until (code = 0) and (S = 'ENDSEC'); end; procedure TGLDXFVectorFile.ReadBlocks; var S: String; code: Integer; blockname: String; blockmesh: TGLFreeForm; begin // This code reads blocks into orphaned TGLFreeForms. // ReadInsert then either copies or parents this object to its parent // unused blocks are freed upon completion repeat code := GetCode; S := ReadLine; if (code = 0) and (S = 'BLOCK') then begin blockmesh := TGLFreeForm.create(owner); blockmesh.IgnoreMissingTextures := True; blockmesh.MaterialLibrary := owner.MaterialLibrary; blockmesh.OnProgress := NIL; blockname := 'DXFBLOCK' + IntToStr(FBlocks.count); repeat code := GetCode; case code of 0: ; 2: blockname := ReadLine; else S := ReadLine; end; until code = 0; PushCode(0); FBlocks.AddObject(blockname, blockmesh); ReadEntities(blockmesh); // basemesh.Direction.SetVector(0,1,0); // code:=GetCode; // s:=ReadLine; // asm nop end; end; until (code = 0) and (S = 'ENDSEC'); end; procedure TGLDXFVectorFile.ReadInsert(basemesh: TGLBaseMesh); var code, idx, indexoffset: Integer; i, j, k: Integer; blockname, S: STRING; pt, insertpoint, scale: TAffineVector; blockmesh: TGLBaseMesh; // blockproxy :TGLProxyObject; mo_block: TGLMeshObject; mo_base: TGLMeshObject; fg_block, fg_base: TFGVertexIndexList; begin blockname := ''; insertpoint := NullVector; scale := XYZvector; repeat // see ReadBlocks for details code := GetCode; case code of 0: ; 2: blockname := ReadLine; 10: insertpoint.X := ReadDouble; 20: insertpoint.Y := ReadDouble; 30: insertpoint.Z := ReadDouble; 41: scale.X := ReadDouble; 42: scale.Y := ReadDouble; 43: scale.Z := ReadDouble; else S := ReadLine; end; until code = 0; idx := FBlocks.IndexOf(blockname); if idx >= 0 then begin blockmesh := FBlocks.Objects[idx] as TGLBaseMesh; // FLAT STRUCTURES // Insert a block into its parent by copying the contents. // the blockmesh will be freed upon completion, leaving only the copies. for i := 0 to blockmesh.MeshObjects.count - 1 do begin mo_block := blockmesh.MeshObjects[i]; mo_base := NeedMesh(basemesh, mo_block.name); indexoffset := mo_base.vertices.count; for j := 0 to mo_block.vertices.count - 1 do begin pt := mo_block.vertices[j]; ScaleVector(pt, scale); AddVector(pt, insertpoint); mo_base.vertices.Add(pt); end; for j := 0 to mo_block.FaceGroups.count - 1 do begin fg_block := mo_block.FaceGroups[j] as TFGVertexIndexList; fg_base := NeedFaceGroup(mo_base, fg_block.mode, fg_block.MaterialName); for k := 0 to fg_block.VertexIndices.count - 1 do begin fg_base.VertexIndices.Add(fg_block.VertexIndices[k] + indexoffset); end; end; end; // TREE STRUCTURES // Instead of copying the contents of the block, they are parented to the // base mesh. If the block already has a parent, a proxy object is created. // WARNING: THE CODE BELOW DOES NOT WORK. (* if blockmesh.Parent =NIL then begin blockmesh.Position.AsAffineVector:=insertpoint; blockmesh.ShowAxes:=TRUE; basemesh.AddChild(blockmesh); for i:=0 to blockmesh.MeshObjects.Count-1 do BuildNormals(blockmesh.MeshObjects[i]); end else begin blockproxy:=TGLproxyObject.CreateAsChild(basemesh); blockproxy.MasterObject:=blockmesh; blockproxy.Position.AsAffineVector:=insertpoint; blockproxy.ShowAxes:=TRUE; end; *) end; PushCode(0); end; function TGLDXFVectorFile.NeedMesh(basemesh: TGLBaseMesh; layer: STRING) : TGLMeshObject; var i: Integer; begin i := 0; while (i < basemesh.MeshObjects.count) and not(basemesh.MeshObjects[i].name = layer) do Inc(i); if i < basemesh.MeshObjects.count then result := basemesh.MeshObjects[i] else begin result := TGLMeshObject.CreateOwned(basemesh.MeshObjects); result.mode := momFaceGroups; result.name := layer; end; end; function TGLDXFVectorFile.NeedFaceGroup(m: TGLMeshObject; fgmode: TGLFaceGroupMeshMode; fgmat: STRING): TFGVertexIndexList; var i: Integer; acadcolor: LONGINT; libmat: TGLLibMaterial; fg: TFGVertexIndexList; begin i := 0; while (i < m.FaceGroups.count) and not((m.FaceGroups[i] is TFGVertexIndexList) and ((m.FaceGroups[i] as TFGVertexIndexList).mode = fgmode) and (m.FaceGroups[i].MaterialName = fgmat)) do Inc(i); if i < m.FaceGroups.count then fg := m.FaceGroups[i] as TFGVertexIndexList else begin fg := TFGVertexIndexList.CreateOwned(m.FaceGroups); fg.mode := fgmode; fg.MaterialName := fgmat; if owner.MaterialLibrary <> NIL then begin libmat := owner.MaterialLibrary.Materials.GetLibMaterialByName(fgmat); if libmat = NIL then // creates a colored material begin acadcolor := StrToIntDef(fgmat, 0); if acadcolor in [1 .. 255] then begin libmat := owner.MaterialLibrary.Materials.Add; libmat.name := fgmat; libmat.Material.FrontProperties.Diffuse.AsWinColor := RGB2BGR(DXFcolorsRGB[acadcolor]); libmat.Material.BackProperties.Diffuse.AsWinColor := RGB2BGR(DXFcolorsRGB[acadcolor]); libmat.Material.FaceCulling := fcNoCull; end; end; end; end; result := fg; end; procedure TGLDXFVectorFile.NeedMeshAndFaceGroup(basemesh: TGLBaseMesh; layer: STRING; fgmode: TGLFaceGroupMeshMode; fgmat: STRING; var m: TGLMeshObject; var fg: TFGVertexIndexList); begin m := NeedMesh(basemesh, layer); fg := NeedFaceGroup(m, fgmode, fgmat); end; procedure TGLDXFVectorFile.ReadEntity3Dface(basemesh: TGLBaseMesh); var code, i: Integer; pts: ARRAY [0 .. 3] of TAffineVector; isquad: Boolean; fg: TFGVertexIndexList; color, layer: STRING; m: TGLMeshObject; begin color := ''; layer := ''; isquad := FALSE; for i := 0 to 3 do pts[i] := NullVector; repeat code := GetCode; case code of 0: ; 8: layer := ReadLine; // Layer 10: pts[0].X := ReadDouble; 11: pts[1].X := ReadDouble; 12: pts[2].X := ReadDouble; 13: begin pts[3].X := ReadDouble; isquad := True end; 20: pts[0].Y := ReadDouble; 21: pts[1].Y := ReadDouble; 22: pts[2].Y := ReadDouble; 23: begin pts[3].Y := ReadDouble; isquad := True end; 30: pts[0].Z := ReadDouble; 31: pts[1].Z := ReadDouble; 32: pts[2].Z := ReadDouble; 33: begin pts[3].Z := ReadDouble; isquad := True end; 62: color := ReadLine; // Color else ReadLine; end; until code = 0; PushCode(0); isquad := isquad and ((pts[2].X <> pts[3].X) or (pts[2].Y <> pts[3].Y) or (pts[2].Z <> pts[3].Z)); if isquad then NeedMeshAndFaceGroup(basemesh, layer, fgmmQuads, color, m, fg) else NeedMeshAndFaceGroup(basemesh, layer, fgmmTriangles, color, m, fg); fg.Add(m.vertices.FindOrAdd(pts[0])); fg.Add(m.vertices.FindOrAdd(pts[1])); fg.Add(m.vertices.FindOrAdd(pts[2])); if isquad then fg.Add(m.vertices.FindOrAdd(pts[3])); end; procedure TGLDXFVectorFile.ReadEntityPolyLine(basemesh: TGLBaseMesh); procedure ReadPolylineVertex(m: TGLMeshObject; vertexindexbase: Integer); var color: STRING; pt: TAffineVector; fg: TFGVertexIndexList; code, idx, i70, i71, i72, i73, i74: Integer; begin color := ''; pt := NullVector; i70 := 0; i71 := 0; i72 := 0; i73 := 0; i74 := 0; repeat code := GetCode; case code of 0: ; 5: ReadLine; // ID :=ReadHex16; 8: ReadLine; // ignore per vertex layer. Polyline vertices cannot cross layers! 10: pt.X := ReadDouble; 20: pt.Y := ReadDouble; 30: pt.Z := ReadDouble; 62: color := ReadLine; 70: i70 := ReadInt; 71: i71 := abs(ReadInt); // negative values should hide points... we cannot 72: i72 := abs(ReadInt); 73: i73 := abs(ReadInt); 74: i74 := abs(ReadInt); 100: ReadLine; // Subclass Marker 330: ReadLine; // Soft Pointer? else ReadLine; end; until code = 0; PushCode(0); if (color = '') or (color = '256') or (color = 'BYLAYER') then begin idx := FLayers.IndexOf(m.name); if idx >= 0 then color := IntToStr(LONGINT(FLayers.Objects[idx])); end; if i70 and 192 = 192 then begin m.vertices.Add(pt); end else if i70 and 192 = 128 then begin i71 := i71 - 1 + vertexindexbase; i72 := i72 - 1 + vertexindexbase; i73 := i73 - 1 + vertexindexbase; if i74 = 0 then begin fg := NeedFaceGroup(m, fgmmTriangles, color); fg.Add(i71); fg.Add(i72); fg.Add(i73); end else begin i74 := i74 - 1 + vertexindexbase; fg := NeedFaceGroup(m, fgmmQuads, color); fg.Add(i71); fg.Add(i72); fg.Add(i73); fg.Add(i74); end end else // hmm? end; var m: TGLMeshObject; code, vertexindexbase: Integer; S, layer: STRING; begin m := NIL; vertexindexbase := 0; repeat code := GetCode; S := ReadLine; if (code = 8) then begin layer := S; m := NeedMesh(basemesh, layer); vertexindexbase := m.vertices.count; end; if (code = 0) and (S = 'VERTEX') and (m <> NIL) then ReadPolylineVertex(m, vertexindexbase); until (code = 0) and (S = 'SEQEND'); repeat code := GetCode; if code <> 0 then ReadLine; until (code = 0); PushCode(0); end; procedure TGLDXFVectorFile.ReadEntities(basemesh: TGLBaseMesh); var code: Integer; S: STRING; begin repeat code := GetCode; // DoProgress (psRunning,FSourceStream.Position/FSourceStream.Size*100,false,''); case code of 0: begin S := ReadLine; if S = 'POLYLINE' then ReadEntityPolyLine(basemesh) else if S = '3DFACE' then ReadEntity3Dface(basemesh) else if S = 'INSERT' then ReadInsert(basemesh) else if S = 'ENDSEC' then begin end else if S = 'ENDBLK' then begin end else (* asm nop end // put breakpoint here to catch other entities *) end; else S := ReadLine; end; until (code = 0) and ((S = 'ENDSEC') or (S = 'ENDBLK')); end; // build normals procedure BuildNormals(m: TGLMeshObject); var i, j: Integer; v1, v2, v3, v4, n: TAffineVector; begin for i := 0 to m.vertices.count - 1 do m.Normals.Add(0, 0, 0); for i := 0 to m.FaceGroups.count - 1 do if m.FaceGroups[i] is TFGVertexIndexList then with m.FaceGroups[i] as TFGVertexIndexList do case mode of fgmmTriangles: begin for j := 0 to (VertexIndices.count div 3) - 1 do begin v1 := m.vertices[VertexIndices[j * 3]]; v2 := m.vertices[VertexIndices[j * 3 + 1]]; v3 := m.vertices[VertexIndices[j * 3 + 2]]; n := CalcPlaneNormal(v1, v2, v3); m.Normals.items[VertexIndices[j * 3]] := VectorAdd(m.Normals.items[VertexIndices[j * 3]], n); m.Normals.items[VertexIndices[j * 3 + 1]] := VectorAdd(m.Normals.items[VertexIndices[j * 3 + 1]], n); m.Normals.items[VertexIndices[j * 3 + 2]] := VectorAdd(m.Normals.items[VertexIndices[j * 3 + 2]], n); end; end; fgmmQuads: begin for j := 0 to (VertexIndices.count div 4) - 1 do begin v1 := m.vertices[VertexIndices[j * 4]]; v2 := m.vertices[VertexIndices[j * 4 + 1]]; v3 := m.vertices[VertexIndices[j * 4 + 2]]; v4 := m.vertices[VertexIndices[j * 4 + 3]]; n := CalcPlaneNormal(v1, v2, v3); m.Normals.items[VertexIndices[j * 4]] := VectorAdd(m.Normals.items[VertexIndices[j * 4]], n); m.Normals.items[VertexIndices[j * 4 + 1]] := VectorAdd(m.Normals.items[VertexIndices[j * 4 + 1]], n); m.Normals.items[VertexIndices[j * 4 + 2]] := VectorAdd(m.Normals.items[VertexIndices[j * 4 + 2]], n); m.Normals.items[VertexIndices[j * 4 + 3]] := VectorAdd(m.Normals.items[VertexIndices[j * 4 + 3]], n); end; end; end; for i := 0 to m.Normals.count - 1 do m.Normals.items[i] := VectorNormalize(m.Normals.items[i]); end; procedure TGLDXFVectorFile.LoadFromStream(aStream: TStream); var S: STRING; code, i: Integer; begin FLastpercentdone := 1; /// DoProgress (psStarting,0,false,'Starting'); FEof := FALSE; FSourceStream := aStream; FLineNo := 0; HasPushedCode := FALSE; FLayers := TStringList.create; FBlocks := TStringList.create; while not FEof do begin /// DoProgress (psStarting,FSourceStream.Position/FSourceStream.Size*90,false,''); code := GetCode; if (code = 0) then begin S := ReadLine; if S = 'EOF' then break else if S = 'SECTION' then begin code := GetCode; if code <> 2 then raise Exception.create('Name must follow Section' + ' on Line #' + IntToStr(FLineNo)) else begin S := ReadLine; if S = 'HEADER' then SkipSection else if S = 'BLOCKS' then ReadBlocks else if S = 'ENTITIES' then ReadEntities(owner) else if S = 'CLASSES' then SkipSection else if S = 'TABLES' then ReadTables else if S = 'OBJECTS' then SkipSection else SkipSection; end end else if S = 'ENDSEC' then raise Exception.create('SECTION/ENDSEC Mismatch' + ' on Line #' + IntToStr(FLineNo)) end else S := ReadLine; // raise Exception.create ('Invalid Group Code'); end; // calc normals FLayers.free; for i := FBlocks.count - 1 downto 0 do (FBlocks.Objects[i] as TGLFreeForm).free; FBlocks.free; for i := 0 to owner.MeshObjects.count - 1 do BuildNormals(owner.MeshObjects[i]); /// DoProgress (psEnding,100,false,''); end; initialization RegisterVectorFileFormat('dxf', 'AutoCAD Exchange Format', TGLDXFVectorFile); end.