mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
api.boundary.edit_attributes: add PhysicalOrVirtualBoundary and InternalOrExternalBoundary params
Both attributes are required by the IFC schema but were not settable via the API function. Add physical_or_virtual and internal_or_external parameters with "NOTDEFINED" defaults for backward compatibility. Update Bonsai boundary panel to expose both fields in the editor. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -377,6 +377,8 @@ class EnableEditingBoundary(bpy.types.Operator):
|
||||
obj = tool.Ifc.get_object(entity)
|
||||
if entity and obj:
|
||||
setattr(bprops, blender_property, obj)
|
||||
bprops.physical_or_virtual = boundary.PhysicalOrVirtualBoundary or "NOTDEFINED"
|
||||
bprops.internal_or_external = boundary.InternalOrExternalBoundary or "NOTDEFINED"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -392,6 +394,8 @@ class DisableEditingBoundary(bpy.types.Operator):
|
||||
bprops.is_editing = False
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
setattr(bprops, blender_property, None)
|
||||
bprops.physical_or_virtual = "NOTDEFINED"
|
||||
bprops.internal_or_external = "NOTDEFINED"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -411,6 +415,8 @@ class EditBoundaryAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = getattr(bprops, blender_property, None)
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
attributes[blender_property] = entity
|
||||
attributes["physical_or_virtual"] = bprops.physical_or_virtual
|
||||
attributes["internal_or_external"] = bprops.internal_or_external
|
||||
ifcopenshell.api.boundary.edit_attributes(tool.Ifc.get(), entity=boundary, **attributes)
|
||||
bpy.ops.bim.disable_editing_boundary()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -21,6 +21,7 @@ from typing import TYPE_CHECKING, Union
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
PointerProperty,
|
||||
)
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -50,12 +51,43 @@ def element_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object
|
||||
return False
|
||||
|
||||
|
||||
def get_internal_or_external_items(
|
||||
self: "BIMObjectBoundaryProperties", context: bpy.types.Context | None
|
||||
) -> list[tuple[str, str, str]]:
|
||||
items = [
|
||||
("INTERNAL", "Internal", ""),
|
||||
("EXTERNAL", "External", ""),
|
||||
]
|
||||
ifc = tool.Ifc.get()
|
||||
if not ifc or ifc.schema != "IFC2X3":
|
||||
items += [
|
||||
("EXTERNAL_EARTH", "External Earth", ""),
|
||||
("EXTERNAL_WATER", "External Water", ""),
|
||||
("EXTERNAL_FIRE", "External Fire", ""),
|
||||
]
|
||||
items.append(("NOTDEFINED", "Not Defined", ""))
|
||||
return items
|
||||
|
||||
|
||||
class BIMObjectBoundaryProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter)
|
||||
related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter)
|
||||
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||
physical_or_virtual: EnumProperty(
|
||||
name="PhysicalOrVirtualBoundary",
|
||||
items=[
|
||||
("PHYSICAL", "Physical", ""),
|
||||
("VIRTUAL", "Virtual", ""),
|
||||
("NOTDEFINED", "Not Defined", ""),
|
||||
],
|
||||
default="NOTDEFINED",
|
||||
)
|
||||
internal_or_external: EnumProperty(
|
||||
name="InternalOrExternalBoundary",
|
||||
items=get_internal_or_external_items,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_editing: bool
|
||||
@@ -63,6 +95,8 @@ class BIMObjectBoundaryProperties(PropertyGroup):
|
||||
related_building_element: Union[bpy.types.Object, None]
|
||||
parent_boundary: Union[bpy.types.Object, None]
|
||||
corresponding_boundary: Union[bpy.types.Object, None]
|
||||
physical_or_virtual: str
|
||||
internal_or_external: str # values depend on schema: IFC2X3 omits EXTERNAL_EARTH/WATER/FIRE
|
||||
|
||||
|
||||
class BIMBoundaryProperties(PropertyGroup):
|
||||
|
||||
@@ -77,6 +77,10 @@ class BIM_PT_Boundary(Panel):
|
||||
self.draw_relation_editor(boundary, "RelatedBuildingElement", "related_building_element")
|
||||
self.draw_relation_editor(boundary, "ParentBoundary", "parent_boundary")
|
||||
self.draw_relation_editor(boundary, "CorrespondingBoundary", "corresponding_boundary")
|
||||
row = self.layout.row()
|
||||
row.prop(self.bprops, "physical_or_virtual")
|
||||
row = self.layout.row()
|
||||
row.prop(self.bprops, "internal_or_external")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.operator("bim.enable_editing_boundary", icon="GREASEPENCIL", text="Edit")
|
||||
@@ -84,6 +88,8 @@ class BIM_PT_Boundary(Panel):
|
||||
self.draw_relation_data(boundary, "RelatedBuildingElement")
|
||||
self.draw_relation_data(boundary, "ParentBoundary")
|
||||
self.draw_relation_data(boundary, "CorrespondingBoundary")
|
||||
self.draw_enum_data(boundary, "PhysicalOrVirtualBoundary")
|
||||
self.draw_enum_data(boundary, "InternalOrExternalBoundary")
|
||||
if hasattr(boundary, "InnerBoundaries"):
|
||||
for i, inner_boundary in enumerate(getattr(boundary, "InnerBoundaries", ())):
|
||||
row = self.layout.row(align=True)
|
||||
@@ -110,6 +116,11 @@ class BIM_PT_Boundary(Panel):
|
||||
else:
|
||||
row.label(text="")
|
||||
|
||||
def draw_enum_data(self, boundary, ifc_attribute: str):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=ifc_attribute)
|
||||
row.label(text=getattr(boundary, ifc_attribute, "") or "")
|
||||
|
||||
def draw_relation_editor(self, boundary, ifc_attribute: str, blender_property: str):
|
||||
if hasattr(boundary, ifc_attribute):
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
@@ -27,12 +27,11 @@ def edit_attributes(
|
||||
related_building_element: ifcopenshell.entity_instance,
|
||||
parent_boundary: Optional[ifcopenshell.entity_instance] = None,
|
||||
corresponding_boundary: Optional[ifcopenshell.entity_instance] = None,
|
||||
physical_or_virtual: str = "NOTDEFINED",
|
||||
internal_or_external: str = "NOTDEFINED",
|
||||
) -> None:
|
||||
"""Modify the relationships of a space boundary relationship
|
||||
|
||||
Currently this function is quite minimal and offers no advantage to
|
||||
manual assignment of the space boundary attributes.
|
||||
|
||||
:param entity: The IfcRelSpaceBoundary to modify
|
||||
:param relating_space: The IfcSpace or IfcExternalSpatialElement that
|
||||
the space boundary is related to.
|
||||
@@ -44,17 +43,18 @@ def edit_attributes(
|
||||
:param corresponding_boundary: The other IfcRelSpaceBoundary on the
|
||||
other side of the related element. The pair together represents a
|
||||
thermal boundary. This only applies to 2nd level boundaries.
|
||||
:param physical_or_virtual: IfcPhysicalOrVirtualEnum value: "PHYSICAL",
|
||||
"VIRTUAL", or "NOTDEFINED".
|
||||
:param internal_or_external: IfcInternalOrExternalEnum value:
|
||||
"INTERNAL", "EXTERNAL", "EXTERNAL_EARTH", "EXTERNAL_WATER",
|
||||
"EXTERNAL_FIRE", or "NOTDEFINED".
|
||||
:return: None
|
||||
"""
|
||||
entity = entity
|
||||
relating_space = relating_space
|
||||
related_building_element = related_building_element
|
||||
parent_boundary = parent_boundary
|
||||
corresponding_boundary = corresponding_boundary
|
||||
|
||||
entity.RelatingSpace = relating_space
|
||||
entity.RelatedBuildingElement = related_building_element
|
||||
if hasattr(entity, "ParentBoundary"):
|
||||
entity.ParentBoundary = parent_boundary
|
||||
if hasattr(entity, "CorrespondingBoundary"):
|
||||
entity.CorrespondingBoundary = corresponding_boundary
|
||||
entity.PhysicalOrVirtualBoundary = physical_or_virtual
|
||||
entity.InternalOrExternalBoundary = internal_or_external
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2026 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
# This file was generated with the assistance of an AI coding tool.
|
||||
|
||||
import ifcopenshell.api.boundary
|
||||
import ifcopenshell.api.root
|
||||
import test.bootstrap
|
||||
|
||||
|
||||
class TestEditAttributes(test.bootstrap.IFC4):
|
||||
def setup_boundary(self):
|
||||
space = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSpace")
|
||||
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
boundary = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcRelSpaceBoundary")
|
||||
return boundary, space, wall
|
||||
|
||||
def test_sets_relating_space_and_building_element(self):
|
||||
boundary, space, wall = self.setup_boundary()
|
||||
ifcopenshell.api.boundary.edit_attributes(
|
||||
self.file, entity=boundary, relating_space=space, related_building_element=wall
|
||||
)
|
||||
assert boundary.RelatingSpace == space
|
||||
assert boundary.RelatedBuildingElement == wall
|
||||
|
||||
def test_defaults_enums_to_notdefined(self):
|
||||
boundary, space, wall = self.setup_boundary()
|
||||
ifcopenshell.api.boundary.edit_attributes(
|
||||
self.file, entity=boundary, relating_space=space, related_building_element=wall
|
||||
)
|
||||
assert boundary.PhysicalOrVirtualBoundary == "NOTDEFINED"
|
||||
assert boundary.InternalOrExternalBoundary == "NOTDEFINED"
|
||||
|
||||
def test_sets_physical_or_virtual(self):
|
||||
boundary, space, wall = self.setup_boundary()
|
||||
ifcopenshell.api.boundary.edit_attributes(
|
||||
self.file,
|
||||
entity=boundary,
|
||||
relating_space=space,
|
||||
related_building_element=wall,
|
||||
physical_or_virtual="PHYSICAL",
|
||||
)
|
||||
assert boundary.PhysicalOrVirtualBoundary == "PHYSICAL"
|
||||
|
||||
def test_sets_internal_or_external(self):
|
||||
boundary, space, wall = self.setup_boundary()
|
||||
ifcopenshell.api.boundary.edit_attributes(
|
||||
self.file,
|
||||
entity=boundary,
|
||||
relating_space=space,
|
||||
related_building_element=wall,
|
||||
internal_or_external="EXTERNAL",
|
||||
)
|
||||
assert boundary.InternalOrExternalBoundary == "EXTERNAL"
|
||||
|
||||
def test_sets_all_enum_variants(self):
|
||||
boundary, space, wall = self.setup_boundary()
|
||||
for value in ("PHYSICAL", "VIRTUAL", "NOTDEFINED"):
|
||||
ifcopenshell.api.boundary.edit_attributes(
|
||||
self.file,
|
||||
entity=boundary,
|
||||
relating_space=space,
|
||||
related_building_element=wall,
|
||||
physical_or_virtual=value,
|
||||
)
|
||||
assert boundary.PhysicalOrVirtualBoundary == value
|
||||
|
||||
for value in ("INTERNAL", "EXTERNAL", "EXTERNAL_EARTH", "EXTERNAL_WATER", "EXTERNAL_FIRE", "NOTDEFINED"):
|
||||
ifcopenshell.api.boundary.edit_attributes(
|
||||
self.file,
|
||||
entity=boundary,
|
||||
relating_space=space,
|
||||
related_building_element=wall,
|
||||
internal_or_external=value,
|
||||
)
|
||||
assert boundary.InternalOrExternalBoundary == value
|
||||
|
||||
|
||||
class TestEditAttributesIFC2X3(test.bootstrap.IFC2X3, TestEditAttributes):
|
||||
def test_sets_all_enum_variants(self):
|
||||
boundary, space, wall = self.setup_boundary()
|
||||
for value in ("PHYSICAL", "VIRTUAL", "NOTDEFINED"):
|
||||
ifcopenshell.api.boundary.edit_attributes(
|
||||
self.file,
|
||||
entity=boundary,
|
||||
relating_space=space,
|
||||
related_building_element=wall,
|
||||
physical_or_virtual=value,
|
||||
)
|
||||
assert boundary.PhysicalOrVirtualBoundary == value
|
||||
|
||||
# IFC2X3 only has INTERNAL, EXTERNAL, NOTDEFINED
|
||||
for value in ("INTERNAL", "EXTERNAL", "NOTDEFINED"):
|
||||
ifcopenshell.api.boundary.edit_attributes(
|
||||
self.file,
|
||||
entity=boundary,
|
||||
relating_space=space,
|
||||
related_building_element=wall,
|
||||
internal_or_external=value,
|
||||
)
|
||||
assert boundary.InternalOrExternalBoundary == value
|
||||
Reference in New Issue
Block a user