Skip to content

Commit 18c38b3

Browse files
committed
Upgrade IfcClash to use new clash capabilities (currently unmerged from feature-clash2)
1 parent 71013a4 commit 18c38b3

File tree

2 files changed

+75
-166
lines changed

2 files changed

+75
-166
lines changed

src/ifcclash/ifcclash/collider.py

Lines changed: 0 additions & 121 deletions
This file was deleted.

src/ifcclash/ifcclash/ifcclash.py

Lines changed: 75 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python3
22

33
# IfcClash - IFC-based clash detection.
4-
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
4+
# Copyright (C) 2020-2024 Dion Moult <dion@thinkmoult.com>
55
#
66
# This file is part of IfcClash.
77
#
@@ -19,70 +19,92 @@
1919
# along with IfcClash. If not, see <http://www.gnu.org/licenses/>.
2020

2121

22-
import numpy as np
2322
import json
24-
import sys
23+
import time
24+
import numpy as np
2525
import multiprocessing
2626
import ifcopenshell
2727
import ifcopenshell.geom
2828
import ifcopenshell.util.selector
29-
from . import collider
3029

3130

3231
class Clasher:
3332
def __init__(self, settings):
3433
self.settings = settings
3534
self.geom_settings = ifcopenshell.geom.settings()
3635
self.clash_sets = []
37-
self.collider = collider.Collider(self.settings.logger)
38-
self.selector = ifcopenshell.util.selector.Selector()
36+
self.logger = self.settings.logger
37+
self.groups = {}
3938
self.ifcs = {}
39+
self.tree = None
4040

4141
def clash(self):
42-
existing_limit = sys.getrecursionlimit()
4342
for clash_set in self.clash_sets:
4443
self.process_clash_set(clash_set)
4544

4645
def process_clash_set(self, clash_set):
47-
self.collider.create_group("a")
46+
self.tree = ifcopenshell.geom.tree()
47+
self.create_group("a")
4848
for source in clash_set["a"]:
4949
source["ifc"] = self.load_ifc(source["file"])
5050
self.add_collision_objects("a", source["ifc"], source.get("mode", None), source.get("selector", None))
5151

52-
if "b" in clash_set:
53-
self.collider.create_group("b")
52+
if "b" in clash_set and clash_set["b"]:
53+
self.create_group("b")
5454
for source in clash_set["b"]:
5555
source["ifc"] = self.load_ifc(source["file"])
5656
self.add_collision_objects("b", source["ifc"], source.get("mode", None), source.get("selector", None))
57-
results = self.collider.collide_group("a", "b")
57+
b = "b"
5858
else:
59-
results = self.collider.collide_internal("a")
59+
b = "a"
60+
61+
mode = clash_set["mode"]
62+
if mode == "intersection":
63+
results = self.tree.clash_intersection_many(
64+
list(self.groups["a"]["elements"].values()),
65+
list(self.groups[b]["elements"].values()),
66+
tolerance=clash_set["tolerance"],
67+
check_all=clash_set["check_all"],
68+
)
69+
elif mode == "collision":
70+
results = self.tree.clash_collision_many(
71+
list(self.groups["a"]["elements"].values()),
72+
list(self.groups[b]["elements"].values()),
73+
allow_touching=clash_set["allow_touching"],
74+
)
75+
elif mode == "clearance":
76+
results = self.tree.clash_clearance_many(
77+
list(self.groups["a"]["elements"].values()),
78+
list(self.groups[b]["elements"].values()),
79+
clearance=clash_set["clearance"],
80+
check_all=clash_set["check_all"],
81+
)
6082

6183
processed_results = {}
6284
for result in results:
63-
element1 = self.get_element(clash_set["a"], result["id1"])
64-
if "b" in clash_set:
65-
element2 = self.get_element(clash_set["b"], result["id2"])
66-
else:
67-
element2 = self.get_element(clash_set["a"], result["id2"])
85+
element1 = result.a
86+
element2 = result.b
6887

69-
contact = result["collision"].getContacts()[0]
70-
processed_results[f"{result['id1']}-{result['id2']}"] = {
71-
"a_global_id": result["id1"],
72-
"b_global_id": result["id2"],
88+
processed_results[f"{element1.get_argument(0)}-{element2.get_argument(0)}"] = {
89+
"a_global_id": element1.get_argument(0),
90+
"b_global_id": element2.get_argument(0),
7391
"a_ifc_class": element1.is_a(),
7492
"b_ifc_class": element2.is_a(),
75-
"a_name": element1.Name,
76-
"b_name": element2.Name,
77-
"normal": list(contact.normal),
78-
"position": list(contact.pos),
79-
"penetration_depth": contact.penetration_depth,
93+
"a_name": element1.get_argument(2),
94+
"b_name": element2.get_argument(2),
95+
"type": ["protrusion", "pierce", "collision", "clearance"][result.clash_type],
96+
"p1": list(result.p1),
97+
"p2": list(result.p2),
98+
"distance": result.distance,
8099
}
81100
clash_set["clashes"] = processed_results
101+
self.logger.info(f"Found clashes: {len(processed_results.keys())}")
82102

83-
def load_ifc(self, path):
84-
import time
103+
def create_group(self, name):
104+
self.logger.info(f"Creating group {name}")
105+
self.groups[name] = {"elements": {}, "objects": {}}
85106

107+
def load_ifc(self, path):
86108
start = time.time()
87109
self.settings.logger.info(f"Loading IFC {path}")
88110
ifc = self.ifcs.get(path, None)
@@ -93,21 +115,36 @@ def load_ifc(self, path):
93115
return ifc
94116

95117
def add_collision_objects(self, name, ifc_file, mode=None, selector=None):
96-
import time
97-
98118
start = time.time()
99119
self.settings.logger.info("Creating iterator")
100-
if not mode:
101-
elements = ifc_file.by_type("IfcElement")
120+
if not mode or not selector:
121+
elements = set(ifc_file.by_type("IfcElement"))
122+
elements -= set(ifc_file.by_type("IfcFeatureElement"))
102123
elif mode == "e":
103-
elements = set(ifc_file.by_type("IfcElement")) - set(self.selector.parse(ifc_file, selector))
124+
elements = set(ifc_file.by_type("IfcElement"))
125+
elements -= set(ifc_file.by_type("IfcFeatureElement"))
126+
elements -= set(ifcopenshell.util.selector.filter_elements(ifc_file, selector))
104127
elif mode == "i":
105-
elements = self.selector.parse(ifc_file, selector)
128+
elements = set(ifcopenshell.util.selector.filter_elements(ifc_file, selector))
106129
iterator = ifcopenshell.geom.iterator(
107130
self.geom_settings, ifc_file, multiprocessing.cpu_count(), include=elements
108131
)
109132
self.settings.logger.info(f"Iterator creation finished {time.time() - start}")
110-
self.collider.create_objects(name, ifc_file, iterator, elements)
133+
134+
start = time.time()
135+
self.logger.info(f"Adding objects {name}")
136+
assert iterator.initialize()
137+
while True:
138+
self.tree.add_element(iterator.get_native(), should_triangulate=True)
139+
# self.tree.add_element(iterator.get())
140+
shape = iterator.get()
141+
if not iterator.next():
142+
break
143+
self.logger.info(f"Tree finished {time.time() - start}")
144+
start = time.time()
145+
self.groups[name]["elements"].update({e.GlobalId: e for e in elements})
146+
self.logger.info(f"Element metadata finished {time.time() - start}")
147+
start = time.time()
111148

112149
def export(self):
113150
if len(self.settings.output) > 4 and self.settings.output[-4:] == ".bcf":
@@ -123,7 +160,9 @@ def export_bcfxml(self):
123160
title = f'{clash["a_ifc_class"]}/{clash["a_name"]} and {clash["b_ifc_class"]}/{clash["b_name"]}'
124161
topic = bcfxml.add_topic(title, title, "IfcClash")
125162
viewpoint = topic.add_viewpoint_from_point_and_guids(
126-
np.array(clash["position"]), clash["a_global_id"], clash["b_global_id"],
163+
np.array(clash["position"]),
164+
clash["a_global_id"],
165+
clash["b_global_id"],
127166
)
128167
snapshot = self.get_viewpoint_snapshot(viewpoint)
129168
if snapshot:
@@ -147,15 +186,6 @@ def export_json(self):
147186
with open(self.settings.output, "w", encoding="utf-8") as clashes_file:
148187
json.dump(clash_sets, clashes_file, indent=4)
149188

150-
def get_element(self, clash_set, global_id):
151-
for source in clash_set:
152-
try:
153-
element = source["ifc"].by_guid(global_id)
154-
if element:
155-
return element
156-
except:
157-
pass
158-
159189
def smart_group_clashes(self, clash_sets, max_clustering_distance):
160190
from sklearn.cluster import OPTICS
161191
from collections import defaultdict

0 commit comments

Comments
 (0)