Skip to content

Commit 6c8a9db

Browse files
committed
stl text reader
1 parent 7dbe30d commit 6c8a9db

11 files changed

Lines changed: 225 additions & 123 deletions

File tree

src/MeshIO.Tests/Common/IOTestsBase.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using MeshIO.Core;
2-
using MeshIO.Tests.TestModels;
1+
using MeshIO.Tests.TestModels;
32
using System.IO;
43
using Xunit;
54
using Xunit.Abstractions;

src/MeshIO.Tests/Entities/Primitives/BoxTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class BoxTests
1111
public void CreateDefault()
1212
{
1313
Box box = new Box();
14-
Mesh mesh = box.CreateMesh();
14+
Mesh mesh = box.ToMesh();
1515

1616
Assert.NotEmpty(mesh.Vertices);
1717
Assert.Equal(24, mesh.Vertices.Count);

src/MeshIO.Tests/Formats/Fbx/FbxWriterTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private Scene createScene()
8787
Scene scene = new Scene();
8888

8989
Node box = new Node("my_node");
90-
Mesh mesh = new Box("my_box").CreateMesh();
90+
Mesh mesh = new Box("my_box").ToMesh();
9191
box.Entities.Add(mesh);
9292

9393
scene.RootNode.Nodes.Add(box);

src/MeshIO.Tests/Formats/Stl/StlReaderTest.cs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
using MeshIO.Formats.Stl;
1+
using MeshIO.Formats;
2+
using MeshIO.Formats.Stl;
23
using MeshIO.Tests.Common;
34
using MeshIO.Tests.TestModels;
5+
using System.IO;
46
using Xunit;
57
using Xunit.Abstractions;
68

@@ -25,20 +27,14 @@ public StlReaderTest(ITestOutputHelper output) : base(output)
2527
[MemberData(nameof(StlAsciiFiles))]
2628
public void IsAsciiTest(FileModel test)
2729
{
28-
using (StlReader reader = new StlReader(test.Path))
29-
{
30-
Assert.False(reader.IsBinary());
31-
}
30+
Assert.Equal(ContentType.ASCII, StlReader.GetContentType(File.OpenRead(test.Path)));
3231
}
3332

3433
[Theory]
3534
[MemberData(nameof(StlBinaryFiles))]
3635
public void IsBinaryTest(FileModel test)
3736
{
38-
using (StlReader reader = new StlReader(test.Path))
39-
{
40-
Assert.True(reader.IsBinary());
41-
}
37+
Assert.Equal(ContentType.Binary, StlReader.GetContentType(File.OpenRead(test.Path)));
4238
}
4339

4440
[Theory]

src/MeshIO/Entities/Geometries/Mesh.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public Mesh(string name) : base(name) { }
2020
/// </summary>
2121
/// <param name="vertices"></param>
2222
/// <exception cref="ArgumentException"></exception>
23-
public void AddPolygons(params XYZ[] vertices)
23+
public void AddPolygons(params IEnumerable<XYZ> vertices)
2424
{
2525
int count = vertices.Count();
2626
if (vertices.Count() % 3 == 0)
@@ -37,7 +37,7 @@ public void AddPolygons(params XYZ[] vertices)
3737
}
3838
}
3939

40-
private void addTriangles(XYZ[] vertices)
40+
private void addTriangles(IEnumerable<XYZ> vertices)
4141
{
4242
if (this.Polygons.Any() && this.Polygons.First().GetType() != typeof(Triangle))
4343
throw new ArgumentException("This mesh is not formed by Triangles");
@@ -56,7 +56,7 @@ private void addTriangles(XYZ[] vertices)
5656
}
5757
}
5858

59-
private void addQuads(XYZ[] vertices)
59+
private void addQuads(IEnumerable<XYZ> vertices)
6060
{
6161
if (this.Polygons.Any() && this.Polygons.First().GetType() != typeof(Quad))
6262
throw new ArgumentException("This mesh is not formed by Quads");
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace MeshIO.Formats
2+
{
3+
public abstract class SceneReaderOptions
4+
{
5+
}
6+
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
namespace MeshIO.Formats.Stl
1+
using MeshIO.Entities.Geometries;
2+
using System.Collections.Generic;
3+
4+
namespace MeshIO.Formats.Stl
25
{
36
internal interface IStlStreamReader
47
{
8+
public event NotificationEventHandler OnNotification;
9+
10+
public IEnumerable<Mesh> Read();
511
}
612
}
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1-
namespace MeshIO.Formats.Stl
1+
using MeshIO.Entities.Geometries;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
5+
namespace MeshIO.Formats.Stl
26
{
3-
internal class StlBinaryStreamReader
7+
internal class StlBinaryStreamReader: IStlStreamReader
48
{
9+
public StlBinaryStreamReader(Stream stream)
10+
{
11+
}
12+
13+
public event NotificationEventHandler OnNotification;
514

15+
public IEnumerable<Mesh> Read()
16+
{
17+
throw new System.NotImplementedException();
18+
}
619
}
720
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace MeshIO.Formats.Stl
2+
{
3+
internal static class StlFileToken
4+
{
5+
public const char Comment = '#';
6+
7+
public const string EndFacet = "endfacet";
8+
9+
public const string EndLoop = "endloop";
10+
11+
public const string EndSolid = "endsolid";
12+
13+
public const string Facet = "facet";
14+
15+
public const string Loop = "loop";
16+
17+
public const string Outer = "outer";
18+
19+
public const string Solid = "solid";
20+
21+
public const string Vertex = "vertex";
22+
}
23+
}
Lines changed: 46 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
using CSMath;
2-
using CSUtilities.Converters;
3-
using CSUtilities.IO;
4-
using MeshIO.Entities.Geometries;
5-
using MeshIO.Entities.Geometries.Layers;
1+
using MeshIO.Entities.Geometries;
2+
using System;
3+
using System.Collections.Generic;
64
using System.IO;
7-
using System.Text.RegularExpressions;
5+
using System.Text;
86

97
namespace MeshIO.Formats.Stl
108
{
@@ -41,31 +39,46 @@ public StlReader(Stream stream, NotificationEventHandler notification = null)
4139
}
4240

4341
/// <summary>
44-
/// Determines whether the underlying stream contains data in binary format.
42+
/// Determines the content type of a mesh stream by inspecting its header.
4543
/// </summary>
46-
/// <remarks>This method inspects the stream from its beginning to assess whether the data is in binary format.
47-
/// The stream position is reset to the start before analysis. Calling this method will modify the stream's current
48-
/// position.</remarks>
49-
/// <returns>true if the stream is detected to be in binary format; otherwise, false.</returns>
50-
public bool IsBinary()
44+
/// <remarks>The method reads the first five bytes of the stream to identify the content type. The stream
45+
/// position is reset to its original position after the operation. The method assumes the stream contains data in a
46+
/// supported mesh format.</remarks>
47+
/// <param name="stream">The input stream containing mesh data to analyze. The stream must be readable and seekable.</param>
48+
/// <returns>A value indicating whether the mesh data in the stream is in ASCII or binary format.</returns>
49+
/// <exception cref="NullReferenceException">Thrown if <paramref name="stream"/> is null.</exception>
50+
public static MeshIO.Formats.ContentType GetContentType(Stream stream)
5151
{
52-
this._stream.Position = 0;
53-
this._stream.ReadString(80);
54-
int nTriangles = this._stream.ReadInt<LittleEndianConverter>();
52+
if (stream == null)
53+
{
54+
throw new NullReferenceException(nameof(stream));
55+
}
56+
57+
if (stream.Length < 5)
58+
{
59+
return ContentType.Binary;
60+
}
61+
62+
byte[] buffer = new byte[5];
63+
string header;
64+
65+
stream.Seek(0, SeekOrigin.Begin);
66+
stream.Read(buffer, 0, buffer.Length);
67+
stream.Seek(0, SeekOrigin.Begin);
5568

56-
return this.checkStreamLenth(nTriangles);
69+
header = Encoding.ASCII.GetString(buffer);
70+
71+
return StlFileToken.Solid.Equals(header, StringComparison.InvariantCultureIgnoreCase) ? ContentType.ASCII : ContentType.Binary;
5772
}
5873

74+
/// <inheritdoc/>
5975
public override Scene Read()
6076
{
6177
Scene scene = new Scene();
6278

63-
Mesh mesh = this.ReadAsMesh();
79+
var meshes = this.ReadMeshes();
6480

65-
Node node = new Node(mesh.Name);
66-
node.Entities.Add(mesh);
67-
68-
scene.RootNode.Nodes.Add(node);
81+
scene.RootNode.Entities.AddRange(meshes);
6982

7083
return scene;
7184
}
@@ -74,99 +87,31 @@ public override Scene Read()
7487
/// Read the STL file
7588
/// </summary>
7689
/// <returns><see cref="Mesh"/> defined in the file</returns>
77-
public Mesh ReadAsMesh()
90+
public IEnumerable<Mesh> ReadMeshes()
7891
{
7992
this._stream.Position = 0;
8093

81-
string header = this._stream.ReadString(80);
82-
this.triggerNotification(header.Replace("\0", ""), NotificationType.Information);
83-
84-
Mesh mesh = new Mesh();
85-
LayerElementNormal normals = new LayerElementNormal();
86-
mesh.Layers.Add(normals);
87-
88-
int nTriangles = this._stream.ReadInt<LittleEndianConverter>();
89-
90-
if (this.checkStreamLenth(nTriangles))
94+
IStlStreamReader reader = null;
95+
var contentType = StlReader.GetContentType(this._stream.Stream);
96+
switch (contentType)
9197
{
92-
for (int i = 0; i < nTriangles; i++)
93-
{
94-
XYZ normal = new XYZ(this._stream.ReadSingle(), this._stream.ReadSingle(), this._stream.ReadSingle());
95-
96-
normals.Add(normal);
97-
98-
XYZ v1 = new XYZ(this._stream.ReadSingle(), this._stream.ReadSingle(), this._stream.ReadSingle());
99-
XYZ v2 = new XYZ(this._stream.ReadSingle(), this._stream.ReadSingle(), this._stream.ReadSingle());
100-
XYZ v3 = new XYZ(this._stream.ReadSingle(), this._stream.ReadSingle(), this._stream.ReadSingle());
101-
102-
mesh.AddPolygons(v1, v2, v3);
103-
104-
ushort attByteCount = this._stream.ReadUShort();
105-
}
98+
case ContentType.Binary:
99+
reader = new StlBinaryStreamReader(this._stream.Stream);
100+
break;
101+
case ContentType.ASCII:
102+
reader = new StlTextStreamReader(this._stream.Stream);
103+
break;
106104
}
107-
else
108-
{
109-
this._stream.Position = 0;
110-
111-
string line = this._stream.ReadUntil('\n');
112-
string name = Regex.Match(line, @"solid \s\n", options: RegexOptions.IgnoreCase).Value;
113-
mesh.Name = name;
114105

115-
line = this._stream.ReadUntil('\n');
106+
reader.OnNotification += this.onNotificationEvent;
116107

117-
while (!line.Contains($"endsolid {name}"))
118-
{
119-
XYZ normal = this.readPoint(line, "facet normal");
120-
normals.Add(normal);
121-
122-
this.checkLine(this._stream.ReadUntil('\n'), "outer loop");
123-
124-
XYZ v1 = this.readPoint(this._stream.ReadUntil('\n'), "vertex");
125-
XYZ v2 = this.readPoint(this._stream.ReadUntil('\n'), "vertex");
126-
XYZ v3 = this.readPoint(this._stream.ReadUntil('\n'), "vertex");
127-
128-
mesh.AddPolygons(v1, v2, v3);
129-
130-
this.checkLine(this._stream.ReadUntil('\n'), "endloop");
131-
this.checkLine(this._stream.ReadUntil('\n'), "endfacet");
132-
133-
line = this._stream.ReadUntil('\n');
134-
}
135-
}
136-
137-
return mesh;
108+
return reader.Read();
138109
}
139110

140111
/// <inheritdoc/>
141112
public override void Dispose()
142113
{
143114
this._stream.Dispose();
144115
}
145-
146-
private bool checkStreamLenth(int nTriangles)
147-
{
148-
//Compare the length of the stream to check if is ascii file
149-
return this._stream.Length == 84 + nTriangles * 50;
150-
}
151-
152-
private void checkLine(string line, string match)
153-
{
154-
if (string.IsNullOrEmpty(match) &&
155-
Regex.Match(line, match + @" \s\n", options: RegexOptions.IgnoreCase).Success)
156-
{
157-
throw new StlException($"Expected match: {match} | line: {line}");
158-
}
159-
}
160-
161-
private XYZ readPoint(string line, string match)
162-
{
163-
this.checkLine(line, match);
164-
165-
var x = Regex.Match(line, @"\d+(\.\d+)?");
166-
var y = x.NextMatch();
167-
var z = y.NextMatch();
168-
169-
return new XYZ(double.Parse(x.Value), double.Parse(y.Value), double.Parse(z.Value));
170-
}
171116
}
172117
}

0 commit comments

Comments
 (0)