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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+