mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 22:33:33 +00:00
Option to apply openings to the geometry #5666
Button location - https://i.imgur.com/6zSv8vV.png Now it's possible to "bake" openings to the geometry, The openings representation will be marked as "Reference".
This commit is contained in:
@@ -386,15 +386,28 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bl_label = "Update Representation"
|
bl_label = "Update Representation"
|
||||||
bl_description = (
|
bl_description = (
|
||||||
"Write selected objects representations to IFC.\n"
|
"Write selected objects representations to IFC.\n"
|
||||||
"A star in the operator name indicates that active object representation in IFC is not yet synced with Blender"
|
"A star in the operator name indicates that active object representation in IFC is not yet synced with Blender.\n"
|
||||||
|
"ALT+CLICK to apply openings to the mesh"
|
||||||
)
|
)
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
obj: bpy.props.StringProperty()
|
obj: bpy.props.StringProperty()
|
||||||
ifc_representation_class: bpy.props.StringProperty()
|
ifc_representation_class: bpy.props.StringProperty()
|
||||||
|
apply_openings: bpy.props.BoolProperty(
|
||||||
|
name="Apply Openings",
|
||||||
|
description=(
|
||||||
|
"Whether to apply openings to the mesh.\n"
|
||||||
|
"If False, operator will skip updating representation that has openings"
|
||||||
|
),
|
||||||
|
default=False,
|
||||||
|
options={"SKIP_SAVE"},
|
||||||
|
)
|
||||||
|
|
||||||
from_ui = False
|
from_ui = False
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
if event.type == "LEFTMOUSE" and event.alt:
|
||||||
|
self.apply_openings = True
|
||||||
|
|
||||||
self.from_ui = True
|
self.from_ui = True
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
@@ -426,7 +439,8 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
# NOTE: Currently iterator doesn't detect whether opening is actually affected the representation
|
# NOTE: Currently iterator doesn't detect whether opening is actually affected the representation
|
||||||
# or it's just present on the element. In theory, we can also allow editing representations
|
# or it's just present on the element. In theory, we can also allow editing representations
|
||||||
# if we know that representation wasn't affected by existing openings.
|
# if we know that representation wasn't affected by existing openings.
|
||||||
if tool.Geometry.has_openings(product) and obj.data.BIMMeshProperties.has_openings_applied:
|
has_openings = tool.Geometry.has_openings(product) and obj.data.BIMMeshProperties.has_openings_applied
|
||||||
|
if has_openings and not self.apply_openings:
|
||||||
# Meshlike things with openings can only be updated without openings applied.
|
# Meshlike things with openings can only be updated without openings applied.
|
||||||
if self.from_ui:
|
if self.from_ui:
|
||||||
self.report({"ERROR"}, f"Object '{obj.name}' has openings - representation cannot be updated.")
|
self.report({"ERROR"}, f"Object '{obj.name}' has openings - representation cannot be updated.")
|
||||||
@@ -514,6 +528,17 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
for inverse in self.file.get_inverse(old_representation):
|
for inverse in self.file.get_inverse(old_representation):
|
||||||
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
|
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
|
||||||
|
|
||||||
|
# As openings are already 'baked' to the geometry, we mark their representation as 'Reference'
|
||||||
|
# as they're not part of the object representation anymore.
|
||||||
|
if has_openings and self.apply_openings:
|
||||||
|
ifc_context = new_representation.ContextOfItems
|
||||||
|
for opening_rel in tool.Geometry.get_openings(product):
|
||||||
|
opening = opening_rel.RelatedOpeningElement
|
||||||
|
representation = ifcopenshell.util.representation.get_representation(opening, ifc_context)
|
||||||
|
if not representation:
|
||||||
|
continue
|
||||||
|
representation.RepresentationIdentifier = "Reference"
|
||||||
|
|
||||||
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
|
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
|
||||||
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
|
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ 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 bonsai.bim.ifc import IfcStore
|
from bonsai.bim.ifc import IfcStore
|
||||||
from typing import Union, Iterable, Optional, Literal, Iterator, List, TYPE_CHECKING, get_args
|
from typing import Union, Iterable, Optional, Literal, Iterator, List, TYPE_CHECKING, get_args, Generator
|
||||||
from typing_extensions import TypeIs
|
from typing_extensions import TypeIs
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -1470,12 +1470,20 @@ class Geometry(bonsai.core.tool.Geometry):
|
|||||||
return use_immediate_repr
|
return use_immediate_repr
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def has_openings(cls, element: ifcopenshell.entity_instance) -> bool:
|
def get_openings(cls, element: ifcopenshell.entity_instance) -> Generator[ifcopenshell.entity_instance, None, None]:
|
||||||
if getattr(element, "HasOpenings", None):
|
"""Get element openings as IfcRelVoidsElements.
|
||||||
return True
|
|
||||||
|
Use `.RelatedOpeningElement` to get the opening element.
|
||||||
|
"""
|
||||||
|
for element in getattr(element, "HasOpenings", ()):
|
||||||
|
yield element
|
||||||
|
|
||||||
if aggregate := ifcopenshell.util.element.get_aggregate(element):
|
if aggregate := ifcopenshell.util.element.get_aggregate(element):
|
||||||
return cls.has_openings(aggregate)
|
yield from cls.get_openings(aggregate)
|
||||||
return False
|
|
||||||
|
@classmethod
|
||||||
|
def has_openings(cls, element: ifcopenshell.entity_instance) -> bool:
|
||||||
|
return bool(next(cls.get_openings(element), False))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_elements_by_representation(
|
def get_elements_by_representation(
|
||||||
|
|||||||
Reference in New Issue
Block a user