using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using QuantumConcepts.Formats.StereoLithography; namespace QuantumConcepts.Formats.StereoLithography { /// The outer-most STL object which contains the that make up the model. public class STLDocument : IEquatable, IEnumerable { /// Defines the buffer size to use when reading from a . private const int DefaultBufferSize = 1024; /// The name of the solid. /// This property is not used for binary STLs. public string Name { get; set; } /// The list of s within this solid. public IList Facets { get; set; } /// Creates a new, empty . public STLDocument() { this.Facets = new List(); } /// Creates a new with the given and populated with the given . /// /// The name of the solid. /// This property is not used for binary STLs. /// /// The facets with which to populate this solid. public STLDocument(string name, IEnumerable facets) : this() { this.Name = name; this.Facets = facets.ToList(); } /// Writes the as text to the provided . /// The stream to which the will be written. public void WriteText(Stream stream) { using (StreamWriter writer = new StreamWriter(stream, Encoding.ASCII, DefaultBufferSize, true)) { //Write the header. writer.WriteLine(this.ToString()); //Write each facet. this.Facets.ForEach(o => o.Write(writer)); //Write the footer. writer.Write("end{0}".Interpolate(this.ToString())); } } /// Writes the as binary to the provided . /// The stream to which the will be written. public void WriteBinary(Stream stream) { using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII, true)) { byte[] header = Encoding.ASCII.GetBytes("Binary STL generated by STLdotNET. QuantumConceptsCorp.com"); byte[] headerFull = new byte[80]; Buffer.BlockCopy(header, 0, headerFull, 0, Math.Min(header.Length, headerFull.Length)); //Write the header and facet count. writer.Write(headerFull); writer.Write((UInt32)this.Facets.Count); //Write each facet. this.Facets.ForEach(o => o.Write(writer)); } } /// Writes the as text to the provided . /// The absolute path where the will be written. public void SaveAsText(string path) { if (path.IsNullOrEmpty()) throw new ArgumentNullException("path"); Directory.CreateDirectory(Path.GetDirectoryName(path)); using (Stream stream = File.Create(path)) WriteText(stream); } /// Writes the as binary to the provided . /// The absolute path where the will be written. public void SaveAsBinary(string path) { if (path.IsNullOrEmpty()) throw new ArgumentNullException("path"); Directory.CreateDirectory(Path.GetDirectoryName(path)); using (Stream stream = File.Create(path)) WriteBinary(stream); } /// Appends the provided facets to this instance's . /// An entire can be passed to this method and all of the facets which it contains will be appended to this instance. /// The facets to append. public void AppendFacets(IEnumerable facets) { foreach (Facet facet in facets) this.Facets.Add(facet); } /// Determines if the contained within the is text-based. /// The will be reset to position 0. /// The stream which contains the STL data. /// True if the is text-based, otherwise false. public static bool IsText(Stream stream) { const string solid = "solid"; byte[] buffer = new byte[5]; string header = null; //Reset the stream to tbe beginning and read the first few bytes, then reset the stream to the beginning again. stream.Seek(0, SeekOrigin.Begin); stream.Read(buffer, 0, buffer.Length); stream.Seek(0, SeekOrigin.Begin); //Read the header as ASCII. header = Encoding.ASCII.GetString(buffer); return solid.Equals(header, StringComparison.InvariantCultureIgnoreCase); } /// Determines if the contained within the is binary-based. /// The will be reset to position 0. /// The stream which contains the STL data. /// True if the is binary-based, otherwise false. public static bool IsBinary(Stream stream) { return !IsText(stream); } /// Reads the contained within the into a new . /// This method will determine how to read the (whether to read it as text or binary data). /// The stream which contains the STL data. /// Set to true to try read as binary if reading as text results in zero facets /// An representing the data contained in the stream or null if the stream is empty. public static STLDocument Read(Stream stream,bool tryBinaryIfTextFailed=false) { //Determine if the stream contains a text-based or binary-based , and then read it. var isText = IsText(stream); STLDocument textStlDocument = null; if (isText) { using (StreamReader reader = new StreamReader(stream, Encoding.ASCII, true, DefaultBufferSize, true)) { textStlDocument = Read(reader); } if (textStlDocument.Facets.Count > 0 || !tryBinaryIfTextFailed) return textStlDocument; stream.Seek(0, SeekOrigin.Begin); } //Try binary if zero Facets were read and tryBinaryIfTextFailed==true using (BinaryReader reader = new BinaryReader(stream, Encoding.ASCII, true)) { var binaryStlDocument = Read(reader); //return text reading result if binary reading also failed and tryBinaryIfTextFailed==true return (binaryStlDocument.Facets.Count>0 || !isText)?binaryStlDocument:textStlDocument; } } /// Reads the STL document contained within the into a new . /// This method expects a text-based STL document to be contained within the . /// The reader which contains the text-based STL data. /// An representing the data contained in the stream or null if the stream is empty. public static STLDocument Read(StreamReader reader) { const string regexSolid = @"solid\s+(?[^\r\n]+)?"; if (reader == null) return null; //Read the header. string header = reader.ReadLine(); Match headerMatch = Regex.Match(header, regexSolid); STLDocument stl = null; Facet currentFacet = null; //Check the header. //if (!headerMatch.Success) // throw new FormatException("Invalid STL header, expected \"solid [name]\" but found \"{0}\".".Interpolate(header)); //Create the STL and extract the name (optional). stl = new STLDocument() { Name = headerMatch.Groups["Name"].Value }; //Read each facet until the end of the stream. while ((currentFacet = Facet.Read(reader)) != null) stl.Facets.Add(currentFacet); return stl; } /// Reads the STL document contained within the parameter into a new . /// A string which contains the STL data. /// An representing the data contained in the parameter or null if the parameter is empty. public static STLDocument Read(string stl) { if (stl.IsNullOrEmpty()) return null; using (MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(stl))) return Read(stream); } /// Reads the STL document located at the into a new . /// A full path to a file which contains the STL data. /// An representing the data contained in the file located at the specified or null if the parameter is empty. public static STLDocument Open(string path) { if (path.IsNullOrEmpty()) throw new ArgumentNullException("path"); using (Stream stream = File.OpenRead(path)) return Read(stream); } /// Reads the STL document contained within the into a new . /// This method will expects a binary-based to be contained within the . /// The reader which contains the binary-based STL data. /// An representing the data contained in the stream or null if the stream is empty. public static STLDocument Read(BinaryReader reader) { if (reader == null) return null; byte[] buffer = new byte[80]; STLDocument stl = new STLDocument(); Facet currentFacet = null; //Read (and ignore) the header and number of triangles. buffer = reader.ReadBytes(80); reader.ReadBytes(4); //Read each facet until the end of the stream. Stop when the end of the stream is reached. while ((reader.BaseStream.Position != reader.BaseStream.Length) && (currentFacet = Facet.Read(reader)) != null) stl.Facets.Add(currentFacet); return stl; } /// Reads the within the as text into the . /// The stream to read from. /// The stream to read into. /// The that was copied. public static STLDocument CopyAsText(Stream inStream, Stream outStream) { STLDocument stl = Read(inStream); stl.WriteText(outStream); return stl; } /// Reads the within the as binary into the . /// The stream to read from. /// The stream to read into. /// The that was copied. public static STLDocument CopyAsBinary(Stream inStream, Stream outStream) { STLDocument stl = Read(inStream); stl.WriteBinary(outStream); return stl; } /// Returns the header representation of this . public override string ToString() { return "solid {0}".Interpolate(this.Name); } /// Determines whether or not this instance is the same as the instance. /// The to which to compare. /// True if this instance is equal to the instance. public bool Equals(STLDocument other) { return (this.Facets.Count == other.Facets.Count && this.Facets.All((i, o) => o.Equals(other.Facets[i]))); } /// Iterates through the collection. public IEnumerator GetEnumerator() { return this.Facets.GetEnumerator(); } /// Iterates through the collection. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } }