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.RemoveActor,
operator.RemoveAddress, operator.RemoveAddress,
operator.RemoveAddressAttribute, operator.RemoveAddressAttribute,
operator.RemoveApplication,
operator.RemoveOrganisation, operator.RemoveOrganisation,
operator.RemovePerson, operator.RemovePerson,
operator.RemovePersonAndOrganisation, operator.RemovePersonAndOrganisation,
@@ -61,6 +62,7 @@ classes = (
ui.BIM_PT_owner, ui.BIM_PT_owner,
ui.BIM_PT_actor, ui.BIM_PT_actor,
ui.BIM_PT_object_actor, ui.BIM_PT_object_actor,
ui.BIM_PT_applications,
) )
@@ -21,6 +21,7 @@ import ifcopenshell
import bonsai.tool as tool import bonsai.tool as tool
from typing import Any, Union from typing import Any, Union
from ifcopenshell.util.doc import get_entity_doc from ifcopenshell.util.doc import get_entity_doc
from natsort import natsorted
def refresh(): def refresh():
@@ -340,3 +341,32 @@ class ObjectActorData:
@classmethod @classmethod
def get_roles(cls, parent: ifcopenshell.entity_instance) -> list[Union[str, None]]: def get_roles(cls, parent: ifcopenshell.entity_instance) -> list[Union[str, None]]:
return [r.UserDefinedRole or r.Role for r in parent.Roles or []] 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 bpy
import bonsai.tool as tool import bonsai.tool as tool
import bonsai.core.owner as core import bonsai.core.owner as core
import ifcopenshell.api.owner
from ifcopenshell.api.owner.add_address import ADDRESS_TYPE from ifcopenshell.api.owner.add_address import ADDRESS_TYPE
from typing import TYPE_CHECKING, get_args 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 (obj := context.active_object)
assert (element := tool.Ifc.get_entity(obj)) assert (element := tool.Ifc.get_entity(obj))
core.unassign_actor(tool.Ifc, actor=tool.Ifc.get().by_id(self.actor), element=element) 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.bim.helper
import bonsai.tool as tool import bonsai.tool as tool
from typing import Any 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: 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): class BIM_PT_actor(bpy.types.Panel):
bl_label = "Actor" bl_label = "Actors"
bl_idname = "BIM_PT_actor" bl_idname = "BIM_PT_actor"
bl_options = {"DEFAULT_CLOSED"} bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES" 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["name"], icon="USER")
row.label(text=actor["role"]) row.label(text=actor["role"])
row.operator("bim.unassign_actor", icon="X", text="").actor = actor["id"] 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"]