mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 10:11:46 +00:00
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:
@@ -87,6 +87,7 @@ classes = (
|
|||||||
opening.HideBooleans,
|
opening.HideBooleans,
|
||||||
opening.HideAllOpenings,
|
opening.HideAllOpenings,
|
||||||
opening.HideOpenings,
|
opening.HideOpenings,
|
||||||
|
opening.PurgeUnusedOpenings,
|
||||||
opening.RecalculateFill,
|
opening.RecalculateFill,
|
||||||
opening.RemoveBooleans,
|
opening.RemoveBooleans,
|
||||||
opening.ShowBooleans,
|
opening.ShowBooleans,
|
||||||
|
|||||||
@@ -574,7 +574,6 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
tool.Model.mark_manual_booleans(element1, booleans)
|
tool.Model.mark_manual_booleans(element1, booleans)
|
||||||
tool.Model.purge_scene_openings()
|
|
||||||
|
|
||||||
bonsai.core.geometry.switch_representation(
|
bonsai.core.geometry.switch_representation(
|
||||||
tool.Ifc,
|
tool.Ifc,
|
||||||
@@ -587,6 +586,7 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
tool.Blender.remove_data_blocks([obj2], remove_unused_data=True)
|
tool.Blender.remove_data_blocks([obj2], remove_unused_data=True)
|
||||||
|
tool.Model.purge_scene_openings()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -837,12 +837,13 @@ class ShowOpenings(Operator, tool.Ifc.Operator):
|
|||||||
while objs:
|
while objs:
|
||||||
obj = objs.pop(0)
|
obj = objs.pop(0)
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
if element and getattr(element, "Decomposes", None):
|
if element:
|
||||||
# Select aggregate recursively
|
if getattr(element, "Decomposes", None):
|
||||||
if element.Decomposes and (aggregate := element.Decomposes[0].RelatingObject):
|
# Select aggregate recursively
|
||||||
aggregate_obj = tool.Ifc.get_object(aggregate)
|
if element.Decomposes and (aggregate := element.Decomposes[0].RelatingObject):
|
||||||
objs.append(aggregate_obj)
|
aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||||
objects_element_map.add((obj, element))
|
objs.append(aggregate_obj)
|
||||||
|
objects_element_map.add((obj, element))
|
||||||
|
|
||||||
for obj, element in objects_element_map:
|
for obj, element in objects_element_map:
|
||||||
self.show_object_openings(obj, element)
|
self.show_object_openings(obj, element)
|
||||||
@@ -1085,6 +1086,47 @@ class CloneOpening(Operator, tool.Ifc.Operator):
|
|||||||
return {"FINISHED"}
|
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?
|
# TODO: merge with ProfileDecorator?
|
||||||
class DecorationsHandler:
|
class DecorationsHandler:
|
||||||
installed = None
|
installed = None
|
||||||
|
|||||||
@@ -537,6 +537,7 @@ class BIM_PT_purge(Panel):
|
|||||||
layout = self.layout
|
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 Profiles").object_type = "PROFILE"
|
||||||
layout.operator("bim.purge_unused_objects", text="Purge Unused Types").object_type = "TYPE"
|
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 = layout.row(align=True)
|
||||||
row.label(text="Materials: ")
|
row.label(text="Materials: ")
|
||||||
row.operator("bim.purge_unused_objects", text="Purge Unused").object_type = "MATERIAL"
|
row.operator("bim.purge_unused_objects", text="Purge Unused").object_type = "MATERIAL"
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import bonsai.bim.import_ifc
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from math import radians, pi
|
from math import radians, pi
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
|
from mathutils.bvhtree import BVHTree
|
||||||
from bonsai.bim.ifc import IfcStore
|
from bonsai.bim.ifc import IfcStore
|
||||||
from typing import Union, Iterable, Optional, Literal, Iterator, List, TYPE_CHECKING, get_args, Generator
|
from typing import Union, Iterable, Optional, Literal, Iterator, List, TYPE_CHECKING, get_args, Generator
|
||||||
from typing_extensions import TypeIs
|
from typing_extensions import TypeIs
|
||||||
@@ -1792,3 +1793,11 @@ class Geometry(bonsai.core.tool.Geometry):
|
|||||||
if not edge.link_faces:
|
if not edge.link_faces:
|
||||||
return True
|
return True
|
||||||
return False
|
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]
|
element = file.by_type("IfcBeam")[0]
|
||||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||||
"""
|
"""
|
||||||
is_not_ifc2x3 = element.file.schema != "IFC2X3"
|
|
||||||
if decomposes := getattr(element, "Decomposes", None):
|
if decomposes := getattr(element, "Decomposes", None):
|
||||||
|
is_not_ifc2x3 = element.file.schema != "IFC2X3"
|
||||||
if is_not_ifc2x3 or decomposes[0].is_a("IfcRelAggregates"):
|
if is_not_ifc2x3 or decomposes[0].is_a("IfcRelAggregates"):
|
||||||
return decomposes[0].RelatingObject
|
return decomposes[0].RelatingObject
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user