Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stl reader #10

Merged
merged 4 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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