mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-27 02:31:09 +00:00
ifcclash typing
This commit is contained in:
@@ -284,6 +284,7 @@ class ExecuteIfcClash(bpy.types.Operator):
|
|||||||
if extension == ".json":
|
if extension == ".json":
|
||||||
tool.Clash.load_clash_sets(self.filepath)
|
tool.Clash.load_clash_sets(self.filepath)
|
||||||
tool.Clash.import_active_clashes()
|
tool.Clash.import_active_clashes()
|
||||||
|
self.report({"INFO"}, "Finished IFC clash.")
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ class BIMClashProperties(PropertyGroup):
|
|||||||
blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty)
|
blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty)
|
||||||
blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty)
|
blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty)
|
||||||
clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet)
|
clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet)
|
||||||
should_create_clash_snapshots: BoolProperty(name="Create Snapshots", default=False)
|
should_create_clash_snapshots: BoolProperty(
|
||||||
|
name="Create Snapshots", description="Create bcf snapshots", default=False
|
||||||
|
)
|
||||||
clash_results_path: StringProperty(name="Clash Results Path")
|
clash_results_path: StringProperty(name="Clash Results Path")
|
||||||
smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path")
|
smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path")
|
||||||
active_clash_set_index: IntProperty(name="Active Clash Set Index")
|
active_clash_set_index: IntProperty(name="Active Clash Set Index")
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ import bonsai.core.tool
|
|||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
|
from ifcclash import ifcclash
|
||||||
|
|
||||||
|
|
||||||
class Clash(bonsai.core.tool.Clash):
|
class Clash(bonsai.core.tool.Clash):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def export_clash_sets(cls):
|
def export_clash_sets(cls) -> list[ifcclash.ClashSet]:
|
||||||
clash_sets = []
|
clash_sets = []
|
||||||
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
|
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
|
||||||
a = []
|
a = []
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
# along with IfcClash. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcClash. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -26,23 +27,71 @@ import multiprocessing
|
|||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
import ifcopenshell.util.selector
|
import ifcopenshell.util.selector
|
||||||
|
from logging import Logger
|
||||||
|
from typing import Optional, Literal, TypedDict, NotRequired
|
||||||
|
|
||||||
|
|
||||||
|
class ClashSource(TypedDict):
|
||||||
|
file: str
|
||||||
|
mode: NotRequired[Literal["a", "e", "i"]]
|
||||||
|
selector: NotRequired[str]
|
||||||
|
# Will be automatically added during clash.
|
||||||
|
ifc: NotRequired[ifcopenshell.file]
|
||||||
|
|
||||||
|
|
||||||
|
ClashType = Literal["protrusion", "pierce", "collision", "clearance"]
|
||||||
|
|
||||||
|
|
||||||
|
class ClashResult(TypedDict):
|
||||||
|
a_global_id: str
|
||||||
|
b_global_id: str
|
||||||
|
a_ifc_class: str
|
||||||
|
b_ifc_class: str
|
||||||
|
a_name: str
|
||||||
|
b_name: str
|
||||||
|
type: ClashType
|
||||||
|
p1: list[float]
|
||||||
|
p2: list[float]
|
||||||
|
distance: float
|
||||||
|
|
||||||
|
|
||||||
|
class ClashSet(TypedDict):
|
||||||
|
name: str
|
||||||
|
a: list[ClashSource]
|
||||||
|
b: NotRequired[list[ClashSource]]
|
||||||
|
mode: Literal["intersection", "collision", "clearance"]
|
||||||
|
# Added during clash.
|
||||||
|
clashes: NotRequired[dict[str, ClashResult]]
|
||||||
|
# intersection, clearance modes.
|
||||||
|
check_all: NotRequired[bool]
|
||||||
|
# inseresection mode.
|
||||||
|
tolerance: NotRequired[float]
|
||||||
|
# collision mode.
|
||||||
|
allow_touching: NotRequired[bool]
|
||||||
|
# clearance mode.
|
||||||
|
clearance: NotRequired[float]
|
||||||
|
|
||||||
|
|
||||||
|
class ClashGroup(TypedDict):
|
||||||
|
elements: dict
|
||||||
|
objects: dict
|
||||||
|
|
||||||
|
|
||||||
class Clasher:
|
class Clasher:
|
||||||
def __init__(self, settings):
|
def __init__(self, settings: ClashSettings):
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.geom_settings = ifcopenshell.geom.settings()
|
self.geom_settings = ifcopenshell.geom.settings()
|
||||||
self.clash_sets = []
|
self.clash_sets: list[ClashSet] = []
|
||||||
self.logger = self.settings.logger
|
self.logger = self.settings.logger
|
||||||
self.groups = {}
|
self.groups: dict[str, ClashGroup] = {}
|
||||||
self.ifcs = {}
|
self.ifcs: dict[str, ifcopenshell.file] = {}
|
||||||
self.tree = None
|
self.tree = None
|
||||||
|
|
||||||
def clash(self):
|
def clash(self) -> None:
|
||||||
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: ClashSet) -> None:
|
||||||
self.tree = ifcopenshell.geom.tree()
|
self.tree = ifcopenshell.geom.tree()
|
||||||
self.create_group("a")
|
self.create_group("a")
|
||||||
for source in clash_set["a"]:
|
for source in clash_set["a"]:
|
||||||
@@ -79,42 +128,51 @@ class Clasher:
|
|||||||
clearance=clash_set["clearance"],
|
clearance=clash_set["clearance"],
|
||||||
check_all=clash_set["check_all"],
|
check_all=clash_set["check_all"],
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
assert False, f"Unexpected mode '{mode}'."
|
||||||
|
|
||||||
processed_results = {}
|
processed_results: dict[str, ClashResult] = {}
|
||||||
for result in results:
|
for result in results:
|
||||||
element1 = result.a
|
element1 = result.a
|
||||||
element2 = result.b
|
element2 = result.b
|
||||||
|
|
||||||
processed_results[f"{element1.get_argument(0)}-{element2.get_argument(0)}"] = {
|
processed_results[f"{element1.get_argument(0)}-{element2.get_argument(0)}"] = ClashResult(
|
||||||
"a_global_id": element1.get_argument(0),
|
a_global_id=element1.get_argument(0),
|
||||||
"b_global_id": element2.get_argument(0),
|
b_global_id=element2.get_argument(0),
|
||||||
"a_ifc_class": element1.is_a(),
|
a_ifc_class=element1.is_a(),
|
||||||
"b_ifc_class": element2.is_a(),
|
b_ifc_class=element2.is_a(),
|
||||||
"a_name": element1.get_argument(2),
|
a_name=element1.get_argument(2),
|
||||||
"b_name": element2.get_argument(2),
|
b_name=element2.get_argument(2),
|
||||||
"type": ["protrusion", "pierce", "collision", "clearance"][result.clash_type],
|
type=["protrusion", "pierce", "collision", "clearance"][result.clash_type],
|
||||||
"p1": list(result.p1),
|
p1=list(result.p1),
|
||||||
"p2": list(result.p2),
|
p2=list(result.p2),
|
||||||
"distance": result.distance,
|
distance=result.distance,
|
||||||
}
|
)
|
||||||
clash_set["clashes"] = processed_results
|
clash_set["clashes"] = processed_results
|
||||||
self.logger.info(f"Found clashes: {len(processed_results.keys())}")
|
self.logger.info(f"Found clashes: {len(processed_results.keys())}")
|
||||||
|
|
||||||
def create_group(self, name):
|
def create_group(self, name: str) -> None:
|
||||||
self.logger.info(f"Creating group {name}")
|
self.logger.info(f"Creating group {name}")
|
||||||
self.groups[name] = {"elements": {}, "objects": {}}
|
self.groups[name] = {"elements": {}, "objects": {}}
|
||||||
|
|
||||||
def load_ifc(self, path):
|
def load_ifc(self, path: str) -> ifcopenshell.file:
|
||||||
start = time.time()
|
start = time.time()
|
||||||
self.settings.logger.info(f"Loading IFC {path}")
|
self.settings.logger.info(f"Loading IFC {path}")
|
||||||
ifc = self.ifcs.get(path, None)
|
ifc = self.ifcs.get(path, None)
|
||||||
if not ifc:
|
if not ifc:
|
||||||
ifc = ifcopenshell.open(path)
|
ifc = ifcopenshell.open(path)
|
||||||
|
assert isinstance(ifc, ifcopenshell.file)
|
||||||
self.ifcs[path] = ifc
|
self.ifcs[path] = ifc
|
||||||
self.settings.logger.info(f"Loading finished {time.time() - start}")
|
self.settings.logger.info(f"Loading finished {time.time() - start}")
|
||||||
return ifc
|
return ifc
|
||||||
|
|
||||||
def add_collision_objects(self, name, ifc_file, mode=None, selector=None):
|
def add_collision_objects(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
ifc_file: ifcopenshell.file,
|
||||||
|
mode: Optional[Literal["a", "e", "i"]] = None,
|
||||||
|
selector: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
start = time.time()
|
start = time.time()
|
||||||
self.settings.logger.info("Creating iterator")
|
self.settings.logger.info("Creating iterator")
|
||||||
if not mode or mode == "a" or not selector:
|
if not mode or mode == "a" or not selector:
|
||||||
@@ -132,7 +190,7 @@ class Clasher:
|
|||||||
self.settings.logger.info(f"Iterator creation finished {time.time() - start}")
|
self.settings.logger.info(f"Iterator creation finished {time.time() - start}")
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
self.logger.info(f"Adding objects {name}")
|
self.logger.info(f"Adding objects {name} ({len(elements)} elements)")
|
||||||
assert iterator.initialize()
|
assert iterator.initialize()
|
||||||
while True:
|
while True:
|
||||||
self.tree.add_element(iterator.get())
|
self.tree.add_element(iterator.get())
|
||||||
@@ -145,12 +203,12 @@ class Clasher:
|
|||||||
self.logger.info(f"Element metadata finished {time.time() - start}")
|
self.logger.info(f"Element metadata finished {time.time() - start}")
|
||||||
start = time.time()
|
start = time.time()
|
||||||
|
|
||||||
def export(self):
|
def export(self) -> None:
|
||||||
if len(self.settings.output) > 4 and self.settings.output[-4:] == ".bcf":
|
if len(self.settings.output) > 4 and self.settings.output[-4:] == ".bcf":
|
||||||
return self.export_bcfxml()
|
return self.export_bcfxml()
|
||||||
self.export_json()
|
self.export_json()
|
||||||
|
|
||||||
def export_bcfxml(self):
|
def export_bcfxml(self) -> None:
|
||||||
from bcf.v2.bcfxml import BcfXml
|
from bcf.v2.bcfxml import BcfXml
|
||||||
|
|
||||||
for i, clash_set in enumerate(self.clash_sets):
|
for i, clash_set in enumerate(self.clash_sets):
|
||||||
@@ -170,12 +228,12 @@ class Clasher:
|
|||||||
suffix = f".{i}" if i else ""
|
suffix = f".{i}" if i else ""
|
||||||
bcfxml.save_project(f"{self.settings.output}{suffix}")
|
bcfxml.save_project(f"{self.settings.output}{suffix}")
|
||||||
|
|
||||||
def get_viewpoint_snapshot(self, viewpoint):
|
def get_viewpoint_snapshot(self, viewpoint) -> None:
|
||||||
# Possible to overload this function in a GUI application if used as a library.
|
# Possible to overload this function in a GUI application if used as a library.
|
||||||
# Should return a tuple of (filename, bytes).
|
# Should return a tuple of (filename, bytes).
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def export_json(self):
|
def export_json(self) -> None:
|
||||||
clash_sets = self.clash_sets.copy()
|
clash_sets = self.clash_sets.copy()
|
||||||
for clash_set in clash_sets:
|
for clash_set in clash_sets:
|
||||||
for source in clash_set["a"]:
|
for source in clash_set["a"]:
|
||||||
@@ -185,7 +243,7 @@ class Clasher:
|
|||||||
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(clash_sets, clashes_file, indent=4)
|
json.dump(clash_sets, clashes_file, indent=4)
|
||||||
|
|
||||||
def smart_group_clashes(self, clash_sets, max_clustering_distance):
|
def smart_group_clashes(self, clash_sets: list[ClashSet], max_clustering_distance: float):
|
||||||
from sklearn.cluster import OPTICS
|
from sklearn.cluster import OPTICS
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
@@ -278,5 +336,5 @@ class Clasher:
|
|||||||
|
|
||||||
class ClashSettings:
|
class ClashSettings:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.logger = None
|
self.logger: Logger = None
|
||||||
self.output = "clashes.json"
|
self.output = "clashes.json"
|
||||||
|
|||||||
Reference in New Issue
Block a user