From 253ddde0698e4bab6c8f374a68d0ade85edee826 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 3 Sep 2022 16:43:56 +0200 Subject: [PATCH] Mesh: support to read 3MF format --- src/Mod/Mesh/App/CMakeLists.txt | 2 + src/Mod/Mesh/App/Core/IO/Reader3MF.cpp | 332 +++++++++++++++++++++++++ src/Mod/Mesh/App/Core/IO/Reader3MF.h | 94 +++++++ src/Mod/Mesh/App/Core/MeshIO.cpp | 29 +++ src/Mod/Mesh/App/Core/MeshIO.h | 2 + src/Mod/Mesh/App/PreCompiled.h | 2 + src/Mod/Mesh/Init.py | 1 + 7 files changed, 462 insertions(+) create mode 100644 src/Mod/Mesh/App/Core/IO/Reader3MF.cpp create mode 100644 src/Mod/Mesh/App/Core/IO/Reader3MF.h diff --git a/src/Mod/Mesh/App/CMakeLists.txt b/src/Mod/Mesh/App/CMakeLists.txt index 8ac48a221f..f68a206b61 100644 --- a/src/Mod/Mesh/App/CMakeLists.txt +++ b/src/Mod/Mesh/App/CMakeLists.txt @@ -100,6 +100,8 @@ SET(Core_SRCS Core/CylinderFit.h Core/SphereFit.cpp Core/SphereFit.h + Core/IO/Reader3MF.cpp + Core/IO/Reader3MF.h Core/IO/Writer3MF.cpp Core/IO/Writer3MF.h ) diff --git a/src/Mod/Mesh/App/Core/IO/Reader3MF.cpp b/src/Mod/Mesh/App/Core/IO/Reader3MF.cpp new file mode 100644 index 0000000000..a4b0ee4381 --- /dev/null +++ b/src/Mod/Mesh/App/Core/IO/Reader3MF.cpp @@ -0,0 +1,332 @@ +/*************************************************************************** + * Copyright (c) 2022 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# if (XERCES_VERSION_MAJOR == 2) +# include +# endif +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + + +#include "Reader3MF.h" +#include "Core/MeshKernel.h" +#include "Core/MeshIO.h" +#include +#include + + +using namespace MeshCore; +XERCES_CPP_NAMESPACE_USE + +Reader3MF::Reader3MF(std::istream &str) + : zip(str) +{ +} + +Reader3MF::Reader3MF(const std::string &filename) + : zip(filename) +{ +} + +std::vector Reader3MF::GetMeshIds() const +{ + std::vector ids; + ids.reserve(meshes.size()); + for (const auto& it : meshes) { + ids.emplace_back(it.first); + } + + return ids; +} + +bool Reader3MF::Load() +{ + zipios::ConstEntryPointer entry; + try { + // The first file might already be 3dmodel.model but unfortunately the + // zipios++ API doesn't have a function to check it. So, try it blindly. + zip.rdbuf(); + if (!LoadModel(zip)) { + entry = zip.getNextEntry(); + while (entry && entry->isValid()) { + if (entry->getName() == "3D/3dmodel.model") { + return LoadModel(zip); + } + + // Note: this throws an exception if there is no further file in the zip + entry = zip.getNextEntry(); + } + return false; + } + return true; + } + catch (const std::exception&) { + return false; + } +} + +bool Reader3MF::LoadModel(std::istream& str) +{ + try { + std::unique_ptr parser(new XercesDOMParser); + parser->setValidationScheme(XercesDOMParser::Val_Auto); + parser->setDoNamespaces(false); + parser->setDoSchema(false); + parser->setValidationSchemaFullChecking(false); + parser->setCreateEntityReferenceNodes(false); + + Base::StdInputSource inputSource(str, "3dmodel.model"); + parser->parse(inputSource); + std::unique_ptr xmlDocument(parser->adoptDocument()); + return LoadModel(*xmlDocument); + } + catch (const XMLException&) { + return false; + } + catch (const DOMException&) { + return false; + } +} + +bool Reader3MF::LoadModel(DOMDocument& xmlDocument) +{ + DOMNodeList *nodes = xmlDocument.getElementsByTagName(XStr("model").unicodeForm()); + for (XMLSize_t i = 0; i < nodes->getLength(); i++) { + DOMNode* node = nodes->item(i); + if (node->getNodeType() == DOMNode::ELEMENT_NODE) { + bool resource = LoadResources(static_cast(node)->getElementsByTagName(XStr("resources").unicodeForm())); + bool build = LoadBuild(static_cast(node)->getElementsByTagName(XStr("build").unicodeForm())); + return (resource && build); + } + } + + return false; +} + +bool Reader3MF::LoadResources(DOMNodeList* nodes) +{ + if (!nodes) + return false; + + for (XMLSize_t i = 0; i < nodes->getLength(); i++) { + DOMNode* node = nodes->item(i); + if (node->getNodeType() == DOMNode::ELEMENT_NODE) { + DOMNodeList *objectList = static_cast(node)->getElementsByTagName(XStr("object").unicodeForm()); + return LoadObjects(objectList); + } + } + + return false; +} + +bool Reader3MF::LoadBuild(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList* nodes) +{ + if (!nodes) + return false; + + for (XMLSize_t i = 0; i < nodes->getLength(); i++) { + DOMNode* node = nodes->item(i); + if (node->getNodeType() == DOMNode::ELEMENT_NODE) { + DOMNodeList *objectList = static_cast(node)->getElementsByTagName(XStr("item").unicodeForm()); + return LoadItems(objectList); + } + } + + return false; +} + +bool Reader3MF::LoadItems(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList* nodes) +{ + if (!nodes) + return false; + + for (XMLSize_t i = 0; i < nodes->getLength(); i++) { + DOMNode* itemNode = nodes->item(i); + DOMNode* idAttr = itemNode->getAttributes()->getNamedItem(XStr("objectid").unicodeForm()); + if (idAttr) { + std::string id = StrX(idAttr->getNodeValue()).c_str(); + int idValue = std::stoi(id); + Base::Matrix4D mat; + + DOMNode* transformAttr = itemNode->getAttributes()->getNamedItem(XStr("transform").unicodeForm()); + if (transformAttr) { + std::string transform = StrX(transformAttr->getNodeValue()).c_str(); + boost::char_separator sep(" ,"); + boost::tokenizer > tokens(transform, sep); + std::vector token_results; + token_results.assign(tokens.begin(),tokens.end()); + if (token_results.size() == 12) { + mat[0][0] = std::stod(token_results[0]); + mat[1][0] = std::stod(token_results[1]); + mat[2][0] = std::stod(token_results[2]); + mat[0][1] = std::stod(token_results[3]); + mat[1][1] = std::stod(token_results[4]); + mat[2][1] = std::stod(token_results[5]); + mat[0][2] = std::stod(token_results[6]); + mat[1][2] = std::stod(token_results[7]); + mat[2][2] = std::stod(token_results[8]); + mat[0][3] = std::stod(token_results[9]); + mat[1][3] = std::stod(token_results[10]); + mat[2][3] = std::stod(token_results[11]); + + try { + meshes.at(idValue).second = mat; + } + catch (const std::exception&) { + } + } + } + } + } + + return true; +} + +bool Reader3MF::LoadObjects(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList* nodes) +{ + if (!nodes) + return false; + + for (XMLSize_t i = 0; i < nodes->getLength(); i++) { + DOMNode* objectNode = nodes->item(i); + if (objectNode->getNodeType() == DOMNode::ELEMENT_NODE) { + DOMNode* idAttr = objectNode->getAttributes()->getNamedItem(XStr("id").unicodeForm()); + if (idAttr) { + int id = std::stoi(StrX(idAttr->getNodeValue()).c_str()); + DOMNodeList *meshList = static_cast(objectNode)->getElementsByTagName(XStr("mesh").unicodeForm()); + LoadMesh(meshList, id); + } + } + } + + return (!meshes.empty()); +} + +void Reader3MF::LoadMesh(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList* nodes, int id) +{ + if (!nodes) + return; + + for (XMLSize_t i = 0; i < nodes->getLength(); i++) { + DOMNode* node = nodes->item(i); + if (node->getNodeType() == DOMNode::ELEMENT_NODE) { + MeshPointArray points; + MeshFacetArray facets; + LoadVertices(static_cast(node)->getElementsByTagName(XStr("vertices").unicodeForm()), points); + LoadTriangles(static_cast(node)->getElementsByTagName(XStr("triangles").unicodeForm()), facets); + + MeshCleanup meshCleanup(points, facets); + meshCleanup.RemoveInvalids(); + MeshPointFacetAdjacency meshAdj(points.size(), facets); + meshAdj.SetFacetNeighbourhood(); + + MeshKernel kernel; + kernel.Adopt(points, facets); + meshes.emplace(id, std::make_pair(kernel, Base::Matrix4D())); + } + } +} + +void Reader3MF::LoadVertices(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList* nodes, MeshPointArray& points) +{ + if (!nodes) + return; + + for (XMLSize_t i = 0; i < nodes->getLength(); i++) { + DOMNode* node = nodes->item(i); + if (node->getNodeType() == DOMNode::ELEMENT_NODE) { + DOMNodeList *vertexList = static_cast(node)->getElementsByTagName(XStr("vertex").unicodeForm()); + if (vertexList) { + XMLSize_t numVertices = vertexList->getLength(); + points.reserve(numVertices); + for (XMLSize_t j = 0; j < numVertices; j++) { + DOMNode* vertexNode = vertexList->item(j); + DOMNamedNodeMap* attr = vertexNode->getAttributes(); + if (attr) { + DOMNode* xAttr = attr->getNamedItem(XStr("x").unicodeForm()); + DOMNode* yAttr = attr->getNamedItem(XStr("y").unicodeForm()); + DOMNode* zAttr = attr->getNamedItem(XStr("z").unicodeForm()); + if (xAttr && yAttr && zAttr) { + float x = std::stof(StrX(xAttr->getNodeValue()).c_str()); + float y = std::stof(StrX(yAttr->getNodeValue()).c_str()); + float z = std::stof(StrX(zAttr->getNodeValue()).c_str()); + points.emplace_back(x, y, z); + } + } + } + } + } + } +} + +void Reader3MF::LoadTriangles(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList* nodes, MeshFacetArray& facets) +{ + if (!nodes) + return; + + for (XMLSize_t i = 0; i < nodes->getLength(); i++) { + DOMNode* node = nodes->item(i); + if (node->getNodeType() == DOMNode::ELEMENT_NODE) { + DOMNodeList *triangleList = static_cast(node)->getElementsByTagName(XStr("triangle").unicodeForm()); + if (triangleList) { + XMLSize_t numTriangles = triangleList->getLength(); + facets.reserve(numTriangles); + for (XMLSize_t j = 0; j < numTriangles; j++) { + DOMNode* triangleNode = triangleList->item(j); + DOMNamedNodeMap* attr = triangleNode->getAttributes(); + if (attr) { + DOMNode* v1Attr = attr->getNamedItem(XStr("v1").unicodeForm()); + DOMNode* v2Attr = attr->getNamedItem(XStr("v2").unicodeForm()); + DOMNode* v3Attr = attr->getNamedItem(XStr("v3").unicodeForm()); + if (v1Attr && v2Attr && v3Attr) { + PointIndex v1 = std::stoul(StrX(v1Attr->getNodeValue()).c_str()); + PointIndex v2 = std::stoul(StrX(v2Attr->getNodeValue()).c_str()); + PointIndex v3 = std::stoul(StrX(v3Attr->getNodeValue()).c_str()); + facets.emplace_back(v1, v2, v3); + } + } + } + } + } + } +} diff --git a/src/Mod/Mesh/App/Core/IO/Reader3MF.h b/src/Mod/Mesh/App/Core/IO/Reader3MF.h new file mode 100644 index 0000000000..46f1007371 --- /dev/null +++ b/src/Mod/Mesh/App/Core/IO/Reader3MF.h @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (c) 2022 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef MESH_IO_READER_3MF_H +#define MESH_IO_READER_3MF_H + +#include +#include +#include +#include +#include +#include + +XERCES_CPP_NAMESPACE_BEGIN +class DOMDocument; +class DOMNodeList; +XERCES_CPP_NAMESPACE_END + +namespace MeshCore +{ + +/** Loads the mesh object from data in 3MF format. */ +class MeshExport Reader3MF +{ +public: + /*! + * \brief Reader3MF + * \param str + * + * Passes an input stream to the constructor. + */ + Reader3MF(std::istream &str); + + /*! + * \brief Reader3MF + * \param filename + * + * Passes a file name to the constructor + */ + Reader3MF(const std::string &filename); + /*! + * \brief Load the mesh from the input stream or file + * \return true on success and false otherwise + */ + bool Load(); + std::vector GetMeshIds() const; + const MeshKernel& GetMesh(int id) const { + return meshes.at(id).first; + } + const Base::Matrix4D& GetTransform(int id) const { + return meshes.at(id).second; + } + +private: + bool LoadModel(std::istream&); + bool LoadModel(XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument&); + bool LoadResources(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList*); + bool LoadBuild(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList*); + bool LoadItems(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList*); + bool LoadObjects(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList*); + void LoadMesh(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList*, int id); + void LoadVertices(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList*, MeshPointArray&); + void LoadTriangles(XERCES_CPP_NAMESPACE_QUALIFIER DOMNodeList*, MeshFacetArray&); + +private: + using MeshKernelAndTransform = std::pair; + std::unordered_map meshes; + zipios::ZipInputStream zip; +}; + +} // namespace MeshCore + + +#endif // MESH_IO_READER_3MF_H diff --git a/src/Mod/Mesh/App/Core/MeshIO.cpp b/src/Mod/Mesh/App/Core/MeshIO.cpp index 46356904d7..c601501df1 100644 --- a/src/Mod/Mesh/App/Core/MeshIO.cpp +++ b/src/Mod/Mesh/App/Core/MeshIO.cpp @@ -30,6 +30,7 @@ #include "Algorithm.h" #include "Builder.h" #include "Degeneration.h" +#include "IO/Reader3MF.h" #include "IO/Writer3MF.h" #include @@ -189,6 +190,9 @@ bool MeshInput::LoadAny(const char* FileName) else if (fi.hasExtension("smf")) { ok = LoadSMF( str ); } + else if (fi.hasExtension("3mf")) { + ok = Load3MF( str ); + } else if (fi.hasExtension("off")) { ok = LoadOFF( str ); } @@ -222,6 +226,8 @@ bool MeshInput::LoadFormat(std::istream &str, MeshIO::Format fmt) return LoadOBJ(str); case MeshIO::SMF: return LoadSMF(str); + case MeshIO::ThreeMF: + return Load3MF(str); case MeshIO::OFF: return LoadOFF(str); case MeshIO::IV: @@ -1486,6 +1492,29 @@ void MeshInput::LoadXML (Base::XMLReader &reader) _rclMesh.Adopt(cPoints, cFacets); } +/** Loads a 3MF file. */ +bool MeshInput::Load3MF(std::istream &inp) +{ + Reader3MF reader(inp); + reader.Load(); + std::vector ids = reader.GetMeshIds(); + if (!ids.empty()) { + MeshKernel compound = reader.GetMesh(ids[0]); + compound.Transform(reader.GetTransform(ids[0])); + + for (std::size_t index = 1; index < ids.size(); index++) { + MeshKernel mesh = reader.GetMesh(ids[index]); + mesh.Transform(reader.GetTransform(ids[index])); + compound.Merge(mesh); + } + + _rclMesh = compound; + return true; + } + + return false; +} + /** Loads an OpenInventor file. */ bool MeshInput::LoadInventor (std::istream &inp) { diff --git a/src/Mod/Mesh/App/Core/MeshIO.h b/src/Mod/Mesh/App/Core/MeshIO.h index 560aceeef8..7ed54f64eb 100644 --- a/src/Mod/Mesh/App/Core/MeshIO.h +++ b/src/Mod/Mesh/App/Core/MeshIO.h @@ -125,6 +125,8 @@ public: bool LoadPLY (std::istream &rstrIn); /** Loads the mesh object from an XML file. */ void LoadXML (Base::XMLReader &reader); + /** Loads the mesh object from a 3MF file. */ + bool Load3MF (std::istream &str); /** Loads a node from an OpenInventor file. */ bool LoadMeshNode (std::istream &rstrIn); /** Loads an OpenInventor file. */ diff --git a/src/Mod/Mesh/App/PreCompiled.h b/src/Mod/Mesh/App/PreCompiled.h index d3bdb5f520..f20fa01e92 100644 --- a/src/Mod/Mesh/App/PreCompiled.h +++ b/src/Mod/Mesh/App/PreCompiled.h @@ -68,6 +68,8 @@ #include #include +#include +#include #endif //_PreComp_ diff --git a/src/Mod/Mesh/Init.py b/src/Mod/Mesh/Init.py index 8b7ee060d4..c0cbf4c1b7 100644 --- a/src/Mod/Mesh/Init.py +++ b/src/Mod/Mesh/Init.py @@ -10,6 +10,7 @@ FreeCAD.addImportType("Alias Mesh (*.obj)", "Mesh") FreeCAD.addImportType("Object File Format Mesh (*.off)", "Mesh") FreeCAD.addImportType("Stanford Triangle Mesh (*.ply)", "Mesh") FreeCAD.addImportType("Simple Model Format (*.smf)", "Mesh") +FreeCAD.addImportType("3D Manufacturing Format (*.3mf)", "Mesh") FreeCAD.addExportType("STL Mesh (*.stl *.ast)", "Mesh") FreeCAD.addExportType("Binary Mesh (*.bms)", "Mesh")