Basic IFC Entities explorer UI

A generic UI to allow editing simple entities like #6869 without creating an entire edit attributes UI inside edit attributes UI.
Still work in progress and should be connected to attributes UI.

Example - https://www.imgchest.com/p/agyv9lpq978
This commit is contained in:
Andrej730
2025-07-04 19:33:57 +05:00
parent 5948282ed0
commit fb78f75541
7 changed files with 256 additions and 1 deletions
@@ -25,13 +25,22 @@ classes = (
operator.EditAttributes, operator.EditAttributes,
operator.GenerateGlobalId, operator.GenerateGlobalId,
operator.CopyAttributeToSelection, operator.CopyAttributeToSelection,
operator.ExplorerAddEntity,
operator.ExplorerEnableEditingEntity,
operator.ExplorerDisableEditingEntity,
operator.ExplorerEditEntity,
prop.BIMAttributeProperties, prop.BIMAttributeProperties,
prop.ExplorerEntity,
prop.BIMExplorerProperties,
ui.BIM_PT_object_attributes, ui.BIM_PT_object_attributes,
ui.BIM_PT_explorer,
ui.BIM_UL_explorer,
) )
def register(): def register():
bpy.types.Object.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties) bpy.types.Object.BIMAttributeProperties = bpy.props.PointerProperty(type=prop.BIMAttributeProperties)
bpy.types.Scene.BIMExplorerProperties = bpy.props.PointerProperty(type=prop.BIMExplorerProperties)
def unregister(): def unregister():
@@ -28,6 +28,9 @@ import bonsai.core.attribute as core
import bonsai.core.spatial import bonsai.core.spatial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING:
import bpy.stub_internal.rna_enums as rna_enums
def get_objs_for_operation( def get_objs_for_operation(
operator_properties: "AttributesOperator", context: bpy.types.Context operator_properties: "AttributesOperator", context: bpy.types.Context
@@ -204,3 +207,62 @@ class CopyAttributeToSelection(bpy.types.Operator, tool.Ifc.Operator):
tool.Ifc, tool.Blender, tool.Root, tool.Spatial, name=self.name, value=value tool.Ifc, tool.Blender, tool.Root, tool.Spatial, name=self.name, value=value
) )
self.report({"INFO"}, f"Attribute was successfully copied to {total} elements.") self.report({"INFO"}, f"Attribute was successfully copied to {total} elements.")
class ExplorerAddEntity(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.explorer_add_entity"
bl_label = "Add Entity"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context) -> None:
props = tool.Attribute.get_explorer_props()
ifc_file = tool.Ifc.get()
entity = ifc_file.create_entity(props.ifc_class)
tool.Attribute.refresh_uilist_entities()
tool.Attribute.enable_editing_entity(entity)
tool.Attribute.import_entity_attributes(entity)
class ExplorerEnableEditingEntity(bpy.types.Operator):
bl_idname = "bim.explorer_enable_editing_entity"
bl_label = "Enable Editing Entity"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context) -> "set[rna_enums.OperatorReturnItems]":
props = tool.Attribute.get_explorer_props()
ifc_file = tool.Ifc.get()
assert (active_entity := props.active_entity)
entity = ifc_file.by_id(active_entity.ifc_definition_id)
tool.Attribute.disable_editing_entity()
tool.Attribute.enable_editing_entity(entity)
tool.Attribute.import_entity_attributes(entity)
return {"FINISHED"}
class ExplorerDisableEditingEntity(bpy.types.Operator):
bl_idname = "bim.explorer_disable_editing_entity"
bl_label = "Disable Editing Entity"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
tool.Attribute.disable_editing_entity()
return {"FINISHED"}
class ExplorerEditEntity(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.explorer_edit_entity"
bl_label = "Edit Entity"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context) -> None:
ifc_file = tool.Ifc.get()
props = tool.Attribute.get_explorer_props()
entity = ifc_file.by_id(props.editing_entity_id)
attrs = tool.Attribute.export_entity_attributes()
for attr, value in attrs.items():
setattr(entity, attr, value)
tool.Attribute.refresh_uilist_entities()
tool.Attribute.disable_editing_entity()
+43 -1
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import bonsai.tool as tool
from bonsai.bim.prop import StrProperty, Attribute from bonsai.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
@@ -29,7 +30,7 @@ from bpy.props import (
FloatVectorProperty, FloatVectorProperty,
CollectionProperty, CollectionProperty,
) )
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Union
class BIMAttributeProperties(PropertyGroup): class BIMAttributeProperties(PropertyGroup):
@@ -39,3 +40,44 @@ class BIMAttributeProperties(PropertyGroup):
if TYPE_CHECKING: if TYPE_CHECKING:
attributes: bpy.types.bpy_prop_collection_idprop[Attribute] attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
is_editing_attributes: bool is_editing_attributes: bool
class ExplorerEntity(PropertyGroup):
ifc_definition_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
if TYPE_CHECKING:
ifc_definition_id: int
class BIMExplorerProperties(PropertyGroup):
def get_ifc_class(self, context: object) -> tool.Blender.BLENDER_ENUM_ITEMS:
# TODO: Add more entities.
classes = [
"IfcPostalAddress",
"IfcTelecomAddress",
]
return [(c, c, "") for c in classes]
def update_ifc_class(self, context: object) -> None:
tool.Attribute.refresh_uilist_entities()
ifc_class: EnumProperty( # pyright: ignore[reportRedeclaration]
name="IFC Class To Search",
items=get_ifc_class,
update=update_ifc_class,
)
entities: CollectionProperty(type=ExplorerEntity) # pyright: ignore[reportRedeclaration]
active_entity_index: IntProperty() # pyright: ignore[reportRedeclaration]
editing_entity_id: IntProperty() # pyright: ignore[reportRedeclaration]
entity_attributes: CollectionProperty(type=Attribute) # pyright: ignore[reportRedeclaration]
if TYPE_CHECKING:
ifc_class: str
entities: bpy.types.bpy_prop_collection_idprop[ExplorerEntity]
active_entity_index: int
editing_entity_id: int
entity_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
@property
def active_entity(self) -> Union[ExplorerEntity, None]:
return tool.Blender.get_active_uilist_element(self.entities, self.active_entity_index)
@@ -16,11 +16,16 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bonsai.bim.helper import bonsai.bim.helper
import bpy.types import bpy.types
from bpy.types import Panel from bpy.types import Panel
from bonsai.bim.module.attribute.data import AttributesData from bonsai.bim.module.attribute.data import AttributesData
import bonsai.tool as tool import bonsai.tool as tool
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.attribute.prop import BIMExplorerProperties, ExplorerEntity
def draw_ui(context: bpy.types.Context, layout: bpy.types.UILayout, attributes) -> None: def draw_ui(context: bpy.types.Context, layout: bpy.types.UILayout, attributes) -> None:
@@ -66,3 +71,57 @@ class BIM_PT_object_attributes(Panel):
if not AttributesData.is_loaded: if not AttributesData.is_loaded:
AttributesData.load() AttributesData.load()
draw_ui(context, self.layout, AttributesData.data["attributes"]) draw_ui(context, self.layout, AttributesData.data["attributes"])
class BIM_PT_explorer(Panel):
bl_label = "Explorer"
bl_idname = "BIM_PT_explorer"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_project_setup"
def draw(self, context):
assert (layout := self.layout)
props = tool.Attribute.get_explorer_props()
active_entity = props.active_entity
layout.prop(props, "ifc_class", text="")
row = layout.row(align=True)
row.label(text=f"{len(props.entities)} entities found")
if active_entity:
if active_entity.ifc_definition_id == props.editing_entity_id:
row.operator("bim.explorer_disable_editing_entity", icon="CANCEL", text="")
else:
row.operator("bim.explorer_enable_editing_entity", icon="GREASEPENCIL", text="")
# TODO: 'Remove' button?
layout.template_list("BIM_UL_explorer", "", props, "entities", props, "active_entity_index")
if props.editing_entity_id:
box = self.layout.box()
row = box.row(align=True)
row.operator("bim.explorer_edit_entity", icon="CHECKMARK")
row.operator("bim.explorer_disable_editing_entity", icon="CANCEL", text="")
bonsai.bim.helper.draw_attributes(props.entity_attributes, box)
class BIM_UL_explorer(bpy.types.UIList):
def draw_item(
self,
context: bpy.types.Context,
layout: bpy.types.UILayout,
data: BIMExplorerProperties,
item: ExplorerEntity,
icon,
active_data,
active_propname,
) -> None:
row = layout.row(align=True)
if item.ifc_definition_id == data.editing_entity_id:
row.label(text=item.name, icon="GREASEPENCIL")
else:
row.label(text=item.name)
+5
View File
@@ -75,6 +75,11 @@ class Aggregate:
def get_relating_object(cls, related_element): pass def get_relating_object(cls, related_element): pass
@interface
class Attribute:
pass
@interface @interface
class Bcf: class Bcf:
pass pass
+1
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from bonsai.tool.aggregate import Aggregate from bonsai.tool.aggregate import Aggregate
from bonsai.tool.attribute import Attribute
from bonsai.tool.bcf import Bcf from bonsai.tool.bcf import Bcf
from bonsai.tool.blender import Blender from bonsai.tool.blender import Blender
from bonsai.tool.boundary import Boundary from bonsai.tool.boundary import Boundary
+77
View File
@@ -0,0 +1,77 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bonsai 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
import bonsai.core.tool
import bonsai.bim.helper as helper
import bonsai.tool as tool
import ifcopenshell
from typing import (
Any,
Optional,
Union,
Literal,
TypeVar,
TYPE_CHECKING,
assert_never,
)
if TYPE_CHECKING:
from bonsai.bim.module.attribute.prop import BIMAttributeProperties, BIMExplorerProperties
T = TypeVar("T")
class Attribute(bonsai.core.tool.Attribute):
@classmethod
def get_explorer_props(cls) -> BIMExplorerProperties:
assert (scene := bpy.context.scene)
return scene.BIMExplorerProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def refresh_uilist_entities(cls) -> None:
props = cls.get_explorer_props()
props.entities.clear()
ifc_file = tool.Ifc.get()
for element in ifc_file.by_type(props.ifc_class):
entity = props.entities.add()
entity.name = str(element)
entity.ifc_definition_id = element.id()
@classmethod
def enable_editing_entity(cls, entity: ifcopenshell.entity_instance) -> None:
props = cls.get_explorer_props()
props.editing_entity_id = entity.id()
@classmethod
def disable_editing_entity(cls) -> None:
props = cls.get_explorer_props()
props.entity_attributes.clear()
props.property_unset("editing_entity_id")
@classmethod
def import_entity_attributes(cls, entity: ifcopenshell.entity_instance) -> None:
props = cls.get_explorer_props()
helper.import_attributes(entity.is_a(), props.entity_attributes, entity.get_info())
@classmethod
def export_entity_attributes(cls) -> dict[str, Any]:
props = cls.get_explorer_props()
attributes = helper.export_attributes(props.entity_attributes)
return attributes