OCCT_OCAF翻译
简述
这个手册用来教授如何使用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 综述
标准属性是即用的属性