簡述
這個手冊用來教授如何使用OpenCascade Application Framework(OCAF),並提供了基礎的OCAF使用文檔。
1.1 OCAF的目的
OCAF是為了快速開發專用設計軟件而設計的易用開發平台。使用OCAF開發的典型應用程序通常具有2維或者3維幾何造型功能的針對特定領域的計算機輔助設計,制造或分析的應用,仿真應用或繪制工具。
開發一個設計應用需要解決很多技術問題。特別的,鑒於應用程序應當具備的功能,至少需要解決以下的問題:
- 設計應用程序的架構--定義軟件組件和組件間的協作;
- 定義數據模型使其能夠滿足功能需求--在整個用戶設計過程中,設計應用程序對數據的操作;
- 構建軟件用來:
- 數據的同步顯示--命令修改對象后更新到顯示界面;
- 支持撤銷重做(undo-redo)功能--這個功能將帶給用戶更好的設計體驗;
- 實現保存數據的功能--如果應用程序有很長的生命周期,應當在不同版本中考慮到數據的兼容性;
- 構建應用軟件的用戶界面。
OCAF所提供的架構指南和即用性解決方案給你提供了如下好處:
- 你可以僅僅關注你應用程序特殊的功能;
- 已經提供了支持應用程序所需的底層機制;
- 由於耦合了其他 Open CASCADE 技術,該應用程序可以快速建立原型模塊;
- 最終應用可以通過工業化的原型來開發--你不需要從頭進行開發;
- 該平台的開源性質,保證了您的開發的長期有效性。
OCAF不僅僅是一個囊括CAS.CADE對象庫的工具包。因為他既可以處理這些庫中的數據和算法(無論建模算法、拓撲還是幾何),又可以作為其邏輯補充。
下面是僅僅使用對象庫和使用OCAF構建建模應用對比表:
表1:OCAF提供的服務
開發任務 | 評論 | 不使用OCAF | 使用OCAF |
---|---|---|---|
創建一個幾何 | 調用建模算法庫 | 由用戶創建 | 由用戶創建 |
數據組織 | 包括特殊屬性和建模流程 | 由用戶創建 | 簡化 |
存儲數據到文件 | 文件概念 | 由用戶創建 | 提供 |
文檔視圖管理 | 由用戶創建 | 提供 | |
應用基礎設施 | 打開、新建、關閉、保存和保存為文件 | 由用戶創建 | 提供 |
撤銷與重做 | 健壯,多層次 | 由用戶創建 | 提供 |
特定於應用程序對話框 | 由用戶創建 | 由用戶創建 |
OCAF使用了Open CASCADE其他模塊--幾何部分依賴於Modeling Data
模塊,顯示部分依賴於Visualization
模塊,建模功能使用了Modeling Algorithms
模塊。
下圖可以很好的表示OCAF同OCCT對象庫之間的關系:
在這個圖片上,OCAF為黑色矩形部分,OCCT中的對象庫以白色矩形呈現。
接下來的章節主要用以介紹和使用OCAF提供的服務。
1.2 架構概述
OCAF提供應用程序(Application)-文檔(Document)-屬性(Attribute)模型的C++庫給用戶。
1.2.1 應用
在工作會話期間,應用(Application) 是一個抽象的類負責文檔管理,即:
- 創建新文檔;
- 保存文檔並打開文檔;
- 初始化文檔視圖。
1.2.2 文檔
Document是應用程序數據的容器,並由具體的Document類實現。Document提供了獲取數據框架方法並滿足了下面幾個目的:
- 管理變化通知
- 更新外部連接
- 管理數據的保存和恢復
- 存儲軟件擴展名
- 管理命令事務
- 管理撤消和重做選項
每個文檔都保存在由其格式和擴展名定義的單個 ASCII 文件(OCAF提供的格式)中。
除了作為數據的容器外,文檔之間可以相互引用;例如:文件A可以引用文件B的特定標簽。這種相互應用通過引用鍵來實現。
1.2.3 屬性
應用數據通過屬性描述,屬性是從 Attribute 抽象類派生的類的實例,並根據OCAF數據框架組織的。
OCAF 數據框架使用單一層次結構中的持久標識符引用屬性集合。OCAF 具有廣泛的屬性,包括:
- 標准屬性 允許操作數據架構中簡單共有數據(例如:整型、實型、字符串型、數組),實現輔助功能(例如:標簽計數器子標簽源屬性),創建依賴項(例如:引用、樹節點)...;
- 體屬性:包含整個模型的幾何體或者元素中包含對形狀的引用和形狀演變的跟蹤。
- 其他幾何屬性:例如:Datums(點、軸或平面)和約束(Constraints)(切線至、給定距離、從一個給定的角度、同心等等);
- 用戶屬性,應用程序的一些屬性。
- 可視化屬性:允許使用數據架構替換視圖信息,顯示對象並展示一些額外信息。
- 功能服務屬性:此屬性的目的是重建對象,用於當對象(參數化對象)被修改后。當文檔的數據發生變化,功能管理將廣播這些變化。功能機制提供了功能之間的聯系和對各種算法的調用。
此外,應用程序的特殊數據可以通過添加新的屬性類來實現;當然,這必然改變了標准文件格式。因此需要實現額外的功能:
- 復制屬性
- 將其轉化為持久數據存儲
1.3 引用鍵值模型(Reference-Key Model)
在大多數已存在的幾何建模系統中,數據都是由拓撲驅動的。它們通常使用邊界表示法(BRep)來附着應用程序的數據。邊界表示法可以使得幾何建模被定義為一系列面、邊和頂點。例如:
- 色彩;
- 材質;
- 特定邊混合的信息。
當幾何模型是參數化模型,若改變參數的數值,幾何形狀便會發生變化。為了維護應用程序數據的附件,幾何信息應當與其他的信息做區分。
在OCAF中,這些信息數據是由reference-key(引用鍵值)
驅動的。引用鍵值是統一的模型,並且是數據持久的標識。所有可訪問的數據,包括幾何等,都被作為屬性(attribute)實現並附着在引用鍵值上。幾何圖形成為 Shape 屬性的值,就像數字是 Integer 和 Real 屬性的值以及字符串是 Name 屬性的值一樣。
在單個引用鍵上,可以聚合許多屬性;應用程序可以在運行時請求哪些屬性屬於激活狀態。例如,給一個幾何模型關聯材質,那么面和材質就會被吸附到同一個引用鍵值上。
引用鍵值可以通過兩種方式創建:
- 編程時,通過application創建
- 運行時,由使用應用程序的終端用戶創建(你需要提供這項功能)
作為一個應用程序開發者,你生成引用鍵值目的是為了給數據賦予語義。例如,一個構建棱柱體的功能可能需要三個引用鍵值:一個用來表示棱柱體的底,另外一個用來表示棱柱的側面,最后一個表示頂面。這構成了應用程序的棱柱功能內置的語義。另一例子,一個指令可以允許用戶去設置材質到他/她選擇的面,如果當前面不存在任何引用,你必須創建一個引用鍵值到這個面(在這個示例中,在棱柱的側面創建一個引用鍵值)。
當你在被選中的拓撲元素(面、邊、頂點)上創建了一個引用鍵值,OCAF將到所選的拓撲元素附着引用鍵信息上--命名屬性。例如:他可能是選中邊的公共面。命名算法使用此信息和每個建模步驟(修改、更新和刪除面)等拓撲演變信息來維護附加到引用鍵的拓撲信息。因此,在參數化模型上,修改參數值后,引用鍵值仍然會處理相應的面,即使它的形狀已經發生改變。因此,若修改了上圖幾何體的大小,那么貼圖仍然附着在面上。
注意拓撲命名基於引用鍵和屬性例如:Naming(選擇信息)和Shape(拓撲演變信息),OCAF不耦合底層建模庫。OCAF僅僅需要建模服務有:
- 每個算法必須提供拓撲演變信息(通過算法使得面的更新,刪除,更新)
- 必須可以解剖幾何模型(一個三維模型是由多個面構成,面由封閉的線段構成,線又由一系列的邊和頂點構成)
現在,OCAF使用open CASCADE的幾何構造庫。
為了設計基於 OCAF 的數據模型,鼓勵應用程序開發人員聚合現成的屬性,而不是通過從抽象根類繼承來定義新屬性。 使用聚合而不是繼承有兩個主要優點:
- 由於您沒有通過定義新類來實現數據,因此 OCAF 提供的保存數據的格式不會改變; 所以你不必編寫 Save 和 Open 函數
- 如果特定屬性可用,應用程序可以在運行時查詢數據
總結
- OCAF基於統一的引用鍵模型,其中:
- 引用鍵提供數據的持久標識;
- 數據,包括幾何,作為屬性被實現並附着到引用鍵上;
- 拓撲命名(naming)維護在參數化模型中被選中的幾何附着在引用鍵上。
- 在很多應用程序,OCAF提供的數據類型不需要擴展;
- OCAF 不耦合底層的建模庫。
數據框架
2.1 數據結構
OCAF數據框架是Open CASCADE以樹結構實現引用鍵模型。它提供了單一環境,該環境可以處理來自不同應用程序組件的數據。並且允許以最高級別的信息和穩定的語義簡單、一致地交換和修改數據。
這種方式構建的基本塊有:
- tag
- label
- attribute
正如前面所說的,跨家中的第一個label是樹的第根label。每一個label有一個表示為整數的tag,並且每一個label從根label開始被定義為獨一無二的tag序列,例如:0:1:2:1。
每一個label可以有一系列屬性,屬性中包含了數據。每個屬性通過GUID來標識,盡管一個label可以有多個屬性,不可能存在兩個GUID相同的屬性。
兄弟label不能共享同一個tag。
最重要的屬性是標簽的條目是它在數據框架中的持久地址。
在此圖像中,圓圈中相應 labels 有一個 tags。tag序列在圓圈的底部。根label通常有一個 0 tag。
根label的子label叫中級label,在圖中就是 1 label和 3 label,這兩個label是兄弟label。
右下label的tag序列是0:3:4
:這個label的tag為4
,它的父label的tag為3
,父label的父label的tag為0
。
2.2 數據結構示例
接下來看一個例子:
在上圖中,用於設計咖啡機的應用程序首先為機器單元分配一個label。然后為主要特征(玻璃咖啡壺、水容器和過濾器)繼續添加子label,主要特征了根據需要再進行細化(咖啡壺的把手和水箱以及水箱的出水口)。
現在可以附上描述手柄的數據--他的幾何和顏色信息、水容器--幾何和材質信息。你可以修改手柄的幾何圖形不會更改它的顏色--兩者仍舊維持了相同的label。
label的嵌套是OCAF的關鍵。這允許標簽擁有自己的結構和本地尋址方案,可以在更加復雜的結構中重用。例如,制作咖啡機。給出一個咖啡機把柄並賦予[1]的標簽,這個把柄在實體中的地址為[0:1:1]。如果你需要建模一個存在兩個把柄的咖啡機。一個label為[1],另一個label為[4]。那么我們得到的兩個手柄的地址為[0:1:1:1]和[0:1:4:1]。通過這種方法我們避免了兩個咖啡手柄之間的混亂。
下一個例子關於設計一個台燈。第一個label分配給燈組(unit)。
根label不存在兄弟節點。所以,我們創建多個台燈時,只能在框架的根label的子label下進行創建。它可以很好的避免在數據框架下不同台燈的混亂。燈的不同部位存在着不同的材質、顏色和屬性,因此為燈分配如下幾個帶有tag號的子label:
- tag為1的燈罩
- tag為2的燈泡
- tag為3的燈座
label的tag隨意選擇。它是燈部件的唯一標識。此時,現在您可以精細化所有單位:通過設置燈或燈部件的圖形、顏色、材質和其他信息等到相應的label。該信息被放入label的特殊屬性中:純粹的label是不帶有信息的-它僅僅是用來索引信息的。
其中要注意的是label是私有的地址,在數據框架之外沒有任何意義。例如,將零件名稱作為tag是錯誤的。這些可能會在應用程序的下個版本中更改或者刪除,而該部分的確切形式可能在設計中重復使用,部分名稱可以作為屬性集成到框架中。
因此,當用戶更改了台燈設計,只需要調整相應的屬性,但是label結構是不需要改變的。必須通過新的屬性值重新創建燈形狀,並且燈形狀的屬性必須引用新形狀。
上圖顯示了多個台燈文檔結構:每個根label的子label都包含了一個形狀屬性和一些包含相應子單元的設計信息的子label。
數據框架結構可以允許更加復雜的結構:每個台燈的子label還可以有子label,這些子label包含了更加細節的信息。
根label也可以擁有屬性,比方說文檔的名字等。
2.3 Tag
一個tag是一個整型數據,用來區分label兩種方式:
- 相對標識。
- 絕對標識。
在相對標識中,一個label的tag僅僅與其父label相關。比方說,對一個特定的label,你可以有四個子label,分別以2,7,18,100作為tag。在使用相對標識時,您可以確保您有一個安全的范圍來設置屬性。
在絕對標識中,label在數據框架中的位置由冒號分隔的所有label的label列表來指明,該label列表由從當前的tag到根label的tag組成。該列表稱為entry(條目)。TDF_Tool::TagList
允許對一個特定的label的條目進行檢索。
無論在相對標識和絕對標識中,我們需要記住的是一個整型是沒有任何固定語義的。換句話說,諸如:0、1、2等這類數字並不重要,tag所使用的整型僅僅作為一個鍵來使用。
tag可以用兩種方式創建:
- 隨機傳遞。
- 用戶定義的方式。
顧名思義,在隨機傳遞方式中,tag的值由計算機隨機生成。在用戶定義的方式中,你可以對tag進行賦值。
2.3.1 使用隨機傳遞的方式創建子label的tag
使用TDF_TagSource::NewChild
來創建一個子label。在下面的例子中,參數level2
是父label。
TDF_Label child1 = TDF_TagSource::NewChild (level2);
TDF_Label child2 = TDF_TagSource::NewChild (level2);
2.3.2 使用用戶定義的方式創建子label的tag
另一種創建子label的方式是使用用戶傳遞tag的方式進行創建。換句話說,你需要指定一個特定的tag。
從您自己指定的label中檢索子label,正如下面例子所示,你需要使用TDF_Label::FindChild
和TDF_Label::Tag
。在這里,3是你想要創建子label的tag,另外一個參數代表的是否創建新label。當第二個參數為false時,將不會創建新的label。
TDF_Label achild = root.FindChild(3,Standard_False);
if (!achild.IsNull()) {
Standard_Integer tag = achild.Tag();
}
2.4 Label
tag為label提供了一個持久地址。 label——tag的語義——是數據框架中的一個位置,該位置還附加數據的屬性。 事實上,數據框架是一棵label樹,以根作為最終父label。
label不能被數據框架刪除,因此,當文件打開時,被創建出來的數據框架的結構是不能被刪除的。因此,當應用程序使用文檔時,對現有label的任何類型的引用都是實際的。
2.4.1 Label創建
label可以在任意label下創建,與兄弟label進行比較和索引。你也可以找到他們在數據框架下的深度(根label的深度為0,根label的子label深度為1,以此類推),無論它們的數據結構、是否有子label、以及其相對位置。TDF_Label
類提供以上功能。
2.4.2 創建子Label
在數據架構下創建子label使用確切傳值的tag,使用TDF_Label::FindChild
。
//creating a label with tag 10 at Root
TDF_Label lab1 = aDF->Root().FindChild(10); //creating labels 7 and 2 on label 10
TDF_Label lab2 = lab1.FindChild(7);
TDF_Label lab3 = lab1.FindChild(2);
你可以使用相同的語法,不同的是,可以將第二個參數設置為true。這樣可以確保當當前的tag不存在時,創建一個新的label。注意,第二個參數默認為true。
TDF_Label level1 = root.FindChild(3,Standard_True);
TDF_Label level2 = level1.FindChild(1,Standard_True);
2.4.3 檢索子Label
你可以檢索當前label的子label(單層)。
TDF_Label current; //
for (TDF_ChildIterator it1 (current,Standard_False); it1.More(); it1.Next())
{
achild = it1.Value();
// // do something on a child (level 1) //
}
你也可以檢索當前label的所有后代子label(所有子層)。
for (TDF_ChildIterator itall (current,Standard_True); itall.More(); itall.Next())
{
achild = itall.Value();
// // do something on a child (all levels) //
}
使用TDF_Tool::Entry
和TDF_ChildIterator
你也可以檢索當前label的子label。
void DumpChildren(const TDF_Label& aLabel) {
TDF_ChildIterator it; TCollection_AsciiString es;
for (it.Initialize(aLabel,Standard_True); it.More(); it.Next())
{
TDF_Tool::Entry(it.Value(),es); cout << as.ToCString() << endl;
}
}
2.4.4 檢索父label
檢索當前label的父label
TDF_Label father = achild.Father();
isroot = father.IsRoot();
2.5 Attribute
label本身不包含數據。無論任何數據-應用程序的或者非應用程序的-都在attribute里。attribute附着在label上,屬性對不同的數據具有不同的類型。OCAF提供很多易用的標准類型例如,整型,實型,約束,軸和平面。同時對於拓撲naming,方法和可視化也有attribute。每個屬性的類型通過GUID來辨別。
OCAF的優勢是所有的屬性都可以通過相同的方式處理。無論什么類型的屬性,你可以實例化它們,索引它們,附着或者移除它們到label,“忘記”或者“記住”特定標簽的屬性。
2.5.1通過Label來檢索Attribute
通過label來檢索屬性,使用TDF_Label::FindAttribute
類。如下例子:整型屬性的GUID和INT為參數傳遞到label的FindAttribute
中,便可獲得屬性。
if(current.FindAttribute(TDataStd_Integer::GetID(),INT))
{
// the attribute is found
}
else {
// the attribute is not found
}
2.5.2 使用GUID來確認屬性
你可以實例化一個新的屬性並檢索GUID。在下面例子中,創建一個整型屬性,它的GUID通過繼承自TDF_Attribute
的方法ID
傳遞給變量guid
。
Handle(TDataStd_Integer) INT = new TDataStd_Integer();
Standard_GUID guid = INT->ID();
2.5.3 添加Attribute到Label
你可以使用TDF_Label::Add
將屬性附着到label。重復調用這個語法,將會產生錯誤,因為有相同GUID的屬性附着在相同的label上。
下面例子用來展示,屬性附着和獲取當前屬性的label。
current.Add (INT); // INT 被附着到 current label上
current.Add (INT); // 重復附着產生錯誤
TDF_Label attach = INT->Label();// 獲取INT的label
2.5.4 測試Label的是否可附加
你可以通過TDF_Attribute::IsA
方法並將GUID傳入該方法,測試屬性是否已經被附着在label上。在下面的例子中,你測試當前label是否存在一個整型屬性,接着獲取有多少個屬性被附着在上面。TDataStd_Integer::GetID
提供參數所需要的GUID。
TDF_Attribute::HasAttribute
用來測試當前屬性是否被附着,TDF_Tool::NbAttributes
將返回當前label有多少個屬性被附着。
//測試屬性是否可附着 //
if (current.IsA(TDataStd_Integer::GetID())) {
// 當前label存在該類型屬性
} if (current.HasAttribute()) {
//當前label至少有一個屬性被附着
Standard_Integer nbatt = current.NbAttributes();
// 當前label被附着的屬性數量
}
2.5.5 在Label中移除Attribute
你可以使用TDF_Label::Forget
並傳入需要刪除屬性的GUID,來刪除一個label上相應的屬性。刪除所有屬性可以使用TDF_Label::ForgetAll
來實現。
current.Forget(TDataStd_Integer::GetID()); //整型屬性當前不附着在current上
current.ForgetAll(); // current標簽上不存在屬性
2.5.6 特殊Attribute創建
標准屬性不能滿足特定數據的顯示任務,那么用戶可以構建自己的數據類型和實現相應數據類型的屬性。
兩種方式實現一個新的數據類型:創建一個新的屬性(標准方法),或者使用標准屬性組合的方式。
在使用標准方法來創建新的屬性,創建一個繼承自TDF_Attribute
的類並實現一些純虛方法:
- ID()-返回一個獨一無二的屬性
- Restore(attribute)-將此屬性的字段設置為相同類型的給定屬性的字段
- Paste(attribute, relocation_table) – 將給定屬性的字段設置為等於該屬性的字段值;如果該屬性引用了數據框架的某些對象並且 relocation_table 具有此元素,則給定的屬性也必須引用此對象。
- NewEmpty()-返回此類的新屬性,其中的字段都為空。
- Dump(stream)-將有關給定屬性的信息輸出到給定的流(通常僅輸出字符串類型的屬性)
方法NewEmpty, Restore, Paste
被用在事務機制中(撤銷、重做命令)。如果你不想讓該屬性具備撤銷、重做功能,您只能編寫這些方法的存根,否則每次更改屬性字段時都必須調用 TDF_Attribute 類的 Backup 方法。
確保新屬性能夠保存/恢復到xml格式,需要做以下內容:
- 使用Xml[package name]作為名字創建一個新的包(例如XmlMyAttributePackage),該包包含
XmlMyAttributePackage_MyAttributeDriver
類。這個類繼承自XmlMDF_ADriver
類並且包含轉義功能:從短暫到持久,反之亦然。例如,見包XmlMDataStd中標准屬性的實現。添加包方法 AddDrivers 它將添加您的類到驅動程序表(見下文)。 - 創建一個新的包(或者也可在當前包下),並實現兩個包方法:
Factory
,加載文檔存儲和檢索驅動程序;AttributeDrivers
,通過調用AddDrivers
來負責文檔的持久化。
- 創建作為可執行文件實現的插件(例如:
XmlPlugin
)。它使用您實現 Factory 方法的包名稱調用PLUGIN
宏。
確保新屬性能夠保存/恢復到二進制格式,需要做以下內容:
- 使用Bin[package name]作為名字創建一個新的包(BinMyAttributePackage),該包包含
BinMyAttributePackage_MyAttributeDriver
類。這個類繼承自BinMDF_ADriver
類並且包含轉義功能:從短暫到持久,反之亦然。例如,見包BinMDataStd中標准屬性的實現。添加包方法 AddDrivers 它將添加您的類到驅動程序表(見下文)。 - 創建一個新的包(或者也可在當前包下),並實現兩個包方法:
Factory
,加載文檔存儲和檢索驅動程序;AttributeDrivers
,通過調用AddDrivers
來負責文檔的持久化。
- 創建作為可執行文件實現的插件(例如:
BinPlugin
)。它使用您實現 Factory 方法的包名稱調用PLUGIN
宏。文檔Saving the document
和文檔Opening the document from file
中存在關於文檔保存和打開機制。
如果你決定使用第二種方式(即通過將標准的屬性進行組合的方式),那么你需要做如下事情:
- 給
TDataStd_UAttribute
設置一個獨一無二的GUID,並將其附着到一個label中。該屬性定義了數據類型的語義。 - 創建子label並通過子label處的標准屬性分配所有必要的數據。
- 定義一個接口類,用於訪問子label的數據。
通過這種方式創建的屬性類型允許“忘記”創建的持久類。標准的持久類將被替換。此外,該方法允許分隔數據和獲取數據的方法。在對應用程序性能要求不是很高的所有情況下,它都可以用於快速開發。
讓我們學習一下兩種方式創建相同的數據類型,即gp_Trsf
類。類gp_Trsf
根據gp_TrsfForm
和一些具有特殊含義的類型(兩個點或者一個向量,一個軸或者一個旋轉角度等等)定義的旋轉方法。
- 方法1:創建一個新的屬性,在示例中通過創建一個新的屬性來實現
gp_Trsf
。 - 方法2:通過標准屬性結合創建一個新的數據類型。因為是變換類型因此他的數據框架理應包含多種標准屬性。例如,兩個點可以定義平移。因此,數據樹應當包含下面元素:
- 一個整型代表變換類型
- 一個
TDataStd_RealArray
代表第一個點位置 - 一個
TDataStd_RealArray
代表第二個點的位置
如果當前的變換為旋轉類型,那么它的數據樹應當是:
- 一個整型代表旋轉類型
- 用一個
TDataStd_RealArray
代表旋轉軸點 - 用另一個
TDataStd_RealArray
代表旋轉軸 - 用
TDataStd_Real
代表旋轉度數
TDataStd_UAttribute
屬性帶有一個獨一無二的GUID。帶有此屬性的label的接口類初始化可以獲取容器里面的數據。
2.6 復合文檔
由於數據標識是持久的,一個文檔可以引用另一個文檔的數據,引用與被引用的文檔被分隔為兩個文件。
重新看回咖啡機。其中咖啡壺可以放在一個文檔中。那么咖啡機文檔僅僅需要包含一個咖啡壺文檔的定位副本。此事件由 XLink 屬性(外部鏈接)定義,該屬性引用了其他文檔的咖啡壺(XLink 包含咖啡壺文檔的相對路徑和咖啡壺數據 [0:1] 的條目)。
在這個上下文下,咖啡機設計應用程序的終端用戶可以打開甚至修改咖啡壺的文檔。例如,修改了咖啡壺的蓄水池,但是,不用擔心會影響到咖啡機文檔。為了解決這種問題,OCAF提供了一種服務,該服務允許應用程序檢測文檔是否更新。這個服務基於每個文檔的修改計數器:當一個外部 link 被創建,被引用文檔的計數器就被關聯到引用文檔的計數器。被引用文檔每次修改就會更改計數器的值,我們可以根據當前 link 下計數器的值,同被引用文檔計數器的值進行比較來判斷文檔是否需要更新。
2.7 事務機制
數據框架提供了數據的事務機制,該機制的靈感來源於數據庫管理系統;數據在交換中被修改,如果修改被驗證則通過 Commit 終止,如果修改被放棄則通過 Abort 終止——然后數據恢復到交換之前的狀態。這種機制在以下情況下十分有用:
- 安全編輯操作(如果產生了錯誤,該交換將被廢棄並且結構保持其完整性)
- 實現取消操作將會非常簡單(當終端用戶開始一個命令時,應用程序將在數據架構啟動交換和操作;放棄該命令將會造成交換 Abort)
- 執行撤銷操作(在提交時,修改將被記錄,為了重現數據到它們先前的狀態)
事務機制方便的管理了屬性的備份。在交換期間,屬性在第一次修改之前被復制。當交換成功后,那么備份將被刪除。如果交換被放棄。那么當前屬性將恢復為拷貝。
事務是以文檔為中心的,即應用程序在一個文檔上啟動一個事務。因此,修改一個被引用文檔和更新引用文檔需要兩個事務,即使這兩個操作是在同一個工作會話中完成的。
標准文檔服務
3.1 綜述
標准文檔提供了簡單易用的以TDF為基礎數據框架的文檔開發方式。每個文檔包含一個框架。
標准文檔自身被包含在TDocStd_Application
類(或其子類)的實例中。應用程序來管理創建、存儲和檢索文檔。
你可以在文檔下實現撤銷重做,在一個文檔的數據框架下引用另一個文檔的數據框架。若要實現這個功能可以使用外部link屬性,這個屬性存儲了外部link的路徑。
總的來說,標准文檔提供了獲取數據框架的功能,同時允許你去:
- 更新外部link ;
- 管理保存和打開數據 ;
- 管理撤銷/重做功能 。
3.2 應用程序(Application)
應用程序作為數據框架的容器,你需要先創建一個文檔,並且你的文檔必須被包含在你的應用程序上。這個應用程序必須是或者繼承自TDocStd_Application
。
3.2.1 創建應用程序
創建一個應用程序,使用下面語法:
Handle(TDocStd_Application) app = new TDocStd_Application ();
3.2.2 新建一個文檔
上例已經創建了一個應用程序,可以通過TDocStd_Application::NewDocument
來創建一個新的文檔。
Handle(TDocStd_Document) doc;
app->NewDocument("NewDocumentFormat", doc);
這里的NewDocumentFormat
用來辨別你文檔的格式。OCCT定義了多種標准格式,通過OCAF屬性和文檔編碼格式來區分(例如,二進制和xml)來區分。如果你定義了特殊的OCAF屬性,那么你需要為其定義你自己格式。
3.2.3 獲取文檔的應用程序
要檢索包含您的文檔的應用程序,使用下面語法:
app = Handle(TDocStd_Application)::DownCast (doc->Application());
3.3 文檔
文檔包含數據框架,並且允許你檢索數據框架,恢復主label,保存文檔,打開或者關閉文檔。
3.3.1 獲取框架(framework)下的主label
你可以使用TDocStd_Document::Main
,獲取整個數據框架的主label,如同下面的例子。主label是根label的第一個子節點,他的索引序列為0:1;
TDF_Label label = doc->Main();
3.3.2 使用label檢索文檔
你可以使用TDocStd_Document::Get
,在數據框架下使用label來檢索文檔。
doc = TDocStd_Document::Get(label);
3.3.3 定義存儲格式
OCAF使用可自定義機制來存儲文檔。為了使OCAF的文檔可以保存和重讀,你需要在你的引用程序中定義一個或多個格式。
我們可以使用TDocStd_Application::DefineFormat()
來達到這一目的:
app->DefineFormat ("NewDocumentFormat", "New format for OCAF documents", "ndf",
new NewDocumentFormat_RetrievalDriver(),
new NewDocumentFormat_StorageDriver()
)
這個例子定義了NewDocumentFormat
這種格式,這種格式是使用ndf
結尾,並且初始化了文檔閱讀的驅動和文檔存儲的功能。當然兩個驅動都可以為空,若是這種情況的話,那么很多操作將不被支持。
OCAF提供了很多標准格式,每種都包含了一系列OCAF屬性:
格式 | 持久化工具 | OCAF屬性 |
---|---|---|
傳統格式(只讀) | ||
OCC-StdLite | TKStdL | TKLCAF |
MDTV-Standard | TKStd | TKLCAF + TKCAF |
二進制 | ||
BinLOcaf | TKBinL | TKLCAF |
BinOcaf | TKBin | TKLCAF + TKCAF |
BinXCAF | TKBinXCAF | TKLCAF + TKCAF + TKXCAF |
TObjBin | TKBinTObj | TKLCAF + TKTObj |
XML格式 | ||
XmlLOcaf | TKXmlL | TKLCAF |
XmlOcaf | TKXml | TKLCAF + TKCAF |
XmlXCAF | TKXmlXCAF | TKLCAF + TKCAF + TKXCAF |
TObjXml | TKXmlTObj | TKLCAF + TKTObj |
為了方便,這些工具提供了靜態方法DefineFormat()
來接收應用程序句柄。這種方法可以使定義數據格式變得輕松,例如:
BinDrivers::DefineFormat (app); // define format "BinOcaf"
使用這些工具包作為實現自定義屬性或新持久性格式的持久性驅動程序的示例。
應用程序可以定義多種存儲格式。當保存時,文檔的指定格式(TDocStd←- _Document::StorageFormat())
)將被使用(若未定義將保存失敗)。當讀文件時,將辨別文檔類型並恢復文檔。
3.3.4 通過資源文件定義存儲格式
另外一種定義文檔格式的方式就是通過使用資源文件。這個方法在OCCT的早期版本中使用,並且已經在7.1版本中棄用了。
3.3.5 保存文檔
為了存儲文檔,確保其參數 StorageFormat() 對應於應用程序中定義的格式之一,並使用TDocStd_Application::SaveAs
方法來實現:
app->SaveAs(doc, "/tmp/example.caf");
3.3.6 通過文件打開文檔
從先前存儲的文件打開一個文檔,你可以如同下面的例子一樣使用TDocStd_Application::Open
來打開。兩個參數分別時文件的路徑和被打開的文檔。
app->Open("/tmp/example.caf", doc);
3.3.7 在文檔中剪切、復制和粘貼
在文檔中實現剪切、復制和粘貼,使用類TDF_CopyLabel
。
事實上,你必須定義一個label,這個label用來存放剪切或者粘貼的中間值。同時你必須定義兩個其他的label:
- 數據容器(
Lab_source
) - 拷貝的目的地(
Lab_source
)
Copy = copy (Lab_Source => Lab_Clipboard)
Cut = copy + Lab_Source.ForgetAll() // command clear the contents of LabelSource.
Paste = copy (Lab_Clipboard => Lab_target)
因此我們需要一個工具去拷貝所有(或者部分)label或者子label的內容,去另一個地方(就是我們要粘貼到的地方)粘貼label。
TDF_CopyLabel aCopy;
TDF_IDFilter aFilter (Standard_False); //Don’t copy TDataStd_TreeNode attribute
aFilter.Ignore(TDataStd_TreeNode::GetDefaultTreeID());
aCopy.Load(aSource, aTarget);
aCopy.UseFilter(aFilter);
aCopy.Perform(); // copy the data structure to clipboard return
aCopy.IsDone();
過濾器(aFilter)用於禁止復制指定類型的屬性。
你也可以看類TDF_Closure
,這個類可以對於確定要從文檔中剪切的部分具有的依賴關系很有用。
3.4 外部連接
外部link使一個文檔依賴引用另一個文檔。允許你延遲更新你的依賴的數據框架。
注意:文檔可以被以更新或者不更新的方式被拷貝。
3.4.1 復制文檔
可以延遲更新
復制一個延遲更新的文檔,你可以使用TDocStd_XLinkTool::CopyWithLink
。
Handle(TDocStd_Document) doc1;
Handle(TDocStd_Document) doc2;
TDF_Label source = doc1->GetData()->Root();
TDF_Label target = doc2->GetData()->Root();
TDocStd_XLinkTool XLinkTool;
XLinkTool.CopyWithLink(target,source);
現在target文檔存在了一份source文檔的拷貝。拷貝文檔也有一個link為了更新拷貝內容,前提是原文件發生了改變。
在下面的例子里,如果source文件有些地方發生了改變。結果是,若需要同步更新target文檔,那么你需要調用TDocStd_XLinkTool::UpdateLink
並將target文檔做參數傳入。
XLinkTool.UpdateLink(target);
不使用link復制文檔
你可以使用TDocStd_XLinkTool::Copy
,來達到不使用link創建文檔的拷貝體到另一個文檔。target用來表示目標文檔,source用來表示源文檔。
XLinkTool.Copy(target, source);
4. OCAF造型(Shape)屬性(Attribute)
4.1 綜述
一個拓撲屬性可以被看做拓撲架構上的鈎子。可以附加數據以定義對它的引用。
OCAF造型屬性被廣泛用在拓撲對象和他們的演化。所有的拓撲對象存儲在數據框架下根label的TNaming_UsedShapes
屬性下。這個屬性包含了一個map,該map包含了文檔使用的所有拓撲造型。
用戶可以添加TNaming_NamedShape
屬性在其他label上。這個屬性包含來自TNaming_UsedShapes
屬性的造型和造型的演化的引用(hook)。TNaming_NamedShape
屬性包含一系列的鈎子對:舊的造型和新的造型(注意看下面的圖片)。它不僅允許通過label獲取拓撲造型,而且跟蹤了造型演變的過程,並且通過更改的形狀正確更新相關形狀。
如果,造型是新創建的,那么相應命名形狀的舊造型是一個空造型。如果造型被刪除,那么該命名下的造型為空。
4.2 數據框架(data framework)中的體屬性(shape attribute)
不同算法對於處置label上最終造型的子造型取決於它的必要性:
- 如果一個子造型一定有一些額外的屬性(每個面的材質或者每個邊的顏色)。在這個情況下,一個特定的子造型被放置到一個單獨的label下(通常是結果造型label的子label),具有該子造型的所有屬性。
- 如果需要拓撲命名算法,則將必要且充分的子造型集放置到結果造型label的子label中。對於基本實體和封閉殼,形狀的所有面都被放置到子label下。
TNaming_NamedShape
包含一系列相同演化的鈎子對。在這種情況下,拓撲造型屬於新造型的復合造型。
考慮到下面的例子。兩個方塊(實體)融合成一個實體。最初每個方塊都作為造型附着在label下,並且該造型具有進化 PRIMITIVE 並引用 TNaming_UsedShapes 映射的相應造型。方塊標簽有他的材質屬性和六個子label,子label包含了方塊的六個面。
在使用融合操作后的結果實體放置在一個單獨的label,將引用舊的一個造型(其中一個方塊)和融合產生的新的造型,並具有MODIFY的演化。
包含修改面信息的命名造型,屬於融合結果子label:
- 標號1的子標簽-方塊1修改的面
- 標號2的子標簽-方塊2修改的面
對於正確的命名機制功能來說以下的信息是必要的:每個結果的子造型可以通過label和名字類型來加以區分:
- 面F1'被修改為面F11
- 面F1''生成為面F12
- 邊緣作為兩個連續面的交集
- 頂點作為三個連續面的交集
在結束對源方塊的修改后,應用程序必須自動的重建命名實體:重新計算方塊(實體和面)的命名造型,並且融合新造型。
4.3 注冊和升級體(shape)
當使用TNaming_NamedShape
去創建屬性時,下面的幾個方面的內容需要被實現:
- 用來存儲新和舊造型的列表。這個的意義在於進化的類型。
- 進化的類型,它是 TNaming_Evolution 枚舉的一個術語,用於放置到單獨標簽的選定形狀:
- PRIMITIVE - 新創建的拓撲造型-沒有先前的造型
- GENERATED - 是由低級圖片通過演化生成的
- MODIFED - 新造型是由老造型修改而來
- DELETE - 新的造型是空的;這個造型僅僅代表着由原先造型已被刪除
- SELECTED - 具有這種演變的命名造型對拓撲的歷史沒有影響。
4.4 使用naming
資源
使用TNaming_Builder
類來創建一個命名造型屬性。它有一個未來屬性的標簽作為構造函數的參數。不同的方法用於造型對的演化和設置。如果為同一個 TNaming_Builder 對象提供了許多具有相同演變的造型對,那么這些對將被放置在生成的命名造型中。創建 TNaming_Builder 類的新對象后,在給定標簽處創建一個空的命名造型。
// 在label上創建一個空的命名造型
TNaming_Builder builder(label);
// 設置一對帶有進化生成的造型
builder.Generated(oldshape1,newshape1);
// 設置另外一對具有相同演化的造型
builder.Generated(oldshape2,newshape2);
//獲取結果 - TNaming_NamedShape屬性
Handle(TNaming_NamedShape) ns = builder.NamedShape();
4.5 讀被命名(named)的造型(shape)屬性內容
你可以使用TNaming_NamedShape::Evolution()
方法來獲取命名造型的演化,使用TNaming_NamedShape::Get()
來獲取所有造型對的新造型。
更多關於命名造型的概念或者關於拓撲修改歷史可以通過以下方式獲取:
TNaming_Tool
提供了公共的高級別獲取造型內容的功能:- 方法
GetShape(Handle(TNaming_NamedShape))
根據給定的命名造型獲取一系列新的造型。 - 方法
CurrentShape(Handle(TNaming_NamedShape))
根據給定的造型獲取最后一個版本的一系列造型。 - 方法
NamedShape(TopoDS_Shape,TDF_Label)
返回一個命名造型,其中包含給定形狀作為新形狀。 給定的標簽是來自數據框架的任何標簽——它只是提供對它的訪問。
- 方法
TNaming_Iterator
用來獲取命名造型和鈎子對。
// 創建一個命名造型的迭代器
TNaming_Iterator iter(namedshape);
// iterate while some pairs are not iterated
while(iter.More()) {
// 根據當前對獲取新造型
TopoDS_Shape newshape = iter.NewShape();
// 獲取當前對的老造型
TopoDS_Shape oldshape = iter.OldShape();
// do something...
//迭代到下一對
iter.Next();
}
4.6 拓撲naming
拓撲命名機制基於三個方面:
- 建模操作算法的歷史;
- 注冊數據框架的結果(例如:在OCAF文檔中,加載“抽出”歷史的必要元素);
- 選中/重算被選中的子造型的算法結果。
去獲取三個組件的抽取結果應當同步和遵守每個組成部分的規則。
4.6.1 算法歷史
命名機制的基礎是基於正確的建模歷史。需要算法支持的操作提供。歷史內容取決於拓撲結果的類型。歷史算法的目的是提供所有系列實體和選擇重算機制的正確工作。下表根據結果類型顯示了預期的實體類型。
結果類型 | 歷史算法所返回的子造型 | 注釋 |
---|---|---|
實體或封閉的殼 | 面 | 所有的面 |
開放實體或單面 | 開放邊界的面和邊 | 開放邊界的所有的面和所有的邊 |
閉合線 | 邊 | 所有邊 |
開放線 | 邊和頂點 | 所有邊加上所有邊界上的頂點 |
邊 | 頂點 | 兩個頂點 |
混合和混合實體 | 上述聲明的規則適用於第一級的所有子形狀 | 混合和混合實體一層一層的進行探索直到滿足要求 |
歷史算法應當返回子造型的初級類型,例如,面,邊和頂點,而其他所謂的聚合類型:組合、多個殼、多個線段通過計算機制自動計算。
對於幾種情況,有一些簡單的例外。例如,如果結果包含一些相同的邊-在圓錐、圓柱形或球面-這些相同的邊應當被歷史算法所追蹤並且附加的內容需要被定義。所有退化的實體都應該被過濾掉並排除在考慮范圍之外。
4.6.2 在數據框架下加載歷史
使用的算法根據上述規則返回的所有元素都應在所謂的結果標簽下以線性順序放入數據框架工作(或換句話說,OCAF 文檔)中。
“結果 Label”是TDF_label,用於在NamedShape屬性中保存來自TopoDS的算法結果Shape。在數據架構中加載結果的子造型時,應當注冊shape及其演變的規則。 這些規則也適用於加載主要造型,即由建模算法產生的結果造型。
4.6.3 選擇和重算機制
當數據框架充滿所有受影響的造型時(包括當前建模操作結果的數據結構和當前建模所依賴的前一次建模操作的數據結構)當前結果造型的任意的子造型都可以被選中,例如,支持對應的方法的新的命名數據結構,可以被生成和保持在數據框架中。
TNaming_Selector
類是拓撲命名的用戶接口之一。它實現了上面所提到的子造型被選擇功能作為附加功能。例如,它可以被用來:
- 在label存儲被選擇的造型
- 獲取命名造型-檢查造型的保留值
- 更新命名-根據先前被選擇的造型來重新計算
選擇器將一個帶有進化“SELECT”的命名造型放置到給定的label上。選擇器創建一個“名字”到指定的造型上,該“名字”是如何找到所選拓撲用作資源的唯一描述(數據結構):
- 所給定的上下文造型,例如,結果label包括所有的子造型的主要造型。
- 它的演變
- 命名結構
在修改上下文造型后並且更新相應的命名結構體,都需要調用TNaming_Selector::Solve
方法。如果命名結構,例如,上面提到的名字,選擇器在相應的命名造型中自動的更新被選擇的子造型。
4.7 探索體(shape)升級
類TNaming_Tool
提供工具去讀當前屬性包含的數據。
如果你需要為已存在的數據去創造一個拓撲屬性,使用NamedShape
方法。
class MyPkg_MyClass {
public: Standard_Boolean SameEdge (const Handle(CafTest_Line)& L1, const Handle(CafTest_Line)& L2);
};
Standard_Boolean CafTest_MyClass::SameEdge (const Handle(CafTest_Line)& L1, const Handle(CafTest_Line)& L2)
{
Handle(TNaming_NamedShape) NS1 = L1->NamedShape();
Handle(TNaming_NamedShape) NS2 = L2->NamedShape();
return BRepTools::Compare(NS1,NS2);
}
4.8 拓撲naming
使用示例
拓撲命名是Open CasCade的一種機制目的是為了保持被選擇實體的引用。例如,如果,我們選擇了一個實體的頂點並且請求拓撲命名保持頂點的引用,無論造型發生什么變化(平移、縮放、與其他的造型融合)都會引用那個頂點。
讓我們思考一個例子:想象一個木板。我們的工作就是向木板釘入幾個釘子:
可能存在不同大小和不同方位的釘子。錘子應該將每個釘子准確地推入頂面的中心點。為此,用戶執行以下操作:
- 用戶需要制作不同規格的釘子,
- 為錘子選擇每個釘子的頂面。
上面的工作准備就緒。接下來需要做-錘子需要計算每個釘子被選擇面的中心點和敲擊每個釘子使其進入到木板內。
若用戶改變了一些釘子的位置將會發生什么?錘子如何知道這個信息?它保持了每個釘子的面的引用。不過,如果一個釘子改變了位置,那么錘子應當知道被選面的位置。否則,錘子會敲擊舊的地方。
拓撲命名機制作為上述例子的“黑匣子”。現在,是時候讓這個盒子變的透明。
這個應用程序包括三個功能:
- 釘子-生產一個釘子造型,
- 移動器-沿着木板移動造型,
- 錘子-驅使釘子進入木板。
每個功能給了拓撲命名一些關於如何處理被選擇的子造型提示:
- 釘子構成一個實體造型,並將造型的每個面添加到label下:
- 移動器移動一個造型並且注冊修改到每個面。它將給被移動的釘子每個子label壓入一對值:“舊”造型-“新”造型。“舊”造型代表着當前釘子面的初始化位置。“新”造型是相同的面,但是擁有不同的位置:
它如何工作?
- 錘子使用
TNaming_Selector::Select()
選擇了釘子的一個平面。此調用為所選造型生成唯一名稱。在我們的例子中,它將直接引用釘子頂面的label(面1)。 - 當用戶沿着木板移動釘子時,移動器通過壓入一對數據:舊造型-新造型到子label下注冊這個變化。
- 當釘子調用
TNaming::Solve()
,拓撲命名將“觀察”被選造型的命名並嘗試去解決它:- 它在數據樹中找到所選造型的第 1 個外觀——它是釘子的Face 1下的一個label。
- 它遵循這個面的演化。在我們的例子中,只有一種演化-移動:面1(頂面)-面1'(改變位置后的頂面)。所以,最后的演變是重新定位的頂面。
- 調用
TNaming_Selector::NamedShape()
方法,錘子獲取被選被選面的最后一個演化-就是改變位置后的頂面。
工作結束。
P.S.讓我們說一下比較復雜的案例-選擇頂面的一個線。它的拓撲名稱是兩個面的“交線”。我們記住釘子僅僅將面加入到label下。因此,被選的線將代表頂面和另外一個在釘子頭部的面的“交線”。另外一個例子就是頂點。它獨一無二的名字可能代表着三個或多個的“交點”。
5. 標准屬性(Standard Attribute)
5.1 綜述
標准屬性是即用的屬性