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

Getting the relationship of connected subparts from assembly step files. #1103

Open sandeshchand opened 2 years ago

sandeshchand commented 2 years ago

I have many parts in the assembly step files.Some parts are connected to other parts and some are not connected.(For example A table has four legs and one flat surface.All legs are connected to the flat surface.Relationship between each leg with flat surface).I want to make a relationship of parts which are directly connected to each other.how could i achieve my goal using pythonocc.It would be great help.Thanks

cafhach commented 2 years ago

Open CASCADE describes a closed body as a "solid". Solids can share edges and faces with other solids. It's not guaranteed though that your input data actually describes shared faces/edges as such or even that your input data contains solids and not just a bag of faces. If all these assumptions apply, here's a possible solution to find all connections between solids (haven't testet it):

Here's a sketch how the code could look like:

from OCC.Extend import TopologyUtils
from OCC.Core.TopoDS import TopAbs_FORWARD
from itertools import combinations
exp_solids = TopologyUtils.TopologyExplorer(root_shape_of_assembly)
edge_sets = {}
for solid in exp_solids.solids():
    edge_set = set()
    exp_solid = TopologyUtils.TopologyExplorer(solid)
    for edge in exp_solid.edges():
        edge_set.insert(edge.Oriented(TopAbs_FORWARD)
    edge_sets[solid] = edge_set
for a,b in combinations(edge_sets, 2):
    es_a = edge_sets[a]
    es_b = edge_sets[b]
    if len(es_a & es_b) > 0:
        print(f"solids {a} and {b} are connected")
sandeshchand87 commented 2 years ago

Dear cafhach, Thanks for your code. i tried your code by updating some lines.but i cannot get the result.i have one asembled part which contains 4 subparts (solid).They are interrelated to each other. but the code doesnot shows the connected part.Sorry, due to some reasons , i cann#t supply my step files. Could you please check the code and test the program from your side with some sample step files. It would be great help.

from OCC.Extend import TopologyUtils

from OCC.Core.TopoDS import TopAbs_FORWARD

from OCC.Core.TopAbs import TopAbs_FORWARD from itertools import combinations file=r"D:\a.stp" shp = read_step_file(os.path.join(file)) exp_solids = TopologyUtils.TopologyExplorer(shp)

print("root",exp_solids)

edge_sets = {} for solid in exp_solids.solids(): edge_set = set() exp_solid = TopologyUtils.TopologyExplorer(solid)

print("jonty",exp_solid)

  for edge in exp_solid.edges():
      edge_set.add(edge.Oriented(TopAbs_FORWARD))
      #edge_set.insert(edge.Oriented(TopAbs_FORWARD))
  edge_sets[solid] = edge_set

for a,b in combinations(edge_sets, 2): es_a = edge_sets[a] es_b = edge_sets[b] if len(es_a & es_b) > 0: print(f"solids {a} and {b} are connected")

cafhach commented 2 years ago

Hi @sandeshchand87 ,

to check if my assumptions hold at all I changed your code to check for connected faces by changing the line "for solid in exp_solids.solids():" into "for solid in exp_solids.faces():". In this case for my example step files the code lists a couple of connections. Thus I would assume that the solids are not actually exported by your CAD software as connected solids.

One possible next step would be to see if you can find a similarity metric which allows you to identify the faces or edges which are considered separate but actually coincide. One simple way could be to use the positions of the two vertices describing the start and end point of each edge respectively.

from OCC.Extend import TopologyUtils
from OCC.Extend.DataExchange import read_step_file
from itertools import combinations
from OCC.Core.ShapeAnalysis import ShapeAnalysis_Edge
from OCC.Core.BRep import BRep_Tool
file=r"/tmp/stp.stp"
shp = read_step_file(file)
exp_solids = TopologyUtils.TopologyExplorer(shp)

edge_sets = {}
for solid in exp_solids.solids():
    edge_set = set()
    exp_solid = TopologyUtils.TopologyExplorer(solid)
    for edge in exp_solid.edges():
        edgeana = ShapeAnalysis_Edge()
        if edgeana.HasCurve3d(edge):
            pnt_start = BRep_Tool.Pnt(edgeana.FirstVertex(edge))
            pnt_stop  = BRep_Tool.Pnt(edgeana.LastVertex(edge))
            # round as an attempt to deal with small numerical differences. But might
            # as well be counterproductive
            start = tuple(round(getattr(pnt_start, p)(), 2) for p in ("X", "Y", "Z"))
            stop  = tuple(round(getattr(pnt_start, p)(), 2) for p in ("X", "Y", "Z"))
            edge_set.add(frozenset({start, stop}))

    edge_sets[solid] = edge_set

for a,b in combinations(edge_sets, 2):
    es_a = edge_sets[a]
    es_b = edge_sets[b]
    if len(es_a & es_b) > 0:
        print(f"solids {a} and {b} are connected")
Tanneguydv commented 2 years ago

Good thing to use set() !

if I may, I think that using frozenset get the last iteration difficult to perform, no ?

A fix could be done as this, but it is far from perfect, and I have added a display to help debugging

from OCC.Extend import TopologyUtils
from OCC.Extend.DataExchange import read_step_file
from itertools import combinations
from OCC.Core.ShapeAnalysis import ShapeAnalysis_Edge
from OCC.Core.BRep import BRep_Tool
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeSphere
from OCC.Core.gp import gp_Pnt

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

file = "example.stp"
shp = read_step_file(file)
display.DisplayShape(shp)
exp_solids = TopologyUtils.TopologyExplorer(shp)

edge_sets = {}
for solid in exp_solids.solids():
    edge_set = set()
    exp_solid = TopologyUtils.TopologyExplorer(solid)
    for edge in exp_solid.edges():
        edgeana = ShapeAnalysis_Edge()
        if edgeana.HasCurve3d(edge):
            pnt_start = BRep_Tool.Pnt(edgeana.FirstVertex(edge))
            pnt_stop  = BRep_Tool.Pnt(edgeana.LastVertex(edge))
            # round as an attempt to deal with small numerical differences. But might
            # as well be counterproductive
            start = tuple(round(getattr(pnt_start, p)(), 2) for p in ("X", "Y", "Z"))
            stop = tuple(round(getattr(pnt_stop, p)(), 2) for p in ("X", "Y", "Z"))
            edge_set.add(start)
            edge_set.add(stop)

    edge_sets[solid] = edge_set

for a, b in combinations(edge_sets, 2):
    es_a = edge_sets[a]
    es_b = edge_sets[b]
    if len(es_a & es_b) > 0:
        print(f"solids {a} and {b} are connected")
        p = es_a.intersection(es_b)
        for pt in p:
            point = gp_Pnt(pt[0], pt[1], pt[2])
            sphere = BRepPrimAPI_MakeSphere(point, 1).Shape()
            display.DisplayShape(sphere, color='BLUE')

start_display()
sandeshchand commented 2 years ago

Hi @sandeshchand87 ,

to check if my assumptions hold at all I changed your code to check for connected faces by changing the line "for solid in exp_solids.solids():" into "for solid in exp_solids.faces():". In this case for my example step files the code lists a couple of connections. Thus I would assume that the solids are not actually exported by your CAD software as connected solids.

One possible next step would be to see if you can find a similarity metric which allows you to identify the faces or edges which are considered separate but actually coincide. One simple way could be to use the positions of the two vertices describing the start and end point of each edge respectively.

from OCC.Extend import TopologyUtils
from OCC.Extend.DataExchange import read_step_file
from itertools import combinations
from OCC.Core.ShapeAnalysis import ShapeAnalysis_Edge
from OCC.Core.BRep import BRep_Tool
file=r"/tmp/stp.stp"
shp = read_step_file(file)
exp_solids = TopologyUtils.TopologyExplorer(shp)

edge_sets = {}
for solid in exp_solids.solids():
    edge_set = set()
    exp_solid = TopologyUtils.TopologyExplorer(solid)
    for edge in exp_solid.edges():
        edgeana = ShapeAnalysis_Edge()
        if edgeana.HasCurve3d(edge):
            pnt_start = BRep_Tool.Pnt(edgeana.FirstVertex(edge))
            pnt_stop  = BRep_Tool.Pnt(edgeana.LastVertex(edge))
            # round as an attempt to deal with small numerical differences. But might
            # as well be counterproductive
            start = tuple(round(getattr(pnt_start, p)(), 2) for p in ("X", "Y", "Z"))
            stop  = tuple(round(getattr(pnt_start, p)(), 2) for p in ("X", "Y", "Z"))
            edge_set.add(frozenset({start, stop}))

    edge_sets[solid] = edge_set

for a,b in combinations(edge_sets, 2):
    es_a = edge_sets[a]
    es_b = edge_sets[b]
    if len(es_a & es_b) > 0:
        print(f"solids {a} and {b} are connected")

Thanks a lot.I have applied on my code.Unfortunately, my stepfile is exported from cad sotware with all connection in solid.I got some connection results but not perfect.My solid contains 4 subparts.There are 3 connections but i am getting only 2 connections using above code.Is there any thing missing??Can we get the internal connection or internal egdes of subparts?Thanks

Tanneguydv commented 2 years ago

I guess that when your speaking about internal connection it means that there are no actual connections between edges or vertex of your 2 solids? Perhaps a boolean operation could get you the result that you expect? example :

from OCC.Extend import TopologyUtils
from OCC.Extend.DataExchange import read_step_file
from itertools import combinations
from OCC.Core.ShapeAnalysis import ShapeAnalysis_Edge
from OCC.Core.BRep import BRep_Tool
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Section
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeSphere
from OCC.Core.gp import gp_Pnt
from OCC.Core.TopAbs import TopAbs_VERTEX
from OCC.Core.TopoDS import topods_Vertex
from OCC.Core.TopExp import TopExp_Explorer

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

file = "table_sphere_cylinder.stp"
shp = read_step_file(file)
display.DisplayShape(shp)
exp_solids = TopologyUtils.TopologyExplorer(shp)

solids = []
edge_sets = {}
for solid in exp_solids.solids():
    solids.append(solid)
    edge_set = set()
    exp_solid = TopologyUtils.TopologyExplorer(solid)
    for edge in exp_solid.edges():
        edgeana = ShapeAnalysis_Edge()
        if edgeana.HasCurve3d(edge):
            pnt_start = BRep_Tool.Pnt(edgeana.FirstVertex(edge))
            pnt_stop  = BRep_Tool.Pnt(edgeana.LastVertex(edge))
            # round as an attempt to deal with small numerical differences. But might
            # as well be counterproductive
            start = tuple(round(getattr(pnt_start, p)(), 2) for p in ("X", "Y", "Z"))
            stop = tuple(round(getattr(pnt_stop, p)(), 2) for p in ("X", "Y", "Z"))
            edge_set.add(start)
            edge_set.add(stop)

    edge_sets[solid] = edge_set

def edge_ana_method(edge_sets):
    for a, b in combinations(edge_sets, 2):
        es_a = edge_sets[a]
        es_b = edge_sets[b]
        if len(es_a & es_b) > 0:
            print(f"solids {a} and {b} are connected")
            p = es_a.intersection(es_b)
            for pt in p:
                point = gp_Pnt(pt[0], pt[1], pt[2])
                sphere = BRepPrimAPI_MakeSphere(point, 1).Shape()
                display.DisplayShape(sphere, color='BLUE')

def bool_method(solids):
    for two_solids in combinations(solids, 2):
        basis = two_solids[0]
        cutter = two_solids[1]
        result = BRepAlgoAPI_Section(basis, cutter).Shape()
        topExp_vertex = TopExp_Explorer()
        topExp_vertex.Init(result, TopAbs_VERTEX)
        vertices = []
        while topExp_vertex.More():
            vert = topods_Vertex(topExp_vertex.Current())
            point = BRep_Tool.Pnt(vert)
            vertices.append(point)
            topExp_vertex.Next()
        if len(vertices) > 0:
            print(f"solids {basis} and {cutter} are connected")
        for v in vertices:
            sphere2 = BRepPrimAPI_MakeSphere(v, 1).Shape()
            display.DisplayShape(sphere2, color='RED')

bool_method(solids)
# edge_ana_method(edge_sets)

start_display()

when running bool_method(solids) I get : bool_method

when running edge_ana_method(edge_sets) I get: edge_ana

It's certainly not the best way to proceed though it works to detect connections between solids

sandeshchand87 commented 2 years ago

Dear Tanneeguydv, Thanks alot. the above solution provides more connection information as compare to previous one.I hope it helps me alot. Thanks for your support.