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/.config/hooks/pre-push b/.config/hooks/pre-push new file mode 100755 index 0000000..cd4f457 --- /dev/null +++ b/.config/hooks/pre-push @@ -0,0 +1,17 @@ +echo 'Running pre-push hook...' + +set -e + +echo +echo 'Building and testing...' +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 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/.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/.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 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..8e3e06a 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,24 @@ 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 + +## 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. To ensure consistent, clean formatting, please initialize the Git hooks by running: `./config/init-hooks` + +Thank you! 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 78% rename from Source/STL/Extensions.cs rename to src/Extensions.cs index 9bb2251..a920ba7 100644 --- a/Source/STL/Extensions.cs +++ b/src/Extensions.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Globalization; - 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 +43,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 +90,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 63% rename from Source/STL/Facet.cs rename to src/Facet.cs index 4e781af..11ad0d3 100644 --- a/Source/STL/Facet.cs +++ b/src/Facet.cs @@ -1,54 +1,44 @@ -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 (). 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 +48,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); + if (writer == null) throw new ArgumentNullException(nameof(writer)); - //Write each vertex. - this.Vertices.ForEach(o => o.Write(writer)); + // Write the normal. + Normal?.Write(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); - } + // Write each vertex. + Vertices.ForEach(o => o.Write(writer)); - /// 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]))); - } - - /// Iterates through the collection. - public IEnumerator GetEnumerator() - { - return this.Vertices.GetEnumerator(); - } - - /// 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 +88,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 76% rename from Source/STL/Normal.cs rename to src/Normal.cs index 146a71b..11c1d17 100644 --- a/Source/STL/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 (). @@ -20,29 +12,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 +37,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 +46,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 79% rename from Source/STL/Properties/AssemblyInfo.cs rename to src/Properties/AssemblyInfo.cs index 0ec962f..767d168 100644 --- a/Source/STL/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.")] @@ -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")] 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 0000000..3844e33 Binary files /dev/null and b/src/Resources/QC-Logo.jpg differ diff --git a/Source/STL/STLDocument.cs b/src/STLDocument.cs similarity index 64% rename from Source/STL/STLDocument.cs rename to src/STLDocument.cs index 1e33101..db9d317 100644 --- a/Source/STL/STLDocument.cs +++ b/src/STLDocument.cs @@ -1,10 +1,5 @@ -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 { @@ -16,16 +11,13 @@ public class STLDocument : IEquatable, 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 54% rename from Source/STL/Vertex.cs rename to src/Vertex.cs index 1e9e920..1f3b22d 100644 --- a/Source/STL/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 { @@ -13,13 +7,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 +22,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 +59,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 +103,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 +120,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..f6692d9 --- /dev/null +++ b/tests/STLDocumentTests.cs @@ -0,0 +1,457 @@ +using System.Reflection; +using System.Text; +using FluentAssertions; +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 + + + + + + + + + + + + + + + +