Mesh: support to read 3MF format
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
332
src/Mod/Mesh/App/Core/IO/Reader3MF.cpp
Normal file
332
src/Mod/Mesh/App/Core/IO/Reader3MF.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
src/Mod/Mesh/App/Core/IO/Reader3MF.h
Normal file
94
src/Mod/Mesh/App/Core/IO/Reader3MF.h
Normal 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
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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_
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user