一、导入模型
- 打开导入对话框
首先从Assimp::Importer
中获取ASSIMP支持导入的文件格式,然后用QFileDialog::getOpenFileName
函数弹出文件对话框。
// cmainwindow.cpp
void CMainWindow::on_actionImport_triggered()
{
// 获取ASSIMP支持的导入格式
Importer t_importer;
std::string szOut;
t_importer.GetExtensionList(szOut); // ASSIMP支持的导入格式
// 筛选文件格式
QString t_assimp=tr("ASSIMP (") +QString::fromStdString(szOut) +tr(")");
QString all_filter;
all_filter+=t_assimp;
// 获取被打开的文件路径
QString filename = QFileDialog::getOpenFileName(this,tr("open file"),"D:/models",all_filter);
...
}
其中Assimp::Importer
的成员函数 GetExtensionList
原型如下,通过它获取ASSIMP支持的所有文件扩展名的完整列表,输出到szOut中,比如"*.3ds;*.obj;*.dae
。
inline void GetExtensionList(std::string& szOut) const;
使用QFileDialog::getOpenFileName
函数显示导入文件框。
显示结果:
- 获取模型内容
构造函数
// cmodel.cpp
// CModel::CModel
{
...
//从外部文件中加载模型
Importer t_importer;
const aiScene *t_scene=t_importer.ReadFile(_filepath.toStdString(),aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace);
if(!t_scene || t_scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !t_scene->mRootNode) // if is Not Zero
{
qDebug() << "ERROR::ASSIMP:: " << t_importer.GetErrorString();
return;
}
// 构造根节点及所有子节点
m_process_nodes(t_scene->mRootNode, t_scene, true);
}
将模型的文件路径输入到Assimp::Importer
的成员函数ReadFile
中,返回场景aiScene
结构体。把结构体放入m_process_nodes
函数中处理场景的节点。
- 处理节点
获取坐标信息
// cmodel.cpp
// CModel::m_process_nodes
aiMatrix4x4 t_matrix=_node->mTransformation; // 获取转换坐标系
// 获取旋转矩阵的缩放比例
ai_real t_scaling_x = aiVector3D(t_matrix.a1,t_matrix.a2,t_matrix.a3).Length();
ai_real t_scaling_y = aiVector3D(t_matrix.b1,t_matrix.b2,t_matrix.b3).Length();
ai_real t_scaling_z = aiVector3D(t_matrix.c1,t_matrix.c2,t_matrix.c3).Length();
// 从 aiMatrix4x4 转换成 gp_Trsf 形式
gp_Trsf t_transfer;
t_transfer.SetValues(t_matrix.a1 / t_scaling_x, t_matrix.a2 / t_scaling_x, t_matrix.a3 / t_scaling_x, t_matrix.a4,
t_matrix.b1 / t_scaling_y, t_matrix.b2 / t_scaling_y, t_matrix.b3 / t_scaling_y, t_matrix.b4,
t_matrix.c1 / t_scaling_z, t_matrix.c2 / t_scaling_z, t_matrix.c3 / t_scaling_z, t_matrix.c4);
遍历节点的所有网格(Mesh)
// cmodel.cpp
// void CModel::m_process_nodes(const aiNode *_node, const aiScene *_scene, bool _is_root)
for(unsigned int imesh = 0; imesh < _node->mNumMeshes; imesh++)
{
aiMesh* t_mesh = _scene->mMeshes[_node->mMeshes[imesh]]; // 获取当前网格
// 遍历网格的所有面
for(unsigned int iface = 0; iface < t_mesh->mNumFaces; iface++)
{
t_mesh->mMaterialIndex;
aiFace t_face = t_mesh->mFaces[iface];
BRepBuilderAPI_MakePolygon t_polygon;
// 遍历面的所有顶点
for(unsigned int ivertex = 0; ivertex < t_face.mNumIndices; ivertex++)
{
// 转换顶点储存模式
gp_Pnt t_pnt=gp_Pnt(t_mesh->mVertices[t_face.mIndices[ivertex]].x,
t_mesh->mVertices[t_face.mIndices[ivertex]].y,
t_mesh->mVertices[t_face.mIndices[ivertex]].z);
t_polygon.Add(t_pnt); // 添加顶点
}
t_polygon.Close(); // 闭合顶点
t_topo_face = BRepBuilderAPI_MakeFace (t_polygon); // 通过闭合的线构造面
if(!t_topo_face.IsNull())
{
t_build_tool.Add (t_compound, t_topo_face); // 将面加入到复合体中
}
}
//! 材质信息
aiMaterial* material = _scene->mMaterials[t_mesh->mMaterialIndex]; //通过索引获取网格在环境中的材质
t_occ_material = m_material_transfer(material); // 从ASSIMP格式转换到OCC材质格式
}
遍历子节点
// cmodel.cpp
// void CModel::m_process_nodes(const aiNode *_node, const aiScene *_scene, bool _is_root)
for(unsigned int i = 0; i < _node->mNumChildren; i++)
{
m_process_nodes(_node->mChildren[i], _scene , t_is_next_root); // 构造子节点
}
- 获取材质及转换
上一节中使用了m_material_transfer
函数将ASSIMP的aiMaterial
结构体内容转换到OpenCASCADE中的Graphic3d_MaterialAspect
形式。
初始化OpenCASCADE材质
// cmodel.cpp
// Graphic3d_MaterialAspect CModel::m_material_transfer(aiMaterial *_material)
Graphic3d_MaterialAspect t_result;
t_result.SetMaterialType(Graphic3d_MATERIAL_PHYSIC);
Quantity_Color t_occ_colors[Graphic3d_TypeOfReflection_NB];
t_occ_colors[Graphic3d_TOR_AMBIENT] = Quantity_Color (Graphic3d_Vec3 (0.2f, 0.2f, 0.2f));
t_occ_colors[Graphic3d_TOR_DIFFUSE] = Quantity_Color (Graphic3d_Vec3 (0.2f, 0.2f, 0.2f));
t_occ_colors[Graphic3d_TOR_SPECULAR] = Quantity_Color (Graphic3d_Vec3 (1.0f, 1.0f, 1.0f));
Standard_ShortReal t_occ_shininess = 0.039f;
获取各种数据,包括名称、环境光、漫反射、镜面光、反光度
// cmodel.cpp
// Graphic3d_MaterialAspect CModel::m_material_transfer(aiMaterial *_material)
aiString name; // 材质名称 原始数据
if (AI_SUCCESS==aiGetMaterialString(_material,AI_MATKEY_NAME,&name))
{
t_result.SetMaterialName(name.C_Str());
}
// 环境光
aiColor4D ambient; // 环境光 原始数据
if(AI_SUCCESS ==aiGetMaterialColor(_material, AI_MATKEY_COLOR_AMBIENT, &ambient))
{
t_occ_colors[Graphic3d_TOR_AMBIENT]=Quantity_Color(ambient.r,ambient.g,ambient.b,Quantity_TOC_RGB);
t_result.SetAmbientColor(t_occ_colors[Graphic3d_TOR_AMBIENT]);
}
// 漫反射
aiColor4D diffuse; // 漫反射 原始数据
if(AI_SUCCESS ==aiGetMaterialColor(_material, AI_MATKEY_COLOR_DIFFUSE, &diffuse))
{
t_occ_colors[Graphic3d_TOR_DIFFUSE]=Quantity_Color(diffuse.r,diffuse.g,diffuse.b,Quantity_TOC_RGB);
t_result.SetDiffuseColor(t_occ_colors[Graphic3d_TOR_DIFFUSE]);
}
// 镜面光
aiColor4D specular; // 镜面光 原始数据
if(AI_SUCCESS ==aiGetMaterialColor(_material, AI_MATKEY_COLOR_SPECULAR, &specular))
{
t_occ_colors[Graphic3d_TOR_SPECULAR]=Quantity_Color(specular.r,specular.g,specular.b,Quantity_TOC_RGB);
t_result.SetSpecularColor(t_occ_colors[Graphic3d_TOR_SPECULAR]);
}
// 反光度
float shininess; // 反光度 原始数据
if(AI_SUCCESS ==aiGetMaterialFloat(_material, AI_MATKEY_SHININESS, &shininess))
{
t_occ_shininess=shininess/128.0; // 由OpenGL值转换成VRML97
// OCC的反光度表示方式只接受0到1之间,否则报错
t_occ_shininess = t_occ_shininess<1.0 ? t_occ_shininess:1.0;
t_occ_shininess = t_occ_shininess<0.0 ? 0.0:t_occ_shininess;
t_result.SetShininess(t_occ_shininess); // 设置反光度
}
- 导入模型结果
打开菜单栏File->Import,弹出文件对话框后选择模型文件并打开。
二、导出模型
- 打开导出对话框
首先判断是否有模型被选择,若无模型则提示框
// cmainwindow.cpp
// void CMainWindow::on_actionExport_triggered()
if(m_3d_widget->m_get_context()->NbSelected() == 0)
{
// 无选择则弹出提示框
QMessageBox::warning(this,tr("Export Error"),tr("There is no object selected!"));
return; // 不执行操作
}
通过Assimp::Exporter
的GetExportFormatCount
函数获取支持格式的数量,然后遍历及通过GetExportFormatDescription
函数获取所有格式的结构体,结构体内包含:①id
唯一标识;②description
格式描述;③fileExtension
文件扩展名
另外通过哈希表储存文件过滤字符串与文件格式id的对应关系,以在后面用作区分用户选择的文件格式id
// cmainwindow.cpp
// void CMainWindow::on_actionExport_triggered()
QString t_all_filter; // 所有文件过滤器
QHash<QString,const char *> t_hash_format; // 格式过滤器与文档描述哈希表
Exporter t_export; // 导出器
for(int i=0; i<t_export.GetExportFormatCount(); i++) // 遍历ASSIMP允许导出的格式
{
const aiExportFormatDesc *t_format_desc = t_export.GetExportFormatDescription(i); // 获取每个文档描述
// 文档过滤器
QString t_single_format = QString(t_format_desc->description)+QString("(*.%1)").arg(t_format_desc->fileExtension);
t_hash_format.insert(t_single_format,t_format_desc->id); // 插入格式过滤器与文档描述哈希表
t_all_filter += t_single_format; // 添加单个文档过滤到整体过滤器
if(i != t_export.GetExportFormatCount()-1) // 最后一个不添加分行
{
t_all_filter+=";;"; // 分行
}
}
打开保存文件对话框,通过返回值filename
获得保存的文件名,并通过t_selected_filter
获取用户选择的过滤器
// cmainwindow.cpp
// void CMainWindow::on_actionExport_triggered()
QString t_selected_filter; // 被选择的过滤器
// 打开文件保存提示框
QString filename = QFileDialog::getSaveFileName(this,tr("Save"),".",t_all_filter,&t_selected_filter);
if(filename.isEmpty()) // 若文件名为空,则不执行操作
{
return; // 不执行操作
}
在对话框点击保存后,进行实质的文件保存操作
// cmainwindow.cpp
// void CMainWindow::on_actionExport_triggered()
// 导出模型文件
CModel::m_export_model(filename,t_hash_format.value(t_selected_filter),m_3d_widget->m_get_context());
运行结果:
- 导出操作
初始化参数
// cmodel.cpp
// static bool m_export_model(QString _filename , const char *_format_id,Handle(AIS_InteractiveContext) _context);
Exporter exporter;
// 定义场景
aiScene *t_scene = new aiScene();
// 单一材质
// 创建根节点
t_scene->mRootNode=new aiNode();
// 创建子节点
int t_NumChildrenNode = _context->NbSelected();
aiNode **t_node_list=new aiNode*[t_NumChildrenNode];
t_scene->mNumMaterials=t_NumChildrenNode;
t_scene->mMaterials = new aiMaterial*[t_NumChildrenNode];
// 定义场景所有网格
t_scene->mNumMeshes = t_NumChildrenNode;
t_scene->mMeshes = new aiMesh*[t_NumChildrenNode];
int t_index = 0;
遍历所有选择的模型
// cmodel.cpp
// static bool m_export_model(QString _filename , const char *_format_id,Handle(AIS_InteractiveContext) _context);
for ( _context->InitSelected(); _context->MoreSelected(); _context->NextSelected() )
{
aiNode *t_node = t_node_list[t_index] = new aiNode();
t_node->mNumMeshes=1; // 一个网格
t_node->mNumChildren=0; // 无子节点
t_node->mMeshes = new uint[1]; //一个网格地址
t_node->mMeshes[0] = t_index; // 网格地址索引
aiMesh* pMesh = t_scene->mMeshes[t_index] = new aiMesh(); // 创建地址0的网格
pMesh->mMaterialIndex = t_index; // 网格材质
Standard_Integer aNbNodes = 0;
Standard_Integer aNbTriangles = 0;
Handle(AIS_InteractiveObject) obj = _context->SelectedInteractive();
......
}
在以上遍历模型的循环内,添加材质参数到ASSIMP中
// cmodel.cpp
// static bool m_export_model(QString _filename , const char *_format_id,Handle(AIS_InteractiveContext) _context);
// for ( _context->InitSelected(); _context->MoreSelected(); _context->NextSelected() )
Handle(AIS_Shape) ais_shape = Handle(AIS_Shape)::DownCast(obj);
Graphic3d_MaterialAspect shpae_material(ais_shape->Material());
aiMaterial* pMaterial = t_scene->mMaterials[t_index] = new aiMaterial();
Quantity_Color amb=shpae_material.AmbientColor();
aiColor4D ambient(amb.Red(),amb.Green(),amb.Blue(),1.0);
pMaterial->AddProperty(&ambient,1,AI_MATKEY_COLOR_AMBIENT);
Quantity_Color diff=shpae_material.DiffuseColor();
aiColor4D diffuse(diff.Red(),diff.Green(),diff.Blue(),1.0);
pMaterial->AddProperty(&diffuse,1,AI_MATKEY_COLOR_DIFFUSE);
Quantity_Color spec=shpae_material.SpecularColor();
aiColor4D specular(spec.Red(),spec.Green(),spec.Blue(),1.0);
pMaterial->AddProperty(&specular,1,AI_MATKEY_COLOR_SPECULAR);
Standard_ShortReal shin=shpae_material.Shininess();
pMaterial->AddProperty(&shin,1,AI_MATKEY_SHININESS);
分别计算模型中的节点和三角面的数量
// cmodel.cpp
// static bool m_export_model(QString _filename , const char *_format_id,Handle(AIS_InteractiveContext) _context);
// for ( _context->InitSelected(); _context->MoreSelected(); _context->NextSelected() )
TopoDS_Shape theShape = ais_shape->Shape();
// calculate total number of the nodes and triangles
for (TopExp_Explorer anExpSF (theShape, TopAbs_FACE); anExpSF.More(); anExpSF.Next())
{
TopLoc_Location aLoc;
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation (TopoDS::Face (anExpSF.Current()), aLoc);
if (! aTriangulation.IsNull())
{
aNbNodes += aTriangulation->NbNodes ();
aNbTriangles += aTriangulation->NbTriangles ();
}
}
pMesh->mNumVertices = aNbNodes;
pMesh->mNumFaces = aNbTriangles;
把所有三角面与定点信息添加到ASSIMP中
// cmodel.cpp
// static bool m_export_model(QString _filename , const char *_format_id,Handle(AIS_InteractiveContext) _context);
// for ( _context->InitSelected(); _context->MoreSelected(); _context->NextSelected() )
int index=0;
int face_index=0;
// fill temporary triangulation
Standard_Integer aNodeOffset = 0;
for (TopExp_Explorer anExpSF (theShape, TopAbs_FACE); anExpSF.More(); anExpSF.Next())
{
TopLoc_Location aLoc;
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation (TopoDS::Face (anExpSF.Current()), aLoc);
const TColgp_Array1OfPnt& aNodes = aTriangulation->Nodes();
const Poly_Array1OfTriangle& aTriangles = aTriangulation->Triangles();
// copy nodes
gp_Trsf aTrsf = aLoc.Transformation();
for (Standard_Integer aNodeIter = aNodes.Lower(); aNodeIter <= aNodes.Upper(); ++aNodeIter)
{
gp_Pnt aPnt = aNodes (aNodeIter);
aPnt.Transform (aTrsf);
qDebug()<<"nodes "<<aPnt.X()<<aPnt.Y()<<aPnt.Z();
vp[index].Set(aPnt.X(),aPnt.Y(),aPnt.Z());
vn[index].Set(0.0,0.0,1.0);
index++;
}
// copy triangles
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
for (Standard_Integer aTriIter = aTriangles.Lower(); aTriIter <= aTriangles.Upper(); ++aTriIter)
{
Poly_Triangle aTri = aTriangles (aTriIter);
Standard_Integer anId[3];
aTri.Get (anId[0], anId[1], anId[2]);
if (anOrientation == TopAbs_REVERSED)
{
// Swap 1, 2.
Standard_Integer aTmpIdx = anId[1];
anId[1] = anId[2];
anId[2] = aTmpIdx;
}
// Update nodes according to the offset.
anId[0] += aNodeOffset;
anId[1] += aNodeOffset;
anId[2] += aNodeOffset;
aiFace& face = pMesh->mFaces[face_index++];
face.mIndices = new unsigned int[face.mNumIndices = 3];
face.mIndices[0]=anId[0]-1;
face.mIndices[1]=anId[1]-1;
face.mIndices[2]=anId[2]-1;
}
aNodeOffset += aNodes.Size();
}
t_index++;
完善aiScene
结构体的内容,使用Assimp::Exporter
的Export
方法将t_scene
对象的内容保存到文件中,文件格式以_format_id
参数决定
// cmodel.cpp
// static bool m_export_model(QString _filename , const char *_format_id,Handle(AIS_InteractiveContext) _context);
// 根节点加入子节点
t_scene->mRootNode->addChildren(t_NumChildrenNode,t_node_list);
exporter.Export(t_scene , _format_id , _filename.toStdString());
- 导出模型文件效果
打开菜单栏File->Export,弹出文件对话框后选择文件路径、输入文件名并点击保存。
输出后的文件内容