2019-12-05 17:28:34 +11:00
|
|
|
#!python
|
|
|
|
|
|
2020-05-13 22:08:30 +10:00
|
|
|
import collision
|
2019-12-01 22:04:28 +11:00
|
|
|
import ifcopenshell
|
|
|
|
|
import ifcopenshell.geom
|
2020-04-01 15:11:05 +11:00
|
|
|
import multiprocessing
|
2019-12-01 22:04:28 +11:00
|
|
|
import numpy as np
|
2019-12-01 22:17:23 +11:00
|
|
|
import json
|
2020-05-15 21:08:27 +10:00
|
|
|
import sys
|
2019-12-05 17:28:34 +11:00
|
|
|
import argparse
|
2020-04-01 15:11:05 +11:00
|
|
|
import logging
|
2019-12-01 22:04:28 +11:00
|
|
|
|
2020-05-13 22:08:30 +10:00
|
|
|
|
|
|
|
|
class Mesh:
|
|
|
|
|
faces: []
|
|
|
|
|
vertices: []
|
|
|
|
|
|
|
|
|
|
|
2019-12-01 22:04:28 +11:00
|
|
|
class IfcClasher:
|
2020-04-01 15:11:05 +11:00
|
|
|
def __init__(self, a_file, b_file, settings):
|
|
|
|
|
self.settings = settings
|
|
|
|
|
self.geom_settings = ifcopenshell.geom.settings()
|
2019-12-01 22:04:28 +11:00
|
|
|
self.tolerance = 0.01
|
|
|
|
|
self.a = None
|
|
|
|
|
self.b = None
|
2019-12-05 17:28:34 +11:00
|
|
|
self.a_file = a_file
|
|
|
|
|
self.b_file = b_file
|
2020-05-13 22:08:30 +10:00
|
|
|
self.clashes = {}
|
|
|
|
|
self.a_meshes = {}
|
|
|
|
|
self.b_meshes = {}
|
2019-12-01 22:04:28 +11:00
|
|
|
|
|
|
|
|
def clash(self):
|
2020-05-13 22:08:30 +10:00
|
|
|
for ab in ['a', 'b']:
|
|
|
|
|
self.settings.logger.info(f'Loading file {ab} ...')
|
|
|
|
|
setattr(self, ab, ifcopenshell.open(getattr(self, f'{ab}_file')))
|
2020-05-15 21:01:18 +10:00
|
|
|
self.patch_ifc(ab)
|
2020-05-13 22:08:30 +10:00
|
|
|
self.settings.logger.info(f'Purging unnecessary elements {ab} ...')
|
|
|
|
|
self.purge_elements(ab)
|
|
|
|
|
self.settings.logger.info(f'Creating collision manager {ab} ...')
|
|
|
|
|
setattr(self, f'{ab}_cm', collision.CollisionManager())
|
|
|
|
|
self.add_collision_objects(ab)
|
|
|
|
|
results = self.a_cm.in_collision_other(self.b_cm, return_data=True)
|
|
|
|
|
|
|
|
|
|
if not results[0]:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
for contact in results[1]:
|
|
|
|
|
a_global_id, b_global_id = contact.names
|
2019-12-01 22:17:23 +11:00
|
|
|
a = self.a.by_guid(a_global_id)
|
|
|
|
|
b = self.b.by_guid(b_global_id)
|
2020-05-13 22:08:30 +10:00
|
|
|
if contact.raw.penetration_depth < self.tolerance:
|
|
|
|
|
continue
|
|
|
|
|
self.clashes[f'{a_global_id}-{b_global_id}'] = {
|
2019-12-01 22:17:23 +11:00
|
|
|
'a_global_id': a_global_id,
|
|
|
|
|
'b_global_id': b_global_id,
|
|
|
|
|
'a_ifc_class': a.is_a(),
|
|
|
|
|
'b_ifc_class': b.is_a(),
|
|
|
|
|
'a_name': a.Name,
|
|
|
|
|
'b_name': b.Name,
|
2020-05-13 22:08:30 +10:00
|
|
|
'normal': list(contact.raw.normal),
|
|
|
|
|
'position': list(contact.raw.pos),
|
|
|
|
|
'penetration_depth': contact.raw.penetration_depth
|
|
|
|
|
}
|
2019-12-01 22:04:28 +11:00
|
|
|
|
2020-05-16 12:54:26 +10:00
|
|
|
def export(self):
|
|
|
|
|
with open(self.settings.output, 'w', encoding='utf-8') as clashes_file:
|
|
|
|
|
json.dump(list(self.clashes.values()), clashes_file, indent=4)
|
|
|
|
|
|
2020-05-13 22:08:30 +10:00
|
|
|
def purge_elements(self, ab):
|
|
|
|
|
# TODO: more filtering abilities
|
|
|
|
|
for element in getattr(self, ab).by_type('IfcSpace'):
|
|
|
|
|
getattr(self, ab).remove(element)
|
|
|
|
|
|
|
|
|
|
def add_collision_objects(self, ab):
|
2020-04-01 15:11:05 +11:00
|
|
|
self.settings.logger.info('Creating collision data for {}'.format(ab))
|
|
|
|
|
iterator = ifcopenshell.geom.iterator(self.geom_settings, getattr(self, ab), multiprocessing.cpu_count())
|
|
|
|
|
valid_file = iterator.initialize()
|
|
|
|
|
if not valid_file:
|
|
|
|
|
return False
|
|
|
|
|
old_progress = -1
|
|
|
|
|
while True:
|
|
|
|
|
progress = iterator.progress() // 2
|
|
|
|
|
if progress > old_progress:
|
|
|
|
|
print("\r[" + "#" * progress + " " * (50 - progress) + "]", end="")
|
|
|
|
|
old_progress = progress
|
2020-05-13 22:08:30 +10:00
|
|
|
self.add_collision_object(ab, iterator.get())
|
2020-04-01 15:11:05 +11:00
|
|
|
if not iterator.next():
|
|
|
|
|
break
|
|
|
|
|
|
2020-05-13 22:08:30 +10:00
|
|
|
def add_collision_object(self, ab, shape):
|
2020-04-01 15:11:05 +11:00
|
|
|
if shape is None:
|
|
|
|
|
return
|
|
|
|
|
element = getattr(self, ab).by_id(shape.guid)
|
|
|
|
|
self.settings.logger.info('Creating object {}'.format(element))
|
|
|
|
|
mesh_name = f'mesh-{shape.geometry.id}'
|
2020-05-13 22:08:30 +10:00
|
|
|
if mesh_name in getattr(self, f'{ab}_meshes'):
|
|
|
|
|
mesh = getattr(self, f'{ab}_meshes')[mesh_name]
|
2020-04-01 15:11:05 +11:00
|
|
|
else:
|
2020-05-13 22:08:30 +10:00
|
|
|
mesh = self.create_mesh(shape)
|
|
|
|
|
getattr(self, f'{ab}_meshes')[mesh_name] = mesh
|
2020-04-01 15:11:05 +11:00
|
|
|
|
|
|
|
|
m = shape.transformation.matrix.data
|
|
|
|
|
mat = np.array(
|
|
|
|
|
[
|
|
|
|
|
[m[0], m[3], m[6], m[9]],
|
|
|
|
|
[m[1], m[4], m[7], m[10]],
|
|
|
|
|
[m[2], m[5], m[8], m[11]],
|
|
|
|
|
[0, 0, 0, 1]
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
mat.transpose()
|
2020-05-13 22:08:30 +10:00
|
|
|
getattr(self, f'{ab}_cm').add_object(shape.guid, mesh, mat)
|
2019-12-01 22:04:28 +11:00
|
|
|
|
2020-05-13 22:08:30 +10:00
|
|
|
def create_mesh(self, shape):
|
2019-12-01 22:04:28 +11:00
|
|
|
f = shape.geometry.faces
|
|
|
|
|
v = shape.geometry.verts
|
2020-05-13 22:08:30 +10:00
|
|
|
mesh = Mesh()
|
|
|
|
|
mesh.vertices = np.array([[v[i], v[i + 1], v[i + 2]]
|
2019-12-01 22:04:28 +11:00
|
|
|
for i in range(0, len(v), 3)])
|
2020-05-13 22:08:30 +10:00
|
|
|
mesh.faces = np.array([[f[i], f[i + 1], f[i + 2]]
|
2019-12-01 22:04:28 +11:00
|
|
|
for i in range(0, len(f), 3)])
|
2020-05-13 22:08:30 +10:00
|
|
|
return mesh
|
|
|
|
|
|
2020-05-15 21:01:18 +10:00
|
|
|
def patch_ifc(self, ab):
|
|
|
|
|
project = getattr(self, ab).by_type('IfcProject')[0]
|
|
|
|
|
sites = self.find_decomposed_ifc_class(project, 'IfcSite')
|
|
|
|
|
for site in sites:
|
|
|
|
|
self.patch_placement_to_origin(site)
|
|
|
|
|
buildings = self.find_decomposed_ifc_class(project, 'IfcBuilding')
|
|
|
|
|
for building in buildings:
|
|
|
|
|
self.patch_placement_to_origin(building)
|
|
|
|
|
|
|
|
|
|
def find_decomposed_ifc_class(self, element, ifc_class):
|
|
|
|
|
results = []
|
|
|
|
|
rel_aggregates = element.IsDecomposedBy
|
|
|
|
|
if not rel_aggregates:
|
|
|
|
|
return results
|
|
|
|
|
for rel_aggregate in rel_aggregates:
|
|
|
|
|
for part in rel_aggregate.RelatedObjects:
|
|
|
|
|
if part.is_a(ifc_class):
|
|
|
|
|
results.append(part)
|
|
|
|
|
results.extend(self.find_decomposed_ifc_class(part, ifc_class))
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
def patch_placement_to_origin(self, element):
|
|
|
|
|
element.ObjectPlacement.RelativePlacement.Location.Coordinates = (0., 0., 0.)
|
|
|
|
|
if element.ObjectPlacement.RelativePlacement.Axis:
|
|
|
|
|
element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0., 0., 1.)
|
|
|
|
|
if element.ObjectPlacement.RelativePlacement.RefDirection:
|
|
|
|
|
element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1., 0., 0.)
|
|
|
|
|
|
2019-12-01 22:04:28 +11:00
|
|
|
|
2020-05-16 12:54:26 +10:00
|
|
|
class IfcClashSettings:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.logger = None
|
|
|
|
|
self.output = 'clashes.json'
|
|
|
|
|
|
|
|
|
|
|
2020-05-15 21:08:27 +10:00
|
|
|
if __name__ == '__main__':
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description='Clashes geometry between two IFC files')
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
'a',
|
|
|
|
|
type=str,
|
|
|
|
|
help='The IFC file containing group A of objects to clash')
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
'b',
|
|
|
|
|
type=str,
|
|
|
|
|
help='The IFC file containing group B of objects to clash')
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
'-o',
|
|
|
|
|
'--output',
|
|
|
|
|
type=str,
|
|
|
|
|
help='The JSON diff file to output. Defaults to clashes.json',
|
|
|
|
|
default='clashes.json')
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
settings = IfcClashSettings()
|
2020-05-16 12:54:26 +10:00
|
|
|
settings.output = args.output
|
2020-05-15 21:08:27 +10:00
|
|
|
settings.logger = logging.getLogger('Clash')
|
|
|
|
|
settings.logger.setLevel(logging.DEBUG)
|
|
|
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
|
|
|
handler.setLevel(logging.DEBUG)
|
|
|
|
|
settings.logger.addHandler(handler)
|
|
|
|
|
ifc_clasher = IfcClasher(args.a, args.b, settings)
|
|
|
|
|
ifc_clasher.clash()
|
2020-05-16 12:54:26 +10:00
|
|
|
ifc_clasher.export()
|