ifcclash typing

This commit is contained in:
Andrej730
2024-09-30 14:53:09 +05:00
parent 9131804e4e
commit 3ad58aba7a
4 changed files with 93 additions and 31 deletions
@@ -284,6 +284,7 @@ class ExecuteIfcClash(bpy.types.Operator):
if extension == ".json":
tool.Clash.load_clash_sets(self.filepath)
tool.Clash.import_active_clashes()
self.report({"INFO"}, "Finished IFC clash.")
return {"FINISHED"}
+3 -1
View File
@@ -86,7 +86,9 @@ class BIMClashProperties(PropertyGroup):
blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty)
blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty)
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")
smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path")
active_clash_set_index: IntProperty(name="Active Clash Set Index")
+2 -1
View File
@@ -24,12 +24,13 @@ import bonsai.core.tool
import bonsai.tool as tool
from contextlib import contextmanager
from mathutils import Vector
from ifcclash import ifcclash
class Clash(bonsai.core.tool.Clash):
@classmethod
def export_clash_sets(cls):
def export_clash_sets(cls) -> list[ifcclash.ClashSet]:
clash_sets = []
for clash_set in bpy.context.scene.BIMClashProperties.clash_sets:
a = []
+87 -29
View File
@@ -19,6 +19,7 @@
# along with IfcClash. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import json
import time
import numpy as np
@@ -26,23 +27,71 @@ import multiprocessing
import ifcopenshell
import ifcopenshell.geom
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:
def __init__(self, settings):
def __init__(self, settings: ClashSettings):
self.settings = settings
self.geom_settings = ifcopenshell.geom.settings()
self.clash_sets = []
self.clash_sets: list[ClashSet] = []
self.logger = self.settings.logger
self.groups = {}
self.ifcs = {}
self.groups: dict[str, ClashGroup] = {}
self.ifcs: dict[str, ifcopenshell.file] = {}
self.tree = None
def clash(self):
def clash(self) -> None:
for clash_set in self.clash_sets:
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.create_group("a")
for source in clash_set["a"]:
@@ -79,42 +128,51 @@ class Clasher:
clearance=clash_set["clearance"],
check_all=clash_set["check_all"],
)
else:
assert False, f"Unexpected mode '{mode}'."
processed_results = {}
processed_results: dict[str, ClashResult] = {}
for result in results:
element1 = result.a
element2 = result.b
processed_results[f"{element1.get_argument(0)}-{element2.get_argument(0)}"] = {
"a_global_id": element1.get_argument(0),
"b_global_id": element2.get_argument(0),
"a_ifc_class": element1.is_a(),
"b_ifc_class": element2.is_a(),
"a_name": element1.get_argument(2),
"b_name": element2.get_argument(2),
"type": ["protrusion", "pierce", "collision", "clearance"][result.clash_type],
"p1": list(result.p1),
"p2": list(result.p2),
"distance": result.distance,
}
processed_results[f"{element1.get_argument(0)}-{element2.get_argument(0)}"] = ClashResult(
a_global_id=element1.get_argument(0),
b_global_id=element2.get_argument(0),
a_ifc_class=element1.is_a(),
b_ifc_class=element2.is_a(),
a_name=element1.get_argument(2),
b_name=element2.get_argument(2),
type=["protrusion", "pierce", "collision", "clearance"][result.clash_type],
p1=list(result.p1),
p2=list(result.p2),
distance=result.distance,
)
clash_set["clashes"] = processed_results
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.groups[name] = {"elements": {}, "objects": {}}
def load_ifc(self, path):
def load_ifc(self, path: str) -> ifcopenshell.file:
start = time.time()
self.settings.logger.info(f"Loading IFC {path}")
ifc = self.ifcs.get(path, None)
if not ifc:
ifc = ifcopenshell.open(path)
assert isinstance(ifc, ifcopenshell.file)
self.ifcs[path] = ifc
self.settings.logger.info(f"Loading finished {time.time() - start}")
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()
self.settings.logger.info("Creating iterator")
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}")
start = time.time()
self.logger.info(f"Adding objects {name}")
self.logger.info(f"Adding objects {name} ({len(elements)} elements)")
assert iterator.initialize()
while True:
self.tree.add_element(iterator.get())
@@ -145,12 +203,12 @@ class Clasher:
self.logger.info(f"Element metadata finished {time.time() - start}")
start = time.time()
def export(self):
def export(self) -> None:
if len(self.settings.output) > 4 and self.settings.output[-4:] == ".bcf":
return self.export_bcfxml()
self.export_json()
def export_bcfxml(self):
def export_bcfxml(self) -> None:
from bcf.v2.bcfxml import BcfXml
for i, clash_set in enumerate(self.clash_sets):
@@ -170,12 +228,12 @@ class Clasher:
suffix = f".{i}" if i else ""
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.
# Should return a tuple of (filename, bytes).
return None
def export_json(self):
def export_json(self) -> None:
clash_sets = self.clash_sets.copy()
for clash_set in clash_sets:
for source in clash_set["a"]:
@@ -185,7 +243,7 @@ class Clasher:
with open(self.settings.output, "w", encoding="utf-8") as clashes_file:
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 collections import defaultdict
@@ -278,5 +336,5 @@ class Clasher:
class ClashSettings:
def __init__(self):
self.logger = None
self.logger: Logger = None
self.output = "clashes.json"