AMCAX Kernel 1.0.0.0
Loading...
Searching...
No Matches
B-rep Representation of Topological Structures​

B-rep Structure

Boundary Representation (B-rep) is currently the most popular data structure. B-rep can maintain completeness and unambiguity while reducing storage requirements. B-rep uses points, curves, and surfaces on the boundaries of entities to represent those entities, and the volume information of the entity is derived from the three-dimensional space enclosed within the boundary. Its two basic elements are: geometric information (Point, Curve, Surface); and topological information (Vertex, Edge, Face connectivity).

Definition of Topological Structure​

Definition

Let’s first define the various levels of topological structures in a B-rep:

  • Vertex is a point in topological terms, corresponding to a geometric Point.
  • Edge is a line in topological terms, corresponding to a geometric Curve, and the two vertices that constrain the Curve’s boundaries.
  • Wire is a set of edges that are connected end to end, with adjacent edges sharing vertices.
  • Face is a surface in topological terms, corresponding to a geometric Surface, and constrained by several closed wires.
  • Shell is a set of connected Faces, where adjacent Faces share one or more edges.
  • Solid is a space enclosed by a closed Shell.
  • CompSolid is a set of Solids.
  • Compound is a collection of multiple topological structures.

Common Examples

Here are some examples to help you better understand the concept.

A Face formed by a Surface

It’s easy to see that this example consists of 4 Vertices, 4 Edges, 1 Wire, and 1 Face.

Shell formed by Two Adjacent Surfaces

Since the overlapping vertices and edges in the middle are shared, this example consists of 6 Vertices, 7 Edges, 2 Wires, 2 Faces, and 1 Shell.

Cube

This example consists of 8 Vertices, 12 Edges, 6 Wires, 6 Faces, and 1 Shell.

Cylinder

This example consists of 2 Vertices, 3 Edges, 3 Wires, 3 Faces, and 1 Shell. Edge 0 is reused in the Wire, making it a classic Seam Edge.

Sphere

This example consists of 2 Vertices, 3 Edges, 1 Wire, 1 Face, and 1 Shell. In this case, there is not only a Seam Edge but also two Edges with no length. Despite having no length in 3D, these two Edges do have length in the parametric domain, making them classic Degenerated Edges.

Edge

Next, we will introduce some common types of Edges.

Ordinary Edge

In a closed TopoShape (like a Shell or Solid), the most common Edge is an edge that is adjacent to two different Faces. An ordinary Edge belongs to two different Faces and has a non-zero length.

Seam Edge

A common special case is the side edge of a cylinder. This Edge is owned by one Face and appears twice in the same Wire of that Face (once forward, once backward). Seam Edges are valid.

Degenerated Edge

A common special case is the top edge of a sphere. This Edge has zero length and degenerates into a point. Degenerated Edges are valid.

Free Edge

An Edge that belongs to only one Face and appears only once in that Face’s Wire is called a Free Edge. Free Edges are invalid in a closed TopoShape.

Non-Manifold Edge

An Edge that belongs to more than two Faces in one Shape or Solid is called a Non-Manifold Edge. Non-Manifold Edges are invalid.

Topology Traversal​

Tree Structure of TopoShape

Let’s first introduce the tree structure of a TopoShape, as shown below:

Traversing

Traversing a topological structure is a common operation. Our kernel provides two ways: AMCAX::TopoExplorer and AMCAX::WireExplorer. Let’s introduce both methods one by one.

TopoExplorer

AMCAX::TopoExplorer can traverse through specified types (Shape, Compound, CompSolid, Solid, Shell, Face, Wire, Edge, Vertex) within a TopoShape in a preorder manner, based on its tree structure, to find child shapes of the specified type. For example, to traverse the Faces of a TopoShape, you can use the following code:

for (AMCAX::TopoExplorer ex(shape, AMCAX::ShapeType::Face); ex.More(); ex.Next())
{
const AMCAX::TopoShape& crshape = ex.Current();
const AMCAX::TopoFace& crface = AMCAX::TopoCast::Face(crshape);
}
static AMCAX_API const TopoFace & Face(const TopoShape &s)
Cast shape to face.
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.
Class of face.
Definition TopoFace.hpp:12
Base class of shape, containing an underlying shape with a location and an orientation.
Definition TopoShape.hpp:15

It’s important to note that AMCAX::TopoExplorer does not consider duplicates (level of "equality"). That is, if an Edge is shared by two Faces, AMCAX::TopoExplorer will traverse this Edge twice. If you don’t want to traverse it twice, you can use a std::unordered_set to remove duplicates, like this:

std::unordered_set<AMCAX::TopoShape> faceSet;
for (AMCAX::TopoExplorer ex(shape, AMCAX::ShapeType::Face); ex.More(); ex.Next())
{
const AMCAX::TopoShape& crshape = ex.Current();
if (faceSet.find(crshape) != faceSet.end())
{
continue;
}
const AMCAX::TopoFace& crface = AMCAX::TopoCast::Face(crshape);
}

In the above, we discussed duplication, which inherently implies the definition of "equality". Next, we will introduce the three levels of "equality" for TopoShape, with increasing strictness:

  • IsPartner:Consistent with TopoTshape
  • IsSame:Consistent with TopoTshape and TopoLocation
  • IsEqual:Consistent with TopoTshape , TopoLocation , and Orientation
  • IndexSet:Default is IsSame level

WireExplorer

When sequential traversal of edges in a Wire is required (where the end vertex of one edge matches the start vertex of the next), AMCAX::TopoExplorer may not meet this requirement. For example, when constructing three edges from three Point3 objects and combining them into a Wire, if the edges are added in an order inconsistent with their actual connectivity, TopoExplorer will return edges in their construction order rather than topological connection order. In such cases, AMCAX::WireExplorer should be used instead, which guarantees traversal following the actual edge connectivity sequence – though with slower performance.

for (AMCAX::WireExplorer ex(wire); ex.More(); ex.Next())
{
const AMCAX::TopoEdge& edge = ex.Current();
}
Class of edge.
Definition TopoEdge.hpp:12
Class of wire.
Definition TopoWire.hpp:12
Class of tool for exploring wire.
Definition WireExplorer.hpp:20
AMCAX_API bool More() const noexcept
Does the explorer have more edges.

Topological Information Retrieval

Index

Due to the tree structure and pre-order traversal, each Edge (similarly for Vertex, Wire, and Face) actually has its own index. The AMCAX::TopoExplorerTool class provides some convenient traversal operations. AMCAX::TopoExplorerTool::MapShapes can get an ordered set of indices for a specific type of topological structure (such as Edge), and the resulting set is unique.

AMCAX::TopoShape box = AMCAX::MakeBox(AMCAX::Point3(-5.0, -5.0, 0.0), AMCAX::Point3(5.0, 5.0, 3.0));
AMCAX::TopoExplorerTool::MapShapes(box, AMCAX::ShapeType::Edge, edgeIndexSet);
AMCAX::TopoEdge edge1 = static_cast<const AMCAX::TopoEdge&>(edgeIndexSet[3]);
//get id
int edgeid = edgeIndexSet.index(edge1);
std::cout << edgeid << std::endl;
Template class of indexed set.
Definition IndexSet.hpp:20
int index(const key_type &key) const
Get the index of a given key.
Definition IndexSet.hpp:213
Class of making a box.
Definition MakeBox.hpp:20
static AMCAX_API void MapShapes(const TopoShape &s, ShapeType t, IndexSet< TopoShape > &shapeSet)
Construct a set of sub-shapes of given type.
PointT< double, 3 > Point3
3D point
Definition PointT.hpp:459

In fact, this index matches the one shown in the lower-left corner of FreeCAD (FreeCAD starts from 1, while our kernel starts from 0).

Associated Face

AMCAX::TopoExplorerTool::MapShapesAndAncestors can retrieve a map from child shapes to their corresponding parent shapes, while AMCAX::TopoExplorerTool::MapShapesAndUniqueAncestors also provides a map. The difference is that when a seam edge is input, MapShapesAndUniqueAncestors will return a single associated face, while MapShapesAndAncestors will return two associated faces. For example, the mapping from Edge to its associated Face can be obtained as follows:

AMCAX::TopoShape cylinder = AMCAX::MakeCylinder(AMCAX::Frame3(AMCAX::Point3(0.0, 0.0, 0.0), AMCAX::Direction3(0.0, 0.0, 1.0)), 3.0, 5.0);
//MapShapesAndAncestors
AMCAX::TopoExplorerTool::MapShapesAndAncestors(cylinder, AMCAX::ShapeType::Edge, AMCAX::ShapeType::Face, edgeFaceMap);
//MapShapesAndUniqueAncestors
AMCAX::TopoExplorerTool::MapShapesAndUniqueAncestors(cylinder, AMCAX::ShapeType::Edge, AMCAX::ShapeType::Face, edgeFaceMap2);
Template class of indexed map.
Definition IndexMap.hpp:21
Class of making a cylinder.
Definition MakeCylinder.hpp:18
static AMCAX_API void MapShapesAndUniqueAncestors(const TopoShape &s, ShapeType ts, ShapeType ta, IndexMap< TopoShape, std::list< TopoShape > > &shapeMap, bool useOrientation=false)
Construct a map from sub-shapes of given type to all the unique ancestor shapes of given type.
static AMCAX_API void MapShapesAndAncestors(const TopoShape &s, ShapeType ts, ShapeType ta, IndexMap< TopoShape, std::list< TopoShape > > &shapeMap)
Construct a map from sub-shapes of given type to all the ancestor shapes of given type.
DirectionT< double, 3 > Direction3
3D direction
Definition DirectionT.hpp:566
FrameT< double, 3 > Frame3
3D frame
Definition FrameT.hpp:887

Degenerated Edge

Determining whether an Edge is degenerated is very important. For example, we should not sample along a degenerated edge. We can use AMCAX::TopoTool::Degenerated to check.

AMCAX::TopoShape sphere = AMCAX::MakeSphere(AMCAX::Point3(-3.0, 0.0, 0.0), 1.0);
AMCAX::TopoExplorerTool::MapShapes(sphere, AMCAX::ShapeType::Edge, edgeSet);
std::cout << AMCAX::TopoTool::Degenerated(de)<<std::endl;
Class of making a sphere.
Definition MakeSphere.hpp:18
static AMCAX_API const TopoEdge & Edge(const TopoShape &s)
Cast shape to edge.
static AMCAX_API bool Degenerated(const TopoEdge &e)
Get the degenerated flag of an edge.

Seam Edge

You can determine whether an Edge is a seam edge using AMCAX::TopoTool::IsClosed.

AMCAX::TopoShape cylinder = AMCAX::MakeCylinder(AMCAX::Frame3(AMCAX::Point3(0.0, 0.0, 0.0), AMCAX::Direction3(0.0, 0.0, 1.0)), 3.0, 5.0);
AMCAX::TopoExplorerTool::MapShapes(cylinder, AMCAX::ShapeType::Edge, edgeSet2);
AMCAX::TopoEdge edge22 = AMCAX::TopoCast::Edge(edgeSet2[1]);
AMCAX::TopoExplorerTool::MapShapes(cylinder, AMCAX::ShapeType::Face, faceSet2);
AMCAX::TopoFace face11 = AMCAX::TopoCast::Face(faceSet2[0]);
std::cout << AMCAX::TopoTool::IsClosed(edge22, face11) << std::endl;
static AMCAX_API bool IsClosed(const TopoShape &s)
Is the shape closed.

Geometric Information Extraction​

Point

A Vertex contains only one Point, representing a point in 3D space. The method to access the Point from a Vertex is AMCAX::TopoTool::Point :

AMCAX::TopoShape box = AMCAX::MakeBox(AMCAX::Point3(-5.0, -5.0, 0.0), AMCAX::Point3(5.0, 5.0, 3.0));
AMCAX::TopoExplorerTool::MapShapes(box, AMCAX::ShapeType::Vertex, vertexSet);
static AMCAX_API const TopoVertex & Vertex(const TopoShape &s)
Cast shape to vertex.
static AMCAX_API Point3 Point(const TopoVertex &v)
Get the point of a vertex.

Curve and PCurve

curve3d is a 3D parametric curve. pcurve is a 2D parametric curve. pcurve is bound to <Edge, Surface, Location>, meaning Edge, Surface, and Location are the indexes of a pcurve.

Edge and Curves

An Edge typically has one curve3d and two pcurves. In fact, a 3D parametric curve can also be constructed using a pcurve and surface, but due to tolerance, the two resulting 3D parametric curves will not coincide with the curve3d. In geometric modeling, there is no requirement for the number of curves in an Edge; it is fine if there are no curves. However, unless in special cases, the final geometric model should include one curve3d and two pcurves.

Next, let's introduce some special cases:

Edge on Geom3Plane

If the Edge lies on a plane (Geom3Plane), no additional pcurve is needed, because the Edge on the plane can be directly represented by a curve3d.

Seam Edge

A Seam Edge has one curve3d and two pcurves, both on one Face. The difference between the two pcurves lies in the Orientation of the Edge: one pcurve corresponds to the forward direction of the Edge, and the other corresponds to the reverse direction.

Degenerated Edge

A Degenerated Edge has only one pcurve, with no curve3d.

Free Edge (Edge not belonging to any face)

A Free Edge has one curve3d and one pcurve.

It is important to note that the kernel does not provide functionality for calculating a pcurve from a curve3d. The correct modeling approach is to first construct the pcurve and then use the pcurve and surface to calculate the curve3d:

AMCAX::TopoShape box = AMCAX::MakeBox(AMCAX::Point3(-5.0, -5.0, 0.0), AMCAX::Point3(5.0, 5.0, 3.0));
static AMCAX_API bool BuildCurves3d(const TopoShape &s)
Build 3d curves for all the edges in a shape.

Accessing the Curve of an Edge

Curve3d

The method to access the Curve3d of an Edge is AMCAX::TopoTool::Curve :

AMCAX::TopoShape box = AMCAX::MakeBox(AMCAX::Point3(-5.0, -5.0, 0.0), AMCAX::Point3(5.0, 5.0, 3.0));
AMCAX::TopoExplorerTool::MapShapes(box, AMCAX::ShapeType::Edge, edgeSet);
double fp, lp;
const std::shared_ptr<AMCAX::Geom3Curve>& curve3d = AMCAX::TopoTool::Curve(AMCAX::TopoCast::Edge(edgeSet[0]), loc3, fp, lp);
Class of local transformation representing location of entities.
Definition TopoLocation.hpp:14
static AMCAX_API const std::shared_ptr< Geom3Curve > & Curve(const TopoEdge &e, TopoLocation &l, double &first, double &last)
Get the curve of an edge with its location and bounds.

There are a few points to note:
1.The interface returns the raw curve. If an Edge is a segment formed by a Geom3Line and two Vertices, this interface will return an infinitely long line (Geom3Line), with fp and lp being the parameters of the two Vertices on the curve.

2.AMCAX::TopoTool::Curve has an overloaded function, one that takes a loc input and one that does not. The interface with loc returns a pointer to the curve memory inside the edge, while the interface without loc, when the edge's location is null, returns a pointer to the memory; otherwise, it returns a pointer to a newly constructed curve.

3.If the input is a degenerated edge, a null pointer will be returned.

4.The interface with loc returns a Curve with an additional location compared to the actual location, and applying location.Transformation() directly on the Curve is prohibited, as this will alter the edge's content. The correct approach is to copy the Curve and then apply transformations to the CopyCurve:

std::shared_ptr<AMCAX::Geom3Curve> CopyCurve = std::dynamic_pointer_cast<AMCAX::Geom3Curve>(curve3d->Copy());
CopyCurve->Transform(loc3.Transformation());// apply the location to the copy curve
AMCAX_API const Transformation3 & Transformation() const noexcept
Get the composite transformation.

pcurve

The method to access the pcurve is AMCAX::TopoTool::CurveOnSurface :

AMCAX::TopoShape cylinder = AMCAX::MakeCylinder(AMCAX::Frame3(AMCAX::Point3(0.0, 0.0, 0.0), AMCAX::Direction3(0.0, 0.0, 1.0)), 3.0, 5.0);
AMCAX::TopoExplorerTool::MapShapes(cylinder, AMCAX::ShapeType::Edge, edgeSet2);
AMCAX::TopoExplorerTool::MapShapes(cylinder, AMCAX::ShapeType::Face, faceSet2);
double fp2=0., lp2=0.;
std::shared_ptr<AMCAX::Geom2Curve> pcurve = AMCAX::TopoTool::CurveOnSurface(AMCAX::TopoCast::Edge(edgeSet2[0]), AMCAX::TopoCast::Face(faceSet2[0]), fp2, lp2);
Class of making a 2D edge.
Definition MakeEdge2d.hpp:24
static AMCAX_API bool Write(const TopoShape &s, std::ostream &os, int format=3)
Write a shape to a stream.
static AMCAX_API std::shared_ptr< Geom2Curve > CurveOnSurface(const TopoEdge &e, const TopoFace &f, double &first, double &last, const std::shared_ptr< bool > &isStored=nullptr)
Get the pcurve of an edge on a given face.

It is important to note that if the input is a seam edge, special handling is required. Depending on the Edge's Orientation, different pcurves will be returned:

AMCAX::TopoShape cylinder = AMCAX::MakeCylinder(AMCAX::Frame3(AMCAX::Point3(0.0, 0.0, 0.0), AMCAX::Direction3(0.0, 0.0, 1.0)), 3.0, 5.0);
AMCAX::TopoExplorerTool::MapShapes(cylinder, AMCAX::ShapeType::Edge, edgeSet2);
AMCAX::TopoExplorerTool::MapShapes(cylinder, AMCAX::ShapeType::Face, faceSet2);
double fp3=0., lp3=0.;
std::shared_ptr<AMCAX::Geom2Curve> pcurve2 = AMCAX::TopoTool::CurveOnSurface(AMCAX::TopoCast::Edge(edgeSet2[1]), AMCAX::TopoCast::Face(faceSet2[0]), fp3, lp3);
std::cout << pcurve2->Value(fp3) << ";" << pcurve2->Value(lp3) << std::endl;
double fp4 = 0., lp4 = 0.;
AMCAX::OrientationType reverseOrientation = AMCAX::TopoTool::Complement(AMCAX::TopoCast::Edge(edgeSet2[1]).Orientation());
AMCAX::TopoEdge reversedEdge = AMCAX::TopoCast::Edge(edgeSet2[1].Oriented(reverseOrientation));
std::shared_ptr<AMCAX::Geom2Curve> pcurve3 = AMCAX::TopoTool::CurveOnSurface(reversedEdge, AMCAX::TopoCast::Face(faceSet2[0]), fp4, lp4);
std::cout << pcurve3->Value(fp4) << ";" << pcurve3->Value(lp4) << std::endl;
static AMCAX_API OrientationType Complement(const OrientationType &o) noexcept
Get the complement of an orientation.
OrientationType
Type of orientations.
Definition TopologyMacros.hpp:11

In the above example, two pcurves for the seam edge of a cylinder were obtained. The orientation of these two pcurves is always consistent and matches the orientation of the curve3d. The edge's orientation is not considered.

Surface

A Face contains only one Surface. The method to access the Surface is AMCAX::TopoTool::Surface:

AMCAX::TopoShape box = AMCAX::MakeBox(AMCAX::Point3(-5.0, -5.0, 0.0), AMCAX::Point3(5.0, 5.0, 3.0));
AMCAX::TopoExplorerTool::MapShapes(box, AMCAX::ShapeType::Face, faceSet);
std::shared_ptr< AMCAX::Geom3Surface > surface1 = AMCAX::TopoTool::Surface(AMCAX::TopoCast::Face(faceSet[0]), loc);
std::shared_ptr< AMCAX::Geom3Surface > surface2 = AMCAX::TopoTool::Surface(AMCAX::TopoCast::Face(faceSet[0]));
static AMCAX_API const std::shared_ptr< Geom3Surface > & Surface(const TopoFace &f, TopoLocation &l)
Get the surface and the location of the surface from a face.

The interface with loc returns a Surface with an additional location compared to the actual location, and applying location.Transformation() directly on the Surface is prohibited, as this will alter the face's content. The correct approach is to copy the Surface and then apply transformations to the CopySurface:

std::shared_ptr<AMCAX::Geom3Surface> CopySurface = std::dynamic_pointer_cast<AMCAX::Geom3Surface>(surface1->Copy());
CopySurface->Transform(loc.Transformation());// apply the location to the copy surface
virtual AMCAX_API std::shared_ptr< Geom3Geometry > Copy() const =0
Get the copied geometry object.

Mesh

Mesh refers to a triangular mesh. The significance of Mesh in CAD is for rendering. Since the basic drawing elements in OpenGL are points, line segments, and triangles, curves must be discretized into polygons for rendering, and surfaces must be discretized into Mesh for rendering. Therefore, in applications with a graphical user interface (GUI), the BRep model that is seen is typically visualized through a triangular mesh. As shown in the figure below.

Clearly, discretization introduces errors, meaning that the BRep model displayed in applications with a GUI is not the model itself, but rather its Mesh. The error between the two is determined by the discretization precision, which is the density of the mesh. Considering that Mesh generation in CAD models must be very fast, the precision of the Mesh is understandably not very high.

There are many issues caused by discretization, such as visually judging whether two identical circles overlap or whether two identical spheres overlap, which can lead to situations like the one shown in the image below. Another issue is rendering a smooth surface as if it has wrinkles. Another type of problem is mesh generation failure, which can cause holes to appear. Therefore, when holes are seen in the model, one needs to consider both the Mesh reasons and the model itself.

Next, we will introduce the generation, retrieval, and read/write methods of Mesh.

AMCAX::TopoShape box = AMCAX::MakeBox(AMCAX::Point3(-5.0, -5.0, 0.0), AMCAX::Point3(5.0, 5.0, 3.0));
AMCAX::TopoExplorerTool::MapShapes(box, AMCAX::ShapeType::Face, faceSet);
AMCAX::BRepMeshIncrementalMesh mesher(box, 0.005, true);
const auto& mesh = AMCAX::TopoTool::Triangulation(AMCAX::TopoCast::Face(faceSet[0]), loc2);
std::cout<<AMCAX::OBJTool::WriteShape(box, "./mesh.obj")<<std::endl;
auto mesh2 = AMCAX::OBJTool::ReadFile("./mesh.obj");
Class of meshing.
Definition BRepMeshIncrementalMesh.hpp:16
static AMCAX_API std::shared_ptr< TriangularMesh > ReadFile(const std::string &file)
Read a mesh from a file.
static AMCAX_API bool WriteShape(const TopoShape &shape, const std::string &file, bool divideGroup=true)
Write the mesh of a shape to a file.
static AMCAX_API const std::shared_ptr< TriangularMesh > & Triangulation(const TopoFace &f, TopoLocation &l, unsigned char purpose=0)
Get the triangular mesh from a face and the location of the face under a given purpose.

Although Mesh is necessary for rendering, it is not always required to use the kernel to generate Mesh for the model after it is constructed, because software with a GUI will generate the Mesh automatically for rendering. Normals in rendering are crucial information. If the Mesh does not provide normals, an approximate normal will be automatically calculated based on the Mesh during rendering. If it is necessary to ensure that the normals of the triangular mesh match those of the CAD model, the AMCAX::MakeShapeTool::EnsureNormalConsistency() can be used.