Fix #3261 : You can now purge unused openings that do not intersect with their related building element.

It's a naive implementation and very inefficient. Moreover there may be false positives because it's testing overlapping the evaluated mesh element with all openings applied.
This commit is contained in:
Gorgious56
2025-01-03 17:37:46 +01:00
parent 7bfa926e6d
commit 3a7488d54b
5 changed files with 61 additions and 8 deletions
@@ -87,6 +87,7 @@ classes = (
opening.HideBooleans,
opening.HideAllOpenings,
opening.HideOpenings,
opening.PurgeUnusedOpenings,
opening.RecalculateFill,
opening.RemoveBooleans,
opening.ShowBooleans,
+49 -7
View File
@@ -574,7 +574,6 @@ class AddBoolean(Operator, tool.Ifc.Operator):
)
tool.Model.mark_manual_booleans(element1, booleans)
tool.Model.purge_scene_openings()
bonsai.core.geometry.switch_representation(
tool.Ifc,
@@ -587,6 +586,7 @@ class AddBoolean(Operator, tool.Ifc.Operator):
)
tool.Blender.remove_data_blocks([obj2], remove_unused_data=True)
tool.Model.purge_scene_openings()
return {"FINISHED"}
@@ -837,12 +837,13 @@ class ShowOpenings(Operator, tool.Ifc.Operator):
while objs:
obj = objs.pop(0)
element = tool.Ifc.get_entity(obj)
if element and getattr(element, "Decomposes", None):
# Select aggregate recursively
if element.Decomposes and (aggregate := element.Decomposes[0].RelatingObject):
aggregate_obj = tool.Ifc.get_object(aggregate)
objs.append(aggregate_obj)
objects_element_map.add((obj, element))
if element:
if getattr(element, "Decomposes", None):
# Select aggregate recursively
if element.Decomposes and (aggregate := element.Decomposes[0].RelatingObject):
aggregate_obj = tool.Ifc.get_object(aggregate)
objs.append(aggregate_obj)
objects_element_map.add((obj, element))
for obj, element in objects_element_map:
self.show_object_openings(obj, element)
@@ -1085,6 +1086,47 @@ class CloneOpening(Operator, tool.Ifc.Operator):
return {"FINISHED"}
class PurgeUnusedOpenings(Operator, tool.Ifc.Operator):
bl_idname = "bim.purge_unused_openings"
bl_label = "Purge Unused Openings"
bl_description = "Purge Openings that do not intersect with their related building element"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return any(
[tool.Geometry.has_openings(element)]
for element in [tool.Ifc.get_entity(obj) for obj in context.selected_objects]
if element
)
def _execute(self, context):
bpy.ops.bim.show_openings()
objects = context.selected_objects[:]
[o.select_set(False) for o in objects]
active_object = context.active_object
purged = 0
for obj in objects:
element = tool.Ifc.get_entity(obj)
if not element or not tool.Geometry.has_openings(element):
continue
obj_bvh_tree = tool.Geometry.get_bvh_tree(obj)
for opening_rel in tool.Geometry.get_openings(element):
opening_elt = opening_rel.RelatedOpeningElement
opening_obj = tool.Ifc.get_object(opening_elt)
opening_bvh_tree = tool.Geometry.get_bvh_tree(opening_obj)
if not opening_bvh_tree.overlap(obj_bvh_tree):
opening_obj.select_set(True)
purged += 1
if context.selected_objects:
bpy.ops.bim.override_object_delete(is_batch=False)
bpy.ops.bim.edit_openings(apply_all=True)
[o.select_set(True) for o in objects]
context.view_layer.objects.active = active_object
self.report({"INFO"}, f"{purged} unused openings were purged.")
return {"FINISHED"}
# TODO: merge with ProfileDecorator?
class DecorationsHandler:
installed = None
@@ -537,6 +537,7 @@ class BIM_PT_purge(Panel):
layout = self.layout
layout.operator("bim.purge_unused_objects", text="Purge Unused Profiles").object_type = "PROFILE"
layout.operator("bim.purge_unused_objects", text="Purge Unused Types").object_type = "TYPE"
layout.operator("bim.purge_unused_openings", text="Purge Unused Openings in Selected Objects")
row = layout.row(align=True)
row.label(text="Materials: ")
row.operator("bim.purge_unused_objects", text="Purge Unused").object_type = "MATERIAL"
+9
View File
@@ -48,6 +48,7 @@ import bonsai.bim.import_ifc
from collections import defaultdict
from math import radians, pi
from mathutils import Vector, Matrix
from mathutils.bvhtree import BVHTree
from bonsai.bim.ifc import IfcStore
from typing import Union, Iterable, Optional, Literal, Iterator, List, TYPE_CHECKING, get_args, Generator
from typing_extensions import TypeIs
@@ -1792,3 +1793,11 @@ class Geometry(bonsai.core.tool.Geometry):
if not edge.link_faces:
return True
return False
@classmethod
def get_bvh_tree(cls, obj:bpy.types.Object) -> BVHTree:
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
bm.transform(obj.matrix_world)
return BVHTree.FromBMesh(bm)
@@ -1132,8 +1132,8 @@ def get_aggregate(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.e
element = file.by_type("IfcBeam")[0]
aggregate = ifcopenshell.util.element.get_aggregate(element)
"""
is_not_ifc2x3 = element.file.schema != "IFC2X3"
if decomposes := getattr(element, "Decomposes", None):
is_not_ifc2x3 = element.file.schema != "IFC2X3"
if is_not_ifc2x3 or decomposes[0].is_a("IfcRelAggregates"):
return decomposes[0].RelatingObject