Mesh: split OBJ reader and writer from MeshIO
This commit is contained in:
@@ -102,8 +102,12 @@ SET(Core_SRCS
|
||||
Core/SphereFit.h
|
||||
Core/IO/Reader3MF.cpp
|
||||
Core/IO/Reader3MF.h
|
||||
Core/IO/ReaderOBJ.cpp
|
||||
Core/IO/ReaderOBJ.h
|
||||
Core/IO/Writer3MF.cpp
|
||||
Core/IO/Writer3MF.h
|
||||
Core/IO/WriterOBJ.cpp
|
||||
Core/IO/WriterOBJ.h
|
||||
)
|
||||
SOURCE_GROUP("Core" FILES ${Core_SRCS})
|
||||
|
||||
|
||||
363
src/Mod/Mesh/App/Core/IO/ReaderOBJ.cpp
Normal file
363
src/Mod/Mesh/App/Core/IO/ReaderOBJ.cpp
Normal file
@@ -0,0 +1,363 @@
|
||||
/***************************************************************************
|
||||
* 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 <istream>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/tokenizer.hpp>
|
||||
#endif
|
||||
|
||||
|
||||
#include "ReaderOBJ.h"
|
||||
#include "Core/MeshKernel.h"
|
||||
#include "Core/MeshIO.h"
|
||||
#include <Base/Tools.h>
|
||||
|
||||
|
||||
using namespace MeshCore;
|
||||
|
||||
ReaderOBJ::ReaderOBJ(MeshKernel& kernel, Material* material)
|
||||
: _kernel(kernel)
|
||||
, _material(material)
|
||||
{
|
||||
}
|
||||
|
||||
bool ReaderOBJ::Load(std::istream &str)
|
||||
{
|
||||
boost::regex rx_m("^mtllib\\s+(.+)\\s*$");
|
||||
boost::regex rx_u("^usemtl\\s+([\\x21-\\x7E]+)\\s*$");
|
||||
boost::regex rx_g("^g\\s+([\\x21-\\x7E]+)\\s*$");
|
||||
boost::regex rx_p("^v\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)\\s*$");
|
||||
boost::regex rx_c("^v\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+(\\d{1,3})\\s+(\\d{1,3})\\s+(\\d{1,3})\\s*$");
|
||||
boost::regex rx_t("^v\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)\\s*$");
|
||||
boost::regex rx_f3("^f\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*"
|
||||
"\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*"
|
||||
"\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*\\s*$");
|
||||
boost::regex rx_f4("^f\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*"
|
||||
"\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*"
|
||||
"\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*"
|
||||
"\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*\\s*$");
|
||||
boost::cmatch what;
|
||||
|
||||
unsigned long segment=0;
|
||||
MeshPointArray meshPoints;
|
||||
MeshFacetArray meshFacets;
|
||||
|
||||
std::string line;
|
||||
float fX, fY, fZ;
|
||||
int i1=1, i2=1, i3=1, i4=1;
|
||||
MeshFacet item;
|
||||
|
||||
if (!str || str.bad())
|
||||
return false;
|
||||
|
||||
std::streambuf* buf = str.rdbuf();
|
||||
if (!buf)
|
||||
return false;
|
||||
|
||||
MeshIO::Binding rgb_value = MeshIO::OVERALL;
|
||||
bool new_segment = true;
|
||||
std::string groupName;
|
||||
std::string materialName;
|
||||
unsigned long countMaterialFacets = 0;
|
||||
|
||||
while (std::getline(str, line)) {
|
||||
if (boost::regex_match(line.c_str(), what, rx_p)) {
|
||||
fX = (float)std::atof(what[1].first);
|
||||
fY = (float)std::atof(what[4].first);
|
||||
fZ = (float)std::atof(what[7].first);
|
||||
meshPoints.push_back(MeshPoint(Base::Vector3f(fX, fY, fZ)));
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_c)) {
|
||||
fX = (float)std::atof(what[1].first);
|
||||
fY = (float)std::atof(what[4].first);
|
||||
fZ = (float)std::atof(what[7].first);
|
||||
float r = std::min<int>(std::atof(what[10].first), 255) / 255.0f;
|
||||
float g = std::min<int>(std::atof(what[11].first), 255) / 255.0f;
|
||||
float b = std::min<int>(std::atof(what[12].first), 255) / 255.0f;
|
||||
meshPoints.push_back(MeshPoint(Base::Vector3f(fX, fY, fZ)));
|
||||
|
||||
App::Color c(r,g,b);
|
||||
unsigned long prop = static_cast<uint32_t>(c.getPackedValue());
|
||||
meshPoints.back().SetProperty(prop);
|
||||
rgb_value = MeshIO::PER_VERTEX;
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_t)) {
|
||||
fX = (float)std::atof(what[1].first);
|
||||
fY = (float)std::atof(what[4].first);
|
||||
fZ = (float)std::atof(what[7].first);
|
||||
float r = static_cast<float>(std::atof(what[10].first));
|
||||
float g = static_cast<float>(std::atof(what[13].first));
|
||||
float b = static_cast<float>(std::atof(what[16].first));
|
||||
meshPoints.push_back(MeshPoint(Base::Vector3f(fX, fY, fZ)));
|
||||
|
||||
App::Color c(r,g,b);
|
||||
unsigned long prop = static_cast<uint32_t>(c.getPackedValue());
|
||||
meshPoints.back().SetProperty(prop);
|
||||
rgb_value = MeshIO::PER_VERTEX;
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_g)) {
|
||||
new_segment = true;
|
||||
groupName = Base::Tools::escapedUnicodeToUtf8(what[1].first);
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_m)) {
|
||||
if (_material)
|
||||
_material->library = Base::Tools::escapedUnicodeToUtf8(what[1].first);
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_u)) {
|
||||
if (!materialName.empty()) {
|
||||
_materialNames.emplace_back(materialName, countMaterialFacets);
|
||||
}
|
||||
materialName = Base::Tools::escapedUnicodeToUtf8(what[1].first);
|
||||
countMaterialFacets = 0;
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_f3)) {
|
||||
// starts a new segment
|
||||
if (new_segment) {
|
||||
if (!groupName.empty()) {
|
||||
_groupNames.push_back(groupName);
|
||||
groupName.clear();
|
||||
}
|
||||
new_segment = false;
|
||||
segment++;
|
||||
}
|
||||
|
||||
// 3-vertex face
|
||||
i1 = std::atoi(what[1].first);
|
||||
i1 = i1 > 0 ? i1-1 : i1+static_cast<int>(meshPoints.size());
|
||||
i2 = std::atoi(what[2].first);
|
||||
i2 = i2 > 0 ? i2-1 : i2+static_cast<int>(meshPoints.size());
|
||||
i3 = std::atoi(what[3].first);
|
||||
i3 = i3 > 0 ? i3-1 : i3+static_cast<int>(meshPoints.size());
|
||||
item.SetVertices(i1,i2,i3);
|
||||
item.SetProperty(segment);
|
||||
meshFacets.push_back(item);
|
||||
countMaterialFacets++;
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_f4)) {
|
||||
// starts a new segment
|
||||
if (new_segment) {
|
||||
if (!groupName.empty()) {
|
||||
_groupNames.push_back(groupName);
|
||||
groupName.clear();
|
||||
}
|
||||
new_segment = false;
|
||||
segment++;
|
||||
}
|
||||
|
||||
// 4-vertex face
|
||||
i1 = std::atoi(what[1].first);
|
||||
i1 = i1 > 0 ? i1-1 : i1+static_cast<int>(meshPoints.size());
|
||||
i2 = std::atoi(what[2].first);
|
||||
i2 = i2 > 0 ? i2-1 : i2+static_cast<int>(meshPoints.size());
|
||||
i3 = std::atoi(what[3].first);
|
||||
i3 = i3 > 0 ? i3-1 : i3+static_cast<int>(meshPoints.size());
|
||||
i4 = std::atoi(what[4].first);
|
||||
i4 = i4 > 0 ? i4-1 : i4+static_cast<int>(meshPoints.size());
|
||||
|
||||
item.SetVertices(i1,i2,i3);
|
||||
item.SetProperty(segment);
|
||||
meshFacets.push_back(item);
|
||||
countMaterialFacets++;
|
||||
|
||||
item.SetVertices(i3,i4,i1);
|
||||
item.SetProperty(segment);
|
||||
meshFacets.push_back(item);
|
||||
countMaterialFacets++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last added material name
|
||||
if (!materialName.empty()) {
|
||||
_materialNames.emplace_back(materialName, countMaterialFacets);
|
||||
}
|
||||
|
||||
// now get back the colors from the vertex property
|
||||
if (rgb_value == MeshIO::PER_VERTEX) {
|
||||
if (_material) {
|
||||
_material->binding = MeshIO::PER_VERTEX;
|
||||
_material->diffuseColor.reserve(meshPoints.size());
|
||||
|
||||
for (MeshPointArray::iterator it = meshPoints.begin(); it != meshPoints.end(); ++it) {
|
||||
unsigned long prop = it->_ulProp;
|
||||
App::Color c;
|
||||
c.setPackedValue(static_cast<uint32_t>(prop));
|
||||
_material->diffuseColor.push_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!materialName.empty()) {
|
||||
// At this point the materials from the .mtl file are not known and will be read-in by the calling instance
|
||||
// but the color list is pre-filled with a default value
|
||||
if (_material) {
|
||||
_material->binding = MeshIO::PER_FACE;
|
||||
_material->diffuseColor.resize(meshFacets.size(), App::Color(0.8f, 0.8f, 0.8f));
|
||||
}
|
||||
}
|
||||
|
||||
_kernel.Clear(); // remove all data before
|
||||
|
||||
MeshCleanup meshCleanup(meshPoints, meshFacets);
|
||||
if (_material)
|
||||
meshCleanup.SetMaterial(_material);
|
||||
meshCleanup.RemoveInvalids();
|
||||
MeshPointFacetAdjacency meshAdj(meshPoints.size(),meshFacets);
|
||||
meshAdj.SetFacetNeighbourhood();
|
||||
_kernel.Adopt(meshPoints,meshFacets);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReaderOBJ::LoadMaterial(std::istream &str)
|
||||
{
|
||||
std::string line;
|
||||
|
||||
if (!_material)
|
||||
return false;
|
||||
|
||||
if (!str || str.bad())
|
||||
return false;
|
||||
|
||||
std::streambuf* buf = str.rdbuf();
|
||||
if (!buf)
|
||||
return false;
|
||||
|
||||
std::map<std::string, App::Color> materialAmbientColor;
|
||||
std::map<std::string, App::Color> materialDiffuseColor;
|
||||
std::map<std::string, App::Color> materialSpecularColor;
|
||||
std::map<std::string, float> materialTransparency;
|
||||
std::string materialName;
|
||||
std::vector<App::Color> ambientColor;
|
||||
std::vector<App::Color> diffuseColor;
|
||||
std::vector<App::Color> specularColor;
|
||||
std::vector<float> transparency;
|
||||
|
||||
auto readColor = [](const std::vector<std::string>& tokens) -> App::Color {
|
||||
if (tokens.size() == 2) {
|
||||
// If only R is given then G and B will be equal
|
||||
float r = boost::lexical_cast<float>(tokens[1]);
|
||||
return App::Color(r, r, r);
|
||||
}
|
||||
else if (tokens.size() == 4) {
|
||||
float r = boost::lexical_cast<float>(tokens[1]);
|
||||
float g = boost::lexical_cast<float>(tokens[2]);
|
||||
float b = boost::lexical_cast<float>(tokens[3]);
|
||||
return App::Color(r, g, b);
|
||||
}
|
||||
|
||||
throw std::length_error("Unexpected number of tokens");
|
||||
};
|
||||
|
||||
while (std::getline(str, line)) {
|
||||
boost::char_separator<char> sep(" ");
|
||||
boost::tokenizer<boost::char_separator<char> > tokens(line, sep);
|
||||
std::vector<std::string> token_results;
|
||||
token_results.assign(tokens.begin(),tokens.end());
|
||||
|
||||
try {
|
||||
if (token_results.size() >= 2) {
|
||||
if (token_results[0] == "newmtl") {
|
||||
materialName = Base::Tools::escapedUnicodeToUtf8(token_results[1]);
|
||||
}
|
||||
else if (token_results[0] == "d") {
|
||||
float a = boost::lexical_cast<float>(token_results[1]);
|
||||
materialTransparency[materialName] = 1.0f - a;
|
||||
}
|
||||
// If only R is given then G and B will be equal
|
||||
else if (token_results[0] == "Ka") {
|
||||
materialAmbientColor[materialName] = readColor(token_results);
|
||||
}
|
||||
else if (token_results[0] == "Kd") {
|
||||
materialDiffuseColor[materialName] = readColor(token_results);
|
||||
}
|
||||
else if (token_results[0] == "Ks") {
|
||||
materialSpecularColor[materialName] = readColor(token_results);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const boost::bad_lexical_cast&) {
|
||||
}
|
||||
catch (const std::exception&) {
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = _materialNames.begin(); it != _materialNames.end(); ++it) {
|
||||
{
|
||||
auto jt = materialAmbientColor.find(it->first);
|
||||
if (jt != materialAmbientColor.end()) {
|
||||
std::vector<App::Color> mat(it->second, jt->second);
|
||||
ambientColor.insert(ambientColor.end(), mat.begin(), mat.end());
|
||||
}
|
||||
}
|
||||
{
|
||||
auto jt = materialDiffuseColor.find(it->first);
|
||||
if (jt != materialDiffuseColor.end()) {
|
||||
std::vector<App::Color> mat(it->second, jt->second);
|
||||
diffuseColor.insert(diffuseColor.end(), mat.begin(), mat.end());
|
||||
}
|
||||
}
|
||||
{
|
||||
auto jt = materialSpecularColor.find(it->first);
|
||||
if (jt != materialSpecularColor.end()) {
|
||||
std::vector<App::Color> mat(it->second, jt->second);
|
||||
specularColor.insert(specularColor.end(), mat.begin(), mat.end());
|
||||
}
|
||||
}
|
||||
{
|
||||
auto jt = materialTransparency.find(it->first);
|
||||
if (jt != materialTransparency.end()) {
|
||||
std::vector<float> transp(it->second, jt->second);
|
||||
transparency.insert(transparency.end(), transp.begin(), transp.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (diffuseColor.size() == _material->diffuseColor.size()) {
|
||||
_material->binding = MeshIO::PER_FACE;
|
||||
_material->ambientColor.swap(ambientColor);
|
||||
_material->diffuseColor.swap(diffuseColor);
|
||||
_material->specularColor.swap(specularColor);
|
||||
_material->transparency.swap(transparency);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
_material->binding = MeshIO::OVERALL;
|
||||
_material->ambientColor.clear();
|
||||
_material->diffuseColor.clear();
|
||||
_material->specularColor.clear();
|
||||
_material->transparency.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
72
src/Mod/Mesh/App/Core/IO/ReaderOBJ.h
Normal file
72
src/Mod/Mesh/App/Core/IO/ReaderOBJ.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/***************************************************************************
|
||||
* 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_OBJ_H
|
||||
#define MESH_IO_READER_OBJ_H
|
||||
|
||||
#include <iosfwd>
|
||||
#include <Mod/Mesh/App/Core/MeshKernel.h>
|
||||
#include <Mod/Mesh/MeshGlobal.h>
|
||||
|
||||
namespace MeshCore
|
||||
{
|
||||
|
||||
class MeshKernel;
|
||||
struct Material;
|
||||
|
||||
/** Loads the mesh object from data in OBJ format. */
|
||||
class MeshExport ReaderOBJ
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \brief ReaderOBJ
|
||||
*/
|
||||
explicit ReaderOBJ(MeshKernel& kernel, Material*);
|
||||
/*!
|
||||
* \brief Load the mesh from the input stream
|
||||
* \return true on success and false otherwise
|
||||
*/
|
||||
bool Load(std::istream &str);
|
||||
/*!
|
||||
* \brief Load the material file to the corresponding OBJ file.
|
||||
* This function must be called after \ref Load().
|
||||
* \param str
|
||||
* \return true on success and false otherwise
|
||||
*/
|
||||
bool LoadMaterial(std::istream &str);
|
||||
|
||||
const std::vector<std::string>& GetGroupNames() const {
|
||||
return _groupNames;
|
||||
}
|
||||
|
||||
private:
|
||||
MeshKernel& _kernel;
|
||||
Material* _material;
|
||||
std::vector<std::string> _groupNames;
|
||||
std::vector<std::pair<std::string, unsigned long> > _materialNames;
|
||||
};
|
||||
|
||||
} // namespace MeshCore
|
||||
|
||||
|
||||
#endif // MESH_IO_READER_OBJ_H
|
||||
287
src/Mod/Mesh/App/Core/IO/WriterOBJ.cpp
Normal file
287
src/Mod/Mesh/App/Core/IO/WriterOBJ.cpp
Normal file
@@ -0,0 +1,287 @@
|
||||
/***************************************************************************
|
||||
* 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_
|
||||
#endif
|
||||
|
||||
|
||||
#include "WriterOBJ.h"
|
||||
#include "Core/Iterator.h"
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Sequencer.h>
|
||||
#include <Base/Tools.h>
|
||||
|
||||
using namespace MeshCore;
|
||||
|
||||
struct WriterOBJ::Color_Less
|
||||
{
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
WriterOBJ::WriterOBJ(const MeshKernel& kernel, const Material* material)
|
||||
: _kernel(kernel)
|
||||
, _material(material)
|
||||
, apply_transform(false)
|
||||
{
|
||||
}
|
||||
|
||||
void WriterOBJ::SetGroups(const std::vector<Group>& g)
|
||||
{
|
||||
_groups = g;
|
||||
}
|
||||
|
||||
void WriterOBJ::SetTransform(const Base::Matrix4D& mat)
|
||||
{
|
||||
_transform = mat;
|
||||
if (mat != Base::Matrix4D())
|
||||
apply_transform = true;
|
||||
}
|
||||
|
||||
bool WriterOBJ::Save(std::ostream& out)
|
||||
{
|
||||
const MeshPointArray& rPoints = _kernel.GetPoints();
|
||||
const MeshFacetArray& rFacets = _kernel.GetFacets();
|
||||
|
||||
if (!out || out.bad())
|
||||
return false;
|
||||
|
||||
Base::SequencerLauncher seq("saving...", _kernel.CountPoints() + _kernel.CountFacets());
|
||||
bool exportColorPerVertex = false;
|
||||
bool exportColorPerFace = false;
|
||||
|
||||
if (_material) {
|
||||
if (_material->binding == MeshIO::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 {
|
||||
exportColorPerVertex = true;
|
||||
}
|
||||
}
|
||||
else if (_material->binding == MeshIO::OVERALL) {
|
||||
if (_material->diffuseColor.empty()) {
|
||||
Base::Console().Warning("Cannot export color information because there is no color defined");
|
||||
}
|
||||
else {
|
||||
exportColorPerVertex = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Header
|
||||
out << "# Created by FreeCAD <http://www.freecadweb.org>\n";
|
||||
if (exportColorPerFace) {
|
||||
out << "mtllib " << _material->library << '\n';
|
||||
}
|
||||
|
||||
out.precision(6);
|
||||
out.setf(std::ios::fixed | std::ios::showpoint);
|
||||
|
||||
// vertices
|
||||
Base::Vector3f pt;
|
||||
std::size_t index = 0;
|
||||
for (MeshPointArray::_TConstIterator it = rPoints.begin(); it != rPoints.end(); ++it, ++index) {
|
||||
if (this->apply_transform) {
|
||||
pt = this->_transform * *it;
|
||||
}
|
||||
else {
|
||||
pt.Set(it->x, it->y, it->z);
|
||||
}
|
||||
|
||||
if (exportColorPerVertex) {
|
||||
App::Color c;
|
||||
if (_material->binding == MeshIO::PER_VERTEX) {
|
||||
c = _material->diffuseColor[index];
|
||||
}
|
||||
else {
|
||||
c = _material->diffuseColor.front();
|
||||
}
|
||||
|
||||
int r = static_cast<int>(c.r * 255.0f);
|
||||
int g = static_cast<int>(c.g * 255.0f);
|
||||
int b = static_cast<int>(c.b * 255.0f);
|
||||
|
||||
out << "v " << pt.x << " " << pt.y << " " << pt.z << " " << r << " " << g << " " << b << '\n';
|
||||
}
|
||||
else {
|
||||
out << "v " << pt.x << " " << pt.y << " " << pt.z << '\n';
|
||||
}
|
||||
seq.next(true); // allow to cancel
|
||||
}
|
||||
// Export normals
|
||||
MeshFacetIterator clIter(_kernel), clEnd(_kernel);
|
||||
const MeshGeomFacet* pclFacet;
|
||||
|
||||
clIter.Begin();
|
||||
clEnd.End();
|
||||
|
||||
while (clIter < clEnd) {
|
||||
pclFacet = &(*clIter);
|
||||
out << "vn " << pclFacet->GetNormal().x << " "
|
||||
<< pclFacet->GetNormal().y << " "
|
||||
<< pclFacet->GetNormal().z << '\n';
|
||||
++clIter;
|
||||
seq.next(true); // allow to cancel
|
||||
}
|
||||
|
||||
if (_groups.empty()) {
|
||||
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;
|
||||
int faceIdx = 1;
|
||||
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()) << '\n';
|
||||
}
|
||||
}
|
||||
out << "f " << it->_aulPoints[0]+1 << "//" << faceIdx << " "
|
||||
<< it->_aulPoints[1]+1 << "//" << faceIdx << " "
|
||||
<< it->_aulPoints[2]+1 << "//" << faceIdx << '\n';
|
||||
seq.next(true); // allow to cancel
|
||||
faceIdx++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// facet indices (no texture and normal indices)
|
||||
std::size_t faceIdx = 1;
|
||||
for (MeshFacetArray::_TConstIterator it = rFacets.begin(); it != rFacets.end(); ++it) {
|
||||
out << "f " << it->_aulPoints[0]+1 << "//" << faceIdx << " "
|
||||
<< it->_aulPoints[1]+1 << "//" << faceIdx << " "
|
||||
<< it->_aulPoints[2]+1 << "//" << faceIdx << '\n';
|
||||
seq.next(true); // allow to cancel
|
||||
faceIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
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()) << '\n';
|
||||
for (std::vector<FacetIndex>::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()) << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
out << "f " << f._aulPoints[0]+1 << "//" << *it + 1 << " "
|
||||
<< f._aulPoints[1]+1 << "//" << *it + 1 << " "
|
||||
<< f._aulPoints[2]+1 << "//" << *it + 1 << '\n';
|
||||
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()) << '\n';
|
||||
for (std::vector<FacetIndex>::const_iterator it = gt->indices.begin(); it != gt->indices.end(); ++it) {
|
||||
const MeshFacet& f = rFacets[*it];
|
||||
out << "f " << f._aulPoints[0]+1 << "//" << *it + 1 << " "
|
||||
<< f._aulPoints[1]+1 << "//" << *it + 1 << " "
|
||||
<< f._aulPoints[2]+1 << "//" << *it + 1 << '\n';
|
||||
seq.next(true); // allow to cancel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriterOBJ::SaveMaterial(std::ostream& out)
|
||||
{
|
||||
if (!out || out.bad())
|
||||
return false;
|
||||
|
||||
if (_material) {
|
||||
if (_material->binding == MeshIO::PER_FACE) {
|
||||
|
||||
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());
|
||||
|
||||
out.precision(6);
|
||||
out.setf(std::ios::fixed | std::ios::showpoint);
|
||||
out << "# Created by FreeCAD <http://www.freecadweb.org>: 'None'\n";
|
||||
out << "# Material Count: " << Kd.size() << '\n';
|
||||
|
||||
for (std::size_t i=0; i<Kd.size(); i++) {
|
||||
out << '\n';
|
||||
out << "newmtl material_" << i << '\n';
|
||||
out << " Ka 0.200000 0.200000 0.200000\n";
|
||||
out << " Kd " << Kd[i].r << " " << Kd[i].g << " " << Kd[i].b << '\n';
|
||||
out << " Ks 1.000000 1.000000 1.000000\n";
|
||||
out << " d 1.000000" << '\n';
|
||||
out << " illum 2" << '\n';
|
||||
out << " Ns 0.000000" << '\n';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
76
src/Mod/Mesh/App/Core/IO/WriterOBJ.h
Normal file
76
src/Mod/Mesh/App/Core/IO/WriterOBJ.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/***************************************************************************
|
||||
* 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_WRITER_OBJ_H
|
||||
#define MESH_IO_WRITER_OBJ_H
|
||||
|
||||
#include <Mod/Mesh/MeshGlobal.h>
|
||||
#include <Mod/Mesh/App/Core/MeshIO.h>
|
||||
|
||||
namespace MeshCore
|
||||
{
|
||||
|
||||
/** Saves the mesh object into OBJ format. */
|
||||
class MeshExport WriterOBJ
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \brief WriterOBJ
|
||||
*/
|
||||
explicit WriterOBJ(const MeshKernel& kernel, const Material*);
|
||||
/*!
|
||||
* \brief Set the mesh groups for the OBJ export.
|
||||
* A mesh group is a list of facet indices, i.e. a mesh segment.
|
||||
* This function must be called before calling \ref Save()
|
||||
* \param g
|
||||
*/
|
||||
void SetGroups(const std::vector<Group>& g);
|
||||
/*!
|
||||
* \brief Apply a transformation for the exported mesh.
|
||||
*/
|
||||
void SetTransform(const Base::Matrix4D&);
|
||||
/*!
|
||||
* \brief Save the mesh to an OBJ file.
|
||||
* \return true if the data could be written successfully, false otherwise.
|
||||
*/
|
||||
bool Save(std::ostream&);
|
||||
/*!
|
||||
* \brief Save material file.
|
||||
* \return true on success and false otherwise.
|
||||
*/
|
||||
bool SaveMaterial(std::ostream&);
|
||||
|
||||
private:
|
||||
struct Color_Less;
|
||||
|
||||
const MeshKernel& _kernel;
|
||||
const Material* _material;
|
||||
Base::Matrix4D _transform;
|
||||
bool apply_transform;
|
||||
std::vector<Group> _groups;
|
||||
};
|
||||
|
||||
} // namespace MeshCore
|
||||
|
||||
|
||||
#endif // MESH_IO_WRITER_OBJ_H
|
||||
@@ -31,7 +31,9 @@
|
||||
#include "Builder.h"
|
||||
#include "Degeneration.h"
|
||||
#include "IO/Reader3MF.h"
|
||||
#include "IO/ReaderOBJ.h"
|
||||
#include "IO/Writer3MF.h"
|
||||
#include "IO/WriterOBJ.h"
|
||||
|
||||
#include <Base/Builder3D.h>
|
||||
#include <Base/Console.h>
|
||||
@@ -54,6 +56,7 @@
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/tokenizer.hpp>
|
||||
#include <boost/convert.hpp>
|
||||
#include <boost/convert/spirit.hpp>
|
||||
|
||||
@@ -92,21 +95,6 @@ struct NODE {float x, y, z;};
|
||||
struct TRIA {int iV[3];};
|
||||
struct QUAD {int iV[4];};
|
||||
|
||||
struct Color_Less
|
||||
{
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
@@ -185,7 +173,7 @@ bool MeshInput::LoadAny(const char* FileName)
|
||||
ok = LoadNastran( str );
|
||||
}
|
||||
else if (fi.hasExtension("obj")) {
|
||||
ok = LoadOBJ( str );
|
||||
ok = LoadOBJ( str, FileName );
|
||||
}
|
||||
else if (fi.hasExtension("smf")) {
|
||||
ok = LoadSMF( str );
|
||||
@@ -305,254 +293,33 @@ bool MeshInput::LoadSTL (std::istream &rstrIn)
|
||||
/** Loads an OBJ file. */
|
||||
bool MeshInput::LoadOBJ (std::istream &rstrIn)
|
||||
{
|
||||
boost::regex rx_m("^mtllib\\s+(.+)\\s*$");
|
||||
boost::regex rx_u("^usemtl\\s+([\\x21-\\x7E]+)\\s*$");
|
||||
boost::regex rx_g("^g\\s+([\\x21-\\x7E]+)\\s*$");
|
||||
boost::regex rx_p("^v\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)\\s*$");
|
||||
boost::regex rx_c("^v\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+(\\d{1,3})\\s+(\\d{1,3})\\s+(\\d{1,3})\\s*$");
|
||||
boost::regex rx_t("^v\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)\\s*$");
|
||||
boost::regex rx_f3("^f\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*"
|
||||
"\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*"
|
||||
"\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*\\s*$");
|
||||
boost::regex rx_f4("^f\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*"
|
||||
"\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*"
|
||||
"\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*"
|
||||
"\\s+([-+]?[0-9]+)/?[-+]?[0-9]*/?[-+]?[0-9]*\\s*$");
|
||||
boost::cmatch what;
|
||||
|
||||
unsigned long segment=0;
|
||||
MeshPointArray meshPoints;
|
||||
MeshFacetArray meshFacets;
|
||||
|
||||
std::string line;
|
||||
float fX, fY, fZ;
|
||||
int i1=1, i2=1, i3=1, i4=1;
|
||||
MeshFacet item;
|
||||
|
||||
if (!rstrIn || rstrIn.bad())
|
||||
return false;
|
||||
|
||||
std::streambuf* buf = rstrIn.rdbuf();
|
||||
if (!buf)
|
||||
return false;
|
||||
|
||||
MeshIO::Binding rgb_value = MeshIO::OVERALL;
|
||||
bool new_segment = true;
|
||||
std::string groupName;
|
||||
std::string materialName;
|
||||
unsigned long countMaterialFacets = 0;
|
||||
|
||||
while (std::getline(rstrIn, line)) {
|
||||
if (boost::regex_match(line.c_str(), what, rx_p)) {
|
||||
fX = (float)std::atof(what[1].first);
|
||||
fY = (float)std::atof(what[4].first);
|
||||
fZ = (float)std::atof(what[7].first);
|
||||
meshPoints.push_back(MeshPoint(Base::Vector3f(fX, fY, fZ)));
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_c)) {
|
||||
fX = (float)std::atof(what[1].first);
|
||||
fY = (float)std::atof(what[4].first);
|
||||
fZ = (float)std::atof(what[7].first);
|
||||
float r = std::min<int>(std::atof(what[10].first), 255) / 255.0f;
|
||||
float g = std::min<int>(std::atof(what[11].first), 255) / 255.0f;
|
||||
float b = std::min<int>(std::atof(what[12].first), 255) / 255.0f;
|
||||
meshPoints.push_back(MeshPoint(Base::Vector3f(fX, fY, fZ)));
|
||||
|
||||
App::Color c(r,g,b);
|
||||
unsigned long prop = static_cast<uint32_t>(c.getPackedValue());
|
||||
meshPoints.back().SetProperty(prop);
|
||||
rgb_value = MeshIO::PER_VERTEX;
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_t)) {
|
||||
fX = (float)std::atof(what[1].first);
|
||||
fY = (float)std::atof(what[4].first);
|
||||
fZ = (float)std::atof(what[7].first);
|
||||
float r = static_cast<float>(std::atof(what[10].first));
|
||||
float g = static_cast<float>(std::atof(what[13].first));
|
||||
float b = static_cast<float>(std::atof(what[16].first));
|
||||
meshPoints.push_back(MeshPoint(Base::Vector3f(fX, fY, fZ)));
|
||||
|
||||
App::Color c(r,g,b);
|
||||
unsigned long prop = static_cast<uint32_t>(c.getPackedValue());
|
||||
meshPoints.back().SetProperty(prop);
|
||||
rgb_value = MeshIO::PER_VERTEX;
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_g)) {
|
||||
new_segment = true;
|
||||
groupName = Base::Tools::escapedUnicodeToUtf8(what[1].first);
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_m)) {
|
||||
if (_material)
|
||||
_material->library = Base::Tools::escapedUnicodeToUtf8(what[1].first);
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_u)) {
|
||||
if (!materialName.empty()) {
|
||||
_materialNames.emplace_back(materialName, countMaterialFacets);
|
||||
}
|
||||
materialName = Base::Tools::escapedUnicodeToUtf8(what[1].first);
|
||||
countMaterialFacets = 0;
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_f3)) {
|
||||
// starts a new segment
|
||||
if (new_segment) {
|
||||
if (!groupName.empty()) {
|
||||
_groupNames.push_back(groupName);
|
||||
groupName.clear();
|
||||
}
|
||||
new_segment = false;
|
||||
segment++;
|
||||
}
|
||||
|
||||
// 3-vertex face
|
||||
i1 = std::atoi(what[1].first);
|
||||
i1 = i1 > 0 ? i1-1 : i1+static_cast<int>(meshPoints.size());
|
||||
i2 = std::atoi(what[2].first);
|
||||
i2 = i2 > 0 ? i2-1 : i2+static_cast<int>(meshPoints.size());
|
||||
i3 = std::atoi(what[3].first);
|
||||
i3 = i3 > 0 ? i3-1 : i3+static_cast<int>(meshPoints.size());
|
||||
item.SetVertices(i1,i2,i3);
|
||||
item.SetProperty(segment);
|
||||
meshFacets.push_back(item);
|
||||
countMaterialFacets++;
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_f4)) {
|
||||
// starts a new segment
|
||||
if (new_segment) {
|
||||
if (!groupName.empty()) {
|
||||
_groupNames.push_back(groupName);
|
||||
groupName.clear();
|
||||
}
|
||||
new_segment = false;
|
||||
segment++;
|
||||
}
|
||||
|
||||
// 4-vertex face
|
||||
i1 = std::atoi(what[1].first);
|
||||
i1 = i1 > 0 ? i1-1 : i1+static_cast<int>(meshPoints.size());
|
||||
i2 = std::atoi(what[2].first);
|
||||
i2 = i2 > 0 ? i2-1 : i2+static_cast<int>(meshPoints.size());
|
||||
i3 = std::atoi(what[3].first);
|
||||
i3 = i3 > 0 ? i3-1 : i3+static_cast<int>(meshPoints.size());
|
||||
i4 = std::atoi(what[4].first);
|
||||
i4 = i4 > 0 ? i4-1 : i4+static_cast<int>(meshPoints.size());
|
||||
|
||||
item.SetVertices(i1,i2,i3);
|
||||
item.SetProperty(segment);
|
||||
meshFacets.push_back(item);
|
||||
countMaterialFacets++;
|
||||
|
||||
item.SetVertices(i3,i4,i1);
|
||||
item.SetProperty(segment);
|
||||
meshFacets.push_back(item);
|
||||
countMaterialFacets++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last added material name
|
||||
if (!materialName.empty()) {
|
||||
_materialNames.emplace_back(materialName, countMaterialFacets);
|
||||
}
|
||||
|
||||
// now get back the colors from the vertex property
|
||||
if (rgb_value == MeshIO::PER_VERTEX) {
|
||||
if (_material) {
|
||||
_material->binding = MeshIO::PER_VERTEX;
|
||||
_material->diffuseColor.reserve(meshPoints.size());
|
||||
|
||||
for (MeshPointArray::iterator it = meshPoints.begin(); it != meshPoints.end(); ++it) {
|
||||
unsigned long prop = it->_ulProp;
|
||||
App::Color c;
|
||||
c.setPackedValue(static_cast<uint32_t>(prop));
|
||||
_material->diffuseColor.push_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!materialName.empty()) {
|
||||
// At this point the materials from the .mtl file are not known and will be read-in by the calling instance
|
||||
// but the color list is pre-filled with a default value
|
||||
if (_material) {
|
||||
_material->binding = MeshIO::PER_FACE;
|
||||
_material->diffuseColor.resize(meshFacets.size(), App::Color(0.8f, 0.8f, 0.8f));
|
||||
}
|
||||
}
|
||||
|
||||
this->_rclMesh.Clear(); // remove all data before
|
||||
|
||||
MeshCleanup meshCleanup(meshPoints, meshFacets);
|
||||
if (_material)
|
||||
meshCleanup.SetMaterial(_material);
|
||||
meshCleanup.RemoveInvalids();
|
||||
MeshPointFacetAdjacency meshAdj(meshPoints.size(),meshFacets);
|
||||
meshAdj.SetFacetNeighbourhood();
|
||||
this->_rclMesh.Adopt(meshPoints,meshFacets);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MeshInput::LoadMTL (std::istream &rstrIn)
|
||||
{
|
||||
boost::regex rx_n("^newmtl\\s+([\\x21-\\x7E]+)\\s*$");
|
||||
boost::regex rx_Kd("^\\s*Kd\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)"
|
||||
"\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)\\s*$");
|
||||
boost::cmatch what;
|
||||
|
||||
std::string line;
|
||||
|
||||
if (!_material)
|
||||
return false;
|
||||
|
||||
if (!rstrIn || rstrIn.bad())
|
||||
return false;
|
||||
|
||||
std::streambuf* buf = rstrIn.rdbuf();
|
||||
if (!buf)
|
||||
return false;
|
||||
|
||||
std::map<std::string, App::Color> materials;
|
||||
std::string materialName;
|
||||
std::vector<App::Color> diffuseColor;
|
||||
|
||||
while (std::getline(rstrIn, line)) {
|
||||
if (boost::regex_match(line.c_str(), what, rx_n)) {
|
||||
materialName = Base::Tools::escapedUnicodeToUtf8(what[1].first);
|
||||
}
|
||||
else if (boost::regex_match(line.c_str(), what, rx_Kd)) {
|
||||
float r = static_cast<float>(std::atof(what[1].first));
|
||||
float g = static_cast<float>(std::atof(what[4].first));
|
||||
float b = static_cast<float>(std::atof(what[7].first));
|
||||
materials[materialName] = App::Color(r,g,b);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = _materialNames.begin(); it != _materialNames.end(); ++it) {
|
||||
auto jt = materials.find(it->first);
|
||||
if (jt != materials.end()) {
|
||||
std::vector<App::Color> mat(it->second, jt->second);
|
||||
diffuseColor.insert(diffuseColor.end(), mat.begin(), mat.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (diffuseColor.size() == _material->diffuseColor.size()) {
|
||||
_material->binding = MeshIO::PER_FACE;
|
||||
_material->diffuseColor.swap(diffuseColor);
|
||||
ReaderOBJ reader(this->_rclMesh, this->_material);
|
||||
if (reader.Load(rstrIn)) {
|
||||
_groupNames = reader.GetGroupNames();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
_material->binding = MeshIO::OVERALL;
|
||||
_material->diffuseColor.clear();
|
||||
return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshInput::LoadOBJ (std::istream &str, const char* filename)
|
||||
{
|
||||
ReaderOBJ reader(this->_rclMesh, this->_material);
|
||||
if (reader.Load( str )) {
|
||||
_groupNames = reader.GetGroupNames();
|
||||
if (this->_material && this->_material->binding == MeshCore::MeshIO::PER_FACE) {
|
||||
Base::FileInfo fi(filename);
|
||||
std::string fn = fi.dirPath() + "/" + this->_material->library;
|
||||
fi.setFile(fn);
|
||||
Base::ifstream mtl(fi, std::ios::in | std::ios::binary);
|
||||
reader.LoadMaterial(mtl);
|
||||
mtl.close();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Loads an SMF file. */
|
||||
@@ -1986,7 +1753,7 @@ bool MeshOutput::SaveAny(const char* FileName, MeshIO::Format format) const
|
||||
}
|
||||
else if (fileformat == MeshIO::OBJ) {
|
||||
// write file
|
||||
if (!SaveOBJ(str))
|
||||
if (!SaveOBJ(str, FileName))
|
||||
throw Base::FileException("Export of OBJ mesh failed",FileName);
|
||||
}
|
||||
else if (fileformat == MeshIO::SMF) {
|
||||
@@ -2235,216 +2002,28 @@ bool MeshOutput::SaveBinarySTL (std::ostream &rstrOut) const
|
||||
/** Saves an OBJ file. */
|
||||
bool MeshOutput::SaveOBJ (std::ostream &out) const
|
||||
{
|
||||
const MeshPointArray& rPoints = _rclMesh.GetPoints();
|
||||
const MeshFacetArray& rFacets = _rclMesh.GetFacets();
|
||||
|
||||
if (!out || out.bad())
|
||||
return false;
|
||||
|
||||
Base::SequencerLauncher seq("saving...", _rclMesh.CountPoints() + _rclMesh.CountFacets());
|
||||
bool exportColorPerVertex = false;
|
||||
bool exportColorPerFace = false;
|
||||
|
||||
if (_material) {
|
||||
if (_material->binding == MeshIO::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 {
|
||||
exportColorPerVertex = true;
|
||||
}
|
||||
}
|
||||
else if (_material->binding == MeshIO::OVERALL) {
|
||||
if (_material->diffuseColor.empty()) {
|
||||
Base::Console().Warning("Cannot export color information because there is no color defined");
|
||||
}
|
||||
else {
|
||||
exportColorPerVertex = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Header
|
||||
out << "# Created by FreeCAD <http://www.freecadweb.org>\n";
|
||||
if (exportColorPerFace) {
|
||||
out << "mtllib " << _material->library << '\n';
|
||||
}
|
||||
|
||||
out.precision(6);
|
||||
out.setf(std::ios::fixed | std::ios::showpoint);
|
||||
|
||||
// vertices
|
||||
Base::Vector3f pt;
|
||||
std::size_t index = 0;
|
||||
for (MeshPointArray::_TConstIterator it = rPoints.begin(); it != rPoints.end(); ++it, ++index) {
|
||||
if (this->apply_transform) {
|
||||
pt = this->_transform * *it;
|
||||
}
|
||||
else {
|
||||
pt.Set(it->x, it->y, it->z);
|
||||
}
|
||||
|
||||
if (exportColorPerVertex) {
|
||||
App::Color c;
|
||||
if (_material->binding == MeshIO::PER_VERTEX) {
|
||||
c = _material->diffuseColor[index];
|
||||
}
|
||||
else {
|
||||
c = _material->diffuseColor.front();
|
||||
}
|
||||
|
||||
int r = static_cast<int>(c.r * 255.0f);
|
||||
int g = static_cast<int>(c.g * 255.0f);
|
||||
int b = static_cast<int>(c.b * 255.0f);
|
||||
|
||||
out << "v " << pt.x << " " << pt.y << " " << pt.z << " " << r << " " << g << " " << b << '\n';
|
||||
}
|
||||
else {
|
||||
out << "v " << pt.x << " " << pt.y << " " << pt.z << '\n';
|
||||
}
|
||||
seq.next(true); // allow to cancel
|
||||
}
|
||||
// Export normals
|
||||
MeshFacetIterator clIter(_rclMesh), clEnd(_rclMesh);
|
||||
const MeshGeomFacet* pclFacet;
|
||||
|
||||
clIter.Begin();
|
||||
clEnd.End();
|
||||
|
||||
while (clIter < clEnd) {
|
||||
pclFacet = &(*clIter);
|
||||
out << "vn " << pclFacet->GetNormal().x << " "
|
||||
<< pclFacet->GetNormal().y << " "
|
||||
<< pclFacet->GetNormal().z << '\n';
|
||||
++clIter;
|
||||
seq.next(true); // allow to cancel
|
||||
}
|
||||
|
||||
if (_groups.empty()) {
|
||||
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;
|
||||
int faceIdx = 1;
|
||||
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()) << '\n';
|
||||
}
|
||||
}
|
||||
out << "f " << it->_aulPoints[0]+1 << "//" << faceIdx << " "
|
||||
<< it->_aulPoints[1]+1 << "//" << faceIdx << " "
|
||||
<< it->_aulPoints[2]+1 << "//" << faceIdx << '\n';
|
||||
seq.next(true); // allow to cancel
|
||||
faceIdx++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// facet indices (no texture and normal indices)
|
||||
std::size_t faceIdx = 1;
|
||||
for (MeshFacetArray::_TConstIterator it = rFacets.begin(); it != rFacets.end(); ++it) {
|
||||
out << "f " << it->_aulPoints[0]+1 << "//" << faceIdx << " "
|
||||
<< it->_aulPoints[1]+1 << "//" << faceIdx << " "
|
||||
<< it->_aulPoints[2]+1 << "//" << faceIdx << '\n';
|
||||
seq.next(true); // allow to cancel
|
||||
faceIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
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()) << '\n';
|
||||
for (std::vector<FacetIndex>::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()) << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
out << "f " << f._aulPoints[0]+1 << "//" << *it + 1 << " "
|
||||
<< f._aulPoints[1]+1 << "//" << *it + 1 << " "
|
||||
<< f._aulPoints[2]+1 << "//" << *it + 1 << '\n';
|
||||
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()) << '\n';
|
||||
for (std::vector<FacetIndex>::const_iterator it = gt->indices.begin(); it != gt->indices.end(); ++it) {
|
||||
const MeshFacet& f = rFacets[*it];
|
||||
out << "f " << f._aulPoints[0]+1 << "//" << *it + 1 << " "
|
||||
<< f._aulPoints[1]+1 << "//" << *it + 1 << " "
|
||||
<< f._aulPoints[2]+1 << "//" << *it + 1 << '\n';
|
||||
seq.next(true); // allow to cancel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
WriterOBJ writer(this->_rclMesh, this->_material);
|
||||
writer.SetTransform(this->_transform);
|
||||
writer.SetGroups(this->_groups);
|
||||
return writer.Save(out);
|
||||
}
|
||||
|
||||
bool MeshOutput::SaveMTL(std::ostream &out) const
|
||||
bool MeshOutput::SaveOBJ (std::ostream &out, const char* filename) const
|
||||
{
|
||||
if (!out || out.bad())
|
||||
return false;
|
||||
|
||||
if (_material) {
|
||||
if (_material->binding == MeshIO::PER_FACE) {
|
||||
|
||||
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());
|
||||
|
||||
out.precision(6);
|
||||
out.setf(std::ios::fixed | std::ios::showpoint);
|
||||
out << "# Created by FreeCAD <http://www.freecadweb.org>: 'None'\n";
|
||||
out << "# Material Count: " << Kd.size() << '\n';
|
||||
|
||||
for (std::size_t i=0; i<Kd.size(); i++) {
|
||||
out << '\n';
|
||||
out << "newmtl material_" << i << '\n';
|
||||
out << " Ns 10.000000" << '\n';
|
||||
out << " Ni 1.000000" << '\n';
|
||||
out << " d 1.000000" << '\n';
|
||||
out << " illum 2" << '\n';
|
||||
out << " Kd " << Kd[i].r << " " << Kd[i].g << " " << Kd[i].b << '\n';
|
||||
}
|
||||
|
||||
return true;
|
||||
WriterOBJ writer(this->_rclMesh, this->_material);
|
||||
writer.SetTransform(this->_transform);
|
||||
writer.SetGroups(this->_groups);
|
||||
if (writer.Save(out)) {
|
||||
if (this->_material && this->_material->binding == MeshCore::MeshIO::PER_FACE) {
|
||||
Base::FileInfo fi(filename);
|
||||
std::string fn = fi.dirPath() + "/" + this->_material->library;
|
||||
fi.setFile(fn);
|
||||
Base::ofstream mtl(fi, std::ios::out | std::ios::binary);
|
||||
writer.SaveMaterial(mtl);
|
||||
mtl.close();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -76,7 +76,13 @@ struct MeshExport Material
|
||||
Material() : binding(MeshIO::OVERALL) {}
|
||||
MeshIO::Binding binding;
|
||||
mutable std::string library;
|
||||
std::vector<App::Color> diffuseColor;
|
||||
|
||||
std::vector<App::Color> ambientColor; /**< Defines the ambient color. */
|
||||
std::vector<App::Color> diffuseColor; /**< Defines the diffuse color. */
|
||||
std::vector<App::Color> specularColor; /**< Defines the specular color. */
|
||||
std::vector<App::Color> emissiveColor; /**< Defines the emissive color. */
|
||||
std::vector<float> shininess;
|
||||
std::vector<float> transparency;
|
||||
};
|
||||
|
||||
struct MeshExport Group
|
||||
@@ -115,8 +121,8 @@ public:
|
||||
bool LoadBinarySTL (std::istream &rstrIn);
|
||||
/** Loads an OBJ Mesh file. */
|
||||
bool LoadOBJ (std::istream &rstrIn);
|
||||
/** Loads the materials of an OBJ file. */
|
||||
bool LoadMTL (std::istream &rstrIn);
|
||||
/** Loads an OBJ Mesh file. */
|
||||
bool LoadOBJ (std::istream &rstrIn, const char* filename);
|
||||
/** Loads an SMF Mesh file. */
|
||||
bool LoadSMF (std::istream &rstrIn);
|
||||
/** Loads an OFF Mesh file. */
|
||||
@@ -143,7 +149,6 @@ protected:
|
||||
MeshKernel &_rclMesh; /**< reference to mesh data structure */
|
||||
Material* _material;
|
||||
std::vector<std::string> _groupNames;
|
||||
std::vector<std::pair<std::string, unsigned long> > _materialNames;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -188,8 +193,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 OBJ file. */
|
||||
bool SaveOBJ (std::ostream &rstrOut, const char* filename) const;
|
||||
/** Saves the mesh object into an SMF file. */
|
||||
bool SaveSMF (std::ostream &rstrOut) const;
|
||||
/** Saves the mesh object into an OFF file. */
|
||||
|
||||
@@ -394,21 +394,7 @@ void MeshObject::save(const char* file, MeshCore::MeshIO::Format f,
|
||||
}
|
||||
|
||||
aWriter.Transform(this->_Mtrx);
|
||||
if (aWriter.SaveAny(file, f)) {
|
||||
if (mat && mat->binding == MeshCore::MeshIO::PER_FACE) {
|
||||
if (f == MeshCore::MeshIO::Undefined)
|
||||
f = MeshCore::MeshOutput::GetFormat(file);
|
||||
|
||||
if (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();
|
||||
}
|
||||
}
|
||||
}
|
||||
aWriter.SaveAny(file, f);
|
||||
}
|
||||
|
||||
void MeshObject::save(std::ostream& str, MeshCore::MeshIO::Format f,
|
||||
@@ -444,20 +430,6 @@ bool MeshObject::load(const char* file, MeshCore::Material* mat)
|
||||
return false;
|
||||
|
||||
swapKernel(kernel, aReader.GetGroupNames());
|
||||
|
||||
if (mat && mat->binding == MeshCore::MeshIO::PER_FACE) {
|
||||
MeshCore::MeshIO::Format format = MeshCore::MeshOutput::GetFormat(file);
|
||||
|
||||
if (format == MeshCore::MeshIO::OBJ) {
|
||||
Base::FileInfo fi(file);
|
||||
std::string fn = fi.dirPath() + "/" + mat->library;
|
||||
fi.setFile(fn);
|
||||
Base::ifstream str(fi, std::ios::in | std::ios::binary);
|
||||
aReader.LoadMTL(str);
|
||||
str.close();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user