GLFileSTL.pas 9.8 KB

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