Basic IfcApplications UI

Just so you can view IfcApplications in the project from UI

Example - https://files.catbox.moe/15a72n.png
This commit is contained in:
Andrej730
2025-06-16 13:25:50 +05:00
parent a75f6a5804
commit bd1871bd44
4 changed files with 123 additions and 2 deletions
@@ -48,6 +48,7 @@ classes = (
operator.RemoveActor,
operator.RemoveAddress,
operator.RemoveAddressAttribute,
operator.RemoveApplication,
operator.RemoveOrganisation,
operator.RemovePerson,
operator.RemovePersonAndOrganisation,
@@ -61,6 +62,7 @@ classes = (
ui.BIM_PT_owner,
ui.BIM_PT_actor,
ui.BIM_PT_object_actor,
ui.BIM_PT_applications,
)
@@ -21,6 +21,7 @@ import ifcopenshell
import bonsai.tool as tool
from typing import Any, Union
from ifcopenshell.util.doc import get_entity_doc
from natsort import natsorted
def refresh():
@@ -340,3 +341,32 @@ class ObjectActorData:
@classmethod
def get_roles(cls, parent: ifcopenshell.entity_instance) -> list[Union[str, None]]:
return [r.UserDefinedRole or r.Role for r in parent.Roles or []]
class ApplicationsData:
data = {}
is_loaded = False
@classmethod
def load(cls) -> None:
cls.is_loaded = True
cls.data = {
"applications": cls.applications(),
}
@classmethod
def applications(cls) -> list[dict[str, Any]]:
applications: list[dict[str, Any]] = []
ifc_file = tool.Ifc.get()
for application in ifc_file.by_type("IfcApplication"):
total_inverses = ifc_file.get_total_inverses(application)
name = application.ApplicationFullName
if total_inverses == 0:
name += " (unused)"
applications.append(
{
"id": application.id(),
"name": name,
}
)
return natsorted(applications, key=lambda x: x["name"])
@@ -19,6 +19,7 @@
import bpy
import bonsai.tool as tool
import bonsai.core.owner as core
import ifcopenshell.api.owner
from ifcopenshell.api.owner.add_address import ADDRESS_TYPE
from typing import TYPE_CHECKING, get_args
@@ -468,3 +469,55 @@ class UnassignActor(bpy.types.Operator, tool.Ifc.Operator):
assert (obj := context.active_object)
assert (element := tool.Ifc.get_entity(obj))
core.unassign_actor(tool.Ifc, actor=tool.Ifc.get().by_id(self.actor), element=element)
class RemoveApplication(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_application"
bl_label = "Remove Application"
bl_options = {"REGISTER", "UNDO"}
bl_description = (
"Remove provided IfcApplication."
"\n\nFor safety will only work on applications without inverses (they are typically marked as '(unused)'."
)
application_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
if TYPE_CHECKING:
application_id: int
@classmethod
def description(cls, context, properties) -> str:
description = cls.bl_description
application_id: int
if not (application_id := properties.application_id):
return description
ifc_file = tool.Ifc.get()
application = ifc_file.by_id(application_id)
application_info = "Additional application info:"
application_info += f"\n- Developer: {application.ApplicationDeveloper.Name}"
application_info += f"\n- Version: {application.Version}"
application_info += f"\n- Identifier: {application.ApplicationIdentifier}"
application_info += f"\n- Number of inverses: {ifc_file.get_total_inverses(application)}"
application_info += f"\n(try to remove application to see the full list of inverses in system console)"
description += f"\n\n{application_info}"
return description
def invoke(self, context, event):
ifc_file = tool.Ifc.get()
assert (application := ifc_file.by_id(self.application_id))
if total_inverses := ifc_file.get_total_inverses(application):
print(f"Present inverses for {application}:")
for inverse in ifc_file.get_inverse(application):
print(f"- {inverse}")
self.report(
{"ERROR"},
f"This IfcApplication still has {total_inverses} inverses, deletion is unsafe. "
"Check system console for the details.",
)
return {"CANCELLED"}
return self.execute(context)
def _execute(self, context):
ifc_file = tool.Ifc.get()
ifcopenshell.api.owner.remove_application(ifc_file, ifc_file.by_id(self.application_id))
+38 -2
View File
@@ -20,7 +20,14 @@ import bpy
import bonsai.bim.helper
import bonsai.tool as tool
from typing import Any
from bonsai.bim.module.owner.data import PeopleData, OrganisationsData, OwnerData, ActorData, ObjectActorData
from bonsai.bim.module.owner.data import (
PeopleData,
OrganisationsData,
OwnerData,
ActorData,
ObjectActorData,
ApplicationsData,
)
def draw_roles(box: bpy.types.UILayout, parent: dict[str, Any]) -> None:
@@ -240,7 +247,7 @@ class BIM_PT_owner(bpy.types.Panel):
class BIM_PT_actor(bpy.types.Panel):
bl_label = "Actor"
bl_label = "Actors"
bl_idname = "BIM_PT_actor"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
@@ -326,3 +333,32 @@ class BIM_PT_object_actor(bpy.types.Panel):
row.label(text=actor["name"], icon="USER")
row.label(text=actor["role"])
row.operator("bim.unassign_actor", icon="X", text="").actor = actor["id"]
class BIM_PT_applications(bpy.types.Panel):
bl_label = "Applications"
bl_idname = "BIM_PT_applications"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_tab_stakeholders"
@classmethod
def poll(cls, context) -> bool:
return bool(tool.Ifc.get())
def draw(self, context):
if not ApplicationsData.is_loaded:
ApplicationsData.load()
assert (layout := self.layout)
props = tool.Owner.get_owner_props()
apps_data: list[dict[str, Any]] = ApplicationsData.data["applications"]
if not apps_data:
layout.label(text="No Applications Found")
for app_data in apps_data:
row = layout.row()
row.label(text=app_data["name"])
row.operator("bim.remove_application", icon="X", text="").application_id = app_data["id"]