Skip to content

Commit

Permalink
Merge pull request #10 from DomCR/Stl-reader
Browse files Browse the repository at this point in the history
Stl reader
  • Loading branch information
DomCR authored Mar 29, 2022
2 parents 4bb674f + b60528b commit 8463d3b
Show file tree
Hide file tree
Showing 13 changed files with 150,562 additions and 11 deletions.
2 changes: 1 addition & 1 deletion CSUtilities
27 changes: 27 additions & 0 deletions MeshIO.STL.Tests/MeshIO.STL.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\MeshIO.STL\MeshIO.STL.csproj" />
<ProjectReference Include="..\MeshIO\MeshIO.csproj" />
</ItemGroup>

</Project>
92 changes: 92 additions & 0 deletions MeshIO.STL.Tests/StlReaderTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using MeshIO.Core;
using MeshIO.Elements;
using MeshIO.Elements.Geometries;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;

namespace MeshIO.STL.Tests
{
public class StlReaderTest
{
private const string _samplesFolder = "../../../../samples/stl";

public static readonly TheoryData<string> AsciiFiles;

public static readonly TheoryData<string> BinaryFiles;

private readonly ITestOutputHelper _output;

static StlReaderTest()
{
AsciiFiles = new TheoryData<string>();
foreach (string file in Directory.GetFiles(_samplesFolder, "*_ascii.stl"))
{
AsciiFiles.Add(file);
}

BinaryFiles = new TheoryData<string>();
foreach (string file in Directory.GetFiles(_samplesFolder, "*_binary.stl"))
{
BinaryFiles.Add(file);
}
}

public StlReaderTest(ITestOutputHelper output)
{
this._output = output;
}

[Theory]
[MemberData(nameof(BinaryFiles))]
public void IsBinaryTest(string test)
{
using (StlReader reader = new StlReader(test, onNotification))
{
Assert.True(reader.IsBinary());
}
}

[Theory]
[MemberData(nameof(AsciiFiles))]
public void IsAsciiTest(string test)
{
using (StlReader reader = new StlReader(test, onNotification))
{
Assert.False(reader.IsBinary());
}
}

[Theory]
[MemberData(nameof(AsciiFiles))]
public void ReadAsciiTest(string test)
{
this.readFile(test);
}

[Theory]
[MemberData(nameof(BinaryFiles))]
public void ReadBinaryTest(string test)
{
this.readFile(test);
}

private Mesh readFile(string path)
{
using (StlReader reader = new StlReader(path, onNotification))
{
return reader.Read();
}
}

private void onNotification(NotificationArgs e)
{
this._output.WriteLine(e.Message);
}
}
}
23 changes: 21 additions & 2 deletions MeshIO.STL/MeshIO.STL.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFrameworks>net5.0;net48;netstandard2.1</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<Authors>DomCr</Authors>
<Company>MeshIO</Company>
<Version>1.0.0</Version>
<PackageId>MeshIO.STL</PackageId>
<PackageTags>C# 3D fbx</PackageTags>
<RepositoryUrl>/~https://github.com/DomCR/MeshIO</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>/~https://github.com/DomCR/MeshIO</PackageProjectUrl>
<Copyright>Copyright (c) 2022 Albert Domenech</Copyright>
<Description>MeshIO module for stl format.</Description>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\MeshIO\MeshIO.csproj" />
</ItemGroup>

<Import Project="..\CSUtilities\CSUtilities\CSUtilities.projitems" Label="Shared" />

</Project>
10 changes: 10 additions & 0 deletions MeshIO.STL/StlException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace MeshIO.STL
{
[Serializable]
public class StlException : Exception
{
public StlException(string message) : base(message) { }
}
}
163 changes: 161 additions & 2 deletions MeshIO.STL/StlReader.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,165 @@
namespace MeshIO.STL
using CSMath;
using CSUtilities.Converters;
using CSUtilities.IO;
using MeshIO.Core;
using MeshIO.Elements.Geometries;
using MeshIO.Elements.Geometries.Layers;
using System;
using System.IO;
using System.Text.RegularExpressions;

namespace MeshIO.STL
{
public class StlReader
/// <summary>
/// Reader for STL files in ascii or binary
/// </summary>
public class StlReader : ReaderBase, IDisposable
{
private StreamIO _stream;

/// <summary>
/// Initializes a new instance of the <see cref="StlReader"/> class for the specified file.
/// </summary>
/// <param name="path">The complete file path to read to.</param>
/// <param name="onNotification"></param>
public StlReader(string path, NotificationHandler onNotification = null)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException(nameof(path));

this._stream = new StreamIO(path, FileMode.Open, FileAccess.Read);

this.OnNotification = onNotification;
}

/// <summary>
/// Initializes a new instance of the <see cref="FbxReader"/> class for the specified stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="errorLevel"></param>
public StlReader(Stream stream, NotificationHandler onNotification = null)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));

if (!stream.CanSeek)
throw new ArgumentException("The stream must support seeking. Try reading the data into a buffer first");

this._stream = new StreamIO(stream);

this.OnNotification = onNotification;
}

/// <summary>
/// Check the format of the file
/// </summary>
/// <returns>true if is binary</returns>
public bool IsBinary()
{
this._stream.Position = 0;
this._stream.ReadString(80);
int nTriangles = this._stream.ReadInt<LittleEndianConverter>();

return checkStreamLenth(nTriangles);
}

/// <summary>
/// Read the STL file
/// </summary>
/// <returns>mesh defined in the file</returns>
public Mesh Read()
{
this._stream.Position = 0;

string header = this._stream.ReadString(80);
this.OnNotification?.Invoke(new NotificationArgs(header.Replace("\0", "")));

Mesh mesh = new Mesh();
LayerElementNormal normals = new LayerElementNormal();
mesh.Layers.Add(normals);

int nTriangles = this._stream.ReadInt<LittleEndianConverter>();

if (checkStreamLenth(nTriangles))
{
for (int i = 0; i < nTriangles; i++)
{
XYZ normal = new XYZ(this._stream.ReadSingle(), this._stream.ReadSingle(), this._stream.ReadSingle());

normals.Add(normal);

XYZ v1 = new XYZ(this._stream.ReadSingle(), this._stream.ReadSingle(), this._stream.ReadSingle());
XYZ v2 = new XYZ(this._stream.ReadSingle(), this._stream.ReadSingle(), this._stream.ReadSingle());
XYZ v3 = new XYZ(this._stream.ReadSingle(), this._stream.ReadSingle(), this._stream.ReadSingle());

mesh.AddTriangles(v1, v2, v3);

ushort attByteCount = this._stream.ReadUShort();
}
}
else
{
this._stream.Position = 0;

string line = this._stream.ReadUntil('\n');
string name = Regex.Match(line, @"solid \s\n", options: RegexOptions.IgnoreCase).Value;
mesh.Name = name;

line = this._stream.ReadUntil('\n');

while (!line.Contains($"endsolid {name}"))
{
XYZ normal = readPoint(line, "facet normal");
normals.Add(normal);

this.checkLine(this._stream.ReadUntil('\n'), "outer loop");

XYZ v1 = readPoint(this._stream.ReadUntil('\n'), "vertex");
XYZ v2 = readPoint(this._stream.ReadUntil('\n'), "vertex");
XYZ v3 = readPoint(this._stream.ReadUntil('\n'), "vertex");

mesh.AddTriangles(v1, v2, v3);

this.checkLine(this._stream.ReadUntil('\n'), "endloop");
this.checkLine(this._stream.ReadUntil('\n'), "endfacet");

line = this._stream.ReadUntil('\n');
}
}

return mesh;
}

/// <inheritdoc/>
public void Dispose()
{
this._stream.Dispose();
}

private bool checkStreamLenth(int nTriangles)
{
//Compare the length of the stream to check if is ascii file
return _stream.Length == 84 + nTriangles * 50;
}

private void checkLine(string line, string match)
{
if (string.IsNullOrEmpty(match) &&
Regex.Match(line, match + @" \s\n", options: RegexOptions.IgnoreCase).Success)
{
throw new StlException($"Expected match: {match} | line: {line}");
}
}

private XYZ readPoint(string line, string match)
{
this.checkLine(line, match);

var x = Regex.Match(line, @"\d+(\.\d+)?");
var y = x.NextMatch();
var z = y.NextMatch();

return new XYZ(double.Parse(x.Value), double.Parse(y.Value), double.Parse(z.Value));
}
}
}
7 changes: 7 additions & 0 deletions MeshIO.STL/StlWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MeshIO.STL
{
public class StlWriter
{

}
}
8 changes: 8 additions & 0 deletions MeshIO.sln
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Submodules", "Submodules",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MeshIO.STL", "MeshIO.STL\MeshIO.STL.csproj", "{9EA2BA2C-39BF-451A-A2EF-1AA0B9E2EC7B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MeshIO.STL.Tests", "MeshIO.STL.Tests\MeshIO.STL.Tests.csproj", "{9C8FFB66-0E53-49E5-88F6-F77F5F70BF95}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
CSUtilities\CSMath\CSMath.projitems*{3f391f00-a310-4681-be94-15329d662a3d}*SharedItemsImports = 5
CSUtilities\CSMath\CSMath.projitems*{411b6122-5ef6-46db-a8f2-43e9c1841e4a}*SharedItemsImports = 13
CSUtilities\CSUtilities\CSUtilities.projitems*{9ea2ba2c-39bf-451a-a2ef-1aa0b9e2ec7b}*SharedItemsImports = 5
CSUtilities\CSUtilities\CSUtilities.projitems*{b4ef345d-52b9-47f1-aa2a-b4ae85f62efc}*SharedItemsImports = 13
CSUtilities\CSUtilities\CSUtilities.projitems*{d1ea3b08-85cc-4085-baa1-c47040e7e621}*SharedItemsImports = 5
EndGlobalSection
Expand Down Expand Up @@ -120,6 +123,10 @@ Global
{9EA2BA2C-39BF-451A-A2EF-1AA0B9E2EC7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9EA2BA2C-39BF-451A-A2EF-1AA0B9E2EC7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9EA2BA2C-39BF-451A-A2EF-1AA0B9E2EC7B}.Release|Any CPU.Build.0 = Release|Any CPU
{9C8FFB66-0E53-49E5-88F6-F77F5F70BF95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9C8FFB66-0E53-49E5-88F6-F77F5F70BF95}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C8FFB66-0E53-49E5-88F6-F77F5F70BF95}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C8FFB66-0E53-49E5-88F6-F77F5F70BF95}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -135,6 +142,7 @@ Global
{FF66D7A6-6DCE-41DE-B30F-492988B2DF63} = {006DAC54-BEE3-4878-900F-4E698B519501}
{60FE28C0-704E-4C4B-A2CD-929039A16F9E} = {4B0FFF9E-199B-4025-9CD2-0C8CE4ECB4C6}
{411B6122-5EF6-46DB-A8F2-43E9C1841E4A} = {D5A86352-D79D-4308-819A-2C4402CBCE96}
{9C8FFB66-0E53-49E5-88F6-F77F5F70BF95} = {4B0FFF9E-199B-4025-9CD2-0C8CE4ECB4C6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B96D2DF1-9226-4CEE-BF70-43C8318F59DC}
Expand Down
6 changes: 6 additions & 0 deletions MeshIO/Elements/Geometries/Layers/LayerElementNormal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ public LayerElementNormal() : base() { }

public LayerElementNormal(Geometry owner) : base(owner) { }

public void Add(XYZ normal, double defaulWheight = 0)
{
Normals.Add(normal);
Weights.Add(defaulWheight);
}

public void CalculateFlatNormals()
{
if (!(this.Owner is Mesh mesh))
Expand Down
Loading

0 comments on commit 8463d3b

Please sign in to comment.