OpenCasCade官方开发文档翻译(12)–OCAF

介绍

本手册解释了如何使用 Open CASCADE Application Framework (OCAF)。它提供了有关使用 OCAF 的基本文档。

OCAF 的宗旨

OCAF(开放 CASCADE 应用程序框架)是一个易于使用的平台,用于快速开发复杂的特定领域设计应用程序。使用 OCAF 开发的典型应用程序处理特定于贸易的计算机辅助设计 (CAD) 系统、制造或分析应用程序、模拟应用程序或插图工具中的二维或三维(2D 或 3D)几何建模。

开发设计应用程序需要解决许多技术方面的问题。特别是,鉴于您的应用程序的功能规范,您必须至少:

  • 设计应用程序的架构——定义软件组件及其协作方式;
  • 定义能够支持所需功能的数据模型——设计应用程序对在整个最终用户工作会话期间维护的数据进行操作;
  • 构建软件以便:
    • 将显示与数据同步——修改对象的命令必须更新视图;
    • 支持通用的撤消重做命令——这个特性必须在设计过程的早期就被考虑在内;
  • 实现数据保存功能——如果应用程序的生命周期较长,则必须解决应用程序版本之间数据的兼容性问题;
  • 构建应用程序用户界面。

OCAF 提供的架构指导和即用型解决方案为您提供以下好处:

  • 您可以专注于特定于您的应用程序的功能;
  • 已经提供了支持应用程序所需的底层机制;
  • 由于耦合了其他 Open CASCADE 技术模块,应用程序可以快速原型化;
  • 最终的应用程序可以通过原型工业化来开发——您不需要从头开始重新开发。
  • 该平台的开源性质保证了您的开发的长期有用性。

OCAF 不仅仅是 CAS.CADE 对象库中众多工具包中的一个工具包。由于它可以处理这些库中的任何数据和算法——无论是建模算法、拓扑还是几何——OCAF 是它们的逻辑补充。

下表对比了单独使用对象库和使用 OCAF 的建模应用程序的设计。

表 1:OCAF 提供的服务

开发任务 注释 没有 OCAF 与 OCAF
创建几何 算法调用建模库 由用户创建 由用户创建
数据组织 包括具体的属性和建模过程 由用户创建 简化
将数据保存在文件中 文件的概念 由用户创建 假如
文档视图管理 由用户创建 假如
应用基础设施 新建、打开、关闭、保存和另存为文件菜单 由用户创建 假如
撤销重做 稳健、多层次 由用户创建 假如
特定于应用程序的对话框 由用户创建 由用户创建

OCAF 使用 Open CASCADE 技术的其他模块——Shape 是使用Modeling Data模块支持的几何图形实现的,查看器是Visualization模块提供的。建模功能可以使用建模算法模块来实现。

OCAF 和开放级联技术 ( OCCT ) 对象库之间的关系如下图所示。

图片[1]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

OCCT架构

在图像中,OCAF(开放 CASCADE 应用程序框架)以黑色矩形显示,OCAF 所需的 OCCT 对象库以白色矩形显示。

本文档的后续章节将解释概念并展示如何使用 OCAF 的服务。

架构概述

OCAF 为您提供了一个由 C++ 类库组成的面向对象的 Application-Document-Attribute 模型。

图片[2]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

应用-文档-属性模型

应用

Application是一个抽象类,负责在工作会话期间处理文件,即:

  • 创建新文件;
  • 保存文件并打开它们;
  • 初始化文档视图。

文档 

由具体类Document实现的文档是应用程序数据的容器。文档提供对数据框架的访问并用于以下目的:

  • 管理更改通知
  • 更新外部链接
  • 管理数据的保存和恢复
  • 存储软件扩展名。
  • 管理命令事务
  • 管理撤消和重做选项。

每个文档都保存在由其格式和扩展名定义的单个平面 ASCII 文件中(OCAF 提供了一种即用型格式)。

除了作为应用程序数据的容器之外,文档还可以相互引用;例如,文档 A 可以引用文档 B 中的特定标签。此功能是通过引用键实现的。

属性

应用程序数据由Attributes描述,它是从Attribute抽象类派生的类的实例,根据 OCAF 数据框架组织。

OCAF数据框架使用单个层次结构中的持久标识符引用属性聚合。OCAF 具有广泛的属性,包括:

  • 标准属性允许对数据框架中的简单通用数据(例如:整数、实数、字符串、数组等数据)进行操作,实现辅助功能(例如:标签计数器的子项的标签源属性),创建依赖项(例如:参考,树节点)….;
  • 形状属性包含整个模型或其元素的几何形状,包括对形状的引用和形状演变的跟踪;
  • 其他几何属性,例如基准(点、轴和平面)和约束切线、给定距离、给定角度、同心等)
  • 用户属性,即应用程序键入的属性
  • 可视化属性允许将查看器信息放置到数据框架、对象的视觉表示和其他辅助视觉信息,这是图形数据表示所需的。
  • 功能服务——这些属性的目的是在对象被修改后重建对象(模型的参数化)。在文档管理更改通知的同时,函数管理这些更改的传播。函数机制提供函数之间的链接以及对各种算法的调用。

此外,可以通过定义新的属性类来添加特定于应用程序的数据;自然,这会改变标准文件格式。唯一需要实现的功能是:

  • 复制属性
  • 将其转换为持久数据存储

参考键模型

在大多数现有的几何建模系统中,数据是拓扑驱动的。它们通常使用边界表示 (BRep),其中几何模型由面、边和顶点的集合定义,应用程序数据附加到这些集合上。数据示例包括:

  • 一种颜色;
  • 一种材料;
  • 特定边缘被混合的信息。

当几何模型被参数化时,也就是说,当您可以更改用于构建模型的参数值(混合半径、肋的厚度等)时,几何形状很容易发生变化。为了保持应用程序数据的附加,必须将几何图形与其他数据区分开来。

在 OCAF 中,数据是引用键驱动的。它是一个统一的模型,其中引用键是数据的持久标识。所有可访问的数据,包括几何图形,都作为附加到引用键的属性来实现。几何成为 Shape 属性的值,就像数字是 Integer 和 Real 属性的值以及字符串是 Name 属性的值一样。

在单个引用键上,可以聚合许多属性;应用程序可以在运行时询问哪些属性可用。例如,要将纹理与几何模型中的面关联,则面和纹理都附加到相同的引用键。

图片[3]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

拓扑驱动与参考键驱动方法

可以通过两种方式创建引用键:

  • 在编程时,由应用程序
  • 在运行时,由应用程序的最终用户(假设您在应用程序中包含此功能)

作为应用程序开发人员,您生成引用键以便为数据提供语义。例如,构建棱镜的函数可能会创建三个参考键:一个用于棱镜的底部,第二个用于侧面,第三个用于顶面。这构成了应用程序棱镜功能中内置的语义。另一方面,在允许最终用户为他/她选择的面设置​​纹理的命令中,如果之前没有在任何特征中引用过,则必须为所选面创建一个引用键(如在棱镜侧面之一的情况)。

当您为选定的拓扑元素(面、边或顶点)创建引用键时,OCAF 会附加到定义选定拓扑的引用键信息 – 命名属性。例如,它可能是选定边所共有的面。命名算法使用此信息以及有关每个建模步骤中拓扑演变的信息(修改、更新和删除的面)来维护附加到引用键的拓扑。因此,在参数化模型上,在修改参数值之后,即使它们的几何形状发生了变化,参考键仍会寻址适当的面。因此,您更改了上图中所示立方体的大小,用户纹理仍然附着在右面。

注意由于拓扑命名基于引用键和属性,例如命名(选择信息)和形状(拓扑演化信息),因此 OCAF 不与底层建模库耦合。OCAF 所需的唯一建模服务如下:

  • 每个算法都必须提供有关拓扑演化的信息(算法修改、更新和删除的面列表)
  • 必须可以探索几何模型(3D 模型由以紧密线为界的面组成,它们本身由通过其顶点连接的一系列边组成)

目前,OCAF 使用 Open CASCADE Technology 建模库。

为了设计基于 OCAF 的数据模型,鼓励应用程序开发人员聚合现成的属性,而不是通过从抽象根类继承来定义新属性。
使用聚合而不是继承有两个主要优点:

  • 由于您不通过定义新类来实现数据,因此 OCAF 提供的已保存数据的格式不会改变;所以你不必编写保存和打开函数
  • 如果特定属性可用,应用程序可以在运行时查询数据

概括

  • OCAF 基于统一的参考密钥模型,其中:
    • 引用键提供数据的持久标识;
    • 包括几何在内的数据被实现为附加到引用键的属性;
    • 拓扑命名保持选定的几何附加到参数化模型中的参考键;
  • 在很多应用中,OCAF提供的数据格式不需要扩展;
  • OCAF 不与底层建模库耦合。

数据框架

数据结构

OCAF 数据框架是树结构中引用键模型的 Open CASCADE 技术实现。它提供了一个单一的环境,可以处理来自不同应用程序组件的数据。这允许以最大程度的信息和稳定的语义简单、一致地交换和修改数据。

这种方法的构建块是:

  • 标签
  • 标签
  • 属性

如前所述,框架中的第一个标签是树的根标签。每个标签都有一个表示为整数值的标签,标签由一个条目唯一定义,该条目表示为来自根的标签列表,例如 0:1:2:1。

每个标签都可以有一个属性列表,其中包含数据,并且可以将多个属性附加到一个标签上。每个属性都由一个 GUID 标识,尽管一个标签可能附有多个属性,但它不能有一个 GUID 的多个属性。

标签的子标签称为其子标签。相反,每个不是根的标签都有一个父亲。Brother 标签不能共享同一个标签。

最重要的属性是标签的条目是它在数据框架中的持久地址。

图片[4]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

一个简单的框架模型

在此图像中,圆圈包含相应标签的标签。标签列表位于圆圈下方。根标签始终具有零标签。

根标签的子标签是带有标签 1 和 3 的中级标签。这些标签是兄弟。

右下标签的标签列表是“0:3:4”:这个标签有标签4,它的父亲(带有条目“0:3”)有标签3,父亲的父亲有标签0(根标签总是有“0”条目)。

数据结构示例

让我们看一下这个例子:

图片[5]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

咖啡机

在图像中,用于设计咖啡机的应用程序首先为机器单元分配一个标签。然后,它为主要特征(玻璃咖啡壶、水容器和过滤器)添加子标签,并根据需要对其进行细化(咖啡壶的把手和储水器以及储水器的喷口)。

您现在附上描述手柄的技术数据——它的几何形状和颜色——以及水库——它的几何形状和材料。稍后,您可以在不更改其颜色的情况下修改手柄的几何形状——两者都保持连接到同一个标签。

图片[6]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

咖啡机的数据结构

标签的嵌套是 OCAF 的关键。这允许标签拥有自己的结构及其本地寻址方案,可以在更复杂的结构中重复使用。以咖啡机为例。鉴于咖啡壶的把手具有标签 [1],仅在咖啡壶(不包括机器单元)上下文中的把手条目是 [0:1:1]。如果您现在为带有两个咖啡壶的咖啡机建模,一个在标签 [1] 处,第二个在机器单元中的标签 [4] 处,第一个壶的手柄将具有条目 [0:1:1: 1] 而第二个锅的把手是 [0:1:4:1]。这样,我们就避免了咖啡壶把手之间的任何混淆。

另一个例子是设计台灯的应用程序。第一个标签分配给灯单元。

图片[7]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

根标签不能有兄弟标签。因此,框架分配中的各种灯对应于根标签的子标签。这可以避免数据框架中台灯之间的任何混淆。不同的灯部件具有不同的材质、颜色等属性,因此为灯的每个子单元分配一个具有指定标签的灯的子标签:

  • 带有标签 1 的灯罩标签
  • 带有标签 2 的灯泡标签
  • 带有标签 3 的词干标签

标签标签是随意选择的。它们只是灯部件的标识符。现在您可以细化所有单位:通过将有关灯或其部件的几何形状、颜色、材料和其他信息设置为指定的标签。这些信息被放入标签的特殊属性中:纯标签不包含数据——它只是访问数据的键。

请记住,标签是私有地址,在数据框架之外没有任何意义。例如,使用零件名称作为标签是错误的。这些可能会在应用程序的下一个版本中更改或从生产中删除,而该部分的确切形式可能会在您的设计中重用,部分名称可以作为属性集成到框架中。

所以,用户更改灯具设计后,只更改了相应的属性,但标签结构保持不变。灯形状必须由新的属性值重新创建,并且灯形状的属性必须引用新形状。

图片[8]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

上图展示了 table-lamps 文档结构:根标签的每个子标签包含一个灯形状属性,并引用子标签,其中包含有关相应子单元的一些设计信息。

数据框架结构允许创建更复杂的结构:每个灯标签子标签可能有子标签,其中包含有关台灯部件及其组件的更详细信息。

请注意,根标签也可以具有属性,通常是全局属性:例如文档的名称。

标签

标签是一个整数,它以两种方式标识标签:

  • 相对识别
  • 绝对认同。

在相对标识中,标签的标签仅具有相对于父标签的含义。例如,对于特定标签,您可能有四个由标签 2、7、18、100 标识的子标签。在使用相对标识时,您可以确保您有一个安全的范围来设置属性。

在绝对标识中,标签在数据框架中的位置由冒号分隔的所有标签的标签列表明确指定,从所讨论的标签到数据框架的根。该列表称为条目。TDF_Tool::TagList允许检索特定标签的条目。

在相对和绝对识别中,重要的是要记住整数的值没有任何内在语义。换句话说,整数所暗示的自然序列,即 0、1、2、3、4 …——在这里并不重要。标签的整数值只是一个键。

可以通过两种方式创建标签:

  • 随机发货
  • 自定义投放

顾名思义,在随机交付中,标签值是由系统以随机方式生成的。在用户定义的交付中,您通过将标签作为参数传递给方法来分配它。

使用随机传送标签创建子标签

要附加并返回新的子标签,请使用TDF_TagSource::NewChild。在下面的示例中,传递给NewChild的参数level2TDF_Label

通过用户从标签交付创建子标签

从标签创建子标签的另一种方法是通过用户交付。换句话说,您指定您希望您的子标签拥有的标签。

要从您自己指定的标签中检索子标签,您需要使用TDF_Label::FindChildTDF_Label::Tag,如下例所示。这里,整数 3 表示您感兴趣的标签的标签,布尔值 false 是参数create的值。当此参数设置为false时,不会创建新的子标签。

TDF_Label achild = root.FindChild(3,Standard_False);
if (!achild.IsNull()) {
Standard_Integer tag = achild.Tag();
}

标签

标签为标签提供了一个永久地址。标签——标签的语义——是数据框架中的一个位置,其中包含数据的属性被附加。数据框架实际上是一棵以根为最终父标签的标签树。

标签无法从数据框架中删除,因此,已创建的数据框架的结构在文档打开时无法删除。因此,在应用程序处理文档时,对现有标签的任何类型的引用都是实际的。

标签创建

可以在任何标签上创建标签,与兄弟标签进行比较和检索。也可以在数据框架中找到它们的深度(根标签的深度为0,根的子标签的深度为1等),是否有子,标签的相对位置,该标签的数据框架. TDF_Label类提供上述服务。

创建子标签

要使用标签的显式传递在数据框架中创建新的子标签,请使用TDF_Label::FindChild

//在Root处创建一个标签为10的标签
TDF_Label lab1 = aDF-> Root ()。FindChild (10);
//在标签 10 上创建标签 7 和 2
TDF_Label lab2 = lab1.FindChild(7);
TDF_Label lab3 = lab1.FindChild(2);

You could also use the same syntax but add the Boolean true as a value of the argument create. This ensures that a new child label will be created if none is found. Note that in the previous syntax, this was also the case since create is true by default.

TDF_Label level1 = root.FindChild(3,Standard_True);
TDF_Label level2 = level1.FindChild(1,Standard_True);

Retrieving child labels

You can retrieve child labels of your current label by iteration on the first level in the scope of this 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)
//
}

您还可以通过在此标签范围内的所有级别上进行迭代来检索当前标签的每个后代中的所有子标签。

for (TDF_ChildIterator itall (current,Standard_True); itall.More(); itall.Next()) {
achild = itall.Value();
//
// do something on a child (all levels)
//
}

TDF_Tool::EntryTDF_ChildIterator一起使用,您也可以检索当前标签的子标签的条目。

无效DumpChildren( const TDF_Label & aLabel)
{
for ( it.Initialize (aLabel,Standard_True); it.more();it.Next ( ) ) {
cout << as.ToCString() << endl;
}
}

检索父亲标签

检索当前标签的父标签。

TDF_Label father = achild.Father();
isroot = father.IsRoot();

属性

标签本身不包含数据。任何类型的所有数据——应用程序或非应用程序——都包含在属性中。这些是附加在标签上的,不同类型的数据有不同的类型。OCAF 提供了许多现成的标准属性,例如整数、实数、约束、轴和平面。还有拓扑命名、功能和可视化的属性。每种类型的属性都由一个 GUID 标识。

OCAF 的优点是上述所有属性类型都以相同的方式处理。无论属性类型是什么,您都可以创建它们的新实例、检索它们、将它们附加到标签或从标签中删除它们,“忘记”和“记住”特定标签的属性。

从标签中检索属性

要从标签中检索属性,请使用TDF_Label::FindAttribute。在下面的示例中,整数属性的 GUID 和属性HandleINT作为参数传递给当前标签的FindAttribute

{
// 找到属性
}
else
{
// 没有找到该属性
}

使用 GUID 识别属性

您可以创建属性的新实例并检索其 GUID。在下面的示例中,创建了一个新的整数属性,并且它的 GUID 通过从TDF_Attribute继承的方法 ID传递给变量guid

Standard_GUID guid = INT->ID();

将属性附加到标签

要将属性附加到标签,请使用TDF_Label::Add。重复此语法会引发错误消息,因为已经有一个具有相同 GUID 的属性附加到当前标签。

TDF_Attribute::Label for INT然后返回INT附加到的标签附加

current.Add (INT); // INT 现在附加到当前
current.Add (INT); //导致失败
TDF_Label attach = INT->Label();

笔记。对于标准属性的某些子集,此规则有一个例外。有关详细信息,请参阅第 6 章标准属性。

测试标签的附件

您可以使用TDF_Attribute::IsA将属性的 GUID 作为参数来测试属性是否附加到标签。在下面的示例中,您测试当前标签是否具有整数属性,然后,如果是,则附加了多少属性。TDataStd_Integer::GetID提供方法 IsAttribute 所需的 GUID 参数。

TDF_Attribute::HasAttribute测试是否有附加属性,并且TDF_Tool::NbAttributes返回附加到相关标签的属性数,例如current

// Testing of attribute attachment
//
if (current.IsA(TDataStd_Integer::GetID())) {
// the label has an Integer attribute attached
}
if (current.HasAttribute()) {
// the label has at least one attribute attached
Standard_Integer nbatt = current.NbAttributes();
// the label has nbatt attributes attached
}

从标签中删除属性

要从标签中删除属性,请使用TDF_Label::Forget和已删除属性的 GUID。要删除标签的所有属性,TDF_Label::ForgetAll

current.Forget( TDataStd_Integer::GetID ());
// 整数属性现在不附加到当前标签
current.ForgetAll();
// 当前有 0 个附加属性

具体属性创建

如果实现标准数据类型的现有和准备使用的属性集不能满足特定数据表示任务的需要,则用户可以构建自己的数据类型和实现这种新数据类型的相应新特定属性。

实现新数据类型有两种方法:创建新属性(标准方法),或通过标准属性的组合使用用户属性的概念(替代方法)

为了以标准方式创建新属性,创建一个继承自TDF_Attribute的类并实现所有纯虚拟和必要的虚拟方法:

  • ID() – 返回给定属性的唯一 GUID
  • Restore(attribute) – 将此属性的字段设置为相同类型的给定属性的字段
  • Paste(attribute, relocation_table) – 将给定属性的字段设置为等于该属性的字段值;如果属性引用了数据框架的某些对象并且 relocation_table 有这个元素,那么给定的属性也必须引用这个对象。
  • NewEmpty() – 返回该类的新属性,其中包含空字段
  • Dump(stream) – 将给定属性的信息输出到给定的流调试(通常只输出字符串类型的属性)

方法NewEmpty、RestorePaste用于常见事务机制(撤消/重做命令)。如果您不需要此属性来响应撤消/重做命令,则可以只编写这些方法的存根,否则每次更改属性字段时都必须调用TDF_Attribute类的 Backup 方法。

要启用以 XML 格式保存/恢复新属性的可能性,请执行以下操作:

  1. 创建一个名为 Xml[package name] 的新包(例如XmlMyAttributePackage),其中包含类XmlMyAttributePackage_MyAttributeDriver。新类继承XmlMDF_ADriver类并包含转换功能:从瞬态到持久,反之亦然(例如,请参阅包XmlMDataStd中标准属性的实现)。添加包方法 AddDrivers 将您的类添加到驱动程序表(见下文)。
  2. 使用两种包方法创建一个新包(或在当前包中执行):
    • Factory,加载文档存储和检索驱动程序;和
    • AttributeDrivers,它为负责文档持久性的所有包调用方法 AddDrivers。
  3. 创建一个作为可执行文件实现的插件(参见示例XmlPlugin)。它使用您实现方法 Factory 的包名称调用宏 PLUGIN。

要启用以二进制格式保存/恢复新属性的可能性,请执行以下操作:

  1. 创建一个名为Bin[package name]的新包(例如BinMyAttributePackage),其中包含一个类BinMyAttributePackage_MyAttributeDriver。新类继承BinMDF_ADriver类并包含转换功能:从瞬态到持久,反之亦然(例如,请参见包BinMDataStd中标准属性的实现)。添加包方法AddDrivers,它将您的类添加到驱动程序表中。
  2. 使用两种包方法创建一个新包(或在当前包中执行):
    • 工厂,加载文档存储和检索驱动程序;和
    • AttributeDrivers,它为负责文档持久性的所有包调用方法 AddDrivers。
  3. 创建一个作为可执行文件实现的插件(参见示例BinPlugin)。它使用您实现方法 Factory 的包名称调用宏 PLUGIN。有关文档保存/打开机制的描述,请参阅保存文档从文件打开文档。

如果您决定使用替代方式(通过UAttribute和其他标准属性的组合创建新属性),请执行以下操作:

  1. 使用附加到标签的唯一 GUID设置TDataStd_UAttribute 。该属性定义数据类型的语义(标识数据类型)。
  2. 创建子标签并通过子标签的标准属性分配所有必要的数据。
  3. 定义一个接口类,用于访问子标签的数据。

选择实现新数据类型的替代方法可以忘记为新数据类型创建持久性类。将使用标准持久性类。此外,这种方式允许分离数据和访问数据的方法(接口)。在对应用程序性能要求不是很高的情况下,它都可以用于快速开发。

让我们以gp_Trsf类表示的转换为例,研究两种方式对同一数据类型的实现。gp_Trsf类根据类型 ( gp_TrsfForm ) 和特定类型变换的一组参数定义变换(用于平移的两个点或向量,用于旋转的轴和角度等)。

  1. 第一种方式:创建新属性。通过创建新属性实现的转换在Samples中表示。
  2. 第二种方式:通过标准属性组合的方式创建新的数据类型。根据转换的类型,它可以通过不同的标准属性保存在数据框架中。例如,平移由两个点定义。因此,用于翻译的数据树如下所示:
图片[9]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

转换数据树

如果变换的类型更改为旋转,数据树如下所示:

图片[10]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

旋转的数据树

具有所选唯一 GUID的属性TDataStd_UAttribute标识数据类型。由该属性的标签初始化的接口类允许访问数据容器(转换类型和根据类型转换的数据)。

复合文件

由于数据的标识是持久的,一个文档可以引用另一个文档中包含的数据,引用和引用的文档保存在两个单独的文件中。

让我们再看一下咖啡机应用程序。咖啡壶可以放在一个文件中。然后,咖啡机文档包括咖啡壶的一个事件(定位副本)。此事件由引用第一个文档的咖啡壶的 XLink 属性(外部链接)定义(XLink 包含咖啡壶文档的相对路径和咖啡壶数据的条目 [0:1])。

图片[11]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

咖啡机复合文件

在这种情况下,咖啡机应用程序的最终用户可以打开咖啡壶文档,修改例如容器的几何形状,并覆盖文档,而不用担心修改对咖啡机文档的影响。为了处理这种情况,OCAF 提供了一项服务,允许应用程序检查文档是否是最新的。该服务基于每个文档中包含的修改计数器:创建外部链接时,引用文档计数器的副本与引用文档中的 XLink 相关联。假设引用文档的每次修改都会增加自己的计数器,
通过比较两个计数器,我们可以检测到必须更新引用文档(还提供了将 XLink 引用的数据导入到引用文档中的更新函数)。

交易机制

数据框架还提供了一种受数据库管理系统启发的事务机制:数据在事务中被修改,如果修改被验证则由 Commit 终止,如果修改被放弃则由 Abort 终止——然后将数据恢复到说明它在交易之前。这种机制对以下情况非常有用:

  • 保护编辑操作(如果发生错误,则放弃事务并且结构保持其完整性)
  • 简化取消功能的实现(当终端用户开始一个命令时,应用程序可能会启动一个事务并直接在数据结构中操作;放弃该动作会导致事务中止)
  • 执行撤消(在提交时,记录修改以便能够将数据恢复到之前的状态)

事务机制只是管理属性的备份副本。在事务期间,属性在第一次修改之前被复制。如果交易被验证,则副本被销毁。如果事务被放弃,则属性恢复到其初始值(添加或删除属性时,操作只是反向操作)。

事务以文档为中心,即应用程序在文档上启动事务。因此,修改引用文档和更新其中一个引用文档需要两个事务,即使这两个操作都是在同一个工作会话中完成的。

标准文件服务

概述

标准文档提供包含基于 TDF 的数据框架的即用型文档。每个文档只能包含一个框架。

文档本身包含在类TDocStd_Application(或其后代)的实例化中。该应用程序管理文档的创建、存储和检索。

您可以在您的文档中实现撤消和重做,并从一个文档的数据框架引用到另一个文档的数据框架。这是通过外部链接属性完成的,该属性存储外部链接的路径和条目。

总而言之,仅标准文档就提供了对数据框架的访问。它们还允许您:

  • 更新外部链接
  • 管理数据的保存和打开
  • 管理撤消/重做功能。

应用程序

作为数据框架的容器,您需要一个文档,并且您的文档必须包含在您的应用程序中。这个应用程序将是一个类TDocStd_Application或从它继承的一个类。

创建应用程序

要创建应用程序,请使用以下语法。

创建新文档

对于您在上一个示例 (4.2.1) 中声明的应用程序,您必须添加文档doc作为TDocStd_Application::NewDocument的参数。

HandleTDocStd_Document)文档;
app->NewDocument( “NewDocumentFormat” , doc);

这里“NewDocumentFormat”是文档格式的标识符。OCCT 定义了几种标准格式,通过一组支持的 OCAF 属性和编码方法(例如二进制数据或 XML)进行区分,如下所述。如果您的应用程序定义了特定的 OCAF 属性,您需要为其定义自己的格式。

检索文档所属的应用程序

要检索包含您的文档的应用程序,请使用以下语法。

app = Handle ( TDocStd_Application )::DownCast (doc->Application());

文件

该文档包含您的数据框架,并允许您检索此框架、恢复其主标签、将其保存在文件中以及打开或关闭此文件。

访问框架的主标签

要访问数据框架中的主标签,请使用TDocStd_Document::Main,如下例所示。主标签是数据框架中根标签的第一个子标签,条目为 0:1。

TDF_Label label = doc->Main();

从其框架中的标签中检索文档

要从其数据框架中的标签检索文档,请使用TDocStd_Document::Get,如下例所示。传递给此方法的参数标签是TDF_Label的实例化。

定义存储格式

OCAF 使用可定制的机制来存储文档。为了使用 OCAF 持久性将文档保存到文件或从文件中读取,您需要在应用程序中定义一种或多种格式。

为此,请使用方法TDocStd_Application::DefineFormat(),例如:

app->DefineFormat (“NewDocumentFormat”, “New format for OCAF documents”, “ndf”,
new NewDocumentFormat_RetrievalDriver(),
new NewDocumentFormat_StorageDriver());

此示例使用默认文件扩展名“ndf”定义格式“NewDocumentFormat”,并实例化用于从该格式读取和存储文档的驱动程序。任何一个驱动程序都可以为空,在这种情况下,该格式将不支持相应的操作。

OCAF 提供了几种标准格式,每种格式都包含一些 OCAF 属性:

格式 持久性工具包 涵盖的 OCAF 属性
旧版格式(只读)
OCC-StdLite TKStdL TKLCAF
MDTV-标准 TKStd TKLCAF + TKCAF
二进制格式
宾洛咖啡 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()接受应用程序的Handle。这些方法允许轻松定义相应的格式,例如:

BinDrivers::DefineFormat (app); // 定义格式“BinOcaf”

使用这些工具包作为实现自定义属性或新持久性格式的持久性驱动程序的示例。

该应用程序可以定义多种存储格式。保存时,将使用文档中指定的格式(参见TDocStd_Document::StorageFormat())(如果应用程序中未定义该格式,保存将失败)。在读取时,存储在文件中的格式标识符被使用并记录在文档中。

通过资源文件定义存储格式

定义格式的另一种方法是使用资源文件。此方法在早期版本的 OCCT 中使用,自 7.1.0 版起被视为已弃用。此方法允许使用插件机制按需加载持久性驱动程序。

要使用此方法,请创建您自己的从TDocStd_Application继承的应用程序类,并覆盖方法ResourcesName()。该方法应该返回一个带有资源文件名称的字符串,例如“NewDocumentFormat”,它将包含格式的描述。

然后创建该资源文件并定义格式的参数:

ndf.FileFormat: NewDocumentFormat
NewDocumentFormat.Description: New Document Format Version 1.0
NewDocumentFormat.FileExtension: ndf
NewDocumentFormat.StoragePlugin: bb5aa176-c65c-4c84-862e-6b7c1fe16921
NewDocumentFormat.RetrievalPlugin: 76fb4c04-ea9a-46aa-88a2-25f6a228d902

GUID 应该是唯一的,并且对应于相关插件支持的 GUID。您可以使用现有插件(参见上表)或创建自己的插件。

最后,从$CASROOT/src/StdResource复制资源文件“Plugin” ,如有必要,在其中添加插件的定义,例如:

bb5aa176-c65c-4c84-862e-6b7c1fe16921.Location: TKNewFormat
76fb4c04-ea9a-46aa-88a2-25f6a228d902.Location: TKNewFormat

为了在程序执行期间加载这些资源文件,需要设置两个环境变量:CSF_PluginDefaultsCSF_NewFormatDefaults。例如,设置目录MyApplicationPath/MyResources中的文件:

setenv CSF_PluginDefaults MyApplicationPath/MyResources
setenv CSF_NewFormatDefaults MyApplicationPath/MyResources

保存文档

要保存文档,请确保其参数StorageFormat()对应于应用程序中定义的格式之一,并使用方法TDocStd_Application::SaveAs,例如:

app->SaveAs(doc, “/tmp/example.caf” );

从文件中打开文档

要从之前保存的文件中打开文档,您可以使用TDocStd_Application::Open,如下例所示。参数是文件的路径和保存在此文件中的文档。

app->Open(“/tmp/example.caf”, doc);

对于二进制格式,只能加载存储文档的一部分。为此,可以使用PCDM_ReadingFilter类。可以定义必须加载或省略哪些属性,或者为必须加载的子树定义一个或多个条目。以下示例打开文档doc,但仅读取“0:1:2”标签及其子标签以及它们上的TDataStd_Name属性。

filter->AddRead(“TDataStd_Name”);
app->Open(“example.cbf”, doc, filter);

此外,使用过滤器,可以将部分文档附加到同一文件中已加载的文档中。例如,要读入先前打开的文档的所有属性,除了TDataStd_NameTDataStd_Integer

filter2->AddSkipped( “TDataStd_Name” );
filter2->AddSkipped( “TDataStd_Integer” );
app->Open( “example.cbf” , doc, filter2);

PCDM_ReaderFilter::AppendMode_Protect表示如果加载算法在文档中找到已经存在的属性,它不会被加载文件中的属性覆盖。如果需要替换现有属性,则必须使用读取模式PCDM_ReaderFilter::AppendMode_Overwrite代替。

属性的AddReadAddSkipped方法不应在一个过滤器中使用。如果是这样,则在读取期间忽略AddSkipped属性。

附加到已加载文件的文档内容可能会在加载文档的相同或不同部分时执行多次。为此,过滤器读取模式必须为PCDM_ReaderFilter::AppendMode_ProtectPCDM_ReaderFilter::AppendMode_Overwrite,这将启用文档打开的“附加”模式。如果过滤器为空或 null 或在参数中被跳过,它将打开禁用“附加”模式和任何加载限制的文档。

在文档中剪切、复制和粘贴

要在文档中剪切、复制和粘贴,请使用TDF_CopyLabel类。

事实上,您必须定义一个Label,其中包含剪切或复制操作的临时值(例如,在Lab_Clipboard中)。您还必须定义另外两个标签:

  • 数据容器(例如Lab_source
  • 副本的目的地(例如Lab_Target
Copy = copy (Lab_Source => Lab_Clipboard)
Cut = copy + Lab_Source.ForgetAll() // command clear the contents of LabelSource.
Paste = copy (Lab_Clipboard => Lab_target)

因此,我们需要一个工具来将标签及其子标签的全部(或部分)内容复制到由标签定义的另一个位置。

TDF_IDFilter aFilter (Standard_False);
//Don’t copy TDataStd_TreeNode attribute
aCopy.Load(aSource, aTarget); aCopy.UseFilter(aFilter); aCopy.Perform();
// copy the data structure to clipboard
return aCopy.IsDone(); }

过滤器用于禁止复制指定类型的属性。

您还可以查看TDF_Closure类,这对于确定要从文档中删除的部分的依赖关系很有用。

外部链接

外部链接是指从一个文档到另一个文档。它们允许您稍后更新数据框架的副本。

图片[12]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

文档之间的外部链接

请注意,无论是否可以更新外部链接,都可以复制文档。

复制文档

以后有可能更新

要复制一个可能稍后更新的文档,您可以使用TDocStd_XLinkTool::CopyWithLink

TDF_Label source = doc1->GetData()->Root();
TDF_Label target = doc2->GetData()->Root();
XLinkTool.CopyWithLink(target,source);

现在目标文档具有源文档的副本。该副本还有一个链接,以便在原始更改时更新副本的内容。

在下面的示例中,源文档中发生了一些变化。因此,您需要更新目标文档中的副本。此副本作为参数target传递给TDocStd_XLinkTool::UpdateLink

XLink工具。更新链接(目标);

副本与原件之间没有任何联系

您还可以创建文档的副本,在原件和副本之间没有链接。使用此选项的语法是TDocStd_XLinkTool::Copy。复制的文档再次由参数target表示,而原始文档 – 由source 表示。

XLink工具。复制(目标,源);

OCAF 形状属性

概述

拓扑属性可以看作是拓扑结构的钩子。可以附加数据以定义对其的引用。

OCAF 形状属性用于拓扑对象及其演化访问。所有拓扑对象都存储在数据框架根标签的一个TNaming_UsedShapes属性中。此属性包含一个地图,其中包含给定文档中使用的所有拓扑形状。

用户可以将TNaming_NamedShape属性添加到其他标签。此属性包含对来自TNaming_UsedShapes属性的形状的引用(挂钩)以及这些形状的演变。TNaming_NamedShape属性包含一组钩子对:形状形状(见下图)。它不仅允许通过标签获得拓扑形状,还允许跟踪形状的演变并通过改变的形状正确更新依赖的形状。

如果形状是新创建的,则相应命名形状的旧形状是空形状。如果删除了某个形状,则此命名形状中的新形状为空。

图片[13]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

数据框架中的形状属性。

不同的算法可能会根据是否有必要在各个标签处设置结果形状的子形状:

  • 如果一个子形状必须有一些额外的属性(每个面的材料或每个边缘的颜色)。在这种情况下,特定的子形状被放置到具有该子形状的所有属性的单独标签(通常是结果形状标签的子标签)。
  • 如果需要拓扑命名算法,则将一组必要且充分的子形状放置到结果形状标签的子标签中。像往常一样,对于基本的实体和封闭壳,形状的所有面都已布置。

TNaming_NamedShape可能包含几对具有相同演变的钩子。在这种情况下,属于命名形状的拓扑形状是新形状的组合。

考虑以下示例。两个盒子(实体)融合成一个实体(结果之一)。最初,每个框都作为命名形状放置到结果标签中,该形状具有进化 PRIMITIVE 并引用TNaming_UsedShapes映射的相应形状。盒子结果标签有一个材质属性和六个包含盒子面命名形状的子标签。

图片[14]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

结果框

熔断操作后,修改后的结果作为命名形状放置到单独的标签中,它指的是旧形状(其中一个框)和熔断操作产生的新形状,并具有进化修改(见下图) )。

包含修改面信息的命名形状属于融合结果子标签:

  • 带有标签 1 的子标签 – 来自框 1 的修改面,
  • 带有标签 2 的子标签 – 来自框 2 的修改面。
图片[15]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

对于正确命名机制的功能,这是必要且充分的信息:结果的任何子形状都可以通过名称类型和标签集明确识别,其中包含命名形状:

  • 面 F1′ 作为面 F11 的修改
  • face F1” 作为 face F12 的生成
  • 边作为两个连续面的交点
  • 顶点作为三个连续面的交集

在对源盒子进行任何修改后,应用程序必须自动重建命名实体:重新计算盒子的命名形状(实体和面)并融合引用新命名形状的结果命名形状(实体和面)。

注册形状及其演变

使用TNaming_NamedShape创建属性时,会填充属性的以下字段:

  • 称为“旧”和“新”形状的形状列表 新形状被重新计算为命名形状的值。这对的含义取决于进化的类型。
  • 进化的类型,它是TNaming_Evolution枚举的一个术语,用于放置到单独标签的选定形状:
    • PRIMITIVE – 新创建的拓扑,没有以前的历史;
    • GENERATED – 像往常一样,命名形状的这种演变意味着新形状是从低级旧形状创建的(例如,边缘的棱镜面);
    • MODIFY – 新形状是修改后的旧形状;
    • DELETE – 新形状为空;具有这种演变的命名形状只是表示从模型中删除了旧的形状拓扑;
    • SELECTED – 具有这种演变的命名形状对拓扑的历史没有影响。

只有进化相同的形状对才能存储在一个命名形状中。

使用命名资源

TNaming_Builder类允许创建命名形状属性。它有一个未来属性的标签作为构造函数的参数。各自的方法用于形状对的演化和设置。如果对于同一个TNaming_Builder对象,给出了许多具有相同演化的形状对,那么这些对将被放置在生成的命名形状中。创建TNaming_Builder类的新对象后,将在给定标签处创建一个空的命名形状。

// 在“label”处创建一个新的空命名形状
// 使用进化生成一对形状
builder.Generated(oldshape1,newshape1);
// 设置另一对具有相同进化的形状
builder.Generated(oldshape2,newshape2);
// 获取结果 – TNaming_NamedShape 属性
Handle( TNaming_NamedShape ) ns = builder.NamedShape();

读取命名形状属性的内容

您可以使用方法TNaming_NamedShape::Evolution()来获取此命名形状的演变,并使用方法TNaming_NamedShape::Get()来获取此命名形状的所有对的新形状的复合。

有关命名形状的内容或拓扑修改历史的更多详细信息可以通过以下方式获得:

  • TNaming_Tool provides a common high-level functionality for access to the named shapes contents:
    • The method GetShape(Handle(TNaming_NamedShape)) returns a compound of new shapes of the given named shape;
    • The method CurrentShape(Handle(TNaming_NamedShape)) returns a compound of the shapes, which are latest versions of the shapes from the given named shape;
    • The method NamedShape(TopoDS_Shape,TDF_Label) returns a named shape, which contains a given shape as a new shape. A given label is any label from the data framework – it just gives access to it.
  • TNaming_Iterator gives access to the named shape and hooks pairs.
// create an iterator for a named shape
TNaming_Iterator iter(namedshape);
// 在某些对未迭代时进行迭代
(iter.More()){
// 从当前对中获取新形状
TopoDS_Shape newshape = iter.NewShape();
// 从当前对中获取旧形状
TopoDS_Shape oldshape = iter.OldShape();
// 做一点事…
// 转到下一对
iter.Next();
}

拓扑命名

拓扑命名机制基于 3 个组件:

  • 使用的建模操作算法的历史;
  • 在数据框架中注册构建的结果(即在 OCAF 文档中加载提取历史的必要元素);
  • 算法结果的“选定”子形状的选择/重新计算。

为了获得预期的结果,三个组件的工作应该是同步的,并且应该尊重每个组件的规则。

算法历史

使用过的建模操作的“正确”历史是命名机制的基础。它应该由支持该操作的算法提供。历史内容取决于拓扑结果的类型。历史的目的是为选择/重新计算机制的一致和正确工作提供所有实体。下表根据结果类型显示了预期的实体类型。

结果类型 算法历史返回的子形状类型 注释
实心或封闭壳 面孔 所有面孔
开壳或单面 仅开放边界的面和边 所有面加上开放边界的所有边
闭合线 边缘 所有边
开线 边和结束顶点 导线的所有边加上结束顶点
边缘 顶点 预计有两个顶点
化合物或 CompSolid 相应地使用上述声明的规则适用于第一级的所有子形状 Compound/CompSolid 将逐级探索,直到满足上述任何类型

历史应该只返回(和跟踪)基本类型的子形状,即面、边和顶点,而其他所谓的聚合类型:复合、壳、线,由选择机制自动计算。

有几种情况有一些简单的例外。例如,如果 Result 包含接缝边缘 – 在圆锥、圆柱或球形表面中 – 此接缝边缘应由历史记录跟踪,此外还应在类型之前定义。所有退化的实体都应该被过滤并排除在考虑之外。

在数据框架中加载历史

使用的算法根据上述规则返回的所有元素都应放入数据框架(或换句话说,OCAF 文档)中,因此在所谓的结果标签下以线性顺序排列。

“结果标签”是TDF_label,用于将来自TopoDS的算法结果Shape保存在NamedShape属性中。在数据框架中加载结果的子形状时,应使用“注册形状及其演变”一章的规则。这些规则也适用于加载主要形状,即建模算法产生的结果形状。

选择/重新计算机制

当数据框架充满所有受影响的实体(包括当前建模操作产生的数据结构和当前操作所依赖的先前建模操作产生的数据结构)时,可以选择当前结果的任何子形状,即支持此功能的相应新命名数据结构可以生成并保存在数据框架中。

拓扑命名的用户界面之一是TNaming_Selector类。它实现了上述子形状“选择”功能作为附加功能。即它可以用于:

  • 将选定的形状存储在标签上——它的选择
  • 访问命名形状 – 检查形状的保留值
  • 更新此命名 – 重新计算先前选定的形状。

选择器将一个带有进化选择的新命名形状放置给定的标签上。选择器创建所选形状的名称,这是如何使用资源查找所选拓扑的唯一描述(数据结构):

  • 给定的上下文形状,即保留在Result Label上的主要形状,其中包含选定的子形状,
  • 它的演变和
  • 命名结构。

在对上下文形状进行任何修改并更新相应的命名结构之后,需要调用方法TNaming_Selector::Solve。如果命名结构,即上面提到的name正确,选择器会自动更新相应命名形状中选择的子形状,否则失败。

探索形状进化

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);
}

拓扑命名用法示例

拓扑命名是 Open CASCADE 的一种机制,旨在保持对所选形状的引用。例如,如果我们选择一个实体形状的顶点并“要求”拓扑命名以保持对该顶点的引用,那么无论形状发生什么(平移、缩放、与另一个形状融合等),它将引用该顶点。 )。

让我们考虑一个例子:想象一个木盘。工作是在里面钉几个钉子:

图片[16]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

钉在木板上的钉子

可能有几个不同大小和位置的钉子。锤子应将每个钉子准确地推入顶的中心点。为此,用户执行以下操作:

  • 制作几个不同高度和直径的钉子(根据需要),
  • 为锤子选择(选择)每个钉子的上表面。

工作完成了。应用程序应该完成剩下的工作——Hammer 为每个选定的钉子表面计算一个中心点,然后“敲击”每个钉子,将钉子钉入木板。

如果用户改变了一些钉子的位置会发生什么?锤子怎么会知道呢?它保持对每个钉子表面的引用。但是,如果重新定位钉子,锤子应该知道所选表面的新位置。否则,它将在旧位置“罢工”(保持手指远离!)……

拓扑命名机制应该有助于 Hammer 获得重新定位的表面。Hammer 通过调用TNaming_Selection::Solve()方法“请求”机制“解析”选定的形状,并通过调用TNaming_Selector::NamedShape()机制“返回”位于新位置的修改曲面。

拓扑命名在上面的示例中表示为“黑匣子”。现在是时候让盒子更“透明”了。

该应用程序包含 3 个功能:

  • Nail – 生成代表指甲的形状,
  • 翻译器——沿着木板翻译一个形状,
  • 锤子– 驱动木板中的钉子。

每个函数都为拓扑命名提供了一些提示,如何“重新求解”选定的子形状:

  • Nail 构造一个实体形状并将形状的每个面放入子标签中:
图片[17]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

通过 Nail 的子标签分布面
  • Translator移动一个形状并记录每个面的修改:它在每个移动的 Nail 的子标签上放置一对:“旧”形状 – “新”形状。“旧”形状表示初始位置处的指甲面。“新”形状——是同一张面,但在一个新的位置:
图片[18]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

钉子面搬迁登记

它是如何工作的?

  • 锤子选择调用TNaming_Selector::Select()的钉子的面。此调用为所选形状创建一个唯一名称。在我们的示例中,它将直接引用 Nail 顶面(Face 1)的标签。
  • 当用户沿着木板移动钉子时,翻译器通过将以下对记录此修改:钉子的“旧”面 – 钉子的新面到其子标签中。
  • 当 Hammer 调用TNaming::Solve()时,拓扑命名“查看”所选形状的唯一名称并尝试重新求解它:
    • 它在数据树中找到所选形状的第一个外观——它是 Nail 函数Face 1下的标签。
    • 它遵循这张面的演变。在我们的例子中,只有一个演变——翻译:Face 1(顶面)-Face 1′(重新定位的顶面)。所以,最后的演变是重新定位的顶面。
  • 调用TNaming_Selector::NamedShape()方法,Hammer 获得所选面的最后演变——重新定位的顶面。

工作完成了。

PS 让我们对一个稍微复杂一点的案例说几句话——顶面线材的选择。它的拓扑名称是两个面的“交集”。我们记得Nail只将面孔放在其标签下。因此,选定的线将代表顶面的“交叉点”和保持指甲“头部”的圆锥面。另一个示例是选定的顶点。它的唯一名称可以表示为三个甚至更多面的“交集”(取决于形状)。

标准属性

概述

标准属性是即用型属性,允许为许多基本数据类型创建和修改属性。它们在包TDataStdTDataXtdTDF中可用。每个属性都属于以下四种类型之一:

  • 几何属性;
  • 一般属性;
  • 关系属性;
  • 辅助属性。

几何属性

  • Axis – 简单地标识,内部具有轴形状的相关TNaming_NamedShape属性属于同一标签;
  • 约束——包含有关几何之间约束的信息:使用的几何属性、类型、值(如果存在)、平面(如果存在)、“反转”、“反转”和“已验证”标志;
  • 几何——简单地识别具有指定类型几何的相关TNaming_NamedShape属性属于同一标签;
  • Plane – 简单地标识,内部具有平面形状的相关TNaming_NamedShape属性属于同一标签;
  • Point – 简单地标识,内部具有点形状的相关TNaming_NamedShape属性属于同一标签;
  • 形状——简单地标识相关的TNaming_NamedShape属性属于同一标签;
  • PatternStd – 识别五种可用图案模型之一(线性、圆形、矩形、圆形矩形和镜像);
  • 位置– 标识 3d 全局空间中的位置。

一般属性

  • AsciiString – 包含 AsciiString 值;
  • BooleanArray – 包含一个布尔数组;
  • BooleanList – 包含一个布尔值列表;
  • ByteArray——包含一个字节(无符号字符)值数组;
  • 评论——包含一个字符串——给定标签(或属性)的评论;
  • 表达式——包含一个表达式字符串和一个使用的变量属性列表;
  • ExtStringArray – 包含一个ExtendedString值的数组;
  • ExtStringList – 包含一个ExtendedString值的列表;
  • 整数——包含一个整数值;
  • IntegerArray – 包含一个整数值数组;
  • IntegerList – 包含整数值列表;
  • IntPackedMap – 包含整数的打包映射;
  • 名称——包含一个字符串——给定标签(或属性)的名称;
  • NamedData – 最多可包含以下命名数据集(词汇表)中的 6 个:DataMapOfStringInteger、DataMapOfStringReal、DataMapOfStringString、DataMapOfStringByte、DataMapOfStringHArray1OfIntegerDataMapOfStringHArray1OfReal
  • NoteBook – 包含一个NoteBook对象属性;
  • Real——包含一个真实的值;
  • RealArray – 包含实数值数组;
  • RealList – 包含实数值列表;
  • 关系——包含一个关系字符串和一个使用的变量属性列表;
  • Tick——定义一个布尔属性;
  • 变量——简单地标识一个变量属于这个标签;包含标志是约束和一串使用的单位(“mm”,“m”…);
  • UAttribute – 具有用户定义的 GUID 的属性。通常,该属性用作标记,它独立于同一标签下的属性(注意,具有相同 GUID 的属性不能属于同一标签)。

关系属性

  • 引用——包含对其自身数据框架标签的引用;
  • ReferenceArray——包含一个引用数组;
  • ReferenceList——包含参考列表;
  • TreeNode – 此属性允许在数据框架中创建内部树;此树由具有指定树 ID 的节点组成;每个节点包含对父亲、上一个兄弟、下一个兄弟、第一个子节点和树 ID 的引用。

辅助属性

  • 目录——子标签管理的高级工具属性;
  • TagSource – 此属性用于创建新的子标签:它存储标签的最后创建的子标签的标签并提供对新子标签创建功能的访问。

所有属性都继承类TDF_Attribute,因此,每个属性都有自己的 GUID 和用于属性创建、操作、访问数据框架的标准方法。

支持同一标签上相同类型的多个属性的属性

默认情况下,同一标签上仅支持一个相同类型的属性。例如,您只能在同一标签上设置一个TDataStd_Real属性。通过向属性添加所谓的“用户定义的 ID”功能,消除了某些预定义的标准属性子集的限制。下面列出的属性收到了这个新功能:

详见第 6.4 段。

所有属性共有的服务

访问 GUID

要访问属性的 GUID,可以使用两种方法:

  • 方法GetID是类的静态方法。它返回任何属性的 GUID,它是指定类的对象(例如,TDataStd_Integer返回整数属性的 GUID)。标准属性列表中只有两个类不支持这些方法:TDataStd_TreeNodeTDataStd_Uattribute,因为这些属性的 GUID 是可变的。
  • 方法ID是属性类的对象的方法。它返回此属性的 GUID。绝对所有的属性都有这个方法:只有通过这个标识符,你才能辨别一个属性的类型。

要查找附加到特定标签的属性,请使用要查找的属性类型的 GUID。可以使用GetID方法和标签的Find方法找到此信息,如下所示:

Standard_GUID anID = MyAttributeClass::GetID();
Standard_Boolean HasAttribute = aLabel.Find(anID,anAttribute);

标准属性的常规接口

通常为属性创建标准命名方法:

  • Method Set(label, [value]) is the static method, which allows to add an attribute to a given label. If an attribute is characterized by one value this method may set it.
  • Method Get() returns the value of an attribute if it is characterized by one value.
  • Method Dump(Standard_OStream) outputs debug information about a given attribute to a given stream.

The choice between standard and custom attributes

When you start to design an application based on OCAF, usually it is necessary to choose, which attribute will be used for allocation of data in the OCAF document: standard or newly-created?

可以通过标准 OCAF 属性来描述任何模型。但是,这种描述在内存和速度方面是否有效,同时是否方便使用,仍然是一个问题。

这取决于特定的模型。

OCAF 强加了一个标签只能分配一种属性类型的限制。有必要考虑应用程序数据树的设计。例如,如果一个标签应该拥有多个 double 值,则需要通过多个子子标签分配它们或使用一个 double 值数组。

让我们考虑OCAF树中同一模型的几种边界实现,并分析每种方法的优缺点。

方法比较与分析

下面描述了两种不同的模型实现:一种基于标准 OCAF 属性,另一种基于创建拥有模型所有数据的新属性。

负载通过形状分布。在由 (x, y 和 z) 坐标定义的特定点处进行测量。负载表示为每个测量点在局部坐标系的 X、Y 和 Z 轴上的投影。将局部坐标系转换为全局坐标系需要一个变换矩阵,但这是可选的。

因此,我们在每个测量点都有 15 个双精度值。例如,如果此类点的数量为 100 000,则意味着我们必须在 OCAF 文档中存储 1 500 000 个双精度值。

第一种方法在于使用标准 OCAF 属性。此外,标准属性的使用方式有多种变体:

  • 将所有 1 500 000 个双精度值分配为一个附在一个标签上的双精度值数组;
  • 将一种负载测量值(15 个值)分配为一组双精度值,并将一个测量点附加到一个标签;
  • 将每个测量点分配为附加到一个标签的 3 个双精度值的数组,将负载投影到局部坐标系轴上作为附加到子标签的另一个 3 个双精度值数组,以及投影矩阵(9 个值) 作为第三个数组也附加到一个子标签。

当然,其他变体也是可能的。

图片[19]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

将所有数据分配为一个双精度值数组

将所有数据分配为一个双值数组的第一种方法节省了初始内存并且易于实现。但是访问数据很困难,因为这些值存储在一个平面数组中。有必要使用几种方法实现一个类,以访问特定字段,如测量点、负载等。

如果可以在应用程序中编辑这些值,则意味着每个版本都会备份整个数组。内存使用量会增加非常快!因此,只有在不可编辑数据的情况下才可以考虑这种方法。

让我们考虑每个标签的每个测量点的数据分配(第二种情况)。在这种情况下,我们创建了 100 000 个标签——每个测量点一个标签,并将一组双精度值附加到这些标签:

图片[20]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

将每个测量点的数据分配为双值数组

就内存使用而言,现在数据版本更安全。一个测量点的值变化(任何值:点坐标、负载等)仅支持一个小的双精度值数组。但是这种结构(树)需要更多的内存空间(额外的标签和属性)。

此外,访问值仍然很困难,需要有一个具有访问数组字段方法的类。

通过OCAF树分配数据的第三种情况如下所示:

图片[21]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

将数据分配到单独的双精度值数组中

在这种情况下,涉及子标签,我们可以轻松访问每个测量点、负载或矩阵的值。我们不需要具有访问数据方法的接口类(如果存在,使用数据结构会有所帮助,但这是可选的)。

一方面,这种方法需要更多内存来分配属性(双值数组)。另一方面,它通过仅备份包含修改数据的小数组来节省数据编辑过程中的内存。因此,如果数据是完全可修改的,则这种方法更可取。

在下结论之前,让我们考虑通过新创建的 OCAF 属性实现的相同模型。

例如,我们可以将属于一个测量点的所有数据分配为一个 OCAF 属性。在这种情况下,我们实现了使用标准属性的第三种变体(见图 3),但我们使用的内存更少(因为我们只使用一个属性而不是三个):

图片[22]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

将数据分配到新创建的 OCAF 属性中

使用标准 OCAF 属性的第二种变体仍然存在缺点:编辑数据时,OCAF 会备份测量点的所有值。

假设我们有一些不可编辑的数据。我们最好将这些数据与可编辑数据分开分配。备份不会影响不可编辑的数据,数据编辑时内存不会增加太多。

结论

在决定选择哪种数据模型实现变体时,有必要考虑应用程序响应时间、内存分配和事务中的内存使用情况。

大多数模型可以仅使用标准 OCAF 属性来实现。其他一些模型需要特殊处理并需要实现新的 OCAF 属性。

具有用户定义 GUID 的标准属性

由于特定用户的 ID,上面列出的属性允许在同一个标​​签上设置尽可能多的相同类型的属性。让我们以TDataStd_Real属性为例来考虑它。上一版本的属性允许使用静态方法 Set 设置属性,方法如下:

静态 Handle( TDataStd_Real ) 设置 ( const TDF_Label & label , const Standard_Real );

这是由属性保留的默认形式。它使用属性标识的默认 GUID – TDataStd_Real::GetID()。如果您想使用新功能(用户定义的 Real 属性),例如定义几个属性,这些属性应该保持相同类型的值 – Standard_Real,但要与不同用户的概念(或对象)相关联,则使用新的静态应该使用方法 Set。在我们的示例中,我们将定义两个 Real 属性,它们表示两个客户的对象 – 密度和体积,并将被放在同一个标​​签上。

#define DENSITY Standard_GUID(“12e9454b-6dbc-11d4-b9c8-0060b0ee2810” )
#define VOLUME Standard_GUID(“161595c0-3628-4737-915a-c160ce94c6f7” )
TDF_Label aLabel = …;
// 具有与用户对象“密度”关联的用户定义 GUID 的真实属性类型
TDataStd_Real::Set (aLabel, DENSITY, 1.2);
// 具有与用户对象“Volume”关联的用户定义 GUID 的真实属性类型
TDataStd_Real::Set (aLabel, VOLUME, 185.5);
要查找用户定义的 Real 属性,只需使用相应的GUID:
aLabel.FindAttribute (DENSITY, anAtt);

使用用户定义的 GUID 创建属性。

您可以使用用户定义的 GUID 创建属性的新实例,并以两种方式将其添加到标签。

  1. 使用静态方法 Set()。例如:
TDF_Label aLabel = …;
Standard_Integer aValue = …;
TDataStd_Integer::Set (aLabel, aGuid, aValue);
  1. 使用默认构造函数
anInt->SetID(aGuid);
aLabel.Add(anInt);
anInt->Set(aValue);

可视化属性

概述

标准可视化属性实现应用程序交互服务(请参阅可视化用户指南)。在开放级联技术应用框架的背景下。标准可视化属性是 AISViewer 和 Presentation,属于 TPrsStd 包。

提供的服务

定义交互式查看器属性

TPrsStd_AISViewer类允许您定义交互式查看器属性。每个数据框架可能只有一个这样的属性,并且它总是放置在根标签上。因此,它可以由数据框架的任何标签(“访问标签”)设置或找到。尽管如此,默认架构可以轻松扩展,用户可以自己管理每个框架的多个查看器。

要如下例所示初始化AIS查看器,请使用方法Find

// “access”是数据框架的任何标签

定义表示属性

TPrsStd_AISPresentation类允许您定义文档标签内容的视觉呈现。除了各种视觉领域(颜色、材质、透明度、isDisplayed等)之外,此属性还包含其驱动程序 GUID。此 GUID 定义功能,每次需要时都会更新演示文稿。

创建自己的驱动程序

抽象类TPrsStd_Driver允许您定义自己的驱动程序类。只需在新类中重新定义 Update 方法,这将重建演示文稿。

如果您的驱动程序使用唯一的驱动程序 GUID 放置到驱动程序表中,则每次查看器使用与驱动程序的 GUID 相同的 GUID更新演示文稿时,必须为这些演示文稿调用驱动程序的Update方法:

图片[23]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

与往常一样,驱动程序的 GUID 和显示属性的 GUID 相同。

为驱动程序使用容器

您经常需要一个容器来存放不同的表示驱动程序。TPrsStd_DriverTable类提供此服务。您可以将驱动程序添加到表中,查看是否添加成功,然后将其填充为标准驱动程序。

要使用标准驱动程序填充驱动程序表,首先如上例所示初始化AIS查看器,然后将InitStandardDrivers方法的返回值传递给Get方法返回的驱动程序表。然后将TNaming_NamedShape附加到标签上,并使用方法Set在表示属性中设置命名形状。然后将表示属性附加到命名形状属性,并且表示属性包含的AIS_InteractiveObject将为命名形状初始化其驱动程序。这可以在下面的示例中看到。

例子

DriverTable::Get() -> InitStandardDrivers();
// 接下来,将您命名的形状附加到标签上
// 这里,将 AISPresentation 附加到 NS。

功能服务

功能服务聚合模型再生所需的数据。TFunction包中的函数机制提供了函数和任何执行算法之间的链接,这些算法从数据框架中获取参数,并将结果写入同一框架内。

当您编辑任何应用程序模型时,您必须通过传播修改来重新生成模型。每个传播步骤调用各种算法。要使这些算法独立于您的应用程序模型,您需要使用函数服务。

图片[24]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

文件结构

举个例子,一个由一个盒子组成的建模序列的例子,在它的一个边上应用了一个圆角。如果你改变盒子的高度,圆角也需要重新生成。

查找函数、它们的所有者和根源

TFunction_Function类是一个属性,它存储了一个指向数据框架中的功能驱动程序的链接。在静态表TFunction_DriverTable中存储了函数属性和驱动程序之间的对应链接。

您可以编写函数属性,即此类属性的驱动程序,它根据给定的更改标签映射更新函数结果,并将带有 GUID 的驱动程序设置为驱动程序表。

然后数据模型的求解器算法可以找到对应标签上的Function属性,并调用Execute驱动方法来更新函数的结果。

存储和访问有关功能状态的信息

为了更新算法优化,每个函数驱动程序都可以访问TFunction_Logbook对象,该对象是一组已触摸、受影响和有效标签的容器。使用这个对象,驱动程序可以知道函数的哪些参数被修改了。

传播修改

应用程序必须实现其功能、功能驱动程序和用于创建参数模型的通用求解器。例如,检查以下模型:

图片[25]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

它的创建过程如下:

  • 创建一个矩形平面F,高度为 100,宽度为 200;
  • 以面F为基础创建棱镜P ;
  • 在棱镜边缘创建圆角L ;
  • 将F的宽度从 200 更改为 300;
  • 面F函数的求解器启动;
  • 求解器检测到面F函数的参数已被修改;
  • 求解器调用面部F函数的驱动程序来重新生成面部;
  • 驱动程序重建F面,并将面宽参数的标签添加到日志中作为触摸,将面F的功能标签添加为受影响;
  • 求解器检测P的函数——它取决于F的函数;
  • 求解器调用 prism P函数的驱动程序;
  • 驱动程序重建 prism P并将此 prism 的标签添加到日志中作为受影响的;
  • 求解器检测L的函数——它取决于P的函数;
  • 求解器调用L函数驱动程序;
  • 驱动程序重建圆角L并将圆角的标签添加到受影响的日志中。

函数机制使用示例

介绍

让我们通过一个简单的例子来描述Open CASCADE应用框架的功能机制的使用。
这个例子代表了一个由一个圆锥和两个不同半径和高度的圆柱体组成的“钉子”:

图片[26]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

钉子

这三个物体(一个圆锥体和两个圆柱体)是独立的,但功能机制使它们相互连接并代表一个物体——一个钉子。
对象“钉子”具有以下参数:

  • 钉子的位置由圆锥的顶点定义。圆柱体建立在圆锥体上,因此它们取决于圆锥体的位置。通过这种方式,我们定义了圆柱体对圆锥体的依赖关系。
  • 钉子的高度由圆锥的高度定义。
    让我们考虑一下长圆柱体有 3 个锥体高度,而集管圆柱体有锥体高度的一半。
  • 钉子的半径由圆锥的半径定义。长圆柱的半径与该值一致。让我们考虑集管圆柱体的圆锥半径是一个半。

因此,圆柱体取决于锥体,而锥体参数定义了钉子的大小。

这意味着重新定位锥体(改变其顶点)移动钉子,锥体半径的变化产生更薄或更厚的钉子,锥体高度的变化使钉子缩短或延长。
 建议检查创建“钉子”的 3D 参数模型所需的编程步骤。

第 1 步:数据树

第一步是在 OCAF 树中分配模型数据。换句话说,有必要决定把数据放在哪里。

在这种情况下,可以使用引用来将数据组织成一个简单的树,以定义相关参数:

  • 指甲

    • 锥体
      • 位置 (x,y,z)
      • 半径
      • 高度
    • 气缸(阀杆)
      • Position = “Cone” 位置翻译为沿 Z 的 “Cone” 高度;
      • 半径=“圆锥”半径;
      • 高度 = “圆锥”高度乘以 3;
    • 气缸(头)
      • 位置 =“长圆柱”位置翻译为“长圆柱”沿 Z 的高度;
      • 半径 = “长圆柱”半径乘以 1.5;
      • 高度 = “圆锥”高度除以 2。

    “钉子”对象在树中有三个子叶:圆锥和两个圆柱。

    锥体对象是独立的。

    代表钉子“茎”的长圆柱体是指锥体的相应参数,以定义其自身的数据(位置、半径和高度)。这意味着长圆柱取决于圆锥。

    头柱的参数可以只用锥体参数来表示,也可以用锥体和长柱体参数来表示。建议用长圆柱的位置和半径来表示头圆柱的位置和半径,用圆锥的高度来表示头圆柱的高度。就是说头柱取决于锥体和长柱体。

第 2 步:接口

数据模型的接口负责上一步表示的数据树的动态创建、数据的修改和删除。

名为INail的接口应包含用于创建钉子数据树、设置和获取其参数、计算、可视化和删除的方法。

指甲的创造

接口的这个方法在给定的 OCAF 数据树的叶子上为钉子创建一个数据树。

它为锥体和两个圆柱体创建三个子叶,并分配必要的数据(长圆柱体和头部圆柱体的子叶处的引用)。

它设置钉子的位置、半径和高度的默认值。

钉子有以下用户参数:

  • 位置——与圆锥的位置重合
  • 钉干部分的半径——与圆锥的半径重合
  • 钉子的高度——圆锥体和两个圆柱体的高度之和

钉子的位置和半径的值是为锥形对象数据定义的。圆锥的高度重新计算为 2 * 钉子的高度并除以 9。

计算

函数机制负责重新计算钉子。稍后将在本文档中详细描述。

数据叶由对实际数据位置的引用和定义所引用数据的乘法系数的实际值组成。

例如,长圆柱体的高度定义为对系数为 3 的圆锥体高度的引用。长圆柱体高度的数据叶应包含两个属性:对圆锥体高度的引用和实际值等于 3。

可视化

 可以使用标准 OCAF 可视化机制显示指甲功能的形状结果。

去除指甲

要从查看器和数据树中自动擦除指甲,只需从属性中清除指甲叶即可。

第 3 步:功能

钉子由四个函数定义:圆锥体、两个圆柱体和钉子函数。
圆锥的功能是独立的。圆柱体的功能取决于锥体功能。钉子函数取决于所有函数的结果:

图片[27]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

函数之间的依赖关系图

模型的计算从圆锥函数开始,然后是长圆柱体,然后是头部圆柱体,最后由函数链末端的钉子函数生成结果。

Open CASCADE 技术的功能机制创建了这个依赖关系图,并允许在依赖关系之后对其进行迭代。Function Mechanism 唯一需要其用户的是实现TFunction_Driver的纯虚方法:

  • ::Arguments() – 返回函数的参数列表
  • ::Results() – 返回函数的结果列表

这些方法为函数机制提供有关参数位置和函数结果的信息,并允许构建函数图。TFunction_Iterator类按执行顺序迭代图的函数。

计算函数的纯虚方法TFunction_Driver::Execute()应该被覆盖。

::MustExecute()方法调用函数驱动程序的::Arguments()方法,理想情况下,此信息(修改函数参数的知识)足以决定是否执行该函数。因此,通常不应覆盖此方法。

圆锥和圆柱函数仅在几何构造算法上有所不同。其他参数相同(位置、半径和高度)。

这意味着可以创建一个基类——三个函数的函数驱动程序,以及两个后代类:一个圆锥体或一个圆柱体。

对于基本功能驱动程序,方法::Arguments()::Results()将被覆盖。负责创建圆锥和圆柱的两个后代函数驱动程序将仅覆盖方法::Execute()

钉子的函数驱动的方法::Arguments()返回位于它下面的函数在叶子树中的结果。::Execute()方法只是收集函数的结果并制作一个形状——钉子。

这样,使用功能机制的数据模型就可以使用了。不要忘记在TFunction_DriverTable类的帮助下介绍功能驱动表的功能驱动。

示例 1:函数的迭代和执行。

这是用于迭代和执行函数的代码示例。

// 定义了函数的范围。
// 接收模型中的修改信息。
TFunction_Logbook & aLog = aScope->GetLogbook();
// 迭代器由函数范围初始化。
TFunction_Iterator anIterator (anyLabel);
anIterator.SetUsageOfExecutionOrder ( true );
// 函数被迭代,它对修改后的数据的依赖被检查并在必要时执行。
for (; anIterator.more(); anIterator.Next())
{
// 函数迭代器可能会返回当前函数的列表以供执行。
// 它可能对函数的多线程执行有用。
const TDF_LabelList & aCurrentFunctions = anIterator.Current();
// 迭代当前函数的列表。
for ( TDF_ListIteratorOfLabelList aCurrentIterator (aCurrentFunctions);
aCurrentIterator.More(); aCurrentIterator.Next())
{
// 创建函数的接口。
TFunction_IFunction anInterface (aCurrentIterator.Value());
// 检索函数驱动程序。
Handle( TFunction_Driver ) aDriver = anInterface.GetDriver();
// 检查函数对修改数据的依赖关系。
if (aDriver->MustExecute (aLog))
{
// 函数被执行。
int aRes = aDriver->执行 (aLog);
如果(aRes)
{
返回
}
}
}
}

示例 2:气缸功能驱动程序

这是圆柱体功能驱动程序的代码示例。为了使事情更清楚,还提到了基类中的方法::Arguments()::Results()

// 虚方法 ::Arguments() 返回函数的参数列表。
CylinderDriver::Arguments( TDF_LabelList & args)
{
// 收集位于函数子叶的直接参数(见图2)
TDF_ChildIterator cIterator( Label(), false );
for (; cIterator.More(); cIterator.Next() )
{
// 直接参数。
TDF_Label sublabel = cIterator.Value();
Args.Append( sublabel );
// 检查对外部数据的引用。
If ( sublabel. FindAttribute ( TDF_Reference::GetID (), ref ) )
{
args.Append( ref-Get() );
}
}
// 一个虚方法 ::Results() 返回一个结果叶子列表。
CylinderDriver::Results( TDF_LabelList & res)
{
// 结果保存在函数标签中。
  Res.Append(标签());
}
// 执行功能驱动程序。
Int CylinderDriver::Execute( TFunction_Logbook & log )
{
// 圆柱体的位置 – 第一个函数的位置(圆锥体)
//沿 Z 提升所有先前函数的高度值。
gp_Ax2 axes = …。// 超出本指南的范围。
// 获取半径值。
// 它位于第二个子子叶(见图2)。
TDF_Label radiusLabel = Label().FindChild( 2 );
// 获取半径 () 的乘数。
Handle(TDF_Reference) refRadius;
RadiusLabel.FindAttribute( TDF_Reference::GetID(), refRadius );
// 获取半径的引用。
Handle(TDF_Reference) refRadius;
RadiusLabel.FindAttribute( TDF_Reference::GetID (), refRadius);
//计算半径值。
double radius = 0.0;
if ( refRadius.IsNull() )
{
radius = radiusValue-Get();
}
else
{
// 获取引用的半径值。
Handle(TDataStd_Real) referencedRadiusValue;
RefRadius-Get().FindAttribute(TDataStd_Real::GetID() ,referencedRadiusValue );
radius = referencedRadiusValue-Get() * radiusValue-Get();
}
// 检索高度值。
double height = … // 与获取半径值类似的代码。
// 圆柱体被创建。
TopoDS_Shape cylinder = BRepPrimAPI_MakeCylinder(axes, radius, height);
// 结果(圆柱)被设置
TNaming_Builder builder( Label() );
Builder.Generated( cylinder );
// 结果叶子的修改保存在日志中。
log.SetImpacted( Label() );
return 0;
}

XML 支持

OCCT中XML文件的读写是由LDOM包提供的,它构成了XML OCAF持久化的一个组成部分,是在Open CASCADE技术之上提供的可选组件。

Light DOM (LDOM) 包包含维护数据结构的类,其主要原则符合 W3C DOM Level 1 Recommendations。XML OCAF 持久性模式所要求的这些类的目的是:

该软件包涵盖了许多称为“DOM 解析器”的产品提供的功能。与它们中的大多数不同,LDOM 是专门为满足以下要求而开发的:

  • 最小化由 DOM 数据结构分配的虚拟内存。平均而言,LDOM 的内存量与 XML 文件大小 (UTF-8) 相同。
  • 最大限度地减少解析和格式化 XML 以及访问 DOM 数据结构所需的时间。

Both these requirements are important when XML files are processed by applications if these files are relatively large (occupying megabytes and even hundreds of megabytes). To meet the requirements, some limitations were imposed on the DOM Level 1 specification; these limitations are insignificant in applications like OCAF. Some of these limitations can be overridden in the course of future developments. The main limitations are:

  • No Unicode support as well as various other encodings; only ASCII strings are used in DOM/XML. Note: There is a data type TCollection_ExtendedString for wide character data. This type is supported by LDOM_String as a sequence of numbers.
  • Some superfluous methods are deleted: getPreviousSibling, getParentNode, etc.
  • 无法解析任何类型的 XML 实体
  • 不支持 DTD:解析器只检查一般 XML 规则的遵守情况,从不验证文档。
  • 只有 5 种可用类型的 DOM 节点:LDOM_ElementLDOM_AttrLDOM_TextLDOM_CommentLDOM_CDATASection
  • 不支持命名空间;使用前缀名称代替限定名称。
  • 不支持DOMException接口(尝试删除不存在的节点时也不例外)。

LDOM 仅依赖于内核 OCCT 类。因此,它可以在可能需要 DOM/XML 支持的各种算法中在 OCAF 持久性之外使用。

文档驱动程序

文档存储和检索的驱动程序管理内存中的瞬态 OCAF 文档与其在容器(磁盘、内存、网络)中的持久反射之间的转换。对于 XML 持久性,它们在包XmlDrivers中定义。

这些驱动程序的主要方法(入口点)是:

  • Write() – 用于存储驱动程序;
  • Read() – 用于检索驱动程序。

最常见的情况(在 XML Persistence 中实现)是向常规 OS 文件写入/读取文档。这种转换分两步进行:

首先需要将瞬态文档转换成另一种形式(称为持久性),适合写入文件,反之亦然。在 XML 持久性中, LDOM_Document用作 OCAF 文档的持久形式,而 DOM_Nodes 是持久对象。OCAF 文档是带有属性的标签树。其转化为持久化形式在功能上可以分为两部分:

  • 标签结构的转换,由方法XmlMDF::FromTo()执行
  • 属性及其底层对象的转换,由相应的属性驱动程序执行(每个属性类型一个驱动程序)。

每个属性的驱动程序是从驱动程序表中选择的,可以按属性类型(存储时)或相应的 DOM_Element 名称(检索时)。驱动程序表由XmlDrivers_DocumentStorageDriver::AttributeDrivers()XmlDrivers_DocumentRetrievalDriver::AttributeDrivers()方法创建。

然后将持久性文档写入文件(或从文件中读取)。在标准持久性存储和 FSD 包中包含用于将持久性文档写入/读取到文件中的类。在 XML 持久性中,改用 LDOMParserLDOM_XmlWriter

通常,包含文档存储和检索驱动程序的库在运行时由插件机制加载。为了在 XML Persistence 中支持这一点,在XmlDrivers包中有一个插件XmlPlugin和一个Factory()方法。此方法将传递的 GUID 与已知的 GUID 进行比较,并返回相应的驱动程序,如果 GUID 未知,则生成异常。

应用程序定义文档存储或检索所需的 GUID 以及应该在哪个库中找到它。这取决于文档格式和应用程序资源。在 StdResource 单元中可以找到用于 XML 持久性和标准持久性的资源。它们是为 XmlOcaf 文档格式编写的。

属性驱动程序

对于一组标准 OCAF 属性中的每个瞬态属性,都有一个用于 XML 持久性的属性驱动程序,但从不存储的属性类型除外(纯瞬态)。标准OCAF 属性收集在六个包中,它们的驱动程序也遵循此分布。属性T*_*的驱动程序称为XmlM*_*。属性的瞬时形式和持久形式之间的转换是通过属性驱动程序的两个方法Paste()来执行的。

XmlMDF_ADriver是所有属性驱动程序的根类。

在存储/检索过程的开始,每个属性驱动程序的一个实例被创建并附加到实现为XmlMDF_ADriverTable的驱动程序表中。在 OCAF 数据存储期间,根据属性类型从驱动程序表中检索属性驱动程序。在检索步骤中,创建链接DOM_Elements名称和属性驱动程序的数据映射,然后通过DOM_Element限定标记名称在该映射中查找属性驱动程序。

每个瞬态属性都保存为带有属性和可能的​​子节点的DOM_Element (OCAF 属性的根元素)。根元素的名称可以在属性驱动程序中定义为传递给基类构造函数的字符串。默认值为属性类型名称。同样,可以为每个属性设置名称空间前缀。没有默认值,但可以传递 NULL 或空字符串来存储没有命名空间前缀的属性。

基本类XmlMDF_ADriver支持通过方法WriteMessage(const TCollection_ExtendedString&)报告错误。它将消息字符串发送到其消息驱动程序,该消息驱动程序在构造函数中使用由文档存储/检索驱动程序从应用程序传递的Handle(CDM_MessageDriver)进行初始化。

XML 文档结构

每个 XML 文档都有一个根元素,它可能具有属性并包含其他节点。在 OCAF XML Documents 中,根元素被命名为“document”,并且具有属性“format”以及用于生成文件的 OCAF Schema 的名称。标准 XML 格式是“XmlOcaf”。以下元素是 <document> 的子元素,并且应该是作为其子元素的唯一条目,按特定顺序排列。顺序是:

  • 元素信息——包含标识 OCAF XML 文档的格式版本和其他参数的字符串。通常,持久性算法使用元素下的数据来正确检索和初始化 OCAF 文档。该数据还包括版权字符串。
  • 元素注释——由无限数量的包含必要注释字符串的 <comment> 子元素组成。
  • 元素标签——文档数据结构的根标签,XML 属性“tag”等于 0。它包含所有 OCAF 数据(标签、属性)作为 XML 元素树。每个子标签都由一个标签(正整数)标识,该标签为标签的所有子标签定义了一个唯一键。每个标签都可以包含任意数量的表示 OCAF 属性的元素(请参阅下面的 OCAF 属性表示)。
  • 元素形状——包含 BRep 格式的几何和拓扑实体。这些实体被写在元素 <label> 下的 OCAF 属性引用。如果文档中没有形状,则此元素为空。仅当DocumentStorageDriver将属性驱动程序XmlMNaming_NamedShapeDriver添加到驱动程序表时才会输出。

OCAF 属性表示

在 XML 文档中,OCAF 属性是其名称标识 OCAF 属性类型的元素。这些元素可能具有简单(字符串或数字)或复杂(子元素)结构,具体取决于 OCAF 属性的体系结构。OCAF 属性的每个 XML 类型都拥有一个唯一的正整数“id”XML 属性,用于标识整个文档中的 OCAF 属性。为了确保“id”的唯一性,属性名称“id”被保留,仅用于指示和标识可能从 OCAF XML 文档的其他部分引用的元素。对于每个标准 OCAF 属性,其 XML 名称与瞬态数据模型中的 C++ 类的名称相匹配。一般OCAF属性的XML名称可以在对应的属性驱动中指定。

生成的 XML 文件示例

以下示例是 XML 文件中的示例文本,该文件通过存储具有两个标签(0: 和 0:2)和两个属性的 OCAF 文档 – TDataStd_Name(在标签 0:上)和TNaming_NamedShape(在标签 0:2 上)。<shapes> 部分内容被省略号替换。

<?xml version=“1.0” encoding=“UTF-8”?>
<document format=“XmlOcaf” xmlns=“http://www.opencascade.org/OCAF/XML” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=“http://www.opencascade.org/OCAF/XML http://www.opencascade.org/OCAF/XML/XmlOcaf.xsd”>
<info date=“2001-10-04” schemav=“0” objnb=“3”>
<iitem>Copyright: Open Cascade, 2001</iitem>
<iitem>STORAGE_VERSION: PCDM_ReadWriter_1</iitem>
<iitem>REFERENCE_COUNTER: 0</iitem>
<iitem>MODIFICATION_COUNTER: 1</iitem>
</info>
<comments/>
<label tag=“0”>
<TDataStd_Name id=“1”>Document_1</TDataStd_Name>
<label tag=“2”>
<TNaming_NamedShape id=“2” evolution=“primitive”>
<olds/>
<news>
<shape tshape=“+34” index=“1”/>
</news>
\<shapes\>
</shapes>
</document>

XML 模式

XML Schema 定义文档的类。

OCAF XML 文档的完整结构被描述为一组 XML W3C Schema 文件,其中包含所有 XML 元素类型的定义。提供的定义不能被覆盖。如果任何应用程序定义了新的持久性模式,它可以使用现有 XSD 文件中的所有定义,但如果它创建新类型或重新定义现有类型,则必须在其他名称空间下完成定义。

还有其他声明 XML 数据的方法,不同于 W3C Schema,并且应该可以使用它们来表达我们 XML 数据模型的特定结构和约束的能力。但是,必须注意 W3C Schema 是声明的主要格式,因此,它是开放 CASCADE 技术未来改进所支持的格式,包括使用 OCAF XML 持久性开发特定应用程序。

Schema 文件 (XSD) 有两个目的:

  • 记录 OCAF 生成的文件的数据格式;
  • 当文件被外部(非 OCAF)应用程序使用时验证文件,例如生成报告。

OCAF XML Persistence 算法在保存和恢复 XML 文档时不使用模式定义。在处理每种类型的数据时,都会进行内部检查以确保有效性。

命名空间管理

XML 格式和 XML OCAF 持久性代码都是可扩展的,因为每个新开发都可以重用以前项目中创建的所有内容。对于 XML 格式,通过将 XML 对象(元素)的名称分配给不同的 XML 命名空间来支持这种可扩展性。因此,在不同项目(不同持久性库中)中定义的 XML 元素可以很容易地组合成相同的 XML 文档。一个示例是作为标准OCAF XML 持久性[文件 XmlXcaf.xsd]的扩展而构建的 XCAF XML 持久性。为了正确管理命名空间,有必要:

  • 在描述格式的新 XSD 文件中定义targetNamespace
  • 声明(在XSD文件中)targetNamespace 中的所有元素和类型都没有命名空间前缀;所有其他元素和类型都使用适当的前缀(例如“ocaf:”)。
  • 使用方法XmlDrivers_DocumentStorageDriver::AddNamespace添加(在新的DocumentStorageDriver中)带有前缀的targetNamespace。对新持久性使用的所有名称空间对象执行相同的操作,“ocaf”名称空间除外。
  • 将targetNamespace的命名空间前缀(在每个 OCAF 属性驱动程序中)传递给XmlMDF_ADriver的构造函数。

TObj 包

介绍

本文档描述了包 TObj,它是 Open CASCADE 应用程序框架 (OCAF) 的附加组件。

该包提供了一组类和辅助工具,有助于在低级 OCAF 数据结构之上创建面向对象的数据模型。这包括:

  • 表示数据对象的类的定义。数据对象使用原始 OCAF 属性存储其数据,利用 OCAF 机制进行撤消/重做和持久性。同时,它们提供了对纯 OCAF 文档结构(标签/属性)的更高层次的抽象。
  • 将数据模型组织为对象的分层(树状)结构。
  • 支持一个模型内或不同模型之间的对象之间的交叉引用。在跨模型引用的情况下,模型应该分层依赖。
  • 用于在 OCAF 文件中存储TObj对象的持久性机制,它允许存储和检索派生类型的对象,而无需编写额外的代码来支持持久性。

本文档描述了基于 TObj 的数据模型的逻辑和物理组织的基本原则以及实现表示模型对象的类的典型方法。

适用性

TObj数据模型的主要目的是使用OCAF提供的现有功能(撤消/重做和持久性)快速开发应用程序的面向对象数据模型,而无需从头开始重新开发此类功能。

与使用裸 OCAF(在标签和属性级别)相反,TObj 有助于处理更接近应用程序域的更高级别的抽象。当应用程序数据自然地以分层结构组织时,它最有效,并且对于属于模型不同部分的对象之间存在依赖关系的复杂数据模型特别有用。

应该注意的是,TObj对于表示在数据结构的每个级别包含有限数量的对象(通常少于 1000 个)的数据结构是有效的。由于 OCAF 文档的基于列表的组织,更多的对象会导致性能问题。因此,对于包含大量统一对象的数据模型或其子部分,建议使用其他存储方法,例如数组。但是,这些方法可以结合使用TObj来表示模型的高级结构。

TObj 模型

TObj 模型结构

TObj数据模型中,数据与管理它们的接口分离。

需要强调的是,TObj包只定义了模型和对象的接口和基本结构,而特定应用程序的模型的实际内容和结构是由其继承自TObj类的特定类定义的。实现可以添加自己的功能,甚至更改默认行为和数据布局,但不建议这样做。

从逻辑上讲,TObj数据模型表示为模型对象树,上层对象通常是其他对象的集合(称为分区,由类TObj_Partition表示)。模型的根对象称为主分区,由模型本身维护。此分区包含一个称为其子对象的子对象列表,每个子对象可能包含其自己的子对象(根据其类型),等等。

图片[28]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

TObj 数据模型

由于TObj数据模型基于 OCAF(Open CASCADE Application Framework)技术,因此将其数据存储在底层的 OCAF 文档中。OCAF 文档由称为标签的项目树组成。每个标签都有一些以属性的形式附加到它的数据,并且可能包含任意数量的子标签。每个子标签都由称为标签的序号标识。从文档根开始的标签及其父标签号的完整序列构成了标签的完整条目,它唯一地标识了它在文档中的位置。

一般TObj数据模型的OCAF树的结构与模型的逻辑结构相对应,可以如下图所示:

图片[29]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

TObj 数据模型映射到 OCAF 文档

模型的所有数据都存储在 OCAF 文档的根标签 (0:1) 中。属性TObj_TModel位于此根标签中。它存储TObj_Model类型的对象。该对象用作访问数据模型的所有数据和功能的主要界面工具。

在简单的情况下,应用程序所需的所有数据都可能包含在单个数据模型中。此外,TObj提供了在多个相互连接的数据模型之间分配数据的可能性。这对于处理大量数据的应用程序特别有用。因为一次只将当前操作所需的数据加载到内存中。假设模型具有分层(树状)结构,其中子模型的对象可以引用父模型的对象,反之亦然。如果确保模型加载和关闭的正确顺序,TObj类将自动维护对象之间的引用。

数据模型基本特征

描述数据模型的类TObj_Model提供以下功能:

  • 从文件中或在文件中加载和保存模型(方法LoadSave
  • 从内存中关闭和删除模型(方法Close
  • 此模型的持久性存储的完整文件名的定义(方法GetFile
  • 用于在分区中组织数据对象和迭代对象的工具(方法GetObjectsGetMainPartitionGetChildrengetPartitiongetElementPartition
  • 为模型对象赋予唯一名称的机制
  • 模型的复制(克隆)(方法NewEmptyPaste
  • 支持早期模型格式以正确转换从应用程序早期版本写入的文件加载的模型(方法GetFormatVersionSetFormatVersion
  • 必要时检查和更新模型的接口(方法Update
  • 在一个应用程序中支持多个数据模型。对于此功能,使用 OCAF 多事务管理器、数据模型的唯一名称和 GUID(方法GetModelNameGetGUID

模型持久性

任何 OCAF 模型的持久表示都包含在 XML 或二进制文件中,这些文件由GetFormat方法返回的格式字符串定义。默认实现使用二进制 OCAF 文档格式 ( BinOcaf )。另一种可用格式是XmlOcafTObj_Model类声明并提供了两个虚拟方法的默认实现:

virtual Standard_Boolean Load ( const char * theFile);
virtual Standard_Boolean SaveAs ( const char * theFile);

它从 OCAF 文件或在 OCAF 文件中检索和存储模型。后代应定义以下受保护的方法来支持加载和保存操作:

virtual Standard_Boolean initNewModel ( const Standard_Boolean IsNew);

此方法在创建新模型或从文件加载后由Load调用;其目的是执行模型的必要初始化(例如创建必要的顶级分区、由于版本更改导致的模型更新等)。请注意,如果指定的文件不存在,方法Load将创建一个新文档并使用参数True调用initNewModel。如果文件已正常加载,则传递参数False 。因此,通过使用空字符串或不存在文件的路径作为参数调用Load来创建一个新的空TObj模型。

如果模型已成功检索(或创建新模型),则Load方法返回True ,如果无法加载模型,则返回False 。如果在初始化(模型检索或创建)期间没有检测到错误,则为模型的所有对象调用虚拟方法AfterRetrieval 。此方法在模型初始化后立即初始化或更新对象。当一些对象数据应该从 OCAF 属性导入到可以在 OCAF 事务机制之外更改的瞬态字段时,它可能很有用。这些字段可以存储到 OCAF 属性中,以便在保存操作期间保存到持久存储中。

为避免内存泄漏,TObj_Model类析构函数调用Close方法,该方法清除 OCAF 文档并在模型被销毁之前从内存中删除所有数据。

对于TObj数据模型的 XML 和二进制持久性,相应的驱动程序在BinLDriversBinMObjXmlLDriversXmlMObj包中实现。这些包包含来自TObj包的模型、模型对象和自定义属性的检索和存储驱动程序。这些模式支持标准 OCAF 和TObj属性的持久性。这对于简单数据模型的实现来说已经足够了,但在某些情况下,添加特定的 OCAF 属性以方便存储特定于应用程序的数据是合理的。在这种情况下,应该使用标准的 OCAF 机制来扩展模式。

访问模型中的对象

模型中的所有对象都存储在主分区中并由迭代器访问。要访问所有模型对象,请使用:

virtual Handle( TObj_ObjectIterator ) GetObjects () const ;

此方法返回模型中存储的所有对象的递归迭代器。

virtual Handle( TObj_ObjectIterator ) GetChildren () const ;

此方法返回主分区的子对象的迭代器。使用以下方法获取主分区:

Handle( TObj_Partition ) GetMainPartition() const ;

要接收特定类型AType对象的迭代器,请使用以下调用:

GetMainPartition()->GetChildren( DEFAULT_TYPE (AType));

为后代类提供了一组受保护的方法来处理分区:

virtual Handle( TObj_Partition ) getPartition ( const TDF_Label , const Standard_Boolean theHidden) const ;

此方法在文档的指定标签中返回(如果需要,创建)一个分区。可以将分区创建为隐藏(TObj_HiddenPartition类)。隐藏分区可用于区分用户在浏览应用程序中的模型时不应该看到的数据。

以下两种方法允许在文档中指定标签(第二种方法的主分区标签)的子标签中获取(创建)分区并使用给定名称:

virtual Handle( TObj_Partition ) getPartition ( const TDF_Label , const Standard_Integer theIndex, const TCollection_ExtendedString & theName, const Standard_Boolean theHidden) const ;
virtual Handle( TObj_Partition ) getPartition ( const Standard_Integer theIndex, const TCollection_ExtendedString & theName, const Standard_Boolean theHidden) const ;

如果开启了默认的对象命名和名称注册机制,则可以通过其唯一名称在模型中找到该对象:

Handle ( TObj_Object ) FindObject ( const Handle ( TCollection_HExtendedString )& theName, const Handle ( TObj_TNameContainer )& theDictionary) const ;

自有模型数据

模型对象可以将自己的数据存储在其主分区的数据标签中,但是,没有标准的 API 用于设置和获取这些数据类型。后代可以使用标准 OCAF 方法添加自己的数据。枚举 DataTag 在TObj_Model中定义,以避免此类及其后代使用的数据标签冲突,类似于对象(见下文)。

对象命名

TObj_Model的基本实现提供了默认的命名机制:所有对象都必须具有唯一的名称,这些名称会自动注册到数据模型字典中。字典是一个TObj_TNameContainer属性,其实例位于模型根标签中。如有必要,开发人员可以在特定分区中添加多个字典,在正确的名称字典中提供名称注册,并在从文件加载文档后恢复名称映射。要忽略名称注册,必须重新定义TObj_Object类的方法SetNameAfterRetrieval并跳过对象名称的注册。对命名机制使用以下方法:

Standard_Boolean IsRegisteredName ( const Handle ( TCollection_HExtendedString )& theName, const Handle ( TObj_TNameContainer )& theDictionary) const ;

如果对象名称已在指定(或模型)字典中注册,则返回True 。

void RegisterName ( const Handle ( TCollection_HExtendedString )& theName, const TDF_Label & theLabel, const Handle ( TObj_TNameContainer )& theDictionary ) const ;

使用 OCAF 文档中对象所在的指示标签注册对象名称。请注意,对象的方法SetName的默认实现会自动注册新名称(如果该名称尚未为任何其他对象注册)

void UnRegisterName ( const Handle ( TCollection_HExtendedString )& theName, const Handle ( TObj_TNameContainer )& theDictionary ) const ;

从字典中注销名称。当从模型中删除对象时, TObj模型对象的名称将从字典中删除。

Handle( TObj_TNameContainer ) GetDictionary() const ;

返回模型字典的默认实例(位于模型根标签处)。默认实现仅适用于一个字典。如果模型对象需要有多个字典,建议重新定义TObj_Object的相应虚拟方法,该方法返回应注册对象名称的字典。

交易机制API

TObj_Model类提供了事务机制的 API(OCAF 支持):

Standard_Boolean HasOpenCommand() const ;

如果命令事务打开,则返回 True

void OpenCommand() const;

打开一个新的命令事务。

void CommitCommand() const;

提交命令事务。什么都不做 如果没有打开的命令事务。

void AbortCommand() const;

中止命令事务。如果没有打开的 Command 事务,则不执行任何操作。

Standard_Boolean IsModified() const ;

如果模型文档具有修改状态(上次保存后有更改),则返回 True

void SetModified( const Standard_Boolean );

强制更改修改状态。对于多个TObj_Model文档中的事务同步,请使用TDocStd_MultiTransactionManager类。

模型格式和版本

TObj_Model为后代类提供了一种通过选择用于存储或检索操作的模式来控制持久文件格式的方法。

virtual TCollection_ExtendedString GetFormat () const ;

返回指示当前持久机制的字符串TObjBinTObjXml 。默认值为TObjBin。由于开发的应用程序功能的演变,其数据模型的内容和结构因版本而异。目标包提供了支持向后版本兼容性的基本机制,这意味着较新版本的应用程序将能够读取由先前版本创建的数据模型文件(但反之亦然),并且数据丢失最少。对于每种类型的数据模型,数据格式的所有已知版本都应按递增顺序枚举,并随着模型格式的每次更改而递增。模型格式的当前版本存储在模型文件中,可以在检索时进行检查。

Standard_Integer GetFormatVersion() const ;

返回存储在模型文件中的格式版本

void SetFormatVersion( const Standard_Integer theVersion);

定义用于保存的格式版本。

在加载模型时,从磁盘打开模型(在 OCAF 文档级别)后立即调用的方法initNewModel()提供了检查存储在该模型中的格式版本的特定代码。如果它比应用程序的当前版本旧,则可以执行数据更新。每个模型都可以有自己的特定转换代码,执行必要的数据转换以使其与当前版本兼容。

当转换结束时,模型提供的信使接口会通知用户(有关更多详细信息,请参阅消息传递章节),并更新模型版本。如果不支持数据模型的版本(它比当前版本新或太旧),加载操作应该会失败。版本更改后更新模型的程序可以直接在相应数据模型类的 C++ 文件中实现为静态方法,而不会将其暴露给应用程序的其他部分。这些代码可以使用直接访问模型和对象数据(属性)而不使用对象接口,因为数据模型 API 和对象类可能已经更改。

请注意,此机制旨在维护模型中存储的数据更改的版本兼容性,而不是针对数据文件的低级格式(例如特定 OCAF 属性的存储格式)的更改。如果数据文件的格式发生变化,则需要根据具体情况进行具体处理。

模型更新

以下方法用于模型更新,以确保在跨模型依赖的情况下其与其他模型的一致性:

virtual Standard_Boolean Update();

该方法通常在加载模型后调用。默认实现什么都不做并返回True

virtual Standard_Boolean initNewModel( const Standard_Boolean IsNew);

此方法执行模型初始化、检查和更新(如上所述)。

virtual void updateBackReferences( const Handle ( TObj_Object )& theObj);

从文件中检索模型后,从上一个方法调用此方法以更新指定对象的反向引用(有关详细信息,请参阅数据模型 – 对象关系章节)

模型复制

要在 OCAF 文档之间复制模型,请使用以下方法:

virtual Standard_Boolean Paste( Handle ( TObj_Model ) theModel,Handle ( TDF_RelocationTable ) theRelocTable = 0);

将当前模型粘贴到新模型。重定位表确保模型的几个部分共享的子数据的正确复制。它将相关类型的已处理原始对象的映射存储在其副本中。

virtual Handle( TObj_Model ) NewEmpty() = 0;

重新定义纯虚方法来创建模型的新空实例。

void CopyReferences ( const Handle ( TObj_Model )& theTarget, const Handle ( TDF_RelocationTable )& theRelocTable);

将参考从当前模型复制到目标模型。

消息传递

消息是使用Message包中的 Open CASCADE Messenger 组织的。messenger 存储为模型实例的字段,可以通过以下方法设置和检索:

void SetMessenger( const Handle ( Message_Messenger )&);
Handle( Message_Messenger ) Messenger() const ;

开发人员应该创建自己的绑定到应用程序用户界面的 Messenger 实例,并将其归因于模型以供将来使用。特别是,信使用于报告持久性机制中的错误和警告。每条消息都有一个唯一的字符串标识符(键)。所有消息密钥都存储在一个特殊的资源文件 TObj.msg 中。该文件应在应用程序开始时通过调用Message_MsgFile类的适当方法来加载。

模型对象

TObj_Object类提供了TObj模型对象重要特性的基本接口和默认实现。此实现定义了推荐给所有后代的基本方法,并提供了便利其使用的工具。

图片[30]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

TObj 对象层次结构

数据与接口分离

TObj数据模型中,数据与管理它们的接口是分开的。属于模型对象的数据以标准 OCAF 属性的形式存储在其根标签和子标签中。这允许使用标准 OCAF 机制来处理这些数据,并简化持久性机制的实现。

接口的实例作为管理对象数据的API(例如表示模型对象)存储在对象的根标签中,一般不带自己的数据。接口类根据应用程序按照与模型对象的自然层次结构相对应的层次结构进行组织。

在下面的文本中,术语“对象”用于表示接口类的实例或对象本身(接口和存储在 OCAF 中的数据)。

属性TObj_TObject的特殊类型用于在 OCAF 树中存储对象接口的实例。TObj_TObject是TObj_Object类型对象的简单容器。数据模型的所有对象(接口)都继承这个类。

图片[31]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

TObj 对象存储在 OCAF 标签上

基本功能

TObj_Object类提供了一些可以被后代继承(或者,如果需要,重新定义)的基本特性

  • 允许访问对象所属的模型(方法GetModel)和存储对象的 OCAF 标签(方法GetLabel)。
  • 支持对相同或另一个模型中的其他对象的引用(和反向引用)(方法getReferencesetReferenceaddReferenceGetReferencesGetBackReferencesAddBackReferenceRemoveBackReferenceReplaceReference
  • 提供包含子对象的能力,因为它是实际的分区对象(方法GetChildrenGetFatherObject
  • 通过分离用于各种数据的主标签的子标签并提供组织这些数据的工具,在 OCAF 结构中组织其数据(见下文)。单独存储的数据种类有:
    • 存储在GetChildLabel方法返回的标签中的子对象
    • 对GetReferenceLabel方法返回的标签中存储的其他对象的引用
    • 其他数据,对于所有对象都是通用的,并且特定于模型对象的每个子类型,都存储在GetDataLabel方法返回的标签中
  • 提供模型中所有对象的唯一名称(方法GetDictionaryGetNameSetName
  • 提供统一的方法来维护持久性(在宏DECLARE_TOBJOCAF_PERSISTENCEIMPLEMENT_TOBJOCAF_PERSISTENCE的帮助下在后代中实现)
  • 允许一个对象从 OCAF 文档中删除自己并检查依赖的对象是否可以根据反向引用删除(方法Detach
  • 实现对象识别和版本控制的方法
  • 使用 OCAF 撤消/重做机制管理对象交互(方法IsAliveAfterRetrievalBeforeStoring
  • 允许进行克隆(方法CloneCopyReferencesCopyChildrencopyData
  • 包含额外的位标志字(方法GetFlagsSetFlagsTestFlagsCl​​earFlags
  • 定义按等级对对象进行排序的接口(方法GetOrderSetOrder
  • 为后代提供了许多辅助方法来设置/获取标准属性值,例如 int、double、string、arrays 等。

可以通过以下方法从模型中接收对象:

static Standard_Boolean GetObj ( const TDF_Label & theLabel, Handle ( TObj_Object )& theResObject, const Standard_Boolean isSuper = Standard_False );

如果在指示的标签中找到对象,则返回True(如果isSuperTrue ,则在上层标签中)。

Handle( TObj_Object ) GetFatherObject ( const Handle ( Default_Type )& theType = NULL) const ;

返回当前对象的指定类型的父对象(如果类型为 NULL,则为直接父对象)。

数据布局和继承

只要数据对象与接口分离并存储在 OCAF 树中,就需要支持继承的功能。每个对象都有自己的数据和引用存储在 OCAF 树的标签中。所有数据都存储在主对象标签的子树中。如果需要从基类继承一个类,则后代类应该使用与其祖先不同的数据和引用标签。

因此,每个TObj类都可以在每个DataReferencesChild子标签中保留标签范围。保留范围由类范围中定义的枚举声明(分别称为 DataTag、RefTag 和 ChildTag)。每种类型的枚举项First通过父类对应枚举的Last项定义,从而保证标签号不重叠。枚举的Last项定义了该类保留的最后一个标记。枚举的其他项定义了用于存储对象的特定数据项的标签。参见TObj_Partition的声明类的例子。

TObj_Object类提供了一组辅助方法,供后代通过其标签号访问存储在子标签中的数据:

TDF_Label getDataLabel ( const Standard_Integer theRank1, const Standard_Integer theRank2 = 0) const ;
TDF_Label getReferenceLabel ( const Standard_Integer theRank1, const Standard_Integer theRank2 = 0) const ;

返回给定标签号 (theRank1) 的数据参考子标签中的标签。第二个参数 theRank2 允许访问下一级层次结构(第 Rank1 数据标签的第 Rank2 子标签)。当要存储的数据由同一类型的多个 OCAF 属性表示时,这很有用(例如,同质数据或引用的序列)。

get/set 方法允许轻松访问位于指定数据标签中的最广泛使用的数据类型(Standard_RealStandard_IntegerTCollection_HExtendedStringTColStd_HArray1OfRealTColStd_HArray1OfIntegerTColStd_HArray1OfExtendedString)的数据。例如,为实数提供的方法是:

Standard_Real getReal ( const Standard_Integer theRank1, const Standard_Integer theRank2 = 0) const ;
Standard_Boolean setReal ( const Standard_Real theValue, const Standard_Integer theRank1, const Standard_Integer theRank2 = 0, const Standard_Real theTolerance = 0.) const ;

提供了类似的方法来访问对其他对象的引用:

Handle ( TObj_Object ) getReference ( const Standard_Integer theRank1, const Standard_Integer theRank2 = 0) const ;
Standard_Boolean setReference ( const Handle ( TObj_Object ) & theObject, const Standard_Integer theRank1, const Standard_Integer theRank2 = 0);

addReference方法提供了一种将同质引用序列存储在一个标签中的简单方法。

TDF_Label addReference ( const Standard_Integer theRank1, const Handle ( TObj_Object ) &theObject);

请注意,虽然对其他对象的引用应由后代类根据对象的类型单独定义,但TObj_Object提供了以统一方式操作(检查、删除、迭代)现有引用的方法,如下所述。

持久性

TObj数据模型的持久性是在标准 OCAF 机制(定义必要插件、驱动程序等的模式)的帮助下实现的。这意味着存储/检索作为标准 OCAF 属性存储的所有数据的可能性。相应的处理程序被添加到TObj特定属性的驱动程序中。

为从TObj_Object继承的类提供了特殊工具,以添加新类型的持久性,而无需重新生成 OCAF 模式。TObj_Persistence类为此提供了基本方法:

  • 对象类型的自动运行时注册
  • 创建指定类型的新对象(注册类型之一)

TObj_Persistence.hxx文件中定义的两个宏必须包含在每个继承TObj_Object的模型对象类的定义中,以激活持久性机制:

DECLARE_TOBJOCAF_PERSISTENCE(类名,祖先名)

应包含在每个继承TObj_Object(hxx 文件)的类的声明的私有部分中。该宏向对象类添加了一个额外的构造函数,并声明了一个辅助(私有)类继承TObj_Persistence ,该类提供了创建适当类型的新对象的工具。

应包含在应保存和恢复的每个对象类的 .cxx 文件中。这对于抽象类型的对象是不需要的。该宏实现了前一个宏声明的函数,并创建了一个静态成员,该成员自动注册该类型以实现持久性。

当保存包含接口对象的属性TObj_TObject时,其持久性处理程序存储对象类的运行时类型。当类型恢复时,处理程序动态识别类型并使用TObj_Persistence提供的机制创建相应的对象。

对象名称

所有TObj模型对象都有名称,用户可以通过该名称引用该对象。在创建时,每个对象都会收到一个默认名称,该名称由对应于对象类型的前缀(更准确地说,前缀由对象所属的分区定义)和当前分区中对象的索引构成。用户可以更改此名称。模型中名称的唯一性由命名机制来保证(如果名称已经被使用,则不能归属于其他对象)。TObj包的这个默认实现与名称容器(字典)的单个实例一起用于对象的名称注册,并且在大多数简单项目中就足够了。如有必要,很容易重新定义几个对象方法(例如GetDictionary ()) 并负责容器的构造和初始化。

此功能由以下方法提供:

virtual Handle( TObj_TNameContainer ) GetDictionary() const ;

返回应该注册对象名称的名称容器。默认实现返回模型名称容器。

Handle( TCollection_HExtendedString ) GetName() const ;
Standard_Boolean GetName( TCollection_ExtendedString & theName) const ;
Standard_Boolean GetName( TCollection_AsciiString & theName) const ;

返回对象名称。如果未定义对象名称,则带有 in / out 参数的方法返回 False。

virtual Standard_Boolean SetName ( const Handle ( TCollection_HExtendedString )& theName ) const ;
Standard_Boolean SetName ( const Handle ( TCollection_HAsciiString )& theName) const ;
Standard_Boolean SetName( const Standard_CString theName) const ;

为对象赋予一个新名称,如果名称已成功赋予,则返回True 。如果名称已经归属于另一个对象,则返回 False。最后两种方法是第一种方法的捷径。

对象之间的引用

TObj_Object类允许创建对模型中其他对象的引用。此类引用描述了模型中的分层对象结构没有充分反映的对象之间的关系(父子关系)。

引用使用属性TObj_TReference在内部存储。该属性位于引用对象(称为master)的子标签中,并保持对被引用对象的主标签的引用。同时被引用对象可以保持对主对象的反向引用。

图片[32]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

对象关系

反向引用不是存储在 OCAF 文档中,而是作为对象的瞬态字段存储;它们是在从文件中恢复模型时创建的,并在操作参考时自动更新。TObj_TReference类允许存储来自不同TObj模型的对象之间的引用,从而促进对象之间复杂关系的构建。

使用引用最常用的方法是:

virtual Standard_Boolean HasReference( const Handle ( TObj_Object )& theObject) const ;

如果当前对象引用了指示的对象,则返回 True。

virtual Handle( TObj_ObjectIterator ) GetReferences ( const Handle ( Default_Type )& theType = NULL) const ;

返回对象引用的迭代器。可选参数theType限制引用对象的类型,如果它为 NULL,则不限制。

virtual void RemoveAllReferences();

从当前对象中删除所有引用。

virtual void RemoveReference( const Handle ( TObj_Object )& theObject);

删除对指定对象的引用。

virtual Handle( TObj_ObjectIterator ) GetBackReferences ( const Handle ( Standard_Type )& theType = NULL) const ;

返回对象反向引用的迭代器。参数 theType 限制主对象的类型,如果它为 NULL,则不限制。

virtual void ReplaceReference ( const Handle ( TObj_Object )& theOldObject, const Handle ( TObj_Object )& theNewObject);

用对 theNewObject 的引用替换对 theOldObject 的引用。Handle theNewObject 可能为 NULL 以删除引用。

virtual Standard_Boolean RelocateReferences ( const TDF_Label & theFromRoot, const TDF_Label & theToRoot, const Standard_Boolean theUpdateackRefs = Standard_True );

将所有对theFromRoot的后代标签的引用替换为对theToRoot下等效标签的引用。如果结果引用不指向TObj_Object ,则返回False。如果 theUpdateackRefs 为True ,则更新反向引用。

virtual Standard_Boolean CanRemoveReference ( const Handle ( TObj_Object )& theObj) const ;

如果可以删除引用并且主对象将保持有效(引用) ,则返回True 。如果没有引用对象(强引用)主对象无效,则返回False 。这会影响从模型中删除对象的行为——如果无法删除引用,则不会删除被引用的对象,或者将删除被引用的对象和主对象(取决于方法Detach中的删除模式)

对象的创建和删除

建议从TObj_Object继承的所有对象都应实现相同的创建和删除方法。

TObj数据模型的对象不能独立于模型实例而创建,只要它将对象数据存储在 OCAF 数据结构中即可。因此,不能直接创建对象类,因为它的构造函数是受保护的。

相反,每个对象都应该提供一个静态方法Create (),它接受模型,带有标签,该标签存储对象和正确定义对象所需的其他类型相关参数。此方法在指定标签中创建一个带有其数据(一组 OCAF 属性)的新对象,并返回对象接口的Handle。

提供方法Detach () 用于从 OCAF 模型中删除对象。对象数据从对应的OCAF标签中删除;但是,对象的Handle仍然有效。对象删除后唯一可用的操作是方法IsAlive () 检查对象是否已被删除,如果对象已被删除,则返回 False。

当从数据模型中删除对象时,该方法检查是否有对该对象的任何活动引用。对对象的引用进行迭代询问每个引用(主)对象是否可以删除引用。如果主对象可以取消链接,则删除引用,否则主对象也将被删除或被引用的对象将保持活动状态。此检查由Detach方法执行,但行为取决于删除模式TObj_DeletingMode

  • TObj_FreeOnly – 对象只有在空闲时才会被销毁,即没有其他对象对它的引用
  • TObj_KeepDepending – 如果没有来自主对象的强引用,则该对象将被销毁(所有引用都可以取消链接)
  • TObj_Force – 对象和所有对其有强引用的依赖主对象将被销毁。

最常用的对象删除方法是:

virtual Standard_Boolean CanDetachObject ( const TObj_DeletingMode theMode = TObj_FreeOnly );

如果可以使用指定的删除模式删除对象,则返回True 。

virtual Standard_Boolean Detach ( const TObj_DeletingMode theMode = TObj_FreeOnly );

如果可能,从文档中删除对象(根据指示的删除模式)。从已移除的对象中取消引用的链接。如果对象已成功删除,则返回True 。

对象数据的转换和复制

TObj_Object提供了许多特殊的虚拟方法来支持对象的复制。这些方法应在必要时由后代重新定义。

virtual Handle( TObj_Object ) 克隆 ( const TDF_Label & theTargetLabel, Handle ( TDF_RelocationTable ) theRelocTable = 0);

将对象复制到目标标签。新对象将具有其原始对象的所有引用。返回新对象的Handle(如果失败,则返回空Handle)。数据是直接复制的,但是名称是通过添加后缀*_copy*来改变的。要为副本分配不同的名称,请重新定义方法:

virtual HandleTCollection_HExtendedString)GetNameForClone(常量 HandleTObj_Object)&)常量

返回新对象副本的名称。如果副本将在另一个模型中或在具有自己的字典的另一个分区中,则返回相同的对象名称可能很有用。Clone方法使用以下公共方法进行对象数据复制:

virtual void CopyReferences ( const const Handle ( TObj_Object )& theTargetObject, const Handle ( TDF_RelocationTable ) theRelocTable);

将其引用添加到原始对象的副本中。

virtual void CopyChildren ( TDF_Label & theTargetLabel, const Handle ( TDF_RelocationTable ) theRelocTable);

将对象的子对象复制到目标子标签。

对象标志

每个TObj_Object实例存储一组位标志,便于存储分配给对象的辅助逻辑信息(对象状态)。枚举ObjectState中定义了几个典型的状态标志:

  • ObjectState_Hidden – 对象被标记为隐藏
  • ObjectState_Saved – 对象在磁盘上有(或应该有)相应的保存文件
  • ObjectState_Imported – 对象是从某处导入的
  • ObjectState_ImportedByFile – 对象已从文件导入,应更新以与其他对象具有正确的关系
  • ObjectState_Ordered – 分区包含可以排序的对象。

用户(开发人员)可以在后代类中定义任何新标志。要设置/获取对象,标志使用以下方法:

Standard_Integer GetFlags() const ;
void SetFlags( const Standard_Integer theMask );
Stadnard_Boolean TestFlags( const Standard_Integer theMask ) const ;
void ClearFlags( const Standard_Integer theMask = 0 );

此外,通用虚拟接口以一组位标志的形式存储对象类的逻辑属性。可以通过以下方法接收类型标志:

virtual Standard_Integer GetTypeFlags() const ;

默认实现返回枚举TypeFlags中定义的标志Visible。此标志用于为浏览模型的用户定义对象的可见性(请参阅类TObj_HiddenPartition)。应用程序可以添加其他标志。

分区

由类TObj_Partition(及其后代TObj_HiddenPartition )定义的特殊类型的对象用于将模型划分为层次结构。该对象代表其他对象的容器。每个TObj模型包含与模型对象放置在相同 OCAF 标签中的主分区,并用作对象树的根。隐藏分区是具有预定义隐藏标志的简单分区。

主要的分区对象方法:

TDF_Label NewLabel() const ;

分配并返回一个新标签以创建新的子对象。

void SetNamePrefix ( const Handle ( TCollection_HExtendedString )& thePrefix);

定义自动生成新创建对象名称的前缀。

Handle( TCollection_HExtendedString ) GetNamePrefix() const ;

返回当前名称前缀。

Handle( TCollection_HExtendedString ) GetNewName ( const Standard_Boolean theIsToChangeCount) const ;

如果 theIsToChangeCount 为True,则生成新名称并增加子对象的内部计数器。

Standard_Integer GetLastIndex() const ;

返回最后一个保留的子索引。

void SetLastIndex( const Standard_Integer theIndex );

设置最后一个保留索引。

辅助课程

除了模型和对象之外,TObj包还提供了一组辅助类:

  • TObj_Application – 定义支持TObj文档存在和操作的 OCAF 应用程序。
  • TObj_Assistant – 类为模型的保存和加载操作期间要使用的静态数据提供接口。特别是,在跨模型依赖的情况下,它允许将父模型上的信息传递给 OCAF 加载器,以便在加载依赖模型时正确解析引用。
  • TObj_TReference – OCAF 属性描述了TObj模型中对象之间的引用。该属性存储被引用模型对象的标签,并提供透明的跨模型引用。在运行时,这些引用是简单的Handle;在持久化模式下,跨模型引用由TObj_TReference属性的持久化机制自动检测和处理。
  • 以TObj_T…开头的其他类- 定义 OCAF 属性,用于存储特定于 TObj 的类和 OCAF 标签上的某些类型的数据。
  • 迭代器——一组实现TObj_ObjectIterator接口的类,用于对TObj对象进行迭代:

TObj迭代器层次结构如下所示:

图片[33]-OpenCasCade官方开发文档翻译(12)–OCAF-卡核

迭代器的层次结构

打包

TObj分布在以下包中:

  • TObj – 为基于 OCAF 的建模器定义实现TObj接口的基本类。
  • BinLDrivers , XmlLDrivers – TObj包的二进制和 XML 驱动
  • BinLPlugin, XmlLPlugin – 二进制和 XML 持久性插件
  • BinMObj、XmlMObj – 二进制和 XML 驱动程序,用于在 OCAF 文档中存储和检索特定TObj数据
  • TKBinL, TKXmlL – 二进制和 XML 持久性工具包

词汇表

  • 应用程序– 一个文档容器,其中包含包含所有应用程序数据的所有文档。
  • 应用程序数据——由应用程序产生的数据,而不是引用它的数据。
  • 数据的关联性——将对一个文档所做的修改传播到引用该文档的其他文档的能力。修改传播是:
    • 单向,即从被引用文件到引用文件,或
    • 双向,从引用到被引用的文档,反之亦然。
  • 属性——应用程序数据的容器。属性附加到数据框架层次结构中的标签。
  • Child – 从另一个标签创建的标签,根据定义,它是父亲标签。
  • 复合文档——一组相互依赖的文档,通过外部引用相互链接。这些引用提供了数据的关联性。
  • 数据框架——一种树状数据结构,在 OCAF 中,是一个标签树,数据以属性的形式附加到它们上面。这个标签树可以通过TDocStd_Document类的服务来访问。
  • 文档——数据框架的容器,它授予对数据的访问权限,并且反过来由应用程序包含。文档还允许您:
    • 管理修改,提供撤消和重做功能
    • 管理命令事务
    • 更新外部链接
    • 管理保存和恢复选项
    • 存储软件扩展名。
  • 驱动程序——一个抽象类,它定义了与系统的通信协议。
  • 条目——一个包含标签标签列表的 ASCII 字符串。例如:
    0:3:24:7:2:7
  • 外部链接——从一个数据结构到另一个文档中的另一个数据结构的引用。要正确存储这些引用,标签还必须包含外部链接属性。
  • 父亲– 一个标签,从该标签中创建了其他标签。根据定义,其他标签是该标签的子标签。
  • 框架——一组协作类,使设计能够重新用于给定类别的问题。该框架通过将应用程序的体系结构分解为抽象类来指导应用程序的体系结构,每个抽象类具有不同的职责并以预定义的方式进行协作。应用程序开发人员通过以下方式创建一个专门的框架:
    • 定义从这些抽象类继承的新类
    • 组合框架类实例
    • 实现框架所需的服务。

在 C++ 中,应用程序行为是在这些派生类中重新定义的虚函数中实现的。这称为覆盖。

  • GUID – 全球通用 ID。用于唯一标识对象的 37 个字符的字符串。例如:
    2a96b602-ec8b-11d0-bee7-080009dc3333
  • 标签——数据框架中的一个点,它允许通过属性将数据附加到它上面。它有一个条目形式的名称,用于标识它在数据框架中的位置。
  • 已修改标签– 包含其数据已被修改的属性。
  • 引用键——一个不变的引用,它可以引用应用程序中使用的任何类型的数据。在它的瞬态形式下,它是数据框架中的一个标签,数据以属性的形式附加在它上面。在其持久形式中,它是标签的一个条目。它允许应用程序恢复当前会话或先前会话中的任何实体。
  • 资源文件——包含每个文档的模式名称列表以及该文档的存储和检索插件的文件。
  • ——数据框架的起点。这一点是框架中的顶部标签。它由 [0] 条目表示,并与您正在处理的文档同时创建。
  • 范围——依赖于给定标签的所有属性和标签的集合。
  • 标签列表——整数列表,用于标识标签在数据框架中的位置。此列表显示在条目中。
  • 拓扑命名——拓扑实体的系统引用,使得这些实体在它们所属的模型经过几个建模步骤后仍然可以被识别。换句话说,拓扑命名允许您通过建模过程中的步骤跟踪实体。在编辑和重新生成模型时需要这种引用,可以看作是旧版本模型中实体的标签和名称属性到新版本中相应实体的映射。请注意,如果模型的拓扑在建模期间发生变化,则此映射可能不会完全重合。例如,布尔运算可能会分割边。
  • 拓扑跟踪——通过编辑和重新生成模型所采取的步骤跟踪模型中的拓扑实体。
  • 有效标签——在数据框架中,这是一个标签,它已经在重新计算序列的范围内重新计算,并且包括包含要重新计算的特征的标签。考虑先添加圆角,然后添加突出特征的框的情况。出于重新计算的目的,仅使用每个施工阶段的有效标签。在重新计算圆角时,它们只是盒子和圆角的那些,而不是后来添加的突出特征。
© 版权声明
THE END
喜欢就支持一下吧
点赞6660 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容