AMCAX Kernel 1.0.0.0
Loading...
Searching...
No Matches
A Step File Reading And Writing Example

Overview

This tutorial introduces the basic usage of AMCAX STEP Reader (hereafter referred to as Reader or StepReader) and AMCAX STEP Writer (hereafter Writer or StepWriter). In addition, this module provides AMCAX STEP Mesh Reader (hereafter StepMeshReader) for converting read data to Mesh format. Currently, there is no interface to export Mesh data in the STEP format.

Prerequisites

Developers should be familiar with modern C++ programming, 3D geometric modeling, and B-Rep topology. The AMCAX kernel and StepReader / StepWriter are developed based on the C++17 standard and make extensive use of STL containers and algorithms.

Import Section

Include Header Files

StepReader

In the Reader part of this example program, you only need to include one header file:

Class for reading and translating STEP files into TopoShape objects.

StepMeshReader

If you need to use Mesh-related interfaces, use StepMeshReader. StepMeshReader is defined in the following header file. It is a subclass of Reader with additional Mesh-related features.

Class for reading STEP file and convert solids / shells to mesh.

In addition, since StepMeshReader is a template class that can accept various Traits, if the relevant macro is not defined in the build options, you can enable the desired Traits type in your code as follows:

// Replace with other macros as needed
#define AMCAXMeshing_Enable_CommonTraits

Creating Reader Objects

StepReader

STEP Reader does not provide static functions; all operations are instance-based. The constructor takes a filename (std::string) or an input stream (std::istream) as a parameter:

std::ifstream ifs("/path/to/input.step");
if (!ifs.is_open())
{
return 1;
}
Class for reading and translating STEP files into TopoShape objects.
Definition StepReader.hpp:29

Or

AMCAX::STEP::StepReader reader("/path/to/input.step");

StepMeshReader

Similarly, StepMeshReader provides the same constructors:

MeshReader reader(ifs);
Class for reading STEP file and convert solids / shells to mesh.
Definition StepMeshReader.hpp:26

Or

MeshReader reader("/path/to/input.step");

(Optional) Set Scaling

By default, Reader treats millimeters as the unit and scales the imported model accordingly. You can adjust the scaling setting as follows:

reader.SetUnit(AMCAX::STEP::StepLengthUnit::PresetLengthUnit::NOSCALE);

After conversion, you can also obtain the unit information specified in the STEP file via StepReader::GetUnit(), StepMeshReader::GetUnit(), StepData::Factors(), or StepMeshData::Factors().

(Optional) Set Progress Callback

Both Reader and StepMeshReader support progress reporting via callback functions. However, their parameter types differ slightly.

StepReader

Usually, c1 represents the number of processed shapes, and c2 represents the total number of shapes.

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.
Definition StepProgress.hpp:54
A carrier for carrying data the progress of the STEP process.
Definition StepProgress.hpp:18

For each state and its parameters, refer to <step/StepProgress.hpp>.

StepMeshReader

The callback function for StepMeshReader is essentially the same, with a slightly different State type:

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.
Definition StepMeshProgress.hpp:16

For state and parameter meanings, refer to <step/mesh/StepMeshProgress.hpp>.

Reading Files

After constructing a Reader or StepMeshReader object, you can read the file:

bool topo_success = reader.Read();

For both Reader and StepMeshReader, this completes the translation from STEP to TopoShape. The Read() method blocks the current thread until parsing completes. Depending on hardware and input file complexity, runtime may range from milliseconds to dozens of minutes. If you use std::istream as a parameter in the previous step, ensure the stream remains valid until this function completes. If reading fails, Read() returns false.

Obtaining Partial Results Early

Although Read() blocks the process, some data may be available early or midway and can be accessed in advance. You can pass AMCAX::STEP::DataCallback as a parameter to Read(); this callback will be invoked when relevant data is ready.

bool topo_success = reader.Read([&reader](AMCAX::STEP::DataEvent e,
const AMCAX::STEP::ProductPtr p,
size_t rep_idx)
{
switch (e)
{
{
// Product tree is assembled;
// You can get results using reader.GetShapes().
// For all nodes, ProductName, Description, Location, Target, Children, and related interfaces are available.
// For Product nodes containing shape data, empty data is filled at this point.
break;
}
{
// A shape is ready.
// You can access the shape via p->ShapeAt(rep_idx),
// and its properties via p->PropertyChainAt(rep_idx).
// Data at p->FactorAt(rep_idx) and p->ShapeRepresentationAt(rep_idx) is also available.
break;
}
{
// A shape and property data (AMCAXAf Label format) are ready.
// Access via p->LabelAt(rep_idx).
// For the same TopoShape, LabelReady occurs after ShapeReady,
// and the p and rep_idx parameters are consistent across the two.
break;
}
default:;
}
});
DataEvent
Definition StepProgress.hpp:134
@ ProductReady
This event indicates that the Product tree is fully constructed, all the data, other than Shapes,...
Definition StepProgress.hpp:142
@ LabelReady
This event comes with 2 parameters. he first one is the pointer to the StepData object....
Definition StepProgress.hpp:152
@ ShapeReady
This event comes with 2 parameters. The first one is the pointer to the StepData object....
Definition StepProgress.hpp:147

Note: Since the callback is called directly in the execution thread, if your callback is too complex, it may affect subsequent computations. If you move or modify data (e.g. std::move, StepData::AddShape) within the Read() call, undefined behavior may occur.

(Mesh Only) Convert TopoShape to Mesh

After STEP translation is complete, you can convert TopoShape(s) to Mesh.

bool mesh_success = reader.ToMesh();

ToMesh() accepts an optional double parameter for meshing.

Creating Data Containers

StepReader

Both Reader and Writer modules use AMCAX::STEP::StepDataList for data transfer:

AMCAX::STEP::StepDataList shapes;

The type AMCAX::STEP::StepData is defined in <step/StepData.hpp>, but usually you do not need to include this header explicitly.

StepMeshReader

Similarly, StepMeshReader uses AMCAX::STEP::StepMeshDataList for data transfer:

AMCAX::STEP::StepMeshDataList<AMCAX::Meshing::Mesh::TriSoupTraits_Coord> shapes;

Defined in <step/mesh/StepMeshData.hpp>.

Obtaining Results

For both StepReader and StepMeshReader, the code to get translation results is the same:

shapes = reader.GetShapes();

StepReader

Depending on the file and conversion, shapes will contain 0 or more StepData objects. StepData corresponds to a Product entity in the STEP file and is used to transfer STEP data. StepData.hpp also defines ProductPtr as an alias for std::shared_ptr<StepData>. This class mainly includes the following members:

  1. Name and description of the current Product, accessed via ProductName() and Description();
  2. Child Products of the current Product (the node's subtree), via Children() / ChildAt(size_t);
  3. TopoShape(s) of the current Product, via Shapes() / ShapeAt(size_t);
  4. Properties (e.g. name, color) and scaling of each TopoShape under the Product, via PropertyChains() and Factors();
  5. Transformation info (position, rotation) when assembling this Product, via Location().

If multiple Product nodes form a tree via Children(), this represents assembly between Products. You can traverse StepDataList and check whether every StepData's ChildrenSize() is zero to see if assemblies are present. For assemblies, since a Product can be assembled into different parents with different transformations, StepData introduces the concept of a Shadow node to avoid duplicated information.

Shadow Nodes

A Shadow node is defined as a node containing only transformation info (Location). You can check StepData::IsShadow() to determine if a node is a Shadow, and use StepData::Target() to get the actual data node. Although Shadow nodes do not directly store shapes, calls to Shapes()/Children() etc. will automatically return data from the Target node. If you do not need Shadow node features, you can convert the entire Product tree to regular nodes as follows:

/* ProductPtr is alias for std::shared_ptr<StepData> */
void ConvertToNormal(ProductPtr& root)
{
if (root->IsShadow())
{
// Copy Target node data to current node and convert to a normal node.
// Note: MarkNormal also deep copies Children from the Target node.
// If you need to update the subtree later, update both this node's and Target's subtrees.
root->MarkNormal();
}
for(size_t i = 0; i < root->ChildrenSize(); ++i)
{
ConvertToNormal(root->ChildAt(i));
}
}
ProductPtr node = shapes[0];
ConvertToNormal(node);

StepMeshReader

The structure and members are basically the same as StepData, with the following addition:

  1. Mesh data of the current Product, accessed via Meshes().

(Optional, TopoShape Only) Flatten Product Tree to Array

If you do not need to keep the assembly structure, you can flatten the Product tree into a one-dimensional array. Each node will have exactly one TopoShape and no children.

// header file
// ...
// or
AMCAX::STEP::StepDataList shapesN2 = AMCAX::STEP::StepDataTool::Flatten(shapes);
Utility class for operations on STEP shape data structures.
static AMCAX_API StepDataList & FlattenInplace(StepDataList &shapes, bool unrolling=true)
Flatten a StepData tree into a one-dimensional array, in place.
static AMCAX_API StepDataList Flatten(const StepDataList &shapes, bool unrolling=true)
Create a flattened, one-dimensional copy of a StepData tree.

Getting Sub-shape Property Info

Once you have a TopoShape, use TopoIterator, TopoExplorer, or TopoExplorerTool from the AMCAX kernel to access sub-shapes. The following code retrieves their names and other info:

// Assume shapes contains at least one Product tree
std::shared_ptr<AMCAX::STEP::StepData> treeroot = shapes[0];
// Assume the current StepData node contains at least one TopoShape of Compound type
AMCAX::TopoShape compound = treeroot->Shapes()[0];
const AMCAX::STEP::PropertyChain& props = treeroot->PropertyChains()[0];
// PropertyChain is a std::vector of pairs <TopoShape, StepPropertyUnit>.
// Each sub-shape appears only once in PropertyChain output by StepReader.
// StepPropertyUnit overloads operator+=() for merging/overriding.
// For frequent lookup/update, you may use std::unordered_map<TopoShape, StepPropertyUnit>.
for (AMCAX::TopoExplorer exp(compound, AMCAX::ShapeType::Solid);
exp.More();
exp.Next())
{
const AMCAX::TopoShape solid = exp.Current();
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.ShapeStyleHasValue())
{
if(property.GetShapeStyle().SurfaceStyleHasValue())
{
auto surfacestyle = property.GetShapeStyle().GetSurfaceStyle();
if(surfacestyle.colorHasValue())
{/* do something with color */}
auto side = surfacestyle.getSide();
{/* do something with side */}
}
if(property.GetShapeStyle().CurveStyleHasValue())
{
auto curvestyle = property.GetShapeStyle().GetCurveStyle();
if(curvestyle.colorHasValue())
{/* do something with color */}
if(curvestyle.fontHasValue())
{/* do something with font */}
if(curvestyle.widthHasValue())
{/* do something with width */}
}
}
}
}
Class of a tool for exploring the B-Rep structure.
Definition TopoExplorer.hpp:14
AMCAX_API bool More() const noexcept
Does the explorer have more shapes.
Base class of shape, containing an underlying shape with a location and an orientation.
Definition TopoShape.hpp:15

(Mesh Only) Get Mesh Info

Since Mesh data is converted from TopoShape, a StepMeshData node will have Mesh data only if it contains TopoShape data. Mesh conversion is handled by StepMesh. For more info see <step/mesh/StepMesh.hpp>. Currently, StepMesh only accepts TopoShape of type Solid. See the following code for how to get Meshes, corresponding TopoShapes, names, and colors:

// Assume shapes contains at least one StepMeshData tree
std::shared_ptr<AMCAX::STEP::StepMeshData<AMCAX::Meshing::Mesh::TriSoupTraits_Coord>> root = shapes[0];
// Assume root has 2 TopoShapes: the first is Compound with 3 solids, 1 face; the second is Solid
std::cout << meshes.size() << '\n'; // "2": Each TopoShape (regardless of how many solids inside)
// corresponds to a std::vector<AMCAX::StepMesh<AMCAX::Meshing::Mesh::TriSoupTraits_Coord>>.
AMCAX::TopoShape& shape0 = root->ShapeAt(0);
std::vector<StepMesh<AMCAX::Meshing::Mesh::TriSoupTraits_Coord>>& meshes0 = root->MeshAt(0);
std::cout << meshes0.size() << '\n'; // "3": Compound with 3 solid sub-shapes; StepMesh handles only solids
AMCAX::TopoShape& shape1 = root->ShapeAt(1);
std::vector<StepMesh<AMCAX::Meshing::Mesh::TriSoupTraits_Coord>>& meshes1 = root->MeshAt(1);
std::cout << meshes1.size() << '\n'; // "1": The second shape is Solid, so only one solid
std::vector<AMCAX::TopoShape> solidSet0;
for (AMCAX::TopoExplorer exp(shape0, AMCAX::ShapeType::Solid); exp.More(); exp.Next())
{
solidSet0.push_back(exp.Current());
}
// Get Mesh, name, color info for the first shape
for (size_t solidIndex = 0; solidIndex < solidSet0.size(); ++solidIndex)
{
const AMCAX::TopoShape& solid = solidSet0[solidIndex];
// Access the corresponding Mesh by index
// For getting solid name/color, see the previous section
// doSomethingWith(curMesh, name, color);
}
// The code for the second shape is similar and omitted here
Class for reading STEP file and convert solid to mesh.
Definition StepMesh.hpp:30

(Mesh Only) Convert StepMeshData to StepData

Sometimes you may need to convert StepMeshData to base StepData, or StepMeshDataList to StepDataList. Refer to the following code:

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 Only) Extract TopoShape from StepData

While StepData::Shape() can be used to get TopoShape, since each StepData may contain multiple shapes and assembly may be complex, the module provides additional methods for complete TopoShape extraction:

Get Only the TopoShape Stored in the Current StepData

for (std::shared_ptr<StepData> node : reader.GetShapes())
{
TopoShape cur_shape = node->OneShape(/* recursive = */ false);
// If Shapes is empty, returns Null Shape
// If Shapes has 1, returns that Shape
// If more than 1, returns a Compound of all Shapes
}

Get TopoShape from Current StepData and All Children

for (std::shared_ptr<StepData> node : reader.GetShapes())
{
TopoShape cur_shape = node->OneShape(/* recursive = */ true);
}

Get TopoShape Composed from Complete StepDataList

TopoShape result = AMCAX::STEP::StepDataTool::MakeCompound(reader.GetShapes());

(TopoShape Only) Export Section

Include Header File

In Writer part, you only need to include one header file:

Class for exporting TopoShape objects and related data to STEP files.

Create Writer Object

The following code creates and initializes a Writer object by specifying the output filename (path):

AMCAX::STEP::StepWriter writer("/path/to/output.step");

Similarly, Writer can accept an output stream as a constructor parameter:

std::ofstream ofs("/path/to/output.step");
if (!ofs.is_open())
{
return 1;
}
Class for exporting TopoShape objects and related data to STEP files.
Definition StepWriter.hpp:30

Note: If the output file exists, it will be overwritten.

(Optional) Set Scaling

As long as the input TopoShape meets the output condition, Writer assumes its unit is 1 mm and outputs it without scaling. However, if the TopoShape was created in another unit, this may result in incorrect size in the exported model.

While no scaling is performed on values, you can set the unit in the STEP file as follows:

writer.SetUnit(AMCAX::STEP::StepLengthUnit::PresetLengthUnit::CENTIMETRE);

You must set this before exporting shape data.

(Optional) Set Progress Callback

Like Reader, Writer also uses callback functions for progress reporting:

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

Exporting

(Optional) Write File Header

To add required info to the STEP file header, call the following first:

writer.Init();

This outputs the ISO standard number, application protocol version, and other necessary entities. To ensure file format, pass TopoShape to Writer after Init() completes. By default, Writer auto-calls Init().

Write TopoShape

You can pass all TopoShape in AMCAX::STEP::StepDataList to Writer at once, or call functions multiple times; the effect is the same (except for color specification):

// Suppose StepDataList shapes contains at least 3 elements
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];
AMCAX::TopoShape shape_last = shapes.back()->Shapes().front();
writer.WriteShapes(shape_part_0);
writer.WriteShape(shape_part_1);
writer.WriteShape(shape_last);

Or

writer.WriteShapes(shapes);

Write Label

AMCAXAf Label is a geometric format supporting properties. You can export shapes with properties by passing Label to Writer:

// init and setup root;
writer.WriteShape(root);
// or writer.WriteLabel(root);
The class of Label.
Definition Label.hpp:27

However, since StepData stores both TopoShape and Label, you must specify which to use when passing StepData to Writer:

// init and setup root;
auto node = std::make_shared<AMCAX::STEP::StepData>();
// AddShape(Label) updates Shapes and Labels; PropertyChains and other data are set to empty placeholders
node->AddShape(root);
// This only outputs TopoShape without any property info
writer.WriteShape(node);
// This uses full info from Label for output
writer.WriteLabel(root);

Finish Exporting and Save File

By default, StepWriter performs actual exporting and closes output stream at destruction (only when created with a file path). You can also manually finish exporting:

writer.Done();

Note: If you call Init() on the Writer object again, the file will be overwritten.

Simplified Interface

AMCAX::STEP::StepDataTool provides utility functions for StepDataList generated by StepReader, and a simplified interface for importing/exporting TopoShape. Note: Part name, assembly, color, and rendering info may not be preserved. Usage:

bool success = AMCAX::STEP::StepDataTool::Read(shape, "/path/to/input.step");
if (success)
{
AMCAX::STEP::StepDataTool::Write(shape, "/path/to/output.step");
}
static AMCAX_API bool Write(const AMCAX::TopoShape &s, std::ostream &os)
Write a TopoShape to a stream in STEP format.
static AMCAX_API bool Read(AMCAX::TopoShape &s, std::istream &is)
Read a TopoShape from a stream in STEP format.

Click here example01 to download the complete Reader source code as example01. Click here example02 to download the complete StepMeshReader source code as example02.

Appendix

#include <iostream>
// Print Product tree structure
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);
}
}
}
// Print all Solid names in Product tree
void printSolidName(const std::shared_ptr<AMCAX::STEP::StepData> node, int indent = 0)
{
for (size_t i = 0; i < node->ShapesSize(); ++i)
{
const AMCAX::TopoShape& origShape = node->ShapeAt(i);
const AMCAX::STEP::PropertyChain& props = node->PropertyChainAt(i);
for (AMCAX::TopoExplorer SolidExp(origShape, AMCAX::ShapeType::Solid);
SolidExp.More();
SolidExp.Next())
{
const AMCAX::TopoShape& solid = SolidExp.Current();
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;
/**** Reading Part ****/
AMCAX::STEP::StepReader reader("./data/bed214T.step");
// Set the unit for imported shapes in the kernel. This will scale the shapes.
reader.SetUnit(AMCAX::STEP::StepLengthUnit::PresetLengthUnit::METRE);
bool topo_success = reader.Read();
if (!topo_success)
{
return -1;
}
shapes = reader.GetShapes();
// Print Product tree names and all Solid names
for (std::shared_ptr<AMCAX::STEP::StepData> root : shapes)
{
printProductName(root);
printSolidName(root);
}
// Extract shapes from StepDataList
// Extract the graphical data from the StepDataList
// Extract the Style data from the StepDataList
std::shared_ptr<AMCAX::STEP::StepData> treeroot = shapes[0];
AMCAX::TopoShape oneshape = treeroot->Shapes()[0];
const AMCAX::STEP::PropertyChain& props = treeroot->PropertyChains()[0];
for (AMCAX::TopoExplorer exp(oneshape, AMCAX::ShapeType::Solid);exp.More();exp.Next())
{
const AMCAX::TopoShape solid = exp.Current();
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.ShapeStyleHasValue())
{
if(property.GetShapeStyle().SurfaceStyleHasValue())
{
auto surfacestyle = property.GetShapeStyle().GetSurfaceStyle();
if(surfacestyle.colorHasValue())
{/* do something with color */}
auto side = surfacestyle.getSide();
{/* do something with side */}
}
if(property.GetShapeStyle().CurveStyleHasValue())
{
auto curvestyle = property.GetShapeStyle().GetCurveStyle();
if(curvestyle.colorHasValue())
{/* do something with color */}
if(curvestyle.fontHasValue())
{/* do something with font */}
if(curvestyle.widthHasValue())
{/* do something with width */}
}
}
}
}
// Flatten StepDataList tree structure to 1D array
AMCAX::STEP::StepDataList flatten = AMCAX::STEP::StepDataTool::Flatten(shapes);
/**** Writing Part ****/
AMCAX::STEP::StepWriter writer("./output.step");
// Set the exported shape's unit in the STEP file. This only affects unit info in output.
writer.SetUnit(AMCAX::STEP::StepLengthUnit::PresetLengthUnit::METRE);
writer.Init();
writer.WriteShapes(shapes);
writer.Done();
}
Class of a tool for exploring the B-Rep structure.
Class of iterator for B-Rep structure.
static AMCAX_API AMCAX::TopoShape MakeCompound(const std::shared_ptr< StepData > &root)
Create a TopoShape by combining all shapes in a StepData tree.