application ui - support changing organization

This commit is contained in:
Andrej730
2025-06-19 19:07:12 +05:00
parent a968cad4cc
commit 42b777b1f0
6 changed files with 74 additions and 8 deletions
+9
View File
@@ -273,6 +273,15 @@ def export_attributes(
return attributes return attributes
def process_exported_entity_attribute(attributes: dict[str, Any], attribute_name: str) -> None:
entity_id = attributes[attribute_name]
if entity_id is None:
# Maybe it was removed by now and enum is invalid.
del attributes[attribute_name]
else:
attributes[attribute_name] = tool.Ifc.get().by_id(int(entity_id))
ENUM_ITEMS_DATA = Union[bpy.types.PropertyGroup, bpy.types.ID, bpy.types.Operator, bpy.types.OperatorProperties] ENUM_ITEMS_DATA = Union[bpy.types.PropertyGroup, bpy.types.ID, bpy.types.Operator, bpy.types.OperatorProperties]
+18 -2
View File
@@ -84,7 +84,13 @@ def cache_string(s: Any) -> str:
cache_string.data: dict[str, str] = {} cache_string.data: dict[str, str] = {}
def get_attribute_enum_values(prop: "Attribute", context: bpy.types.Context) -> list[tuple[str, str, str]]: def get_attribute_enum_values(prop: "Attribute", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
if "EnumData" not in globals() or TYPE_CHECKING:
from bonsai.bim.ui import EnumData
if dynamic_identifier := prop.enum_items_dynamic:
return EnumData.get_data(dynamic_identifier)
# Support weird buildingSMART dictionary mappings which behave like enums # Support weird buildingSMART dictionary mappings which behave like enums
items: list[tuple[str, str, str]] = [] items: list[tuple[str, str, str]] = []
data = json.loads(prop.enum_items) data = json.loads(prop.enum_items)
@@ -314,6 +320,11 @@ class Attribute(PropertyGroup):
name="Value", description=tooltip, get=get_length_value, set=set_length_value, unit="LENGTH" name="Value", description=tooltip, get=get_length_value, set=set_length_value, unit="LENGTH"
) )
enum_items: StringProperty(name="Value") enum_items: StringProperty(name="Value")
"""Json serialized mapping of enum items:
Typically a dictionary of string identifiers to item names.
"""
enum_items_dynamic: StringProperty()
"""Dynamic enum items identifier."""
enum_descriptions: CollectionProperty(type=StrProperty) enum_descriptions: CollectionProperty(type=StrProperty)
enum_value: EnumProperty(items=get_attribute_enum_values, name="Value", update=update_attribute_value) enum_value: EnumProperty(items=get_attribute_enum_values, name="Value", update=update_attribute_value)
filepath_value: PointerProperty(type=MultipleFileSelect) filepath_value: PointerProperty(type=MultipleFileSelect)
@@ -343,6 +354,7 @@ class Attribute(PropertyGroup):
float_value: float float_value: float
length_value: float length_value: float
enum_items: str enum_items: str
enum_items_dynamic: str
enum_descriptions: bpy.types.bpy_prop_collection_idprop[StrProperty] enum_descriptions: bpy.types.bpy_prop_collection_idprop[StrProperty]
enum_value: str enum_value: str
filepath_value: MultipleFileSelect filepath_value: MultipleFileSelect
@@ -365,7 +377,11 @@ class Attribute(PropertyGroup):
return self.string_value.replace("\\n", "\n") return self.string_value.replace("\\n", "\n")
if self.data_type == "file": if self.data_type == "file":
return [f.name for f in self.filepath_value.file_list] return [f.name for f in self.filepath_value.file_list]
value = getattr(self, str(self.get_value_name()), None) value_name = self.get_value_name()
if value_name == "enum_value":
value = tool.Blender.get_enum_safe(self, "enum_value")
else:
value = getattr(self, value_name, None)
if self.special_type == "LOGICAL" and value != "UNKNOWN": if self.special_type == "LOGICAL" and value != "UNKNOWN":
# IfcOpenShell expects bool if IfcLogical is True/False. # IfcOpenShell expects bool if IfcLogical is True/False.
value = value == "TRUE" value = value == "TRUE"
+17
View File
@@ -36,6 +36,7 @@ import bonsai.tool as tool
from ifcopenshell.util.file import IfcHeaderExtractor from ifcopenshell.util.file import IfcHeaderExtractor
from bonsai.bim.prop import Attribute from bonsai.bim.prop import Attribute
from typing import Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
from natsort import natsorted
class IFCFileSelector: class IFCFileSelector:
@@ -1167,6 +1168,22 @@ class BIM_PT_tab_operations(Panel):
def refresh(): def refresh():
UIData.is_loaded = False UIData.is_loaded = False
EnumData.data.clear()
class EnumData:
data: dict[str, tool.Blender.BLENDER_ENUM_ITEMS] = {}
@classmethod
def get_data(cls, identifier: str) -> tool.Blender.BLENDER_ENUM_ITEMS:
if identifier not in EnumData.data:
cls.data[identifier] = getattr(cls, identifier)()
return cls.data[identifier]
@classmethod
def organizations(cls) -> tool.Blender.BLENDER_ENUM_ITEMS:
organizations = tool.Ifc.get().by_type("IfcOrganization")
return natsorted(((str(e.id()), e.Name, "") for e in organizations), key=lambda x: x[1])
class UIData: class UIData:
+1 -1
View File
@@ -93,7 +93,7 @@ class Blender(bonsai.core.tool.Blender):
- (identifier, name, description, icon, number) - (identifier, name, description, icon, number)
""" """
BLENDER_ENUM_ITEMS = list[BLENDER_ENUM_ITEM] BLENDER_ENUM_ITEMS = Iterable[BLENDER_ENUM_ITEM]
@classmethod @classmethod
def activate_camera(cls, obj: bpy.types.Object) -> None: def activate_camera(cls, obj: bpy.types.Object) -> None:
+15 -1
View File
@@ -26,6 +26,7 @@ from typing import Union, Any, TYPE_CHECKING, Literal, assert_never
if TYPE_CHECKING: if TYPE_CHECKING:
from bonsai.bim.module.owner.prop import BIMOwnerProperties from bonsai.bim.module.owner.prop import BIMOwnerProperties
from bonsai.bim.prop import Attribute
class Owner(bonsai.core.tool.Owner): class Owner(bonsai.core.tool.Owner):
@@ -327,10 +328,23 @@ class Owner(bonsai.core.tool.Owner):
application = tool.Ifc.get().by_id(props.active_application_id) application = tool.Ifc.get().by_id(props.active_application_id)
props.application_attributes.clear() props.application_attributes.clear()
bonsai.bim.helper.import_attributes("IfcApplication", props.application_attributes, application.get_info()) def callback(name: str, prop: Union[Attribute, None], data: dict[str, Any]):
if name == "ApplicationDeveloper":
new = props.application_attributes.add()
new.name = name
new.data_type = "enum"
new.is_optional = False
new.enum_items_dynamic = "organizations"
new.enum_value = str(data["ApplicationDeveloper"].id())
return True
bonsai.bim.helper.import_attributes(
"IfcApplication", props.application_attributes, application.get_info(), callback
)
@classmethod @classmethod
def export_application_attributes(cls) -> dict[str, Any]: def export_application_attributes(cls) -> dict[str, Any]:
props = cls.get_owner_props() props = cls.get_owner_props()
attributes = bonsai.bim.helper.export_attributes(props.application_attributes) attributes = bonsai.bim.helper.export_attributes(props.application_attributes)
bonsai.bim.helper.process_exported_entity_attribute(attributes, "ApplicationDeveloper")
return attributes return attributes
+14 -4
View File
@@ -23,6 +23,8 @@ import bonsai.core.tool
import bonsai.tool as tool import bonsai.tool as tool
from test.bim.bootstrap import NewFile from test.bim.bootstrap import NewFile
from bonsai.tool.owner import Owner as subject from bonsai.tool.owner import Owner as subject
from typing import Any
from functools import cache
class TestImplementsTool(NewFile): class TestImplementsTool(NewFile):
@@ -584,12 +586,18 @@ class TestSetUser(NewFile):
class TestApplicationUI(NewFile): class TestApplicationUI(NewFile):
ifc_class = "IfcApplication" ifc_class = "IfcApplication"
attrs = { attrs_: dict[str, Any] = {
"Version": "v001", "Version": "v001",
"ApplicationFullName": "IfcOpenShell", "ApplicationFullName": "IfcOpenShell",
"ApplicationIdentifier": "IfcOpenShell", "ApplicationIdentifier": "IfcOpenShell",
} }
@cache
def get_attrs(self, ifc_file: ifcopenshell.file) -> dict[str, Any]:
attrs = self.attrs_.copy()
attrs["ApplicationDeveloper"] = ifc_file.create_entity("IfcOrganization", Name="Test")
return attrs
def test_set(self): def test_set(self):
application = ifcopenshell.file().create_entity(self.ifc_class) application = ifcopenshell.file().create_entity(self.ifc_class)
subject.set_application(application) subject.set_application(application)
@@ -614,14 +622,16 @@ class TestApplicationUI(NewFile):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc.set(ifc) tool.Ifc.set(ifc)
application = ifc.create_entity(self.ifc_class) application = ifc.create_entity(self.ifc_class)
for attr, value in self.attrs.items(): for attr, value in self.get_attrs(ifc).items():
setattr(application, attr, value) setattr(application, attr, value)
subject.set_application(application) subject.set_application(application)
subject.import_application_attributes() subject.import_application_attributes()
props = subject.get_owner_props() props = subject.get_owner_props()
for attr, value in self.attrs.items(): for attr, value in self.get_attrs(ifc).items():
if isinstance(value, ifcopenshell.entity_instance):
value = str(value.id())
assert props.application_attributes[attr].get_value() == value assert props.application_attributes[attr].get_value() == value
def test_export(self): def test_export(self):
self.test_import() self.test_import()
assert subject.export_application_attributes() == self.attrs assert subject.export_application_attributes() == self.get_attrs(tool.Ifc.get())