mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-05 23:41:44 +00:00
IFC clash now optimistically skips clashes that are likely to be merely coincident, thus resulting in less false positives
This commit is contained in:
Executable → Regular
+81
-8
@@ -22,7 +22,8 @@ class IfcClasher:
|
||||
self.settings = settings
|
||||
self.geom_settings = ifcopenshell.geom.settings()
|
||||
self.clash_sets = []
|
||||
#self.tolerance = 0.01
|
||||
self.clash_data = {'meshes': {}}
|
||||
self.global_data = {'meshes': {}, 'matrices': {}}
|
||||
|
||||
def clash(self):
|
||||
for clash_set in self.clash_sets:
|
||||
@@ -62,10 +63,62 @@ class IfcClasher:
|
||||
b = self.get_element(clash_set['a'], b_global_id)
|
||||
if contact.raw.penetration_depth < tolerance:
|
||||
continue
|
||||
|
||||
# fcl returns contact data for faces that aren't actually
|
||||
# penetrating, but just touching. If our tolerance is zero, then we
|
||||
# consider these as clashes and we move on. If our tolerance is not
|
||||
# zero, fcl has a strange behaviour where the penetration depth can
|
||||
# be a large number even though objects are just touching
|
||||
# https://github.com/flexible-collision-library/fcl/issues/503 In
|
||||
# this case, I don't trust the penetration depth and I run my own
|
||||
# triangle-triangle intersection test. Optimistically, this skips
|
||||
# the false positives. Conservatively, we let the user manually deal
|
||||
# with the false positives and we mark it as a clash.
|
||||
is_optimistic = True # TODO: let user configure this
|
||||
|
||||
if is_optimistic and tolerance != 0:
|
||||
# We'll now check if the contact data's two faces are actually
|
||||
# intersecting, using this brute force check:
|
||||
# https://stackoverflow.com/questions/7113344/find-whether-two-triangles-intersect-or-not
|
||||
# I'm not very good at this kind of code. If you know this stuff
|
||||
# please help rewrite this.
|
||||
|
||||
# Get vertices of clashing tris
|
||||
p1 = self.global_data['meshes'][contact.names[0]].faces[contact.index(contact.names[0])]
|
||||
p2 = self.global_data['meshes'][contact.names[1]].faces[contact.index(contact.names[1])]
|
||||
m1 = self.global_data['matrices'][contact.names[0]]
|
||||
m2 = self.global_data['matrices'][contact.names[1]]
|
||||
v1 = []
|
||||
v2 = []
|
||||
|
||||
for v in p1:
|
||||
v1.append((m1 @ np.array([*self.global_data['meshes'][contact.names[0]].vertices[v], 1]))[0:3].round(2))
|
||||
for v in p2:
|
||||
v2.append((m2 @ np.array([*self.global_data['meshes'][contact.names[1]].vertices[v], 1]))[0:3].round(2))
|
||||
|
||||
tri1_x = 0
|
||||
tri2_x = 0
|
||||
tri1_x += 1 if self.intersect_line_triangle(v1[0], v1[1], v2[0], v2[1], v2[2]) is not None else 0
|
||||
tri1_x += 1 if self.intersect_line_triangle(v1[1], v1[2], v2[0], v2[1], v2[2]) is not None else 0
|
||||
tri1_x += 1 if self.intersect_line_triangle(v1[2], v1[0], v2[0], v2[1], v2[2]) is not None else 0
|
||||
|
||||
tri2_x += 1 if self.intersect_line_triangle(v2[0], v2[1], v1[0], v1[1], v1[2]) is not None else 0
|
||||
tri2_x += 1 if self.intersect_line_triangle(v2[1], v2[2], v1[0], v1[1], v1[2]) is not None else 0
|
||||
tri2_x += 1 if self.intersect_line_triangle(v2[2], v2[0], v1[0], v1[1], v1[2]) is not None else 0
|
||||
intersections = [tri1_x, tri2_x]
|
||||
if intersections == [0, 2] or intersections == [2, 0] or intersections == [1, 1]:
|
||||
# This is a penetrating collision
|
||||
pass
|
||||
else:
|
||||
# This is probably two triangles which just touch
|
||||
continue
|
||||
|
||||
key = f'{a_global_id}-{b_global_id}'
|
||||
|
||||
if key in clash_set['clashes'] \
|
||||
and clash_set['clashes'][key]['penetration_depth'] > contact.raw.penetration_depth:
|
||||
continue
|
||||
|
||||
clash_set['clashes'][key] = {
|
||||
'a_global_id': a_global_id,
|
||||
'b_global_id': b_global_id,
|
||||
@@ -78,6 +131,24 @@ class IfcClasher:
|
||||
'penetration_depth': contact.raw.penetration_depth
|
||||
}
|
||||
|
||||
# https://stackoverflow.com/questions/42740765/intersection-between-line-and-triangle-in-3d
|
||||
def intersect_line_triangle(self, q1, q2, p1, p2, p3):
|
||||
def signed_tetra_volume(a,b,c,d):
|
||||
return np.sign(np.dot(np.cross(b-a,c-a),d-a)/6.0)
|
||||
|
||||
s1 = signed_tetra_volume(q1,p1,p2,p3)
|
||||
s2 = signed_tetra_volume(q2,p1,p2,p3)
|
||||
|
||||
if s1 != s2:
|
||||
s3 = signed_tetra_volume(q1,q2,p1,p2)
|
||||
s4 = signed_tetra_volume(q1,q2,p2,p3)
|
||||
s5 = signed_tetra_volume(q1,q2,p3,p1)
|
||||
if s3 == s4 and s4 == s5:
|
||||
n = np.cross(p2-p1,p3-p1)
|
||||
t = -np.dot(q1,n-p1) / np.dot(q1,q2-q1)
|
||||
return q1 + t * (q2-q1)
|
||||
return None
|
||||
|
||||
def export(self):
|
||||
results = self.clash_sets.copy()
|
||||
for result in results:
|
||||
@@ -85,9 +156,8 @@ class IfcClasher:
|
||||
del result['b_cm']
|
||||
for ab in ['a', 'b']:
|
||||
for data in result[ab]:
|
||||
for key in ['ifc', 'meshes']:
|
||||
if key in data:
|
||||
del data[key]
|
||||
if 'ifc' in data:
|
||||
del data['ifc']
|
||||
with open(self.settings.output, 'w', encoding='utf-8') as clashes_file:
|
||||
json.dump(results, clashes_file, indent=4)
|
||||
|
||||
@@ -119,7 +189,7 @@ class IfcClasher:
|
||||
data['ifc'].remove(element)
|
||||
|
||||
def add_collision_objects(self, data, cm):
|
||||
data['meshes'] = {}
|
||||
self.clash_data['meshes'] = {}
|
||||
iterator = ifcopenshell.geom.iterator(self.geom_settings, data['ifc'], multiprocessing.cpu_count())
|
||||
valid_file = iterator.initialize()
|
||||
if not valid_file:
|
||||
@@ -140,11 +210,12 @@ class IfcClasher:
|
||||
element = data['ifc'].by_id(shape.guid)
|
||||
self.settings.logger.info('Creating object {}'.format(element))
|
||||
mesh_name = f'mesh-{shape.geometry.id}'
|
||||
if mesh_name in data['meshes']:
|
||||
mesh = data['meshes'][mesh_name]
|
||||
if mesh_name in self.clash_data['meshes']:
|
||||
mesh = self.clash_data['meshes'][mesh_name]
|
||||
else:
|
||||
mesh = self.create_mesh(shape)
|
||||
data['meshes'][mesh_name] = mesh
|
||||
self.clash_data['meshes'][mesh_name] = mesh
|
||||
self.global_data['meshes'][shape.guid] = mesh
|
||||
|
||||
m = shape.transformation.matrix.data
|
||||
mat = np.array(
|
||||
@@ -155,7 +226,9 @@ class IfcClasher:
|
||||
[0, 0, 0, 1]
|
||||
]
|
||||
)
|
||||
|
||||
mat.transpose()
|
||||
self.global_data['matrices'][shape.guid] = mat
|
||||
cm.add_object(shape.guid, mesh, mat)
|
||||
|
||||
def create_mesh(self, shape):
|
||||
|
||||
Reference in New Issue
Block a user