unity/Assets/Scripts/pb_Stl/pb_Stl_Importer.cs (255 lines of code) (raw):

#pragma warning disable 0219 using UnityEngine; using System.Text; using System.Collections; using System.Collections.Generic; using System.IO; namespace Parabox.STL { /** * Import methods for STL files. */ public static class pb_Stl_Importer { const int MAX_FACETS_PER_MESH = 65535 / 3; class Facet { public Vector3 normal; public Vector3 a, b, c; public override string ToString() { return string.Format("{0:F2}: {1:F2}, {2:F2}, {3:F2}", normal, a, b, c); } } /** * Import an STL file at path. */ public static List<Mesh> Import(string path) { if (IsBinary(path)) { try { return ImportBinary(path); } catch (System.Exception e) { Logger.Error("pb_Stl_Importer::ImportBinary::Failed importing mesh at path {0} -- {1}", path, e.Message); return null; } } else { return ImportAscii(path); } } private static List<Mesh> ImportBinary(string path) { using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) { return ImportBinaryStream(fs); } } public static List<Mesh> ImportBytes(byte[] bytes) { using (MemoryStream ms = new MemoryStream(bytes)) { return ImportBinaryStream(ms); } } private static List<Mesh> ImportBinaryStream(Stream stream) { Facet[] facets; using (BinaryReader br = new BinaryReader(stream, new ASCIIEncoding())) { // read header byte[] header = br.ReadBytes(80); uint facetCount = br.ReadUInt32(); facets = new Facet[facetCount]; for (uint i = 0; i < facetCount; i++) facets[i] = br.GetFacet(); } return CreateMeshWithFacets(facets); } private static Facet GetFacet(this BinaryReader binaryReader) { Facet facet = new Facet(); facet.normal = binaryReader.GetVector3(); // maintain counter-clockwise orientation of vertices: facet.a = binaryReader.GetVector3(); facet.b = binaryReader.GetVector3(); facet.c = binaryReader.GetVector3(); if (facet.normal.magnitude < 0.01f) { facet.normal = CalculateNormal(facet.a, facet.b, facet.c); } binaryReader.ReadUInt16(); // padding return facet; } private static Vector3 CalculateNormal(Vector3 a, Vector3 b, Vector3 c) { return Vector3.Cross(b - a, c - a).normalized; } private static Vector3 GetVector3(this BinaryReader binaryReader) { Vector3 vector3 = new Vector3(); for (int i = 0; i < 3; i++) vector3[i] = binaryReader.ReadSingle(); return vector3.UnityCoordTrafo(); } private static Vector3 UnityCoordTrafo(this Vector3 vector3) { return new Vector3(vector3.x, vector3.y, vector3.z); } const int SOLID = 1; const int FACET = 2; const int OUTER = 3; const int VERTEX = 4; const int ENDLOOP = 5; const int ENDFACET = 6; const int ENDSOLID = 7; const int EMPTY = 0; private static int ReadState(string line) { if (line.StartsWith("solid")) return SOLID; else if (line.StartsWith("facet")) return FACET; else if (line.StartsWith("outer")) return OUTER; else if (line.StartsWith("vertex")) return VERTEX; else if (line.StartsWith("endloop")) return ENDLOOP; else if (line.StartsWith("endfacet")) return ENDFACET; else if (line.StartsWith("endsolid")) return ENDSOLID; else return EMPTY; } private static List<Mesh> ImportAscii(string path) { List<Facet> facets = new List<Facet>(); using (StreamReader sr = new StreamReader(path)) { string line; int state = EMPTY, vertex = 0; Facet f = null; bool exit = false; while (sr.Peek() > 0 && !exit) { line = sr.ReadLine().Trim(); int previousState = state; state = ReadState(line); switch (state) { case SOLID: continue; case FACET: f = new Facet(); f.normal = StringToVec3(line.Replace("facet normal ", "")); break; case OUTER: vertex = 0; break; case VERTEX: // maintain counter-clockwise orientation of vertices: if (vertex == 0) f.a = StringToVec3(line.Replace("vertex ", "")); else if (vertex == 2) f.c = StringToVec3(line.Replace("vertex ", "")); else if (vertex == 1) f.b = StringToVec3(line.Replace("vertex ", "")); vertex++; break; case ENDLOOP: break; case ENDFACET: facets.Add(f); break; case ENDSOLID: exit = true; break; case EMPTY: default: break; } } } return CreateMeshWithFacets(facets); } private static Vector3 StringToVec3(string str) { string[] split = str.Trim().Split(null); Vector3 v = new Vector3(); float.TryParse(split[0], out v.x); float.TryParse(split[1], out v.y); float.TryParse(split[2], out v.z); return v.UnityCoordTrafo(); } /** * Read the first 80 bytes of a file and if they are all 0x0 it's likely * that this file is binary. */ private static bool IsBinary(string path) { // http://stackoverflow.com/questions/968935/compare-binary-files-in-c-sharp FileInfo file = new FileInfo(path); if (file.Length < 130) return false; var isBinary = false; using (FileStream f0 = file.OpenRead()) { using (BufferedStream bs0 = new BufferedStream(f0)) { for (long i = 0; i < 80; i++) { var readByte = bs0.ReadByte(); if (readByte == 0x0) { isBinary = true; break; } } } } if (!isBinary) { using (FileStream f0 = file.OpenRead()) { using (BufferedStream bs0 = new BufferedStream(f0)) { var byteArray = new byte[6]; for (var i = 0; i < 6; i++) { byteArray[i] = (byte)bs0.ReadByte(); } var text = Encoding.UTF8.GetString(byteArray); isBinary = text != "solid "; } } } return isBinary; } private static int MeshesCount(int facets) { return (facets + MAX_FACETS_PER_MESH - 1) / MAX_FACETS_PER_MESH; } /** * @todo test with > USHORT_MAX vertex count meshes */ private static List<Mesh> CreateMeshWithFacets(IList<Facet> facets) { int facet_count = facets.Count, current_facet = 0, full_mesh_vertices = MAX_FACETS_PER_MESH * 3; List<Mesh> meshes = new List<Mesh>(); Vector3 center = Vector3.zero; foreach (Facet facet in facets) { center += facet.a + facet.b + facet.c; } center /= facets.Count * 3.0f; for (int i = 0; i < MeshesCount(facets.Count); i++) { int len = System.Math.Min(full_mesh_vertices, (facet_count - current_facet) * 3); Vector3[] v = new Vector3[len]; Vector3[] n = new Vector3[len]; Vector2[] uv = new Vector2[len]; int[] t = new int[len]; Dictionary<Vector3, List<Vector3>> normals = new Dictionary<Vector3, List<Vector3>>(); int facet_index = current_facet; for (int it = 0; it < len; it += 3) { v[it] = facets[facet_index].a; v[it + 1] = facets[facet_index].b; v[it + 2] = facets[facet_index].c; AddNormal(normals, facets[facet_index].a, facets[facet_index].normal.normalized); AddNormal(normals, facets[facet_index].b, facets[facet_index].normal.normalized); AddNormal(normals, facets[facet_index].c, facets[facet_index].normal.normalized); t[it] = it; t[it + 1] = it + 1; t[it + 2] = it + 2; uv[it] = GetUv(facets[facet_index].a - center); uv[it + 1] = GetUv(facets[facet_index].b - center); uv[it + 2] = GetUv(facets[facet_index].c - center); facet_index++; } facet_index = current_facet; for (int it = 0; it < len; it += 3) { n[it] = CalculateNormal(facets[facet_index].normal, normals[facets[facet_index].a]); n[it + 1] = CalculateNormal(facets[facet_index].normal, normals[facets[facet_index].b]); n[it + 2] = CalculateNormal(facets[facet_index].normal, normals[facets[facet_index].c]); facet_index++; } current_facet = facet_index; Mesh mesh = new Mesh(); mesh.vertices = v; mesh.normals = n; mesh.triangles = t; mesh.uv = uv; meshes.Add(mesh); } return meshes; } private static void AddNormal(Dictionary<Vector3, List<Vector3>> dictionary, Vector3 vertex, Vector3 normal) { if (dictionary.ContainsKey(vertex)) { dictionary[vertex].Add(normal); } else { dictionary[vertex] = new List<Vector3>(); dictionary[vertex].Add(normal); } } private static Vector3 CalculateNormal(Vector3 base_normal, List<Vector3> normal_list) { Vector3 sum = Vector3.zero; int sum_count = 0; foreach (Vector3 normal in normal_list) { if (Vector3.Angle(base_normal, normal) < 60.0) { sum += normal; sum_count++; } } return (sum / sum_count).normalized; } private static Vector2 GetUv(Vector3 dir) { return new Vector2(0.5f + Mathf.Atan2(dir.x, dir.y) / (Mathf.PI * 2.0f), 0.5f + Mathf.Acos(dir.normalized.z) / Mathf.PI); } } }