thingiloader.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. Thingiloader = function(event) {
  2. // Code from https://developer.mozilla.org/En/Using_XMLHttpRequest#Receiving_binary_data
  3. this.load_binary_resource = function(url) {
  4. var req = new XMLHttpRequest();
  5. req.open('GET', url, false);
  6. // The following line says we want to receive data as Binary and not as Unicode
  7. req.overrideMimeType('text/plain; charset=x-user-defined');
  8. req.send(null);
  9. if (req.status != 200) return '';
  10. return req.responseText;
  11. };
  12. this.loadSTL = function(url) {
  13. var looksLikeBinary = function(reader) {
  14. // STL files don't specify a way to distinguish ASCII from binary.
  15. // The usual way is checking for "solid" at the start of the file --
  16. // but Thingiverse has seen at least one binary STL file in the wild
  17. // that breaks this.
  18. // The approach here is different: binary STL files contain a triangle
  19. // count early in the file. If this correctly predicts the file's length,
  20. // it is most probably a binary STL file.
  21. reader.seek(80); // skip the header
  22. var count = reader.readUInt32();
  23. var predictedSize = 80 /* header */ + 4 /* count */ + 50 * count;
  24. return reader.getSize() == predictedSize;
  25. };
  26. workerFacadeMessage({'status':'message', 'content':'Downloading ' + url});
  27. var file = this.load_binary_resource(url);
  28. var reader = new BinaryReader(file);
  29. if (looksLikeBinary(reader)) {
  30. this.loadSTLBinary(reader);
  31. } else {
  32. this.loadSTLString(file);
  33. }
  34. };
  35. this.loadOBJ = function(url) {
  36. workerFacadeMessage({'status':'message', 'content':'Downloading ' + url});
  37. var file = this.load_binary_resource(url);
  38. this.loadOBJString(file);
  39. };
  40. this.loadJSON = function(url) {
  41. workerFacadeMessage({'status':'message', 'content':'Downloading ' + url});
  42. var file = this.load_binary_resource(url);
  43. this.loadJSONString(file);
  44. };
  45. this.loadPLY = function(url) {
  46. workerFacadeMessage({'status':'message', 'content':'Downloading ' + url});
  47. var file = this.load_binary_resource(url);
  48. if (file.match(/format ascii/i)) {
  49. this.loadPLYString(file);
  50. } else {
  51. this.loadPLYBinary(file);
  52. }
  53. };
  54. this.loadSTLString = function(STLString) {
  55. workerFacadeMessage({'status':'message', 'content':'Parsing STL String...'});
  56. workerFacadeMessage({'status':'complete', 'content':this.ParseSTLString(STLString)});
  57. };
  58. this.loadSTLBinary = function(STLBinary) {
  59. workerFacadeMessage({'status':'message', 'content':'Parsing STL Binary...'});
  60. workerFacadeMessage({'status':'complete', 'content':this.ParseSTLBinary(STLBinary)});
  61. };
  62. this.loadOBJString = function(OBJString) {
  63. workerFacadeMessage({'status':'message', 'content':'Parsing OBJ String...'});
  64. workerFacadeMessage({'status':'complete', 'content':this.ParseOBJString(OBJString)});
  65. };
  66. this.loadJSONString = function(JSONString) {
  67. workerFacadeMessage({'status':'message', 'content':'Parsing JSON String...'});
  68. workerFacadeMessage({'status':'complete', 'content':eval(JSONString)});
  69. };
  70. this.loadPLYString = function(PLYString) {
  71. workerFacadeMessage({'status':'message', 'content':'Parsing PLY String...'});
  72. workerFacadeMessage({'status':'complete_points', 'content':this.ParsePLYString(PLYString)});
  73. };
  74. this.loadPLYBinary = function(PLYBinary) {
  75. workerFacadeMessage({'status':'message', 'content':'Parsing PLY Binary...'});
  76. workerFacadeMessage({'status':'complete_points', 'content':this.ParsePLYBinary(PLYBinary)});
  77. };
  78. this.ParsePLYString = function(input) {
  79. var properties = [];
  80. var vertices = [];
  81. var colors = [];
  82. var vertex_count = 0;
  83. var header = /ply\n([\s\S]+)\nend_header/ig.exec(input)[1];
  84. var data = /end_header\n([\s\S]+)$/ig.exec(input)[1];
  85. // workerFacadeMessage({'status':'message', 'content':'header:\n' + header});
  86. // workerFacadeMessage({'status':'message', 'content':'data:\n' + data});
  87. header_parts = header.split("\n");
  88. for (i in header_parts) {
  89. if (/element vertex/i.test(header_parts[i])) {
  90. vertex_count = /element vertex (\d+)/i.exec(header_parts[i])[1];
  91. } else if (/property/i.test(header_parts[i])) {
  92. properties.push(/property (.*) (.*)/i.exec(header_parts[i])[2]);
  93. }
  94. }
  95. // workerFacadeMessage({'status':'message', 'content':'properties: ' + properties});
  96. data_parts = data.split("\n");
  97. for (i in data_parts) {
  98. data_line = data_parts[i];
  99. data_line_parts = data_line.split(" ");
  100. vertices.push([
  101. parseFloat(data_line_parts[properties.indexOf("x")]),
  102. parseFloat(data_line_parts[properties.indexOf("y")]),
  103. parseFloat(data_line_parts[properties.indexOf("z")])
  104. ]);
  105. colors.push([
  106. parseInt(data_line_parts[properties.indexOf("red")]),
  107. parseInt(data_line_parts[properties.indexOf("green")]),
  108. parseInt(data_line_parts[properties.indexOf("blue")])
  109. ]);
  110. }
  111. // workerFacadeMessage({'status':'message', 'content':'vertices: ' + vertices});
  112. return [vertices, colors];
  113. };
  114. this.ParsePLYBinary = function(input) {
  115. return false;
  116. };
  117. this.ParseSTLBinary = function(input) {
  118. // Skip the header.
  119. input.seek(80);
  120. // Load the number of vertices.
  121. var count = input.readUInt32();
  122. // During the parse loop we maintain the following data structures:
  123. var vertices = []; // Append-only list of all unique vertices.
  124. var vert_hash = {}; // Mapping from vertex to index in 'vertices', above.
  125. var faces = []; // List of triangle descriptions, each a three-element
  126. // list of indices in 'vertices', above.
  127. for (var i = 0; i < count; i++) {
  128. if (i % 100 == 0) {
  129. workerFacadeMessage({
  130. 'status':'message',
  131. 'content':'Parsing ' + (i+1) + ' of ' + count + ' polygons...'
  132. });
  133. workerFacadeMessage({
  134. 'status':'progress',
  135. 'content':parseInt(i / count * 100) + '%'
  136. });
  137. }
  138. // Skip the normal (3 single-precision floats)
  139. input.seek(input.getPosition() + 12);
  140. var face_indices = [];
  141. for (var x = 0; x < 3; x++) {
  142. var vertex = [input.readFloat(), input.readFloat(), input.readFloat()];
  143. var vertexIndex = vert_hash[vertex];
  144. if (vertexIndex == null) {
  145. vertexIndex = vertices.length;
  146. vertices.push(vertex);
  147. vert_hash[vertex] = vertexIndex;
  148. }
  149. face_indices.push(vertexIndex);
  150. }
  151. faces.push(face_indices);
  152. // Skip the "attribute" field (unused in common models)
  153. input.readUInt16();
  154. }
  155. return [vertices, faces];
  156. };
  157. // build stl's vertex and face arrays
  158. this.ParseSTLString = function(STLString) {
  159. var vertexes = [];
  160. var faces = [];
  161. var face_vertexes = [];
  162. var vert_hash = {}
  163. // console.log(STLString);
  164. // strip out extraneous stuff
  165. STLString = STLString.replace(/\r/, "\n");
  166. STLString = STLString.replace(/^solid[^\n]*/, "");
  167. STLString = STLString.replace(/\n/g, " ");
  168. STLString = STLString.replace(/facet normal /g,"");
  169. STLString = STLString.replace(/outer loop/g,"");
  170. STLString = STLString.replace(/vertex /g,"");
  171. STLString = STLString.replace(/endloop/g,"");
  172. STLString = STLString.replace(/endfacet/g,"");
  173. STLString = STLString.replace(/endsolid[^\n]*/, "");
  174. STLString = STLString.replace(/\s+/g, " ");
  175. STLString = STLString.replace(/^\s+/, "");
  176. // console.log(STLString);
  177. var facet_count = 0;
  178. var block_start = 0;
  179. var points = STLString.split(" ");
  180. workerFacadeMessage({'status':'message', 'content':'Parsing vertices...'});
  181. for (var i=0; i<points.length/12-1; i++) {
  182. if ((i % 100) == 0) {
  183. workerFacadeMessage({'status':'progress', 'content':parseInt(i / (points.length/12-1) * 100) + '%'});
  184. }
  185. var face_indices = [];
  186. for (var x=0; x<3; x++) {
  187. var vertex = [parseFloat(points[block_start+x*3+3]), parseFloat(points[block_start+x*3+4]), parseFloat(points[block_start+x*3+5])];
  188. var vertexIndex = vert_hash[vertex];
  189. if (vertexIndex == null) {
  190. vertexIndex = vertexes.length;
  191. vertexes.push(vertex);
  192. vert_hash[vertex] = vertexIndex;
  193. }
  194. face_indices.push(vertexIndex);
  195. }
  196. faces.push(face_indices);
  197. block_start = block_start + 12;
  198. }
  199. return [vertexes, faces];
  200. };
  201. this.ParseOBJString = function(OBJString) {
  202. var vertexes = [];
  203. var faces = [];
  204. var lines = OBJString.split("\n");
  205. // var normal_position = 0;
  206. for (var i=0; i<lines.length; i++) {
  207. workerFacadeMessage({'status':'progress', 'content':parseInt(i / lines.length * 100) + '%'});
  208. line_parts = lines[i].replace(/\s+/g, " ").split(" ");
  209. if (line_parts[0] == "v") {
  210. vertexes.push([parseFloat(line_parts[1]), parseFloat(line_parts[2]), parseFloat(line_parts[3])]);
  211. } else if (line_parts[0] == "f") {
  212. faces.push([parseFloat(line_parts[1].split("/")[0])-1, parseFloat(line_parts[2].split("/")[0])-1, parseFloat(line_parts[3].split("/")[0]-1), 0])
  213. }
  214. }
  215. return [vertexes, faces];
  216. };
  217. switch(event.data.cmd) {
  218. case "loadSTL":
  219. this.loadSTL(event.data.param);
  220. break;
  221. case "loadSTLString":
  222. this.loadSTLString(event.data.param);
  223. break;
  224. case "loadSTLBinary":
  225. this.loadSTLBinary(event.data.param);
  226. break;
  227. case "loadOBJ":
  228. this.loadOBJ(event.data.param);
  229. break;
  230. case "loadOBJString":
  231. this.loadOBJString(event.data.param);
  232. break;
  233. case "loadJSON":
  234. this.loadJSON(event.data.param);
  235. break;
  236. case "loadPLY":
  237. this.loadPLY(event.data.param);
  238. break;
  239. case "loadPLYString":
  240. this.loadPLYString(event.data.param);
  241. break;
  242. case "loadPLYBinary":
  243. this.loadPLYBinary(event.data.param);
  244. break;
  245. }
  246. };
  247. if (typeof(window) === "undefined") {
  248. onmessage = Thingiloader;
  249. workerFacadeMessage = postMessage;
  250. importScripts('binaryReader.js');
  251. } else {
  252. workerFacadeMessage = WorkerFacade.add(thingiurlbase + "/thingiloader.js", Thingiloader);
  253. }