stlFileLoader.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import { Nullable } from "babylonjs/types";
  2. import { Tools } from "babylonjs/Misc/tools";
  3. import { VertexBuffer } from "babylonjs/Meshes/buffer";
  4. import { Skeleton } from "babylonjs/Bones/skeleton";
  5. import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
  6. import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
  7. import { Mesh } from "babylonjs/Meshes/mesh";
  8. import { SceneLoader, ISceneLoaderPlugin, ISceneLoaderPluginExtensions } from "babylonjs/Loading/sceneLoader";
  9. import { AssetContainer } from "babylonjs/assetContainer";
  10. import { Scene } from "babylonjs/scene";
  11. /**
  12. * STL file type loader.
  13. * This is a babylon scene loader plugin.
  14. */
  15. export class STLFileLoader implements ISceneLoaderPlugin {
  16. /** @hidden */
  17. public solidPattern = /solid (\S*)([\S\s]*?)endsolid[ ]*(\S*)/g;
  18. /** @hidden */
  19. public facetsPattern = /facet([\s\S]*?)endfacet/g;
  20. /** @hidden */
  21. public normalPattern = /normal[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;
  22. /** @hidden */
  23. public vertexPattern = /vertex[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;
  24. /**
  25. * Defines the name of the plugin.
  26. */
  27. public name = "stl";
  28. /**
  29. * Defines the extensions the stl loader is able to load.
  30. * force data to come in as an ArrayBuffer
  31. * we'll convert to string if it looks like it's an ASCII .stl
  32. */
  33. public extensions: ISceneLoaderPluginExtensions = {
  34. ".stl": { isBinary: true },
  35. };
  36. /**
  37. * Import meshes into a scene.
  38. * @param meshesNames An array of mesh names, a single mesh name, or empty string for all meshes that filter what meshes are imported
  39. * @param scene The scene to import into
  40. * @param data The data to import
  41. * @param rootUrl The root url for scene and resources
  42. * @param meshes The meshes array to import into
  43. * @param particleSystems The particle systems array to import into
  44. * @param skeletons The skeletons array to import into
  45. * @param onError The callback when import fails
  46. * @returns True if successful or false otherwise
  47. */
  48. public importMesh(meshesNames: any, scene: Scene, data: any, rootUrl: string, meshes: Nullable<AbstractMesh[]>, particleSystems: Nullable<IParticleSystem[]>, skeletons: Nullable<Skeleton[]>): boolean {
  49. var matches;
  50. if (typeof data !== "string") {
  51. if (this._isBinary(data)) {
  52. // binary .stl
  53. var babylonMesh = new Mesh("stlmesh", scene);
  54. this._parseBinary(babylonMesh, data);
  55. if (meshes) {
  56. meshes.push(babylonMesh);
  57. }
  58. return true;
  59. }
  60. // ASCII .stl
  61. // convert to string
  62. var array_buffer = new Uint8Array(data);
  63. var str = '';
  64. for (var i = 0; i < data.byteLength; i++) {
  65. str += String.fromCharCode(array_buffer[i]); // implicitly assumes little-endian
  66. }
  67. data = str;
  68. }
  69. //if arrived here, data is a string, containing the STLA data.
  70. while (matches = this.solidPattern.exec(data)) {
  71. var meshName = matches[1];
  72. var meshNameFromEnd = matches[3];
  73. if (meshName != meshNameFromEnd) {
  74. Tools.Error("Error in STL, solid name != endsolid name");
  75. return false;
  76. }
  77. // check meshesNames
  78. if (meshesNames && meshName) {
  79. if (meshesNames instanceof Array) {
  80. if (!meshesNames.indexOf(meshName)) {
  81. continue;
  82. }
  83. } else {
  84. if (meshName !== meshesNames) {
  85. continue;
  86. }
  87. }
  88. }
  89. // stl mesh name can be empty as well
  90. meshName = meshName || "stlmesh";
  91. var babylonMesh = new Mesh(meshName, scene);
  92. this._parseASCII(babylonMesh, matches[2]);
  93. if (meshes) {
  94. meshes.push(babylonMesh);
  95. }
  96. }
  97. return true;
  98. }
  99. /**
  100. * Load into a scene.
  101. * @param scene The scene to load into
  102. * @param data The data to import
  103. * @param rootUrl The root url for scene and resources
  104. * @param onError The callback when import fails
  105. * @returns true if successful or false otherwise
  106. */
  107. public load(scene: Scene, data: any, rootUrl: string): boolean {
  108. var result = this.importMesh(null, scene, data, rootUrl, null, null, null);
  109. return result;
  110. }
  111. /**
  112. * Load into an asset container.
  113. * @param scene The scene to load into
  114. * @param data The data to import
  115. * @param rootUrl The root url for scene and resources
  116. * @param onError The callback when import fails
  117. * @returns The loaded asset container
  118. */
  119. public loadAssetContainer(scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void): AssetContainer {
  120. var container = new AssetContainer(scene);
  121. scene._blockEntityCollection = true;
  122. this.importMesh(null, scene, data, rootUrl, container.meshes, null, null);
  123. scene._blockEntityCollection = false;
  124. return container;
  125. }
  126. private _isBinary(data: any) {
  127. // check if file size is correct for binary stl
  128. var faceSize, nFaces, reader;
  129. reader = new DataView(data);
  130. faceSize = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8);
  131. nFaces = reader.getUint32(80, true);
  132. if (80 + (32 / 8) + (nFaces * faceSize) === reader.byteLength) {
  133. return true;
  134. }
  135. // check characters higher than ASCII to confirm binary
  136. var fileLength = reader.byteLength;
  137. for (var index = 0; index < fileLength; index++) {
  138. if (reader.getUint8(index) > 127) {
  139. return true;
  140. }
  141. }
  142. return false;
  143. }
  144. private _parseBinary(mesh: Mesh, data: ArrayBuffer) {
  145. var reader = new DataView(data);
  146. var faces = reader.getUint32(80, true);
  147. var dataOffset = 84;
  148. var faceLength = 12 * 4 + 2;
  149. var offset = 0;
  150. var positions = new Float32Array(faces * 3 * 3);
  151. var normals = new Float32Array(faces * 3 * 3);
  152. var indices = new Uint32Array(faces * 3);
  153. var indicesCount = 0;
  154. for (var face = 0; face < faces; face++) {
  155. var start = dataOffset + face * faceLength;
  156. var normalX = reader.getFloat32(start, true);
  157. var normalY = reader.getFloat32(start + 4, true);
  158. var normalZ = reader.getFloat32(start + 8, true);
  159. for (var i = 1; i <= 3; i++) {
  160. var vertexstart = start + i * 12;
  161. // ordering is intentional to match ascii import
  162. positions[offset] = reader.getFloat32(vertexstart, true);
  163. positions[offset + 2] = reader.getFloat32(vertexstart + 4, true);
  164. positions[offset + 1] = reader.getFloat32(vertexstart + 8, true);
  165. normals[offset] = normalX;
  166. normals[offset + 2] = normalY;
  167. normals[offset + 1] = normalZ;
  168. offset += 3;
  169. }
  170. indices[indicesCount] = indicesCount++;
  171. indices[indicesCount] = indicesCount++;
  172. indices[indicesCount] = indicesCount++;
  173. }
  174. mesh.setVerticesData(VertexBuffer.PositionKind, positions);
  175. mesh.setVerticesData(VertexBuffer.NormalKind, normals);
  176. mesh.setIndices(indices);
  177. mesh.computeWorldMatrix(true);
  178. }
  179. private _parseASCII(mesh: Mesh, solidData: string) {
  180. var positions = [];
  181. var normals = [];
  182. var indices = [];
  183. var indicesCount = 0;
  184. //load facets, ignoring loop as the standard doesn't define it can contain more than vertices
  185. var matches;
  186. while (matches = this.facetsPattern.exec(solidData)) {
  187. var facet = matches[1];
  188. //one normal per face
  189. var normalMatches = this.normalPattern.exec(facet);
  190. this.normalPattern.lastIndex = 0;
  191. if (!normalMatches) {
  192. continue;
  193. }
  194. var normal = [Number(normalMatches[1]), Number(normalMatches[5]), Number(normalMatches[3])];
  195. var vertexMatch;
  196. while (vertexMatch = this.vertexPattern.exec(facet)) {
  197. positions.push(Number(vertexMatch[1]), Number(vertexMatch[5]), Number(vertexMatch[3]));
  198. normals.push(normal[0], normal[1], normal[2]);
  199. }
  200. indices.push(indicesCount++, indicesCount++, indicesCount++);
  201. this.vertexPattern.lastIndex = 0;
  202. }
  203. this.facetsPattern.lastIndex = 0;
  204. mesh.setVerticesData(VertexBuffer.PositionKind, positions);
  205. mesh.setVerticesData(VertexBuffer.NormalKind, normals);
  206. mesh.setIndices(indices);
  207. mesh.computeWorldMatrix(true);
  208. }
  209. }
  210. if (SceneLoader) {
  211. SceneLoader.RegisterPlugin(new STLFileLoader());
  212. }