對(duì)象完全指南:GUI 顯示層架構(gòu)與開(kāi)發(fā)實(shí)踐)
FreeCAD 視圖提供者View Provider對(duì)象完全指南GUI 顯示層架構(gòu)與開(kāi)發(fā)實(shí)踐【免費(fèi)下載鏈接】FreeCADOfficial source code of FreeCAD, a free and opensource multiplatform 3D parametric modeler.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/fr/FreeCAD本篇技術(shù)指南以 FreeCAD 官方 Sphinx 文檔源 src/Doc/sphinx/ViewProvider.rst 為核心骨架系統(tǒng)講解 FreeCAD GUI 架構(gòu)中承載 3D 視圖與樹(shù)視圖顯示的ViewProvider視圖提供者對(duì)象。讀者將掌握 ViewProvider 在 App/Gui 分層架構(gòu)中的定位、其基于 Coin3D 場(chǎng)景圖的核心節(jié)點(diǎn)結(jié)構(gòu)與顯示模式機(jī)制、選擇處理、樹(shù)視圖交互、編輯模式以及 Python 側(cè)的編程接口并能結(jié)合倉(cāng)庫(kù)源碼理解如何為自定義對(duì)象實(shí)現(xiàn)自己的視圖提供者。一、什么是 ViewProviderGUI 層與數(shù)據(jù)層之間的橋FreeCAD 采用嚴(yán)格的數(shù)據(jù)App/界面Gui分離架構(gòu)App層的 DocumentObject 只負(fù)責(zé)幾何數(shù)據(jù)與文檔結(jié)構(gòu)本身不關(guān)心對(duì)象在屏幕上的樣子而ViewProvider則是Gui層中所有可視化內(nèi)容的通用接口。在 src/Gui/ViewProvider.h 的類(lèi)注釋中明確寫(xiě)道General interface for all visual stuff in FreeCAD. This class is used to generate and handle all around visualizing and presenting objects from the FreeCAD App layer to the user. This class and its descendents have to be implemented for any object type in order to show them in the 3DView and TreeView.即任何對(duì)象類(lèi)型要想在 3D 視圖3DView和樹(shù)視圖TreeView中顯示都必須為它實(shí)現(xiàn) ViewProvider 及其子類(lèi)。文檔 src/Doc/sphinx/ViewProvider.rst 通過(guò) Sphinx 的automodule/autoclass指令將Gui::ViewProvider的完整成員自動(dòng)生成為 Python API 參考文檔是開(kāi)發(fā)者查閱 ViewProvider 編程接口的官方入口該文檔被掛接在 src/Doc/sphinx/index.rst 的 Python API 目錄樹(shù)中。ViewProvider 繼承自App::TransactionalObject事務(wù)對(duì)象這意味著它與 App 層對(duì)象一樣具備屬性Property系統(tǒng)和事務(wù)記錄能力。其核心職責(zé)可歸納為構(gòu)建并維護(hù)該對(duì)象在 3D 視圖中的 Coin3D 場(chǎng)景圖子圖響應(yīng) App 層屬性變化同步刷新顯示內(nèi)容updateData處理鼠標(biāo)拾取、選擇高亮等交互向樹(shù)視圖提供圖標(biāo)、子對(duì)象分組、拖放支持管理顯示模式線框/著色/隱藏線等與可見(jiàn)性進(jìn)入/退出編輯模式時(shí)接管視圖交互。二、核心場(chǎng)景圖結(jié)構(gòu)Root、ModeSwitch、Transform 與 AnnotationViewProvider 顯示的核心載體是 Coin3DOpen Inventor場(chǎng)景圖。在 src/Gui/ViewProvider.h 中可以看到它維護(hù)了四個(gè)關(guān)鍵節(jié)點(diǎn)指針成員類(lèi)型作用pcRootSoSeparator*ViewProvider 場(chǎng)景圖的根分離器被掛接到 3D 視圖主場(chǎng)景pcTransformSoTransform*該視圖對(duì)象的變換節(jié)點(diǎn)承載位置/旋轉(zhuǎn)/縮放pcModeSwitchSoSwitch*顯示模式開(kāi)關(guān)節(jié)點(diǎn)所有不同顯示模式的子圖都收集于此pcAnnotationSoSeparator*注解根節(jié)點(diǎn)如尺寸標(biāo)注、臨時(shí)顯示元素可為空對(duì)外訪問(wèn)接口分別為getRoot()、getModeSwitch()、getTransformNode()、getAnnotation()與getOrCreateAnnotation()。此外還有三個(gè)根級(jí)訪問(wèn)方法反映了場(chǎng)景圖的組織層次getFrontRoot()前置根節(jié)點(diǎn)getChildRoot()收集子對(duì)象的根節(jié)點(diǎn)返回SoGroup*getBackRoot()后置根節(jié)點(diǎn)。canAddToSceneGraph()決定該 ViewProvider 是否應(yīng)加入場(chǎng)景圖isPartOfPhysicalObject()決定它是加入對(duì)象分組true還是僅加入場(chǎng)景圖false。而claimChildren3D()則向 3D 視圖交付應(yīng)被分組到該對(duì)象場(chǎng)景圖下的子對(duì)象直接影響到子對(duì)象的可見(jiàn)性與 3D 位置跟隨行為。節(jié)點(diǎn)生命周期由倉(cāng)庫(kù)內(nèi)自帶的SoRefPtrT/CoinPtrT智能指針管理見(jiàn) src/Gui/ViewProvider.h它們封裝了 Coin3D 節(jié)點(diǎn)的ref()/unref()引用計(jì)數(shù)避免懸掛指針。顯示模式切換的底層實(shí)現(xiàn)hide()與show()的實(shí)現(xiàn)見(jiàn) src/Gui/ViewProvider.cpp直接操作pcModeSwitchvoid ViewProvider::hide() { // ... 通知擴(kuò)展 if (pcModeSwitch-whichChild.getValue() 0) { pcModeSwitch-whichChild -1; // -1 表示不渲染任何子節(jié)點(diǎn) // ... 通知擴(kuò)展 modeSwitch 變化 } } void ViewProvider::show() { setModeSwitch(); // 恢復(fù)當(dāng)前模式對(duì)應(yīng)的子節(jié)點(diǎn)索引 // ... 通知擴(kuò)展 show } bool ViewProvider::isShow() const { return pcModeSwitch-whichChild.getValue() ! -1; }可見(jiàn)可見(jiàn)性本質(zhì)上是把SoSwitch的whichChild設(shè)為-1全部隱藏或恢復(fù)為實(shí)際模式索引。setVisible(bool)與isVisible()分別是這對(duì)操作的便捷封裝。setModeSwitch()src/Gui/ViewProvider.cpp則負(fù)責(zé)把開(kāi)關(guān)切到正確分支void ViewProvider::setModeSwitch() { if (viewOverrideMode -1) { pcModeSwitch-whichChild _iActualMode; // 正常模式 } else if (viewOverrideMode pcModeSwitch-getNumChildren()) { pcModeSwitch-whichChild viewOverrideMode; // 覆蓋模式如高斯曲率著色 } // ... }這里體現(xiàn)了顯示覆蓋模式setOverrideMode與實(shí)際顯示模式getActualMode的分離setOverrideMode(As Is)恢復(fù)原狀否則在_sDisplayMaskModes映射中查找模式名對(duì)應(yīng)的節(jié)點(diǎn)索引并臨時(shí)切換見(jiàn) src/Gui/ViewProvider.cpp。三、顯示模式系統(tǒng)getDisplayModes / setDisplayMode / DisplayMaskModeViewProvider 提供兩組模式機(jī)制容易混淆需區(qū)分顯示模式Display Modes如線框、著色、隱藏線等通過(guò)getDisplayModes()返回可用模式名列表setDisplayMode(const char*)切換getActiveDisplayMode()/getDefaultDisplayMode()查詢(xún)當(dāng)前與默認(rèn)模式。基類(lèi)實(shí)現(xiàn)會(huì)向所有ViewProviderExtension擴(kuò)展轉(zhuǎn)發(fā)extensionSetDisplayMode/extensionGetDisplayModes見(jiàn) src/Gui/ViewProvider.cpp子類(lèi)通常需要重寫(xiě)以處理新模式。顯示掩碼模式Display Mask Modes主要控制SoSwitch選擇不同的顯示掩碼分支。與顯示模式數(shù)量不必一一對(duì)應(yīng)——例如高斯曲率、平均曲率、灰度等多個(gè)顯示模式可能共享同一個(gè)處理顏色的掩碼分支。相關(guān) API 為addDisplayMaskMode(SoNode*, const char*)、setDisplayMaskMode、getDisplayMaskMode、getDisplayMaskModes()見(jiàn) src/Gui/ViewProvider.h。此外還有setRenderCacheMode(int)控制渲染緩存以及toggleVisibility()——默認(rèn)切換自身可見(jiàn)性但可被重寫(xiě)以重定向到其他目標(biāo)如容器對(duì)象由Std_ToggleVisibility命令調(diào)用。四、ViewProviderDocumentObject與文檔對(duì)象綁定的基類(lèi)實(shí)際中絕大多數(shù)功能對(duì)象的視圖提供者繼承自Gui::ViewProviderDocumentObject見(jiàn) src/Gui/ViewProviderDocumentObject.h。它在 ViewProvider 基礎(chǔ)上增加了一個(gè)指向 App 層對(duì)象的pcObject指針、所屬 GUI 文檔pcDocument并提供attach(App::DocumentObject*)將視圖提供者綁定到文檔對(duì)象首次創(chuàng)建時(shí)調(diào)用reattach重新綁定getObject()取回關(guān)聯(lián)的 App 對(duì)象updateView()/forceUpdate()強(qiáng)制重繪部分視圖提供者在隱藏時(shí)會(huì)跳過(guò)視覺(jué)更新需要強(qiáng)制刷新startRestoring()/finishRestoring()文檔加載時(shí)從GuiDocument.xml恢復(fù)狀態(tài)src/Gui/ViewProviderDocumentObject.cpp 中可以看到恢復(fù)時(shí)先hide()并同步 App 對(duì)象的Visibility屬性getTreeRank()樹(shù)視圖中的排序?qū)蛹?jí)。內(nèi)置屬性Display Options 與 Selection 分組構(gòu)造函數(shù)src/Gui/ViewProviderDocumentObject.cpp注冊(cè)了 5 個(gè)內(nèi)置屬性分為兩組Display Options顯示選項(xiàng)組屬性類(lèi)型說(shuō)明DisplayModePropertyEnumeration顯示模式對(duì)應(yīng)視圖提供者的模式列表VisibilityPropertyBool是否在 3D 視圖中顯示該對(duì)象ShowInTreePropertyBool是否在樹(shù)視圖中顯示該對(duì)象Selection選擇組屬性類(lèi)型枚舉值說(shuō)明SelectionStylePropertyEnumerationShape、BoundBox對(duì)象選擇樣式按形狀拾取或按包圍盒拾取OnTopWhenSelectedPropertyEnumerationDisabled、Enabled、Object、Element選中時(shí)是否置頂顯示Object表示僅當(dāng)整個(gè)對(duì)象被選中時(shí)置頂Element表示僅當(dāng)對(duì)象某個(gè)子元素被選中時(shí)置頂這些屬性即為屬性視圖中View標(biāo)簽頁(yè)外觀/顯示的底層數(shù)據(jù)來(lái)源。getTaskViewContent()會(huì)向任務(wù)面板注入TaskAppearance外觀編輯框見(jiàn) src/Gui/ViewProviderDocumentObject.cpp。五、選擇處理從拾取點(diǎn)到子元素名ViewProvider 負(fù)責(zé)把鼠標(biāo)在 3D 視圖中的拾取結(jié)果翻譯為 FreeCAD 的子元素引用如Face1、Edge5這是選擇、測(cè)量、約束等一切基于子元素功能的基礎(chǔ)。相關(guān)方法集中在 src/Gui/ViewProvider.h 的 Selection handling 分組useNewSelectionModel()是否使用新的統(tǒng)一選擇模型isSelectable()是否可被選擇getElementPicked(const SoPickedPoint*, std::string subname)根據(jù)拾取點(diǎn)返回命中元素getElement(const SoDetail*)根據(jù) Coin3D 細(xì)節(jié)對(duì)象返回子元素名getDetail(const char*)反向返回子元素對(duì)應(yīng)的 Coin 節(jié)點(diǎn)細(xì)節(jié)getDetailPath(subname, pPath, append, det)返回指向子元素的 Coin 路徑與細(xì)節(jié)。若該視圖提供者鏈接了其他視圖提供者實(shí)現(xiàn)還必須追加中間節(jié)點(diǎn)直到被鏈接視圖提供者的模式開(kāi)關(guān)getRelatedElements(subname, pickPoint)將一次拾取擴(kuò)展為一組邏輯相關(guān)的子元素例如同一特征上相鄰的面默認(rèn)返回空向量subname如Face1subName為完整子元素引用如InternalFace1getModelPoints()拾取點(diǎn)對(duì)應(yīng)的模型空間坐標(biāo)getSelectionShape(Element)返回某元素或整個(gè)形狀的高亮線partialRender(subelements, clear)局部渲染——只渲染指定的子元素集合需場(chǎng)景中存在至少一個(gè)SoFCSelectRoot節(jié)點(diǎn)cleartrue時(shí)移除局部渲染onSelectionChanged(const SelectionChanges)選擇變化回調(diào)。包圍盒查詢(xún)getBoundingBox(subname, mat, transform, view, depth)無(wú)論對(duì)象當(dāng)前是否可見(jiàn)都能工作depth參數(shù)用于防止無(wú)限遞歸鏈接對(duì)象場(chǎng)景內(nèi)部實(shí)現(xiàn)為受保護(hù)的_getBoundingBox()子類(lèi)可重寫(xiě)定制。六、樹(shù)視圖交互圖標(biāo)、子對(duì)象分組與拖放ViewProvider 同時(shí)驅(qū)動(dòng)樹(shù)視圖組合視圖中的模型樹(shù)的表現(xiàn)對(duì)應(yīng) src/Gui/ViewProvider.h 的分組樹(shù)表現(xiàn)getIcon()返回樹(shù)中顯示的圖標(biāo)mergeColorfulOverlayIcons()/mergeGreyableOverlayIcons()疊加彩色/可置灰的角標(biāo)圖標(biāo)如狀態(tài)標(biāo)記claimChildren()/claimChildrenRecursive()返回應(yīng)被分組到該對(duì)象標(biāo)簽下的子對(duì)象列表分組、裝配、鏈接等對(duì)象的典型用法showInTree()是否出現(xiàn)在樹(shù)中canToggleVisibility()是否允許切換可見(jiàn)性——ToggleVisibilityMode枚舉CanToggleVisibility/NoToggleVisibility控制的特性對(duì)VarSet、Spreadsheet這類(lèi)不渲染的對(duì)象返回 false見(jiàn) src/Gui/ViewProvider.h。拖放Drag Drop拖放能力遵循先聲明、后實(shí)現(xiàn)的約定canDragObjects()/canDropObjects()聲明是否支持拖出/拖入canDragObject()/canDropObject()可按對(duì)象類(lèi)型細(xì)粒度過(guò)濾實(shí)際動(dòng)作由dragObject()/dropObject()完成。跨文檔場(chǎng)景使用canDropObjectEx()/dropObjectEx()變體樹(shù)視圖優(yōu)先調(diào)用它們傳入完整限定名、父對(duì)象owner、子名引用subname與被選中的非對(duì)象子元素elements。默認(rèn)實(shí)現(xiàn)在ViewProviderDocumentObject中禁止跨文檔拖放重寫(xiě)它才能啟用跨文檔鏈接。replaceObject(oldObj, newObj)支持拖放替換返回 1 成功 / 0 未找到 / -1 不支持getDropPrefix()返回承接拖放對(duì)象的子對(duì)象引用前綴acceptReorderingObjects()決定是否接受拖放時(shí)重排子對(duì)象。七、編輯模式與任務(wù)面板當(dāng)用戶(hù)雙擊對(duì)象或執(zhí)行編輯命令時(shí)ViewProvider 進(jìn)入編輯模式接管視圖交互。相關(guān)機(jī)制見(jiàn) src/Gui/ViewProvider.h編輯模式EditMode枚舉Default 0、Transform、Cutting、Color該枚舉被反映到Application.h的userEditModes映射中用戶(hù)可通過(guò) GUI 選擇默認(rèn)編輯模式setEdit(int ModNum)受保護(hù)進(jìn)入編輯unsetEdit(int ModNum)退出startEditing()/isEditing()/finishEditing()為公開(kāi)入口setEditViewer()/unsetEditViewer()進(jìn)入/離開(kāi)編輯時(shí)調(diào)整視圖設(shè)置keyPressed()、mouseMove()、mouseButtonPressed()、mouseWheelEvent()編輯期間的鍵盤(pán)/鼠標(biāo)事件轉(zhuǎn)發(fā)setupContextMenu()構(gòu)造支持編輯模式的右鍵菜單doubleClicked()樹(shù)中雙擊回調(diào)getTransactionText()返回 undo/redo 對(duì)話框中顯示的事務(wù)名返回 null 則不開(kāi)啟事務(wù)selectAll()編輯期間響應(yīng)全選命令返回 false 表示忽略。任務(wù)面板getTaskViewContent()返回與該對(duì)象關(guān)聯(lián)的TaskView::TaskContent列表用于在任務(wù)面板Task panel中呈現(xiàn)參數(shù)編輯框。ViewProvider 還通過(guò)onDelete(subNames)在刪除前征詢(xún)意見(jiàn)返回 false 可阻止刪除beforeDelete()保證在刪除前被調(diào)用canDelete(App::DocumentObject*)詢(xún)問(wèn)其 outlist 中的對(duì)象能否被移除。八、Python 側(cè)編程接口ViewProvider.pyiViewProvider 通過(guò)getPyObject()暴露給 PythonPython 封裝類(lèi)為ViewProviderPy聲明為友元。類(lèi)型存根 src/Gui/ViewProvider.pyi 完整列出了腳本開(kāi)發(fā)者可用的成員與 C 接口一一對(duì)應(yīng)場(chǎng)景圖與外觀vp.RootNode # pivy Separator本 ViewProvider 的根節(jié)點(diǎn) vp.SwitchNode # pivy SoSwitch顯示模式開(kāi)關(guān)節(jié)點(diǎn) vp.Annotation # pivy Separator用于追加自定義場(chǎng)景圖 vp.IV # 整個(gè) ViewProvider 的 Inventor 字符串表示 vp.DefaultMode # 以 coin 節(jié)點(diǎn)索引表示的默認(rèn)顯示模式可讀寫(xiě) vp.toString() # 返回 Inventor 節(jié)點(diǎn)字符串顯示模式vp.addDisplayMode(obj, mode) # obj: coin.SoNodemode: 模式名 vp.listDisplayModes() # 列出全部顯示模式 vp.show() / vp.hide() / vp.isVisible() vp.setTransformation(trans) # trans: Base.Placement 或 Base.Matrix選擇與拾取vp.getElementPicked(pickPoint) # pickPoint: coin.SoPickedPoint返回拾取子元素 vp.getDetailPath(subelement, path, appendTrue) vp.partialRender(sub, clearFalse) # 局部渲染subNone 時(shí)重置 vp.getBoundingBox(subnameNone, transformTrue, viewNone, matNone, depth0)顏色與樹(shù)vp.getElementColors(elementNameNone) # {elementName:(r,g,b,a)} vp.setElementColors(colors) vp.claimChildren() / claimChildrenRecursive() vp.canDragObject(obj) / dragObject(obj) vp.canDropObject(obj, *, owner, subname, elem) / dropObject(...) vp.replaceObject(oldObj, newObj) vp.doubleClicked() vp.signalChangeIcon() # 觸發(fā)圖標(biāo)變更信號(hào) vp.Icon / vp.CanRemoveChildrenFromRoot / vp.LinkVisibility vp.ToggleVisibility # ToggleVisibilityMode 枚舉其中addProperty/removeProperty/supportedProperties繼承自ExtensionContainer允許腳本為視圖提供者動(dòng)態(tài)添加屬性。這些接口使 FreeCAD 的 Python 宏與工作臺(tái)代碼能夠直接操控場(chǎng)景節(jié)點(diǎn)、顯示模式與選擇行為是開(kāi)發(fā)自定義視圖提供者腳本的基礎(chǔ)。九、擴(kuò)展機(jī)制ViewProviderExtension自基類(lèi)起ViewProvider 就支持通過(guò)ViewProviderExtension見(jiàn) src/Gui/ViewProviderExtension.h以擴(kuò)展方式注入行為而無(wú)需修改繼承鏈。從 src/Gui/ViewProvider.cpp 的多處實(shí)現(xiàn)可以看到這一模式貫穿始終setDisplayMode()遍歷所有擴(kuò)展并調(diào)用extensionSetDisplayMode()getDisplayModes()匯總各擴(kuò)展的extensionGetDisplayModes()hide()/show()/setModeSwitch()中分別調(diào)用extensionHide()/extensionShow()/extensionModeSwitchChange()。這樣工作臺(tái)可以在不子類(lèi)化的前提下通過(guò)擴(kuò)展給現(xiàn)有 ViewProvider 增加顯示模式、響應(yīng)顯隱事件實(shí)現(xiàn)行為的組合式復(fù)用。十、小結(jié)與開(kāi)發(fā)建議ViewProvider 是 FreeCAD GUI 架構(gòu)的樞紐它一頭連接 App 層的數(shù)據(jù)對(duì)象另一頭連接 Coin3D 場(chǎng)景圖與 Qt 樹(shù)視圖并同時(shí)向 Python 腳本開(kāi)放完整接口。官方 API 文檔入口即 src/Doc/sphinx/ViewProvider.rst通過(guò) Sphinxautoclass從 C 封裝自動(dòng)生成配合 src/Gui/ViewProvider.h、src/Gui/ViewProvider.cpp 與 src/Gui/ViewProviderDocumentObject.cpp 可以追查每個(gè)方法的真實(shí)實(shí)現(xiàn)。為自定義對(duì)象開(kāi)發(fā)視圖提供者時(shí)建議遵循以下路徑繼承Gui::ViewProviderDocumentObject在構(gòu)造函數(shù)中調(diào)用ADD_PROPERTY_TYPE注冊(cè)自己的外觀屬性實(shí)現(xiàn)attach()構(gòu)造 Coin3D 場(chǎng)景子圖并掛到getModeSwitch()下重寫(xiě)getDisplayModes()/setDisplayMode()/getDefaultDisplayMode()將模式名映射到SoSwitch子節(jié)點(diǎn)索引重寫(xiě)updateData()響應(yīng) App 層屬性變化getElementPicked()/getDetailPath()提供子元素拾取支持需要特殊樹(shù)結(jié)構(gòu)時(shí)重寫(xiě)claimChildren()與getIcon()需要交互時(shí)實(shí)現(xiàn)編輯模式與拖放方法若功能可復(fù)用優(yōu)先考慮寫(xiě)成ViewProviderExtension而非強(qiáng)制子類(lèi)化。【免費(fèi)下載鏈接】FreeCADOfficial source code of FreeCAD, a free and opensource multiplatform 3D parametric modeler.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/fr/FreeCAD創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考