概述
本教程介绍了 AMCAX STEP Reader(下称 Reader 或 StepReader)和 AMCAX STEP Writer(下称 Writer 或 StepWriter)的基本用法。 此外,本模块还提供了将读取数据转换为 Mesh 格式的 AMCAX STEP Mesh Reader(下称 StepMeshReader)。 目前没有提供以 STEP 协议导出 Mesh 数据的接口。
所需知识
开发人员需具备现代 C++ 编程、3D 几何建模和 B-Rep 拓扑结构的基础知识。AMCAX 内核及 StepReader / StepWriter 基于 C++17 标准开发,广泛应用 STL 容器与算法。
导入部分
引入头文件
StepReader
本示例程序中 Reader 部分只需要引入一个头文件:
Class for reading and translating STEP files into TopoShape objects.
StepMeshReader
若需要使用 Mesh 相关接口,则需要使用 StepMeshReader。StepMeshReader 在以下头文件中定义。StepMeshReader 是 Reader 的子类,在其基础上追加了 Mesh 相关功能。
Class for reading STEP file and convert solids / shells to mesh.
另外,StepMeshReader作为可以接受多种 Traits 的模板类型,如果未在编译参数中定义相关宏,也可通过如下方式在代码中启用对应 Traits 类型:
#define AMCAXMeshing_Enable_CommonTraits
创建 Reader 对象
StepReader
STEP Reader 不提供静态函数,所有操作均需基于对象。 构造时需要传入一个文件名 (std::string) 或输入流 (std::istream) 作为参数:
std::ifstream ifs("/path/to/input.step");
if (!ifs.is_open())
{
return 1;
}
Class for reading and translating STEP files into TopoShape objects.
定义 StepReader.hpp:29
或者
StepMeshReader
类似的,StepMeshReader 也提供了相同的构造器:
MeshReader reader(ifs);
Class for reading STEP file and convert solids / shells to mesh.
定义 StepMeshReader.hpp:26
或者
MeshReader reader("/path/to/input.step");
(可选)设置缩放
默认情况下,Reader 会以毫米为单位长度,对读取到的模型进行缩放操作。 可通过如下代码调整单位缩放设置:
reader.SetUnit(AMCAX::STEP::StepLengthUnit::PresetLengthUnit::NOSCALE);
在转换完成后,亦可以通过 StepReader::GetUnit() / StepMeshReader::GetUnit() 或 StepData::Factors() / StepMeshData::Factors() 获取 STEP 文件中提供的单位。
(可选)设置进度汇报
无论是对 Reader 或是对 StepMeshReader,加载进度都是通过回调函数汇报的。然而,其使用的参数稍有不同:
StepReader
一般来说,c1 表示已处理的图形数量,c2 表示总图形数。
reader.SetProgressCallback(
{
std::cout << " State: " << int(state) << std::endl;
if (c2.type == AMCAX::STEP::StepProgressCarrier::U64_TYPE && c2.payload.u64)
{
std::cout << " Carrier1: " << c1.payload.u64 << std::endl;
std::cout << " Carrier2: " << c2.payload.u64 << std::endl;
}
});
A class used for representing the states in the progress of the STEP process.
定义 StepProgress.hpp:54
A carrier for carrying data the progress of the STEP process.
定义 StepProgress.hpp:18
对于每个状态及其参数的含义,可以参考 <step/StepProgress.hpp>
StepMeshReader
StepMeshReader 的回调函数与 Reader 基本一致,仅 State 的类型有一点区别:
reader.SetProgressCallback(
{
std::cout << " State: " << int(state) << std::endl;
if (c2.type == AMCAX::STEP::StepProgressCarrier::U64_TYPE && c2.payload.u64)
{
std::cout << " Carrier1: " << c1.payload.u64 << std::endl;
std::cout << " Carrier2: " << c2.payload.u64 << std::endl;
}
});
Class used for reporting the state of the STEP process.
定义 StepMeshProgress.hpp:16
对于每个状态及其参数的含义,可以参考 <step/mesh/StepMeshProgress.hpp>
读取文件
在构建 Reader 或 StepMeshReader 对象后,便可以读取文件:
bool topo_success = reader.Read();
不管是对 Reader 还是对 StepMeshReader,这一步都将完成 STEP 至 TopoShape 的翻译工作。 Read() 方法阻塞当前线程直至解析完成。受到软硬件以及输入文件的复杂度差异影响,运行时间会持续数毫秒到数十分钟不等。 若在前一步骤中使用 std::istream 作为参数,用户需要确保在这一函数执行完毕前该输入流的生命周期不会失效。 如果读取失败,Read() 会返回 false 。
提前获得已经完成的结果
虽然 Read() 函数会阻塞当前进程,但有一部分数据在其执行早期或中期就已经准备完毕,可以提前获取。 用户可以将 AMCAX::STEP::DataCallback 作为参数传递给 Read() 函数,该 DataCallback 会在相关数据准备完毕时被调用。
const AMCAX::STEP::ProductPtr p,
size_t rep_idx)
{
switch (e)
{
{
break;
}
{
break;
}
{
break;
}
default:;
}
});
DataEvent
定义 StepProgress.hpp:134
@ ProductReady
This event indicates that the Product tree is fully constructed, all the data, other than Shapes,...
定义 StepProgress.hpp:142
@ LabelReady
This event comes with 2 parameters. he first one is the pointer to the StepData object....
定义 StepProgress.hpp:152
@ ShapeReady
This event comes with 2 parameters. The first one is the pointer to the StepData object....
定义 StepProgress.hpp:147
需要注意的是,由于 callback 函数是直接在执行线程中被调用的,如果用户提供的函数定义过于复杂,可能会影响到后续计算的执行。 如果用户在 Read() 函数阻塞期间对获取到的数据执行 std::move 或 StepData::AddShape 等操作,可能会产生未定义的结果。
(仅 Mesh)将 TopoShape 转换为 Mesh 格式
完成 STEP 的翻译工作之后,便可以进行 TopoShape 至 Mesh 的转换工作。
bool mesh_success = reader.ToMesh();
其中,ToMesh() 接受一个可选的 double 类型参数作为网格化的参数。
创建用于储存数据的容器
StepReader
本模块的读、写子模块均使用 AMCAX::STEP::StepDataList 传输数据:
AMCAX::STEP::StepDataList shapes;
AMCAX::STEP::StepData 类型由 <step/StepData.hpp> 头文件定义,但一般情况下无需显式引入该头文件。
StepMeshReader
类似的,StepMeshReader 作为 Reader 的扩展,使用 AMCAX::STEP::StepMeshDataList 类型传输数据:
AMCAX::STEP::StepMeshDataList<AMCAX::Meshing::Mesh::TriSoupTraits_Coord> shapes;
定义于 <step/mesh/StepMeshData.hpp>。
获取结果
不管是对 Reader 还是对 StepMeshReader,获取翻译结果的代码都是相同的:
shapes = reader.GetShapes();
StepReader
根据文件的内容和转换的情况,shapes 中会包含 0 或更多 StepData 对象。 StepData 对应 STEP 文件中的 Product 实体,是用来传输 STEP 数据的类。 因此, StepData.hpp 中也定义了一个 ProductPtr,作为 std::shared_ptr<StepData> 的别名。 此类主要包含以下成员变量:
- 当前 Product 的名称、描述信息,通过 ProductName() 与 Description() 获取;
- 当前 Product 的 Child Product,即当前 StepData 节点的子树,数量为 0 或更多,通过 Children() / ChildAt(size_t) 获取;
- 当前 Product 的 TopoShape,数量为 0 或更多,通过 Shapes() / ShapeAt(size_t) 获取;
- 当前 Product 下针对每个 TopoShape 的属性(如名称、颜色)和缩放信息,通过 PropertyChains() 与 Factors() 获取;
- 当前 Product 装配至上级 Product 时使用的位移、旋转信息,通过 Location() 获取;
如果多个 Product 节点之间通过 Children() 形成树状结构,则表示 Product 之间的装配。可以遍历先前获取到的 StepDataList,判断每个 StepData 对象中 ChildrenSize() 是否全部为 0,来确认当前文件是否包含装配结构。 另外,对于包含装配结构的情况,由于同一个 Product 可以配合不同的位移、旋转信息,任意多次装配进不同的上级 Product,StepData 为了减少重复信息,提供了 Shadow 节点的概念。
Shadow 节点
Shadow 节点被定义为仅包含位移、旋转信息(Location)的节点。用户可以通过 StepData::IsShadow() 判断当前节点是否为 Shadow 节点,可以通过 StepData::Target() 获取实际储存数据的节点。 虽然没有直接存储图形数据,如果对 Shadow 节点调用 StepData::Shapes() / StepData::Children() 等函数,会自动返回 Target 节点上的数据。 如果不需要使用 Shadow 节点相关特性,可以通过以下方法将整个 Product 树转换为普通节点:
void ConvertToNormal(ProductPtr& root)
{
if (root->IsShadow())
{
root->MarkNormal();
}
for(size_t i = 0; i < root->ChildrenSize(); ++i)
{
ConvertToNormal(root->ChildAt(i));
}
}
ProductPtr node = shapes[0];
ConvertToNormal(node);
StepMeshReader
结构、成员与 StepData 基本一致,但是增加了如下变量:
- 当前 Product 的 Mesh 数据,通过 Meshes() 获取。
(可选,仅 TopoShape)将树状 StepData 转换为一维数组
如果不需要保留 Product 的装配结构信息,可以将树状 Product 树状结构转换为一维数组,每个节点中有且只有 1 个 TopoShape,且没有子节点。
获取 sub-shape 的属性信息
获取到具体的 TopoShape 之后,可以通过 TopoIterator、TopoExplorer、TopoExplorerTool 等由 AMCAX 内核提供的接口获取其 sub-shape。 可以通过以下代码获取其名称等信息:
std::shared_ptr<AMCAX::STEP::StepData> treeroot = shapes[0];
const AMCAX::STEP::PropertyChain& props = treeroot->PropertyChains()[0];
exp.Next())
{
auto it = std::find_if(props.begin(), props.end(),
[&solid](const AMCAX::STEP::PropertyPair& pair)
{ return solid.IsSame(pair.first); });
if (it != props.end())
{
const StepPropertyUnit& property = it->second;
if (property.NameHasValue())
{ }
if (property.ColorHasValue())
{ }
}
}
用于遍历 B-Rep 结构的工具类
定义 TopoExplorer.hpp:14
AMCAX_API bool More() const
判断遍历器中是否还有形状未遍历
形状的基类,包含具有位置和方向信息的基础形状
定义 TopoShape.hpp:15
(仅 Mesh)获取 Mesh 信息
由于 Mesh 数据是由 TopoShape 转换而来的,因此当且仅当一个 StepMeshData 节点中包含 TopoShape 数据时,才可能出现对应的 Mesh 数据。 Mesh 的转换工作由 StepMesh 类完成,可以参考 <step/mesh/StepMesh.hpp> 获取更多信息。现阶段 StepMesh 只接受类型为 Solid 的 TopoShape。 参考以下代码,获取 Mesh 数据以及对应的 TopoShape,名称,颜色等信息:
std::shared_ptr<AMCAX::STEP::StepMeshData<AMCAX::Meshing::Mesh::TriSoupTraits_Coord>> root = shapes[0];
std::cout << meshes.size() << '\n';
std::vector<StepMesh<AMCAX::Meshing::Mesh::TriSoupTraits_Coord>>& meshes0 = root->MeshAt(0);
std::cout << meshes0.size() << '\n';
std::vector<StepMesh<AMCAX::Meshing::Mesh::TriSoupTraits_Coord>>& meshes1 = root->MeshAt(1);
std::cout << meshes1.size() << '\n';
std::vector<AMCAX::TopoShape> solidSet0;
{
solidSet0.push_back(exp.Current());
}
for (size_t solidIndex = 0; solidIndex < solidSet0.size(); ++solidIndex)
{
}
Class for reading STEP file and convert solid to mesh.
定义 StepMeshData.hpp:22
(仅 Mesh)将 StepMeshData 转换为 StepData
在部分情况下,也会出现需要将 StepMeshData 转换为基类 StepData、或是将 StepMeshDataList 转换为 StepDataList 的场景。 可以参考以下代码:
using MeshDataList = AMCAX::STEP::StepMeshDataList<AMCAX::Meshing::Mesh::TriSoupTraits_Coord>;
MeshDataList smdl = reader.GetShapes();
AMCAX::STEP::StepDataList sdl;
sdl.reserve(smdl.size());
for (auto& smdptr : smdl)
{
sdl.push_back(smdptr->ToStepData());
}
(仅 TopoShape)提取 StepData 中的 TopoShape
虽然上文中已经给出使用 StepData::Shape() 获取 TopoShape 的方法,但考虑到每个 StepData 对象中可能包含不止一个图形数据,且装配结构中的情况可能更为复杂,模块中也提供了额外的方法提供完整的 TopoShape 对象:
仅获取当前 StepData 储存的 TopoShape
for (std::shared_ptr<StepData> node : reader.GetShapes())
{
TopoShape cur_shape = node->OneShape( false);
}
获取当前 StepData 以及其所有 Children 组成的 TopoShape
for (std::shared_ptr<StepData> node : reader.GetShapes())
{
TopoShape cur_shape = node->OneShape( true);
}
获取完整 StepDataList 组成的 TopoShape
(仅 TopoShape) 导出部分
导入头文件
本示例程序中 Writer 部分只需要引入一个头文件,通过以下代码将该头文件加入程序:
Class for exporting TopoShape objects and related data to STEP files.
创建 Writer 对象
以下代码通过指定输出文件之文件名(路径)的方式创建并初始化一个 Writer 对象:
Class for exporting TopoShape objects and related data to STEP files.
定义 StepWriter.hpp:30
同 Reader 类似, Writer 可以接受输出流作为构造参数:
std::ofstream ofs("/path/to/output.step");
if (!ofs.is_open())
{
return 1;
}
注意:若指定输出文件已存在,将被覆盖。
(可选)设置缩放
只要输入的 TopoShape 符合输出条件,Writer 就会假设其单位长度为 1 毫米,并直接将其数值以不做缩放的方式输出至文件。 然而,假使该 TopoShape 创建时使用了其他单位,这会导致导出的图形尺寸不符。
虽然不会对输出的数值进行缩放,但可以通过以下代码调整 STEP 文件中定义的单位长度:
writer.SetUnit(AMCAX::STEP::StepLengthUnit::PresetLengthUnit::CENTIMETRE);
需要在导出图形数据之前完成该操作。
(可选)设置进度汇报
和 Reader 一样,Writer 也使用回调函数进行进度汇报,参考以下代码:
writer.SetProgressCallback(
{
std::cout << " State: " << int(state) << std::endl;
if (c2.type == AMCAX::STEP::StepProgressCarrier::U64_TYPE && c2.payload.u64)
{
std::cout << " Carrier1: " << c1.payload.u64 << std::endl;
std::cout << " Carrier2: " << c2.payload.u64 << std::endl;
}
});
进行导出
(可选)写文件头
为了在 STEP 文件头部添加必要的信息,用户需要首先调用:
这一步将在文件中输出 ISO 标准编号,应用协议版本,以及其它一些必要的实体。 为了保证文件格式,需要在 Init() 执行完毕后再将 TopoShape 传入 Writer。 不过,默认情况下,Writer 会自动执行 Init()。
写 TopoShape
无论是一次性将包含所有 TopoShape 的 AMCAX::STEP::StepDataList 传入 Writer,还是多次调用函数传入,都有一样的效果。 也就是说,除去出于展示需要而特别指定颜色的部分,以下两段代码的效果是一致的:
AMCAX::STEP::StepDataList shape_part_0 = {shapes.begin(), shapes.begin() + shapes.size() - 2};
std::shared_ptr<AMCAX::STEP::StepData> shape_part_1 = shapes[shapes.size() - 2];
writer.WriteShapes(shape_part_0);
writer.WriteShape(shape_part_1);
writer.WriteShape(shape_last);
或
writer.WriteShapes(shapes);
写 Label
AMCAXAf Label 是一种支持属性的图形格式,因此,直接将 Label 传递给 Writer,也可以导出带有属性的图形数据:
writer.WriteShape(root);
The class of Label
定义 Label.hpp:26
然而,由于 StepData 类同时储存 TopoShape 和 Label 两种数据,因此在将 StepData 对象传递给 Writer 时,需要明确告知 writer 使用何种数据:
auto node = std::make_shared<AMCAX::STEP::StepData>();
node->AddShape(root);
writer.WriteShape(node);
writer.WriteLabel(root);
完成导出并保存文件
默认情况下,StepWriter 在析构时才会执行真正的导出工作,并关闭输出流(当且仅当该 Writer 对象是使用文件路径创建的)。然而,也可以通过以下代码手动完成导出:
注意:如果再次对该 Writer 对象调用 Init() 函数,已输出的文件将会被覆盖。
简化接口
在 AMCAX::STEP::StepDataTool 类中,除了对 StepReader 产生的 StepDataList 操作的工具函数以外,亦提供了简化的接口提供导入、导出 TopoShape 的功能。 注意其中零件名称、装配、颜色、渲染等信息可能不会被保留。 其使用方式如下:
点击这里example01可获得 Reader 的完整源码,大家根据学习需要自行下载。 点击这里example02可获得 StepMeshReader 的完整源码,大家根据学习需要自行下载。
附录
#include <iostream>
void printProductName(const std::shared_ptr<AMCAX::STEP::StepData> node, int indent = 0)
{
for (int i = 0; i < indent; ++i)
{
std::cout << "| ";
}
if (node->IsShadow())
{
std::cout << "SHADOW: " << node->ProductName() << std::endl;
}
else
{
std::cout << node->ProductName() << std::endl;
for (const std::shared_ptr<AMCAX::STEP::StepData> child : node->Children())
{
printProductName(child, indent + 1);
}
}
}
void printSolidName(const std::shared_ptr<AMCAX::STEP::StepData> node, int indent = 0)
{
for (size_t i = 0; i < node->ShapesSize(); ++i)
{
const AMCAX::STEP::PropertyChain& props = node->PropertyChainAt(i);
SolidExp.Next())
{
auto it = std::find_if(props.begin(), props.end(),
[&solid](const AMCAX::STEP::PropertyPair& pair)
{ return solid.IsSame(pair.first); });
if (it != props.end() && (*it).second.NameHasValue())
{
std::string name = (*it).second.Name();
std::cout << '"' << name << std::endl;
}
}
}
for (const std::shared_ptr<AMCAX::STEP::StepData> child : node->Children())
{
printSolidName(child, indent + 1);
}
}
int main()
{
AMCAX::STEP::StepDataList shapes;
reader.SetUnit(AMCAX::STEP::StepLengthUnit::PresetLengthUnit::METRE);
bool topo_success = reader.Read();
if (!topo_success)
{
return -1;
}
shapes = reader.GetShapes();
for (std::shared_ptr<AMCAX::STEP::StepData> root : shapes)
{
printProductName(root);
printSolidName(root);
}
writer.SetUnit(AMCAX::STEP::StepLengthUnit::PresetLengthUnit::METRE);
writer.Init();
writer.WriteShapes(shapes);
writer.Done();
}