tpaviot / pythonocc-core

Python package for 3D geometry CAD/BIM/CAM
GNU Lesser General Public License v3.0
1.32k stars 372 forks source link

Checking Collision between Solids in STEP files #807

Open joaoprioli opened 4 years ago

joaoprioli commented 4 years ago

Hello, I trying to write a code that opens a STEP file and check if any solid is colliding/intersecting with the rest of the solids in the STEP file. In that case, the STEP can contain solids colliding or not, so the code would output True/False for each solid check.

Example of STEP colliding/not colliding

image

image

I started with the code below, but I have no idea how to continue. Could someone help me?

from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
from OCC.Core import TopoDS
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Common as makeCommon
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Check
from OCC.Core.IntAna import IntAna_IntConicQuad, IntAna_QuadQuadGeo
import os
from random import randint
from OCC.Extend.TopologyUtils import TopologyExplorer
from OCC.Extend.DataExchange import read_step_file

shp = read_step_file(os.path.join('Assem3.stp'))
t = TopologyExplorer(shp)
for solid in t.solids():
    if (BRepAlgoAPI_Check.IsValid(solid) == True):
        print("colliding")
rainman110 commented 4 years ago

A very simple solution, but definitely not the fastest: Make a boolean intersection!

If the resulting shape has a finite volume, you have an intersection.

There are probably faster algorithms, working e.g. on a surface mesh and checking for intersections.

joaoprioli commented 4 years ago

How could I split the solids in the STEP file and then check one against each one?

tpaviot commented 4 years ago

@joaoprioli from the less accurate/fastest to the most accurate/slowest methods :

  1. check clashes using bounding boxes

  2. check class using meshed shapes and a mesh intersection algorithm

  3. use BRep boolean operation

tpaviot commented 4 years ago

Note for myself it should be worth trying to wrap the vtk part of opencascade technology.

johannesmichael commented 4 years ago

And how would you check, if a solid is completly inside of another solid?

tpaviot commented 4 years ago

@johannesmichael Depends on how you define the "is completely inside".

jf--- commented 4 years ago

A wonderful collision detection library I've integrated with PythonOCC is FCL, the flexible collision library. Recommended.

johannesmichael commented 4 years ago

I have bigger boxes/spaces, like areas in a building and I want to add properties to the solids (walls, pipes, ...) that are inside that space. So first I am trying to find all that are inside that space, second will be the task to define where the ones are belonging, that extend to both spaces. So with completly I just wanted to express: the whole solid is inside the other.

Edit: Working with IFC

jf--- commented 4 years ago

Have you looked into the BRepClass3d_SolidClassifier / BRepClass3d_SolidExplorer?

johannesmichael commented 4 years ago

@jf--- yes, but are still in the process of understanding how to use it. I had used the BrepAlgoAPI before, so I started there. The FCL doesn 't help here, or did I missed something?

jf--- commented 4 years ago

Boolean ops are probably in terms of performance the slowest, but as a quick hack its clever.

On the BRep level something like BRepClass3d_SolidExplorer is already much more optimised for the use case.

The FCL doesn 't help here, or did I missed something?

Well you can access the tesselation of your TopoDS_* and infer intersections via FCL. This is faster & more robust, when you intend to implement clash detection, you really have to do so at the mesh rather than the BRep level, its orders of magnitude in terms of performance.

johannesmichael commented 4 years ago

With the booleans I could not find a way to identify if inside the box. Inside and outside behave the same. With BRepClass3d_SolidExplorer I am struggling to find the right method. Is it right, that there I have to check if the points or line are in the TopoDS_Shape? Something like BRepClass3d_SolidExplorer(boxshape).RejectShell(line_of_other_shape)

tpaviot commented 4 years ago

@jf--- Interesting, didn't know about this library. Does it take meshes ? and what about using vtk or cgal to perform collision detection ?

joaoprioli commented 4 years ago

@joaoprioli from the less accurate/fastest to the most accurate/slowest methods :

  1. check clashes using bounding boxes
  2. check class using meshed shapes and a mesh intersection algorithm
  3. use BRep boolean operation

Could you have a brief code to explain how to use the BRep boolean operation in the collision, I still facing problems trying to check the collision in the STEP file.

joaoprioli commented 4 years ago

When I play the following code seems that I can't split the solids inside the STEP file. Any clue?

from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
from OCC.Core import TopoDS
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Common as makeCommon
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Check
from OCC.Core.BRepClass3d import BRepClass3d_SolidExplorer
from OCC.Core.BRepAlgo import BRepAlgo_BooleanOperation, BRepAlgo_Common, BRepAlgo_Cut
from OCC.Core.IntAna import IntAna_IntConicQuad, IntAna_QuadQuadGeo

import os
from random import randint
from OCC.Display.WebGl.jupyter_renderer import JupyterRenderer, format_color
from OCC.Extend.TopologyUtils import TopologyExplorer
from OCC.Extend.DataExchange import read_step_file

shp = read_step_file(os.path.join('Assem3.stp'))
my_renderer = JupyterRenderer(size=(700, 700))

# loop over subshapes so that each subshape is meshed/displayed
t = TopologyExplorer(shp)

if (t.number_of_solids()!=1):
    for solid in t.solids():
        for solid2 in t.solids():
            if (solid != solid2):
                collision = BRepAlgo_Common(solid, solid2)
                if (BRepAlgoAPI_Check.IsValid(collision)):
                    print("colliding")

RuntimeError: Standard_ConstructionError Geom_TrimmedCurve::U1 == U2 wrapper details:

jf--- commented 4 years ago

Does it take meshes ?

Yes indeed...

and what about using vtk or cgal to perform collision detection ?

CGAL is a master piece of engineering, but challenging to install, and the python lib is a subset of the C++ API. CGAL has a focus on exactness rather than speed.

FCL is a library from the ROS project, and is specifically focussed on collision detection and highly optimized for this task. The cool thing is that you can also give a margin ( think offset ) for collisions to occur within and it performs sweeping; testing collisions during a transformation.

In contrast to vtk and CGAL, FCL is a specialised on collision detection. Where the respective focus of vtk and CGAL is visualization and (discrete) geometry

jf--- commented 4 years ago

When I play the following code seems that I can't split the solids inside the STEP file. Any clue?

kind of looks like solid != solid2 is not doing what is expected

Are you processing non-manifold geometry? Eg, a box within a box that doesn't intersect.

joaoprioli commented 4 years ago

Are you processing non-manifold geometry? Eg, a box within a box that doesn't intersect.

Not in this case, I using two manifold geometries that intersects in part. The interesting thing is when I change the function to BRepAlgoAPI_Common() it worked but I can't check the validity of the intersection, follows the code:

from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
from OCC.Core import TopoDS
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Common
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Check
from OCC.Core.BRepClass3d import BRepClass3d_SolidExplorer, BRepClass3d_SolidClassifier
from OCC.Core.BRepAlgo import BRepAlgo_BooleanOperation, BRepAlgo_Common, BRepAlgo_Cut
from OCC.Core.IntAna import IntAna_IntConicQuad, IntAna_QuadQuadGeo

import os
from random import randint
from OCC.Display.WebGl.jupyter_renderer import JupyterRenderer, format_color
from OCC.Extend.TopologyUtils import TopologyExplorer
from OCC.Extend.DataExchange import read_step_file
from OCC.Core.BRepCheck import BRepCheck_Analyzer

shp = read_step_file(os.path.join('Assem3.stp'))
my_renderer = JupyterRenderer(size=(700, 700))

t = TopologyExplorer(shp)

h = BRepClass3d_SolidExplorer(shp)
print(h)

if (t.number_of_solids()!=1):
    for solid in t.solids():
        for solid2 in t.solids():
            if (solid != solid2):
                collision = BRepAlgoAPI_Common(solid, solid2)
                check_coli = BRepAlgoAPI_Check(collision)
                if not check_coli.IsValid():
                    print('Shape is not valid. Attempting to fix...')
                elif check_coli.IsValid():
                    print("colliding")

I get the follow error: TypeError: in method 'new_BRepAlgoAPI_Check', argument 1 of type 'TopoDS_Shape const &'

johannesmichael commented 4 years ago

BTW, I found a quick solution for my problem: Bnd_Box.IsOut() https://dev.opencascade.org/doc/refman/html/class_bnd___box.html#a66bc2b9bf21ddc0bfe74db79a6bcf5b5

bbox_check.IsOut(bbox) is False if the bbox_check is completly or is partly inside.

Thanks for helping!

tpaviot commented 4 years ago

@johannesmichael You could also use the BRepExtrema_DistShapeShape class to compute the minimal distance between two solids. If this distance is greater than an epsilon you define, then you ensure there's no collision.Otherwise returns 0.0. See an example here https://github.com/tpaviot/pythonocc-demos/blob/master/examples/core_geometry_minimal_distance.py

Using the minimal distance to detect collision might be a good balance between accuracy and computation time/memory cost. Can you please report your result if you compare both solutions ?

johannesmichael commented 4 years ago

@tpaviot I looked at the Bnd_Box.Distance(), but since I am only interested in the objects that are inside a space, this was not of more value for me. But I will evaluate your aproach and report back.

joaoprioli commented 4 years ago

You could also use the BRepExtrema_DistShapeShape

@tpaviot , I used the BRepExtrema_DistShapeShape class to compute the collision, it works for most of the situations, but it still don't tell if the shape is trespassing or just in direct contact with the other. Do you have some idea to differ from trespassing and contact?

krontzo commented 4 years ago

Hi, I have recently found BRepExtrema. It has collision detection.

Check a function I've prepared:

def test_proximity(shape_a, shape_b):
    aMesher = BRepMesh_IncrementalMesh(shape_a, 1.0);
    aMesher = BRepMesh_IncrementalMesh(shape_b, 1.0);
    proximity = BRepExtrema_ShapeProximity(shape_a, shape_b)
    proximity.Perform()

    shapes_in_contact_a = []
    shapes_in_contact_b = []
    if proximity.IsDone():
        print('ShapeProximity is done')
        subs1 = proximity.OverlapSubShapes1().Keys()
        nb_subs1 = proximity.OverlapSubShapes1().Size()
        print(f'subshapes1: {subs1} {nb_subs1}')
        subs2 = proximity.OverlapSubShapes2().Keys()
        nb_subs2 = proximity.OverlapSubShapes2().Size()
        print(f'subshapes2: {subs2} {nb_subs2}')
        for sa in subs1:
            temp = translate_shp(proximity.GetSubShape1(sa), gp_Vec(0, 0, 5))
            shapes_in_contact_a.append(temp)
        for sa in subs2:
            temp = translate_shp(proximity.GetSubShape2(sa), gp_Vec(0, 0, 5))
            shapes_in_contact_b.append(temp)

    else:
        print('No contact or interference')

    aFaceList = BRepExtrema_ShapeList()
    ex = TopExp_Explorer(shape_b, TopAbs_FACE)
    while ex.More():
        #print('cara..')
        aFaceList.Append(topods_Face(ex.Current()))
        ex.Next()

    aTriangleSet = BRepExtrema_TriangleSet(aFaceList)

    from OCC.Display.SimpleGui import init_display
    display, start_display, add_menu, add_function_to_menu = init_display()
    display.SetSelectionModeVertex() # review

    display.DisplayShape(shape_a, update=False, transparency=0.)
    display.DisplayShape(shape_b, update=True, transparency=0.1)
    display.DisplayShape(shapes_in_contact_a, color='red')
    display.DisplayShape(shapes_in_contact_b, color='blue', transparency=0.3)
    start_display()

The translation is for visualization purposes.

BTW, I made a wrapper for PQP (PQP: Fast Proximity Queries with Swept Sphere Volumes) https://gamma.cs.unc.edu/SSV/; but I liked FCL better when working with OpenCASCADE in C++. But the python wrapper for Win32 gave false results.

I think BRepExtrema_ShapeProximity might be enough for your use case.

wzl-muenker commented 3 years ago

Well you can access the tesselation of your TopoDS_* and infer intersections via FCL.

@jf--- Your approach is very interesting for the app I'm currently working on. I'm trying to implement this by using the ShapeTesselator. Firstly I extract vertices & triangles from the Tesselator, than I create a mesh using fcl.BVHModel(). Generelly works, but I didn't find a proper way to validate visually. So I rendered meshes using Trimesh for visualization purposes. Seems like my meshes are very inaccurate and seem to have some wrong faces.

def get_tessellation(shape, mesh_quality):
    tess = ShapeTesselator(shape)
    tess.Compute(mesh_quality=mesh_quality)

    vertices_position = tess.GetVerticesPositionAsTuple()

    number_of_vertices = len(vertices_position)
    number_of_triangles = tess.ObjGetTriangleCount()

    vertices = np.array(vertices_position).reshape(int(number_of_vertices / 3), 3)

    triangles = []
    for triangle in range(0, number_of_triangles):
        i1, i2, i3 = tess.GetTriangleIndex(triangle)
        triangles.append([i1, i2, i3])

    return vertices, triangles

def create_mesh(vertices, triangles):
    m = fcl.BVHModel()
    m.beginModel(len(vertices), len(triangles))
    m.addSubModel(vertices, triangles)
    m.endModel()
    return m

This is what the shape looks like: grafik

This is my result showing the Trimesh Visualization: grafik

jf--- commented 3 years ago

hi @wzl-muenker, so rather than creating another mesh, I explore the mesh that's representing the shape in the viewer, with BRep_Tool_Triangulation, and IIRC, BRepMesh_IncrementalMesh updates the existing mesh to the parameters supplied in the call to the function ( I'm not certain though, its been a while )

Note that some discrepancies between the brep and the fcl.BVHModel mesh might occur, due to the requirement of convexity. If that's not acceptable, probably a per-subshape collision detection is required.

The following might help.

def mesh_from_brep(occ_brep, theLinDeflection=0.8):
    """
    returns a list of triangles that represent the mesh, represening the `occ_brep` BRep
    """
    # create / update a mesh
    # this is important when a mesh has not been display
    # in this case it has no mesh to iterate through
    inc_mesh = BRepMesh_IncrementalMesh(occ_brep, theLinDeflection) 
    assert inc_mesh.IsDone()

    tp = Topo(occ_brep, ignore_orientation=True)
    triangles = collections.deque()

    for f in tp.faces():
        loc = TopLoc_Location()
        triangulation = BRep_Tool_Triangulation(f, loc)

        if triangulation.IsNull():
            continue

        facing = triangulation.GetObject()
        tri = facing.Triangles()
        nodes = facing.Nodes()

        for i in range(1, facing.NbTriangles() + 1):
            trian = tri.Value(i)
            index1, index2, index3 = trian.Get()
            tria=nodes.Value(index1) #.Coord()
            trib=nodes.Value(index2) #.Coord()
            tric=nodes.Value(index3) #.Coord()
            triangles.append(
                (   (tria.X(),tria.Y(),tria.Z()),
                    (trib.X(),trib.Y(),trib.Z()),
                    (tric.X(),tric.Y(),tric.Z()),
       )
    )
    return triangles

def fcl_collision_object_from_shape(shape):
    """
    create a fcl.BVHModel instance from the `shape` TopoDS_Shape
    """
    occ_mesh_tris = mesh_from_brep(shape)
    _mesh = fcl.BVHModel()
    n_tris = len(occ_mesh_tris)
    _mesh.beginModel(n_tris, n_tris * 3)
    map(lambda x: _mesh.addTriangle(*x), occ_mesh_tris)
    _mesh.endModel()
    return fcl.CollisionObject(_mesh)
wzl-muenker commented 3 years ago

Thanks you very much @jf--- ! Helped me a lot. I combined your code with the code from pythonocc-demos/examples/core_simple_mesh.py to get it running. Now I'm pretty confident, that I have correct & similar meshes for fcl collision check & for visualization.

Now I just have two more issues to solve:

  1. Automatically align parts of an assembly in global world coordinates by analysing transformations of single part's local coordinate systems to each other (Does anybody already has a solution for that?)
  2. Solve the initial issue from @joaoprioli with completely/partially inside components (what's more concerned as a fcl issue now). The situation throws me a collision, even though no surfaces are in contact.

grafik

dphilpott03 commented 3 years ago

Hi, sorry to start this thread again. I am a final year studied understanding a Masters in Mechanical Engineering, at present, I am studying the use of python and pythonocc concerning automating the pre-processing phase when converting from model to analysis. I have minimal experience with pythonocc as I have only started to explore it this year. I am wishing to identify which solids have collisions and also which face the collision occurs on, I am aware this may be a simple solution, I was wondering if there was the possibility for some guidance or to obtain some existing code. I am working from STEP files at present and would ideally like to iterate through the solids initially to identify if there is a collision then to identify which face the collision occurs on.

Many thanks in advance,

tpaviot commented 3 years ago

In order to look for clashes, you can for instance compute the minimal distance for each couple of TopoDS_Shape or TopoDS_Compound available from the STEP file. This will be time and memory consuming, but that's possible. See demo code at https://github.com/tpaviot/pythonocc-demos/blob/4832a91404272f2da27bb2d454718e78e9512a35/examples/core_geometry_minimal_distance.py

If the minimal distance is < 0, then the two shapes collide (I guess, this should be tested, please report any related feedback).

dphilpott03 commented 3 years ago

Hi, I have previously tried this method and found that it is good for showing a gap is present. However, I found that through using the BRepExtrema module I can obtain collisions reliably. I have tried to use the minimal distance script to determine the overlap of interfering shapes. I have found that the minimal distance method will not return a value less than zero in my use case, where there are any collisions present the minimal distance of the two shapes involved is always 0 regardless of overlap.