GLS.FileSTL.pas 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/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. GLS.VectorTypes,
  22. GLS.VectorGeometry,
  23. GLS.VectorLists,
  24. GLS.VectorFileObjects,
  25. GLS.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. Result := True;
  133. try
  134. if (AStream.Size - AStream.Position) < SizeOf(TSTLHeader) then Abort;
  135. P := AStream.Position;
  136. PHeader := @Header;
  137. AStream.Read(PHeader^, SizeOf(Header));
  138. AStream.Position := P;
  139. if not
  140. (
  141. (Header.Dummy[0] = 's') and
  142. (Header.Dummy[1] = 'o') and
  143. (Header.Dummy[2] = 'l') and
  144. (Header.Dummy[3] = 'i') and
  145. (Header.Dummy[4] = 'd')
  146. ) then Exit;
  147. if AStream.Size <> (SizeOf(TSTLHeader) + (Header.Faces * FACET_SIZE)) then Abort;
  148. Result := True;
  149. except
  150. Result := False;
  151. end;
  152. end;
  153. begin
  154. // create mesh object
  155. Mesh := TGLMeshObject.CreateOwned(Owner.MeshObjects);
  156. try
  157. Mesh.Mode := momTriangles;
  158. if IsBinary then
  159. begin
  160. // BINARY STL READER
  161. AStream.Read(Header, SizeOf(TSTLHeader));
  162. Mesh.Vertices.Capacity := Header.nbFaces * 3;
  163. Mesh.Normals.Capacity := Header.nbFaces * 3;
  164. Mesh.Colors.Capacity := Header.nbFaces * 3;
  165. for I := 0 to Header.nbFaces - 1 do
  166. begin
  167. aStream.Read(DataFace, SizeOf(TSTLFace));
  168. with DataFace do
  169. begin
  170. // STL faces have a normal, but do not necessarily follow the winding rule,
  171. // so we must first determine if the triangle is properly oriented and
  172. // rewind it properly if not...
  173. CalcNormal := CalcPlaneNormal(v1, v2, v3);
  174. if VectorDotProduct(CalcNormal, normal) > 0 then
  175. Mesh.Vertices.Add(v1, v2, v3)
  176. else
  177. Mesh.Vertices.Add(v3, v2, v1);
  178. Mesh.Normals.Add(normal, normal, normal);
  179. //
  180. // evaluates vertices colors for Fusion360 STL extended format
  181. //
  182. // https://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL
  183. //
  184. if STLUseEmbeddedColors then
  185. begin
  186. L := padding[0] or (padding[1] shl 8);
  187. R := (L and $1F) shl 3;
  188. G := ((L shr 5) and $1F) shl 3;
  189. B := ((L shr 10) and $1F) shl 3;
  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. Mesh.Colors.Add(R / 255, G / 255, B / 255, 1.0);
  193. end;
  194. end;
  195. end;
  196. STLUseEmbeddedColors := False;
  197. end
  198. else
  199. begin
  200. // ASCII STL READER
  201. FileContent := TStringList.Create;
  202. Sl := TStringList.Create;
  203. try
  204. FileContent.LoadFromStream(AStream);
  205. I := 0;
  206. CurLine := Trim(UpperCase(FileContent[I]));
  207. if Pos(cSOLID_LABEL, CurLine) = 1 then
  208. begin
  209. Mesh.Vertices.Capacity := (FileContent.Count - 2) div 7;
  210. Mesh.Normals.Capacity := (FileContent.Count -2) div 7;
  211. Inc(I);
  212. CurLine := Trim(UpperCase(FileContent[I]));
  213. while I < FileContent.Count do
  214. begin
  215. if Pos(cFACETNORMAL_LABEL, CurLine) = 1 then
  216. begin
  217. _DecodeSTLNormals(CurLine, DataFace.normal);
  218. Inc(I);
  219. CurLine := Trim(UpperCase(FileContent[I]));
  220. if Pos(cOUTERLOOP_LABEL, CurLine) = 1 then
  221. begin
  222. Inc(I);
  223. CurLine := Trim(FileContent[I]);
  224. _DecodeSTLVertex(CurLine, DataFace.v1);
  225. Inc(I);
  226. CurLine := Trim(FileContent[I]);
  227. _DecodeSTLVertex(CurLine, DataFace.v2);
  228. Inc(I);
  229. CurLine := Trim(FileContent[I]);
  230. _DecodeSTLVertex(CurLine, DataFace.v3);
  231. end;
  232. Inc(I);
  233. CurLine := Trim(UpperCase(FileContent[I]));
  234. if Pos(cENDLOOP_LABEL, CurLine) <> 1 then
  235. raise Exception.Create('End of Loop Not Found')
  236. else
  237. begin
  238. CalcNormal := CalcPlaneNormal(DataFace.v1, DataFace.v2, DataFace.v3);
  239. if VectorDotProduct(CalcNormal, DataFace.normal) > 0 then
  240. Mesh.Vertices.Add(DataFace.v1, DataFace.v2, DataFace.v3)
  241. else
  242. Mesh.Vertices.Add(DataFace.v3, DataFace.v2, DataFace.v1);
  243. Mesh.Normals.Add(DataFace.normal, DataFace.normal, DataFace.normal);
  244. end;
  245. end;
  246. Inc(I);
  247. CurLine := Trim(UpperCase(FileContent[I]));
  248. if Pos(cENDFACET_LABEL, curLine) <> 1 then
  249. raise Exception.Create('End of Facet Not found');
  250. Inc(I);
  251. CurLine := Trim(UpperCase(FileContent[I]));
  252. if Pos(cENDSOLID_LABEL, curLine) = 1 then Break;
  253. end;
  254. end;
  255. finally
  256. Sl.Free;
  257. FileContent.Free;
  258. end;
  259. end;
  260. except
  261. on E: Exception do
  262. Mesh.Free;
  263. end;
  264. end;
  265. procedure TGLSTLVectorFile.SaveToStream(aStream: TStream);
  266. var
  267. I: Integer;
  268. DataFace: TSTLFace;
  269. Header: TSTLHeader;
  270. List: TGLAffineVectorList;
  271. const
  272. cHeaderTag = 'STL export';
  273. begin
  274. List := Owner.MeshObjects.ExtractTriangles;
  275. try
  276. FillChar(Header.dummy[0], SizeOf(Header.dummy), 0);
  277. Move(cHeaderTag, Header.dummy[0], Length(cHeaderTag));
  278. Header.nbFaces := List.Count div 3;
  279. aStream.Write(Header, SizeOf(Header));
  280. I := 0;
  281. while I <list.Count do
  282. begin
  283. DataFace.normal := CalcPlaneNormal(List[I], List[I + 1], List[I + 2]);
  284. DataFace.v1 := List[I];
  285. DataFace.v2 := List[I + 1];
  286. DataFace.v3 := List[I + 2];
  287. aStream.Write(DataFace, SizeOf(DataFace));
  288. Inc(I, 3);
  289. end;
  290. finally
  291. list.Free;
  292. end;
  293. end;
  294. // ------------------------------------------------------------------
  295. initialization
  296. // ------------------------------------------------------------------
  297. STLUseEmbeddedColors := False;
  298. RegisterVectorFileFormat('stl', 'Stereolithography files', TGLSTLVectorFile);
  299. end.