From a9949ab0a4d79480d9f52f0b5995b218df2539a8 Mon Sep 17 00:00:00 2001 From: Ian Rees Date: Sun, 5 Feb 2017 17:15:56 +1300 Subject: [PATCH] Basic AMF writing support TODO: * Compression * Understand translations, constellations * Curved surfaces * Material parameters * Metadata, etc --- src/Mod/Mesh/App/AmfExport.cpp | 161 ++++++++++++++++++++++++++++++++ src/Mod/Mesh/App/AmfExport.h | 83 ++++++++++++++++ src/Mod/Mesh/App/AppMeshPy.cpp | 72 ++++++++------ src/Mod/Mesh/App/CMakeLists.txt | 2 + 4 files changed, 290 insertions(+), 28 deletions(-) create mode 100644 src/Mod/Mesh/App/AmfExport.cpp create mode 100644 src/Mod/Mesh/App/AmfExport.h diff --git a/src/Mod/Mesh/App/AmfExport.cpp b/src/Mod/Mesh/App/AmfExport.cpp new file mode 100644 index 000000000..32401aca9 --- /dev/null +++ b/src/Mod/Mesh/App/AmfExport.cpp @@ -0,0 +1,161 @@ +/*************************************************************************** + * Copyright (c) Ian Rees * + * * + * 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 +#endif // #ifndef _PreComp_ + +#include "AmfExport.h" + +#include "Core/Iterator.h" + +#include "Base/Exception.h" +#include "Base/FileInfo.h" +#include "Base/Sequencer.h" +#include "Base/Stream.h" + +using namespace Mesh; + +AmfExporter::AmfExporter(const char *fileName) : + outputStreamPtr(nullptr), nextObjectIndex(0) +{ + // ask for write permission + Base::FileInfo fi(fileName); + Base::FileInfo di(fi.dirPath().c_str()); + if ((fi.exists() && !fi.isWritable()) || !di.exists() || !di.isWritable()) + throw Base::FileException("No write permission for file", fileName); + + outputStreamPtr = new Base::ofstream(fi, std::ios::out | std::ios::binary); + if (outputStreamPtr) { + *outputStreamPtr << "\n" + << "\n"; + } +} + +AmfExporter::~AmfExporter() +{ + if (outputStreamPtr) { + *outputStreamPtr << "\t\n"; + for (auto objId(0); objId < nextObjectIndex; ++objId) { + *outputStreamPtr << "\t\t\n" + << "\t\t\t0\n" + << "\t\t\t0\n" + << "\t\t\t0\n" + << "\t\t\n"; + } + *outputStreamPtr << "\t\n" + << "\n"; + delete outputStreamPtr; + } +} + + +int AmfExporter::addObject(const MeshCore::MeshKernel &meshKernel) +{ + if (!outputStreamPtr || outputStreamPtr->bad()) { + return -1; + } + + auto numFacets( meshKernel.CountFacets() ); + + if (numFacets == 0) { + return -1; + } + + MeshCore::MeshFacetIterator clIter(meshKernel), clEnd(meshKernel); + + Base::SequencerLauncher seq("Saving...", 2 * numFacets + 1); + + *outputStreamPtr << "\t\n" + << "\t\t\n" + << "\t\t\t\n"; + + const MeshCore::MeshGeomFacet *facet; + + // Iterate through all facets of the mesh, and construct a: + // * Cache (map) of used vertices, outputting each new unique vertex to + // the output stream as we find it + // * Vector of the vertices, referred to by the indices from 1 + std::map vertices; + auto vertItr(vertices.begin()); + auto vertexCount(0UL); + + // {facet1A, facet1B, facet1C, facet2A, ..., facetNC} + std::vector facets; + + // For each facet in mesh + for(clIter.Begin(), clEnd.End(); clIter < clEnd; ++clIter) { + facet = &(*clIter); + + // For each vertex in facet + for (auto i(0); i < 3; ++i) { + vertItr = vertices.find(facet->_aclPoints[i]); + + if ( vertItr == vertices.end() ) { + facets.push_back(vertexCount); + + vertices[facet->_aclPoints[i]] = vertexCount++; + + // Output facet + *outputStreamPtr << "\t\t\t\t\n" + << "\t\t\t\t\t\n"; + for ( auto j(0); j < 3; ++j) { + char axis('x' + j); + *outputStreamPtr << "\t\t\t\t\t\t<" << axis << '>' + << facet->_aclPoints[i][j] + << "\n"; + } + *outputStreamPtr << "\t\t\t\t\t\n" + << "\t\t\t\t\n"; + } else { + facets.push_back(vertItr->second); + } + } + + seq.next(true); // allow to cancel + } + + *outputStreamPtr << "\t\t\t\n" + << "\t\t\t\n"; + + // Now that we've output all the vertices, we can + // output the facets that refer to them! + for (auto triItr(facets.begin()); triItr != facets.end(); ) { + *outputStreamPtr << "\t\t\t\t\n"; + for (auto i(1); i < 4; ++i) { + *outputStreamPtr << "\t\t\t\t\t' + << *(triItr++) << "\n"; + } + *outputStreamPtr << "\t\t\t\t\n"; + seq.next(true); // allow to cancel + } + + *outputStreamPtr << "\t\t\t\n" + << "\t\t\n" + << "\t\n"; + + return nextObjectIndex++; +} + diff --git a/src/Mod/Mesh/App/AmfExport.h b/src/Mod/Mesh/App/AmfExport.h new file mode 100644 index 000000000..edcf1f620 --- /dev/null +++ b/src/Mod/Mesh/App/AmfExport.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * Copyright (c) Ian Rees * + * * + * 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_AMFEXPORTER_H +#define MESH_AMFEXPORTER_H + +#include "Core/MeshKernel.h" + +#ifndef _PreComp_ + #include +#endif // #ifndef _PreComp_ + +namespace Mesh +{ + +/// Used for exporting Additive Manufacturing Format (AMF) files +/*! + * The constructor and destructor write the beginning and end of the AMF, + * addObject() is used to add... objects! + */ +class AmfExporter +{ + public: + /// Writes AMF header + AmfExporter(const char *fileName); + + /// Writes AMF footer + ~AmfExporter(); + + /// Writes an object tag with data from passed-in mesh + /*! + * \return -1 on error, or the index of the object in AMF file + */ + int addObject(const MeshCore::MeshKernel &meshKernel); + + private: + std::ostream *outputStreamPtr; + int nextObjectIndex; + + /// Helper for putting Base::Vector3f objects into a std::map in addObject() + class VertLess + { + public: + bool operator()(const Base::Vector3f &a, const Base::Vector3f &b) const + { + if (a.x == b.x) { + if (a.y == b.y) { + if (a.z == b.z) { + return false; + } else { + return a.z < b.z; + } + } else { + return a.y < b.y; + } + } else { + return a.x < b.x; + } + } + }; +}; // class AmfExporter + +} // namespace Mesh +#endif // MESH_AMFEXPORTER_H diff --git a/src/Mod/Mesh/App/AppMeshPy.cpp b/src/Mod/Mesh/App/AppMeshPy.cpp index f20ac5a13..3b0ebbd8a 100644 --- a/src/Mod/Mesh/App/AppMeshPy.cpp +++ b/src/Mod/Mesh/App/AppMeshPy.cpp @@ -50,6 +50,7 @@ #include "WildMagic4/Wm4ContBox3.h" #include "Mesh.h" +#include "AmfExport.h" #include "FeatureMeshImport.h" #include @@ -301,31 +302,35 @@ private: auto exportFormat( MeshOutput::GetFormat(EncodedName.c_str()) ); - // Currently, AMF is the only export format where we export separate meshes - // into the same file. - auto combineMeshes( exportFormat != MeshIO::AMF ); - if (!combineMeshes) { - Base::Console().Message("AMF Export isn't quite supported yet."); - return Py::None(); + // TODO: Make a similar exporter class to replace global_mesh with + AmfExporter *exporter(nullptr); + if (exportFormat == MeshIO::AMF) { + exporter = new AmfExporter(EncodedName.c_str()); } - Py::Sequence list(object); Base::Type meshId = Base::Type::fromName("Mesh::Feature"); Base::Type partId = Base::Type::fromName("Part::Feature"); - for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { - PyObject* item = (*it).ptr(); + + Py::Sequence list(object); + for (auto it : list) { + PyObject* item = it.ptr(); if (PyObject_TypeCheck(item, &(App::DocumentObjectPy::Type))) { App::DocumentObject* obj = static_cast(item)->getDocumentObjectPtr(); + auto countFacets( global_mesh.countFacets() ); + if (obj->getTypeId().isDerivedFrom(meshId)) { const MeshObject& mesh = static_cast(obj)->Mesh.getValue(); MeshCore::MeshKernel kernel = mesh.getKernel(); kernel.Transform(mesh.getTransform()); - unsigned long countFacets = global_mesh.countFacets(); - if (countFacets == 0) - global_mesh.setKernel(kernel); - else - global_mesh.addMesh(kernel); + if (exporter) { + exporter->addObject(kernel); + } else { + if (countFacets == 0) + global_mesh.setKernel(kernel); + else + global_mesh.addMesh(kernel); + } // if the mesh already has persistent segments then use them instead unsigned long numSegm = mesh.countSegments(); @@ -346,7 +351,6 @@ private: global_mesh.addSegment(new_segm); } } - } else { // now create a segment for the added mesh @@ -357,11 +361,12 @@ private: segm.setName(obj->Label.getValue()); global_mesh.addSegment(segm); } - } + } // if (obj->getTypeId().isDerivedFrom(meshId)) else if (obj->getTypeId().isDerivedFrom(partId)) { App::Property* shape = obj->getPropertyByName("Shape"); - Base::Reference mesh(new MeshObject()); + if (shape && shape->getTypeId().isDerivedFrom(App::PropertyComplexGeoData::getClassTypeId())) { + Base::Reference mesh(new MeshObject()); std::vector aPoints; std::vector aTopo; const Data::ComplexGeoData* data = static_cast(shape)->getComplexData(); @@ -369,11 +374,18 @@ private: data->getFaces(aPoints, aTopo, fTolerance); mesh->addFacets(aTopo, aPoints); - unsigned long countFacets = global_mesh.countFacets(); - if (countFacets == 0) - global_mesh = *mesh; - else - global_mesh.addMesh(*mesh); + if (exporter) { + // TODO: Figure out Tranform-constellation-iterator interaction + MeshCore::MeshKernel kernel = mesh->getKernel(); + kernel.Transform(mesh->getTransform()); + exporter->addObject(kernel); + // delete mesh; + } else { + if (countFacets == 0) + global_mesh = *mesh; + else + global_mesh.addMesh(*mesh); + } // now create a segment for the added mesh std::vector indices; @@ -391,14 +403,18 @@ private: } } - // if we have more than one segment set the 'save' flag - if (global_mesh.countSegments() > 1) { - for (unsigned long i = 0; i < global_mesh.countSegments(); ++i) { - global_mesh.getSegment(i).save(true); + if (exporter) { + delete exporter; + } else { + // if we have more than one segment set the 'save' flag + if (global_mesh.countSegments() > 1) { + for (unsigned long i = 0; i < global_mesh.countSegments(); ++i) { + global_mesh.getSegment(i).save(true); + } } + // export mesh compound + global_mesh.save(EncodedName.c_str()); } - // export mesh compound - global_mesh.save(EncodedName.c_str()); return Py::None(); } diff --git a/src/Mod/Mesh/App/CMakeLists.txt b/src/Mod/Mesh/App/CMakeLists.txt index 4eaef4777..5267c4afe 100644 --- a/src/Mod/Mesh/App/CMakeLists.txt +++ b/src/Mod/Mesh/App/CMakeLists.txt @@ -316,6 +316,8 @@ SET(Mesh_SRCS ${Mesh_XML_SRCS} AppMesh.cpp AppMeshPy.cpp + AmfExport.cpp + AmfExport.h Facet.cpp Facet.h FacetPyImp.cpp