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();
}
}
}