GLFileSTL.pas 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. //
  2. // This unit is part of the GLScene Engine, http://glscene.org
  3. //
  4. unit GLFileSTL;
  5. (*
  6. Support-code to load STL Files into TGLFreeForm-Components in GLScene.
  7. Note that you must manually add this unit to one of your project's uses
  8. to enable support for STL files at run-time.
  9. The STL vector file (stereolithography format).
  10. It is a list of the triangular surfaces that describe a computer generated solid model.
  11. This is the standard input for most rapid prototyping machines.
  12. There are two flavors of STL, the "text" and the "binary".
  13. This class reads both, but exports only the "binary" version.
  14. Original Binary importer code by Paul M. Bearne, Text importer by Adem.
  15. *)
  16. interface
  17. uses
  18. System.Classes,
  19. System.SysUtils,
  20. GLApplicationFileIO,
  21. GLVectorTypes,
  22. GLVectorGeometry,
  23. GLVectorLists,
  24. GLVectorFileObjects;
  25. type
  26. TSTLHeader = packed record
  27. dummy : array[0..79] of byte;
  28. nbFaces : Longint;
  29. end;
  30. TSTLVertex = TAffineVector;
  31. TSTLFace = packed record
  32. normal : TSTLVertex; // facet surface normal
  33. v1 : TSTLVertex; // vertex 1
  34. v2 : TSTLVertex; // vertex 2
  35. v3 : TSTLVertex; // vertex 3
  36. padding : array[0..1] of byte;
  37. end;
  38. type
  39. TglSTLVectorFile = class(TglVectorFile)
  40. public
  41. class function Capabilities: TGLDataFileCapabilities; override;
  42. procedure LoadFromStream(aStream: TStream); override;
  43. procedure SaveToStream(aStream: TStream); override;
  44. end;
  45. {$IFDEF USE_MULTITHREAD}
  46. threadvar
  47. {$ELSE}
  48. var
  49. {$ENDIF}
  50. STLUseEmbeddedColors: Boolean;
  51. // ------------------------------------------------------------------
  52. implementation
  53. // ------------------------------------------------------------------
  54. const
  55. cSOLID_LABEL = 'SOLID';
  56. cFACETNORMAL_LABEL = 'FACET NORMAL ';
  57. cOUTERLOOP_LABEL = 'OUTER LOOP';
  58. cVERTEX_LABEL = 'VERTEX';
  59. cENDLOOP_LABEL = 'ENDLOOP';
  60. cENDFACET_LABEL = 'ENDFACET';
  61. cENDSOLID_LABEL = 'ENDSOLID';
  62. cFULL_HEADER_LEN = 84;
  63. // ------------------
  64. // ------------------ TGLSTLVectorFile ------------------
  65. // ------------------
  66. class function TglSTLVectorFile.Capabilities: TglDataFileCapabilities;
  67. begin
  68. Result := [dfcRead, dfcWrite];
  69. end;
  70. procedure TGLSTLVectorFile.LoadFromStream(aStream: TStream);
  71. var
  72. Sl: TStringList;
  73. procedure _DecodeSTLNormals(const aString: String; var aNormal: TSTLVertex);
  74. begin
  75. Sl.CommaText := aString;
  76. if Sl.Count <> 5 then
  77. raise Exception.Create('Invalid Normal')
  78. else
  79. begin
  80. aNormal.X := StrToFloatDef(Sl[2], 0);
  81. aNormal.Y := StrToFloatDef(Sl[3], 0);
  82. aNormal.Z := StrToFloatDef(Sl[4], 0);
  83. end;
  84. end;
  85. procedure _DecodeSTLVertex(const aString: String; var aVertex: TSTLVertex);
  86. begin
  87. Sl.CommaText := aString;
  88. if (Sl.Count <> 4) or (CompareText(Sl[0], cVERTEX_LABEL) <> 0) then
  89. raise Exception.Create('Invalid Vertex')
  90. else
  91. begin
  92. aVertex.X := StrToFloatDef(Sl[1], 0);
  93. aVertex.Y := StrToFloatDef(Sl[2], 0);
  94. aVertex.Z := StrToFloatDef(Sl[3], 0);
  95. end;
  96. end;
  97. var
  98. R: Byte;
  99. G: Byte;
  100. B: Byte;
  101. I: Integer;
  102. L: Integer;
  103. CurLine: string;
  104. Mesh: TMeshObject;
  105. DataFace: TSTLFace;
  106. Header: TSTLHeader;
  107. FileContent: TStringList;
  108. CalcNormal: TAffineVector;
  109. // evaluates STL file header to detect if ascii or binary format
  110. //
  111. // NOTE ABOUT STL:
  112. // The minimum size of an empty ASCII file is 15 bytes.
  113. // In binary format each facet contains:
  114. // - Normals: 3 floats (4 bytes)
  115. // - Vertices: 3x floats (4 byte each, 12 bytes total)
  116. // - AttributeCount: 1 short (2 bytes)
  117. // Total: 50 bytes per facet
  118. function IsBinary: Boolean;
  119. const
  120. FACET_SIZE = 50;
  121. type
  122. TSTLHeader = packed record
  123. Dummy: array[0..79] of AnsiChar;
  124. Faces: Longint;
  125. end;
  126. var
  127. P: Int64;
  128. Header: TSTLHeader;
  129. PHeader: ^TSTLHeader;
  130. begin
  131. Result := True;
  132. try
  133. if (AStream.Size - AStream.Position) < SizeOf(TSTLHeader) then Abort;
  134. P := AStream.Position;
  135. PHeader := @Header;
  136. AStream.Read(PHeader^, SizeOf(Header));
  137. AStream.Position := P;
  138. if not
  139. (
  140. (Header.Dummy[0] = 's') and
  141. (Header.Dummy[1] = 'o') and
  142. (Header.Dummy[2] = 'l') and
  143. (Header.Dummy[3] = 'i') and
  144. (Header.Dummy[4] = 'd')
  145. ) then Exit;
  146. if AStream.Size <> (SizeOf(TSTLHeader) + (Header.Faces * FACET_SIZE)) then Abort;
  147. Result := True;
  148. except
  149. Result := False;
  150. end;
  151. end;
  152. begin
  153. // create mesh object
  154. Mesh := TMeshObject.CreateOwned(Owner.MeshObjects);
  155. try
  156. Mesh.Mode := momTriangles;
  157. if IsBinary then
  158. begin
  159. // BINARY STL READER
  160. AStream.Read(Header, SizeOf(TSTLHeader));
  161. Mesh.Vertices.Capacity := Header.nbFaces * 3;
  162. Mesh.Normals.Capacity := Header.nbFaces * 3;
  163. Mesh.Colors.Capacity := Header.nbFaces * 3;
  164. for I := 0 to Header.nbFaces - 1 do
  165. begin
  166. aStream.Read(DataFace, SizeOf(TSTLFace));
  167. with DataFace do
  168. begin
  169. // STL faces have a normal, but do not necessarily follow the winding rule,
  170. // so we must first determine if the triangle is properly oriented and
  171. // rewind it properly if not...
  172. CalcNormal := CalcPlaneNormal(v1, v2, v3);
  173. if VectorDotProduct(CalcNormal, normal) > 0 then
  174. Mesh.Vertices.Add(v1, v2, v3)
  175. else
  176. Mesh.Vertices.Add(v3, v2, v1);
  177. Mesh.Normals.Add(normal, normal, normal);
  178. //
  179. // evaluates vertices colors for Fusion360 STL extended format
  180. //
  181. // https://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL
  182. //
  183. if STLUseEmbeddedColors then
  184. begin
  185. L := padding[0] or (padding[1] shl 8);
  186. R := (L and $1F) shl 3;
  187. G := ((L shr 5) and $1F) shl 3;
  188. B := ((L shr 10) and $1F) shl 3;
  189. Mesh.Colors.Add(R / 255, G / 255, B / 255, 1.0);
  190. Mesh.Colors.Add(R / 255, G / 255, B / 255, 1.0);
  191. Mesh.Colors.Add(R / 255, G / 255, B / 255, 1.0);
  192. end;
  193. end;
  194. end;
  195. STLUseEmbeddedColors := False;
  196. end
  197. else
  198. begin
  199. // ASCII STL READER
  200. FileContent := TStringList.Create;
  201. Sl := TStringList.Create;
  202. try
  203. FileContent.LoadFromStream(AStream);
  204. I := 0;
  205. CurLine := Trim(UpperCase(FileContent[I]));
  206. if Pos(cSOLID_LABEL, CurLine) = 1 then
  207. begin
  208. Mesh.Vertices.Capacity := (FileContent.Count - 2) div 7;
  209. Mesh.Normals.Capacity := (FileContent.Count -2) div 7;
  210. Inc(I);
  211. CurLine := Trim(UpperCase(FileContent[I]));
  212. while I < FileContent.Count do
  213. begin
  214. if Pos(cFACETNORMAL_LABEL, CurLine) = 1 then
  215. begin
  216. _DecodeSTLNormals(CurLine, DataFace.normal);
  217. Inc(I);
  218. CurLine := Trim(UpperCase(FileContent[I]));
  219. if Pos(cOUTERLOOP_LABEL, CurLine) = 1 then
  220. begin
  221. Inc(I);
  222. CurLine := Trim(FileContent[I]);
  223. _DecodeSTLVertex(CurLine, DataFace.v1);
  224. Inc(I);
  225. CurLine := Trim(FileContent[I]);
  226. _DecodeSTLVertex(CurLine, DataFace.v2);
  227. Inc(I);
  228. CurLine := Trim(FileContent[I]);
  229. _DecodeSTLVertex(CurLine, DataFace.v3);
  230. end;
  231. Inc(I);
  232. CurLine := Trim(UpperCase(FileContent[I]));
  233. if Pos(cENDLOOP_LABEL, CurLine) <> 1 then
  234. raise Exception.Create('End of Loop Not Found')
  235. else
  236. begin
  237. CalcNormal := CalcPlaneNormal(DataFace.v1, DataFace.v2, DataFace.v3);
  238. if VectorDotProduct(CalcNormal, DataFace.normal) > 0 then
  239. Mesh.Vertices.Add(DataFace.v1, DataFace.v2, DataFace.v3)
  240. else
  241. Mesh.Vertices.Add(DataFace.v3, DataFace.v2, DataFace.v1);
  242. Mesh.Normals.Add(DataFace.normal, DataFace.normal, DataFace.normal);
  243. end;
  244. end;
  245. Inc(I);
  246. CurLine := Trim(UpperCase(FileContent[I]));
  247. if Pos(cENDFACET_LABEL, curLine) <> 1 then
  248. raise Exception.Create('End of Facet Not found');
  249. Inc(I);
  250. CurLine := Trim(UpperCase(FileContent[I]));
  251. if Pos(cENDSOLID_LABEL, curLine) = 1 then Break;
  252. end;
  253. end;
  254. finally
  255. Sl.Free;
  256. FileContent.Free;
  257. end;
  258. end;
  259. except
  260. on E: Exception do
  261. Mesh.Free;
  262. end;
  263. end;
  264. procedure TGLSTLVectorFile.SaveToStream(aStream: TStream);
  265. var
  266. I: Integer;
  267. DataFace: TSTLFace;
  268. Header: TSTLHeader;
  269. List: TAffineVectorList;
  270. const
  271. cHeaderTag = 'STL export';
  272. begin
  273. List := Owner.MeshObjects.ExtractTriangles;
  274. try
  275. FillChar(Header.dummy[0], SizeOf(Header.dummy), 0);
  276. Move(cHeaderTag, Header.dummy[0], Length(cHeaderTag));
  277. Header.nbFaces := List.Count div 3;
  278. aStream.Write(Header, SizeOf(Header));
  279. I := 0;
  280. while I <list.Count do
  281. begin
  282. DataFace.normal := CalcPlaneNormal(List[I], List[I + 1], List[I + 2]);
  283. DataFace.v1 := List[I];
  284. DataFace.v2 := List[I + 1];
  285. DataFace.v3 := List[I + 2];
  286. aStream.Write(DataFace, SizeOf(DataFace));
  287. Inc(I, 3);
  288. end;
  289. finally
  290. list.Free;
  291. end;
  292. end;
  293. // ------------------------------------------------------------------
  294. initialization
  295. // ------------------------------------------------------------------
  296. STLUseEmbeddedColors := False;
  297. RegisterVectorFileFormat('stl', 'Stereolithography files', TGLSTLVectorFile);
  298. end.