背景
由于工作需要,需要将.stl文件转化为.u3d文件,经过调研发现,开源软件Meshlab有import mesh导入.stl和export mesh导出.u3d文件的功能,于是尝试将这个功能截取。
一、环境信息
系统环境:
Windows 10 64bits
软件环境:
Microsoft Visual Studio 2017 professional
Meshlab 源代码
Vcg 库
Qt5.9.1-mscv2017 源代码
二、大致步骤
1.安装并准备所需要的源码以及库文件
·安装QT库:由于我们只需要QT的VS2017支持库,所以其他什么也不要,只选择msvc2017 64-bit
2.将Meshlab源码全部加载到VS2017中,通过Meshlab的ui信息,找到所需要的加载.stl、输出.u3d格式文件功能的源文件。Meshlab源码从https://github.com/cnr-isti-vclab/meshlab可以下载。
1)将Meshlab源文件加入项目中
2)在解决方案中寻找u3d相关代码,容易发现:
3)从中发现了依赖外部的代码:
注:此时发现由于Meshlab需要依赖于外部的Vcglib以及Qt的库函数,所以此时有很多报错。
4)可以发现,这是调用vcglib中的函数。由于当前环境没有加入vcg库,所以给项目配置相关环境。(配置环境需要一下一下寻找)
3.从获取到的Meshlab中找到的代码中挖掘,发现Meshlab实际上是利用了Vcglib中的函数:
vcg::tri::io::ImporterSTL::Open()
vcg::tri::io::ExporterU3D::Save()
4.阅读Meshlab和Vcglib相关的源码,将上述的代码相关的部分——包括参数结构的构建,函数输出构建理清楚,准备开始编写程序。
三、编写程序
变量定义:
typedef float MESHLAB_SCALAR;
typedef vcg::Box3 Box3m;
typedef vcg::Matrix44 Matrix44m;
typedef bool CallBackPos(const int pos, const char * str);
typedef vcg::Point3 Point3m;
要使用Open与Save函数,需要构建相关类。经过阅读代码后,发现需要构建以下类:
class CVertexO;
class CEdgeO;
class CFaceO;
class CUsedTypesO
class CMeshO;
namespace vcg
{
namespace vertex
{
template class Coord3m : public Coord, T> {
public: static int Name(std::vector& name) { name.push_back(std::string("Coord3m")); T::Name(name); }
};
template class Normal3m : public Normal, T> {
public: static int Name(std::vector& name) { name.push_back(std::string("Normal3m")); T::Name(name); }
};
template class CurvatureDirmOcf : public CurvatureDirOcf, T> {
public: static int Name(std::vector& name) { name.push_back(std::string("CurvatureDirmOcf")); T::Name(name); }
};
template class RadiusmOcf : public RadiusOcf {
public: static int Name(std::vector& name) { name.push_back(std::string("RadiusmOcf")); T::Name(name); }
};
}//end namespace vertex
namespace face
{
template class Normal3m : public NormalAbs, T> {
public: static int Name(std::vector& name) { name.push_back(std::string("Normal3m")); T::Name(name); }
};
template class CurvatureDirmOcf : public CurvatureDirOcf, T> {
public: static int Name(std::vector& name) { name.push_back(std::string("CurvatureDirdOcf")); T::Name(name); }
};
}//end namespace face
}//end namespace vcg
// Forward declarations needed for creating the used types
class CVertexO;
class CEdgeO;
class CFaceO;
// Declaration of the semantic of the used types
class CUsedTypesO : public vcg::UsedTypes < vcg::Use::AsVertexType,
vcg::Use::AsEdgeType,
vcg::Use::AsFaceType > {};
// The Main Vertex Class
// Most of the attributes are optional and must be enabled before use.
// Each vertex needs 40 byte, on 32bit arch. and 44 byte on 64bit arch.
class CVertexO : public vcg::Vertex< CUsedTypesO,
vcg::vertex::InfoOcf, /* 4b */
vcg::vertex::Coord3m, /* 12b */
vcg::vertex::BitFlags, /* 4b */
vcg::vertex::Normal3m, /* 12b */
vcg::vertex::Qualityf, /* 4b */
vcg::vertex::Color4b, /* 4b */
vcg::vertex::VFAdjOcf, /* 0b */
vcg::vertex::MarkOcf, /* 0b */
vcg::vertex::TexCoordfOcf, /* 0b */
vcg::vertex::CurvaturefOcf, /* 0b */
vcg::vertex::CurvatureDirmOcf, /* 0b */
vcg::vertex::RadiusmOcf /* 0b */
> {
};
// The Main Edge Class
class CEdgeO : public vcg::Edge {
};
// Each face needs 32 byte, on 32bit arch. and 48 byte on 64bit arch.
class CFaceO : public vcg::Face< CUsedTypesO,
vcg::face::InfoOcf, /* 4b */
vcg::face::VertexRef, /*12b */
vcg::face::BitFlags, /* 4b */
vcg::face::Normal3m, /*12b */
vcg::face::QualityfOcf, /* 0b */
vcg::face::MarkOcf, /* 0b */
vcg::face::Color4bOcf, /* 0b */
vcg::face::FFAdjOcf, /* 0b */
vcg::face::VFAdjOcf, /* 0b */
vcg::face::CurvatureDirmOcf, /* 0b */
vcg::face::WedgeTexCoordfOcf /* 0b */
> {};
class CMeshO : public vcg::tri::TriMesh< vcg::vertex::vector_ocf, vcg::face::vector_ocf >
{
public:
int sfn; //The number of selected faces.
int svn; //The number of selected vertices.
int pvn; //the number of the polygonal vertices
int pfn; //the number of the polygonal faces
Matrix44m Tr; // Usually it is the identity. It is applied in rendering and filters can or cannot use it. (most of the filter will ignore this)
const Box3m& trBB()
{
static Box3m bb;
bb.SetNull();
bb.Add(Tr, bbox);
return bb;
}
};
vcg::tri::io::u3dparametersclasses::Movie15Parameters _param;
typedef vcg::Color4 Color4b;
void getSurroundingFacesVF(CFaceO* fac, int vert_pos, std::vector* surround) {
CVertexO* vert = fac->V(vert_pos);
int pos = vert->cVFi();
CFaceO* first_fac = vert->cVFp();
CFaceO* curr_f = first_fac;
do {
CFaceO* temp = curr_f->VFp(pos);
if (curr_f != 0 && !curr_f->IsD()) {
surround->push_back(curr_f);
pos = curr_f->VFi(pos);
}
curr_f = temp;
} while (curr_f != first_fac && curr_f != 0);
}
void applyColor(CVertexO* vertex, const vcg::Color4b& newcol, int opac)
{
vcg::Color4b orig = vertex->C();
opac *= ((float)newcol[3] / 255.0);
for (int i = 0; i < 3; i++)
orig[i] = std::min(255, ((newcol[i] - orig[i]) * opac + orig[i] * 100) / 100);
vertex->C() = orig;
}
Main函数为:
int main(int argc, char *argv[]) {
QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));
char buff[1000];
_getcwd(buff, 1000);
argc = 3;
string in_file = argv[1];
string out_file = argv[2];
CMeshO m;
int loadmask = vcg::tri::io::Mask::IOM_VERTCOORD | vcg::tri::io::Mask::IOM_FACEINDEX;
const int &b = loadmask;
//open a mesh
vcg::tri::io::ImporterSTL::Open(m, in_file.c_str());
vcg::tri::io::u3dparametersclasses::Movie15Parameters _param;
_param._campar = new vcg::tri::io::u3dparametersclasses::Movie15Parameters::CameraParameters(m.bbox.Center(), m.bbox.Diag());
_param.positionQuality = 1000;
//QApplication q(argc, argv);
//save a mesh
QString c = QString::fromLocal8Bit(buff);
QString a = c + "\\IDTFConverter.exe";
int mask = 0 | vcg::tri::io::Mask::IOM_VERTNORMAL | vcg::tri::io::Mask::IOM_VERTCOLOR | vcg::tri::io::Mask::IOM_FACECOLOR;// | vcg::tri::io::Mask::IOM_WEDGTEXCOORD;
//int mask = vcg::tri::io::ExporterU3D::GetExportMaskCapability();
const int &ra = mask;
QString curr = QDir::currentPath();
int k = vcg::tri::io::ExporterU3D::Save(m, out_file.c_str(), a.toUtf8().data(), _param, 0, curr);
//q.~QApplication();
//test
if (k == 1)
printf("Successful '%s'\n '%s'\n '%s'\n", a.toUtf8().data(), c.toLocal8Bit().data(), buff);
else
printf("Fail");
return 0;
}
渲染功能截取同理,需要添加额外函数:目的是获取每个面的边和点以及上色
typedef vcg::Color4 Color4b;
void getSurroundingFacesVF(CFaceO* fac, int vert_pos, std::vector* surround) {
CVertexO* vert = fac->V(vert_pos);
int pos = vert->cVFi();
CFaceO* first_fac = vert->cVFp();
CFaceO* curr_f = first_fac;
do {
CFaceO* temp = curr_f->VFp(pos);
if (curr_f != 0 && !curr_f->IsD()) {
surround->push_back(curr_f);
pos = curr_f->VFi(pos);
}
curr_f = temp;
} while (curr_f != first_fac && curr_f != 0);
}
void applyColor(CVertexO* vertex, const vcg::Color4b& newcol, int opac)
{
vcg::Color4b orig = vertex->C();
opac *= ((float)newcol[3] / 255.0);
for (int i = 0; i < 3; i++)
orig[i] = std::min(255, ((newcol[i] - orig[i]) * opac + orig[i] * 100) / 100);
vertex->C() = orig;
}
渲染功能需要在主函数load和save mesh之间添加:
//上色,设置透明度
std::vector face;
int red, blue, green, opac; //RGB值以及透明度(最大均255)
red = 255;
green = 50;
blue = 70;
opac = 125;
//QColor newcol = QColor::QColor(red, green, blue, opac);
QColor newcol = vcg::Color4b::Green;
for (size_t i = 0; i < m.face.size(); ++i) {
if (!m.face[i].IsD())
face.push_back(&m.face[i]);
}
printf("\nkkkkkk %zu kkkkkk\n", face.size());
Color4b color(newcol.red(), newcol.green(), newcol.blue(), newcol.alpha());
CFaceO* fac = face.at(0);
for (size_t j = 0; j < m.face.size(); ++j) {
bool who = face[j]->IsS();
CFaceO* fac = face.at(j);
if (who == fac->IsS()) {
for (int lauf = 0; lauf < 3; lauf++)
applyColor(fac->V(lauf), color, opac);
}
}
四、部分代码解释:
1.QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));
//为了支持中文路径,需要设定编码模式为”GBK”
2.Mask的作用是,由于VCG兼容众多图形,各种格式的图形所包含的内容不一样,所以需要掩码来决定使用哪些部分(如边角点颜色),以防格式混乱。
3.QString a = c + "\IDTFConverter.exe";由于使用exportU3D函数需要使用IDTFConverter.exe文件进行文件转化,所以需要预设其所在位置。
五、生成解决方案
右击项目点击生成,就会在debug目录下看到项目的exe可执行文件,通过命令行调用,如 project3.exe test.stl test.u3d命令,则可以得到最终转化后的文件。
六、可能遇到的问题
1.LNK链接错误,将依赖库文件QtWidgerd.dll,Qtcored.dll(release版本则为QtWidger.dll,Qtcore.dll)添加到连接器的附加库目录中。
2.执行exe文件时报错:当前文件目录下没有所依赖的.dll动态库文件,复制即可。
3.没有.u3d文件且未报错:命令权限不足,需要在非C盘路径下使用。
4.只有.tex文件:同3或缺失IDTFConverter.exe文件。