Hello. Could you share please files and complete sample. What is shape1/2.
Archived discussion
Performance Issues with BRepExtrema_DistShapeShape
Hi everyone,
I'm encountering a severe performance issue in our assembly graph generation code that uses OpenCASCADE. The function are_connected(shape1, shape2) is responsible for determining if two shapes are "connected" by computing the distance using BRepExtrema_DistShapeShape(shape1, shape2). However, for some STEP files—even some relatively small ones (around 0.5 MB)—this distance calculation can take anywhere from 5 to 10 minutes per iteration, whereas for larger files (around 5 MB) it might only take about 1 second per iteration.
The problem is significant because if I omit this distance check, the resulting graph loses many connections, leading to an incomplete or incorrect assembly graph.
Here's a simplified excerpt of the problematic part:
def are_connected(shape1, shape2):
# Compute tolerance based on bounding box dimensions
tolerance = max(get_tolerance(shape1), get_tolerance(shape2))
# This is where the delay happens on some STEP files
dist_tool = BRepExtrema_DistShapeShape(shape1, shape2)
if dist_tool.IsDone() and dist_tool.Value() <= tolerance:
return True
# Fallback to vertex check if needed
return fallback_vertex_check(shape1, shape2, tolerance)
Discussion
2 archived replies
Posts are displayed in their archived order.
Hi sorry for the late reply,
This is how I get the shapes from stepfile;
class StepFile:
def __init__(self, filename):
self.filename = filename
self.parts = []
self.main_shape = None
self.unnamed_counter = 0
def _get_unique_name(self, name):
if not name or name.lower() in ['noname', 'unnamed', 'untitled', '']:
name = f"unnamed_{self.unnamed_counter}"
self.unnamed_counter += 1
return name
def read(self):
if not os.path.isfile(self.filename):
raise FileNotFoundError(f"{self.filename} not found.")
doc = TDocStd_Document("pythonocc-doc-step-import")
shape_tool = XCAFDoc_DocumentTool.ShapeTool(doc.Main())
step_reader = STEPCAFControl_Reader()
step_reader.SetNameMode(True)
status = step_reader.ReadFile(self.filename)
if status != IFSelect_RetDone:
raise ValueError("Error parsing STEP file")
ok = step_reader.Transfer(doc)
if not ok:
raise ValueError("Transfer failed")
output_shapes = {}
locs = []
def _get_shapes():
labels = TDF_LabelSequence()
shape_tool.GetShapes(labels)
for i in range(1, labels.Length() + 1):
label = labels.Value(i)
if not shape_tool.IsSimpleShape(label):
continue
shape = shape_tool.GetShape(label)
if shape.IsNull():
continue
name = ''
name = self._get_unique_name(name)
# Get location directly from the shape
loc = shape.Location()
if not loc.IsIdentity():
shape_to_loc = BRepBuilderAPI_Transform(shape, loc.Transformation())
shape = shape_to_loc.Shape()
output_shapes[shape] = name
locs.append(loc)
sub_shapes = TDF_LabelSequence()
if shape_tool.GetSubShapes(label, sub_shapes):
for j in range(1, sub_shapes.Length() + 1):
sub_label = sub_shapes.Value(j)
sub_shape = shape_tool.GetShape(sub_label)
if sub_shape.IsNull():
continue
sub_name = ''
sub_name = self._get_unique_name(sub_name)
# Get location directly from the subshape
sub_loc = sub_shape.Location()
if not sub_loc.IsIdentity():
sub_shape_to_loc = BRepBuilderAPI_Transform(sub_shape, sub_loc.Transformation())
sub_shape = sub_shape_to_loc.Shape()
output_shapes[sub_shape] = sub_name
_get_shapes()
self.parts = [(name, shape) for shape, name in output_shapes.items()]
self.parts.sort(key=lambda x: x[0])
shape_tool = XCAFDoc_DocumentTool.ShapeTool(doc.Main())
labels = TDF_LabelSequence()
shape_tool.GetFreeShapes(labels)
if labels.Length() > 0:
self.main_shape = shape_tool.GetShape(labels.Value(1))
else:
raise ValueError(“No shapes found in the transferred document”)
return self.parts, self.main_shape
then I filter the shapes with this:
@staticmethod
def is_valid_shape_type(shape):
“””Check if the shape is a valid solid, compound, shell, or face.”””
from OCC.Core.TopAbs import TopAbs_SOLID, TopAbs_COMPOUND, TopAbs_SHELL, TopAbs_FACE
if not isinstance(shape, TopoDS_Shape):
return False
stype = shape.ShapeType()
return stype in [TopAbs_SOLID, TopAbs_COMPOUND, TopAbs_SHELL, TopAbs_FACE]
and finally check the connection like this:
@staticmethod
def get_tolerance(shape):
“””
Calculate the appropriate tolerance for a shape.
“””
max_tolerance = 0.0
# Check the tolerance of edges
explorer = TopExp_Explorer(shape, TopAbs_EDGE)
while explorer.More():
edge = topods_Edge(explorer.Current())
tolerance = BRep_Tool.Tolerance(edge)
max_tolerance = max(max_tolerance, tolerance)
explorer.Next()
# Check the tolerance of faces
explorer = TopExp_Explorer(shape, TopAbs_FACE)
while explorer.More():
face = topods_Face(explorer.Current())
tolerance = BRep_Tool.Tolerance(face)
max_tolerance = max(max_tolerance, tolerance)
explorer.Next()
# If no valid tolerance found, use a default value
if max_tolerance <= 0.0:
max_tolerance = 0.01 # 0.01 mm is a common default tolerance in CAD
return max_tolerance
@staticmethod
def are_connected(shape1, shape2):
“””
Check if two shapes are connected to each other.
This method determines if two parts in an assembly are connected
by calculating the minimum distance between them and comparing it
to their combined tolerance values.
Args:
shape1: The first shape to check
shape2: The second shape to check
Returns:
bool: True if the shapes are connected, False otherwise
“””
try:
# Get the tolerances for the shapes
tolerance1 = ShapeUtils.get_tolerance(shape1)
tolerance2 = ShapeUtils.get_tolerance(shape2)
# Use BRepExtrema_DistShapeShape to calculate the minimum distance between two shapes
distance_calculator = BRepExtrema_DistShapeShape(shape1, shape2)
# If the computation fails, consider them not connected
if not distance_calculator.IsDone():
logging.warning(“Distance computation failed between shapes”)
return False
# Get the minimum distance
min_distance = distance_calculator.Value()
# Consider them connected if the minimum distance is less than the sum of their tolerances
# Add a small buffer to account for numerical precision issues
connection_threshold = tolerance1 + tolerance2 + 1e-6
return min_distance <= connection_threshold
except Exception as e:
logging.warning(f”Error checking connection between shapes: {str(e)}”)
return FalseThe problem is I am processing a big dataset and for some files BRepExtrema_DistShapeShape(shape1, shape2) this part runs forever. I tried with a timeout method and switch back to vertex based comparison as well but it did not help. I checked the dimensions, surface areas of shapes (simplify if too complex) still did not help. I am using pythonocc-core=7.8.1.1 and attached the problematic step file.