From 6313528886331176e157da5f4d18707189311c3e Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 6 Sep 2016 17:00:34 +0200 Subject: [PATCH] improve obj export of mesh with colors --- src/Mod/Mesh/App/Core/MeshIO.cpp | 243 +++++++++++++++++++++++------- src/Mod/Mesh/App/Core/MeshIO.h | 5 + src/Mod/Mesh/App/Mesh.cpp | 15 +- src/Mod/Mesh/Gui/Command.cpp | 11 +- src/Mod/Mesh/Gui/ViewProvider.cpp | 29 ++++ src/Mod/Mesh/Gui/ViewProvider.h | 1 + 6 files changed, 241 insertions(+), 63 deletions(-) diff --git a/src/Mod/Mesh/App/Core/MeshIO.cpp b/src/Mod/Mesh/App/Core/MeshIO.cpp index ca73740271e2..c0b4e15839ac 100644 --- a/src/Mod/Mesh/App/Core/MeshIO.cpp +++ b/src/Mod/Mesh/App/Core/MeshIO.cpp @@ -117,6 +117,26 @@ struct NODE {float x, y, z;}; struct TRIA {int iV[3];}; struct QUAD {int iV[4];}; +namespace MeshCore { + +struct Color_Less : public std::binary_function +{ + bool operator()(const App::Color& x, + const App::Color& y) const + { + if (x.r != y.r) + return x.r < y.r; + if (x.g != y.g) + return x.g < y.g; + if (x.b != y.b) + return x.b < y.b; + return false; // equal colors + } +}; + +} + // -------------------------------------------------------------- bool MeshInput::LoadAny(const char* FileName) @@ -1569,6 +1589,50 @@ void MeshOutput::Transform(const Base::Matrix4D& mat) apply_transform = true; } +MeshIO::Format MeshOutput::GetFormat(const char* FileName) +{ + Base::FileInfo file(FileName); + if (file.hasExtension("bms")) { + return MeshIO::BMS; + } + else if (file.hasExtension("stl")) { + return MeshIO::BSTL; + } + else if (file.hasExtension("ast")) { + return MeshIO::ASTL; + } + else if (file.hasExtension("obj")) { + return MeshIO::OBJ; + } + else if (file.hasExtension("off")) { + return MeshIO::OFF; + } + else if (file.hasExtension("ply")) { + return MeshIO::PLY; + } + else if (file.hasExtension("iv")) { + return MeshIO::IV; + } + else if (file.hasExtension("x3d")) { + return MeshIO::X3D; + } + else if (file.hasExtension("py")) { + return MeshIO::PY; + } + else if (file.hasExtension("wrl") || file.hasExtension("vrml")) { + return MeshIO::VRML; + } + else if (file.hasExtension("wrz")) { + return MeshIO::WRZ; + } + else if (file.hasExtension("nas") || file.hasExtension("bdf")) { + return MeshIO::NAS; + } + else { + return MeshIO::Undefined; + } +} + /// Save in a file, format is decided by the extension if not explicitly given bool MeshOutput::SaveAny(const char* FileName, MeshIO::Format format) const { @@ -1580,42 +1644,7 @@ bool MeshOutput::SaveAny(const char* FileName, MeshIO::Format format) const MeshIO::Format fileformat = format; if (fileformat == MeshIO::Undefined) { - if (file.hasExtension("bms")) { - fileformat = MeshIO::BMS; - } - else if (file.hasExtension("stl")) { - fileformat = MeshIO::BSTL; - } - else if (file.hasExtension("ast")) { - fileformat = MeshIO::ASTL; - } - else if (file.hasExtension("obj")) { - fileformat = MeshIO::OBJ; - } - else if (file.hasExtension("off")) { - fileformat = MeshIO::OFF; - } - else if (file.hasExtension("ply")) { - fileformat = MeshIO::PLY; - } - else if (file.hasExtension("iv")) { - fileformat = MeshIO::IV; - } - else if (file.hasExtension("x3d")) { - fileformat = MeshIO::X3D; - } - else if (file.hasExtension("py")) { - fileformat = MeshIO::PY; - } - else if (file.hasExtension("wrl") || file.hasExtension("vrml")) { - fileformat = MeshIO::VRML; - } - else if (file.hasExtension("wrz")) { - fileformat = MeshIO::WRZ; - } - else if (file.hasExtension("nas") || file.hasExtension("bdf")) { - fileformat = MeshIO::NAS; - } + fileformat = GetFormat(FileName); } Base::ofstream str(file, std::ios::out | std::ios::binary); @@ -1856,17 +1885,24 @@ bool MeshOutput::SaveOBJ (std::ostream &out) const return false; Base::SequencerLauncher seq("saving...", _rclMesh.CountPoints() + _rclMesh.CountFacets()); - bool exportColor = false; + bool exportColorPerVertex = false; + bool exportColorPerFace = false; + if (_material) { if (_material->binding == MeshIO::PER_FACE) { - Base::Console().Warning("Cannot export color information because it's defined per face"); + if (_material->diffuseColor.size() != rFacets.size()) { + Base::Console().Warning("Cannot export color information because there is a different number of faces and colors"); + } + else { + exportColorPerFace = true; + } } else if (_material->binding == MeshIO::PER_VERTEX) { if (_material->diffuseColor.size() != rPoints.size()) { Base::Console().Warning("Cannot export color information because there is a different number of points and colors"); } else { - exportColor = true; + exportColorPerVertex = true; } } else if (_material->binding == MeshIO::OVERALL) { @@ -1874,13 +1910,17 @@ bool MeshOutput::SaveOBJ (std::ostream &out) const Base::Console().Warning("Cannot export color information because there is no color defined"); } else { - exportColor = true; + exportColorPerVertex = true; } } } // Header out << "# Created by FreeCAD " << std::endl; + if (exportColorPerFace) { + out << "mtllib " << _material->library << std::endl; + } + out.precision(6); out.setf(std::ios::fixed | std::ios::showpoint); @@ -1895,7 +1935,7 @@ bool MeshOutput::SaveOBJ (std::ostream &out) const pt.Set(it->x, it->y, it->z); } - if (exportColor) { + if (exportColorPerVertex) { App::Color c; if (_material->binding == MeshIO::PER_VERTEX) { c = _material->diffuseColor[index]; @@ -1917,23 +1957,83 @@ bool MeshOutput::SaveOBJ (std::ostream &out) const } if (_groups.empty()) { - // facet indices (no texture and normal indices) - for (MeshFacetArray::_TConstIterator it = rFacets.begin(); it != rFacets.end(); ++it) { - out << "f " << it->_aulPoints[0]+1 << " " - << it->_aulPoints[1]+1 << " " - << it->_aulPoints[2]+1 << std::endl; - seq.next(true); // allow to cancel + if (exportColorPerFace) { + // facet indices (no texture and normal indices) + + // make sure to use the 'usemtl' statement as less often as possible + std::vector colors = _material->diffuseColor; + std::sort(colors.begin(), colors.end(), Color_Less()); + colors.erase(std::unique(colors.begin(), colors.end()), colors.end()); + + std::size_t index = 0; + App::Color prev; + const std::vector& Kd = _material->diffuseColor; + for (MeshFacetArray::_TConstIterator it = rFacets.begin(); it != rFacets.end(); ++it, index++) { + if (index == 0 || prev != Kd[index]) { + prev = Kd[index]; + std::vector::iterator c_it = std::find(colors.begin(), colors.end(), prev); + if (c_it != colors.end()) { + out << "usemtl material_" << (c_it - colors.begin()) << std::endl; + } + } + out << "f " << it->_aulPoints[0]+1 << " " + << it->_aulPoints[1]+1 << " " + << it->_aulPoints[2]+1 << std::endl; + seq.next(true); // allow to cancel + } + } + else { + // facet indices (no texture and normal indices) + for (MeshFacetArray::_TConstIterator it = rFacets.begin(); it != rFacets.end(); ++it) { + out << "f " << it->_aulPoints[0]+1 << " " + << it->_aulPoints[1]+1 << " " + << it->_aulPoints[2]+1 << std::endl; + seq.next(true); // allow to cancel + } } } else { - for (std::vector::const_iterator gt = _groups.begin(); gt != _groups.end(); ++gt) { - out << "g " << Base::Tools::escapedUnicodeFromUtf8(gt->name.c_str()) << std::endl; - for (std::vector::const_iterator it = gt->indices.begin(); it != gt->indices.end(); ++it) { - const MeshFacet& f = rFacets[*it]; - out << "f " << f._aulPoints[0]+1 << " " - << f._aulPoints[1]+1 << " " - << f._aulPoints[2]+1 << std::endl; - seq.next(true); // allow to cancel + if (exportColorPerFace) { + + // make sure to use the 'usemtl' statement as less often as possible + std::vector colors = _material->diffuseColor; + std::sort(colors.begin(), colors.end(), Color_Less()); + colors.erase(std::unique(colors.begin(), colors.end()), colors.end()); + + bool first = true; + App::Color prev; + const std::vector& Kd = _material->diffuseColor; + + for (std::vector::const_iterator gt = _groups.begin(); gt != _groups.end(); ++gt) { + out << "g " << Base::Tools::escapedUnicodeFromUtf8(gt->name.c_str()) << std::endl; + for (std::vector::const_iterator it = gt->indices.begin(); it != gt->indices.end(); ++it) { + const MeshFacet& f = rFacets[*it]; + if (first || prev != Kd[*it]) { + first = false; + prev = Kd[*it]; + std::vector::iterator c_it = std::find(colors.begin(), colors.end(), prev); + if (c_it != colors.end()) { + out << "usemtl material_" << (c_it - colors.begin()) << std::endl; + } + } + + out << "f " << f._aulPoints[0]+1 << " " + << f._aulPoints[1]+1 << " " + << f._aulPoints[2]+1 << std::endl; + seq.next(true); // allow to cancel + } + } + } + else { + for (std::vector::const_iterator gt = _groups.begin(); gt != _groups.end(); ++gt) { + out << "g " << Base::Tools::escapedUnicodeFromUtf8(gt->name.c_str()) << std::endl; + for (std::vector::const_iterator it = gt->indices.begin(); it != gt->indices.end(); ++it) { + const MeshFacet& f = rFacets[*it]; + out << "f " << f._aulPoints[0]+1 << " " + << f._aulPoints[1]+1 << " " + << f._aulPoints[2]+1 << std::endl; + seq.next(true); // allow to cancel + } } } } @@ -1941,6 +2041,39 @@ bool MeshOutput::SaveOBJ (std::ostream &out) const return true; } +bool MeshOutput::SaveMTL(std::ostream &out) const +{ + if (!out || out.bad()) + return false; + + if (_material) { + if (_material->binding == MeshIO::PER_FACE) { + + out.precision(6); + out.setf(std::ios::fixed | std::ios::showpoint); + out << "# Created by FreeCAD : 'None'" << std::endl; + out << "# Material Count: " << _material->diffuseColor.size() << std::endl; + + std::vector Kd = _material->diffuseColor; + std::sort(Kd.begin(), Kd.end(), Color_Less()); + Kd.erase(std::unique(Kd.begin(), Kd.end()), Kd.end()); + for (std::size_t i=0; i diffuseColor; }; @@ -151,6 +152,8 @@ class MeshExport MeshOutput * automatically filled up with spaces. */ static void SetSTLHeaderData(const std::string&); + /// Determine the mesh format by file extension + static MeshIO::Format GetFormat(const char* FileName); /// Saves the file, decided by extension if not explicitly given bool SaveAny(const char* FileName, MeshIO::Format f=MeshIO::Undefined) const; /// Saves to a stream and the given format @@ -162,6 +165,8 @@ class MeshExport MeshOutput bool SaveBinarySTL (std::ostream &rstrOut) const; /** Saves the mesh object into an OBJ file. */ bool SaveOBJ (std::ostream &rstrOut) const; + /** Saves the materials of an OBJ file. */ + bool SaveMTL(std::ostream &rstrOut) const; /** Saves the mesh object into an OFF file. */ bool SaveOFF (std::ostream &rstrOut) const; /** Saves the mesh object into a binary PLY file. */ diff --git a/src/Mod/Mesh/App/Mesh.cpp b/src/Mod/Mesh/App/Mesh.cpp index 5ef5bbf46b8e..c7ed5e7a30be 100644 --- a/src/Mod/Mesh/App/Mesh.cpp +++ b/src/Mod/Mesh/App/Mesh.cpp @@ -354,9 +354,22 @@ void MeshObject::save(const char* file, MeshCore::MeshIO::Format f, } } aWriter.SetGroups(groups); + if (mat && mat->library.empty()) { + Base::FileInfo fi(file); + const_cast(mat)->library = fi.fileNamePure() + ".mtl"; + } aWriter.Transform(this->_Mtrx); - aWriter.SaveAny(file, f); + if (aWriter.SaveAny(file, f)) { + if (mat && f == MeshCore::MeshIO::OBJ) { + Base::FileInfo fi(file); + std::string fn = fi.dirPath() + "/" + mat->library; + fi.setFile(fn); + Base::ofstream str(fi, std::ios::out | std::ios::binary); + aWriter.SaveMTL(str); + str.close(); + } + } } void MeshObject::save(std::ostream& str, MeshCore::MeshIO::Format f, diff --git a/src/Mod/Mesh/Gui/Command.cpp b/src/Mod/Mesh/Gui/Command.cpp index 2d3f1ea8e232..464f2f8db91f 100644 --- a/src/Mod/Mesh/Gui/Command.cpp +++ b/src/Mod/Mesh/Gui/Command.cpp @@ -476,13 +476,10 @@ void CmdMeshExport::activated(int iMsg) } } - //openCommand("Export Mesh"); - doCommand(Doc,"FreeCAD.ActiveDocument.getObject(\"%s\").Mesh.write(\"%s\",\"%s\",\"%s\")", - docObj->getNameInDocument(), - (const char*)fn.toUtf8(), - (const char*)extension, - docObj->Label.getValue()); - //commitCommand(); + MeshGui::ViewProviderMesh* vp = dynamic_cast(Gui::Application::Instance->getViewProvider(docObj)); + if (vp) { + vp->exportMesh((const char*)fn.toUtf8(), (const char*)extension); + } } } diff --git a/src/Mod/Mesh/Gui/ViewProvider.cpp b/src/Mod/Mesh/Gui/ViewProvider.cpp index e7038c22d109..4a138fb9423c 100644 --- a/src/Mod/Mesh/Gui/ViewProvider.cpp +++ b/src/Mod/Mesh/Gui/ViewProvider.cpp @@ -665,6 +665,35 @@ bool ViewProviderMesh::exportToVrml(const char* filename, const MeshCore::Materi return false; } +void ViewProviderMesh::exportMesh(const char* filename, const char* fmt) const +{ + MeshCore::MeshIO::Format format = MeshCore::MeshIO::Undefined; + if (fmt) { + std::string dummy = "meshfile."; + dummy += fmt; + format = MeshCore::MeshOutput::GetFormat(dummy.c_str()); + } + + MeshCore::Material mat; + int numColors = pcShapeMaterial->diffuseColor.getNum(); + const SbColor* colors = pcShapeMaterial->diffuseColor.getValues(0); + mat.diffuseColor.reserve(numColors); + for (int i=0; i(getObject())->Mesh.getValue(); + if (mat.diffuseColor.size() == mesh.countPoints()) + mat.binding = MeshCore::MeshIO::PER_VERTEX; + else if (mat.diffuseColor.size() == mesh.countFacets()) + mat.binding = MeshCore::MeshIO::PER_FACE; + else + mat.binding = MeshCore::MeshIO::OVERALL; + + mesh.save(filename, format, &mat, getObject()->Label.getValue()); +} + void ViewProviderMesh::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) { ViewProviderGeometryObject::setupContextMenu(menu, receiver, member); diff --git a/src/Mod/Mesh/Gui/ViewProvider.h b/src/Mod/Mesh/Gui/ViewProvider.h index 638c09cc8219..f1ff82bf4db3 100644 --- a/src/Mod/Mesh/Gui/ViewProvider.h +++ b/src/Mod/Mesh/Gui/ViewProvider.h @@ -133,6 +133,7 @@ class MeshGuiExport ViewProviderMesh : public Gui::ViewProviderGeometryObject /// returns a list of all possible modes virtual std::vector getDisplayModes(void) const; bool exportToVrml(const char* filename, const MeshCore::Material&, bool binary=false) const; + void exportMesh(const char* filename, const char* fmt=0) const; void setupContextMenu(QMenu*, QObject*, const char*); /** @name Editing */