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 final class STL { 22 public float[] normals; 23 public float[] vertices; 24 25 // this makes only sense for binary stl format 26 public ubyte[] header = cast(ubyte[])"##Binary STL####################################################################"; 27 28 // in the standard format, attributes should be zero because most software does not understand anything else 29 // public ushort[] attributes; // maybe implement this. 30 31 package string fname; 32 33 this(){ 34 35 } 36 37 @property 38 int numOfTriangles(){ 39 if(!vertices.length) 40 return 0; 41 return cast(int)vertices.length/9; 42 } 43 44 bool empty(){ 45 return vertices is null || !vertices.length; 46 } 47 } 48 49 STL readSTL(string filePath){ 50 import std.typecons: tuple; 51 import std.path: baseName; 52 53 auto stl = new STL(); 54 55 stl.fname = baseName(filePath, ".stl"); 56 57 if(!isBinarySTL(filePath)) 58 stl.readAsciiFile(filePath); 59 else 60 stl.readBinaryFile(filePath); 61 62 return stl; 63 } 64 65 bool isBinarySTL(S)(auto ref S filePath){ 66 67 auto file = File(filePath, "r"); 68 scope(exit) file.close(); 69 70 char[] _line; 71 file.readln(_line); 72 73 enforce(_line.length, "File is not valid!"); 74 75 if(_line[0..5] == "solid") 76 return false; 77 78 return true; 79 } 80 81 void readAsciiFile(S)(STL stl, auto ref S filePath){ 82 auto file = File(filePath, "r"); 83 scope(exit) file.close(); 84 85 float parseFloat(S)(auto ref S str){ 86 return str.to!float; 87 } 88 89 stl.normals.reserve(20); 90 stl.vertices.reserve(20*3); 91 92 while (!file.eof){ 93 char[] _line; 94 file.readln(_line); 95 _line = chomp(_line); 96 if(!_line.length) continue; 97 98 string line = assumeUnique(_line); 99 100 string[] tokens = line.strip.split!isWhite; 101 102 if(tokens[0] == "vertex"){ 103 stl.vertices ~= parseFloat(tokens[1]); 104 stl.vertices ~= parseFloat(tokens[2]); 105 stl.vertices ~= parseFloat(tokens[3]); 106 } else if(tokens[0] == "facet"){ 107 stl.normals ~= parseFloat(tokens[2]); 108 stl.normals ~= parseFloat(tokens[3]); 109 stl.normals ~= parseFloat(tokens[4]); 110 } 111 112 } 113 } 114 115 void toBinarySTLFile(STL stl, string filePath){ 116 File fwriter; 117 118 fwriter.open(filePath, "wb"); 119 scope(exit) fwriter.close(); 120 121 int numOfTri = stl.numOfTriangles; 122 123 ubyte[] sysBuf = new ubyte[80+4+(12+12+12+12+2) * numOfTri]; 124 125 sysBuf[0..80] = stl.header; 126 127 sysBuf[80..84] = (cast(ubyte*)&numOfTri)[0..int.sizeof]; 128 129 short _attr = 0; 130 131 int i; 132 foreach (vchunk, nchunk; zip(chunks(stl.vertices, 9), chunks(stl.normals, 3))){ 133 134 ubyte[50] tbuff; 135 136 tbuff[0..12] = cast(ubyte[])(nchunk[]); 137 tbuff[12..48] = cast(ubyte[])(vchunk[]); 138 139 tbuff[48..50] = (cast(ubyte*)&_attr)[0..short.sizeof]; 140 141 sysBuf[84 + i*50 .. 84 + (i+1)*50] = tbuff[]; 142 ++i; 143 } 144 145 fwriter.rawWrite(sysBuf[]); 146 } 147 148 void readBinaryFile(S)(STL stl, auto ref S filePath){ 149 File file; 150 file.open(filePath, "rb"); 151 scope(exit) file.close(); 152 153 ubyte[80] _header; 154 file.rawRead(_header[]); 155 stl.header = _header[]; 156 157 ubyte[4] _numOfTriangles; 158 file.rawRead(_numOfTriangles[]); 159 160 int numOfTriangles = *(cast(int*)_numOfTriangles[].ptr); 161 162 stl.normals = new float[numOfTriangles * 3]; 163 stl.vertices = new float[numOfTriangles * 9]; 164 165 //debug writeln(cast(string)assumeUnique( stl.header)); 166 //debug writeln(numOfTriangles); 167 168 foreach (i; 0..numOfTriangles){ 169 170 ubyte[50] tbuff; 171 172 file.rawRead(tbuff[]); 173 174 stl.normals[i*3..(i+1)*3] = cast(float[])tbuff[0..12]; 175 stl.vertices[i*9..(i+1)*9] = cast(float[])tbuff[12..48]; 176 177 const short dummy_attr = *(cast(short*)tbuff[48..50].ptr); 178 } 179 180 } 181 182 void toAsciiSTLFile(STL stl, string filePath){ 183 assert(0, "toAsciiSTLFile is not implemented yet!"); 184 }