Mesh: support to read 3MF format

This commit is contained in:
wmayer
2022-09-03 16:43:56 +02:00
parent 3b29cf18be
commit 253ddde069
7 changed files with 462 additions and 0 deletions

View File

@@ -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
)

View File

@@ -0,0 +1,332 @@
/***************************************************************************
* Copyright (c) 2022 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 <memory>
# include <ostream>
# include <sstream>
# include <boost/lexical_cast.hpp>
# include <boost/tokenizer.hpp>
# include <xercesc/util/PlatformUtils.hpp>
# include <xercesc/util/XercesVersion.hpp>
# include <xercesc/dom/DOM.hpp>
# include <xercesc/dom/DOMImplementation.hpp>
# include <xercesc/dom/DOMImplementationLS.hpp>
# if (XERCES_VERSION_MAJOR == 2)
# include <xercesc/dom/DOMWriter.hpp>
# endif
# include <xercesc/framework/StdOutFormatTarget.hpp>
# include <xercesc/framework/LocalFileFormatTarget.hpp>
# include <xercesc/framework/LocalFileInputSource.hpp>
# include <xercesc/parsers/XercesDOMParser.hpp>
# include <xercesc/util/XMLUni.hpp>
# include <xercesc/util/XMLUniDefs.hpp>
# include <xercesc/util/XMLString.hpp>
# include <xercesc/sax/ErrorHandler.hpp>
# include <xercesc/sax/SAXParseException.hpp>
#endif
#include "Reader3MF.h"
#include "Core/MeshKernel.h"
#include "Core/MeshIO.h"
#include <Base/InputSource.h>
#include <Base/XMLTools.h>
using namespace MeshCore;
XERCES_CPP_NAMESPACE_USE
Reader3MF::Reader3MF(std::istream &str)
: zip(str)
{
}
Reader3MF::Reader3MF(const std::string &filename)
: zip(filename)
{
}
std::vector<int> Reader3MF::GetMeshIds() const
{
std::vector<int> 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<XercesDOMParser> 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<DOMDocument> 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<DOMElement*>(node)->getElementsByTagName(XStr("resources").unicodeForm()));
bool build = LoadBuild(static_cast<DOMElement*>(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<DOMElement*>(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<DOMElement*>(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<char> sep(" ,");
boost::tokenizer<boost::char_separator<char> > tokens(transform, sep);
std::vector<std::string> 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<DOMElement*>(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<DOMElement*>(node)->getElementsByTagName(XStr("vertices").unicodeForm()), points);
LoadTriangles(static_cast<DOMElement*>(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<DOMElement*>(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<DOMElement*>(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);
}
}
}
}
}
}
}

View File

@@ -0,0 +1,94 @@
/***************************************************************************
* Copyright (c) 2022 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 <iosfwd>
#include <unordered_map>
#include <zipios++/zipinputstream.h>
#include <xercesc/util/XercesDefs.hpp>
#include <Mod/Mesh/App/Core/MeshKernel.h>
#include <Mod/Mesh/MeshGlobal.h>
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<int> 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<MeshKernel, Base::Matrix4D>;
std::unordered_map<int, MeshKernelAndTransform> meshes;
zipios::ZipInputStream zip;
};
} // namespace MeshCore
#endif // MESH_IO_READER_3MF_H

View File

@@ -30,6 +30,7 @@
#include "Algorithm.h"
#include "Builder.h"
#include "Degeneration.h"
#include "IO/Reader3MF.h"
#include "IO/Writer3MF.h"
#include <Base/Builder3D.h>
@@ -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<int> 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)
{

View File

@@ -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. */

View File

@@ -68,6 +68,8 @@
#include <boost/algorithm/string/replace.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/tokenizer.hpp>
#endif //_PreComp_

View File

@@ -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")