Skip to content

Commit

Permalink
Merge pull request #38 from DomCR/obj-reader
Browse files Browse the repository at this point in the history
Obj Reader
DomCR authored Feb 13, 2024
2 parents 414c6f3 + 329286d commit 20edef4
Showing 26 changed files with 725 additions and 102 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -360,3 +360,5 @@ MigrationBackup/
!MeshIO.OBJ
/local
/src/Tests/outFiles
!src/Tests/inFiles/obj/
!src/Tests/inFiles/obj/*.obj
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -30,6 +30,13 @@ For more [information](https://github.com/DomCR/MeshIO/tree/master/src/MeshIO.ST

For more [information](https://github.com/DomCR/MeshIO/tree/master/src/MeshIO.GLTF).

### MeshIO.OBJ

- Read Wavefront OBJ files

For more [information](https://github.com/DomCR/MeshIO/tree/master/src/MeshIO.OBJ).


Contributing
------------

27 changes: 5 additions & 22 deletions src/MeshIO.FBX/FbxReader.cs
Original file line number Diff line number Diff line change
@@ -10,28 +10,17 @@ public class FbxReader : ReaderBase
{
public FbxReaderOptions Options { get; } = new FbxReaderOptions();

private Stream _stream;

/// <summary>
/// Initializes a new instance of the <see cref="FbxReader"/> class for the specified file.
/// </summary>
/// <param name="path">The complete file path to read to.</param>
public FbxReader(string path) : this(File.OpenRead(path)) { }
/// <param name="path">The complete file path to read from</param>
public FbxReader(string path) : base(File.OpenRead(path)) { }

/// <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>
public FbxReader(Stream stream)
{
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 = stream;
}
/// <param name="stream">The stream to read from</param>
public FbxReader(Stream stream) : base(stream) { }

/// <summary>
/// Read a fbx file into an scene
@@ -76,7 +65,7 @@ public FbxRootNode Parse()
/// <summary>
/// Read the FBX file
/// </summary>
public Scene Read()
public override Scene Read()
{
FbxRootNode root = this.Parse();
var reader = FbxFileBuilderBase.Create(root, this.Options);
@@ -85,12 +74,6 @@ public Scene Read()
return reader.Read();
}

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

private static IFbxParser getParser(Stream stream, FbxReaderOptions options)
{
IFbxParser parser = null;
10 changes: 5 additions & 5 deletions src/MeshIO.FBX/Templates/FbxMeshTemplate.cs
Original file line number Diff line number Diff line change
@@ -137,10 +137,10 @@ private void readPolygons()

protected List<Polygon> mapPolygons(int[] arr)
{
List<Polygon> Polygons = new List<Polygon>();
List<Polygon> polygons = new List<Polygon>();

if (arr == null)
return Polygons;
return polygons;

//Check if the arr are faces or quads
if (arr[2] < 0)
@@ -153,7 +153,7 @@ protected List<Polygon> mapPolygons(int[] arr)
//Substract a unit to the last
Math.Abs(arr[i]) - 1);

Polygons.Add(tmp);
polygons.Add(tmp);
}
}
//Quads
@@ -168,11 +168,11 @@ protected List<Polygon> mapPolygons(int[] arr)
//Substract a unit to the last
Math.Abs(arr[i]) - 1);

Polygons.Add(tmp);
polygons.Add(tmp);
}
}

return Polygons;
return polygons;
}

protected int[] polygonsArray(Mesh mesh)
38 changes: 16 additions & 22 deletions src/MeshIO.GLTF/GltfReader.cs
Original file line number Diff line number Diff line change
@@ -13,47 +13,42 @@ public class GltfReader : ReaderBase
{
private GlbHeader _header;
private GltfRoot _root;
private readonly StreamIO _stream;
private StreamIO _binaryStream;

private readonly StreamIO _streamIO;

public GltfReader(string path) : this(new FileStream(path, FileMode.Open)) { }

public GltfReader(Stream stream)
public GltfReader(Stream stream) : base(stream)
{
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._streamIO = new StreamIO(this._stream);
}

/// <summary>
/// Read the GLTF file
/// </summary>
public Scene Read()
public override Scene Read()
{
//The 12-byte header consists of three 4-byte entries:
this._header = new GlbHeader();
//magic equals 0x46546C67. It is ASCII string glTF, and can be used to identify data as Binary glTF.
this._header.Magic = this._stream.ReadUInt<LittleEndianConverter>();
this._header.Magic = this._streamIO.ReadUInt<LittleEndianConverter>();
//version indicates the version of the Binary glTF container format. This specification defines version 2.
this._header.Version = this._stream.ReadUInt<LittleEndianConverter>();
this._header.Version = this._streamIO.ReadUInt<LittleEndianConverter>();
//length is the total length of the Binary glTF, including Header and all Chunks, in bytes.
this._header.Length = this._stream.ReadUInt<LittleEndianConverter>();
this._header.Length = this._streamIO.ReadUInt<LittleEndianConverter>();

if (this._header.Version != 2)
throw new NotSupportedException($"Version {this._header.Version} not supported");

//Chunk 0 Json
uint jsonChunkLength = this._stream.ReadUInt<LittleEndianConverter>();
string jsonChunkType = this._stream.ReadString(4);
uint jsonChunkLength = this._streamIO.ReadUInt<LittleEndianConverter>();
string jsonChunkType = this._streamIO.ReadString(4);

if (jsonChunkType != "JSON")
throw new GltfReaderException("Chunk type does not match", this._stream.Position);
throw new GltfReaderException("Chunk type does not match", this._streamIO.Position);

string json = this._stream.ReadString((int)jsonChunkLength);
string json = this._streamIO.ReadString((int)jsonChunkLength);

#if NETFRAMEWORK || NETSTANDARD
_root = Newtonsoft.Json.JsonConvert.DeserializeObject<GltfRoot>(json);
@@ -62,14 +57,14 @@ public Scene Read()
#endif

//Chunk 1 bin
uint binChunkLength = this._stream.ReadUInt<LittleEndianConverter>();
string binChunkType = this._stream.ReadString(4);
uint binChunkLength = this._streamIO.ReadUInt<LittleEndianConverter>();
string binChunkType = this._streamIO.ReadString(4);

//Check the chunk type
if (binChunkType != "BIN\0")
throw new GltfReaderException("Chunk type does not match", this._stream.Position);
throw new GltfReaderException("Chunk type does not match", this._streamIO.Position);

byte[] binChunk = this._stream.ReadBytes((int)binChunkLength);
byte[] binChunk = this._streamIO.ReadBytes((int)binChunkLength);
this._binaryStream = new StreamIO(binChunk);

var reader = GltfBinaryReaderBase.GetBynaryReader((int)this._header.Version, this._root, binChunk);
@@ -81,7 +76,6 @@ public Scene Read()
/// <inheritdoc/>
public override void Dispose()
{
this._stream.Dispose();
this._binaryStream?.Dispose();
}
}
27 changes: 27 additions & 0 deletions src/MeshIO.OBJ.Tests/MeshIO.OBJ.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.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.OBJ\MeshIO.OBJ.csproj" />
</ItemGroup>

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

</Project>
38 changes: 38 additions & 0 deletions src/MeshIO.OBJ.Tests/ObjReaderTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using MeshIO.Tests.Shared;
using System.IO;
using Xunit;
using Xunit.Abstractions;

namespace MeshIO.OBJ.Tests
{
public class ObjReaderTest : IOTestsBase
{
public static readonly TheoryData<string> Files;

static ObjReaderTest()
{
Files = new TheoryData<string>();
foreach (string file in Directory.GetFiles(FolderPath.InFilesObj, "*.obj"))
{
Files.Add(file);
}
}

public ObjReaderTest(ITestOutputHelper output) : base(output) { }

[Theory]
[MemberData(nameof(Files))]
public void ReadTest(string test)
{
Scene scene = null;
using (ObjReader reader = new ObjReader(test))
{
reader.OnNotification += this.onNotification;
scene = reader.Read();
}

Assert.NotNull(scene);
Assert.NotEmpty(scene.RootNode.Nodes);
}
}
}
3 changes: 3 additions & 0 deletions src/MeshIO.OBJ/MeshIO.OBJ.csproj
Original file line number Diff line number Diff line change
@@ -2,6 +2,9 @@

<PropertyGroup>
<TargetFrameworks>net6.0;net5.0;net48;netstandard2.1</TargetFrameworks>
<PackageId>MeshIO.OBJ</PackageId>
<PackageTags>C# 3D obj</PackageTags>
<Description>MeshIO module for obj format.</Description>
</PropertyGroup>

<ItemGroup>
52 changes: 52 additions & 0 deletions src/MeshIO.OBJ/ObjData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using CSMath;
using System.Collections.Generic;

namespace MeshIO.OBJ
{
internal class ObjData
{
public ObjTemplate Current { get; private set; }

public ObjTemplate Placeholder { get; private set; }

public List<ObjTemplate> Templates { get; private set; } = [];

public ObjData()
{
Placeholder = new ObjTemplate(string.Empty);
}

public void CreateIndexer(string line)
{
this.MoveNext();

this.Current = new ObjTemplate(line);
}

public void MoveNext()
{
if (this.Current == null)
{
return;
}

this.Current.Vertices.AddRange(this.Placeholder.Vertices);
this.Current.Normals.AddRange(this.Placeholder.Normals);
this.Current.UVs.AddRange(this.Placeholder.UVs);

this.Current.MeshPolygons.AddRange(this.Placeholder.MeshPolygons);
this.Current.TexturePolygons.AddRange(this.Placeholder.TexturePolygons);
this.Current.NormalPolygons.AddRange(this.Placeholder.NormalPolygons);

this.Templates.Add(this.Current);

this.Placeholder.Vertices.Clear();
this.Placeholder.Normals.Clear();
this.Placeholder.UVs.Clear();

this.Placeholder.MeshPolygons.Clear();
this.Placeholder.TexturePolygons.Clear();
this.Placeholder.NormalPolygons.Clear();
}
}
}
34 changes: 34 additions & 0 deletions src/MeshIO.OBJ/ObjFileParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace MeshIO.OBJ
{
internal class ObjFileParser
{
public static bool ParseToken(string text, out ObjFileToken token)
{
token = ObjFileToken.Undefined;

switch (text.ToLower())
{
case "o":
token = ObjFileToken.Object;
return true;
case "v":
token = ObjFileToken.Vertice;
return true;
case "f":
token = ObjFileToken.Face;
return true;
case "vn":
token = ObjFileToken.Normal;
return true;
case "vt":
token = ObjFileToken.TextureVertice;
return true;
case "#":
token = ObjFileToken.Comment;
return true;
default:
return false;
}
}
}
}
31 changes: 31 additions & 0 deletions src/MeshIO.OBJ/ObjFileToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace MeshIO.OBJ
{
internal enum ObjFileToken
{
Undefined = 0,
/// <summary>
/// o
/// </summary>
Object,
/// <summary>
/// v
/// </summary>
Vertice,
/// <summary>
/// vt
/// </summary>
TextureVertice,
/// <summary>
/// vn
/// </summary>
Normal,
/// <summary>
/// f
/// </summary>
Face,
/// <summary>
/// #
/// </summary>
Comment
}
}
217 changes: 210 additions & 7 deletions src/MeshIO.OBJ/ObjReader.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,231 @@
using MeshIO.Core;
using CSMath;
using CSUtilities.Extensions;
using MeshIO.Core;
using MeshIO.Entities.Geometries;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace MeshIO.OBJ
{
public class ObjReader : ReaderBase
{
public ObjReader()
private readonly Regex _matchNoneWhiteSpaces;
private readonly StreamReader _reader;

/// <summary>
/// Initializes a new instance of the <see cref="ObjReader"/> class for the specified file.
/// </summary>
/// <param name="path">The complete file path to read from</param>
public ObjReader(string path) : this(File.OpenRead(path)) { }

/// <summary>
/// Initializes a new instance of the <see cref="ObjReader"/> class for the specified stream.
/// </summary>
/// <param name="stream">The stream to read from</param>
public ObjReader(Stream stream) : base(stream)
{
_reader = new StreamReader(stream);
_matchNoneWhiteSpaces = new Regex(@"\s+", RegexOptions.Compiled);
}

/// <summary>
/// Read the Obj file
/// </summary>
public override Scene Read()
{
ObjData data = new ObjData();
Scene scene = new Scene();

while (!_reader.EndOfStream)
{
string line = _reader.ReadLine();
if (string.IsNullOrEmpty(line) || !this.processLine(line, out ObjFileToken token, out string values))
{
continue;
}

switch (token)
{
case ObjFileToken.Object:
data.CreateIndexer(values);
break;
case ObjFileToken.Vertice:
data.Placeholder.Vertices.Add(this.parseVertex(values));
break;
case ObjFileToken.Normal:
data.Placeholder.Normals.Add(this.parseNormal(values));
break;
case ObjFileToken.TextureVertice:
data.Placeholder.UVs.Add(this.parse<XYZ>(values));
break;
case ObjFileToken.Face:
this.parseFace(values, data);
break;
}
}

data.MoveNext();
this.processData(data, scene);

return scene;
}

private void processData(ObjData data, Scene scene)
{
foreach (ObjTemplate item in data.Templates)
{
Mesh mesh = item.CreateMesh();
Node node = new Node(item.Name);
node.Entities.Add(mesh);

scene.RootNode.Nodes.Add(node);
}
}

public void Read()
private T parse<T>(string line)
where T : IVector, new()
{
T v = new T();
string[] arr = (string[])line.Split(' ');

int i;
for (i = 0; i < arr.Length; i++)
{
v[i] = double.Parse(arr[i]);
}

if (arr.Length < v.Dimension)
{
v[i] = 1.0d;
}

return v;
}

private void parseFace(string line, ObjData objdata)
{
string[] data = line.Split(' ');
List<int> vertices = new();
List<int> textures = new();
List<int> normals = new();

foreach (string item in data)
{
List<string> indices = item.Split('/').ToList();

//vertex_index/texture_index/normal_index
vertices.Add(int.Parse(indices[0]));

if (indices.TryGet(1, out string texture))
{
textures.Add(int.Parse(texture));
}

if (indices.TryGet(2, out string normal))
{
normals.Add(int.Parse(normal));
}
}

objdata.Placeholder.MeshPolygons.Add(createPolygon(vertices));
objdata.Placeholder.TexturePolygons.Add(createPolygon(textures));
objdata.Placeholder.NormalPolygons.Add(createPolygon(normals));
}

public override void Dispose()
protected Polygon createPolygon(List<int> arr)
{
throw new NotImplementedException();
//Check if the arr are faces or quads
if (arr.Count % 3 == 0)
{
return new Triangle(arr[0], arr[1], arr[2]);
}
//Quads
else if (arr.Count % 4 == 0)
{
return new Triangle(arr[0], arr[1], arr[2]);
}
else
{
throw new ArgumentException();
}
}

private XYZM parseVertex(string line)
{
XYZM v = new XYZM();
string[] arr = (string[])line.Split(' ');

v.X = double.Parse(arr[0]);
v.Y = double.Parse(arr[1]);
v.Z = double.Parse(arr[2]);

if (arr.Length == 4)
{
v.M = double.Parse(arr[3]);
}
else
{
v.M = 1.0d;
}

return v;
}

private XYZ parseNormal(string line)
{
XYZ v = new XYZ();
string[] arr = (string[])line.Split(' ');

v.X = double.Parse(arr[0]);
v.Y = double.Parse(arr[1]);
v.Z = double.Parse(arr[2]);

return v;
}

private bool processLine(string line, out ObjFileToken token, out string values)
{
token = ObjFileToken.Undefined;
values = string.Empty;
if (line == null)
{
return false;
}

line = _matchNoneWhiteSpaces.Replace(line, " ").Trim();

if (this.isComment(line))
{
return false;
}

string strToken = string.Empty;
int indexOfSpace = line.IndexOf(' ');
if (indexOfSpace == -1)
{
strToken = line;
}
else
{
strToken = line.Substring(0, indexOfSpace);
values = line.Substring(indexOfSpace + 1);
}

if (!ObjFileParser.ParseToken(strToken, out token))
{
this.triggerNotification($"[{nameof(ObjReader)}] Unknown token: {strToken}", NotificationType.Warning);
return false;
}

return true;
}

private bool isComment(string line)
{
return line.StartsWith("#");
}
}
}
59 changes: 59 additions & 0 deletions src/MeshIO.OBJ/ObjTemplate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using CSMath;
using MeshIO.Entities.Geometries;
using MeshIO.Entities.Geometries.Layers;
using System;
using System.Collections.Generic;
using System.Linq;

namespace MeshIO.OBJ
{
internal class ObjTemplate
{
public string Name { get; set; }

public List<XYZM> Vertices { get; } = [];

public List<XYZ> Normals { get; } = [];

public List<XYZ> UVs { get; } = [];

public List<Polygon> MeshPolygons { get; } = [];

public List<Polygon> TexturePolygons { get; } = [];

public List<Polygon> NormalPolygons { get; } = [];

public ObjTemplate(string name)
{
this.Name = name;
}

public Mesh CreateMesh()
{
Mesh mesh = new Mesh();

mesh.Vertices.AddRange(Vertices.Select(v => v.Convert<XYZ>()));

if (Normals.Any())
{
LayerElementNormal normals = new LayerElementNormal(MappingMode.ByPolygonVertex, ReferenceMode.IndexToDirect);
normals.AddRange(this.Normals);
mesh.Layers.Add(normals);
}

if (UVs.Any())
{
LayerElementUV uv = new LayerElementUV(MappingMode.ByPolygonVertex, ReferenceMode.IndexToDirect);
uv.AddRange(this.UVs.Select(xy => xy.Convert<XY>()));
mesh.Layers.Add(uv);
}

if (MeshPolygons.Any())
{
mesh.Polygons.AddRange(MeshPolygons);
}

return mesh;
}
}
}
6 changes: 6 additions & 0 deletions src/MeshIO.OBJ/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# MeshIO.OBJ

Module to read Wavefront OBJ files.

## Features

- Read **OBJ** files into a **Mesh**
2 changes: 1 addition & 1 deletion src/MeshIO.STL.Tests/StlReaderTest.cs
Original file line number Diff line number Diff line change
@@ -63,7 +63,7 @@ public void ReadBinaryTest(string test)
this.readFile(test);
}

private Mesh readFile(string path)
private Scene readFile(string path)
{
using (StlReader reader = new StlReader(path))
{
83 changes: 43 additions & 40 deletions src/MeshIO.STL/StlReader.cs
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
using MeshIO.Core;
using MeshIO.Entities.Geometries;
using MeshIO.Entities.Geometries.Layers;
using System;
using System.IO;
using System.Text.RegularExpressions;

@@ -15,33 +14,23 @@ namespace MeshIO.STL
/// </summary>
public class StlReader : ReaderBase
{
private StreamIO _stream;
private StreamIO _streamIO;

/// <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>
public StlReader(string path)
public StlReader(string path) : this(new FileStream(path, FileMode.Open))
{
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException(nameof(path));

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

/// <summary>
/// Initializes a new instance of the <see cref="StlReader"/> class for the specified stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
public StlReader(Stream stream)
public StlReader(Stream stream) : base(stream)
{
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._streamIO = new StreamIO(this._stream);
}

/// <summary>
@@ -50,74 +39,88 @@ public StlReader(Stream stream)
/// <returns>true if is binary</returns>
public bool IsBinary()
{
this._stream.Position = 0;
this._stream.ReadString(80);
int nTriangles = this._stream.ReadInt<LittleEndianConverter>();
this._streamIO.Position = 0;
this._streamIO.ReadString(80);
int nTriangles = this._streamIO.ReadInt<LittleEndianConverter>();

return this.checkStreamLenth(nTriangles);
}

public override Scene Read()
{
Scene scene = new Scene();

Mesh mesh = this.ReadAsMesh();

Node node = new Node(mesh.Name);
node.Entities.Add(mesh);

scene.RootNode.Nodes.Add(node);

return scene;
}

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

string header = this._stream.ReadString(80);
string header = this._streamIO.ReadString(80);
this.triggerNotification(header.Replace("\0", ""), NotificationType.Information);

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

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

if (this.checkStreamLenth(nTriangles))
{
for (int i = 0; i < nTriangles; i++)
{
XYZ normal = new XYZ(this._stream.ReadSingle(), this._stream.ReadSingle(), this._stream.ReadSingle());
XYZ normal = new XYZ(this._streamIO.ReadSingle(), this._streamIO.ReadSingle(), this._streamIO.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());
XYZ v1 = new XYZ(this._streamIO.ReadSingle(), this._streamIO.ReadSingle(), this._streamIO.ReadSingle());
XYZ v2 = new XYZ(this._streamIO.ReadSingle(), this._streamIO.ReadSingle(), this._streamIO.ReadSingle());
XYZ v3 = new XYZ(this._streamIO.ReadSingle(), this._streamIO.ReadSingle(), this._streamIO.ReadSingle());

mesh.AddPolygons(v1, v2, v3);

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

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

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

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

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

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

mesh.AddPolygons(v1, v2, v3);

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

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

@@ -127,13 +130,13 @@ public Mesh Read()
/// <inheritdoc/>
public override void Dispose()
{
this._stream.Dispose();
this._streamIO.Dispose();
}

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

private void checkLine(string line, string match)
4 changes: 4 additions & 0 deletions src/MeshIO.Tests.Shared/FolderPath.cs
Original file line number Diff line number Diff line change
@@ -8,6 +8,9 @@ public static class FolderPath
public static readonly string InFilesFbx = Path.Combine(InFiles, "fbx");
public static readonly string OutFilesFbx = Path.Combine(OutFiles, "fbx");

public static readonly string InFilesObj = Path.Combine(InFiles, "obj");
public static readonly string OutFilesObj = Path.Combine(OutFiles, "obj");

public static readonly string InFilesStl = Path.Combine(InFiles, "stl");
public static readonly string OutFilesStl = Path.Combine(OutFiles, "stl");

@@ -20,6 +23,7 @@ static FolderPath()
{
OutFiles,
OutFilesFbx,
OutFilesObj,
OutFilesStl,
OutFilesGltf,
};
8 changes: 8 additions & 0 deletions src/MeshIO.sln
Original file line number Diff line number Diff line change
@@ -46,6 +46,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "MeshIO.Tests.Shared", "Mesh
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MeshIO.GLTF.Tests", "MeshIO.GLTF.Tests\MeshIO.GLTF.Tests.csproj", "{A6F2252F-C802-4317-BF05-073F31397AA1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MeshIO.OBJ.Tests", "MeshIO.OBJ.Tests\MeshIO.OBJ.Tests.csproj", "{F5D9C9C7-0C3D-405B-A2AD-20E36557B2E7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -88,6 +90,10 @@ Global
{A6F2252F-C802-4317-BF05-073F31397AA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6F2252F-C802-4317-BF05-073F31397AA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6F2252F-C802-4317-BF05-073F31397AA1}.Release|Any CPU.Build.0 = Release|Any CPU
{F5D9C9C7-0C3D-405B-A2AD-20E36557B2E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F5D9C9C7-0C3D-405B-A2AD-20E36557B2E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5D9C9C7-0C3D-405B-A2AD-20E36557B2E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F5D9C9C7-0C3D-405B-A2AD-20E36557B2E7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -101,6 +107,7 @@ Global
{94C45D19-8231-437F-AB4D-02FA072A923C} = {4B0FFF9E-199B-4025-9CD2-0C8CE4ECB4C6}
{F82685A4-99C9-4B22-B9A9-C8897C9965C4} = {4B0FFF9E-199B-4025-9CD2-0C8CE4ECB4C6}
{A6F2252F-C802-4317-BF05-073F31397AA1} = {4B0FFF9E-199B-4025-9CD2-0C8CE4ECB4C6}
{F5D9C9C7-0C3D-405B-A2AD-20E36557B2E7} = {4B0FFF9E-199B-4025-9CD2-0C8CE4ECB4C6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B96D2DF1-9226-4CEE-BF70-43C8318F59DC}
@@ -114,6 +121,7 @@ Global
MeshIO.Tests.Shared\MeshIO.Tests.Shared.projitems*{94c45d19-8231-437f-ab4d-02fa072a923c}*SharedItemsImports = 5
MeshIO.Tests.Shared\MeshIO.Tests.Shared.projitems*{a6f2252f-c802-4317-bf05-073f31397aa1}*SharedItemsImports = 5
CSUtilities\CSUtilities\CSUtilities.projitems*{ad858f2b-04a1-41dd-bb3b-dde4804cd2e6}*SharedItemsImports = 13
MeshIO.Tests.Shared\MeshIO.Tests.Shared.projitems*{f5d9c9c7-0c3d-405b-a2ad-20e36557b2e7}*SharedItemsImports = 5
MeshIO.Tests.Shared\MeshIO.Tests.Shared.projitems*{f82685a4-99c9-4b22-b9a9-c8897c9965c4}*SharedItemsImports = 13
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions src/MeshIO/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -20,3 +20,4 @@
[assembly: InternalsVisibleTo("MeshIO.FBX")]
[assembly: InternalsVisibleTo("MeshIO.GLTF")]
[assembly: InternalsVisibleTo("MeshIO.STL")]
[assembly: InternalsVisibleTo("MeshIO.OBJ")]
21 changes: 20 additions & 1 deletion src/MeshIO/Core/ReaderBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO;

namespace MeshIO.Core
{
@@ -8,8 +9,26 @@ public abstract class ReaderBase : IDisposable
{
public event NotificationEventHandler OnNotification;

protected readonly Stream _stream;

protected ReaderBase(Stream stream)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));

if (!stream.CanSeek)
throw new ArgumentException("The stream must support seeking");

this._stream = stream;
}

public abstract Scene Read();

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

protected void triggerNotification(string message, NotificationType notificationType, Exception ex = null)
{
3 changes: 1 addition & 2 deletions src/MeshIO/Entities/Geometries/Layers/LayerElement.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;

namespace MeshIO.Entities.Geometries.Layers
{
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ public void AddRange(IEnumerable<XYZ> normals, double wheight = 0)

public void CalculateFlatNormals()
{
if (!(this.Owner is Mesh mesh))
if (this.Owner is not Mesh mesh)
throw new InvalidOperationException();

this.Normals.Clear();
86 changes: 86 additions & 0 deletions src/Tests/inFiles/obj/model.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# This is a title comment
o Box
v 0.5 0.5 0.5
v 0.5 0.5 -0.5
v 0.5 -0.5 0.5
v 0.5 -0.5 -0.5
v -0.5 0.5 -0.5
v -0.5 0.5 0.5
v -0.5 -0.5 -0.5
v -0.5 -0.5 0.5
v -0.5 0.5 -0.5
v 0.5 0.5 -0.5
v -0.5 0.5 0.5
v 0.5 0.5 0.5
v -0.5 -0.5 0.5
v 0.5 -0.5 0.5
v -0.5 -0.5 -0.5
v 0.5 -0.5 -0.5
v -0.5 0.5 0.5
v 0.5 0.5 0.5
v -0.5 -0.5 0.5
v 0.5 -0.5 0.5
v 0.5 0.5 -0.5
v -0.5 0.5 -0.5
v 0.5 -0.5 -0.5
v -0.5 -0.5 -0.5
vt 0 1
vt 1 1
vt 0 0
vt 1 0
vt 0 1
vt 1 1
vt 0 0
vt 1 0
vt 0 1
vt 1 1
vt 0 0
vt 1 0
vt 0 1
vt 1 1
vt 0 0
vt 1 0
vt 0 1
vt 1 1
vt 0 0
vt 1 0
vt 0 1
vt 1 1
vt 0 0
vt 1 0
vn 1 0 0
vn 1 0 0
vn 1 0 0
vn 1 0 0
vn -1 0 0
vn -1 0 0
vn -1 0 0
vn -1 0 0
vn 0 1 0
vn 0 1 0
vn 0 1 0
vn 0 1 0
vn 0 -1 0
vn 0 -1 0
vn 0 -1 0
vn 0 -1 0
vn 0 0 1
vn 0 0 1
vn 0 0 1
vn 0 0 1
vn 0 0 -1
vn 0 0 -1
vn 0 0 -1
vn 0 0 -1
f 1/1/1 3/3/3 2/2/2
f 3/3/3 4/4/4 2/2/2
f 5/5/5 7/7/7 6/6/6
f 7/7/7 8/8/8 6/6/6
f 9/9/9 11/11/11 10/10/10
f 11/11/11 12/12/12 10/10/10
f 13/13/13 15/15/15 14/14/14
f 15/15/15 16/16/16 14/14/14
f 17/17/17 19/19/19 18/18/18
f 19/19/19 20/20/20 18/18/18
f 21/21/21 23/23/23 22/22/22
f 23/23/23 24/24/24 22/22/22
12 changes: 12 additions & 0 deletions src/Tests/inFiles/obj/sample_basic_box.mtl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 3ds Max Wavefront OBJ Exporter v0.99 - (c)2007 guruware
# File Created: 04.04.2023 20:09:44

newmtl wire_008061138
Ns 32
d 1
Tr 0
Tf 1 1 1
illum 2
Ka 0.0314 0.2392 0.5412
Kd 0.0314 0.2392 0.5412
Ks 0.3500 0.3500 0.3500
52 changes: 52 additions & 0 deletions src/Tests/inFiles/obj/sample_basic_box.obj
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 3ds Max Wavefront OBJ Exporter v0.99 - (c)2007 guruware
# File Created: 04.04.2023 20:09:44

mtllib sample_basic_box.mtl

#
# object Box001
#

v -5.0000 0.0000 5.0000
v -5.0000 0.0000 -5.0000
v 5.0000 0.0000 -5.0000
v 5.0000 0.0000 5.0000
v -5.0000 10.0000 5.0000
v 5.0000 10.0000 5.0000
v 5.0000 10.0000 -5.0000
v -5.0000 10.0000 -5.0000
# 8 vertices

vn -0.5774 -0.5774 0.5774
vn -0.5774 -0.5774 -0.5774
vn 0.5774 -0.5774 -0.5774
vn 0.5774 -0.5774 0.5774
vn -0.5774 0.5774 0.5774
vn 0.5774 0.5774 0.5774
vn 0.5774 0.5774 -0.5774
vn -0.5774 0.5774 -0.5774
# 8 vertex normals

vt 1.0000 0.0000 0.0000
vt 1.0000 1.0000 0.0000
vt 0.0000 1.0000 0.0000
vt 0.0000 0.0000 0.0000
# 4 texture coords

o Box001
g Box001
usemtl wire_008061138
s 2
f 1/1/1 2/2/2 3/3/3 4/4/4
s 4
f 5/4/5 6/1/6 7/2/7 8/3/8
s 8
f 1/4/1 4/1/4 6/2/6 5/3/5
s 16
f 4/4/4 3/1/3 7/2/7 6/3/6
s 32
f 3/4/3 2/1/2 8/2/8 7/3/7
s 64
f 2/4/2 1/1/1 5/2/5 8/3/8
# 6 polygons

0 comments on commit 20edef4

Please sign in to comment.