From c137068598558441628e753bc5ce7936ceaf00cf Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Sun, 29 May 2022 13:50:12 -0400 Subject: [PATCH 1/5] rework w/.NET Core and multitargeting --- .config/dotnet-tools.json | 12 + .vscode/.gitignore | 2 + .vscode/tasks.json | 59 +++ LICENSE.txt | 6 +- QuantumConcepts.Formats.STL.dll.nuspec | 22 - README.md | 13 +- STLdotNET.sln | 28 ++ Source/STL.sln | 47 -- Source/STL/STL.csproj | 62 --- Source/Test/Properties/AssemblyInfo.cs | 12 - Source/Test/STLTests.cs | 404 ---------------- Source/Test/Test.csproj | 96 ---- {Source/STL => src}/Extensions.cs | 25 +- {Source/STL => src}/Facet.cs | 139 +++--- {Source/STL => src}/Normal.cs | 27 +- .../STL => src}/Properties/AssemblyInfo.cs | 4 +- src/QuantumConcepts.Formats.STL.dll.nuspec | 53 ++ src/Resources/QC-Logo.jpg | Bin 0 -> 30845 bytes {Source/STL => src}/STLDocument.cs | 226 +++++---- src/STLdotNET.csproj | 14 + {Source/STL => src}/Vertex.cs | 140 +++--- {Source/Test => tests}/Data/ASCII.stl | 0 {Source/Test => tests}/Data/Binary.stl | Bin tests/STLDocumentTests.cs | 457 ++++++++++++++++++ tests/Tests.csproj | 35 ++ 25 files changed, 976 insertions(+), 907 deletions(-) create mode 100644 .config/dotnet-tools.json create mode 100644 .vscode/.gitignore create mode 100644 .vscode/tasks.json delete mode 100644 QuantumConcepts.Formats.STL.dll.nuspec create mode 100644 STLdotNET.sln delete mode 100644 Source/STL.sln delete mode 100644 Source/STL/STL.csproj delete mode 100644 Source/Test/Properties/AssemblyInfo.cs delete mode 100644 Source/Test/STLTests.cs delete mode 100644 Source/Test/Test.csproj rename {Source/STL => src}/Extensions.cs (80%) rename {Source/STL => src}/Facet.cs (64%) rename {Source/STL => src}/Normal.cs (83%) rename {Source/STL => src}/Properties/AssemblyInfo.cs (84%) create mode 100644 src/QuantumConcepts.Formats.STL.dll.nuspec create mode 100644 src/Resources/QC-Logo.jpg rename {Source/STL => src}/STLDocument.cs (64%) create mode 100644 src/STLdotNET.csproj rename {Source/STL => src}/Vertex.cs (57%) rename {Source/Test => tests}/Data/ASCII.stl (100%) rename {Source/Test => tests}/Data/Binary.stl (100%) create mode 100644 tests/STLDocumentTests.cs create mode 100644 tests/Tests.csproj diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..d8d5a3b --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-format": { + "version": "5.1.250801", + "commands": [ + "dotnet-format" + ] + } + } +} \ No newline at end of file diff --git a/.vscode/.gitignore b/.vscode/.gitignore new file mode 100644 index 0000000..d091d55 --- /dev/null +++ b/.vscode/.gitignore @@ -0,0 +1,2 @@ +. +!tasks.json \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..cd4c7de --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,59 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "shell", + "args": [ + "build", + // Ask dotnet build to generate full paths for file names. + "/property:GenerateFullPaths=true", + // Do not generate summary otherwise it leads to duplicate errors in Problems panel + "/consoleloggerparameters:NoSummary" + ], + "group": "build", + "presentation": { + "reveal": "silent" + }, + "problemMatcher": "$msCompile" + }, + { + "label": "test", + "command": "dotnet", + "type": "shell", + "args": [ + "test", + "tests/Tests.csproj" + ], + "group": "test" + }, + { + "label": "test & watch", + "command": "dotnet", + "type": "shell", + "args": [ + "watch", + "test", + "tests/Tests.csproj" + ], + "group": "test" + }, + { + "label": "pack", + "command": "dotnet", + "type": "shell", + "args": [ + "pack", + "-c", + "Release", + "--include-symbols", + "--include-source", + "src/STLdotNET.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index dba13ed..bae94e1 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -643,7 +643,7 @@ the "copyright" line and a pointer to where the full notice is found. GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. @@ -658,4 +658,4 @@ specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see -. +. \ No newline at end of file diff --git a/QuantumConcepts.Formats.STL.dll.nuspec b/QuantumConcepts.Formats.STL.dll.nuspec deleted file mode 100644 index 2bc6c15..0000000 --- a/QuantumConcepts.Formats.STL.dll.nuspec +++ /dev/null @@ -1,22 +0,0 @@ - - - - QuantumConcepts.Formats.STL - 1.3.1 - Quantum Concepts STLdotNET - Quantum Concepts - Quantum Concepts - https://github.com/QuantumConcepts/STLdotNET/raw/master/LICENSE.txt - https://github.com/QuantumConcepts/STLdotNET - http://quantumconceptscorp.com/Resources/Images/QCLogoButton-32.png - false - This library facilitates the reading and writing of Stereo Lithograph (STL) files. - .NET 4.5; fixed binary writing of STLs; numerous enhancements. More info here: https://github.com/QuantumConcepts/STLdotNET/issues?milestone=2&page=1&state=closed - Copyright 2014 Quantum Concepts Corporation, released under the GNU Affero General Public License./ - 3d reprap stl - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 1b73acf..544dca8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # An STL Reading and Writing Library for .NET + This library facilitates the reading and writing of Stereo Lithograph (STL) files. It is written in C# 4 and was created with Visual Studio 2012. ## Features + * Reads ASCII STL files. * Reads binary STL files. * Writes ASCII STL files. @@ -9,9 +11,16 @@ This library facilitates the reading and writing of Stereo Lithograph (STL) file * Provides an object-oriented mechanism by which to create STL files from scratch. ## Installation + You may [find the latest release here](https://github.com/QuantumConcepts/STLdotNET/releases). You can download the source and build it yourself, download the binaries from the release, or install the NuGet package: Install-Package QuantumConcepts.Formats.StereoLithography -## DotNet Core -If you're using dotNET Core, see [this port](https://github.com/Chedberg84/STL.NetCore) until this repo is updated to support mutl-targeting. +## Supported Runtimes + +Multiple runtimes are supported: + +- .NET 5 +- .NET 6 +- .NET Standard 2.1 +- .NET Core \ No newline at end of file diff --git a/STLdotNET.sln b/STLdotNET.sln new file mode 100644 index 0000000..28bec78 --- /dev/null +++ b/STLdotNET.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STLdotNET", "src\STLdotNET.csproj", "{1A9EE063-0F2F-40CD-8F81-BBF3513A62B6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "tests\Tests.csproj", "{38FF1867-EF9F-4C8F-9D7D-A19BB0DBD0D9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A9EE063-0F2F-40CD-8F81-BBF3513A62B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A9EE063-0F2F-40CD-8F81-BBF3513A62B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A9EE063-0F2F-40CD-8F81-BBF3513A62B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A9EE063-0F2F-40CD-8F81-BBF3513A62B6}.Release|Any CPU.Build.0 = Release|Any CPU + {38FF1867-EF9F-4C8F-9D7D-A19BB0DBD0D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38FF1867-EF9F-4C8F-9D7D-A19BB0DBD0D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38FF1867-EF9F-4C8F-9D7D-A19BB0DBD0D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38FF1867-EF9F-4C8F-9D7D-A19BB0DBD0D9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Source/STL.sln b/Source/STL.sln deleted file mode 100644 index 7fb97ad..0000000 --- a/Source/STL.sln +++ /dev/null @@ -1,47 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STL", "STL\STL.csproj", "{F32F3151-1595-4211-A01E-2F7BEB34F88D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{F8604512-723C-493E-BFD3-ECA49558CA72}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{84888A48-89A6-4EA3-90C1-FD07AC7369A3}" - ProjectSection(SolutionItems) = preProject - ..\.gitignore = ..\.gitignore - ..\LICENSE.txt = ..\LICENSE.txt - ..\QuantumConcepts.Formats.STL.dll.nuspec = ..\QuantumConcepts.Formats.STL.dll.nuspec - ..\README.md = ..\README.md - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F32F3151-1595-4211-A01E-2F7BEB34F88D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F32F3151-1595-4211-A01E-2F7BEB34F88D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F32F3151-1595-4211-A01E-2F7BEB34F88D}.Debug|x64.ActiveCfg = Debug|Any CPU - {F32F3151-1595-4211-A01E-2F7BEB34F88D}.Debug|x86.ActiveCfg = Debug|Any CPU - {F32F3151-1595-4211-A01E-2F7BEB34F88D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F32F3151-1595-4211-A01E-2F7BEB34F88D}.Release|Any CPU.Build.0 = Release|Any CPU - {F32F3151-1595-4211-A01E-2F7BEB34F88D}.Release|x64.ActiveCfg = Release|Any CPU - {F32F3151-1595-4211-A01E-2F7BEB34F88D}.Release|x86.ActiveCfg = Release|Any CPU - {F8604512-723C-493E-BFD3-ECA49558CA72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F8604512-723C-493E-BFD3-ECA49558CA72}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8604512-723C-493E-BFD3-ECA49558CA72}.Debug|x64.ActiveCfg = Debug|Any CPU - {F8604512-723C-493E-BFD3-ECA49558CA72}.Debug|x86.ActiveCfg = Debug|Any CPU - {F8604512-723C-493E-BFD3-ECA49558CA72}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F8604512-723C-493E-BFD3-ECA49558CA72}.Release|Any CPU.Build.0 = Release|Any CPU - {F8604512-723C-493E-BFD3-ECA49558CA72}.Release|x64.ActiveCfg = Release|Any CPU - {F8604512-723C-493E-BFD3-ECA49558CA72}.Release|x86.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/Source/STL/STL.csproj b/Source/STL/STL.csproj deleted file mode 100644 index fadfba3..0000000 --- a/Source/STL/STL.csproj +++ /dev/null @@ -1,62 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {F32F3151-1595-4211-A01E-2F7BEB34F88D} - Library - Properties - QuantumConcepts.Formats.STL - QuantumConcepts.Formats.StereoLithography - v4.5.1 - 512 - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Source/Test/Properties/AssemblyInfo.cs b/Source/Test/Properties/AssemblyInfo.cs deleted file mode 100644 index dc764d1..0000000 --- a/Source/Test/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("STL Format Reader and Writer Test")] -[assembly: AssemblyDescription("Tests reading and writing the STL format.")] -[assembly: AssemblyCompany("Quantum Concepts Corporation")] -[assembly: AssemblyProduct("STL Format Reader and Writer Test")] -[assembly: AssemblyCopyright("Copyright © Quantum Concepts Corporation")] -[assembly: AssemblyTrademark("Copyright © Quantum Concepts Corporation")] -[assembly: AssemblyVersion("1.2.0.0")] -[assembly: AssemblyFileVersion("1.2.0.0")] \ No newline at end of file diff --git a/Source/Test/STLTests.cs b/Source/Test/STLTests.cs deleted file mode 100644 index 2b78cde..0000000 --- a/Source/Test/STLTests.cs +++ /dev/null @@ -1,404 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace QuantumConcepts.Formats.StereoLithography.Test -{ - [TestClass] - public class STLTests - { - [TestMethod] - [Description("Ensures that both string and binary STL files can be read by the STLDocument.Read method.")] - public void FromStringAndBinary() - { - STLDocument stlString = null; - STLDocument stlBinary = null; - - using (Stream stream = GetData("ASCII.stl")) - { - stlString = STLDocument.Read(stream); - } - - ValidateSTL(stlString); - - using (Stream stream = GetData("Binary.stl")) - { - stlBinary = STLDocument.Read(stream); - } - - ValidateSTL(stlBinary); - } - - [TestMethod] - [Description("Ensures that reading text-based STLs works correctly.")] - public void FromText() - { - STLDocument stl = null; - - using (Stream stream = GetData("ASCII.stl")) - { - using (StreamReader reader = new StreamReader(stream, Encoding.ASCII, true, 1024, true)) - { - stl = STLDocument.Read(reader); - } - } - - ValidateSTL(stl); - } - - [TestMethod] - [Description("Ensures that reading binary-based STLs works correctly.")] - public void FromBinary() - { - STLDocument stl = null; - - using (Stream stream = GetData("Binary.stl")) - { - using (BinaryReader reader = new BinaryReader(stream)) - { - { - stl = STLDocument.Read(reader); - } - } - } - - ValidateSTL(stl); - } - - [TestMethod] - [Description("Ensures that reading STLs from a string works correctly.")] - public void FromString() - { - string stlText = null; - STLDocument stl = null; - - using (Stream stream = GetData("ASCII.stl")) - using (StreamReader reader = new StreamReader(stream)) - stlText = reader.ReadToEnd(); - - stl = STLDocument.Read(stlText); - - ValidateSTL(stl); - } - - [TestMethod] - [Description("Ensures that reading STLs from a file works correctly.")] - public void FromFile() - { - STLDocument stl = null; - - using (Stream inStream = GetData("ASCII.stl")) - { - string tempFilePath = Path.GetTempFileName(); - - using (var outStream = File.Create(tempFilePath)) - { - inStream.CopyTo(outStream); - } - - stl = STLDocument.Open(tempFilePath); - - try - { - File.Delete(tempFilePath); - } - catch { /* Ignore. */ } - } - - ValidateSTL(stl); - } - - [TestMethod] - [Description("Ensures that writing string STLs works correctly.")] - public void WriteString() - { - STLDocument stl1 = new STLDocument("WriteString", new List() - { - new Facet(new Normal( 0.23f, 0, 1), new List() - { - new Vertex( 0, 0, 0), - new Vertex(-10.123f, -10, 0), - new Vertex(-10.123f, 0, 0) - }, 0) - }); - STLDocument stl2 = null; - byte[] stl1Data = null; - string stl1String = null; - byte[] stl2Data = null; - string stl2String = null; - - using (MemoryStream stream = new MemoryStream()) - { - stl1.WriteText(stream); - stl1Data = stream.ToArray(); - stl1String = Encoding.ASCII.GetString(stl1Data); - } - - using (MemoryStream stream = new MemoryStream(stl1Data)) - { - stl2 = STLDocument.Read(stream); - stl2Data = stream.ToArray(); - stl2String = Encoding.ASCII.GetString(stl2Data); - } - - Assert.IsTrue(stl1.Equals(stl2)); - Assert.AreEqual(stl1String, stl2String); - } - - [TestMethod] - [Description("Ensures that writing binary STLs works correctly.")] - public void WriteBinary() - { - STLDocument stl1 = new STLDocument("WriteBinary", new List() - { - new Facet(new Normal( 0, 0, 1), new List() - { - new Vertex( 0, 0, 0), - new Vertex(-10, -10, 0), - new Vertex(-10, 0, 0) - }, 0) - }); - STLDocument stl2 = null; - byte[] stl1Data = null; - byte[] stl2Data = null; - - using (MemoryStream stream = new MemoryStream()) - { - stl1.WriteBinary(stream); - stl1Data = stream.ToArray(); - } - - using (MemoryStream stream = new MemoryStream(stl1Data)) - { - stl2 = STLDocument.Read(stream); - stl2Data = stream.ToArray(); - } - - Assert.IsTrue(stl1.Equals(stl2)); - Assert.IsTrue(stl1Data.SequenceEqual(stl2Data)); - } - - [TestMethod] - [Description("Ensures that copying an STL document as text works correctly.")] - public void CopyAsText() - { - STLDocument stlStringFrom = null; - STLDocument stlStringTo = null; - STLDocument stlBinaryFrom = null; - STLDocument stlBinaryTo = null; - - using (Stream inStream = GetData("ASCII.stl"), outStream = new MemoryStream()) - { - stlStringFrom = STLDocument.Read(inStream); - stlStringTo = STLDocument.CopyAsText(inStream, outStream); - } - - Assert.IsNotNull(stlStringFrom); - Assert.IsNotNull(stlStringTo); - Assert.IsTrue(stlStringFrom.Equals(stlStringTo)); - - using (Stream inStream = GetData("Binary.stl"), outStream = new MemoryStream()) - { - stlBinaryFrom = STLDocument.Read(inStream); - stlBinaryTo = STLDocument.CopyAsText(inStream, outStream); - } - - Assert.IsNotNull(stlBinaryFrom); - Assert.IsNotNull(stlBinaryTo); - Assert.IsTrue(stlBinaryFrom.Equals(stlBinaryTo)); - } - - [TestMethod] - [Description("Ensures that copying an STL document as binary works correctly.")] - public void CopyAsBinary() - { - STLDocument stlStringFrom = null; - STLDocument stlStringTo = null; - STLDocument stlBinaryFrom = null; - STLDocument stlBinaryTo = null; - - using (Stream inStream = GetData("ASCII.stl"), outStream = new MemoryStream()) - { - stlStringFrom = STLDocument.Read(inStream); - stlStringTo = STLDocument.CopyAsBinary(inStream, outStream); - } - - Assert.IsNotNull(stlStringFrom); - Assert.IsNotNull(stlStringTo); - Assert.IsTrue(stlStringFrom.Equals(stlStringTo)); - - using (Stream inStream = GetData("Binary.stl"), outStream = new MemoryStream()) - { - stlBinaryFrom = STLDocument.Read(inStream); - stlBinaryTo = STLDocument.CopyAsBinary(inStream, outStream); - } - - Assert.IsNotNull(stlBinaryFrom); - Assert.IsNotNull(stlBinaryTo); - Assert.IsTrue(stlBinaryFrom.Equals(stlBinaryTo)); - } - - [TestMethod] - [Description("Ensures that the stream is left open after reading a text-based STL.")] - public void StreamLeftOpen() - { - STLDocument stl = null; - - using (Stream stream = GetData("ASCII.stl")) - { - stl = STLDocument.Read(stream); - - try - { - stream.ReadByte(); - } - catch (ObjectDisposedException) - { - Assert.Fail("Stream is closed."); - } - } - } - - [TestMethod] - [Description("Ensures that STL equality comparison functions correctly.")] - public void Equality() - { - STLDocument[] stls = new STLDocument[2]; - - for (int i = 0; i < stls.Length; i++) - { - using (Stream stream = GetData("ASCII.stl")) - { - using (StreamReader reader = new StreamReader(stream)) - { - stls[i] = STLDocument.Read(reader); - } - } - } - - Assert.IsTrue(stls[0].Equals(stls[1])); - } - - [TestMethod] - [Description("Ensures that facet appending functions correctly.")] - public void AppendFacets() - { - STLDocument stl1 = null; - STLDocument stl2 = null; - int facetCount = 0; - - using (Stream stream = GetData("ASCII.stl")) - { - stl1 = STLDocument.Read(stream); - stl2 = STLDocument.Read(stream); - } - - ValidateSTL(stl1); - ValidateSTL(stl2); - - facetCount = (stl1.Facets.Count + stl2.Facets.Count); - stl1.AppendFacets(stl2); - - ValidateSTL(stl1, facetCount); - } - - [TestMethod] - [Description("Ensures that saving to a file functions correctly.")] - public void SaveToFile() - { - STLDocument stl = null; - STLDocument stlText = null; - STLDocument stlBinary = null; - string stlTextPath = Path.GetTempFileName(); - string stlBinaryPath = Path.GetTempFileName(); - - using (Stream stream = GetData("ASCII.stl")) - stl = STLDocument.Read(stream); - - stl.SaveAsText(stlTextPath); - stlText = STLDocument.Open(stlTextPath); - stl.SaveAsBinary(stlBinaryPath); - stlBinary = STLDocument.Open(stlBinaryPath); - - ValidateSTL(stlText); - ValidateSTL(stlBinary); - - try { File.Delete(stlTextPath); } - catch { } - - try { File.Delete(stlBinaryPath); } - catch { } - } - - [TestMethod] - [Description("Ensures that facet (vertex) shifting functions correctly.")] - public void ShiftFacets() - { - STLDocument stl1 = null; - STLDocument stl2 = null; - Vertex shift = new Vertex(100, -100, 50); - - using (Stream stream = GetData("ASCII.stl")) - { - stl1 = STLDocument.Read(stream); - stl2 = STLDocument.Read(stream); - } - - stl2.Facets.Shift(shift); - - for (int f = 0; f < stl1.Facets.Count; f++) - { - for (int v = 0; v < stl1.Facets[f].Vertices.Count; v++) - { - Assert.AreEqual(stl1.Facets[f].Vertices[v].X, stl2.Facets[f].Vertices[v].X - shift.X); - Assert.AreEqual(stl1.Facets[f].Vertices[v].Y, stl2.Facets[f].Vertices[v].Y - shift.Y); - Assert.AreEqual(stl1.Facets[f].Vertices[v].Z, stl2.Facets[f].Vertices[v].Z - shift.Z); - } - } - } - - [TestMethod] - [Description("Ensures that facet (normal) inversion functions correctly.")] - public void InvertFacets() - { - STLDocument stl1 = null; - STLDocument stl2 = null; - - using (Stream stream = GetData("ASCII.stl")) - { - stl1 = STLDocument.Read(stream); - stl2 = STLDocument.Read(stream); - } - - stl2.Facets.Invert(); - - for (int f = 0; f < stl1.Facets.Count; f++) - { - for (int v = 0; v < stl1.Facets[f].Vertices.Count; v++) - { - Assert.AreEqual(stl1.Facets[f].Normal.X, (stl2.Facets[f].Normal.X * -1)); - Assert.AreEqual(stl1.Facets[f].Normal.Y, (stl2.Facets[f].Normal.Y * -1)); - Assert.AreEqual(stl1.Facets[f].Normal.Z, (stl2.Facets[f].Normal.Z * -1)); - } - } - } - - private Stream GetData(string filename) - { - return Assembly.GetExecutingAssembly().GetManifestResourceStream("QuantumConcepts.Formats.StereoLithography.Test.Data.{0}".Interpolate(filename)); - } - - private void ValidateSTL(STLDocument stl, int expectedFacetCount = 12) - { - Assert.IsNotNull(stl); - Assert.AreEqual(expectedFacetCount, stl.Facets.Count); - - foreach (Facet facet in stl.Facets) - Assert.AreEqual(3, facet.Vertices.Count); - } - } -} diff --git a/Source/Test/Test.csproj b/Source/Test/Test.csproj deleted file mode 100644 index ba7b4a2..0000000 --- a/Source/Test/Test.csproj +++ /dev/null @@ -1,96 +0,0 @@ - - - - Debug - AnyCPU - {F8604512-723C-493E-BFD3-ECA49558CA72} - Library - Properties - QuantumConcepts.Formats.StereoLithography.Test - QuantumConcepts.Formats.StereoLithography.Test - v4.5.1 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - {f32f3151-1595-4211-a01e-2f7beb34f88d} - STL - - - - - - - - - - - - - False - - - False - - - False - - - False - - - - - - - - \ No newline at end of file diff --git a/Source/STL/Extensions.cs b/src/Extensions.cs similarity index 80% rename from Source/STL/Extensions.cs rename to src/Extensions.cs index 9bb2251..552c958 100644 --- a/Source/STL/Extensions.cs +++ b/src/Extensions.cs @@ -4,6 +4,7 @@ namespace QuantumConcepts.Formats.StereoLithography { + /// General STL extendsions for working with facets and vertices. public static class Extensions { /// Shifts the vertices within the enumerable by , and . @@ -46,7 +47,7 @@ public static void Shift(this IEnumerable vertices, Vertex shift) /// The facets to invert. public static void Invert(this IEnumerable facets) { - facets.ForEach(f => f.Normal.Invert()); + facets.ForEach(f => f?.Normal?.Invert()); } /// Iterates the provided enumerable, applying the provided action to each element. @@ -93,27 +94,5 @@ public static bool IsNullOrEmpty(this string value) { return string.IsNullOrEmpty(value); } - - /// Interpolates the provided formatted string with the provided args using the default culture. - /// The formatted string. - /// The values to use for interpolation. - public static string Interpolate(this string format, params object[] args) - { - return format.Interpolate(CultureInfo.InvariantCulture, args); - } - - /// Interpolates the provided formatted string with the provided args. - /// The formatted string. - /// The culture info to use. - /// The values to use for interpolation. - public static string Interpolate(this string format, CultureInfo culture, params object[] args) - { - if (format != null) - { - return string.Format(culture, format, args); - } - - return null; - } } } diff --git a/Source/STL/Facet.cs b/src/Facet.cs similarity index 64% rename from Source/STL/Facet.cs rename to src/Facet.cs index 4e781af..36b96a2 100644 --- a/Source/STL/Facet.cs +++ b/src/Facet.cs @@ -12,43 +12,41 @@ namespace QuantumConcepts.Formats.StereoLithography public class Facet : IEquatable, IEnumerable { /// Indicates the directionality of the . - public Normal Normal { get; set; } + public Normal? Normal { get; set; } = null; /// Indicates the location of the . - public IList Vertices { get; set; } + public IList Vertices { get; set; } = new List(); /// Additional data attached to the facet. /// Depending on the source of the STL, this could be used to indicate such things as the color of the . This functionality only exists in binary STLs. public UInt16 AttributeByteCount { get; set; } /// Creates a new, empty . - public Facet() - { - this.Vertices = new List(); - } + public Facet() { } /// Creates a new using the provided parameters. /// The directionality of the . /// The location of the . /// Additional data to attach to the . - public Facet(Normal normal, IEnumerable vertices, UInt16 attributeByteCount) - : this() + public Facet(Normal normal, IEnumerable vertices, UInt16 attributeByteCount) : this() { - this.Normal = normal; - this.Vertices = vertices.ToList(); - this.AttributeByteCount = attributeByteCount; + Normal = normal; + Vertices = vertices.ToList(); + AttributeByteCount = attributeByteCount; } /// Writes the as text to the . /// The writer to which the will be written at the current position. public void Write(StreamWriter writer) { + if (writer == null) throw new ArgumentNullException(nameof(writer)); + writer.Write("\t"); - writer.WriteLine(this.ToString()); + writer.WriteLine(this); writer.WriteLine("\t\touter loop"); - //Write each vertex. - this.Vertices.ForEach(o => o.Write(writer)); + // Write each vertex. + Vertices.ForEach(o => o.Write(writer)); writer.WriteLine("\t\tendloop"); writer.WriteLine("\tendfacet"); @@ -58,64 +56,39 @@ public void Write(StreamWriter writer) /// The writer to which the will be written at the current position. public void Write(BinaryWriter writer) { - //Write the normal. - this.Normal.Write(writer); - - //Write each vertex. - this.Vertices.ForEach(o => o.Write(writer)); + if (writer == null) throw new ArgumentNullException(nameof(writer)); - //Write the attribute byte count. - writer.Write(this.AttributeByteCount); - } - - /// Returns the string representation of this . - public override string ToString() - { - return "facet {0}".Interpolate(this.Normal); - } - - /// Determines whether or not this instance is the same as the instance. - /// The to which to compare. - public bool Equals(Facet other) - { - return (this.Normal.Equals(other.Normal) - && this.Vertices.Count == other.Vertices.Count - && this.Vertices.All((i, o) => o.Equals(other.Vertices[i]))); - } + // Write the normal. + Normal?.Write(writer); - /// Iterates through the collection. - public IEnumerator GetEnumerator() - { - return this.Vertices.GetEnumerator(); - } + // Write each vertex. + Vertices.ForEach(o => o.Write(writer)); - /// Iterates through the collection. - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); + // Write the attribute byte count. + writer.Write(AttributeByteCount); } /// Reads a single from the . - /// The reader which contains a to be read at the current position + /// The reader which contains a to be read at the current position. public static Facet Read(StreamReader reader) { - if (reader == null) - return null; + if (reader == null) throw new NullReferenceException(nameof(reader)); - //Create the facet. + // Create the facet. Facet facet = new Facet(); - //Read the normal. - if ((facet.Normal = Normal.Read(reader)) == null) - return null; + // Read the normal. + facet.Normal = Normal.Read(reader); + + if (facet.Normal == null) throw new InvalidOperationException("Facet has no Normal."); - //Skip the "outer loop". + // Skip the "outer loop". reader.ReadLine(); - //Read 3 vertices. + // Read 3 vertices. facet.Vertices = Enumerable.Range(0, 3).Select(o => Vertex.Read(reader)).ToList(); - //Read the "endloop" and "endfacet". + // Read the "endloop" and "endfacet". reader.ReadLine(); reader.ReadLine(); @@ -123,25 +96,65 @@ public static Facet Read(StreamReader reader) } /// Reads a single from the . - /// The reader which contains a to be read at the current position + /// The reader which contains a to be read at the current position. public static Facet Read(BinaryReader reader) { - if (reader == null) - return null; + if (reader == null) throw new NullReferenceException(nameof(reader)); - //Create the facet. + // Create the facet. Facet facet = new Facet(); - //Read the normal. + // Read the normal. facet.Normal = Normal.Read(reader); - //Read 3 vertices. + // Read 3 vertices. facet.Vertices = Enumerable.Range(0, 3).Select(o => Vertex.Read(reader)).ToList(); - //Read the attribute byte count. + // Read the attribute byte count. facet.AttributeByteCount = reader.ReadUInt16(); return facet; } + + /// Returns the string representation of this . + public override string ToString() + { + return $"facet {Normal}"; + } + + /// + public override int GetHashCode() + { + return this.ToString().GetHashCode(); + } + + /// + public override bool Equals(object? other) + { + return Equals(other as Facet); + } + + /// Determines whether or not this instance is the same as the instance. + /// The to which to compare. + public bool Equals(Facet? other) + { + return + other != null && + Object.Equals(Normal, other.Normal) && + Vertices.Count == other.Vertices.Count && + Vertices.All((i, o) => Object.Equals(o, other.Vertices[i])); + } + + /// Iterates through the collection. + public IEnumerator GetEnumerator() + { + return Vertices.GetEnumerator(); + } + + /// Iterates through the collection. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } } } diff --git a/Source/STL/Normal.cs b/src/Normal.cs similarity index 83% rename from Source/STL/Normal.cs rename to src/Normal.cs index 146a71b..2a1b162 100644 --- a/Source/STL/Normal.cs +++ b/src/Normal.cs @@ -20,29 +20,21 @@ public Normal(float x, float y, float z) : base(x, y, z) { } /// Flips the normal so it faces the opposite direction. public void Invert() { - this.X *= -1; - this.Y *= -1; - this.Z *= -1; - } - - /// Returns the string representation of this . - public override string ToString() - { - //return "normal {0} {1} {2}".FormatString(this.X, this.Y, this.Z); - return String.Format(CultureInfo.InvariantCulture, "normal {0} {1} {2}", this.X, this.Y, this.Z); - + X *= -1; + Y *= -1; + Z *= -1; } /// Reads a single from the . /// The reader which contains a to be read at the current position - public static Normal Read(StreamReader reader) + public static new Normal Read(StreamReader reader) { return Normal.FromVertex(Vertex.Read(reader)); } /// Reads a single from the . /// The reader which contains a to be read at the current position - public static Normal Read(BinaryReader reader) + public static new Normal Read(BinaryReader reader) { return Normal.FromVertex(Vertex.Read(reader)); } @@ -53,8 +45,7 @@ public static Normal Read(BinaryReader reader) /// A or null if the is null. public static Normal FromVertex(Vertex vertex) { - if (vertex == null) - return null; + if (vertex == null) throw new NullReferenceException(nameof(vertex)); return new Normal() { @@ -63,5 +54,11 @@ public static Normal FromVertex(Vertex vertex) Z = vertex.Z }; } + + /// Returns the string representation of this . + public override string ToString() + { + return $"normal {X} {Y} {Z}"; + } } } diff --git a/Source/STL/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs similarity index 84% rename from Source/STL/Properties/AssemblyInfo.cs rename to src/Properties/AssemblyInfo.cs index 0ec962f..380c679 100644 --- a/Source/STL/Properties/AssemblyInfo.cs +++ b/src/Properties/AssemblyInfo.cs @@ -6,5 +6,5 @@ [assembly: AssemblyProduct("STL Format Reader and Writer")] [assembly: AssemblyCopyright("Copyright © Quantum Concepts Corporation")] [assembly: AssemblyTrademark("Copyright © Quantum Concepts Corporation")] -[assembly: AssemblyVersion("1.3.1")] -[assembly: AssemblyFileVersion("1.3.1")] \ No newline at end of file +[assembly: AssemblyVersion("2.0.0")] +[assembly: AssemblyFileVersion("2.0.0")] \ No newline at end of file diff --git a/src/QuantumConcepts.Formats.STL.dll.nuspec b/src/QuantumConcepts.Formats.STL.dll.nuspec new file mode 100644 index 0000000..8af9541 --- /dev/null +++ b/src/QuantumConcepts.Formats.STL.dll.nuspec @@ -0,0 +1,53 @@ + + + + + QuantumConcepts.Formats.STL + 2.0.0 + Quantum Concepts STLdotNET + This library facilitates the reading and writing of Stereo Lithograph (STL) files. + Quantum Concepts + Quantum Concepts + images/QC-Logo.jpg + https://github.com/QuantumConcepts/STLdotNET + docs/LICENSE.txt + false + Updated to support multi-targetting. + Copyright 2022 Quantum Concepts LLC, released under the GNU Affero General Public License./ + 3d reprap stl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Resources/QC-Logo.jpg b/src/Resources/QC-Logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3844e33d6c077d7097acb355bedc64fab59d5887 GIT binary patch literal 30845 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 b_xYT&I3Q|qjuG%*j7-z);h6rI_3r-wQ, IEnumerable /// The name of the solid. /// This property is not used for binary STLs. - public string Name { get; set; } + public string? Name { get; set; } = null; /// The list of s within this solid. - public IList Facets { get; set; } + public IList Facets { get; set; } = new List(); /// Creates a new, empty . - public STLDocument() - { - this.Facets = new List(); - } + public STLDocument() { } /// Creates a new with the given and populated with the given . /// @@ -33,27 +25,28 @@ public STLDocument() /// This property is not used for binary STLs. /// /// The facets with which to populate this solid. - public STLDocument(string name, IEnumerable facets) - : this() + public STLDocument(string name, IEnumerable facets) : this() { - this.Name = name; - this.Facets = facets.ToList(); + Name = name; + Facets = facets.ToList(); } /// Writes the as text to the provided . /// The stream to which the will be written. public void WriteText(Stream stream) { - using (StreamWriter writer = new StreamWriter(stream, Encoding.ASCII, DefaultBufferSize, true)) + if (stream == null) throw new NullReferenceException(nameof(stream)); + + using (var writer = new StreamWriter(stream, Encoding.ASCII, DefaultBufferSize, true)) { - //Write the header. - writer.WriteLine(this.ToString()); + // Write the header. + writer.WriteLine(this); - //Write each facet. - this.Facets.ForEach(o => o.Write(writer)); + // Write each facet. + Facets.ForEach(o => o.Write(writer)); - //Write the footer. - writer.Write("end{0}".Interpolate(this.ToString())); + // Write the footer. + writer.Write($"end{this}"); } } @@ -61,19 +54,21 @@ public void WriteText(Stream stream) /// The stream to which the will be written. public void WriteBinary(Stream stream) { - using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII, true)) + if (stream == null) throw new NullReferenceException(nameof(stream)); + + using (var writer = new BinaryWriter(stream, Encoding.ASCII, true)) { - byte[] header = Encoding.ASCII.GetBytes("Binary STL generated by STLdotNET. QuantumConceptsCorp.com"); + byte[] header = Encoding.ASCII.GetBytes("Binary STL generated by STLdotNET"); byte[] headerFull = new byte[80]; Buffer.BlockCopy(header, 0, headerFull, 0, Math.Min(header.Length, headerFull.Length)); - //Write the header and facet count. + // Write the header and facet count. writer.Write(headerFull); - writer.Write((UInt32)this.Facets.Count); + writer.Write((UInt32)Facets.Count); - //Write each facet. - this.Facets.ForEach(o => o.Write(writer)); + // Write each facet. + Facets.ForEach(o => o.Write(writer)); } } @@ -81,26 +76,36 @@ public void WriteBinary(Stream stream) /// The absolute path where the will be written. public void SaveAsText(string path) { - if (path.IsNullOrEmpty()) - throw new ArgumentNullException("path"); + CreatePathDirectories(path); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - using (Stream stream = File.Create(path)) + using (var stream = File.Create(path)) + { WriteText(stream); + } } /// Writes the as binary to the provided . /// The absolute path where the will be written. public void SaveAsBinary(string path) { - if (path.IsNullOrEmpty()) - throw new ArgumentNullException("path"); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); + CreatePathDirectories(path); - using (Stream stream = File.Create(path)) + using (var stream = File.Create(path)) + { WriteBinary(stream); + } + } + + private void CreatePathDirectories(string path) + { + if (path.IsNullOrEmpty()) throw new ArgumentNullException("path"); + + var dir = Path.GetDirectoryName(path); + + if (dir == null) throw new InvalidOperationException($"Could not determine directory name for path: {path}"); + + // Create dir(s). + Directory.CreateDirectory(dir); } /// Appends the provided facets to this instance's . @@ -108,8 +113,10 @@ public void SaveAsBinary(string path) /// The facets to append. public void AppendFacets(IEnumerable facets) { - foreach (Facet facet in facets) - this.Facets.Add(facet); + foreach (var facet in facets) + { + Facets.Add(facet); + } } /// Determines if the contained within the is text-based. @@ -118,17 +125,19 @@ public void AppendFacets(IEnumerable facets) /// True if the is text-based, otherwise false. public static bool IsText(Stream stream) { + if (stream == null) throw new NullReferenceException(nameof(stream)); + const string solid = "solid"; byte[] buffer = new byte[5]; - string header = null; + string header; - //Reset the stream to tbe beginning and read the first few bytes, then reset the stream to the beginning again. + // 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. + // Read the header as ASCII. header = Encoding.ASCII.GetString(buffer); return solid.Equals(header, StringComparison.InvariantCultureIgnoreCase); @@ -148,62 +157,81 @@ public static bool IsBinary(Stream stream) /// The stream which contains the STL data. /// Set to true to try read as binary if reading as text results in zero facets /// An representing the data contained in the stream or null if the stream is empty. - public static STLDocument Read(Stream stream,bool tryBinaryIfTextFailed=false) + public static STLDocument Read(Stream stream, bool tryBinaryIfTextFailed = false) { - //Determine if the stream contains a text-based or binary-based , and then read it. + if (stream == null) throw new NullReferenceException(nameof(stream)); + + // Determine if the stream contains a text-based or binary-based , and then read it. var isText = IsText(stream); - STLDocument textStlDocument = null; + STLDocument? textDoc = null; + STLDocument? binaryDoc = null; + STLDocument? finalDoc = null; + if (isText) { - using (StreamReader reader = new StreamReader(stream, Encoding.ASCII, true, DefaultBufferSize, true)) + using (var reader = new StreamReader(stream, Encoding.ASCII, true, DefaultBufferSize, true)) { - textStlDocument = Read(reader); + textDoc = Read(reader); + } + + if (textDoc.Facets.Count > 0 || !tryBinaryIfTextFailed) + { + return textDoc; } - - 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)) + // Try binary if zero Facets were read and `tryBinaryIfTextFailed == true`. + if (!isText || (textDoc?.Facets.Count == 0 && tryBinaryIfTextFailed)) { - var binaryStlDocument = Read(reader); + // Make sure we're at the beginning of the stream (in case we tried to read it as text above). + stream.Seek(0, SeekOrigin.Begin); - //return text reading result if binary reading also failed and tryBinaryIfTextFailed==true - return (binaryStlDocument.Facets.Count>0 || !isText)?binaryStlDocument:textStlDocument; + using (var reader = new BinaryReader(stream, Encoding.ASCII, true)) + { + binaryDoc = Read(reader); + } } + + // Use text document if binary reading also failed. + finalDoc = (binaryDoc?.Facets.Count > 0 || !isText) ? binaryDoc : textDoc; + + // Make sure we have a text or binary document. + if (finalDoc == null) throw new InvalidOperationException("Could not read stream as text or binary STL document."); + + return finalDoc; } - /// Reads the STL document contained within the into a new . + /// Reads the STL document contained within the into a new . /// This method expects a text-based STL document to be contained within the . /// The reader which contains the text-based STL data. /// An representing the data contained in the stream or null if the stream is empty. public static STLDocument Read(StreamReader reader) { - const string regexSolid = @"solid\s+(?[^\r\n]+)?"; + if (reader == null) throw new NullReferenceException(nameof(reader)); - if (reader == null) - return null; - - //Read the header. - string header = reader.ReadLine(); - Match headerMatch = Regex.Match(header, regexSolid); - STLDocument stl = null; - Facet currentFacet = null; + const string regexSolid = @"solid\s+(?[^\r\n]+)?"; + string? header = reader.ReadLine(); + Match headerMatch; + STLDocument stl; - //Check the header. - if (!headerMatch.Success) - throw new FormatException("Invalid STL header, expected \"solid [name]\" but found \"{0}\".".Interpolate(header)); + // Check the header. + if (header == null || !(headerMatch = Regex.Match(header, regexSolid)).Success) throw new FormatException($"Invalid STL header, expected \"solid [name]\" but found: {header}"); - //Create the STL and extract the name (optional). + // 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); + // Read each facet until the end of the stream. + while (!reader.EndOfStream) + { + // Peek the next char to make sure it's a facet (e.g. not "endsolid"). + if (((char)reader.Peek()) == 'e') break; + + stl.Facets.Add(Facet.Read(reader)); + + } return stl; } @@ -213,11 +241,12 @@ public static STLDocument Read(StreamReader reader) /// An representing the data contained in the parameter or null if the parameter is empty. public static STLDocument Read(string stl) { - if (stl.IsNullOrEmpty()) - return null; + if (stl.IsNullOrEmpty()) return new STLDocument(); - using (MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(stl))) + using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(stl))) + { return Read(stream); + } } /// Reads the STL document located at the into a new . @@ -226,32 +255,37 @@ public static STLDocument Read(string stl) public static STLDocument Open(string path) { if (path.IsNullOrEmpty()) + { throw new ArgumentNullException("path"); + } - using (Stream stream = File.OpenRead(path)) + using (var stream = File.OpenRead(path)) + { return Read(stream); + } } - /// Reads the STL document contained within the into a new . + /// Reads the STL document contained within the into a new . /// This method will expects a binary-based to be contained within the . /// The reader which contains the binary-based STL data. /// An representing the data contained in the stream or null if the stream is empty. public static STLDocument Read(BinaryReader reader) { - if (reader == null) - return null; + if (reader == null) throw new NullReferenceException(nameof(reader)); byte[] buffer = new byte[80]; STLDocument stl = new STLDocument(); - Facet currentFacet = null; + Facet currentFacet; - //Read (and ignore) the header and number of triangles. + // 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. + // 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; } @@ -285,22 +319,36 @@ public static STLDocument CopyAsBinary(Stream inStream, Stream outStream) /// Returns the header representation of this . public override string ToString() { - return "solid {0}".Interpolate(this.Name); + return $"solid {Name}"; + } + + /// + public override int GetHashCode() + { + return this.ToString().GetHashCode(); + } + + /// + public override bool Equals(object? other) + { + return Equals(other as STLDocument); } /// Determines whether or not this instance is the same as the instance. /// The to which to compare. /// True if this instance is equal to the instance. - public bool Equals(STLDocument other) + public bool Equals(STLDocument? other) { - return (this.Facets.Count == other.Facets.Count - && this.Facets.All((i, o) => o.Equals(other.Facets[i]))); + return + other != null && + Facets.Count == other.Facets.Count && + Facets.All((i, o) => o.Equals(other.Facets[i])); } /// Iterates through the collection. public IEnumerator GetEnumerator() { - return this.Facets.GetEnumerator(); + return Facets.GetEnumerator(); } /// Iterates through the collection. diff --git a/src/STLdotNET.csproj b/src/STLdotNET.csproj new file mode 100644 index 0000000..0306a8f --- /dev/null +++ b/src/STLdotNET.csproj @@ -0,0 +1,14 @@ + + + + 10.0 + net6;net5;netstandard2.0;netstandard2.1;netcoreapp3.1 + enable + enable + false + true + true + QuantumConcepts.Formats.STL.dll.nuspec + + + diff --git a/Source/STL/Vertex.cs b/src/Vertex.cs similarity index 57% rename from Source/STL/Vertex.cs rename to src/Vertex.cs index 1e9e920..e1423a7 100644 --- a/Source/STL/Vertex.cs +++ b/src/Vertex.cs @@ -13,13 +13,13 @@ namespace QuantumConcepts.Formats.StereoLithography public class Vertex : IEquatable { /// The X coordinate of this . - public float X { get; set; } + public float X { get; set; } = 0; /// The Y coordinate of this . - public float Y { get; set; } + public float Y { get; set; } = 0; /// The Z coordinate of this . - public float Z { get; set; } + public float Z { get; set; } = 0; /// Creates a new, empty . public Vertex() { } @@ -28,53 +28,36 @@ public Vertex() { } /// /// /// - public Vertex(float x, float y, float z) - : this() + public Vertex(float x, float y, float z) : this() { - this.X = x; - this.Y = y; - this.Z = z; + X = x; + Y = y; + Z = z; } - /// Shifts the by the X, Y and Z values in the parameter. + /// Shifts the by the X, Y and Z values in the parameter. /// The amount to shift the vertex. public void Shift(Vertex shift) { - this.X += shift.X; - this.Y += shift.Y; - this.Z += shift.Z; + X += shift.X; + Y += shift.Y; + Z += shift.Z; } /// Writes the as text to the . /// The writer to which the will be written at the current position. public void Write(StreamWriter writer) { - writer.WriteLine("\t\t\t{0}".Interpolate(this.ToString())); + writer.WriteLine($"\t\t\t{this}"); } /// Writes the as binary to the . /// The writer to which the will be written at the current position. public void Write(BinaryWriter writer) { - writer.Write(this.X); - writer.Write(this.Y); - writer.Write(this.Z); - } - - /// Returns the string representation of this . - public override string ToString() - { - //return "vertex {0} {1} {2}".FormatString(this.X, this.Y, this.Z); - return String.Format(CultureInfo.InvariantCulture, "vertex {0} {1} {2}", this.X, this.Y, this.Z); - } - - /// Determines whether or not this instance is the same as the instance. - /// The to which to compare. - public bool Equals(Vertex other) - { - return (this.X.Equals(other.X) - && this.Y.Equals(other.Y) - && this.Z.Equals(other.Z)); + writer.Write(X); + writer.Write(Y); + writer.Write(Z); } /// Reads a single from the . @@ -82,45 +65,43 @@ public bool Equals(Vertex other) public static Vertex Read(StreamReader reader) { const string regex = @"\s*(facet normal|vertex)\s+(?[^\s]+)\s+(?[^\s]+)\s+(?[^\s]+)"; - const NumberStyles numberStyle = (NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign); - - string data = null; - float x, y, z; - Match match = null; + string? data; + Match match; - if (reader == null) - return null; + if (reader == null) throw new ArgumentNullException(nameof(reader)); - //Read the next line of data. + // Read the next line of data. data = reader.ReadLine(); - if (data == null) - return null; + if (data == null) throw new InvalidOperationException("No data could be read from reader."); - //Ensure that the data is formatted correctly. + // Ensure that the data is formatted correctly. match = Regex.Match(data, regex, RegexOptions.IgnoreCase); - if (!match.Success) - return null; - - //Parse the three coordinates. - if (!float.TryParse(match.Groups["X"].Value, numberStyle, CultureInfo.InvariantCulture, out x)) - throw new FormatException("Could not parse X coordinate \"{0}\" as a decimal.".Interpolate(match.Groups["X"])); - - if (!float.TryParse(match.Groups["Y"].Value, numberStyle, CultureInfo.InvariantCulture, out y)) - throw new FormatException("Could not parse Y coordinate \"{0}\" as a decimal.".Interpolate(match.Groups["Y"])); - - if (!float.TryParse(match.Groups["Z"].Value, numberStyle, CultureInfo.InvariantCulture, out z)) - throw new FormatException("Could not parse Z coordinate \"{0}\" as a decimal.".Interpolate(match.Groups["Z"])); + if (!match.Success) throw new InvalidOperationException($"Vertex is not formatted correctly: {data}"); + // Parse the three coordinates. return new Vertex() { - X = x, - Y = y, - Z = z + X = ParseCoordinate("X", match.Groups["X"].Value), + Y = ParseCoordinate("Y", match.Groups["Y"].Value), + Z = ParseCoordinate("Z", match.Groups["Z"].Value), }; } + private static float ParseCoordinate(string which, string value) + { + const NumberStyles numberStyle = (NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign); + float parsed; + + if (!float.TryParse(value, numberStyle, CultureInfo.InvariantCulture, out parsed)) + { + throw new FormatException($"Could not parse {which} coordinate as a decimal from value: {value}"); + } + + return parsed; + } + /// Reads a single from the . /// The reader which contains a to be read at the current position public static Vertex Read(BinaryReader reader) @@ -128,20 +109,16 @@ public static Vertex Read(BinaryReader reader) const int floatSize = sizeof(float); const int vertexSize = (floatSize * 3); - if (reader == null) - return null; + if (reader == null) throw new ArgumentNullException(nameof(reader)); - //Read 3 floats. + // Read 3 floats. byte[] data = new byte[vertexSize]; int bytesRead = reader.Read(data, 0, data.Length); - //If no bytes are read then we're at the end of the stream. - if (bytesRead == 0) - return null; - else if (bytesRead != data.Length) - throw new FormatException("Could not convert the binary data to a vertex. Expected {0} bytes but found {1}.".Interpolate(vertexSize, bytesRead)); + if (bytesRead == 0) throw new InvalidOperationException("No data could be read from reader."); + if (bytesRead != data.Length) throw new FormatException($"Could not convert the binary data to a vertex. Expected {vertexSize} bytes but found {bytesRead}."); - //Convert the read bytes to their numeric representation. + // Convert the read bytes to their numeric representation. return new Vertex() { X = BitConverter.ToSingle(data, 0), @@ -149,5 +126,34 @@ public static Vertex Read(BinaryReader reader) Z = BitConverter.ToSingle(data, (floatSize * 2)) }; } + + /// Returns the string representation of this . + public override string ToString() + { + return $"vertex {X} {Y} {Z}"; + } + + /// + public override int GetHashCode() + { + return this.ToString().GetHashCode(); + } + + /// + public override bool Equals(object? other) + { + return Equals(other as Vertex); + } + + /// Determines whether or not this instance is the same as the instance. + /// The to which to compare. + public bool Equals(Vertex? other) + { + return + other != null && + X == other.X && + Y == other.Y && + Z == other.Z; + } } } diff --git a/Source/Test/Data/ASCII.stl b/tests/Data/ASCII.stl similarity index 100% rename from Source/Test/Data/ASCII.stl rename to tests/Data/ASCII.stl diff --git a/Source/Test/Data/Binary.stl b/tests/Data/Binary.stl similarity index 100% rename from Source/Test/Data/Binary.stl rename to tests/Data/Binary.stl diff --git a/tests/STLDocumentTests.cs b/tests/STLDocumentTests.cs new file mode 100644 index 0000000..41940f4 --- /dev/null +++ b/tests/STLDocumentTests.cs @@ -0,0 +1,457 @@ +using FluentAssertions; +using System.Reflection; +using System.Text; +using Xunit; + +namespace QuantumConcepts.Formats.StereoLithography.Test +{ + public class STLDocumentTests + { + [Fact] + public void Read__FromTextStream() + { + STLDocument stlString; + + using (var stream = GetData("ASCII.stl")) + { + stlString = STLDocument.Read(stream); + } + + ValidateSTL(stlString); + } + + [Fact] + public void Read__FromBinaryStream() + { + STLDocument stlBinary; + + using (var stream = GetData("Binary.stl")) + { + stlBinary = STLDocument.Read(stream); + } + + ValidateSTL(stlBinary); + } + + [Fact] + public void Read__FromTextReader() + { + STLDocument stl; + + using (var stream = GetData("ASCII.stl")) + { + using (var reader = new StreamReader(stream, Encoding.ASCII, true, 1024, true)) + { + stl = STLDocument.Read(reader); + } + } + + ValidateSTL(stl); + } + + [Fact] + public void Read__FromBinaryReader() + { + STLDocument stl; + + using (var stream = GetData("Binary.stl")) + { + using (var reader = new BinaryReader(stream)) + { + { + stl = STLDocument.Read(reader); + } + } + } + + ValidateSTL(stl); + } + + [Fact] + public void Read__FromString() + { + string stlText; + STLDocument stl; + + using (var stream = GetData("ASCII.stl")) + using (var reader = new StreamReader(stream)) + { + stlText = reader.ReadToEnd(); + } + + stl = STLDocument.Read(stlText); + + ValidateSTL(stl); + } + + [Fact] + public void Read__FromStream__LeavesStreamOpen() + { + STLDocument stl; + + using (var stream = GetData("ASCII.stl")) + { + stl = STLDocument.Read(stream); + + try + { + stream.ReadByte(); + } + catch (ObjectDisposedException) + { + throw new Exception("Stream is closed."); + } + } + } + + [Fact] + public void Open__FromFilePath() + { + STLDocument stl; + + using (var inStream = GetData("ASCII.stl")) + { + string tempFilePath = Path.GetTempFileName(); + + using (var outStream = File.Create(tempFilePath)) + { + inStream.CopyTo(outStream); + } + + stl = STLDocument.Open(tempFilePath); + + try + { + File.Delete(tempFilePath); + } + catch { /* Ignore. */ } + } + + ValidateSTL(stl); + } + + [Fact] + public void WriteText__ToStream__Then__Read__FromStream__ProducesSameDocument() + { + STLDocument stl1 = new STLDocument("WriteString", new List() + { + new Facet(new Normal( 0.23f, 0, 1), new List() + { + new Vertex( 0, 0, 0), + new Vertex(-10.123f, -10, 0), + new Vertex(-10.123f, 0, 0) + }, 0) + }); + STLDocument stl2; + byte[] stl1Data; + string stl1String; + byte[] stl2Data; + string stl2String; + + using (var stream = new MemoryStream()) + { + stl1.WriteText(stream); + stl1Data = stream.ToArray(); + stl1String = Encoding.ASCII.GetString(stl1Data); + } + + using (var stream = new MemoryStream(stl1Data)) + { + stl2 = STLDocument.Read(stream); + stl2Data = stream.ToArray(); + stl2String = Encoding.ASCII.GetString(stl2Data); + } + + CompareSTLs(stl1, stl2); + Assert.Equal(stl1String, stl2String); + } + + [Fact] + public void WriteBinary__ToStream__Then__Read__FromStream__ProducesSameDocument() + { + STLDocument stl1 = new STLDocument("WriteBinary", new List() + { + new Facet(new Normal( 0, 0, 1), new List() + { + new Vertex( 0, 0, 0), + new Vertex(-10, -10, 0), + new Vertex(-10, 0, 0) + }, 0) + }); + STLDocument stl2; + byte[] stl1Data; + byte[] stl2Data; + + using (var stream = new MemoryStream()) + { + stl1.WriteBinary(stream); + stl1Data = stream.ToArray(); + } + + using (var stream = new MemoryStream(stl1Data)) + { + stl2 = STLDocument.Read(stream); + stl2Data = stream.ToArray(); + } + + CompareSTLs(stl1, stl2, true); + Assert.True(stl1Data.SequenceEqual(stl2Data)); + } + + [Fact] + public void SaveAsText() + { + STLDocument stl; + STLDocument stlText; + string stlTextPath = Path.GetTempFileName(); + + using (var stream = GetData("ASCII.stl")) + { + stl = STLDocument.Read(stream); + } + + stl.SaveAsText(stlTextPath); + stlText = STLDocument.Open(stlTextPath); + + ValidateSTL(stlText); + + try { File.Delete(stlTextPath); } + catch { } + } + + [Fact] + public void SaveAsBinary() + { + STLDocument stl; + STLDocument stlBinary; + string stlBinaryPath = Path.GetTempFileName(); + + using (var stream = GetData("ASCII.stl")) + { + stl = STLDocument.Read(stream); + } + + stl.SaveAsBinary(stlBinaryPath); + stlBinary = STLDocument.Open(stlBinaryPath); + + ValidateSTL(stlBinary); + + try { File.Delete(stlBinaryPath); } + catch { } + } + + [Fact] + public void CopyAsText() + { + STLDocument stlStringFrom; + STLDocument stlStringTo; + STLDocument stlBinaryFrom; + STLDocument stlBinaryTo; + + using (var inStream = GetData("ASCII.stl")) + using (var outStream = new MemoryStream()) + { + stlStringFrom = STLDocument.Read(inStream); + stlStringTo = STLDocument.CopyAsText(inStream, outStream); + } + + Assert.NotNull(stlStringFrom); + Assert.NotNull(stlStringTo); + Assert.Equal(stlStringFrom, stlStringTo); + + using (var inStream = GetData("Binary.stl")) + using (var outStream = new MemoryStream()) + { + stlBinaryFrom = STLDocument.Read(inStream); + stlBinaryTo = STLDocument.CopyAsText(inStream, outStream); + } + + Assert.NotNull(stlBinaryFrom); + Assert.NotNull(stlBinaryTo); + Assert.Equal(stlBinaryFrom, stlBinaryTo); + } + + [Fact] + public void CopyAsBinary() + { + STLDocument stlStringFrom; + STLDocument stlStringTo; + STLDocument stlBinaryFrom; + STLDocument stlBinaryTo; + + using (var inStream = GetData("ASCII.stl")) + using (var outStream = new MemoryStream()) + { + stlStringFrom = STLDocument.Read(inStream); + stlStringTo = STLDocument.CopyAsBinary(inStream, outStream); + } + + Assert.NotNull(stlStringFrom); + Assert.NotNull(stlStringTo); + Assert.Equal(stlStringFrom, stlStringTo); + + using (var inStream = GetData("Binary.stl")) + using (var outStream = new MemoryStream()) + { + stlBinaryFrom = STLDocument.Read(inStream); + stlBinaryTo = STLDocument.CopyAsBinary(inStream, outStream); + } + + Assert.NotNull(stlBinaryFrom); + Assert.NotNull(stlBinaryTo); + Assert.Equal(stlBinaryFrom, stlBinaryTo); + } + + [Fact] + public void Equalz() + { + var stls = new STLDocument[2]; + + for (int i = 0; i < stls.Length; i++) + { + using (var stream = GetData("ASCII.stl")) + { + using (var reader = new StreamReader(stream)) + { + stls[i] = STLDocument.Read(reader); + } + } + } + + Assert.Equal(stls[0], stls[1]); + } + + [Fact] + public void AppendFacets() + { + STLDocument stl1; + STLDocument stl2; + int facetCount = 0; + + using (var stream = GetData("ASCII.stl")) + { + stl1 = STLDocument.Read(stream); + stl2 = STLDocument.Read(stream); + } + + ValidateSTL(stl1); + ValidateSTL(stl2); + + facetCount = (stl1.Facets.Count + stl2.Facets.Count); + stl1.AppendFacets(stl2); + + ValidateSTL(stl1, facetCount); + } + + [Fact] + public void ShiftFacets() + { + STLDocument stl1; + STLDocument stl2; + Vertex shift = new Vertex(100, -100, 50); + + using (var stream = GetData("ASCII.stl")) + { + stl1 = STLDocument.Read(stream); + stl2 = STLDocument.Read(stream); + } + + stl2.Facets.Shift(shift); + + for (int f = 0; f < stl1.Facets.Count; f++) + { + for (int v = 0; v < stl1.Facets[f].Vertices.Count; v++) + { + Assert.Equal(stl1.Facets[f].Vertices[v].X, stl2.Facets[f].Vertices[v].X - shift.X); + Assert.Equal(stl1.Facets[f].Vertices[v].Y, stl2.Facets[f].Vertices[v].Y - shift.Y); + Assert.Equal(stl1.Facets[f].Vertices[v].Z, stl2.Facets[f].Vertices[v].Z - shift.Z); + } + } + } + + [Fact] + public void InvertFacets() + { + STLDocument stl1; + STLDocument stl2; + + using (var stream = GetData("ASCII.stl")) + { + stl1 = STLDocument.Read(stream); + stl2 = STLDocument.Read(stream); + } + + stl2.Facets.Invert(); + + for (int f = 0; f < stl1.Facets.Count; f++) + { + for (int v = 0; v < stl1.Facets[f].Vertices.Count; v++) + { + Assert.Equal(stl1.Facets[f].Normal.X, (stl2.Facets[f].Normal.X * -1)); + Assert.Equal(stl1.Facets[f].Normal.Y, (stl2.Facets[f].Normal.Y * -1)); + Assert.Equal(stl1.Facets[f].Normal.Z, (stl2.Facets[f].Normal.Z * -1)); + } + } + } + + private Stream GetData(string filename) + { + var assembly = Assembly.GetExecutingAssembly(); + var interpolatedFilename = $"Tests.Data.{filename}"; + var stream = assembly.GetManifestResourceStream(interpolatedFilename); + + if (stream == null) + { + throw new Exception($"Failed to load resource stream: {interpolatedFilename}"); + } + else + { + return stream; + } + } + + private void ValidateSTL(STLDocument stl, int expectedFacetCount = 12) + { + Assert.NotNull(stl); + Assert.Equal(expectedFacetCount, stl.Facets.Count); + + foreach (var facet in stl.Facets) + Assert.Equal(3, facet.Vertices.Count); + } + + private void CompareSTLs(STLDocument doc1, STLDocument doc2, bool isBinary = false) + { + CompareSTLsLeftToRight(doc1, doc2, isBinary); + CompareSTLsLeftToRight(doc2, doc1, isBinary); + } + + private void CompareSTLsLeftToRight(STLDocument left, STLDocument right, bool isBinary = false) + { + if (!isBinary) right.Name.Should().Be(left.Name); + + right.Facets.Count.Should().Be(left.Facets.Count); + + for (var f = 0; f < left.Facets.Count; f++) + { + var leftFacet = left.Facets[f]; + var rightFacet = right.Facets[f]; + + if (isBinary) rightFacet.AttributeByteCount.Should().Be(leftFacet.AttributeByteCount); + + rightFacet.Normal.X.Should().Be(leftFacet.Normal.X); + rightFacet.Normal.Y.Should().Be(leftFacet.Normal.Y); + rightFacet.Normal.Z.Should().Be(leftFacet.Normal.Z); + + for (var v = 0; v < leftFacet.Vertices.Count; v++) + { + var leftVertice = leftFacet.Vertices[v]; + var rightVertice = rightFacet.Vertices[v]; + + rightVertice.X.Should().Be(leftVertice.X, $"vertices at index {v} should be equal"); + rightVertice.Y.Should().Be(leftVertice.Y, $"vertices at index {v} should be equal"); + rightVertice.Z.Should().Be(leftVertice.Z, $"vertices at index {v} should be equal"); + } + } + } + } +} diff --git a/tests/Tests.csproj b/tests/Tests.csproj new file mode 100644 index 0000000..e02a587 --- /dev/null +++ b/tests/Tests.csproj @@ -0,0 +1,35 @@ + + + + 10.0 + net6 + enable + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + From 448113cc733997876db56641edc8b90e2225d008 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Sun, 29 May 2022 14:00:01 -0400 Subject: [PATCH 2/5] formatting --- .config/omnisharp.json | 74 +++++++++++++++++++++ .editorconfig | 117 +++++++++++++++++++++++++++++++++ README.md | 14 +++- src/Extensions.cs | 4 -- src/Facet.cs | 8 --- src/Normal.cs | 8 --- src/Properties/AssemblyInfo.cs | 4 +- src/STLDocument.cs | 2 +- src/Vertex.cs | 6 -- tests/STLDocumentTests.cs | 2 +- 10 files changed, 208 insertions(+), 31 deletions(-) create mode 100644 .config/omnisharp.json create mode 100644 .editorconfig diff --git a/.config/omnisharp.json b/.config/omnisharp.json new file mode 100644 index 0000000..76d4f26 --- /dev/null +++ b/.config/omnisharp.json @@ -0,0 +1,74 @@ +{ + "FormattingOptions": { + "NewLine": "\n", + "UseTabs": false, + "TabSize": 4, + "IndentationSize": 4, + + "OrganizeImports": true, + + "SpacingAfterMethodDeclarationName": false, + "SpaceWithinMethodDeclarationParenthesis": false, + "SpaceBetweenEmptyMethodDeclarationParentheses": false, + + "SpaceAfterMethodCallName": false, + "SpaceWithinMethodCallParentheses": false, + "SpaceBetweenEmptyMethodCallParentheses": false, + + "SpaceAfterControlFlowStatementKeyword": true, + "SpaceWithinExpressionParentheses": false, + "SpaceWithinOtherParentheses": false, + + "SpaceWithinCastParentheses": false, + "SpaceAfterCast": false, + + "SpaceBeforeOpenSquareBracket": false, + "SpaceWithinSquareBrackets": false, + "SpaceBetweenEmptySquareBrackets": false, + + "SpaceBeforeColonInBaseTypeDeclaration": true, + "SpaceAfterColonInBaseTypeDeclaration": true, + + "SpaceBeforeComma": false, + "SpaceAfterComma": true, + + "SpaceBeforeDot": false, + "SpaceAfterDot": false, + + "SpaceAfterSemicolonsInForStatement": true, + "SpaceBeforeSemicolonsInForStatement": false, + + "SpacingAroundBinaryOperator": "single", + + "IndentBraces": false, + "IndentBlock": true, + + "IndentSwitchSection": true, + "IndentSwitchCaseSection": true, + "IndentSwitchCaseSectionWhenBlock": true, + + "LabelPositioning": "oneLess", + + "WrappingPreserveSingleLine": true, + "WrappingKeepStatementsOnSingleLine": true, + + "NewLinesForBracesInTypes": false, + "NewLinesForBracesInMethods": false, + "NewLinesForBracesInProperties": false, + "NewLinesForBracesInAccessors": false, + "NewLinesForBracesInAnonymousMethods": false, + "NewLinesForBracesInAnonymousTypes": false, + "NewLinesForBracesInControlBlocks": false, + "NewLinesForBracesInObjectCollectionArrayInitializers": false, + "NewLinesForBracesInLambdaExpressionBody": false, + + "NewLineForElse": true, + "NewLineForCatch": true, + "NewLineForFinally": true, + + "NewLineForMembersInObjectInit": true, + "NewLineForMembersInAnonymousTypes": true, + + "NewLineForClausesInQuery": true + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3f1b9e0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,117 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true + +[*.cs] +# Organize usings +dotnet_sort_system_directives_first = true + +# this. preferences +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:silent + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent + +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false + +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true diff --git a/README.md b/README.md index 544dca8..c2dc9a8 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,16 @@ Multiple runtimes are supported: - .NET 5 - .NET 6 - .NET Standard 2.1 -- .NET Core \ No newline at end of file +- .NET Core + +## Contributing + +To contribute a code change, please submit a Pull Request. If you find an issue, please feel free to report it via GitHub. + +If you do decide to make a code change, before pushing your branch and opening the Pull Request, please make sure you... + +- Run `dotnet build` and fix any errors _and_ warnings. +- Run `dotnet test` and fix any broken tests. +- Run `dotnet format` to automatically fix any formatting issues. + +Thank you! diff --git a/src/Extensions.cs b/src/Extensions.cs index 552c958..a920ba7 100644 --- a/src/Extensions.cs +++ b/src/Extensions.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Globalization; - namespace QuantumConcepts.Formats.StereoLithography { /// General STL extendsions for working with facets and vertices. diff --git a/src/Facet.cs b/src/Facet.cs index 36b96a2..11ad0d3 100644 --- a/src/Facet.cs +++ b/src/Facet.cs @@ -1,11 +1,3 @@ -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 { /// A representation of a facet which is defined by its location () and directionality (). diff --git a/src/Normal.cs b/src/Normal.cs index 2a1b162..11c1d17 100644 --- a/src/Normal.cs +++ b/src/Normal.cs @@ -1,11 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using QuantumConcepts.Formats.StereoLithography; -using System.Globalization; - namespace QuantumConcepts.Formats.StereoLithography { /// A simple XYZ representation of a normal (). diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs index 380c679..767d168 100644 --- a/src/Properties/AssemblyInfo.cs +++ b/src/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; [assembly: AssemblyTitle("STL Format Reader and Writer")] [assembly: AssemblyDescription("Handles reading and writing the STL format.")] @@ -7,4 +7,4 @@ [assembly: AssemblyCopyright("Copyright © Quantum Concepts Corporation")] [assembly: AssemblyTrademark("Copyright © Quantum Concepts Corporation")] [assembly: AssemblyVersion("2.0.0")] -[assembly: AssemblyFileVersion("2.0.0")] \ No newline at end of file +[assembly: AssemblyFileVersion("2.0.0")] diff --git a/src/STLDocument.cs b/src/STLDocument.cs index f25d64b..db9d317 100644 --- a/src/STLDocument.cs +++ b/src/STLDocument.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; using System.Text.RegularExpressions; namespace QuantumConcepts.Formats.StereoLithography diff --git a/src/Vertex.cs b/src/Vertex.cs index e1423a7..1f3b22d 100644 --- a/src/Vertex.cs +++ b/src/Vertex.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using QuantumConcepts.Formats.StereoLithography; namespace QuantumConcepts.Formats.StereoLithography { diff --git a/tests/STLDocumentTests.cs b/tests/STLDocumentTests.cs index 41940f4..f6692d9 100644 --- a/tests/STLDocumentTests.cs +++ b/tests/STLDocumentTests.cs @@ -1,6 +1,6 @@ -using FluentAssertions; using System.Reflection; using System.Text; +using FluentAssertions; using Xunit; namespace QuantumConcepts.Formats.StereoLithography.Test From 29f1efc4da1de09b7291a684d9fae8daf1464f2a Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Sun, 29 May 2022 14:39:52 -0400 Subject: [PATCH 3/5] add pre-commit hook --- .config/hooks/pre-push | 11 +++++++++++ .config/init-hooks | 4 ++++ README.md | 6 +----- 3 files changed, 16 insertions(+), 5 deletions(-) create mode 100755 .config/hooks/pre-push create mode 100755 .config/init-hooks diff --git a/.config/hooks/pre-push b/.config/hooks/pre-push new file mode 100755 index 0000000..8c2be38 --- /dev/null +++ b/.config/hooks/pre-push @@ -0,0 +1,11 @@ +echo 'Running pre-push hook...' + +set -e + +echo +echo 'Building and testing...' +dotnet test + +echo +echo 'Formatting code...' +dotnet format diff --git a/.config/init-hooks b/.config/init-hooks new file mode 100755 index 0000000..5695290 --- /dev/null +++ b/.config/init-hooks @@ -0,0 +1,4 @@ +#!/bin/bash + +echo 'Initializing Git hooks...' +ln -s ../../.config/hooks/pre-push ./.git/hooks/pre-push diff --git a/README.md b/README.md index c2dc9a8..8e3e06a 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,6 @@ Multiple runtimes are supported: To contribute a code change, please submit a Pull Request. If you find an issue, please feel free to report it via GitHub. -If you do decide to make a code change, before pushing your branch and opening the Pull Request, please make sure you... - -- Run `dotnet build` and fix any errors _and_ warnings. -- Run `dotnet test` and fix any broken tests. -- Run `dotnet format` to automatically fix any formatting issues. +If you do decide to make a code change, before pushing your branch and opening the Pull Request. To ensure consistent, clean formatting, please initialize the Git hooks by running: `./config/init-hooks` Thank you! From 0374c57ba2e5e0aef5fadbb0ae74520bf4f2d0c9 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Sun, 29 May 2022 14:51:17 -0400 Subject: [PATCH 4/5] add GitHub test job --- .github/workflows/test.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a3fabf9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,11 @@ +on: push +jobs: + build-and-test: + name: Build & Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-dotnet@v2.1.0 + with: + dotnet-version: 6.x + - run: dotnet test From 6ae0e643005bdc74c9f31137b0a6a28c7bc32b69 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Sun, 29 May 2022 15:25:33 -0400 Subject: [PATCH 5/5] update pre-push hook --- .config/hooks/pre-push | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.config/hooks/pre-push b/.config/hooks/pre-push index 8c2be38..cd4f457 100755 --- a/.config/hooks/pre-push +++ b/.config/hooks/pre-push @@ -9,3 +9,9 @@ dotnet test echo echo 'Formatting code...' dotnet format + +if [ `git status --porcelain=v1 2>/dev/null | wc -l` -gt 0 ]; then + echo + echo 'Found uncommitted changes (perhaps due to auto-formatting). Please commit or stash your changes and try again.' + exit 1 +fi