mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 22:50:21 +00:00
black ifcclash
This commit is contained in:
+36
-88
@@ -29,10 +29,7 @@ class ContactData(object):
|
|||||||
The contact in question.
|
The contact in question.
|
||||||
"""
|
"""
|
||||||
self.names = names
|
self.names = names
|
||||||
self._inds = {
|
self._inds = {names[0]: contact.b1, names[1]: contact.b2}
|
||||||
names[0]: contact.b1,
|
|
||||||
names[1]: contact.b2
|
|
||||||
}
|
|
||||||
self._point = contact.pos
|
self._point = contact.pos
|
||||||
self.raw = contact
|
self.raw = contact
|
||||||
|
|
||||||
@@ -83,14 +80,8 @@ class DistanceData(object):
|
|||||||
The distance query result.
|
The distance query result.
|
||||||
"""
|
"""
|
||||||
self.names = set(names)
|
self.names = set(names)
|
||||||
self._inds = {
|
self._inds = {names[0]: result.b1, names[1]: result.b2}
|
||||||
names[0]: result.b1,
|
self._points = {names[0]: result.nearest_points[0], names[1]: result.nearest_points[1]}
|
||||||
names[1]: result.b2
|
|
||||||
}
|
|
||||||
self._points = {
|
|
||||||
names[0]: result.nearest_points[0],
|
|
||||||
names[1]: result.nearest_points[1]
|
|
||||||
}
|
|
||||||
self._distance = result.min_distance
|
self._distance = result.min_distance
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -149,7 +140,7 @@ class CollisionManager(object):
|
|||||||
Initialize a mesh-mesh collision manager.
|
Initialize a mesh-mesh collision manager.
|
||||||
"""
|
"""
|
||||||
if fcl is None:
|
if fcl is None:
|
||||||
raise ValueError('No FCL Available!')
|
raise ValueError("No FCL Available!")
|
||||||
# {name: {geom:, obj}}
|
# {name: {geom:, obj}}
|
||||||
self._objs = {}
|
self._objs = {}
|
||||||
# {id(bvh) : str, name}
|
# {id(bvh) : str, name}
|
||||||
@@ -162,10 +153,7 @@ class CollisionManager(object):
|
|||||||
self._manager = fcl.DynamicAABBTreeCollisionManager()
|
self._manager = fcl.DynamicAABBTreeCollisionManager()
|
||||||
self._manager.setup()
|
self._manager.setup()
|
||||||
|
|
||||||
def add_object(self,
|
def add_object(self, name, mesh, transform=None):
|
||||||
name,
|
|
||||||
mesh,
|
|
||||||
transform=None):
|
|
||||||
"""
|
"""
|
||||||
Add an object to the collision manager.
|
Add an object to the collision manager.
|
||||||
|
|
||||||
@@ -187,7 +175,7 @@ class CollisionManager(object):
|
|||||||
transform = np.eye(4)
|
transform = np.eye(4)
|
||||||
transform = np.asanyarray(transform, dtype=np.float64)
|
transform = np.asanyarray(transform, dtype=np.float64)
|
||||||
if transform.shape != (4, 4):
|
if transform.shape != (4, 4):
|
||||||
raise ValueError('transform must be (4,4)!')
|
raise ValueError("transform must be (4,4)!")
|
||||||
|
|
||||||
# create or recall from cache BVH
|
# create or recall from cache BVH
|
||||||
bvh = self._get_BVH(mesh)
|
bvh = self._get_BVH(mesh)
|
||||||
@@ -198,8 +186,7 @@ class CollisionManager(object):
|
|||||||
# Add collision object to set
|
# Add collision object to set
|
||||||
if name in self._objs:
|
if name in self._objs:
|
||||||
self._manager.unregisterObject(self._objs[name])
|
self._manager.unregisterObject(self._objs[name])
|
||||||
self._objs[name] = {'obj': o,
|
self._objs[name] = {"obj": o, "geom": bvh}
|
||||||
'geom': bvh}
|
|
||||||
# store the name of the geometry
|
# store the name of the geometry
|
||||||
self._names[id(bvh)] = name
|
self._names[id(bvh)] = name
|
||||||
|
|
||||||
@@ -217,14 +204,14 @@ class CollisionManager(object):
|
|||||||
The identifier for the object
|
The identifier for the object
|
||||||
"""
|
"""
|
||||||
if name in self._objs:
|
if name in self._objs:
|
||||||
self._manager.unregisterObject(self._objs[name]['obj'])
|
self._manager.unregisterObject(self._objs[name]["obj"])
|
||||||
self._manager.update(self._objs[name]['obj'])
|
self._manager.update(self._objs[name]["obj"])
|
||||||
# remove objects from _objs
|
# remove objects from _objs
|
||||||
geom_id = id(self._objs.pop(name)['geom'])
|
geom_id = id(self._objs.pop(name)["geom"])
|
||||||
# remove names
|
# remove names
|
||||||
self._names.pop(geom_id)
|
self._names.pop(geom_id)
|
||||||
else:
|
else:
|
||||||
raise ValueError('{} not in collision manager!'.format(name))
|
raise ValueError("{} not in collision manager!".format(name))
|
||||||
|
|
||||||
def set_transform(self, name, transform):
|
def set_transform(self, name, transform):
|
||||||
"""
|
"""
|
||||||
@@ -239,18 +226,14 @@ class CollisionManager(object):
|
|||||||
A new homogeneous transform matrix for the object
|
A new homogeneous transform matrix for the object
|
||||||
"""
|
"""
|
||||||
if name in self._objs:
|
if name in self._objs:
|
||||||
o = self._objs[name]['obj']
|
o = self._objs[name]["obj"]
|
||||||
o.setRotation(transform[:3, :3])
|
o.setRotation(transform[:3, :3])
|
||||||
o.setTranslation(transform[:3, 3])
|
o.setTranslation(transform[:3, 3])
|
||||||
self._manager.update(o)
|
self._manager.update(o)
|
||||||
else:
|
else:
|
||||||
raise ValueError('{} not in collision manager!'.format(name))
|
raise ValueError("{} not in collision manager!".format(name))
|
||||||
|
|
||||||
def in_collision_single(self,
|
def in_collision_single(self, mesh, transform=None, return_names=False, return_data=False):
|
||||||
mesh,
|
|
||||||
transform=None,
|
|
||||||
return_names=False,
|
|
||||||
return_data=False):
|
|
||||||
"""
|
"""
|
||||||
Check a single object for collisions against all objects in the
|
Check a single object for collisions against all objects in the
|
||||||
manager.
|
manager.
|
||||||
@@ -288,9 +271,7 @@ class CollisionManager(object):
|
|||||||
# Collide with manager's objects
|
# Collide with manager's objects
|
||||||
cdata = fcl.CollisionData()
|
cdata = fcl.CollisionData()
|
||||||
if return_names or return_data:
|
if return_names or return_data:
|
||||||
cdata = fcl.CollisionData(request=fcl.CollisionRequest(
|
cdata = fcl.CollisionData(request=fcl.CollisionRequest(num_max_contacts=100000, enable_contact=True))
|
||||||
num_max_contacts=100000,
|
|
||||||
enable_contact=True))
|
|
||||||
|
|
||||||
self._manager.collide(o, cdata, fcl.defaultCollisionCallback)
|
self._manager.collide(o, cdata, fcl.defaultCollisionCallback)
|
||||||
result = cdata.result.is_collision
|
result = cdata.result.is_collision
|
||||||
@@ -305,7 +286,7 @@ class CollisionManager(object):
|
|||||||
cg = contact.o2
|
cg = contact.o2
|
||||||
name = self._extract_name(cg)
|
name = self._extract_name(cg)
|
||||||
|
|
||||||
names = (name, '__external')
|
names = (name, "__external")
|
||||||
if cg == contact.o2:
|
if cg == contact.o2:
|
||||||
names = reversed(names)
|
names = reversed(names)
|
||||||
|
|
||||||
@@ -349,8 +330,7 @@ class CollisionManager(object):
|
|||||||
"""
|
"""
|
||||||
cdata = fcl.CollisionData()
|
cdata = fcl.CollisionData()
|
||||||
if return_names or return_data:
|
if return_names or return_data:
|
||||||
cdata = fcl.CollisionData(request=fcl.CollisionRequest(
|
cdata = fcl.CollisionData(request=fcl.CollisionRequest(num_max_contacts=1000000, enable_contact=True))
|
||||||
num_max_contacts=1000000, enable_contact=True))
|
|
||||||
|
|
||||||
self._manager.collide(cdata, fcl.defaultCollisionCallback)
|
self._manager.collide(cdata, fcl.defaultCollisionCallback)
|
||||||
|
|
||||||
@@ -360,8 +340,7 @@ class CollisionManager(object):
|
|||||||
contact_data = []
|
contact_data = []
|
||||||
if return_names or return_data:
|
if return_names or return_data:
|
||||||
for contact in cdata.result.contacts:
|
for contact in cdata.result.contacts:
|
||||||
names = (self._extract_name(contact.o1),
|
names = (self._extract_name(contact.o1), self._extract_name(contact.o2))
|
||||||
self._extract_name(contact.o2))
|
|
||||||
|
|
||||||
if return_names:
|
if return_names:
|
||||||
objs_in_collision.add(tuple(sorted(names)))
|
objs_in_collision.add(tuple(sorted(names)))
|
||||||
@@ -377,8 +356,7 @@ class CollisionManager(object):
|
|||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def in_collision_other(self, other_manager,
|
def in_collision_other(self, other_manager, return_names=False, return_data=False):
|
||||||
return_names=False, return_data=False):
|
|
||||||
"""
|
"""
|
||||||
Check if any object from this manager collides with any object
|
Check if any object from this manager collides with any object
|
||||||
from another manager.
|
from another manager.
|
||||||
@@ -408,13 +386,8 @@ class CollisionManager(object):
|
|||||||
"""
|
"""
|
||||||
cdata = fcl.CollisionData()
|
cdata = fcl.CollisionData()
|
||||||
if return_names or return_data:
|
if return_names or return_data:
|
||||||
cdata = fcl.CollisionData(
|
cdata = fcl.CollisionData(request=fcl.CollisionRequest(num_max_contacts=100000, enable_contact=True))
|
||||||
request=fcl.CollisionRequest(
|
self._manager.collide(other_manager._manager, cdata, fcl.defaultCollisionCallback)
|
||||||
num_max_contacts=100000,
|
|
||||||
enable_contact=True))
|
|
||||||
self._manager.collide(other_manager._manager,
|
|
||||||
cdata,
|
|
||||||
fcl.defaultCollisionCallback)
|
|
||||||
result = cdata.result.is_collision
|
result = cdata.result.is_collision
|
||||||
|
|
||||||
objs_in_collision = set()
|
objs_in_collision = set()
|
||||||
@@ -422,11 +395,9 @@ class CollisionManager(object):
|
|||||||
if return_names or return_data:
|
if return_names or return_data:
|
||||||
for contact in cdata.result.contacts:
|
for contact in cdata.result.contacts:
|
||||||
reverse = False
|
reverse = False
|
||||||
names = (self._extract_name(contact.o1),
|
names = (self._extract_name(contact.o1), other_manager._extract_name(contact.o2))
|
||||||
other_manager._extract_name(contact.o2))
|
|
||||||
if names[0] is None:
|
if names[0] is None:
|
||||||
names = (self._extract_name(contact.o2),
|
names = (self._extract_name(contact.o2), other_manager._extract_name(contact.o1))
|
||||||
other_manager._extract_name(contact.o1))
|
|
||||||
reverse = True
|
reverse = True
|
||||||
|
|
||||||
if return_names:
|
if return_names:
|
||||||
@@ -445,11 +416,7 @@ class CollisionManager(object):
|
|||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def min_distance_single(self,
|
def min_distance_single(self, mesh, transform=None, return_name=False, return_data=False):
|
||||||
mesh,
|
|
||||||
transform=None,
|
|
||||||
return_name=False,
|
|
||||||
return_data=False):
|
|
||||||
"""
|
"""
|
||||||
Get the minimum distance between a single object and any
|
Get the minimum distance between a single object and any
|
||||||
object in the manager.
|
object in the manager.
|
||||||
@@ -486,10 +453,7 @@ class CollisionManager(object):
|
|||||||
# Collide with manager's objects
|
# Collide with manager's objects
|
||||||
ddata = fcl.DistanceData()
|
ddata = fcl.DistanceData()
|
||||||
if return_data:
|
if return_data:
|
||||||
ddata = fcl.DistanceData(
|
ddata = fcl.DistanceData(fcl.DistanceRequest(enable_nearest_points=True), fcl.DistanceResult())
|
||||||
fcl.DistanceRequest(enable_nearest_points=True),
|
|
||||||
fcl.DistanceResult()
|
|
||||||
)
|
|
||||||
|
|
||||||
self._manager.distance(o, ddata, fcl.defaultDistanceCallback)
|
self._manager.distance(o, ddata, fcl.defaultDistanceCallback)
|
||||||
|
|
||||||
@@ -504,7 +468,7 @@ class CollisionManager(object):
|
|||||||
|
|
||||||
name = self._extract_name(cg)
|
name = self._extract_name(cg)
|
||||||
|
|
||||||
names = (name, '__external')
|
names = (name, "__external")
|
||||||
if cg == ddata.result.o2:
|
if cg == ddata.result.o2:
|
||||||
names = reversed(names)
|
names = reversed(names)
|
||||||
data = DistanceData(names, ddata.result)
|
data = DistanceData(names, ddata.result)
|
||||||
@@ -541,10 +505,7 @@ class CollisionManager(object):
|
|||||||
"""
|
"""
|
||||||
ddata = fcl.DistanceData()
|
ddata = fcl.DistanceData()
|
||||||
if return_data:
|
if return_data:
|
||||||
ddata = fcl.DistanceData(
|
ddata = fcl.DistanceData(fcl.DistanceRequest(enable_nearest_points=True), fcl.DistanceResult())
|
||||||
fcl.DistanceRequest(enable_nearest_points=True),
|
|
||||||
fcl.DistanceResult()
|
|
||||||
)
|
|
||||||
|
|
||||||
self._manager.distance(ddata, fcl.defaultDistanceCallback)
|
self._manager.distance(ddata, fcl.defaultDistanceCallback)
|
||||||
|
|
||||||
@@ -552,8 +513,7 @@ class CollisionManager(object):
|
|||||||
|
|
||||||
names, data = None, None
|
names, data = None, None
|
||||||
if return_names or return_data:
|
if return_names or return_data:
|
||||||
names = (self._extract_name(ddata.result.o1),
|
names = (self._extract_name(ddata.result.o1), self._extract_name(ddata.result.o2))
|
||||||
self._extract_name(ddata.result.o2))
|
|
||||||
data = DistanceData(names, ddata.result)
|
data = DistanceData(names, ddata.result)
|
||||||
names = tuple(sorted(names))
|
names = tuple(sorted(names))
|
||||||
|
|
||||||
@@ -566,8 +526,7 @@ class CollisionManager(object):
|
|||||||
else:
|
else:
|
||||||
return distance
|
return distance
|
||||||
|
|
||||||
def min_distance_other(self, other_manager,
|
def min_distance_other(self, other_manager, return_names=False, return_data=False):
|
||||||
return_names=False, return_data=False):
|
|
||||||
"""
|
"""
|
||||||
Get the minimum distance between any pair of objects,
|
Get the minimum distance between any pair of objects,
|
||||||
one in each manager.
|
one in each manager.
|
||||||
@@ -596,26 +555,19 @@ class CollisionManager(object):
|
|||||||
"""
|
"""
|
||||||
ddata = fcl.DistanceData()
|
ddata = fcl.DistanceData()
|
||||||
if return_data:
|
if return_data:
|
||||||
ddata = fcl.DistanceData(
|
ddata = fcl.DistanceData(fcl.DistanceRequest(enable_nearest_points=True), fcl.DistanceResult())
|
||||||
fcl.DistanceRequest(enable_nearest_points=True),
|
|
||||||
fcl.DistanceResult()
|
|
||||||
)
|
|
||||||
|
|
||||||
self._manager.distance(other_manager._manager,
|
self._manager.distance(other_manager._manager, ddata, fcl.defaultDistanceCallback)
|
||||||
ddata,
|
|
||||||
fcl.defaultDistanceCallback)
|
|
||||||
|
|
||||||
distance = ddata.result.min_distance
|
distance = ddata.result.min_distance
|
||||||
|
|
||||||
names, data = None, None
|
names, data = None, None
|
||||||
if return_names or return_data:
|
if return_names or return_data:
|
||||||
reverse = False
|
reverse = False
|
||||||
names = (self._extract_name(ddata.result.o1),
|
names = (self._extract_name(ddata.result.o1), other_manager._extract_name(ddata.result.o2))
|
||||||
other_manager._extract_name(ddata.result.o2))
|
|
||||||
if names[0] is None:
|
if names[0] is None:
|
||||||
reverse = True
|
reverse = True
|
||||||
names = (self._extract_name(ddata.result.o2),
|
names = (self._extract_name(ddata.result.o2), other_manager._extract_name(ddata.result.o1))
|
||||||
other_manager._extract_name(ddata.result.o1))
|
|
||||||
|
|
||||||
dnames = tuple(names)
|
dnames = tuple(names)
|
||||||
if reverse:
|
if reverse:
|
||||||
@@ -681,10 +633,8 @@ def mesh_to_BVH(mesh):
|
|||||||
BVH of input geometry
|
BVH of input geometry
|
||||||
"""
|
"""
|
||||||
bvh = fcl.BVHModel()
|
bvh = fcl.BVHModel()
|
||||||
bvh.beginModel(num_tris_=len(mesh.faces),
|
bvh.beginModel(num_tris_=len(mesh.faces), num_vertices_=len(mesh.vertices))
|
||||||
num_vertices_=len(mesh.vertices))
|
bvh.addSubModel(verts=mesh.vertices, triangles=mesh.faces)
|
||||||
bvh.addSubModel(verts=mesh.vertices,
|
|
||||||
triangles=mesh.faces)
|
|
||||||
bvh.endModel()
|
bvh.endModel()
|
||||||
return bvh
|
return bvh
|
||||||
|
|
||||||
@@ -709,7 +659,5 @@ def scene_to_collision(scene):
|
|||||||
objects = {}
|
objects = {}
|
||||||
for node in scene.graph.nodes_geometry:
|
for node in scene.graph.nodes_geometry:
|
||||||
T, geometry = scene.graph[node]
|
T, geometry = scene.graph[node]
|
||||||
objects[node] = manager.add_object(name=node,
|
objects[node] = manager.add_object(name=node, mesh=scene.geometry[geometry], transform=T)
|
||||||
mesh=scene.geometry[geometry],
|
|
||||||
transform=T)
|
|
||||||
return manager, objects
|
return manager, objects
|
||||||
|
|||||||
+110
-108
@@ -22,43 +22,43 @@ 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.clash_data = {'meshes': {}}
|
self.clash_data = {"meshes": {}}
|
||||||
self.global_data = {'meshes': {}, 'matrices': {}}
|
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:
|
||||||
self.process_clash_set(clash_set)
|
self.process_clash_set(clash_set)
|
||||||
|
|
||||||
def process_clash_set(self, clash_set):
|
def process_clash_set(self, clash_set):
|
||||||
for ab in ['a', 'b']:
|
for ab in ["a", "b"]:
|
||||||
self.settings.logger.info(f'Creating collision manager {ab} ...')
|
self.settings.logger.info(f"Creating collision manager {ab} ...")
|
||||||
clash_set[f'{ab}_cm'] = collision.CollisionManager()
|
clash_set[f"{ab}_cm"] = collision.CollisionManager()
|
||||||
self.settings.logger.info(f'Loading files {ab} ...')
|
self.settings.logger.info(f"Loading files {ab} ...")
|
||||||
for data in clash_set[ab]:
|
for data in clash_set[ab]:
|
||||||
data['ifc'] = ifcopenshell.open(data['file'])
|
data["ifc"] = ifcopenshell.open(data["file"])
|
||||||
self.patch_ifc(data['ifc'])
|
self.patch_ifc(data["ifc"])
|
||||||
self.settings.logger.info(f'Creating collision data for {ab} ...')
|
self.settings.logger.info(f"Creating collision data for {ab} ...")
|
||||||
if len(data['ifc'].by_type('IfcElement')) > 0:
|
if len(data["ifc"].by_type("IfcElement")) > 0:
|
||||||
self.add_collision_objects(data, clash_set[f'{ab}_cm'])
|
self.add_collision_objects(data, clash_set[f"{ab}_cm"])
|
||||||
|
|
||||||
if 'b' in clash_set and clash_set['b']:
|
if "b" in clash_set and clash_set["b"]:
|
||||||
results = clash_set['a_cm'].in_collision_other(clash_set['b_cm'], return_data=True)
|
results = clash_set["a_cm"].in_collision_other(clash_set["b_cm"], return_data=True)
|
||||||
else:
|
else:
|
||||||
results = clash_set['a_cm'].in_collision_internal(return_data=True)
|
results = clash_set["a_cm"].in_collision_internal(return_data=True)
|
||||||
|
|
||||||
if not results[0]:
|
if not results[0]:
|
||||||
return
|
return
|
||||||
|
|
||||||
tolerance = clash_set['tolerance'] if 'tolerance' in clash_set else 0.01
|
tolerance = clash_set["tolerance"] if "tolerance" in clash_set else 0.01
|
||||||
clash_set['clashes'] = {}
|
clash_set["clashes"] = {}
|
||||||
|
|
||||||
for contact in results[1]:
|
for contact in results[1]:
|
||||||
a_global_id, b_global_id = contact.names
|
a_global_id, b_global_id = contact.names
|
||||||
a = self.get_element(clash_set['a'], a_global_id)
|
a = self.get_element(clash_set["a"], a_global_id)
|
||||||
if 'b' in clash_set and clash_set['b']:
|
if "b" in clash_set and clash_set["b"]:
|
||||||
b = self.get_element(clash_set['b'], b_global_id)
|
b = self.get_element(clash_set["b"], b_global_id)
|
||||||
else:
|
else:
|
||||||
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
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ class IfcClasher:
|
|||||||
# triangle-triangle intersection test. Optimistically, this skips
|
# triangle-triangle intersection test. Optimistically, this skips
|
||||||
# the false positives. Conservatively, we let the user manually deal
|
# the false positives. Conservatively, we let the user manually deal
|
||||||
# with the false positives and we mark it as a clash.
|
# with the false positives and we mark it as a clash.
|
||||||
is_optimistic = True # TODO: let user configure this
|
is_optimistic = True # TODO: let user configure this
|
||||||
|
|
||||||
if is_optimistic and tolerance != 0:
|
if is_optimistic and tolerance != 0:
|
||||||
# We'll now check if the contact data's two faces are actually
|
# We'll now check if the contact data's two faces are actually
|
||||||
@@ -82,17 +82,21 @@ class IfcClasher:
|
|||||||
# please help rewrite this.
|
# please help rewrite this.
|
||||||
|
|
||||||
# Get vertices of clashing tris
|
# Get vertices of clashing tris
|
||||||
p1 = self.global_data['meshes'][contact.names[0]].faces[contact.index(contact.names[0])]
|
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])]
|
p2 = self.global_data["meshes"][contact.names[1]].faces[contact.index(contact.names[1])]
|
||||||
m1 = self.global_data['matrices'][contact.names[0]]
|
m1 = self.global_data["matrices"][contact.names[0]]
|
||||||
m2 = self.global_data['matrices'][contact.names[1]]
|
m2 = self.global_data["matrices"][contact.names[1]]
|
||||||
v1 = []
|
v1 = []
|
||||||
v2 = []
|
v2 = []
|
||||||
|
|
||||||
for v in p1:
|
for v in p1:
|
||||||
v1.append((m1 @ np.array([*self.global_data['meshes'][contact.names[0]].vertices[v], 1]))[0:3].round(2))
|
v1.append(
|
||||||
|
(m1 @ np.array([*self.global_data["meshes"][contact.names[0]].vertices[v], 1]))[0:3].round(2)
|
||||||
|
)
|
||||||
for v in p2:
|
for v in p2:
|
||||||
v2.append((m2 @ np.array([*self.global_data['meshes'][contact.names[1]].vertices[v], 1]))[0:3].round(2))
|
v2.append(
|
||||||
|
(m2 @ np.array([*self.global_data["meshes"][contact.names[1]].vertices[v], 1]))[0:3].round(2)
|
||||||
|
)
|
||||||
|
|
||||||
tri1_x = 0
|
tri1_x = 0
|
||||||
tri2_x = 0
|
tri2_x = 0
|
||||||
@@ -111,75 +115,89 @@ class IfcClasher:
|
|||||||
# This is probably two triangles which just touch
|
# This is probably two triangles which just touch
|
||||||
continue
|
continue
|
||||||
|
|
||||||
key = f'{a_global_id}-{b_global_id}'
|
key = f"{a_global_id}-{b_global_id}"
|
||||||
|
|
||||||
if key in clash_set['clashes'] \
|
if (
|
||||||
and clash_set['clashes'][key]['penetration_depth'] > contact.raw.penetration_depth:
|
key in clash_set["clashes"]
|
||||||
|
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,
|
||||||
'a_ifc_class': a.is_a(),
|
"a_ifc_class": a.is_a(),
|
||||||
'b_ifc_class': b.is_a(),
|
"b_ifc_class": b.is_a(),
|
||||||
'a_name': a.Name,
|
"a_name": a.Name,
|
||||||
'b_name': b.Name,
|
"b_name": b.Name,
|
||||||
'normal': list(contact.raw.normal),
|
"normal": list(contact.raw.normal),
|
||||||
'position': list(contact.raw.pos),
|
"position": list(contact.raw.pos),
|
||||||
'penetration_depth': contact.raw.penetration_depth
|
"penetration_depth": contact.raw.penetration_depth,
|
||||||
}
|
}
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/42740765/intersection-between-line-and-triangle-in-3d
|
# https://stackoverflow.com/questions/42740765/intersection-between-line-and-triangle-in-3d
|
||||||
def intersect_line_triangle(self, q1, q2, p1, p2, p3):
|
def intersect_line_triangle(self, q1, q2, p1, p2, p3):
|
||||||
def signed_tetra_volume(a,b,c,d):
|
def signed_tetra_volume(a, b, c, d):
|
||||||
return np.sign(np.dot(np.cross(b-a,c-a),d-a)/6.0)
|
return np.sign(np.dot(np.cross(b - a, c - a), d - a) / 6.0)
|
||||||
|
|
||||||
s1 = signed_tetra_volume(q1,p1,p2,p3)
|
s1 = signed_tetra_volume(q1, p1, p2, p3)
|
||||||
s2 = signed_tetra_volume(q2,p1,p2,p3)
|
s2 = signed_tetra_volume(q2, p1, p2, p3)
|
||||||
|
|
||||||
if s1 != s2:
|
if s1 != s2:
|
||||||
s3 = signed_tetra_volume(q1,q2,p1,p2)
|
s3 = signed_tetra_volume(q1, q2, p1, p2)
|
||||||
s4 = signed_tetra_volume(q1,q2,p2,p3)
|
s4 = signed_tetra_volume(q1, q2, p2, p3)
|
||||||
s5 = signed_tetra_volume(q1,q2,p3,p1)
|
s5 = signed_tetra_volume(q1, q2, p3, p1)
|
||||||
if s3 == s4 and s4 == s5:
|
if s3 == s4 and s4 == s5:
|
||||||
n = np.cross(p2-p1,p3-p1)
|
n = np.cross(p2 - p1, p3 - p1)
|
||||||
t = -np.dot(q1,n-p1) / np.dot(q1,q2-q1)
|
t = -np.dot(q1, n - p1) / np.dot(q1, q2 - q1)
|
||||||
return q1 + t * (q2-q1)
|
return q1 + t * (q2 - q1)
|
||||||
return None
|
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:
|
||||||
del result['a_cm']
|
del result["a_cm"]
|
||||||
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]:
|
||||||
if 'ifc' in data:
|
if "ifc" in data:
|
||||||
del data['ifc']
|
del data["ifc"]
|
||||||
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)
|
||||||
|
|
||||||
def get_element(self, clash_group, global_id):
|
def get_element(self, clash_group, global_id):
|
||||||
for data in clash_group:
|
for data in clash_group:
|
||||||
try:
|
try:
|
||||||
element = data['ifc'].by_guid(global_id)
|
element = data["ifc"].by_guid(global_id)
|
||||||
if element:
|
if element:
|
||||||
return element
|
return element
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def add_collision_objects(self, data, cm):
|
def add_collision_objects(self, data, cm):
|
||||||
self.clash_data['meshes'] = {}
|
self.clash_data["meshes"] = {}
|
||||||
selector = ifcopenshell.util.selector.Selector()
|
selector = ifcopenshell.util.selector.Selector()
|
||||||
if 'selector' not in data:
|
if "selector" not in data:
|
||||||
iterator = ifcopenshell.geom.iterator(self.geom_settings, data['ifc'], multiprocessing.cpu_count(),
|
iterator = ifcopenshell.geom.iterator(
|
||||||
exclude=(data['ifc'].by_type('IfcSpatialStructureElement')))
|
self.geom_settings,
|
||||||
elif data['mode'] == 'e':
|
data["ifc"],
|
||||||
iterator = ifcopenshell.geom.iterator(self.geom_settings, data['ifc'], multiprocessing.cpu_count(),
|
multiprocessing.cpu_count(),
|
||||||
exclude=selector.parse(data['ifc'], data['selector']))
|
exclude=(data["ifc"].by_type("IfcSpatialStructureElement")),
|
||||||
elif data['mode'] == 'i':
|
)
|
||||||
iterator = ifcopenshell.geom.iterator(self.geom_settings, data['ifc'], multiprocessing.cpu_count(),
|
elif data["mode"] == "e":
|
||||||
include=selector.parse(data['ifc'], data['selector']))
|
iterator = ifcopenshell.geom.iterator(
|
||||||
|
self.geom_settings,
|
||||||
|
data["ifc"],
|
||||||
|
multiprocessing.cpu_count(),
|
||||||
|
exclude=selector.parse(data["ifc"], data["selector"]),
|
||||||
|
)
|
||||||
|
elif data["mode"] == "i":
|
||||||
|
iterator = ifcopenshell.geom.iterator(
|
||||||
|
self.geom_settings,
|
||||||
|
data["ifc"],
|
||||||
|
multiprocessing.cpu_count(),
|
||||||
|
include=selector.parse(data["ifc"], data["selector"]),
|
||||||
|
)
|
||||||
valid_file = iterator.initialize()
|
valid_file = iterator.initialize()
|
||||||
if not valid_file:
|
if not valid_file:
|
||||||
return False
|
return False
|
||||||
@@ -196,46 +214,37 @@ class IfcClasher:
|
|||||||
def add_collision_object(self, data, cm, shape):
|
def add_collision_object(self, data, cm, shape):
|
||||||
if shape is None:
|
if shape is None:
|
||||||
return
|
return
|
||||||
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 self.clash_data['meshes']:
|
if mesh_name in self.clash_data["meshes"]:
|
||||||
mesh = self.clash_data['meshes'][mesh_name]
|
mesh = self.clash_data["meshes"][mesh_name]
|
||||||
else:
|
else:
|
||||||
mesh = self.create_mesh(shape)
|
mesh = self.create_mesh(shape)
|
||||||
self.clash_data['meshes'][mesh_name] = mesh
|
self.clash_data["meshes"][mesh_name] = mesh
|
||||||
self.global_data['meshes'][shape.guid] = mesh
|
self.global_data["meshes"][shape.guid] = mesh
|
||||||
|
|
||||||
m = shape.transformation.matrix.data
|
m = shape.transformation.matrix.data
|
||||||
mat = np.array(
|
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]])
|
||||||
[
|
|
||||||
[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()
|
mat.transpose()
|
||||||
self.global_data['matrices'][shape.guid] = mat
|
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):
|
||||||
f = shape.geometry.faces
|
f = shape.geometry.faces
|
||||||
v = shape.geometry.verts
|
v = shape.geometry.verts
|
||||||
mesh = Mesh()
|
mesh = Mesh()
|
||||||
mesh.vertices = np.array([[v[i], v[i + 1], v[i + 2]]
|
mesh.vertices = np.array([[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)])
|
||||||
for i in range(0, len(v), 3)])
|
mesh.faces = np.array([[f[i], f[i + 1], f[i + 2]] for i in range(0, len(f), 3)])
|
||||||
mesh.faces = np.array([[f[i], f[i + 1], f[i + 2]]
|
|
||||||
for i in range(0, len(f), 3)])
|
|
||||||
return mesh
|
return mesh
|
||||||
|
|
||||||
def patch_ifc(self, ifc_file):
|
def patch_ifc(self, ifc_file):
|
||||||
project = ifc_file.by_type('IfcProject')[0]
|
project = ifc_file.by_type("IfcProject")[0]
|
||||||
sites = self.find_decomposed_ifc_class(project, 'IfcSite')
|
sites = self.find_decomposed_ifc_class(project, "IfcSite")
|
||||||
for site in sites:
|
for site in sites:
|
||||||
self.patch_placement_to_origin(site)
|
self.patch_placement_to_origin(site)
|
||||||
buildings = self.find_decomposed_ifc_class(project, 'IfcBuilding')
|
buildings = self.find_decomposed_ifc_class(project, "IfcBuilding")
|
||||||
for building in buildings:
|
for building in buildings:
|
||||||
self.patch_placement_to_origin(building)
|
self.patch_placement_to_origin(building)
|
||||||
|
|
||||||
@@ -252,43 +261,36 @@ class IfcClasher:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def patch_placement_to_origin(self, element):
|
def patch_placement_to_origin(self, element):
|
||||||
element.ObjectPlacement.RelativePlacement.Location.Coordinates = (0., 0., 0.)
|
element.ObjectPlacement.RelativePlacement.Location.Coordinates = (0.0, 0.0, 0.0)
|
||||||
if element.ObjectPlacement.RelativePlacement.Axis:
|
if element.ObjectPlacement.RelativePlacement.Axis:
|
||||||
element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0., 0., 1.)
|
element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0.0, 0.0, 1.0)
|
||||||
if element.ObjectPlacement.RelativePlacement.RefDirection:
|
if element.ObjectPlacement.RelativePlacement.RefDirection:
|
||||||
element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1., 0., 0.)
|
element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
|
||||||
class IfcClashSettings:
|
class IfcClashSettings:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.logger = None
|
self.logger = None
|
||||||
self.output = 'clashes.json'
|
self.output = "clashes.json"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(description="Clashes geometry between two IFC files")
|
||||||
description='Clashes geometry between two IFC files')
|
parser.add_argument("input", type=str, help="A JSON dataset describing a series of clashsets")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'input',
|
"-o", "--output", type=str, help="The JSON diff file to output. Defaults to output.json", default="output.json"
|
||||||
type=str,
|
)
|
||||||
help='A JSON dataset describing a series of clashsets')
|
|
||||||
parser.add_argument(
|
|
||||||
'-o',
|
|
||||||
'--output',
|
|
||||||
type=str,
|
|
||||||
help='The JSON diff file to output. Defaults to output.json',
|
|
||||||
default='output.json')
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
settings = IfcClashSettings()
|
settings = IfcClashSettings()
|
||||||
settings.output = args.output
|
settings.output = args.output
|
||||||
settings.logger = logging.getLogger('Clash')
|
settings.logger = logging.getLogger("Clash")
|
||||||
settings.logger.setLevel(logging.DEBUG)
|
settings.logger.setLevel(logging.DEBUG)
|
||||||
handler = logging.StreamHandler(sys.stdout)
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
handler.setLevel(logging.DEBUG)
|
handler.setLevel(logging.DEBUG)
|
||||||
settings.logger.addHandler(handler)
|
settings.logger.addHandler(handler)
|
||||||
ifc_clasher = IfcClasher(settings)
|
ifc_clasher = IfcClasher(settings)
|
||||||
with open(args.input, 'r') as clash_sets_file:
|
with open(args.input, "r") as clash_sets_file:
|
||||||
ifc_clasher.clash_sets = json.loads(clash_sets_file.read())
|
ifc_clasher.clash_sets = json.loads(clash_sets_file.read())
|
||||||
ifc_clasher.clash()
|
ifc_clasher.clash()
|
||||||
ifc_clasher.export()
|
ifc_clasher.export()
|
||||||
|
|||||||
Reference in New Issue
Block a user