mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
clash - typing fixes and some refactor
This commit is contained in:
@@ -29,6 +29,7 @@ from math import radians
|
||||
from mathutils import Matrix, Vector
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.module.clash.decorator import ClashDecorator
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
class ExportClashSets(bpy.types.Operator, ExportHelper):
|
||||
@@ -127,7 +128,8 @@ class AddClashSource(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
clash_set = props.active_clash_set
|
||||
source = getattr(clash_set, self.group).add()
|
||||
assert clash_set
|
||||
clash_set.get_clash_sources_group(self.group).add()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -142,7 +144,8 @@ class RemoveClashSource(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
clash_set = props.active_clash_set
|
||||
getattr(clash_set, self.group).remove(self.index)
|
||||
assert clash_set
|
||||
clash_set.get_clash_sources_group(self.group).remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -159,7 +162,9 @@ class SelectClashSource(bpy.types.Operator, ImportHelper):
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
clash_set = props.active_clash_set
|
||||
getattr(clash_set, self.group)[self.index].name = self.filepath
|
||||
assert clash_set
|
||||
clash_source = clash_set.get_clash_sources_group(self.group)[self.index]
|
||||
clash_source.name = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -191,9 +196,21 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
bl_idname = "bim.execute_ifc_clash"
|
||||
bl_label = "Execute IFC Clash"
|
||||
bl_description = "Execute clash detection and save the information to a .bcf or .json file"
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.json", options={"HIDDEN"})
|
||||
format: bpy.props.EnumProperty(name="Format", items=[(i, i, "") for i in ("bcf", "json")])
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"})
|
||||
|
||||
filter_glob: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
default="*.bcf;*.json", options={"HIDDEN"}
|
||||
)
|
||||
format: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Format", items=[(i, i, "") for i in ("bcf", "json")]
|
||||
)
|
||||
filepath: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
subtype="FILE_PATH", options={"SKIP_SAVE"}
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
filter_glob: str
|
||||
format: str
|
||||
filepath: str
|
||||
|
||||
@property
|
||||
def filename_ext(self) -> str:
|
||||
@@ -225,11 +242,14 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
|
||||
if self.props.should_create_clash_snapshots:
|
||||
|
||||
def get_viewpoint_snapshot(viewpoint):
|
||||
def get_viewpoint_snapshot(viewpoint) -> tuple[str, bytes]:
|
||||
assert context.scene
|
||||
|
||||
camera = bpy.data.objects.get("IFC Clash Camera")
|
||||
if not camera:
|
||||
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
|
||||
context.scene.collection.objects.link(camera)
|
||||
assert isinstance(camera.data, bpy.types.Camera)
|
||||
|
||||
bcf_camera = viewpoint.visualization_info.perspective_camera
|
||||
p = bcf_camera.camera_view_point
|
||||
@@ -238,6 +258,7 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
y = bcf_camera.camera_up_vector
|
||||
y = Vector([y.x, y.y, y.z])
|
||||
x = y.cross(z)
|
||||
assert isinstance(x, Vector)
|
||||
|
||||
mat = Matrix(
|
||||
[
|
||||
@@ -251,9 +272,9 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
camera.matrix_world = mat
|
||||
context.scene.camera = camera
|
||||
camera.data.angle = radians(60)
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
area.spaces[0].shading.show_xray = True
|
||||
assert (space := tool.Blender.get_view3d_space()) and space.region_3d
|
||||
space.region_3d.view_perspective = "CAMERA"
|
||||
space.shading.show_xray = True
|
||||
context.scene.render.resolution_x = 480
|
||||
context.scene.render.resolution_y = 270
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
@@ -271,7 +292,7 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
if extension == ".json":
|
||||
tool.Clash.load_clash_sets(self.filepath)
|
||||
tool.Clash.import_active_clashes()
|
||||
self.report({"INFO"}, "Finished IFC clash.")
|
||||
self.report({"INFO"}, f"IFC Clash results are saved to '{Path(self.filepath).name}'.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -348,8 +369,10 @@ class SelectClash(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
self.props = tool.Clash.get_clash_props()
|
||||
clash_set = tool.Clash.get_clash_set(self.props.active_clash_set.name)
|
||||
active_clash = self.props.active_clash
|
||||
assert (active_clash := self.props.active_clash)
|
||||
assert (active_clash_set := self.props.active_clash_set)
|
||||
clash_set = tool.Clash.get_clash_set(active_clash_set.name)
|
||||
assert clash_set
|
||||
clash = tool.Clash.get_clash(clash_set, active_clash.a_global_id, active_clash.b_global_id)
|
||||
|
||||
if not clash:
|
||||
|
||||
@@ -35,9 +35,12 @@ from typing import TYPE_CHECKING, Literal, Union
|
||||
|
||||
|
||||
class ClashSource(PropertyGroup):
|
||||
name: StringProperty(name="File")
|
||||
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
|
||||
mode: EnumProperty(
|
||||
name: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="File",
|
||||
description="Absolute filepath to existing .ifc file to use as a clash source.",
|
||||
)
|
||||
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups") # pyright: ignore[reportRedeclaration]
|
||||
mode: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
items=[
|
||||
("a", "All Elements", "All elements will be used for clashing"),
|
||||
("i", "Include", "Only the selected elements are included for clashing"),
|
||||
@@ -47,6 +50,7 @@ class ClashSource(PropertyGroup):
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]
|
||||
mode: Literal["a", "i", "e"]
|
||||
|
||||
@@ -56,7 +60,11 @@ class Clash(PropertyGroup):
|
||||
b_global_id: StringProperty(name="B")
|
||||
a_name: StringProperty(name="A Name")
|
||||
b_name: StringProperty(name="B Name")
|
||||
status: BoolProperty(name="Status", default=False)
|
||||
status: BoolProperty(
|
||||
name="Status",
|
||||
description="Clash status, not stored anywhere - currently just displayed in UI for convenience.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
a_global_id: str
|
||||
@@ -99,6 +107,16 @@ class ClashSet(PropertyGroup):
|
||||
b: bpy.types.bpy_prop_collection_idprop[ClashSource]
|
||||
clashes: bpy.types.bpy_prop_collection_idprop[Clash]
|
||||
|
||||
def get_clash_sources_group(
|
||||
self, group: tool.Clash.ClashSourceGroup
|
||||
) -> "bpy.types.bpy_prop_collection_idprop[ClashSource]":
|
||||
return getattr(self, group)
|
||||
|
||||
def get_clash_sources(
|
||||
self,
|
||||
) -> "dict[tool.Clash.ClashSourceGroup, bpy.types.bpy_prop_collection_idprop[ClashSource]]":
|
||||
return {g: self.get_clash_sources_group(g) for g in tool.Clash.CLASH_SOURCE_GROUP_LITERALS}
|
||||
|
||||
|
||||
class SmartClashGroup(PropertyGroup):
|
||||
number: StringProperty(name="Number")
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.helper
|
||||
from bpy.types import Panel
|
||||
from bonsai.bim.module.clash.data import ClashData
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.clash.prop import BIMClashProperties, ClashSet, SmartClashGroup, Clash
|
||||
|
||||
|
||||
class BIM_PT_ifcclash(Panel):
|
||||
@@ -36,6 +41,7 @@ class BIM_PT_ifcclash(Panel):
|
||||
if not ClashData.is_loaded:
|
||||
ClashData.load()
|
||||
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
props = tool.Clash.get_clash_props()
|
||||
|
||||
@@ -155,6 +161,7 @@ class BIM_PT_smart_clash_manager(Panel):
|
||||
bl_parent_id = "BIM_PT_clash_manager"
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
props = tool.Clash.get_clash_props()
|
||||
|
||||
@@ -188,8 +195,16 @@ class BIM_PT_smart_clash_manager(Panel):
|
||||
|
||||
|
||||
class BIM_UL_clash_sets(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
ob = data
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMClashProperties,
|
||||
item: ClashSet,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
layout.prop(item, "name", text="", emboss=False)
|
||||
else:
|
||||
@@ -197,8 +212,16 @@ class BIM_UL_clash_sets(bpy.types.UIList):
|
||||
|
||||
|
||||
class BIM_UL_smart_groups(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
ob = data
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMClashProperties,
|
||||
item: SmartClashGroup,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
layout.label(text=str(item.number), translate=False, icon="NONE", icon_value=0)
|
||||
else:
|
||||
@@ -206,7 +229,16 @@ class BIM_UL_smart_groups(bpy.types.UIList):
|
||||
|
||||
|
||||
class BIM_UL_clashes(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMClashProperties,
|
||||
item: Clash,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=str(item.a_name), translate=False, icon="NONE", icon_value=0)
|
||||
|
||||
@@ -26,7 +26,8 @@ import bonsai.tool as tool
|
||||
from contextlib import contextmanager
|
||||
from mathutils import Vector
|
||||
from ifcclash import ifcclash
|
||||
from typing import TYPE_CHECKING, Union
|
||||
from ifcclash.ifcclash import ClashSource
|
||||
from typing import TYPE_CHECKING, Union, Literal, get_args
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.clash.prop import BIMClashProperties
|
||||
@@ -38,16 +39,19 @@ class Clash(bonsai.core.tool.Clash):
|
||||
def get_clash_props(cls) -> BIMClashProperties:
|
||||
return bpy.context.scene.BIMClashProperties
|
||||
|
||||
ClashSourceGroup = Literal["a", "b"]
|
||||
CLASH_SOURCE_GROUP_LITERALS = ("a", "b")
|
||||
|
||||
@classmethod
|
||||
def export_clash_sets(cls) -> list[ifcclash.ClashSet]:
|
||||
clash_sets: list[ifcclash.ClashSet] = []
|
||||
props = cls.get_clash_props()
|
||||
for clash_set in props.clash_sets:
|
||||
a = []
|
||||
b = []
|
||||
for ab in ["a", "b"]:
|
||||
for data in getattr(clash_set, ab):
|
||||
clash_source = {"file": data.name}
|
||||
a: list[ClashSource] = []
|
||||
b: list[ClashSource] = []
|
||||
for ab, ab_data in clash_set.get_clash_sources().items():
|
||||
for data in ab_data:
|
||||
clash_source: ClashSource = {"file": data.name}
|
||||
query = tool.Search.export_filter_query(data.filter_groups)
|
||||
if query and data.mode != "a":
|
||||
clash_source["selector"] = query
|
||||
@@ -96,13 +100,15 @@ class Clash(bonsai.core.tool.Clash):
|
||||
clash_set.clashes.clear()
|
||||
result = tool.Clash.get_clash_set(clash_set.name)
|
||||
assert result is not None
|
||||
for clash in sorted(result.get("clashes", {}).values(), key=lambda x: x["distance"]):
|
||||
if "clashes" not in result:
|
||||
return
|
||||
for clash in sorted(result["clashes"].values(), key=lambda x: x["distance"]):
|
||||
blender_clash = clash_set.clashes.add()
|
||||
blender_clash.a_global_id = clash["a_global_id"]
|
||||
blender_clash.b_global_id = clash["b_global_id"]
|
||||
blender_clash.a_name = "{}/{}".format(clash["a_ifc_class"], clash["a_name"])
|
||||
blender_clash.b_name = "{}/{}".format(clash["b_ifc_class"], clash["b_name"])
|
||||
blender_clash.status = False if not "status" in clash.keys() else clash["status"]
|
||||
blender_clash.status = False if not "status" in clash else clash["status"]
|
||||
|
||||
@classmethod
|
||||
def load_clash_sets(cls, fn: str) -> None:
|
||||
|
||||
@@ -28,7 +28,7 @@ import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.selector
|
||||
from logging import Logger
|
||||
from typing import Literal, TypedDict
|
||||
from typing import Literal, TypedDict, Union
|
||||
from typing_extensions import NotRequired
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ class Clasher:
|
||||
start = time.time()
|
||||
|
||||
def export(self) -> None:
|
||||
"""Save clash results to ``settings.output``."""
|
||||
if len(self.settings.output) > 4 and self.settings.output[-4:] == ".bcf":
|
||||
return self.export_bcfxml()
|
||||
self.export_json()
|
||||
@@ -230,12 +231,12 @@ class Clasher:
|
||||
suffix = f".{i}" if i else ""
|
||||
bcfxml.save(f"{self.settings.output}{suffix}")
|
||||
|
||||
def get_viewpoint_snapshot(self, viewpoint) -> None:
|
||||
def get_viewpoint_snapshot(self, viewpoint) -> Union[None, tuple[str, bytes]]:
|
||||
# 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) -> None:
|
||||
"""Saved clash results as ``list[ClashSet]``."""
|
||||
clash_sets = self.clash_sets.copy()
|
||||
for clash_set in clash_sets:
|
||||
for source in clash_set["a"]:
|
||||
|
||||
Reference in New Issue
Block a user