基于OpenCASCADE自制三维建模软件(十一)使用ASSIMP导入导出

一、导入模型

  1. 打开导入对话框
    首先从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);

    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函数显示导入文件框。

显示结果:
在这里插入图片描述

  1. 获取模型内容

构造函数

// 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函数中处理场景的节点。

  1. 处理节点

获取坐标信息

// 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);   // 构造子节点
    }
  1. 获取材质及转换

上一节中使用了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); // 设置反光度
    }
  1. 导入模型结果
    打开菜单栏File->Import,弹出文件对话框后选择模型文件并打开。
    在这里插入图片描述

二、导出模型

  1. 打开导出对话框

首先判断是否有模型被选择,若无模型则提示框

// 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::ExporterGetExportFormatCount函数获取支持格式的数量,然后遍历及通过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());

运行结果:
在这里插入图片描述

  1. 导出操作

初始化参数

// 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::ExporterExport方法将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());
  1. 导出模型文件效果
    打开菜单栏File->Export,弹出文件对话框后选择文件路径、输入文件名并点击保存。
    在这里插入图片描述
    输出后的文件内容
    在这里插入图片描述

项目仓库

https://github.com/Jelatine/JellyCAD

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);
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);
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);
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片