Get Location and Eulerian Angles in global coordinates of TopoDS_Shape that is a rotated and translated Box

Hi everyone, I am trying to extract some data (mass, volume, area, position (of the center of mass) and its orientation, that is the angle that the figure is rotated about each global axis) from the attached STEP file, containing a 20 mm cube translated 40mm in every axis and rotated 30 deg in every axis.

I know how to get the mass, provided there is a density in the file (which in this case is, but I OCCT somehow does not read it), the area, the volume and its center of mass (ironically in global coordinates) but rotation angles are challenging me.

I am able to get the rotation angles in the local coordinate system, which evidently are all 0. The process I use is:

Read STEP file & extract TopoDS_Shape from it -> TopoDS_Shape.Location() -> TopoDS_Shape.Location().Transformation()

From the gp_Trsf it returns (let s call it trsf) I do trsf.TranslationPart().Coord() to get the coordinates, which are (0,0,0).

From the gp_Trsf it returns (let s call it trsf) I do trsf.GetRotation().GetEulerAngles(gp_EulerSequence.gp_Extrinsic_XYZ) to get the eulerian angles, which are (0,0,0) too.

I suspect this happens because TopoDS_Shape.Location() returns the TopLoc_Location in the local coordinate system, but I am not sure.

Does anybody know how to extract these properties from a global (absolute) coordinate system ?

Thank you very much before hand.

Attachments: 
Kirill Gavrilov's picture

I know how to get the mass, provided there is a density in the file (which in this case is, but I OCCT somehow does not read it)

OCCT does read material density from STEP - see information displayed by CAD Assistant from XCAF document:


I suspect this happens because TopoDS_Shape.Location() returns the TopLoc_Location in the local coordinate system, but I am not sure.

TopoDS_Shape::Location() returns location it was set before. Whatever it is defined in some local or global coordinate system depends on how your coordinates systems are defined and how / with which flags you explore the root shape. Please share a comprehensive code sample on how you change location and how you try to read it back to give some advise on what might be done wrong.

LuisGMM's picture

Hi Kirill, thank you very much for your answer.

I am using a Python wrapper of Open Cascade, so this sample is in written in Python:

from OCC.Core.BRepGProp import brepgprop
from OCC.Core.gp import gp_EulerSequence
from OCC.Core.GProp import GProp_GProps
from OCC.Core.TopoDS import TopoDS_Shape

from OCC.Extend.DataExchange import read_step_file


def main():

    path: str = ...
    shape: TopoDS_Shape = read_step_file(path)

    lprops = GProp_GProps()
    brepgprop.VolumeProperties(shape, lprops)

    mass_or_volume = lprops.Mass()

    lprops = GProp_GProps()
    brepgprop.SurfaceProperties(shape, lprops)

    area = lprops.Mass()

    center_of_mass = lprops.CentreOfMass().Coord()

    # For the location
    location = shape.Location()
    transformation = location.Transformation()
    point = transformation.TranslationPart()

    location_coords = point.X(), point.Y(), point.Z()

    # For the orientation
    location = shape.Location()
    transformation = location.Transformation()
    quaternion = transformation.GetRotation()

    rotation_angles = quaternion.GetEulerAngles(gp_EulerSequence.gp_Extrinsic_XYZ)

    print('Volume: ', mass_or_volume)
    print('Area: ', area)
    print('Center of mass: ', center_of_mass)
    print('Location coordinates: ', location_coords)
    print('Rotation angles: ', rotation_angles)


if __name__ == '__main__':
    main()

I suspect that the method read_step_file is part of this python library. This is its definition:

import os
from OCC.Core.STEPControl import STEPControl_Reader

def read_step_file(filename, as_compound=True, verbosity=True):
    """read the STEP file and returns a compound
    filename: the file path
    verbosity: optional, False by default.
    as_compound: True by default. If there are more than one shape at root,
    gather all shapes into one compound. Otherwise returns a list of shapes.
    """
    if not os.path.isfile(filename):
        raise FileNotFoundError(f"{filename} not found.")

    step_reader = STEPControl_Reader()
    status = step_reader.ReadFile(filename)

    if status == IFSelect_RetDone:  # check status
        if verbosity:
            failsonly = False
            step_reader.PrintCheckLoad(failsonly, IFSelect_ItemsByEntity)
            step_reader.PrintCheckTransfer(failsonly, IFSelect_ItemsByEntity)
        transfer_result = step_reader.TransferRoots()
        if not transfer_result:
            raise AssertionError("Transfer failed.")
        _nbs = step_reader.NbShapes()
        if _nbs == 0:
            raise AssertionError("No shape to transfer.")
        if _nbs == 1:  # most cases
            return step_reader.Shape(1)
        if _nbs > 1:
            print("Number of shapes:", _nbs)
            shps = []
            # loop over root shapes
            for k in range(1, _nbs + 1):
                new_shp = step_reader.Shape(k)
                if not new_shp.IsNull():
                    shps.append(new_shp)
            if as_compound:
                compound, result = list_of_shapes_to_compound(shps)
                if not result:
                    print("Warning: all shapes were not added to the compound")
                return compound
            print("Warning, returns a list of shapes.")
            return shps
    else:
        raise AssertionError("Error: can't read file.")
    return None

Similarly, I assume that the method list_of_shapes_to_compound is also from the library. Here it is:

from OCC.Core.TopoDS import TopoDS_Compound, TopoDS_Shape
from OCC.Core.BRep import BRep_Builder

def list_of_shapes_to_compound(
    list_of_shapes: List[TopoDS_Shape],
) -> Tuple[TopoDS_Compound, bool]:
    """takes a list of shape in input, gather all shapes into one compound
    returns the compound and a boolean, True if all shapes were added to the compund,
    False otherwise
    """
    all_shapes_converted = True
    the_compound = TopoDS_Compound()
    the_builder = BRep_Builder()
    the_builder.MakeCompound(the_compound)
    for shp in list_of_shapes:
        # first ensure the shape is not Null
        if shp.IsNull():
            all_shapes_converted = False
            continue
        the_builder.Add(the_compound, shp)
    return the_compound, all_shapes_converted

About the mass and density, why is it that I don't get the mass in the variable mass_or_volume ? About the rotation angles and location, hope you can understand this python code. If not, I could try to transcribe it into C++, but it would be very bad code.

Thank you for the effort!

Attachments: 
Kirill Gavrilov's picture

About the mass and density, why is it that I don't get the mass in the variable mass_or_volume

Density / material properties are not part of B-Rep (TopoDS_Shape) definition - it is a supplementary information stored as attributes. In case of OCCT, it could be retrieved from XCAF document (XCAFDoc_Material/XCAFDoc_MaterialTool) when STEP file is translated into XCAF. Your code uses a simplified interface read_step_file() returning TopoDS_Shape, so that you loose all document attributes (names, colors, materials) except geometry definition.

About the rotation angles and location, hope you can understand this python code.

Your code doesn't applies any transformation to the shape and just prints location of a TopoDS_Compound storing the list of all root (free) shapes in the document, so that of course this compound will have an identity location. You need to explore model to see locations assigned to root shapes or it's subshapes, in case if they are defined within STEP file (I guess this is not the case), or set your own location to this compound moving it to another place.