diff --git a/Modules/IO/IOMeshSTL/CMakeLists.txt b/Modules/IO/IOMeshSTL/CMakeLists.txt new file mode 100644 index 000000000000..9f9ce821e7f4 --- /dev/null +++ b/Modules/IO/IOMeshSTL/CMakeLists.txt @@ -0,0 +1,4 @@ +project(IOMeshSTL) +set(IOMeshSTL_LIBRARIES IOMeshSTL) + +itk_module_impl() diff --git a/Modules/IO/IOMeshSTL/LICENSE b/Modules/IO/IOMeshSTL/LICENSE new file mode 100644 index 000000000000..62589edd12a3 --- /dev/null +++ b/Modules/IO/IOMeshSTL/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Modules/IO/IOMeshSTL/README.md b/Modules/IO/IOMeshSTL/README.md new file mode 100644 index 000000000000..41cf600827d6 --- /dev/null +++ b/Modules/IO/IOMeshSTL/README.md @@ -0,0 +1,22 @@ +# IOMeshSTL + +In-tree ITK module providing read/write support for the +stereolithography (STL) mesh format, in both ASCII and binary +encodings. STL is the canonical interchange format for 3D-printing, +CAD, and surgical-planning workflows. + +The flagship classes are `itk::STLMeshIO` and the `STLMeshIOFactory` +plug-in registration that lets `itk::MeshFileReader` / +`itk::MeshFileWriter` discover STL automatically. + +## Origin + +Ingested from the standalone remote module +[**InsightSoftwareConsortium/ITKIOMeshSTL**](https://github.com/InsightSoftwareConsortium/ITKIOMeshSTL) +on 2026-05-04 via the v4 ingestion pipeline. The upstream repository +will be archived read-only after this PR merges; it remains +reachable at the URL above for historical reference. + +## References + +- Mueller D. *STL file format reader and writer for ITK.* The Insight Journal. 2014. diff --git a/Modules/IO/IOMeshSTL/include/itkSTLMeshIO.h b/Modules/IO/IOMeshSTL/include/itkSTLMeshIO.h new file mode 100644 index 000000000000..2f04ef52c5fa --- /dev/null +++ b/Modules/IO/IOMeshSTL/include/itkSTLMeshIO.h @@ -0,0 +1,300 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ +#ifndef itkSTLMeshIO_h +#define itkSTLMeshIO_h + +#include "IOMeshSTLExport.h" + +#include "itkMeshIOBase.h" + +#include +#include + +namespace itk +{ +/** \class STLMeshIO + * \brief This class defines how to read and write STL file format. + * + * \author Luis Ibanez, Kitware Inc. + * + * \ingroup IOFilters + * \ingroup IOMeshSTL + */ +class IOMeshSTL_EXPORT STLMeshIO : public MeshIOBase +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(STLMeshIO); + + /** Standard "Self" type alias. */ + using Self = STLMeshIO; + using Superclass = MeshIOBase; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkTypeMacro(STLMeshIO, MeshIOBase); + + /**-------- This part of the interfaces deals with reading data. ----- */ + + /** Determine if the file can be read with this MeshIO implementation. + * \param FileNameToRead The name of the file to test for reading. + * \post Sets classes MeshIOBase::m_FileName variable to be FileNameToWrite + * \return Returns true if this MeshIO can read the file specified. + */ + bool + CanReadFile(const char * fileName) override; + + /** Read the content of the file into a Mesh. */ + virtual void + Read(); + + /** Set the spacing and dimension information for the set filename. */ + void + ReadMeshInformation() override; + + /** Stores the point data into the memory buffer provided. */ + void + ReadPoints(void * buffer) override; + + /** Stores the cell data into the memory buffer provided. */ + void + ReadCells(void * buffer) override; + + /** Indicates whether ReadPoints() should be called. */ + bool + GetUpdatePoints() const override; + + /** Indicates whether ReadCells() should be called. */ + bool + GetUpdateCells() const override; + + /** STL files do not carry information in points or cells. + * Therefore the following two methods are implemented as null + * operations. */ + void + ReadPointData(void * itkNotUsed(buffer)) override {}; + void + ReadCellData(void * itkNotUsed(buffer)) override {}; + + /*-------- This part of the interfaces deals with writing data. ----- */ + /** Determine if the file can be written with this MeshIO implementation. + * \param FileNameToWrite The name of the file to test for writing. + * \post Sets classes MeshIOBase::m_FileName variable to be FileNameToWrite + * \return Returns true if this MeshIO can write the file specified. + */ + bool + CanWriteFile(const char * fileName) override; + + /** Write header of the STL file */ + void + WriteMeshInformation() override; + + /** Write the content of the Mesh into an STL file. */ + void + Write() override; + + /** The STL format stores point coordinates repeatedly as part of every + * triangle. Therefore point coordinates are writen as part of the + * WriteCells() method, and not as an independent operation. + * Consequently, this method only performs an internal copy of the Point + * coordinates data, than then is used in the WriteCells() method. + */ + void + WritePoints(void * buffer) override; + + /** The WriteCells() method does most of the work. It writes + * out every triangle in the mesh. For every triangle, it + * writes out its normal, followed by the coordinates of its + * three vertices. + * + * A typical cell looks as follows in an ASCII STL file + * + * facet normal 0.357406 0.862856 0.357406 + * outer loop + * vertex 0 1 0 + * vertex 0 0.707107 0.707107 + * vertex 0.707107 0.707107 0 + * endloop + * endfacet + * + */ + void + WriteCells(void * buffer) override; + + /** STL files do not carry information in points or cells. + * Therefore the following two methods are implemented as null + * operations. */ + void + WritePointData(void * itkNotUsed(buffer)) override {}; + void + WriteCellData(void * itkNotUsed(buffer)) override {}; + +protected: + STLMeshIO(); + ~STLMeshIO() override = default; + + void + PrintSelf(std::ostream & os, Indent indent) const override; + + /** Templated version of write points method, that is aware of the specific + * type used to represent the point coordinates. */ + template + void + WritePointsTyped(const TPointsBuffer * const buffer) + { + + const unsigned int pointDimension = this->GetPointDimension(); + + if (pointDimension != 3) + { + itkExceptionMacro("STL only supports 3D points"); + } + + const TPointsBuffer * pointCoordinates = buffer; + + this->m_Points.clear(); + + const IdentifierType numberOfPoints = this->GetNumberOfPoints(); + + this->m_Points.resize(numberOfPoints); + + for (IdentifierType pi = 0; pi < numberOfPoints; ++pi) + { + for (unsigned int i = 0; i < pointDimension; ++i) + { + m_Points[pi][i] = static_cast(*pointCoordinates++); + } + } + } + + +private: + std::ofstream m_OutputStream; // output file + std::ifstream m_InputStream; // input file + + std::string m_InputLine; // helper during reading + + using PointValueType = float; // type to represent point coordinates + + using PointType = Point; + using VectorType = Vector; + using NormalType = CovariantVector; + + using PointContainerType = std::vector; + + /** Helper functions to write elements to binary file */ + void + WriteInt32AsBinary(int32_t value); + void + WriteUInt32AsBinary(uint32_t value); + void + WriteInt16AsBinary(int16_t value); + void + WriteNormalAsBinary(const NormalType & normal); + void + WritePointAsBinary(const PointType & point); + + /** Helper functions to read elements from ASCII and BINARY files. */ + void + ReadMeshInternalFromAscii(); + void + ReadMeshInternalFromBinary(); + + /** Helper functions to read elements from binary file */ + void + ReadInt32AsBinary(int32_t & value); + void + ReadUInt32AsBinary(uint32_t & value); + void + ReadInt16AsBinary(int16_t & value); + void + ReadNormalAsBinary(NormalType & normal); + void + ReadPointAsBinary(PointType & point); + + /** Helper functions to read elements from ASCII files. */ + void + ReadStringFromAscii(const std::string & expected); + void + ReadPointAsAscii(PointType & point); + bool + CheckStringFromAscii(const std::string & expected); + + /** Helper function to write cells as ASCII or BINARY. */ + virtual void + WriteCellsAsAscii(void * buffer); + virtual void + WriteCellsAsBinary(void * buffer); + + /** Functions to create set of points and disambiguate them. */ + void + InsertPointIntoSet(const PointType & point); + + PointContainerType m_Points; + + unsigned int m_InputLineNumber; + + struct PointCompare + { + bool + operator()(const PointType & p1, const PointType & p2) const + { + if (p1[0] != p2[0]) + { + return p1[0] < p2[0]; + } + else + { + if (p1[1] != p2[1]) + { + return p1[1] < p2[1]; + } + } + + return p1[2] < p2[2]; + } + }; + + using PointsMapType = std::map; + + PointsMapType m_PointsMap; + + // Helper variable to put Ids in points as they are read + IdentifierType m_LatestPointId; + + // Triplet to hold the Ids of points in a triagle as they are being read + class TripletType + { + public: + IdentifierType p0; + IdentifierType p1; + IdentifierType p2; + }; + + TripletType m_TrianglePointIds; + unsigned int m_PointInTriangleCounter; + + using CellsVectorType = std::vector; + CellsVectorType m_CellsVector; +}; +} // end namespace itk + +#endif // itkSTLMeshIO_h diff --git a/Modules/IO/IOMeshSTL/include/itkSTLMeshIOFactory.h b/Modules/IO/IOMeshSTL/include/itkSTLMeshIOFactory.h new file mode 100644 index 000000000000..4e6cc79235b2 --- /dev/null +++ b/Modules/IO/IOMeshSTL/include/itkSTLMeshIOFactory.h @@ -0,0 +1,75 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ +#ifndef itkSTLMeshIOFactory_h +#define itkSTLMeshIOFactory_h + +#include "IOMeshSTLExport.h" + +#include "itkObjectFactoryBase.h" +#include "itkMeshIOBase.h" + +namespace itk +{ +/** \class STLMeshIOFactory + * \brief Create instances of STLMeshIO objects using an object factory. + * + * \ingroup IOMeshSTL + */ +class IOMeshSTL_EXPORT STLMeshIOFactory : public ObjectFactoryBase +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(STLMeshIOFactory); + + /** Standard class type alias. */ + using Self = STLMeshIOFactory; + using Superclass = ObjectFactoryBase; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + + /** Class methods used to interface with the registered factories. */ + const char * + GetITKSourceVersion() const override; + + const char * + GetDescription() const override; + + /** Method for class instantiation. */ + itkFactorylessNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkTypeMacro(STLMeshIOFactory, ObjectFactoryBase); + + /** Register one factory of this type */ + static void + RegisterOneFactory() + { + STLMeshIOFactory::Pointer meshFactory = STLMeshIOFactory::New(); + + ObjectFactoryBase::RegisterFactory(meshFactory); + } + +protected: + STLMeshIOFactory(); + ~STLMeshIOFactory() override; + + void + PrintSelf(std::ostream & os, Indent indent) const override; +}; +} // end namespace itk + +#endif diff --git a/Modules/IO/IOMeshSTL/itk-module.cmake b/Modules/IO/IOMeshSTL/itk-module.cmake new file mode 100644 index 000000000000..7d1208b98d25 --- /dev/null +++ b/Modules/IO/IOMeshSTL/itk-module.cmake @@ -0,0 +1,25 @@ +# the top-level README is used for describing this module, just +# re-used it for documentation here +get_filename_component(MY_CURRENT_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +file(READ "${MY_CURRENT_DIR}/README.md" DOCUMENTATION) + +# itk_module() defines the module dependencies in IOMeshSTL +# The testing module in IOMeshSTL depends on ITKTestKernel +# By convention those modules outside of ITK are not prefixed with +# ITK. + +# define the dependencies of the include module and the tests +itk_module( + IOMeshSTL + ENABLE_SHARED + DEPENDS + ITKCommon + ITKIOMeshBase + TEST_DEPENDS + ITKTestKernel + ITKQuadEdgeMesh + FACTORY_NAMES + MeshIO::STL + DESCRIPTION "${DOCUMENTATION}" + EXCLUDE_FROM_DEFAULT +) diff --git a/Modules/IO/IOMeshSTL/src/CMakeLists.txt b/Modules/IO/IOMeshSTL/src/CMakeLists.txt new file mode 100644 index 000000000000..1ab151987345 --- /dev/null +++ b/Modules/IO/IOMeshSTL/src/CMakeLists.txt @@ -0,0 +1,7 @@ +set( + IOMeshSTL_SRC + itkSTLMeshIO.cxx + itkSTLMeshIOFactory.cxx +) + +itk_module_add_library(IOMeshSTL ${ITK_LIBRARY_BUILD_TYPE} ${IOMeshSTL_SRC}) diff --git a/Modules/IO/IOMeshSTL/src/itkSTLMeshIO.cxx b/Modules/IO/IOMeshSTL/src/itkSTLMeshIO.cxx new file mode 100644 index 000000000000..cdb299804421 --- /dev/null +++ b/Modules/IO/IOMeshSTL/src/itkSTLMeshIO.cxx @@ -0,0 +1,889 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include "itkSTLMeshIO.h" +#include "itkMetaDataObject.h" +#include "itkByteSwapper.h" + +#include +#include +#include + +namespace itk +{ +// Constructor +STLMeshIO ::STLMeshIO() +{ + this->AddSupportedWriteExtension(".stl"); + this->AddSupportedWriteExtension(".STL"); + + // STL uses float type by default to store point data + this->SetPointComponentType(IOComponentEnum::FLOAT); + + // STL uses UINT32 to store the number of points, + // hence the point Ids are of the same UINT32 type. + this->SetCellComponentType(IOComponentEnum::UINT); + + this->SetPointDimension(3); +} + +bool +STLMeshIO ::CanReadFile(const char * fileName) +{ + if (!itksys::SystemTools::FileExists(fileName, true)) + { + return false; + } + + const std::string extension = itksys::SystemTools::GetFilenameLastExtension(fileName); + + if (extension != ".stl" && extension != ".STL") + { + return false; + } + + return true; +} + +bool +STLMeshIO ::CanWriteFile(const char * fileName) +{ + const std::string extension = itksys::SystemTools::GetFilenameLastExtension(fileName); + + if (extension != ".stl" && extension != ".STL") + { + return false; + } + + return true; +} + +void +STLMeshIO ::Read() +{} + +void +STLMeshIO ::ReadMeshInformation() +{ + + // Use default filetype + if (this->GetFileType() == IOFileEnum::ASCII) + { + this->m_InputStream.open(this->m_FileName.c_str(), std::ios::in); + } + else if (this->GetFileType() == IOFileEnum::BINARY) + { + this->m_InputStream.open(this->m_FileName.c_str(), std::ios::in | std::ios::binary); + } + + if (!this->m_InputStream.is_open()) + { + itkExceptionMacro("Unable to open file\n" + "inputFilename= " + << this->m_FileName); + } + + + // Read STL header file(the first line) + char headerBuffer[6]; + this->m_InputStream.read(headerBuffer, 6); + this->m_InputStream.seekg(0); // Reset to the beginning of the file. + + headerBuffer[5] = '\0'; + std::string header(headerBuffer); + + bool inputFileIsASCII = false; + + // Based on the header line define whether the file is ASCII or BINARY + if (header.find("solid") != std::string::npos) + { + inputFileIsASCII = true; + } + + // Determine file type + if (inputFileIsASCII) + { + if (this->GetFileType() != IOFileEnum::ASCII) + { + this->SetFileType(IOFileEnum::ASCII); +#ifdef _WIN32 + this->m_InputStream.close(); + this->m_InputStream.open(this->m_FileName.c_str(), std::ios::in); + if (!this->m_InputStream.is_open()) + { + itkExceptionMacro("Unable to open file\n" + "inputFilename= " + << this->m_FileName); + } +#endif + } + + this->ReadMeshInternalFromAscii(); + } + else + { + if (this->GetFileType() != IOFileEnum::BINARY) + { + this->SetFileType(IOFileEnum::BINARY); +#ifdef _WIN32 + this->m_InputStream.close(); + this->m_InputStream.open(this->m_FileName.c_str(), std::ios::in | std::ios::binary); + if (!this->m_InputStream.is_open()) + { + itkExceptionMacro("Unable to open file\n" + "inputFilename= " + << this->m_FileName); + } +#endif + } + + this->ReadMeshInternalFromBinary(); + } + + this->m_InputStream.close(); +} + + +void +STLMeshIO ::ReadMeshInternalFromAscii() +{ + // Read all the points, and reduce them to unique ones + PointType p0; + PointType p1; + PointType p2; + + this->m_InputLineNumber = 2; + + this->m_LatestPointId = NumericTraits::Zero; + + // read header line + std::getline(this->m_InputStream, this->m_InputLine, '\n'); + + while (!this->CheckStringFromAscii("endsolid")) + { + // + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + // facet normal ni nj nk + // outer loop + // vertex v1x v1y v1z + // vertex v2x v2y v2z + // vertex v3x v3y v3z + // endloop + // endfacet + // + this->m_PointInTriangleCounter = 0; + + this->ReadStringFromAscii("facet normal"); + this->ReadStringFromAscii("outer loop"); + this->ReadPointAsAscii(p0); + this->ReadPointAsAscii(p1); + this->ReadPointAsAscii(p2); + this->ReadStringFromAscii("endloop"); + this->ReadStringFromAscii("endfacet"); + + this->m_CellsVector.push_back(this->m_TrianglePointIds); + } + + this->SetNumberOfPoints(this->m_PointsMap.size()); + this->SetNumberOfCells(this->m_CellsVector.size()); + + // + // The factor 5 accounts for five integers + // 1. cell type id + // 2. number of points in cell + // 3. point id of point 0 + // 4. point id of point 1 + // 5. point id of point 2 + // + this->SetCellBufferSize(5 * this->m_CellsVector.size()); +} + + +void +STLMeshIO ::ReadStringFromAscii(const std::string & expected) +{ + if (this->m_InputLine.empty()) + { + std::getline(this->m_InputStream, this->m_InputLine, '\n'); + } + + if (this->m_InputLine.find(expected) == std::string::npos) + { + itkExceptionMacro("Parsing error: missed " << expected << " in line " << this->m_InputLineNumber + << " found: " << this->m_InputLine); + } + + this->m_InputLine.clear(); + + this->m_InputLineNumber++; +} + + +bool +STLMeshIO ::CheckStringFromAscii(const std::string & expected) +{ + std::getline(this->m_InputStream, this->m_InputLine, '\n'); + + if (this->m_InputLine.find(expected) != std::string::npos) + { + this->m_InputLineNumber++; + return true; + } + + return false; +} + + +void +STLMeshIO ::ReadMeshInternalFromBinary() +{ + // + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + // UINT8[80] header + // + char header[80]; + this->m_InputStream.read(header, 80); + + header[79] = '\0'; // insert string terminator + + + this->m_LatestPointId = NumericTraits::Zero; + + // + // UINT32 -- Number of Triangles + // + uint32_t numberOfTriangles; + this->ReadUInt32AsBinary(numberOfTriangles); + + this->SetNumberOfCells(numberOfTriangles); + + // + // foreach triangle + // + NormalType normal; + PointType p0; + PointType p1; + PointType p2; + int16_t bytecount; + + while (numberOfTriangles--) + { + // + // REAL32[3] – Normal vector + // REAL32[3] – Vertex 1 + // REAL32[3] – Vertex 2 + // REAL32[3] – Vertex 3 + // UINT16 – Attribute byte count + // + this->m_PointInTriangleCounter = 0; + + this->ReadNormalAsBinary(normal); + this->ReadPointAsBinary(p0); + this->ReadPointAsBinary(p1); + this->ReadPointAsBinary(p2); + this->ReadInt16AsBinary(bytecount); + + this->m_CellsVector.push_back(this->m_TrianglePointIds); + } + + this->SetNumberOfPoints(this->m_PointsMap.size()); + + // + // The factor 5 accounts for five integers + // 1. cell type id + // 2. number of points in cell + // 3. point id of point 0 + // 4. point id of point 1 + // 5. point id of point 2 + // + this->SetCellBufferSize(5 * this->m_CellsVector.size()); +} + + +void +STLMeshIO ::ReadPoints(void * buffer) +{ + // + // The Point and Cell data were read in the ReadMeshInformation() method. + // Here, we can focus on packaging the point data into the return buffer. + // + PointsMapType::const_iterator pointItr = this->m_PointsMap.begin(); + PointsMapType::const_iterator pointEnd = this->m_PointsMap.end(); + + auto * pointsBuffer = reinterpret_cast(buffer); + + while (pointItr != pointEnd) + { + // Get the reference to that PointType object. + const PointType & point = pointItr->first; + + // Get the location in the buffer where the point should be stored + float * pointCoordinates = pointsBuffer + 3 * pointItr->second; + + // + // Store the Point coordinates in the buffer. + // + *pointCoordinates++ = point[0]; + *pointCoordinates++ = point[1]; + *pointCoordinates++ = point[2]; + + ++pointItr; + } +} + + +void +STLMeshIO ::ReadCells(void * buffer) +{ + // + // The Point and Cell data were read in the ReadMeshInformation() method. + // Here, we can focus on packaging the cell data into the return buffer. + // + CellsVectorType::const_iterator cellItr = this->m_CellsVector.begin(); + CellsVectorType::const_iterator cellEnd = this->m_CellsVector.end(); + + using CellIDType = unsigned int; + auto * cellPointIds = reinterpret_cast(buffer); + + constexpr unsigned int numberOfPointsInCell = 3; + + while (cellItr != cellEnd) + { + + *cellPointIds++ = static_cast(CellGeometryEnum::TRIANGLE_CELL); + *cellPointIds++ = numberOfPointsInCell; + + // + // Store the Point Ids for this cell, in the buffer. + // + *cellPointIds++ = cellItr->p0; + *cellPointIds++ = cellItr->p1; + *cellPointIds++ = cellItr->p2; + + ++cellItr; + } +} + + +void +STLMeshIO ::WriteMeshInformation() +{ + + // Use default filetype + if (this->GetFileType() == IOFileEnum::ASCII) + { + this->m_OutputStream.open(this->m_FileName.c_str(), std::ios::out); + } + else if (this->GetFileType() == IOFileEnum::BINARY) + { + this->m_OutputStream.open(this->m_FileName.c_str(), std::ios::out | std::ios::binary); + } + + if (!this->m_OutputStream.is_open()) + { + itkExceptionMacro("Unable to open file\n" + "outputFilename= " + << this->m_FileName); + } + + if (this->GetFileType() == IOFileEnum::ASCII) + { + this->m_OutputStream << "solid ascii" << std::endl; + } + else if (this->GetFileType() == IOFileEnum::BINARY) + { + // + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + // UINT8[80] header + // + this->m_OutputStream << std::setfill(' ') << std::setw(80) << "binary STL generated from ITK"; + } +} + + +void +STLMeshIO ::Write() +{ + // All has been done in the WriteCells() method. + + // Here we only need to close the output stream. + m_OutputStream.close(); +} + +void +STLMeshIO ::WritePoints(void * buffer) +{ + + const IOComponentEnum pointValueType = this->GetPointComponentType(); + + switch (pointValueType) + { + case IOComponentEnum::UCHAR: + { + using CoordType = unsigned char; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::CHAR: + { + using CoordType = char; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::USHORT: + { + using CoordType = unsigned short; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::SHORT: + { + using CoordType = short; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::UINT: + { + using CoordType = unsigned int; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::INT: + { + using CoordType = int; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::ULONG: + { + using CoordType = unsigned long; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::LONG: + { + using CoordType = long; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::ULONGLONG: + { + using CoordType = unsigned long long; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::LONGLONG: + { + using CoordType = long long; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::FLOAT: + { + using CoordType = float; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::DOUBLE: + { + using CoordType = double; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + case IOComponentEnum::LDOUBLE: + { + using CoordType = long double; + this->WritePointsTyped(reinterpret_cast(buffer)); + break; + } + default: + itkExceptionMacro(<< "Unknonwn point component type"); + } +} + + +void +STLMeshIO ::WriteCells(void * buffer) +{ + if (this->GetFileType() == IOFileEnum::BINARY) + { + this->WriteCellsAsBinary(buffer); + } + else + { + this->WriteCellsAsAscii(buffer); + } +} + +void +STLMeshIO ::WriteCellsAsBinary(void * buffer) +{ + + const IdentifierType numberOfPolygons = this->GetNumberOfCells(); + + const auto * cellsBuffer = reinterpret_cast(buffer); + + SizeValueType index = 0; + + NormalType normal; + + // + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + // UINT32 -- Number of Triangles + // + uint32_t numberOfTriangles = 0; + + SizeValueType index2 = 0; + + for (SizeValueType polygonItr = 0; polygonItr < numberOfPolygons; polygonItr++) + { + const auto cellType = static_cast(cellsBuffer[index2++]); + const auto numberOfVerticesInCell = static_cast(cellsBuffer[index2++]); + + const bool isTriangle = (cellType == CellGeometryEnum::TRIANGLE_CELL) || + (cellType == CellGeometryEnum::POLYGON_CELL && numberOfVerticesInCell == 3); + + if (isTriangle) + { + numberOfTriangles++; + } + else + { + itkExceptionMacro("Found Non-Triangular Cell."); + } + + index2 += numberOfVerticesInCell; + } + + this->WriteUInt32AsBinary(numberOfTriangles); + + for (SizeValueType polygonItr = 0; polygonItr < numberOfPolygons; polygonItr++) + { + // skip cell type and number of vertices in cell + index += 2; + + const PointType & p0 = m_Points[cellsBuffer[index++]]; + const PointType & p1 = m_Points[cellsBuffer[index++]]; + const PointType & p2 = m_Points[cellsBuffer[index++]]; + + const VectorType v10(p0 - p1); + const VectorType v12(p2 - p1); + + CrossProduct(normal, v12, v10); + + // + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + // foreach triangle + // REAL32[3] – Normal vector + // REAL32[3] – Vertex 1 + // REAL32[3] – Vertex 2 + // REAL32[3] – Vertex 3 + // UINT16 – Attribute byte count + // + this->WriteNormalAsBinary(normal); + this->WritePointAsBinary(p0); + this->WritePointAsBinary(p1); + this->WritePointAsBinary(p2); + this->WriteInt16AsBinary(0); + } + + // + // There is no ending section when doing BINARY + // +} + +void +STLMeshIO ::WriteCellsAsAscii(void * buffer) +{ + + const IdentifierType numberOfPolygons = this->GetNumberOfCells(); + + const auto * cellsBuffer = reinterpret_cast(buffer); + + SizeValueType index = 0; + + NormalType normal; + + for (SizeValueType polygonItr = 0; polygonItr < numberOfPolygons; polygonItr++) + { + const auto cellType = static_cast(cellsBuffer[index++]); + const auto numberOfVerticesInCell = static_cast(cellsBuffer[index++]); + + const bool isTriangle = (cellType == CellGeometryEnum::TRIANGLE_CELL) || + (cellType == CellGeometryEnum::POLYGON_CELL && numberOfVerticesInCell == 3); + + if (!isTriangle) + { + itkExceptionMacro("Found Non-Triangular Cell."); + } + + const PointType & p0 = m_Points[cellsBuffer[index++]]; + const PointType & p1 = m_Points[cellsBuffer[index++]]; + const PointType & p2 = m_Points[cellsBuffer[index++]]; + + const VectorType v10(p0 - p1); + const VectorType v12(p2 - p1); + + CrossProduct(normal, v12, v10); + + this->m_OutputStream << " facet normal "; + this->m_OutputStream << normal[0] << " " << normal[1] << " " << normal[2] << std::endl; + + this->m_OutputStream << " outer loop" << std::endl; + + this->m_OutputStream << " vertex " << p0[0] << " " << p0[1] << " " << p0[2] << std::endl; + this->m_OutputStream << " vertex " << p1[0] << " " << p1[1] << " " << p1[2] << std::endl; + this->m_OutputStream << " vertex " << p2[0] << " " << p2[1] << " " << p2[2] << std::endl; + + this->m_OutputStream << " endloop" << std::endl; + this->m_OutputStream << " endfacet" << std::endl; + } + + this->m_OutputStream << "endsolid" << std::endl; +} + +void +STLMeshIO ::WriteInt32AsBinary(int32_t value) +{ + // + // Binary values in STL files are expected to be in little endian + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + ByteSwapper::SwapFromSystemToLittleEndian(&value); + this->m_OutputStream.write(reinterpret_cast(&value), sizeof(value)); +} + + +void +STLMeshIO ::WriteUInt32AsBinary(uint32_t value) +{ + ByteSwapper::SwapFromSystemToLittleEndian(&value); + this->m_OutputStream.write(reinterpret_cast(&value), sizeof(value)); +} + + +void +STLMeshIO ::WriteInt16AsBinary(int16_t value) +{ + // + // Binary values in STL files are expected to be in little endian + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + ByteSwapper::SwapFromSystemToLittleEndian(&value); + this->m_OutputStream.write(reinterpret_cast(&value), sizeof(value)); +} + + +void +STLMeshIO ::WriteNormalAsBinary(const NormalType & normal) +{ + for (unsigned int i = 0; i < 3; ++i) + { + float value = normal[i]; + // + // Binary values in STL files are expected to be in little endian + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + ByteSwapper::SwapFromSystemToLittleEndian(&value); + this->m_OutputStream.write(reinterpret_cast(&value), sizeof(value)); + } +} + + +void +STLMeshIO ::WritePointAsBinary(const PointType & point) +{ + for (unsigned int i = 0; i < 3; ++i) + { + float value = point[i]; + // + // Binary values in STL files are expected to be in little endian + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + ByteSwapper::SwapFromSystemToLittleEndian(&value); + this->m_OutputStream.write(reinterpret_cast(&value), sizeof(value)); + } +} + + +void +STLMeshIO ::ReadNormalAsBinary(NormalType & normal) +{ + float value; + for (unsigned int i = 0; i < 3; ++i) + { + this->m_InputStream.read(reinterpret_cast(&value), sizeof(value)); + // + // Binary values in STL files are expected to be in little endian + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + ByteSwapper::SwapFromSystemToLittleEndian(&value); + normal[i] = value; + } +} + + +void +STLMeshIO ::ReadInt32AsBinary(int32_t & value) +{ + this->m_InputStream.read(reinterpret_cast(&value), sizeof(value)); + // + // Binary values in STL files are expected to be in little endian + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + ByteSwapper::SwapFromSystemToLittleEndian(&value); +} + + +void +STLMeshIO ::ReadUInt32AsBinary(uint32_t & value) +{ + this->m_InputStream.read(reinterpret_cast(&value), sizeof(value)); + ByteSwapper::SwapFromSystemToLittleEndian(&value); +} + + +void +STLMeshIO ::ReadInt16AsBinary(int16_t & value) +{ + this->m_InputStream.read(reinterpret_cast(&value), sizeof(value)); + // + // Binary values in STL files are expected to be in little endian + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + ByteSwapper::SwapFromSystemToLittleEndian(&value); +} + + +void +STLMeshIO ::ReadPointAsBinary(PointType & point) +{ + float value; + for (unsigned int i = 0; i < 3; ++i) + { + this->m_InputStream.read(reinterpret_cast(&value), sizeof(value)); + // + // Binary values in STL files are expected to be in little endian + // https://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL + // + ByteSwapper::SwapFromSystemToLittleEndian(&value); + point[i] = value; + } + + this->InsertPointIntoSet(point); +} + + +void +STLMeshIO ::ReadPointAsAscii(PointType & point) +{ + std::string keyword; + this->m_InputStream >> keyword; + + if (keyword.find("vertex") == std::string::npos) + { + itkExceptionMacro("Parsing error: missed 'vertex' in line " << this->m_InputLineNumber); + } + + this->m_InputStream >> point; + + this->InsertPointIntoSet(point); + + // read remaining of the line, most often just the end of line. + std::string restOfLine; + std::getline(this->m_InputStream, restOfLine, '\n'); + + this->m_InputLineNumber++; +} + + +void +STLMeshIO ::InsertPointIntoSet(const PointType & point) +{ + + PointsMapType::const_iterator pointMapItr = this->m_PointsMap.find(point); + + IdentifierType pointId; + + if (pointMapItr == this->m_PointsMap.end()) + { + this->m_PointsMap[point] = this->m_LatestPointId; + pointId = this->m_LatestPointId; + this->m_LatestPointId++; + } + else + { + pointId = pointMapItr->second; + } + + switch (this->m_PointInTriangleCounter) + { + case 0: + this->m_TrianglePointIds.p0 = pointId; + break; + case 1: + this->m_TrianglePointIds.p1 = pointId; + break; + case 2: + this->m_TrianglePointIds.p2 = pointId; + break; + default: + itkExceptionMacro("Point counter went beyond value 2"); + } + + this->m_PointInTriangleCounter++; + + if (this->m_PointInTriangleCounter == 3) + { + this->m_PointInTriangleCounter = 0; + } +} + + +bool +STLMeshIO ::GetUpdatePoints() const +{ + // Always true, since we are reading the point information + // in ReadMeshInformation(), and we need ReadPoints() to be + // called in order to store the point data into the buffer. + return true; +} + + +bool +STLMeshIO ::GetUpdateCells() const +{ + // Always true, since we are reading the cell information + // in ReadMeshInformation(), and we need ReadCells() to be + // called in order to store the cell data into the buffer. + return true; +} + + +void +STLMeshIO ::PrintSelf(std::ostream & os, Indent indent) const +{ + Superclass::PrintSelf(os, indent); +} + +} // end of namespace itk diff --git a/Modules/IO/IOMeshSTL/src/itkSTLMeshIOFactory.cxx b/Modules/IO/IOMeshSTL/src/itkSTLMeshIOFactory.cxx new file mode 100644 index 000000000000..8653dfe6de60 --- /dev/null +++ b/Modules/IO/IOMeshSTL/src/itkSTLMeshIOFactory.cxx @@ -0,0 +1,62 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include "IOMeshSTLExport.h" +#include "itkSTLMeshIO.h" +#include "itkSTLMeshIOFactory.h" +#include "itkVersion.h" + +namespace itk +{ +void +STLMeshIOFactory ::PrintSelf(std::ostream &, Indent) const +{} + +STLMeshIOFactory ::STLMeshIOFactory() +{ + this->RegisterOverride("itkMeshIOBase", "itkSTLMeshIO", "STL IO", true, CreateObjectFunction::New()); +} + +STLMeshIOFactory ::~STLMeshIOFactory() = default; + +const char * +STLMeshIOFactory ::GetITKSourceVersion() const +{ + return ITK_SOURCE_VERSION; +} + +const char * +STLMeshIOFactory ::GetDescription() const +{ + return "STL MeshIO Factory, allows the loading of STL QuadEdgeMesh data into ITK"; +} + +// Undocumented API used to register during static initialization. +// DO NOT CALL DIRECTLY. +static bool STLMeshIOFactoryHasBeenRegistered; + +void IOMeshSTL_EXPORT +STLMeshIOFactoryRegister__Private() +{ + if (!STLMeshIOFactoryHasBeenRegistered) + { + STLMeshIOFactoryHasBeenRegistered = true; + STLMeshIOFactory::RegisterOneFactory(); + } +} +} // end namespace itk diff --git a/Modules/IO/IOMeshSTL/test/Baseline/sphere.stl.cid b/Modules/IO/IOMeshSTL/test/Baseline/sphere.stl.cid new file mode 100644 index 000000000000..00350e636533 --- /dev/null +++ b/Modules/IO/IOMeshSTL/test/Baseline/sphere.stl.cid @@ -0,0 +1 @@ +bafkreigjpheqapzh2bfdd7prplhccxc4kr452lgkjmmbigqidlos2slrzm diff --git a/Modules/IO/IOMeshSTL/test/Baseline/sphere.vtk.cid b/Modules/IO/IOMeshSTL/test/Baseline/sphere.vtk.cid new file mode 100644 index 000000000000..fb4ce908e40b --- /dev/null +++ b/Modules/IO/IOMeshSTL/test/Baseline/sphere.vtk.cid @@ -0,0 +1 @@ +bafkreicks2ostzx2se6mfkbcgx3my5rpil4lwguga4gwomcjwo6rr5eyga diff --git a/Modules/IO/IOMeshSTL/test/Baseline/tetrahedron.stl.cid b/Modules/IO/IOMeshSTL/test/Baseline/tetrahedron.stl.cid new file mode 100644 index 000000000000..ef63e2ea1b16 --- /dev/null +++ b/Modules/IO/IOMeshSTL/test/Baseline/tetrahedron.stl.cid @@ -0,0 +1 @@ +bafkreidm3izfjpw456c5pezwg74xdiaoe7f6xeafo2jsnyyqgxeevrwj54 diff --git a/Modules/IO/IOMeshSTL/test/Baseline/tetrahedron.vtk.cid b/Modules/IO/IOMeshSTL/test/Baseline/tetrahedron.vtk.cid new file mode 100644 index 000000000000..72bffdba630a --- /dev/null +++ b/Modules/IO/IOMeshSTL/test/Baseline/tetrahedron.vtk.cid @@ -0,0 +1 @@ +bafkreihljrjpezuelqzwp5qqhhjzfnru4nvwthnjerkkffuha2t2budvpu diff --git a/Modules/IO/IOMeshSTL/test/CMakeLists.txt b/Modules/IO/IOMeshSTL/test/CMakeLists.txt new file mode 100644 index 000000000000..d667e49bfcb3 --- /dev/null +++ b/Modules/IO/IOMeshSTL/test/CMakeLists.txt @@ -0,0 +1,85 @@ +itk_module_test() + +set(IOMeshSTLTests itkSTLMeshIOTest.cxx) + +createtestdriver(IOMeshSTL "${IOMeshSTL-Test_LIBRARIES}" "${IOMeshSTLTests}" ) + +itk_add_test( + NAME itkSTLMeshIOTest00 + COMMAND + IOMeshSTLTestDriver + itkSTLMeshIOTest + DATA{Baseline/sphere.vtk} + ${ITK_TEST_OUTPUT_DIR}/sphere00.stl + 0 # write in ASCII +) + +itk_add_test( + NAME itkSTLMeshIOTest01 + COMMAND + IOMeshSTLTestDriver + itkSTLMeshIOTest + DATA{Baseline/sphere.vtk} + ${ITK_TEST_OUTPUT_DIR}/sphere01.stl + 1 # write in BINARY +) + +itk_add_test( + NAME itkSTLMeshIOTest02 + COMMAND + IOMeshSTLTestDriver + itkSTLMeshIOTest + DATA{Baseline/sphere.stl} + ${ITK_TEST_OUTPUT_DIR}/sphere02.stl + 0 # write in ASCII +) + +itk_add_test( + NAME itkSTLMeshIOTest03 + COMMAND + IOMeshSTLTestDriver + itkSTLMeshIOTest + DATA{Baseline/sphere.stl} + ${ITK_TEST_OUTPUT_DIR}/sphere03.stl + 1 # write in BINARY +) + +itk_add_test( + NAME itkSTLMeshIOTest04 + COMMAND + IOMeshSTLTestDriver + itkSTLMeshIOTest + DATA{Baseline/tetrahedron.vtk} + ${ITK_TEST_OUTPUT_DIR}/tetrahedron01.stl + 0 # write in ASCII +) + +itk_add_test( + NAME itkSTLMeshIOTest05 + COMMAND + IOMeshSTLTestDriver + itkSTLMeshIOTest + DATA{Baseline/tetrahedron.vtk} + ${ITK_TEST_OUTPUT_DIR}/tetrahedron02.stl + 1 # write in BINARY +) + +itk_add_test( + NAME itkSTLMeshIOTest06 + COMMAND + IOMeshSTLTestDriver + itkSTLMeshIOTest + DATA{Baseline/tetrahedron.stl} + ${ITK_TEST_OUTPUT_DIR}/tetrahedron03.stl + 0 # write in ASCII +) + +itk_add_test( + NAME itkSTLMeshIOTest07 + COMMAND + IOMeshSTLTestDriver + itkSTLMeshIOTest + DATA{Baseline/tetrahedron.stl} + ${ITK_TEST_OUTPUT_DIR}/tetrahedron04.stl + 1 # write in BINARY +) diff --git a/Modules/IO/IOMeshSTL/test/itkSTLMeshIOTest.cxx b/Modules/IO/IOMeshSTL/test/itkSTLMeshIOTest.cxx new file mode 100644 index 000000000000..d8c41cc2b6a6 --- /dev/null +++ b/Modules/IO/IOMeshSTL/test/itkSTLMeshIOTest.cxx @@ -0,0 +1,99 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include "itkQuadEdgeMesh.h" +#include "itkSTLMeshIOFactory.h" +#include "itkSTLMeshIO.h" +#include "itkMeshFileReader.h" +#include "itkMeshFileWriter.h" +#include "itkTestingMacros.h" + +int +itkSTLMeshIOTest(int argc, char * argv[]) +{ + if (argc < 4) + { + std::cerr << "Missing Arguments." << std::endl; + std::cerr << "Usage: " << std::endl; + std::cerr << "inputMesh outputMesh (0:ASCII/1:BINARY) " << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int Dimension = 3; + using PixelType = float; + + using QEMeshType = itk::QuadEdgeMesh; + + itk::STLMeshIOFactory::RegisterOneFactory(); + + using ReaderType = itk::MeshFileReader; + using WriterType = itk::MeshFileWriter; + + ReaderType::Pointer reader = ReaderType::New(); + WriterType::Pointer writer = WriterType::New(); + + reader->SetFileName(argv[1]); + writer->SetFileName(argv[2]); + + int fileMode = atoi(argv[3]); + + if (fileMode == 0) + { + writer->SetFileTypeAsASCII(); + } + else if (fileMode == 1) + { + writer->SetFileTypeAsBINARY(); + } + + ITK_TRY_EXPECT_NO_EXCEPTION(reader->Update()); + QEMeshType * mesh = reader->GetOutput(); + + writer->SetInput(reader->GetOutput()); + + ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update()); + + + // + // Exercising additional methods + // + itk::STLMeshIO::Pointer meshIO = itk::STLMeshIO::New(); + + ITK_EXERCISE_BASIC_OBJECT_METHODS(meshIO, STLMeshIO, MeshIOBase); + + mesh->Print(std::cout); + reader->GetMeshIO()->Print(std::cout); + writer->GetMeshIO()->Print(std::cout); + + // + // Report the System Endianness + // + std::cout << std::endl; + if (itk::ByteSwapper::SystemIsLittleEndian()) + { + std::cout << "This system is Little Endian" << std::endl; + } + else + { + std::cout << "This system is Big Endian" << std::endl; + } + + + std::cout << "Test finished." << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/IO/IOMeshSTL/wrapping/CMakeLists.txt b/Modules/IO/IOMeshSTL/wrapping/CMakeLists.txt new file mode 100644 index 000000000000..acc04954e537 --- /dev/null +++ b/Modules/IO/IOMeshSTL/wrapping/CMakeLists.txt @@ -0,0 +1,3 @@ +itk_wrap_module(IOMeshSTL) +itk_auto_load_submodules() +itk_end_wrap_module() diff --git a/Modules/IO/IOMeshSTL/wrapping/itkSTLMeshIO.wrap b/Modules/IO/IOMeshSTL/wrapping/itkSTLMeshIO.wrap new file mode 100644 index 000000000000..96be37823616 --- /dev/null +++ b/Modules/IO/IOMeshSTL/wrapping/itkSTLMeshIO.wrap @@ -0,0 +1,2 @@ +itk_wrap_simple_class("itk::STLMeshIO" POINTER) +itk_wrap_simple_class("itk::STLMeshIOFactory" POINTER) diff --git a/Modules/Remote/IOMeshSTL.remote.cmake b/Modules/Remote/IOMeshSTL.remote.cmake deleted file mode 100644 index 9c7d3c44612b..000000000000 --- a/Modules/Remote/IOMeshSTL.remote.cmake +++ /dev/null @@ -1,52 +0,0 @@ -#-- # Grading Level Criteria Report -#-- EVALUATION DATE: 2020-03-01 -#-- EVALUATORS: [<>,<>] -#-- -#-- ## Compliance level 5 star (AKA ITK main modules, or remote modules that could become core modules) -#-- - [ ] Widespread community dependance -#-- - [ ] Above 90% code coverage -#-- - [ ] CI dashboards and testing monitored rigorously -#-- - [ ] Key API features are exposed in wrapping interface -#-- - [ ] All requirements of Levels 4,3,2,1 -#-- -#-- ## Compliance Level 4 star (Very high-quality code, perhaps small community dependance) -#-- - [ ] Meets all ITK code style standards -#-- - [ ] No external requirements beyond those needed by ITK proper -#-- - [ ] Builds and passes tests on all supported platforms within 1 month of each core tagged release -#-- - [ ] Windows Shared Library Build with Visual Studio -#-- - [ ] Mac with clang compiller -#-- - [ ] Linux with gcc compiler -#-- - [ ] Active developer community dedicated to maintaining code-base -#-- - [ ] 75% code coverage demonstrated for testing suite -#-- - [ ] Continuous integration testing performed -#-- - [ ] All requirements of Levels 3,2,1 -#-- -#-- ## Compliance Level 3 star (Quality beta code) -#-- - [ ] API | executable interface is considered mostly stable and feature complete -#-- - [ ] 10% C0-code coverage demonstrated for testing suite -#-- - [ ] Some tests exist and pass on at least some platform -#-- - [X] All requirements of Levels 2,1 -#-- -#-- ## Compliance Level 2 star (Alpha code feature API development or niche community/execution environment dependance ) -#-- - [X] Compiles for at least 1 niche set of execution envirionments, and perhaps others -#-- (may depend on specific external tools like a java environment, or specific external libraries to work ) -#-- - [X] All requirements of Levels 1 -#-- -#-- ## Compliance Level 1 star (Pre-alpha features under development and code of unknown quality) -#-- - [X] Code complies on at least 1 platform -#-- -#-- ## Compliance Level 0 star ( Code/Feature of known poor-quality or deprecated status ) -#-- - [ ] Code reviewed and explicitly identified as not recommended for use -#-- -#-- ### Please document here any justification for the criteria above -# Code style enforced by clang-format on 2020-02-19, and clang-tidy modernizations completed - -# Contact: Luis Ibanez -itk_fetch_module( - IOMeshSTL - "This module contains classes for reading and writing QuadEdgeMeshes using - the STL (STereoLithography) file format. https://doi.org/10.54294/7rr2te" - MODULE_COMPLIANCE_LEVEL 2 - GIT_REPOSITORY https://github.com/InsightSoftwareConsortium/ITKIOMeshSTL.git - GIT_TAG 8b76de8678c1a4ec2cd177fd32c44099dcd29667 - ) diff --git a/pyproject.toml b/pyproject.toml index 52c56027848e..79285e9fbab4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ cmd = '''cmake -DModule_Montage:BOOL=ON -DModule_FastBilateral:BOOL=ON -DModule_GenericLabelInterpolator:BOOL=ON + -DModule_IOMeshSTL:BOOL=ON -DModule_LabelErodeDilate:BOOL=ON -DModule_MeshNoise:BOOL=ON -DModule_MGHIO:BOOL=ON