improve obj export of mesh with colors
This commit is contained in:
parent
cc167bd2c8
commit
6313528886
|
@ -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<const App::Color&,
|
||||
const App::Color&, bool>
|
||||
{
|
||||
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 <http://www.freecadweb.org>" << 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<App::Color> 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<App::Color>& 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<App::Color>::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<Group>::const_iterator gt = _groups.begin(); gt != _groups.end(); ++gt) {
|
||||
out << "g " << Base::Tools::escapedUnicodeFromUtf8(gt->name.c_str()) << std::endl;
|
||||
for (std::vector<unsigned long>::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<App::Color> 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<App::Color>& Kd = _material->diffuseColor;
|
||||
|
||||
for (std::vector<Group>::const_iterator gt = _groups.begin(); gt != _groups.end(); ++gt) {
|
||||
out << "g " << Base::Tools::escapedUnicodeFromUtf8(gt->name.c_str()) << std::endl;
|
||||
for (std::vector<unsigned long>::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<App::Color>::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<Group>::const_iterator gt = _groups.begin(); gt != _groups.end(); ++gt) {
|
||||
out << "g " << Base::Tools::escapedUnicodeFromUtf8(gt->name.c_str()) << std::endl;
|
||||
for (std::vector<unsigned long>::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 <http://www.freecadweb.org>: 'None'" << std::endl;
|
||||
out << "# Material Count: " << _material->diffuseColor.size() << std::endl;
|
||||
|
||||
std::vector<App::Color> 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<Kd.size(); i++) {
|
||||
out << std::endl;
|
||||
out << "newmtl material_" << i << std::endl;
|
||||
out << " Ns 10.000000" << std::endl;
|
||||
out << " Ni 1.000000" << std::endl;
|
||||
out << " d 1.000000" << std::endl;
|
||||
out << " illum 2" << std::endl;
|
||||
out << " Kd " << Kd[i].r << " " << Kd[i].g << " " << Kd[i].b << std::endl;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Saves an OFF file. */
|
||||
bool MeshOutput::SaveOFF (std::ostream &out) const
|
||||
{
|
||||
|
|
|
@ -66,6 +66,7 @@ struct MeshExport Material
|
|||
{
|
||||
Material() : binding(MeshIO::OVERALL) {}
|
||||
MeshIO::Binding binding;
|
||||
std::string library;
|
||||
std::vector<App::Color> diffuseColor;
|
||||
};
|
||||
|
||||
|
@ -151,6 +152,8 @@ public:
|
|||
* 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 @@ public:
|
|||
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. */
|
||||
|
|
|
@ -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<MeshCore::Material*>(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,
|
||||
|
|
|
@ -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<MeshGui::ViewProviderMesh*>(Gui::Application::Instance->getViewProvider(docObj));
|
||||
if (vp) {
|
||||
vp->exportMesh((const char*)fn.toUtf8(), (const char*)extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<numColors; i++) {
|
||||
const SbColor& c = colors[i];
|
||||
mat.diffuseColor.push_back(App::Color(c[0], c[1], c[2]));
|
||||
}
|
||||
|
||||
const Mesh::MeshObject& mesh = static_cast<Mesh::Feature*>(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);
|
||||
|
|
|
@ -133,6 +133,7 @@ public:
|
|||
/// returns a list of all possible modes
|
||||
virtual std::vector<std::string> 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 */
|
||||
|
|
Loading…
Reference in New Issue
Block a user