mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +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.settings = settings
|
||||||
self.geom_settings = ifcopenshell.geom.settings()
|
self.geom_settings = ifcopenshell.geom.settings()
|
||||||
self.clash_sets = []
|
self.clash_sets = []
|
||||||
#self.tolerance = 0.01
|
self.clash_data = {'meshes': {}}
|
||||||
|
self.global_data = {'meshes': {}, 'matrices': {}}
|
||||||
|
|
||||||
def clash(self):
|
def clash(self):
|
||||||
for clash_set in self.clash_sets:
|
for clash_set in self.clash_sets:
|
||||||
@@ -62,10 +63,62 @@ class IfcClasher:
|
|||||||
b = self.get_element(clash_set['a'], b_global_id)
|
b = self.get_element(clash_set['a'], b_global_id)
|
||||||
if contact.raw.penetration_depth < tolerance:
|
if contact.raw.penetration_depth < tolerance:
|
||||||
continue
|
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}'
|
key = f'{a_global_id}-{b_global_id}'
|
||||||
|
|
||||||
if key in clash_set['clashes'] \
|
if key in clash_set['clashes'] \
|
||||||
and clash_set['clashes'][key]['penetration_depth'] > contact.raw.penetration_depth:
|
and clash_set['clashes'][key]['penetration_depth'] > contact.raw.penetration_depth:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
clash_set['clashes'][key] = {
|
clash_set['clashes'][key] = {
|
||||||
'a_global_id': a_global_id,
|
'a_global_id': a_global_id,
|
||||||
'b_global_id': b_global_id,
|
'b_global_id': b_global_id,
|
||||||
@@ -78,6 +131,24 @@ class IfcClasher:
|
|||||||
'penetration_depth': contact.raw.penetration_depth
|
'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):
|
def export(self):
|
||||||
results = self.clash_sets.copy()
|
results = self.clash_sets.copy()
|
||||||
for result in results:
|
for result in results:
|
||||||
@@ -85,9 +156,8 @@ class IfcClasher:
|
|||||||
del result['b_cm']
|
del result['b_cm']
|
||||||
for ab in ['a', 'b']:
|
for ab in ['a', 'b']:
|
||||||
for data in result[ab]:
|
for data in result[ab]:
|
||||||
for key in ['ifc', 'meshes']:
|
if 'ifc' in data:
|
||||||
if key in data:
|
del data['ifc']
|
||||||
del data[key]
|
|
||||||
with open(self.settings.output, 'w', encoding='utf-8') as clashes_file:
|
with open(self.settings.output, 'w', encoding='utf-8') as clashes_file:
|
||||||
json.dump(results, clashes_file, indent=4)
|
json.dump(results, clashes_file, indent=4)
|
||||||
|
|
||||||
@@ -119,7 +189,7 @@ class IfcClasher:
|
|||||||
data['ifc'].remove(element)
|
data['ifc'].remove(element)
|
||||||
|
|
||||||
def add_collision_objects(self, data, cm):
|
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())
|
iterator = ifcopenshell.geom.iterator(self.geom_settings, data['ifc'], multiprocessing.cpu_count())
|
||||||
valid_file = iterator.initialize()
|
valid_file = iterator.initialize()
|
||||||
if not valid_file:
|
if not valid_file:
|
||||||
@@ -140,11 +210,12 @@ class IfcClasher:
|
|||||||
element = data['ifc'].by_id(shape.guid)
|
element = data['ifc'].by_id(shape.guid)
|
||||||
self.settings.logger.info('Creating object {}'.format(element))
|
self.settings.logger.info('Creating object {}'.format(element))
|
||||||
mesh_name = f'mesh-{shape.geometry.id}'
|
mesh_name = f'mesh-{shape.geometry.id}'
|
||||||
if mesh_name in data['meshes']:
|
if mesh_name in self.clash_data['meshes']:
|
||||||
mesh = data['meshes'][mesh_name]
|
mesh = self.clash_data['meshes'][mesh_name]
|
||||||
else:
|
else:
|
||||||
mesh = self.create_mesh(shape)
|
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
|
m = shape.transformation.matrix.data
|
||||||
mat = np.array(
|
mat = np.array(
|
||||||
@@ -155,7 +226,9 @@ class IfcClasher:
|
|||||||
[0, 0, 0, 1]
|
[0, 0, 0, 1]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
mat.transpose()
|
mat.transpose()
|
||||||
|
self.global_data['matrices'][shape.guid] = mat
|
||||||
cm.add_object(shape.guid, mesh, mat)
|
cm.add_object(shape.guid, mesh, mat)
|
||||||
|
|
||||||
def create_mesh(self, shape):
|
def create_mesh(self, shape):
|
||||||
|
|||||||
Reference in New Issue
Block a user