Files
wg_cpso/STL/STLDocument.cs
2026-03-25 18:20:24 +08:00

313 lines
14 KiB
C#

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
{
/// <summary>The outer-most STL object which contains the <see cref="Facets"/> that make up the model.</summary>
public class STLDocument : IEquatable<STLDocument>, IEnumerable<Facet>
{
/// <summary>Defines the buffer size to use when reading from a <see cref="StreamReader"/>.</summary>
private const int DefaultBufferSize = 1024;
/// <summary>The name of the solid.</summary>
/// <remarks>This property is not used for binary STLs.</remarks>
public string Name { get; set; }
/// <summary>The list of <see cref="Facet"/>s within this solid.</summary>
public IList<Facet> Facets { get; set; }
/// <summary>Creates a new, empty <see cref="STLDocument"/>.</summary>
public STLDocument()
{
this.Facets = new List<Facet>();
}
/// <summary>Creates a new <see cref="STLDocument"/> with the given <paramref name="name"/> and populated with the given <paramref name="facets"/>.</summary>
/// <param name="name">
/// The name of the solid.
/// <remarks>This property is not used for binary STLs.</remarks>
/// </param>
/// <param name="facets">The facets with which to populate this solid.</param>
public STLDocument(string name, IEnumerable<Facet> facets)
: this()
{
this.Name = name;
this.Facets = facets.ToList();
}
/// <summary>Writes the <see cref="STLDocument"/> as text to the provided <paramref name="stream"/>.</summary>
/// <param name="stream">The stream to which the <see cref="STLDocument"/> will be written.</param>
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()));
}
}
/// <summary>Writes the <see cref="STLDocument"/> as binary to the provided <paramref name="stream"/>.</summary>
/// <param name="stream">The stream to which the <see cref="STLDocument"/> will be written.</param>
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));
}
}
/// <summary>Writes the <see cref="STLDocument"/> as text to the provided <paramref name="path"/>.</summary>
/// <param name="path">The absolute path where the <see cref="STLDocument"/> will be written.</param>
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);
}
/// <summary>Writes the <see cref="STLDocument"/> as binary to the provided <paramref name="path"/>.</summary>
/// <param name="path">The absolute path where the <see cref="STLDocument"/> will be written.</param>
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);
}
/// <summary>Appends the provided facets to this instance's <see cref="Facets"/>.</summary>
/// <remarks>An entire <see cref="STLDocument"/> can be passed to this method and all of the facets which it contains will be appended to this instance.</remarks>
/// <param name="facets">The facets to append.</param>
public void AppendFacets(IEnumerable<Facet> facets)
{
foreach (Facet facet in facets)
this.Facets.Add(facet);
}
/// <summary>Determines if the <see cref="STLDocument"/> contained within the <paramref name="stream"/> is text-based.</summary>
/// <remarks>The <paramref name="stream"/> will be reset to position 0.</remarks>
/// <param name="stream">The stream which contains the STL data.</param>
/// <returns>True if the <see cref="STLDocument"/> is text-based, otherwise false.</returns>
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);
}
/// <summary>Determines if the <see cref="STLDocument"/> contained within the <paramref name="stream"/> is binary-based.</summary>
/// <remarks>The <paramref name="stream"/> will be reset to position 0.</remarks>
/// <param name="stream">The stream which contains the STL data.</param>
/// <returns>True if the <see cref="STLDocument"/> is binary-based, otherwise false.</returns>
public static bool IsBinary(Stream stream)
{
return !IsText(stream);
}
/// <summary>Reads the <see cref="STLDocument"/> contained within the <paramref name="stream"/> into a new <see cref="STLDocument"/>.</summary>
/// <remarks>This method will determine how to read the <see cref="STLDocument"/> (whether to read it as text or binary data).</remarks>
/// <param name="stream">The stream which contains the STL data.</param>
/// <param name="tryBinaryIfTextFailed">Set to true to try read as binary if reading as text results in zero facets</param>
/// <returns>An <see cref="STLDocument"/> representing the data contained in the stream or null if the stream is empty.</returns>
public static STLDocument Read(Stream stream,bool tryBinaryIfTextFailed=false)
{
//Determine if the stream contains a text-based or binary-based <see cref="STLDocument"/>, 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;
}
}
/// <summary>Reads the STL document contained within the <paramref name="stream"/> into a new <see cref="STLDocument"/>.</summary>
/// <remarks>This method expects a text-based STL document to be contained within the <paramref name="reader"/>.</remarks>
/// <param name="reader">The reader which contains the text-based STL data.</param>
/// <returns>An <see cref="STLDocument"/> representing the data contained in the stream or null if the stream is empty.</returns>
public static STLDocument Read(StreamReader reader)
{
const string regexSolid = @"solid\s+(?<Name>[^\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;
}
/// <summary>Reads the STL document contained within the <paramref name="stl"/> parameter into a new <see cref="STLDocument"/>.</summary>
/// <param name="stl">A string which contains the STL data.</param>
/// <returns>An <see cref="STLDocument"/> representing the data contained in the <paramref name="stl"/> parameter or null if the parameter is empty.</returns>
public static STLDocument Read(string stl)
{
if (stl.IsNullOrEmpty())
return null;
using (MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(stl)))
return Read(stream);
}
/// <summary>Reads the STL document located at the <paramref name="path"/> into a new <see cref="STLDocument"/>.</summary>
/// <param name="path">A full path to a file which contains the STL data.</param>
/// <returns>An <see cref="STLDocument"/> representing the data contained in the file located at the <paramref name="path"/> specified or null if the parameter is empty.</returns>
public static STLDocument Open(string path)
{
if (path.IsNullOrEmpty())
throw new ArgumentNullException("path");
using (Stream stream = File.OpenRead(path))
return Read(stream);
}
/// <summary>Reads the STL document contained within the <paramref name="stream"/> into a new <see cref="STLDocument"/>.</summary>
/// <remarks>This method will expects a binary-based <see cref="STLDocument"/> to be contained within the <paramref name="reader"/>.</remarks>
/// <param name="reader">The reader which contains the binary-based STL data.</param>
/// <returns>An <see cref="STLDocument"/> representing the data contained in the stream or null if the stream is empty.</returns>
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;
}
/// <summary>Reads the <see cref="STLDocument"/> within the <paramref name="inStream"/> as text into the <paramref name="outStream"/>.</summary>
/// <param name="inStream">The stream to read from.</param>
/// <param name="outStream">The stream to read into.</param>
/// <returns>The <see cref="STLDocument"/> that was copied.</returns>
public static STLDocument CopyAsText(Stream inStream, Stream outStream)
{
STLDocument stl = Read(inStream);
stl.WriteText(outStream);
return stl;
}
/// <summary>Reads the <see cref="STLDocument"/> within the <paramref name="inStream"/> as binary into the <paramref name="outStream"/>.</summary>
/// <param name="inStream">The stream to read from.</param>
/// <param name="outStream">The stream to read into.</param>
/// <returns>The <see cref="STLDocument"/> that was copied.</returns>
public static STLDocument CopyAsBinary(Stream inStream, Stream outStream)
{
STLDocument stl = Read(inStream);
stl.WriteBinary(outStream);
return stl;
}
/// <summary>Returns the header representation of this <see cref="STLDocument"/>.</summary>
public override string ToString()
{
return "solid {0}".Interpolate(this.Name);
}
/// <summary>Determines whether or not this instance is the same as the <paramref name="other"/> instance.</summary>
/// <param name="other">The <see cref="STLDocument"/> to which to compare.</param>
/// <returns>True if this instance is equal to the <paramref name="other"/> instance.</returns>
public bool Equals(STLDocument other)
{
return (this.Facets.Count == other.Facets.Count
&& this.Facets.All((i, o) => o.Equals(other.Facets[i])));
}
/// <summary>Iterates through the <see cref="Facets"/> collection.</summary>
public IEnumerator<Facet> GetEnumerator()
{
return this.Facets.GetEnumerator();
}
/// <summary>Iterates through the <see cref="Facets"/> collection.</summary>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}