GXS.FileSTL.pas 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. //
  2. // The graphics engine GXScene https://github.com/glscene
  3. //
  4. unit GXS.FileSTL;
  5. (*
  6. Support-code to load STL Files into TgxFreeForm-Components.
  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. *)
  10. interface
  11. uses
  12. System.Classes,
  13. System.SysUtils,
  14. GLScene.VectorTypes,
  15. GLScene.VectorGeometry,
  16. GXS.VectorLists,
  17. GXS.VectorFileObjects,
  18. GXS.ApplicationFileIO,
  19. GXS.ImageUtils;
  20. type
  21. TSTLHeader = packed record
  22. dummy: array [0 .. 79] of byte;
  23. nbFaces: Longint;
  24. end;
  25. TSTLVertex = TAffineVector;
  26. (* Original specs : = packed record
  27. x : single;
  28. y : single;
  29. z : single;
  30. end; *)
  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. (* The STL vector file (stereolithography format).
  40. It is a list of the triangular surfaces that describe a computer generated
  41. solid model. This is the standard input for most rapid prototyping machines.
  42. There are two flavors of STL, the "text" and the "binary", this class
  43. reads both, but exports only the "binary" version.
  44. Original Binary importer code by Paul M. Bearne, Text importer by Adem. *)
  45. TgxSTLVectorFile = class(TgxVectorFile)
  46. public
  47. class function Capabilities: TDataFileCapabilities; override;
  48. procedure LoadFromStream(aStream: TStream); override;
  49. procedure SaveToStream(aStream: TStream); override;
  50. end;
  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. // ------------------ TgxSTLVectorFile ------------------
  65. // ------------------
  66. class function TgxSTLVectorFile.Capabilities: TDataFileCapabilities;
  67. begin
  68. Result := [dfcRead, dfcWrite];
  69. end;
  70. procedure TgxSTLVectorFile.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. isBinary: Boolean;
  99. headerBuf: array [0 .. cFULL_HEADER_LEN - 1] of AnsiChar;
  100. positionBackup: Integer;
  101. fileContent: TStringList;
  102. curLine: String;
  103. i: Integer;
  104. mesh: TgxMeshObject;
  105. header: TSTLHeader;
  106. dataFace: TSTLFace;
  107. calcNormal: TAffineVector;
  108. begin
  109. positionBackup := aStream.Position;
  110. aStream.Read(headerBuf[0], cFULL_HEADER_LEN);
  111. aStream.Position := positionBackup;
  112. isBinary := True;
  113. i := 0;
  114. while i < 80 do
  115. begin
  116. if (headerBuf[i] < #32) and (headerBuf[i] <> #0) then
  117. begin
  118. isBinary := False;
  119. Break;
  120. end;
  121. Inc(i);
  122. end;
  123. mesh := TgxMeshObject.CreateOwned(Owner.MeshObjects);
  124. try
  125. mesh.Mode := momTriangles;
  126. if isBinary then
  127. begin
  128. aStream.Read(header, SizeOf(TSTLHeader));
  129. for i := 0 to header.nbFaces - 1 do
  130. begin
  131. aStream.Read(dataFace, SizeOf(TSTLFace));
  132. with dataFace, mesh do
  133. begin
  134. // STL faces have a normal, but do not necessarily follow the winding rule,
  135. // so we must first determine if the triangle is properly oriented
  136. // and rewind it properly if not...
  137. calcNormal := CalcPlaneNormal(v1, v2, v3);
  138. if VectorDotProduct(calcNormal, normal) > 0 then
  139. Vertices.Add(v1, v2, v3)
  140. else
  141. Vertices.Add(v3, v2, v1);
  142. Normals.Add(normal, normal, normal);
  143. end;
  144. end;
  145. end
  146. else
  147. begin
  148. fileContent := TStringList.Create;
  149. sl := TStringList.Create;
  150. try
  151. fileContent.LoadFromStream(aStream);
  152. i := 0;
  153. curLine := Trim(UpperCase(fileContent[i]));
  154. if Pos(cSOLID_LABEL, curLine) = 1 then
  155. begin
  156. mesh.Vertices.Capacity := (fileContent.Count - 2) div 7;
  157. mesh.Normals.Capacity := (fileContent.Count - 2) div 7;
  158. Inc(i);
  159. curLine := Trim(UpperCase(fileContent[i]));
  160. while i < fileContent.Count do
  161. begin
  162. if Pos(cFACETNORMAL_LABEL, curLine) = 1 then
  163. begin
  164. DecodeSTLNormals(curLine, dataFace.normal);
  165. Inc(i);
  166. curLine := Trim(UpperCase(fileContent[i]));
  167. if Pos(cOUTERLOOP_LABEL, curLine) = 1 then
  168. begin
  169. Inc(i);
  170. curLine := Trim(fileContent[i]);
  171. DecodeSTLVertex(curLine, dataFace.v1);
  172. Inc(i);
  173. curLine := Trim(fileContent[i]);
  174. DecodeSTLVertex(curLine, dataFace.v2);
  175. Inc(i);
  176. curLine := Trim(fileContent[i]);
  177. DecodeSTLVertex(curLine, dataFace.v3);
  178. end;
  179. Inc(i);
  180. curLine := Trim(UpperCase(fileContent[i]));
  181. if Pos(cENDLOOP_LABEL, curLine) <> 1 then
  182. raise Exception.Create('End of Loop Not Found')
  183. else
  184. begin
  185. calcNormal := CalcPlaneNormal(dataFace.v1, dataFace.v2,
  186. dataFace.v3);
  187. if VectorDotProduct(calcNormal, dataFace.normal) > 0 then
  188. mesh.Vertices.Add(dataFace.v1, dataFace.v2, dataFace.v3)
  189. else
  190. mesh.Vertices.Add(dataFace.v3, dataFace.v2, dataFace.v1);
  191. mesh.Normals.Add(dataFace.normal, dataFace.normal,
  192. dataFace.normal);
  193. end;
  194. end;
  195. Inc(i);
  196. curLine := Trim(UpperCase(fileContent[i]));
  197. if Pos(cENDFACET_LABEL, curLine) <> 1 then
  198. raise Exception.Create('End of Facet Not found');
  199. Inc(i);
  200. curLine := Trim(UpperCase(fileContent[i]));
  201. if Pos(cENDSOLID_LABEL, curLine) = 1 then
  202. Break;
  203. end;
  204. end;
  205. finally
  206. sl.Free;
  207. fileContent.Free;
  208. end;
  209. end;
  210. except
  211. on E: Exception do
  212. begin
  213. mesh.Free;
  214. end;
  215. end;
  216. end;
  217. procedure TgxSTLVectorFile.SaveToStream(aStream: TStream);
  218. var
  219. i: Integer;
  220. header: TSTLHeader;
  221. dataFace: TSTLFace;
  222. list: TgxAffineVectorList;
  223. const
  224. cHeaderTag = 'GXScene STL export';
  225. begin
  226. list := Owner.MeshObjects.ExtractTriangles;
  227. try
  228. FillChar(header.dummy[0], SizeOf(header.dummy), 0);
  229. Move(cHeaderTag, header.dummy[0], Length(cHeaderTag));
  230. header.nbFaces := list.Count div 3;
  231. aStream.Write(header, SizeOf(header));
  232. i := 0;
  233. while i < list.Count do
  234. begin
  235. dataFace.normal := CalcPlaneNormal(list[i], list[i + 1], list[i + 2]);
  236. dataFace.v1 := list[i];
  237. dataFace.v2 := list[i + 1];
  238. dataFace.v3 := list[i + 2];
  239. aStream.Write(dataFace, SizeOf(dataFace));
  240. Inc(i, 3);
  241. end;
  242. finally
  243. list.Free;
  244. end;
  245. end;
  246. // ------------------------------------------------------------------
  247. initialization
  248. // ------------------------------------------------------------------
  249. RegisterVectorFileFormat('stl', 'Stereolithography files', TgxSTLVectorFile);
  250. end.