mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 02:23:34 +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.HideAllOpenings,
|
||||
opening.HideOpenings,
|
||||
opening.PurgeUnusedOpenings,
|
||||
opening.RecalculateFill,
|
||||
opening.RemoveBooleans,
|
||||
opening.ShowBooleans,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user