1 /** Some utility for reading and writing both ASCII and binary STL files 2 3 based on https://en.wikipedia.org/wiki/STL_(file_format) 4 5 Copyright: 6 Copyright (c) 2021, Ferhat Kurtulmuş. 7 8 License: 9 $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 10 */ 11 module stlutils; 12 13 import std.stdio; 14 import std.exception; 15 import std..string; 16 import std.uni : isWhite; 17 import std.array : split; 18 import std.range; 19 import std.conv; 20 21 // TODO: use exceptions more for error handling 22 23 final class STL { 24 public float[] normals; 25 public float[] vertices; 26 27 // this makes only sense for binary stl format 28 public ubyte[80] header = cast(ubyte[80])"##Binary STL####################################################################"; 29 30 // in the standard format, attributes should be zero because most software does not understand anything else 31 // public ushort[] attributes; // maybe implement this. 32 33 package string fname; 34 35 this(){ 36 37 } 38 39 @property 40 int numOfTriangles(){ 41 if(!vertices.length) 42 return 0; 43 return cast(int)vertices.length/9; 44 } 45 46 bool empty(){ 47 return vertices is null || !vertices.length; 48 } 49 } 50 51 STL readSTL(string filePath){ 52 import std.path: baseName; 53 54 auto stl = new STL(); 55 56 stl.fname = baseName(filePath, ".stl"); 57 58 if(!isBinarySTL(filePath)) 59 stl.readAsciiFile(filePath); 60 else 61 stl.readBinaryFile(filePath); 62 63 return stl; 64 } 65 66 bool isBinarySTL(S)(auto ref S filePath){ 67 68 auto file = File(filePath, "r"); 69 scope(exit) file.close(); 70 71 char[] _line; 72 file.readln(_line); 73 74 enforce(_line.length, "File is not valid!"); 75 76 if(_line[0..5] == "solid") 77 return false; 78 79 return true; 80 } 81 82 void readAsciiFile(S)(STL stl, auto ref S filePath){ 83 auto file = File(filePath, "r"); 84 scope(exit) file.close(); 85 86 float parseFloat(S)(auto ref S str){ 87 return str.to!float; 88 } 89 90 // maybe preallocate vertices and normals based on the number of the lines 91 stl.normals.reserve(20); 92 stl.vertices.reserve(20*9); 93 94 while (!file.eof){ 95 char[] _line; 96 file.readln(_line); 97 _line = chomp(_line); 98 if(!_line.length) continue; 99 100 string line = assumeUnique(_line); 101 102 string[] tokens = line.strip.split!isWhite; 103 104 if(tokens[0] == "vertex"){ 105 stl.vertices ~= parseFloat(tokens[1]); 106 stl.vertices ~= parseFloat(tokens[2]); 107 stl.vertices ~= parseFloat(tokens[3]); 108 } else if(tokens[0] == "facet"){ 109 stl.normals ~= parseFloat(tokens[2]); 110 stl.normals ~= parseFloat(tokens[3]); 111 stl.normals ~= parseFloat(tokens[4]); 112 } 113 114 } 115 } 116 117 /* // write-at-once 118 void toBinarySTLFile(STL stl, string filePath){ 119 File fwriter; 120 121 fwriter.open(filePath, "wb"); 122 scope(exit) fwriter.close(); 123 124 int numOfTri = stl.numOfTriangles; 125 126 ubyte[] sysBuf = new ubyte[80+4+(12+12+12+12+2) * numOfTri]; 127 128 sysBuf[0..80] = stl.header[]; 129 130 sysBuf[80..84] = (cast(ubyte*)&numOfTri)[0..int.sizeof]; 131 132 short _attr = 0; 133 134 int i; 135 foreach (vchunk, nchunk; zip(chunks(stl.vertices, 9), chunks(stl.normals, 3))){ 136 137 ubyte[50] tbuff; 138 139 tbuff[0..12] = cast(ubyte[])(nchunk[]); 140 tbuff[12..48] = cast(ubyte[])(vchunk[]); 141 142 tbuff[48..50] = (cast(ubyte*)&_attr)[0..short.sizeof]; 143 144 sysBuf[84 + i*50 .. 84 + (i+1)*50] = tbuff[]; 145 ++i; 146 } 147 148 fwriter.rawWrite(sysBuf[]); 149 } 150 */ 151 152 void toBinarySTLFile(STL stl, string filePath){ 153 File fwriter; 154 155 fwriter.open(filePath, "wb"); 156 scope(exit) fwriter.close(); 157 158 int numOfTri = stl.numOfTriangles; 159 160 fwriter.rawWrite(stl.header[]); 161 162 fwriter.rawWrite((cast(ubyte*)&numOfTri)[0..int.sizeof]); 163 164 short _attr = 0; 165 166 foreach (vchunk, nchunk; zip(chunks(stl.vertices, 9), chunks(stl.normals, 3))){ 167 168 ubyte[50] tbuff; 169 170 tbuff[0..12] = cast(ubyte[])(nchunk[]); 171 tbuff[12..48] = cast(ubyte[])(vchunk[]); 172 173 tbuff[48..50] = (cast(ubyte*)&_attr)[0..short.sizeof]; 174 175 fwriter.rawWrite(tbuff[]); 176 } 177 } 178 179 void readBinaryFile(S)(STL stl, auto ref S filePath){ 180 File file; 181 file.open(filePath, "rb"); 182 scope(exit) file.close(); 183 184 ubyte[80] _header; 185 file.rawRead(_header[]); 186 stl.header[] = _header[]; 187 188 ubyte[4] _numOfTriangles; 189 file.rawRead(_numOfTriangles[]); 190 191 int numOfTriangles = *(cast(int*)_numOfTriangles[].ptr); 192 193 stl.normals = new float[numOfTriangles * 3]; 194 stl.vertices = new float[numOfTriangles * 9]; 195 196 //debug writeln(cast(string)assumeUnique( stl.header)); 197 //debug writeln(numOfTriangles); 198 199 foreach (i; 0..numOfTriangles){ 200 201 ubyte[50] tbuff; 202 203 file.rawRead(tbuff[]); 204 205 stl.normals[i*3..(i+1)*3] = cast(float[])tbuff[0..12]; 206 stl.vertices[i*9..(i+1)*9] = cast(float[])tbuff[12..48]; 207 208 const short dummy_attr = *(cast(short*)tbuff[48..50].ptr); 209 } 210 211 } 212 213 void toAsciiSTLFile(STL stl, string filePath){ 214 File file; 215 file.open(filePath, "w"); 216 scope(exit) file.close(); 217 218 file.writeln("solid STLExport"); 219 220 foreach (vchunk, nchunk; zip(chunks(stl.vertices, 9), chunks(stl.normals, 3))){ 221 file.writefln("facet normal %f %f %f", nchunk[0], nchunk[1], nchunk[2]); 222 file.writeln(" outer loop"); 223 file.writefln(" vertex %f %f %f", vchunk[0], vchunk[1], vchunk[2]); 224 file.writefln(" vertex %f %f %f", vchunk[3], vchunk[4], vchunk[5]); 225 file.writefln(" vertex %f %f %f", vchunk[6], vchunk[7], vchunk[8]); 226 file.writeln(" endloop"); 227 file.writeln("endfacet"); 228 } 229 230 file.writeln("endsolid STLExport"); 231 232 } 233 234 void toOBJFile(STL stl, string filePath){ 235 File file; 236 file.open(filePath, "w"); 237 scope(exit) file.close(); 238 239 file.writeln("#"); 240 file.writeln("# object STLExport"); 241 file.writeln("#"); 242 file.write("\n"); 243 244 foreach (v; chunks(stl.vertices, 9)){ 245 file.writefln("v %f %f %f", v[0], v[1], v[2]); 246 file.writefln("v %f %f %f", v[3], v[4], v[5]); 247 file.writefln("v %f %f %f", v[6], v[7], v[8]); 248 } 249 250 file.writefln("# %u vertices", stl.vertices.length/9); 251 252 file.write("\n"); 253 254 foreach (n; chunks(stl.normals, 3)){ 255 file.writefln("vn %f %f %f", n[0], n[1], n[2]); 256 } 257 258 file.writefln("# %u vertex normals", stl.normals.length/3); 259 260 file.write("\n"); 261 262 file.writeln("g STLExport"); 263 file.writeln("s 1"); 264 265 ulong a1 = 1, a2 = 1, b1 = 2, b2 = 1, c1 = 3, c2 = 1; 266 267 foreach ( _ ; 0..stl.vertices.length/9){ 268 file.writefln("f %u//%u %u//%u %u//%u", a1, a2, b1, b2, c1, c2); 269 a1 += 3; 270 a2 += 1; 271 b1 += 3; 272 b2 += 1; 273 c1 += 3; 274 c2 += 1; 275 } 276 277 file.writefln("# %u polygons", stl.vertices.length/9); 278 }