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
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]
+18 -2
View File
@@ -84,7 +84,13 @@ def cache_string(s: Any) -> 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
items: list[tuple[str, str, str]] = []
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"
)
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_value: EnumProperty(items=get_attribute_enum_values, name="Value", update=update_attribute_value)
filepath_value: PointerProperty(type=MultipleFileSelect)
@@ -343,6 +354,7 @@ class Attribute(PropertyGroup):
float_value: float
length_value: float
enum_items: str
enum_items_dynamic: str
enum_descriptions: bpy.types.bpy_prop_collection_idprop[StrProperty]
enum_value: str
filepath_value: MultipleFileSelect
@@ -365,7 +377,11 @@ class Attribute(PropertyGroup):
return self.string_value.replace("\\n", "\n")
if self.data_type == "file":
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":
# IfcOpenShell expects bool if IfcLogical is True/False.
value = value == "TRUE"
+17
View File
@@ -36,6 +36,7 @@ import bonsai.tool as tool
from ifcopenshell.util.file import IfcHeaderExtractor
from bonsai.bim.prop import Attribute
from typing import Optional, TYPE_CHECKING
from natsort import natsorted
class IFCFileSelector:
@@ -1167,6 +1168,22 @@ class BIM_PT_tab_operations(Panel):
def refresh():
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:
+1 -1
View File
@@ -93,7 +93,7 @@ class Blender(bonsai.core.tool.Blender):
- (identifier, name, description, icon, number)
"""
BLENDER_ENUM_ITEMS = list[BLENDER_ENUM_ITEM]
BLENDER_ENUM_ITEMS = Iterable[BLENDER_ENUM_ITEM]
@classmethod
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:
from bonsai.bim.module.owner.prop import BIMOwnerProperties
from bonsai.bim.prop import Attribute
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)
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
def export_application_attributes(cls) -> dict[str, Any]:
props = cls.get_owner_props()
attributes = bonsai.bim.helper.export_attributes(props.application_attributes)
bonsai.bim.helper.process_exported_entity_attribute(attributes, "ApplicationDeveloper")
return attributes
+14 -4
View File
@@ -23,6 +23,8 @@ import bonsai.core.tool
import bonsai.tool as tool
from test.bim.bootstrap import NewFile
from bonsai.tool.owner import Owner as subject
from typing import Any
from functools import cache
class TestImplementsTool(NewFile):
@@ -584,12 +586,18 @@ class TestSetUser(NewFile):
class TestApplicationUI(NewFile):
ifc_class = "IfcApplication"
attrs = {
attrs_: dict[str, Any] = {
"Version": "v001",
"ApplicationFullName": "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):
application = ifcopenshell.file().create_entity(self.ifc_class)
subject.set_application(application)
@@ -614,14 +622,16 @@ class TestApplicationUI(NewFile):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
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)
subject.set_application(application)
subject.import_application_attributes()
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
def test_export(self):
self.test_import()
assert subject.export_application_attributes() == self.attrs
assert subject.export_application_attributes() == self.get_attrs(tool.Ifc.get())