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#
1919# along with IfcClash. If not, see <http://www.gnu.org/licenses/>.
2020
2121
22- import numpy as np
2322import json
24- import sys
23+ import time
24+ import numpy as np
2525import multiprocessing
2626import ifcopenshell
2727import ifcopenshell .geom
2828import ifcopenshell .util .selector
29- from . import collider
3029
3130
3231class 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