FreeCAD中数据显示流程

FreeCAD中数据显示流程

济南友泉软件有限公司

  • Open Inventor

Open Inventor(以下简称OIV)是SGI公司使用C++编写的基于OpenGL的面向对象三维图形软件开发包。使用OIV开发包,程序员可以快速、简洁地开发出各种类型的交互式三维图形软件。OIV具有平台无关性,它可以在Microsoft Windows、Unix、Linux等多种操作系统中使用。OIV允许使用C、C++、Java、DotNet多种编程语言进行程序开发。经过多年的发展,OIV已经基本上成为面向对象的3D图形开发“事实上”的工业标准。广泛地应用于机械工程设计与仿真、医学和科学图像、地理科学、石油钻探、虚拟现实、科学数据可视化等领域。

OIV目前主要由三家公司负责维护,相应的有三个主要的版本。SGI最早提出并开发OIV的UNIX版本;TGS公司最早将OIV由Unix系统移植到Microsoft Windows下的公司;SIM公司开发的Coin3D OIV可以同时在UNIX和Microsoft Windows下使用,Coin3D OIV免费版本的使用协议采用的是GPL协议,但如果要在商业软件使用Coin3D,商业用户需要每年支付一笔很少的开发费用。

目前,FreeCAD使用的是Coin3D OIV这个版本。

  1. OIV的原理与概念

由于OIV采用C++面向对象的编程思想进行设计,因此相对于直接调用OpenGL C API来说,使用OIV开发图形图像软件将会更加简单而且开发效率更高。

使用OIV编写应用程序比较简单,其要点是根据需要把景物节点组装成景物(Scene),并设置景物节点的事件及其响应。

  1. 景物与节点

OIV使用层次化的树状结构来存储模型数据,其中的节点称之为景物节点(SoNode及其子类);而景物(Scene)则是由若干景物节点组成的;景物图(Scene)则指场景中景物的集合。

SoCube

立方体

 

 

 

 

  1. 行为

行为(SoAction及其子类)主要用来渲染景物。

SoGLRenderAction

应用GL图形库对景物图进行渲染

SoGetBoundingBoxAction

在景物途中计算一个对象的三维包围盒

SoWriteAction

将景物图写入文件

SoHandleEventAction

处理事件

SoRayPickAction

沿直线选择对象

SoCallbackAction

调用回调函数

  1. 事件

事件(SoEvent及其子类)用来表示键盘、鼠标等外部设备对景物的操作。

SoButtonEvent

 

SoLocation2Event

 

SoMotion3Event

 

  1. 传感器

传感器(SoSensor及其子类)用于检测各类事件,当有事件发生时便会调用注册的对应回调函数。

  1. 引擎

引擎(SoEngine及其子类)用于景物运动模拟,适合于物体运动、机械结构关节点等应用场景。

  1. OIV内存管理

虚基类SoBase不仅提供了类型管理功能。而且实现了基于引用计数的内存管理。每个对象都有一个引用计数的变量,只要它被使用过一次,引用计数就加1,不再使用时,引用计数就减1,如果引用计数变成0,那么这个对象就自动被删除。

  1. 代码示例

通过下面的代码可以直观地了解OIV程序开发的流程,

#include <Inventor/Qt/SoQt.h>
#include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoMaterial.h>
int main(int, char **argv)
{
    //初始化Inventor
    QWidget *myWindow = SoQt::init(argv[0]);
    //创建观察器
    SoQtExaminerViewer *myViewer = new SoQtExaminerViewer(myWindow);
    //创建场景
    SoSeparator *root = new SoSeparator;
    SoMaterial *myMaterial = new SoMaterial;
    myMaterial->diffuseColor.setValue(1.0, 0.0, 0.0);//红色
    root->addChild(myMaterial);
    root->addChild(new SoCube);//增加上一个立方体
    //观察器和场景相关联
    myViewer->setSceneGraph(root);
    //显示主窗口
    myViewer->show();
    SoQt::show(myWindow);
    // 主循环
    SoQt::mainLoop();
    return 0;
}

程序运行结果如下:

图片[1]-FreeCAD中数据显示流程-卡核

 

  • App::DocumentObject

App::Document采用层次化的树状结构存储文档数据,每个文档节点称之为文档对象(App::DocumentObjectObject)。

App::DocumentObjectObject包含描述对象的数据,提供了对象数据持久化的功能,同时采用Boost库中信号-槽机制用于管理数据更新操作的相应。

 

  • ViewProvider

ViewProvider定义了各种数据在View3Dinventor(派生于MDIView)、TreeView中显示的接口。对于View3Dinventor,主要完成模型数据转换成OIV渲染数据结构的功能;对于TreeView,主要定义了节点图标、双击响应等方面的接口。

 

  • View3DInventorViewer

View3DInventor通过其内部View3DInventorViewer来完成在Qt窗口内使用OIV渲染几何文档数据。

  1. 设置景物图

为了显示模型,仅需要调用setSceneGraph()指定需要显示的所有景物的根节点。

void View3DInventorViewer::setSceneGraph(SoNode *root);

 

  • 对象数据的3D显示流程

本节以Part模块中Part::Box类型文档对象来分析文档对象的创建、显示过程。

  1. 命令触发

当在点击菜单[Part/Primitives/Cube]或者直接在工具栏上点击Cube图标,便会调用CmdPartBox::activated()函数,

void CmdPartBox::activated(int iMsg)
{
    Q_UNUSED(iMsg);
    QString cmd;
    cmd = qApp->translate("CmdPartBox","Cube");
    openCommand((const char*)cmd.toUtf8());
    runCommand(Doc,"App.ActiveDocument.addObject(\\"Part::Box\\",\\"Box\\")");
    cmd = QString::fromLatin1("App.ActiveDocument.ActiveObject.Label = \\"%1\\"")
        .arg(qApp->translate("CmdPartBox","Cube"));
    runCommand(Doc,cmd.toUtf8());
    commitCommand();
    updateActive();
    runCommand(Gui, "Gui.SendMsgToActiveView(\\"ViewFit\\")");
}

在这个函数中,会调用App::Document::addObject()来创建Part::Box类型的文档对象。

  1. 创建对象

在App::Document::addObject()中,会根据传入的对象文档类型创建对象文件,然后分别触发signalNewObject、signalActivatedObject等信号。

boost::signals2::signal<void(const App::DocumentObject&)> App::Document::signalNewObject;
boost::signals2::signal<void(const App::DocumentObject&)> App::Document:: signalActivatedObject;
DocumentObject * Document::addObject(const char* sType, const char* pObjectName, bool isNew)
{
    Base::BaseClass* base = static_cast<Base::BaseClass*>(Base::Type::createInstanceByName(sType,true));
    string ObjectName;
    if (!base)
        return 0;
    if (!base->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId())) {
        delete base;
        std::stringstream str;
        str << "'" << sType << "' is not a document object type";
        throw Base::TypeError(str.str());
    }
    App::DocumentObject* pcObject = static_cast<App::DocumentObject*>(base);
    pcObject->setDocument(this);
    // do no transactions if we do a rollback!
    if (!d->rollback) {
        // Undo stuff
        if (d->activeUndoTransaction)
            d->activeUndoTransaction->addObjectDel(pcObject);
    }
    // get Unique name
    if (pObjectName && pObjectName[0] != '\\0')
        ObjectName = getUniqueObjectName(pObjectName);
    else
        ObjectName = getUniqueObjectName(sType);
    d->activeObject = pcObject;
    // insert in the name map
    d->objectMap[ObjectName] = pcObject;
    // cache the pointer to the name string in the Object (for performance of DocumentObject::getNameInDocument())
    pcObject->pcNameInDocument = &(d->objectMap.find(ObjectName)->first);
    // insert in the vector
    d->objectArray.push_back(pcObject);
    // insert in the adjacence list and reference through the ConectionMap
    //_DepConMap[pcObject] = add_vertex(_DepList);
    // If we are restoring, don't set the Label object now; it will be restored later. This is to avoid potential duplicate
    // label conflicts later.
    if (!d->StatusBits.test(Restoring))
        pcObject->Label.setValue( ObjectName );
    // Call the object-specific initialization
    if (!d->undoing && !d->rollback && isNew) {
        pcObject->setupObject ();
    }
    // mark the object as new (i.e. set status bit 2) and send the signal
    pcObject->setStatus(ObjectStatus::New, true);
    signalNewObject(*pcObject);
    // do no transactions if we do a rollback!
    if (!d->rollback && d->activeUndoTransaction) {
        signalTransactionAppend(*pcObject, d->activeUndoTransaction);
    }
    signalActivatedObject(*pcObject);
    // return the Object
    return pcObject;
}
  1. 信号-槽的关联

在Gui::Document中,可以看到信号signalNewObject关联到了Gui::Document::slotNewObject,即

Document::Document(App::Document* pcDocument,Application * app)
{
    d = new DocumentP;

    d->_iWinCount = 1;

    // new instance

    d->_iDocId = (++_iDocCount);

    d->_isClosing = false;

    d->_isModified = false;

    d->_pcAppWnd = app;

    d->_pcDocument = pcDocument;

    d->_editViewProvider = 0;

    // Setup the connections

    d->connectNewObject = pcDocument->signalNewObject.connect

        (boost::bind(&Gui::Document::slotNewObject, this, _1));

    d->connectDelObject = pcDocument->signalDeletedObject.connect

        (boost::bind(&Gui::Document::slotDeletedObject, this, _1));

    d->connectCngObject = pcDocument->signalChangedObject.connect

        (boost::bind(&Gui::Document::slotChangedObject, this, _1, _2));

    d->connectRenObject = pcDocument->signalRelabelObject.connect

        (boost::bind(&Gui::Document::slotRelabelObject, this, _1));

    d->connectActObject = pcDocument->signalActivatedObject.connect

        (boost::bind(&Gui::Document::slotActivatedObject, this, _1));

    d->connectActObjectBlocker = boost::signals2::shared_connection_block

        (d->connectActObject, false);

    d->connectSaveDocument = pcDocument->signalSaveDocument.connect

        (boost::bind(&Gui::Document::Save, this, _1));

    d->connectRestDocument = pcDocument->signalRestoreDocument.connect

        (boost::bind(&Gui::Document::Restore, this, _1));

    d->connectStartLoadDocument = App::GetApplication().signalStartRestoreDocument.connect

        (boost::bind(&Gui::Document::slotStartRestoreDocument, this, _1));

    d->connectFinishLoadDocument = App::GetApplication().signalFinishRestoreDocument.connect

        (boost::bind(&Gui::Document::slotFinishRestoreDocument, this, _1));

    d->connectExportObjects = pcDocument->signalExportViewObjects.connect

        (boost::bind(&Gui::Document::exportObjects, this, _1, _2));

    d->connectImportObjects = pcDocument->signalImportViewObjects.connect

        (boost::bind(&Gui::Document::importObjects, this, _1, _2, _3));

    d->connectUndoDocument = pcDocument->signalUndo.connect

        (boost::bind(&Gui::Document::slotUndoDocument, this, _1));

    d->connectRedoDocument = pcDocument->signalRedo.connect

        (boost::bind(&Gui::Document::slotRedoDocument, this, _1));

    d->connectTransactionAppend = pcDocument->signalTransactionAppend.connect

        (boost::bind(&Gui::Document::slotTransactionAppend, this, _1, _2));

    d->connectTransactionRemove = pcDocument->signalTransactionRemove.connect

        (boost::bind(&Gui::Document::slotTransactionRemove, this, _1, _2));

    // pointer to the python class

    // NOTE: As this Python object doesn't get returned to the interpreter we

    // mustn't increment it (Werner Jan-12-2006)

    _pcDocPy = new Gui::DocumentPy(this);

    if (App::GetApplication().GetParameterGroupByPath

        ("User parameter:BaseApp/Preferences/Document")->GetBool("UsingUndo",true)){

        d->_pcDocument->setUndoMode(1);

        // set the maximum stack size

        d->_pcDocument->setMaxUndoStackSize(App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")->GetInt("MaxUndoSize",20));

    }

}
  1. 渲染对象

在Document::slotNewObject()函数中,主要完成以下工作:

  • 根绝文档对象obj关联的ViewProvider类型,创建派生于ViewProviderDocumentObject的pcProvider,
  • pcProvider调用attach()关联文档对象obj
  • 将pcProvider添加到所有ViewInventor3D视图中进行显示
void Document::slotNewObject(const App::DocumentObject& Obj)

{

    ViewProviderDocumentObject* pcProvider = static_cast<ViewProviderDocumentObject*>(getViewProvider(&Obj));

    if (!pcProvider) {

        //Base::Console().Log("Document::slotNewObject() called\\n");

        std::string cName = Obj.getViewProviderName();

        if (cName.empty()) {

            // handle document object with no view provider specified

            Base::Console().Log("%s has no view provider specified\\n", Obj.getTypeId().getName());

            return;

        }

        setModified(true);

        Base::BaseClass* base = static_cast<Base::BaseClass*>(Base::Type::createInstanceByName(cName.c_str(),true));

        if (base) {

            // type not derived from ViewProviderDocumentObject!!!

            assert(base->getTypeId().isDerivedFrom(Gui::ViewProviderDocumentObject::getClassTypeId()));

            pcProvider = static_cast<ViewProviderDocumentObject*>(base);

            d->_ViewProviderMap[&Obj] = pcProvider;

            try {

                // if successfully created set the right name and calculate the view

                //FIXME: Consider to change argument of attach() to const pointer

                pcProvider->attach(const_cast<App::DocumentObject*>(&Obj));

                pcProvider->updateView();

                pcProvider->setActiveMode();

            }

            catch(const Base::MemoryException& e){

                Base::Console().Error("Memory exception in '%s' thrown: %s\\n",Obj.getNameInDocument(),e.what());

            }

            catch(Base::Exception &e){

                e.ReportException();

            }

#ifndef FC_DEBUG

            catch(...){

                Base::Console().Error("App::Document::_RecomputeFeature(): Unknown exception in Feature \\"%s\\" thrown\\n",Obj.getNameInDocument());

            }

#endif

        }

        else {

            Base::Console().Warning("Gui::Document::slotNewObject() no view provider for the object %s found\\n",cName.c_str());

        }

    }

    if (pcProvider) {

        std::list<Gui::BaseView*>::iterator vIt;

        // cycling to all views of the document

        for (vIt = d->baseViews.begin();vIt != d->baseViews.end();++vIt) {

            View3DInventor *activeView = dynamic_cast<View3DInventor *>(*vIt);

            if (activeView)

                activeView->getViewer()->addViewProvider(pcProvider);

        }

        // adding to the tree

        signalNewObject(*pcProvider);

        // it is possible that a new viewprovider already claims children

        handleChildren3D(pcProvider);

    }

}

运行结果:

图片[2]-FreeCAD中数据显示流程-卡核

 

  • TreeDockWidget中数据的显示流程

TreeDockWidget通过内嵌的TreeWidget类型的对象来层次化地显示文档对象。

  1. 新建文档

在TreeWidget的构造函数中,关联了新建文档等信号,可以看到App::Application::signalNewDocument信号关联到了Gui::TreeWidget::slotNewDocument。

TreeWidget::TreeWidget(QWidget* parent)

    : QTreeWidget(parent), contextItem(0), fromOutside(false)

{

    this->setDragEnabled(true);

    this->setAcceptDrops(true);

    this->setDropIndicatorShown(false);

    this->setRootIsDecorated(false);

    this->createGroupAction = new QAction(this);

    this->createGroupAction->setText(tr("Create group..."));

    this->createGroupAction->setStatusTip(tr("Create a group"));

    connect(this->createGroupAction, SIGNAL(triggered()),

            this, SLOT(onCreateGroup()));

    this->relabelObjectAction = new QAction(this);

    this->relabelObjectAction->setText(tr("Rename"));

    this->relabelObjectAction->setStatusTip(tr("Rename object"));

#ifndef Q_OS_MAC

    this->relabelObjectAction->setShortcut(Qt::Key_F2);

#endif

    connect(this->relabelObjectAction, SIGNAL(triggered()),

            this, SLOT(onRelabelObject()));

    this->finishEditingAction = new QAction(this);

    this->finishEditingAction->setText(tr("Finish editing"));

    this->finishEditingAction->setStatusTip(tr("Finish editing object"));

    connect(this->finishEditingAction, SIGNAL(triggered()),

            this, SLOT(onFinishEditing()));

    this->skipRecomputeAction = new QAction(this);

    this->skipRecomputeAction->setCheckable(true);

    this->skipRecomputeAction->setText(tr("Skip recomputes"));

    this->skipRecomputeAction->setStatusTip(tr("Enable or disable recomputations of document"));

    connect(this->skipRecomputeAction, SIGNAL(toggled(bool)),

            this, SLOT(onSkipRecompute(bool)));

    this->markRecomputeAction = new QAction(this);

    this->markRecomputeAction->setText(tr("Mark to recompute"));

    this->markRecomputeAction->setStatusTip(tr("Mark this object to be recomputed"));

    connect(this->markRecomputeAction, SIGNAL(triggered()),

            this, SLOT(onMarkRecompute()));

    this->searchObjectsAction = new QAction(this);

    this->searchObjectsAction->setText(tr("Search..."));

    this->searchObjectsAction->setStatusTip(tr("Search for objects"));

    connect(this->searchObjectsAction, SIGNAL(triggered()),

            this, SLOT(onSearchObjects()));

    // Setup connections

    connectNewDocument = Application::Instance->signalNewDocument.connect(boost::bind(&TreeWidget::slotNewDocument, this, _1));

    connectDelDocument = Application::Instance->signalDeleteDocument.connect(boost::bind(&TreeWidget::slotDeleteDocument, this, _1));

    connectRenDocument = Application::Instance->signalRenameDocument.connect(boost::bind(&TreeWidget::slotRenameDocument, this, _1));

    connectActDocument = Application::Instance->signalActiveDocument.connect(boost::bind(&TreeWidget::slotActiveDocument, this, _1));

    connectRelDocument = Application::Instance->signalRelabelDocument.connect(boost::bind(&TreeWidget::slotRelabelDocument, this, _1));

    QStringList labels;

    labels << tr("Labels & Attributes");

    this->setHeaderLabels(labels);

    // make sure to show a horizontal scrollbar if needed

#if QT_VERSION >= 0x050000

    this->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);

#else

    this->header()->setResizeMode(0, QHeaderView::ResizeToContents);

#endif

    this->header()->setStretchLastSection(false);

    // Add the first main label

    this->rootItem = new QTreeWidgetItem(this);

    this->rootItem->setText(0, tr("Application"));

    this->rootItem->setFlags(Qt::ItemIsEnabled);

    this->expandItem(this->rootItem);

    this->setSelectionMode(QAbstractItemView::ExtendedSelection);

#if QT_VERSION >= 0x040200

    // causes unexpected drop events (possibly only with Qt4.1.x)

    this->setMouseTracking(true); // needed for itemEntered() to work

#endif

    this->statusTimer = new QTimer(this);

    connect(this->statusTimer, SIGNAL(timeout()),

            this, SLOT(onTestStatus()));

    connect(this, SIGNAL(itemEntered(QTreeWidgetItem*, int)),

            this, SLOT(onItemEntered(QTreeWidgetItem*)));

    connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)),

            this, SLOT(onItemCollapsed(QTreeWidgetItem*)));

    connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)),

            this, SLOT(onItemExpanded(QTreeWidgetItem*)));

    connect(this, SIGNAL(itemSelectionChanged()),

            this, SLOT(onItemSelectionChanged()));

    this->statusTimer->setSingleShot(true);

    this->statusTimer->start(300);

    documentPixmap = new QPixmap(Gui::BitmapFactory().pixmap("Document"));

}
  1. 新建文档对象

当新建文档时,调用TreeWidget:: slotNewDocument()完成了DocumentItem类型文档项的创建,对应文档模型的根节点。

void TreeWidget::slotNewDocument(const Gui::Document& Doc)
{
    DocumentItem* item = new DocumentItem(&Doc, this->rootItem);

    this->expandItem(item);

    item->setIcon(0, *documentPixmap);

    item->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue()));

    DocumentMap[ &Doc ] = item;

}

而在DocumentItem的构造函数中,完成了新建对象文档事件的关联。可以看到,Gui::Document::signalNewObject关联到了Gui::DocumentItem::slotNewObject

DocumentItem::DocumentItem(const Gui::Document* doc, QTreeWidgetItem * parent)

    : QTreeWidgetItem(parent, TreeWidget::DocumentType), pDocument(doc)

{

    // Setup connections

    connectNewObject = doc->signalNewObject.connect(boost::bind(&DocumentItem::slotNewObject, this, _1));

    connectDelObject = doc->signalDeletedObject.connect(boost::bind(&DocumentItem::slotDeleteObject, this, _1));

    connectChgObject = doc->signalChangedObject.connect(boost::bind(&DocumentItem::slotChangeObject, this, _1));

    connectRenObject = doc->signalRelabelObject.connect(boost::bind(&DocumentItem::slotRenameObject, this, _1));

    connectActObject = doc->signalActivatedObject.connect(boost::bind(&DocumentItem::slotActiveObject, this, _1));

    connectEdtObject = doc->signalInEdit.connect(boost::bind(&DocumentItem::slotInEdit, this, _1));

    connectResObject = doc->signalResetEdit.connect(boost::bind(&DocumentItem::slotResetEdit, this, _1));

    connectHltObject = doc->signalHighlightObject.connect(boost::bind(&DocumentItem::slotHighlightObject, this, _1,_2,_3));

    connectExpObject = doc->signalExpandObject.connect(boost::bind(&DocumentItem::slotExpandObject, this, _1,_2));

    connectScrObject = doc->signalScrollToObject.connect(boost::bind(&DocumentItem::slotScrollToObject, this, _1));

    setFlags(Qt::ItemIsEnabled/*|Qt::ItemIsEditable*/);

}
  1. 创建节点

在DocumentItem::slotNewObject中通过调用createNewItem创建树节点。

void DocumentItem::slotNewObject(const Gui::ViewProviderDocumentObject& obj) {

    createNewItem(obj);

}
  1. TreeWidget中对象数据的显示

结合前述文档对象的创建过程可以看出,当App::Document::addObject()创建完成文档对象之后,触发App::Document::signalNewObject事件,由于此事件关联到了Gui::DocumentItem::slotNewObject槽函数,会调用createNewItem函数在TreeWidget创建对应的树节点。

boost::signals2::signal<void(const App::DocumentObject&)> App::Document::signalNewObject;

bool DocumentItem::createNewItem(const Gui::ViewProviderDocumentObject& obj,

            QTreeWidgetItem *parent, int index, DocumentObjectItemsPtr ptrs)

{

    const char *name;

    if (!obj.showInTree() || !(name=obj.getObject()->getNameInDocument()))

        return false;

    if (!ptrs) {

        auto &items = ObjectMap[name];

        if (!items) {

            items.reset(new DocumentObjectItems);

        }

        else if(items->size() && parent==NULL) {

            Base::Console().Warning("DocumentItem::slotNewObject: Cannot add view provider twice.\\n");

            return false;

        }

        ptrs = items;

    }

    std::string displayName = obj.getObject()->Label.getValue();

    DocumentObjectItem* item = new DocumentObjectItem(

        const_cast<Gui::ViewProviderDocumentObject*>(&obj), ptrs);

    if (!parent)

        parent = this;

    if (index<0)

        parent->addChild(item);

    else

        parent->insertChild(index,item);

    // Couldn't be added and thus don't continue populating it

    // and delete it again

    if (!item->parent()) {

        delete item;

    }

    else {

        item->setIcon(0, obj.getIcon());

        item->setText(0, QString::fromUtf8(displayName.c_str()));

        populateItem(item);

    }

    return true;

}

附录A:  OIV测试CMakeLists

#add_defintions(-D_FC_GUI_ENABLED_)

#add_defintions(-DFREECADMAINPY)

######################## OIV ########################

SET(OIV_SRCS

    Test_OIV.cpp

)

include_directories(

    ${COIN3D_INCLUDE_DIRS}

)

SET(OIV_LIBS

    ${Boost_LIBRARIES}

    ${COIN3D_LIBRARIES}

    ${SOQT_LIBRARIE}

    ${OPENGL_gl_LIBRARY}

    FreeCADGui

)

if (BUILD_QT5)

    include_directories(

        ${Qt5Core_INCLUDE_DIRS}

        ${Qt5Widgets_INCLUDE_DIRS}

        ${Qt5OpenGL_INCLUDE_DIRS}

        ${Qt5PrintSupport_INCLUDE_DIRS}

        ${Qt5Svg_INCLUDE_DIRS}

        ${Qt5Network_INCLUDE_DIRS}

        ${Qt5UiTools_INCLUDE_DIRS}

    )

    list(APPEND OIV_LIBS

        ${Qt5Core_LIBRARIES}

        ${Qt5Widgets_LIBRARIES}

        ${Qt5OpenGL_LIBRARIES}

        ${Qt5PrintSupport_LIBRARIES}

        ${Qt5Svg_LIBRARIES}

        ${Qt5Network_LIBRARIES}

        ${Qt5UiTools_LIBRARIES}

    )

else()

    include_directories(

        ${QT_INCLUDE_DIR}

    )

    list(APPEND OIV_LIBS

        ${QT_LIBRARIES}

        ${QT_QTUITOOLS_LIBRARY}

    )

endif()

add_definitions(-D SOQT_DLL)

add_executable(Test_OIV WIN32 ${OIV_SRCS})

target_link_libraries(Test_OIV  ${OIV_LIBS})

SET_BIN_DIR(Test_OIV Test_OIV)

if(WIN32)

    INSTALL(TARGETS Test_OIV

        RUNTIME DESTINATION bin

        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}

    )

elseif(APPLE AND NOT BUILD_WITH_CONDA)

    INSTALL(TARGETS Test_OIV

    RUNTIME DESTINATION MacOS

        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}

    )

else()

    INSTALL(TARGETS Test_OIV

        RUNTIME DESTINATION bin

    )

endif()

参考资料

  1. 左旭卫原平. 面向对象的三维图形软件包——Open Inventor
  2. Open Inventor Toolkit
  3. FreeCAD Developer hub
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容