GLS.FileSTL.pas 9.5 KB

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