GXS.FileSTL.pas 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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. Stage.VectorTypes,
  15. Stage.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. implementation // ------------------------------------------------------------
  52. const
  53. cSOLID_LABEL = 'SOLID';
  54. cFACETNORMAL_LABEL = 'FACET NORMAL ';
  55. cOUTERLOOP_LABEL = 'OUTER LOOP';
  56. cVERTEX_LABEL = 'VERTEX';
  57. cENDLOOP_LABEL = 'ENDLOOP';
  58. cENDFACET_LABEL = 'ENDFACET';
  59. cENDSOLID_LABEL = 'ENDSOLID';
  60. cFULL_HEADER_LEN = 84;
  61. // ------------------
  62. // ------------------ TGXSTLVectorFile ------------------
  63. // ------------------
  64. class function TGXSTLVectorFile.Capabilities: TDataFileCapabilities;
  65. begin
  66. Result := [dfcRead, dfcWrite];
  67. end;
  68. procedure TGXSTLVectorFile.LoadFromStream(aStream: TStream);
  69. var
  70. sl: TStringList;
  71. procedure DecodeSTLNormals(const aString: String; var aNormal: TSTLVertex);
  72. begin
  73. sl.CommaText := aString;
  74. if sl.Count <> 5 then
  75. raise Exception.Create('Invalid Normal')
  76. else
  77. begin
  78. aNormal.X := StrToFloatDef(sl[2], 0);
  79. aNormal.Y := StrToFloatDef(sl[3], 0);
  80. aNormal.Z := StrToFloatDef(sl[4], 0);
  81. end;
  82. end;
  83. procedure DecodeSTLVertex(const aString: String; var aVertex: TSTLVertex);
  84. begin
  85. sl.CommaText := aString;
  86. if (sl.Count <> 4) or (CompareText(sl[0], cVERTEX_LABEL) <> 0) then
  87. raise Exception.Create('Invalid Vertex')
  88. else
  89. begin
  90. aVertex.X := StrToFloatDef(sl[1], 0);
  91. aVertex.Y := StrToFloatDef(sl[2], 0);
  92. aVertex.Z := StrToFloatDef(sl[3], 0);
  93. end;
  94. end;
  95. var
  96. isBinary: Boolean;
  97. headerBuf: array [0 .. cFULL_HEADER_LEN - 1] of AnsiChar;
  98. positionBackup: Integer;
  99. fileContent: TStringList;
  100. curLine: String;
  101. i: Integer;
  102. mesh: TgxMeshObject;
  103. header: TSTLHeader;
  104. dataFace: TSTLFace;
  105. calcNormal: TAffineVector;
  106. begin
  107. positionBackup := aStream.Position;
  108. aStream.Read(headerBuf[0], cFULL_HEADER_LEN);
  109. aStream.Position := positionBackup;
  110. isBinary := True;
  111. i := 0;
  112. while i < 80 do
  113. begin
  114. if (headerBuf[i] < #32) and (headerBuf[i] <> #0) then
  115. begin
  116. isBinary := False;
  117. Break;
  118. end;
  119. Inc(i);
  120. end;
  121. mesh := TgxMeshObject.CreateOwned(Owner.MeshObjects);
  122. try
  123. mesh.Mode := momTriangles;
  124. if isBinary then
  125. begin
  126. aStream.Read(header, SizeOf(TSTLHeader));
  127. for i := 0 to header.nbFaces - 1 do
  128. begin
  129. aStream.Read(dataFace, SizeOf(TSTLFace));
  130. with dataFace, mesh do
  131. begin
  132. // STL faces have a normal, but do not necessarily follow the winding rule,
  133. // so we must first determine if the triangle is properly oriented
  134. // and rewind it properly if not...
  135. calcNormal := CalcPlaneNormal(v1, v2, v3);
  136. if VectorDotProduct(calcNormal, normal) > 0 then
  137. Vertices.Add(v1, v2, v3)
  138. else
  139. Vertices.Add(v3, v2, v1);
  140. Normals.Add(normal, normal, normal);
  141. end;
  142. end;
  143. end
  144. else
  145. begin
  146. fileContent := TStringList.Create;
  147. sl := TStringList.Create;
  148. try
  149. fileContent.LoadFromStream(aStream);
  150. i := 0;
  151. curLine := Trim(UpperCase(fileContent[i]));
  152. if Pos(cSOLID_LABEL, curLine) = 1 then
  153. begin
  154. mesh.Vertices.Capacity := (fileContent.Count - 2) div 7;
  155. mesh.Normals.Capacity := (fileContent.Count - 2) div 7;
  156. Inc(i);
  157. curLine := Trim(UpperCase(fileContent[i]));
  158. while i < fileContent.Count do
  159. begin
  160. if Pos(cFACETNORMAL_LABEL, curLine) = 1 then
  161. begin
  162. DecodeSTLNormals(curLine, dataFace.normal);
  163. Inc(i);
  164. curLine := Trim(UpperCase(fileContent[i]));
  165. if Pos(cOUTERLOOP_LABEL, curLine) = 1 then
  166. begin
  167. Inc(i);
  168. curLine := Trim(fileContent[i]);
  169. DecodeSTLVertex(curLine, dataFace.v1);
  170. Inc(i);
  171. curLine := Trim(fileContent[i]);
  172. DecodeSTLVertex(curLine, dataFace.v2);
  173. Inc(i);
  174. curLine := Trim(fileContent[i]);
  175. DecodeSTLVertex(curLine, dataFace.v3);
  176. end;
  177. Inc(i);
  178. curLine := Trim(UpperCase(fileContent[i]));
  179. if Pos(cENDLOOP_LABEL, curLine) <> 1 then
  180. raise Exception.Create('End of Loop Not Found')
  181. else
  182. begin
  183. calcNormal := CalcPlaneNormal(dataFace.v1, dataFace.v2,
  184. dataFace.v3);
  185. if VectorDotProduct(calcNormal, dataFace.normal) > 0 then
  186. mesh.Vertices.Add(dataFace.v1, dataFace.v2, dataFace.v3)
  187. else
  188. mesh.Vertices.Add(dataFace.v3, dataFace.v2, dataFace.v1);
  189. mesh.Normals.Add(dataFace.normal, dataFace.normal,
  190. dataFace.normal);
  191. end;
  192. end;
  193. Inc(i);
  194. curLine := Trim(UpperCase(fileContent[i]));
  195. if Pos(cENDFACET_LABEL, curLine) <> 1 then
  196. raise Exception.Create('End of Facet Not found');
  197. Inc(i);
  198. curLine := Trim(UpperCase(fileContent[i]));
  199. if Pos(cENDSOLID_LABEL, curLine) = 1 then
  200. Break;
  201. end;
  202. end;
  203. finally
  204. sl.Free;
  205. fileContent.Free;
  206. end;
  207. end;
  208. except
  209. on E: Exception do
  210. begin
  211. mesh.Free;
  212. end;
  213. end;
  214. end;
  215. procedure TGXSTLVectorFile.SaveToStream(aStream: TStream);
  216. var
  217. i: Integer;
  218. header: TSTLHeader;
  219. dataFace: TSTLFace;
  220. list: TgxAffineVectorList;
  221. const
  222. cHeaderTag = 'GXScene STL export';
  223. begin
  224. list := Owner.MeshObjects.ExtractTriangles;
  225. try
  226. FillChar(header.dummy[0], SizeOf(header.dummy), 0);
  227. Move(cHeaderTag, header.dummy[0], Length(cHeaderTag));
  228. header.nbFaces := list.Count div 3;
  229. aStream.Write(header, SizeOf(header));
  230. i := 0;
  231. while i < list.Count do
  232. begin
  233. dataFace.normal := CalcPlaneNormal(list[i], list[i + 1], list[i + 2]);
  234. dataFace.v1 := list[i];
  235. dataFace.v2 := list[i + 1];
  236. dataFace.v3 := list[i + 2];
  237. aStream.Write(dataFace, SizeOf(dataFace));
  238. Inc(i, 3);
  239. end;
  240. finally
  241. list.Free;
  242. end;
  243. end;
  244. // ------------------------------------------------------------------
  245. initialization
  246. // ------------------------------------------------------------------
  247. RegisterVectorFileFormat('stl', 'Stereolithography files', TGXSTLVectorFile);
  248. end.