Big bSDD overhaul for properties and classifications. See description.

* bSDD now supports a couple more args when querying the API.
 * Fetching properties via the classification UI has been removed
 * The bSDD dictionary selector has been merged into the classification
source dropdown
 * The bSDD dictionary selector merged into the property set name
dropdown
 * You can now search for properties in the bSDD directly when adding
properties
 * You can browse properties via groups or classifications (based on
assigned classifications)
 * You can browse properties via keyword (with some limitations due to
the API)
 * You can selectively choose which properties you then want to add.
Only basic support right now.
This commit is contained in:
Dion Moult
2025-05-28 20:51:02 +10:00
parent e9eb5f892a
commit a929ab0c4f
15 changed files with 448 additions and 85 deletions
@@ -20,15 +20,20 @@ import bpy
from . import ui, prop, operator
classes = (
operator.GetBSDDClassificationProperties,
operator.AddBSDDProperties,
operator.ImportBSDDClasses,
operator.LoadBSDDDictionaries,
operator.SearchBSDDClass,
operator.SearchBSDDClassifications,
operator.SearchBSDDProperties,
prop.BSDDDictionary,
prop.BSDDClassification,
prop.BSDDProperty,
prop.BSDDPset,
prop.BIMBSDDProperties,
ui.BIM_UL_bsdd_dictionaries,
ui.BIM_UL_bsdd_classifications,
ui.BIM_UL_bsdd_dictionaries,
ui.BIM_UL_bsdd_classes,
ui.BIM_UL_bsdd_properties,
ui.BIM_PT_bsdd,
)
+58 -12
View File
@@ -19,6 +19,7 @@
import bpy
import bsdd
import bonsai.tool as tool
import ifcopenshell.util.element
from bonsai.core import bsdd as core
@@ -32,28 +33,73 @@ class LoadBSDDDictionaries(bpy.types.Operator):
return {"FINISHED"}
class SearchBSDDClass(bpy.types.Operator):
class SearchBSDDClassifications(bpy.types.Operator):
bl_idname = "bim.search_bsdd_classifications"
bl_label = "Search bSDD Class"
bl_description = "Search for bSDD classes by the provided keyword"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
keyword = context.scene.BIMBSDDProperties.keyword
classes_found = core.search_class(keyword, tool.Bsdd)
self.report({"INFO"}, f"{classes_found} bSDD classes found for '{keyword}'.")
total = core.search_bsdd_class(tool.Bsdd, context.scene.BIMBSDDProperties.keyword)
self.report({"INFO"}, f"{total} bSDD classes found.")
return {"FINISHED"}
class GetBSDDClassificationProperties(bpy.types.Operator):
bl_idname = "bim.get_bsdd_classification_properties"
bl_label = "Search bSDD Class Properties"
bl_description = "Search for bSDD class properties for the currently selected class"
class ImportBSDDClasses(bpy.types.Operator):
bl_idname = "bim.import_bsdd_classes"
bl_label = "Import bSDD Classes"
bl_description = "Load bSDD classes that apply to the current element"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
pset_data = core.get_class_properties(tool.Bsdd)
psets_found = len(pset_data)
props_found = sum(len(pset) for pset in pset_data.values())
self.report({"INFO"}, f"{psets_found} psets found ({props_found} props) for the active classification.")
props = tool.Bsdd.get_bsdd_props()
core.import_bsdd_classes(tool.Bsdd, self.obj, self.obj_type)
self.report({"INFO"}, f"{len(props.classes)} bSDD classes found.")
return {"FINISHED"}
class SearchBSDDProperties(bpy.types.Operator):
bl_idname = "bim.search_bsdd_properties"
bl_label = "Search bSDD Properties"
bl_description = "Search for bSDD properties that apply to the current element"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def execute(self, context):
props = tool.Bsdd.get_bsdd_props()
core.search_bsdd_properties(tool.Bsdd, context.scene.BIMBSDDProperties.keyword, self.obj, self.obj_type)
self.report({"INFO"}, f"{len(props.properties)} bSDD properties found.")
return {"FINISHED"}
class AddBSDDProperties(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_bsdd_properties"
bl_label = "Add bSDD Properties"
bl_description = "Add selected bSDD properties"
bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
obj_type: bpy.props.StringProperty()
def _execute(self, context):
self.file = tool.Ifc.get()
bprops = tool.Bsdd.get_bsdd_props()
psets = {}
for selected_property in bprops.selected_properties:
psets.setdefault(selected_property.metadata, {})[selected_property.name] = selected_property.get_value()
props = tool.Pset.get_pset_props(self.obj, self.obj_type)
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
element = tool.Ifc.get().by_id(ifc_definition_id)
properties = {}
current_psets = ifcopenshell.util.element.get_psets(element, verbose=True)
for pset_name, properties in psets.items():
if pset := current_psets.get(pset_name, None):
pset = self.file.by_id(pset["id"])
else:
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name=pset_name)
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties=properties)
+39 -1
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
from bpy.types import PropertyGroup
from bonsai.bim.module.bsdd.data import BSDDData
from bonsai.bim.prop import Attribute, StrProperty
@@ -30,6 +31,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import Union
def get_active_dictionary(self, context):
@@ -42,6 +44,15 @@ def update_is_active(self: "BSDDDictionary", context: bpy.types.Context) -> None
BSDDData.data["active_dictionary"] = BSDDData.active_dictionary()
def update_is_selected(self: "BSDDProperty", context: bpy.types.Context) -> None:
tool.Bsdd.import_selected_properties()
def update_active_class_index(self: "BIMBSDDProperties", context: bpy.types.Context) -> None:
tool.Bsdd.import_class_properties()
BSDDData.data["active_dictionary"] = BSDDData.active_dictionary()
class BSDDDictionary(PropertyGroup):
name: StringProperty(name="Name")
uri: StringProperty(name="URI")
@@ -57,11 +68,21 @@ class BSDDDictionary(PropertyGroup):
class BSDDClassification(PropertyGroup):
name: StringProperty(name="Name")
reference_code: StringProperty(name="Reference Code")
uri: StringProperty(name="Namespace URI")
uri: StringProperty(name="URI")
dictionary_name: StringProperty(name="Dictionary Name")
dictionary_namespace_uri: StringProperty(name="Dictionary Namespace URI")
class BSDDProperty(PropertyGroup):
name: StringProperty(name="Name")
code: StringProperty(name="Code")
uri: StringProperty(name="URI")
pset: StringProperty(name="Pset")
is_selected: BoolProperty(
name="Is Selected", description="Select to add or edit this property", default=False, update=update_is_selected
)
class BSDDPset(PropertyGroup):
name: StringProperty(name="Name")
properties: CollectionProperty(name="Properties", type=Attribute)
@@ -75,6 +96,19 @@ class BIMBSDDProperties(PropertyGroup):
active_dictionary_index: IntProperty(name="Active Dictionary Index")
classifications: CollectionProperty(name="Classifications", type=BSDDClassification)
active_classification_index: IntProperty(name="Active Classification Index")
property_filter_mode: EnumProperty(
name="Property Filter Mode",
items=[
("CLASS", "By Class", "Browse properties by class or group"),
("KEYWORD", "By Keyword", "Search properties directly using a keyword"),
],
default="CLASS",
)
classes: CollectionProperty(name="Classes", type=BSDDClassification)
active_class_index: IntProperty(name="Active Class Index", update=update_active_class_index)
properties: CollectionProperty(name="Properties", type=BSDDProperty)
active_property_index: IntProperty(name="Active Property Index")
selected_properties: CollectionProperty(name="Selected Properties", type=Attribute)
keyword: StringProperty(name="Keyword", description="Query for bsdd classes search, case and accent insensitive")
should_filter_ifc_class: BoolProperty(
name="Filter Active IFC Class",
@@ -96,3 +130,7 @@ class BIMBSDDProperties(PropertyGroup):
name="Load Test Dictionaries", description="Load dictionaries that are for testing only", default=False
)
classification_psets: CollectionProperty(name="Classification Psets", type=BSDDPset)
@property
def active_class(self) -> Union[BSDDClassification, None]:
return tool.Blender.get_active_uilist_element(self.classes, self.active_class_index)
+20 -1
View File
@@ -54,7 +54,7 @@ class BIM_PT_bsdd(Panel):
selected_dictionary = None
if selected_dictionary:
layout.label(text="Selected dictionary:")
layout.label(text="Selected Dictionary:")
box = layout.box()
row = box.row(align=True)
row.label(text="Language")
@@ -92,3 +92,22 @@ class BIM_UL_bsdd_classifications(UIList):
row.label(text=item.reference_code)
row.label(text=item.name)
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
class BIM_UL_bsdd_classes(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
class BIM_UL_bsdd_properties(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
if item.pset:
row.label(text=item.pset)
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
row.prop(item, "is_selected", icon="CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT", text="", emboss=False)
@@ -41,6 +41,7 @@ class ClassificationsData:
cls.data["has_classification_file"] = cls.has_classification_file()
cls.data["classifications"] = cls.classifications()
cls.data["available_classifications"] = cls.available_classifications()
cls.data["classification_source"] = cls.classification_source()
@classmethod
def has_classification_file(cls):
@@ -62,6 +63,19 @@ class ClassificationsData:
return []
return [(str(e.id()), e.Name, "") for e in IfcStore.classification_file.by_type("IfcClassification")]
@classmethod
def classification_source(cls):
items = [
("FILE", "IFC File", ""),
("MANUAL", "Manual Entry", ""),
]
bprops = tool.Bsdd.get_bsdd_props()
dictionaries = [(d.uri, f"bSDD: {d.name}", "") for d in bprops.dictionaries if d.is_active]
if dictionaries:
items.append(("BSDD", "All Active bSDDs", ""))
items.extend(dictionaries)
return items
class ReferencesData:
@classmethod
@@ -115,11 +115,12 @@ class AddClassificationFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMBSDDProperties
if props.active_dictionary == "ALL":
dictionaries = [d.uri for d in props.dictionaries if d.is_active]
cprops = context.scene.BIMClassificationProperties
bprops = tool.Bsdd.get_bsdd_props()
if cprops.classification_source == "BSDD":
dictionaries = [d.uri for d in bprops.dictionaries if d.is_active]
else:
dictionaries = [props.active_dictionary]
dictionaries = [cprops.classification_source]
for uri in dictionaries:
if not (dictionary := tool.Bsdd.get_dictionary(uri)):
continue
@@ -44,6 +44,12 @@ def get_classifications(self, context):
return ClassificationReferencesData.data["classifications"]
def get_classification_source(self, context):
if not ClassificationsData.is_loaded:
ClassificationsData.load()
return ClassificationsData.data["classification_source"]
class ClassificationReference(PropertyGroup):
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
@@ -54,15 +60,7 @@ class ClassificationReference(PropertyGroup):
class BIMClassificationProperties(PropertyGroup):
is_adding: BoolProperty(name="Is Adding", default=False)
classification_source: EnumProperty(
items=[
("FILE", "IFC File", ""),
("BSDD", "buildingSMART Data Dictionary", ""),
("MANUAL", "Manual Entry", ""),
],
name="Classification Source",
default="FILE",
)
classification_source: EnumProperty(items=get_classification_source, name="Classification Source")
available_classifications: EnumProperty(items=get_available_classifications, name="Available Classifications")
classification_attributes: CollectionProperty(name="Classification Attributes", type=Attribute)
active_classification_id: IntProperty(name="Active Classification Id")
@@ -54,10 +54,10 @@ class BIM_PT_classifications(Panel):
if self.props.classification_source == "FILE":
self.draw_add_file_ui(context)
elif self.props.classification_source == "BSDD":
self.draw_add_bsdd_ui(context)
elif self.props.classification_source == "MANUAL":
self.draw_add_manual_ui(context)
else:
self.draw_add_bsdd_ui(context)
for classification in ClassificationsData.data["classifications"]:
if self.props.active_classification_id == classification["id"]:
@@ -76,11 +76,6 @@ class BIM_PT_classifications(Panel):
row.operator("bim.enable_adding_manual_classification", text="Add Classification", icon="ADD")
def draw_add_bsdd_ui(self, context):
self.bprops = context.scene.BIMBSDDProperties
row = self.layout.row()
row.prop(self.bprops, "active_dictionary", text="")
row = self.layout.row()
row.operator("bim.add_classification_from_bsdd", icon="ADD")
@@ -139,10 +134,10 @@ class ReferenceUI:
if self.sprops.classification_source == "FILE":
self.draw_add_file_ui(context)
elif self.sprops.classification_source == "BSDD":
self.draw_add_bsdd_ui(context)
elif self.sprops.classification_source == "MANUAL":
self.draw_add_manual_ui(context)
else:
self.draw_add_bsdd_ui(context)
def draw_add_manual_ui(self, context):
row = self.layout.row()
@@ -158,17 +153,11 @@ class ReferenceUI:
row.operator("bim.enable_adding_manual_classification_reference", text="Add Reference", icon="ADD")
def draw_add_bsdd_ui(self, context):
row = self.layout.row()
row.prop(self.bprops, "active_dictionary", text="")
row = self.layout.row(align=True)
row.prop(self.bprops, "keyword", text="")
row.prop(self.bprops, "should_filter_ifc_class", text="", icon="FILTER")
row.operator("bim.search_bsdd_classifications", text="", icon="VIEWZOOM")
row = self.layout.row()
row.prop(self.bprops, "should_filter_ifc_class")
row.prop(self.bprops, "use_only_ifc_properties")
if len(self.bprops.classifications):
self.layout.template_list(
"BIM_UL_bsdd_classifications",
@@ -189,21 +178,6 @@ class ReferenceUI:
)
op.obj = self.obj
op.obj_type = self.obj_type
row.operator("bim.get_bsdd_classification_properties", text="", icon="COPY_ID")
if len(self.bprops.classification_psets):
use_only_ifc_properties = self.bprops.use_only_ifc_properties
for pset in self.bprops.classification_psets:
properties = pset.properties
if use_only_ifc_properties:
properties = [p for p in properties if p.metadata == "IFC"]
if not properties:
continue
box = self.layout.box()
row = box.row()
row.label(text=pset.name, icon="COPY_ID")
bonsai.bim.helper.draw_attributes(properties, box)
def draw_add_file_ui(self, context):
if not self.data.data["active_classification_library"]:
+18 -1
View File
@@ -81,7 +81,24 @@ def get_pset_name(self, context):
results = get_profile_pset_names(self, context)
elif prop_type == "WorkSchedulePsetProperties":
results = get_work_schedule_pset_names(self, context)
return [("BBIM_CUSTOM", "Custom Pset", "Create a property set without using a template."), None] + results
items = [("BBIM_CUSTOM", "Custom Pset", "Create a property set without using a template.")]
bprops = tool.Bsdd.get_bsdd_props()
dictionaries = [(d.uri, f"bSDD: {d.name}", "") for d in bprops.dictionaries if d.is_active]
if dictionaries:
items.extend(
[
None,
(
"BBIM_BSDD",
"All Data Dictionaries",
"Manage properties from all active buildingSMART Data Dictionaries",
),
]
)
items.extend(dictionaries)
items.append(None)
items.extend(results)
return items
def get_object_pset_name(self, context):
+67 -4
View File
@@ -20,7 +20,7 @@ from __future__ import annotations
import bpy
import bonsai.tool as tool
from bpy.types import Panel
from bonsai.bim.helper import prop_with_search, get_display_value
from bonsai.bim.helper import prop_with_search, get_display_value, draw_attribute
from bonsai.bim.module.pset.data import (
ObjectPsetsData,
ObjectQtosData,
@@ -256,11 +256,74 @@ class BIM_PT_object_psets(Panel):
ObjectPsetsData.load()
props = context.active_object.PsetProperties
self.bprops = context.scene.BIMBSDDProperties
row = self.layout.row(align=True)
prop_with_search(row, props, "pset_name", text="")
op = row.operator("bim.add_pset", icon="ADD", text="")
op.obj = context.active_object.name
op.obj_type = "Object"
if props.pset_name != "BBIM_BSDD" and not props.pset_name.startswith(tool.Bsdd.identifier_url):
op = row.operator("bim.add_pset", icon="ADD", text="")
op.obj = context.active_object.name
op.obj_type = "Object"
else:
row = self.layout.row(align=True)
row.prop(self.bprops, "property_filter_mode", text="")
if self.bprops.property_filter_mode == "CLASS":
row.prop(self.bprops, "should_filter_ifc_class", text="", icon="FILTER")
op = row.operator("bim.import_bsdd_classes", text="", icon="FILE_REFRESH")
op.obj = context.active_object.name
op.obj_type = "Object"
if len(self.bprops.classes):
self.layout.template_list(
"BIM_UL_bsdd_classes",
"",
self.bprops,
"classes",
self.bprops,
"active_class_index",
)
if len(self.bprops.properties):
self.layout.template_list(
"BIM_UL_bsdd_properties",
"",
self.bprops,
"properties",
self.bprops,
"active_property_index",
)
else:
row = self.layout.row()
row.label(text="No Results")
else:
row = self.layout.row()
row.label(text="No Results")
elif self.bprops.property_filter_mode == "KEYWORD":
row.prop(self.bprops, "keyword", text="")
op = row.operator("bim.search_bsdd_properties", text="", icon="VIEWZOOM")
op.obj = context.active_object.name
op.obj_type = "Object"
if len(self.bprops.properties):
self.layout.template_list(
"BIM_UL_bsdd_properties",
"",
self.bprops,
"properties",
self.bprops,
"active_property_index",
)
else:
row = self.layout.row()
row.label(text="No Results")
for selected_property in self.bprops.selected_properties:
row = self.layout.row(align=True)
# row.prop(selected_property, "metadata", text="")
draw_attribute(selected_property, row)
row = self.layout.row()
op = row.operator("bim.add_bsdd_properties", icon="ADD")
op.obj = context.active_object.name
op.obj_type = "Object"
global_props = tool.Pset.get_global_pset_props()
if not props.active_pset_id and props.active_pset_name and props.active_pset_type == "PSET":
+7 -9
View File
@@ -26,14 +26,12 @@ if TYPE_CHECKING:
import bonsai.tool as tool
def get_class_properties(bsdd: tool.Bsdd) -> dict[str, dict[str, Any]]:
bsdd.clear_class_psets()
data = bsdd.get_active_class_data()
pset_dict = bsdd.get_property_dict(data)
if pset_dict is None:
return {}
bsdd.create_class_psets(pset_dict)
return pset_dict
def import_bsdd_classes(bsdd: tool.Bsdd, obj, obj_type) -> int:
return bsdd.import_classes(obj, obj_type)
def search_bsdd_properties(bsdd: tool.Bsdd, keyword: str, obj, obj_type) -> int:
return bsdd.import_properties(obj, obj_type, keyword)
def load_bsdd(bsdd: tool.Bsdd) -> None:
@@ -41,7 +39,7 @@ def load_bsdd(bsdd: tool.Bsdd) -> None:
bsdd.create_dictionaries(bsdd.get_dictionaries())
def search_class(keyword: str, bsdd: tool.Bsdd) -> int:
def search_bsdd_class(bsdd: tool.Bsdd, keyword: str) -> int:
bsdd.clear_classes()
related_entities = bsdd.get_related_ifc_entities()
return bsdd.search_class(keyword, related_entities)
+174 -7
View File
@@ -5,11 +5,15 @@ import json
import bsdd
import ifcopenshell.util.type
import ifcopenshell.util.element
import ifcopenshell.util.classification
from typing import Any, Union, Optional, TYPE_CHECKING
class Bsdd(bonsai.core.tool.Bsdd):
identifier_url = "https://identifier.buildingsmart.org"
client = bsdd.Client()
bsdd_classes: dict[str, dict] = {}
bsdd_properties: dict[str, dict] = {}
@classmethod
def get_bsdd_props(cls):
@@ -23,6 +27,10 @@ class Bsdd(bonsai.core.tool.Bsdd):
def clear_classes(cls) -> None:
bpy.context.scene.BIMBSDDProperties.classifications.clear()
@classmethod
def clear_properties(cls) -> None:
bpy.context.scene.BIMBSDDProperties.properties.clear()
@classmethod
def clear_dictionaries(cls) -> None:
bpy.context.scene.BIMBSDDProperties.dictionaries.clear()
@@ -94,10 +102,12 @@ class Bsdd(bonsai.core.tool.Bsdd):
return list(filter(lambda d: d["status"] in statuses, dicts))
@classmethod
def get_property_dict(cls, class_data: Union[bsdd.ClassContractV1, dict]) -> Union[dict[str, dict[str, Any]], None]:
def get_class_properties(
cls, class_data: Union[bsdd.ClassContractV1, dict]
) -> Union[dict[str, dict[str, Any]], None]:
properties = class_data.get("classProperties", None)
if not properties:
return None
return {}
ifc_class = class_data.get("relatedIfcEntityNames") or ""
if ifc_class:
@@ -155,11 +165,12 @@ class Bsdd(bonsai.core.tool.Bsdd):
limit: int = 100,
should_paginate: bool = True,
):
props = cls.get_bsdd_props()
cprops = bpy.context.scene.BIMClassificationProperties
bprops = cls.get_bsdd_props()
dictionary_uris = (
[d.uri for d in props.dictionaries if d.is_active]
if props.active_dictionary == "ALL"
else [props.active_dictionary]
[d.uri for d in bprops.dictionaries if d.is_active]
if cprops.classification_source == "BSDD"
else [cprops.classification_source]
)
for dictionary_uri in dictionary_uris:
for related_ifc_entity in related_ifc_entities or [None]:
@@ -174,7 +185,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
dictionary_name = response.get("name", "")
dictionary_namespace_uri = response.get("uri", "")
for _class in sorted(response.get("classes", []), key=lambda c: c["referenceCode"]):
prop = props.classifications.add()
prop = bprops.classifications.add()
prop.name = _class["name"]
prop.reference_code = _class["referenceCode"]
prop.uri = _class["uri"]
@@ -198,3 +209,159 @@ class Bsdd(bonsai.core.tool.Bsdd):
@classmethod
def should_filter_ifc_class(cls) -> bool:
return bpy.context.scene.BIMBSDDProperties.should_filter_ifc_class
@classmethod
def get_bsdd_class(cls, uri: str) -> dict:
if not (bsdd_class := cls.bsdd_classes.get(uri, {})):
bsdd_class = cls.client.get_class(uri)
cls.bsdd_classes[uri] = bsdd_class
return bsdd_class
@classmethod
def get_bsdd_property(cls, uri: str) -> dict:
if not (bsdd_property := cls.bsdd_properties.get(uri, {})):
bsdd_property = cls.client.get_property(uri, include_classes=True)
cls.bsdd_properties[uri] = bsdd_property
return bsdd_property
@classmethod
def import_classes(cls, obj, obj_type) -> None:
pprops = tool.Pset.get_pset_props(obj, obj_type)
props = cls.get_bsdd_props()
props.classes.clear()
classes = set()
for obj in tool.Blender.get_selected_objects(include_active=True):
if element := tool.Ifc.get_entity(obj):
for reference in ifcopenshell.util.classification.get_references(element):
if (uri := reference.Location) and uri.startswith(cls.identifier_url):
classes.add((reference[1] or reference[2] or "Unnamed", uri))
dictionary_uris = (
[d.uri for d in props.dictionaries if d.is_active]
if pprops.pset_name == "BBIM_BSDD"
else [pprops.pset_name]
)
related_ifc_entities = cls.get_related_ifc_entities()
for dictionary_uri in dictionary_uris:
for related_ifc_entity in related_ifc_entities or [None]:
bsdd_classes = cls.client.get_classes(
dictionary_uri=dictionary_uri,
class_type="GroupOfProperties",
use_nested_classes=False,
related_ifc_entity=related_ifc_entity,
)
for bsdd_class in bsdd_classes["classes"]:
classes.add((bsdd_class["name"], bsdd_class["uri"]))
for bsdd_class in classes:
new = props.classes.add()
new.name = bsdd_class[0]
new.uri = bsdd_class[1]
@classmethod
def import_class_properties(cls) -> None:
props = cls.get_bsdd_props()
props.properties.clear()
if not (active_class := props.active_class):
return
if not (bsdd_class := cls.get_bsdd_class(active_class.uri)):
return
for bsdd_prop in bsdd_class.get("classProperties", []):
if not bsdd_prop.get("propertySet", None):
continue
cls.bsdd_properties[bsdd_prop["uri"]] = bsdd_prop
new = props.properties.add()
new.name = bsdd_prop["name"]
new.pset = bsdd_prop["propertySet"]
new.uri = bsdd_prop["uri"]
@classmethod
def import_properties(cls, obj, obj_type, keyword) -> None:
props = cls.get_bsdd_props()
props.properties.clear()
pprops = tool.Pset.get_pset_props(obj, obj_type)
dictionary_uris = (
[d.uri for d in props.dictionaries if d.is_active]
if pprops.pset_name == "BBIM_BSDD"
else [pprops.pset_name]
)
for dictionary_uri in dictionary_uris:
for bsdd_prop in cls.client.get_properties(dictionary_uri, keyword)["properties"]:
new = props.properties.add()
new.name = bsdd_prop["name"]
new.uri = bsdd_prop["uri"]
@classmethod
def import_selected_properties(cls) -> None:
props = cls.get_bsdd_props()
data_type_map = {
"String": "string",
"Real": "float",
"Boolean": "boolean",
}
imported_props = set()
for bsdd_prop in props.properties:
if not bsdd_prop.is_selected:
continue
if not (pset_name := bsdd_prop.pset):
prop_data = cls.get_bsdd_property(bsdd_prop.uri)
pset_name = prop_data.get("propertyClasses", [{}])[0].get("propertySet", "")
imported_props.add((pset_name, bsdd_prop.name))
if (
selected_property := props.selected_properties.get(bsdd_prop.name)
) and selected_property.metadata == pset_name:
continue
data = cls.bsdd_properties[bsdd_prop.uri]
predefined_value = data.get("predefinedValue")
if predefined_value:
possible_values = [predefined_value]
else:
possible_values = data.get("allowedValues", []) or []
possible_values = [v["value"] for v in possible_values]
new = props.selected_properties.add()
new.name = bsdd_prop.name
if possible_values:
new.enum_items = json.dumps(possible_values)
new.data_type = "enum"
else:
new.data_type = data_type_map.get(data["dataType"], "string")
new.description = data.get("description", "")
new.metadata = pset_name
to_remove = []
for i, selected_property in enumerate(props.selected_properties):
if (selected_property.metadata, selected_property.name) not in imported_props:
to_remove.append(i)
for i in to_remove[::-1]:
props.selected_properties.remove(i)
@classmethod
def get_applicable_psets(cls, element: ifcopenshell.entity_instance):
uris = set()
for reference in ifcopenshell.util.classification.get_references(element):
if (uri := reference.Location) and uri.startswith(cls.identifier_url):
uris.add(uri)
psets = set()
for uri in uris:
if not (bsdd_class := cls.bsdd_classes.get(uri, None)):
continue
for class_pset in bsdd_class.get("classProperties", []):
if not (pset_name := class_pset.get("propertySet", None)):
continue
psets.add((uri, bsdd_class["name"], pset_name))
return psets
@classmethod
def is_applicable(cls, pset_uri: str, element: ifcopenshell.entity_instance) -> bool:
uris = set()
for reference in ifcopenshell.util.classification.get_references(element):
if (uri := reference.Location) and uri.startswith(cls.identifier_url):
uris.add(uri)
class_uri, pset_name = pset_uri.rsplit("#", 1)
return class_uri in uris
+1 -1
View File
@@ -73,7 +73,7 @@ class Pset(bonsai.core.tool.Pset):
def get_pset_name(cls, obj: str, obj_type: tool.Ifc.OBJECT_TYPE, pset_type: PSET_TYPE = "PSET") -> str:
props = cls.get_pset_props(obj, obj_type)
name = props.pset_name if pset_type == "PSET" else props.qto_name
if name == "BBIM_CUSTOM":
if name in ("BBIM_CUSTOM", "BBIM_BSDD"):
return ""
return name
+14 -2
View File
@@ -658,13 +658,25 @@ class Client:
return self.get(endpoint, params)
def get_properties(
self, dictionary_uri: str, offset: int = 0, limit: int = 100, language_code: str = "", version: int = 1
self,
dictionary_uri: str,
search_text: str = "",
offset: int = 0,
limit: int = 100,
language_code: str = "",
version: int = 1,
) -> DictionaryPropertiesResponseContractV1:
"""
Get Dictionary with its properties
"""
endpoint = f"Dictionary/v{version}/Properties"
params = {"Uri": dictionary_uri, "languageCode": language_code, "offset": offset, "limit": limit}
params = {
"Uri": dictionary_uri,
"SearchText": search_text,
"languageCode": language_code,
"offset": offset,
"limit": limit,
}
return self.get(endpoint, params)
def get_class(
@@ -21,6 +21,12 @@ from typing import Optional
def get_references(element: ifcopenshell.entity_instance, should_inherit=True) -> set[ifcopenshell.entity_instance]:
"""Gets classification references associated with the element
:param should_inherit: If true, classification references are inherited
from the type. Classifications can be overriden per system.
:return: A set of IfcClassificationReference
"""
results = set()
if not element.is_a("IfcRoot"):
if (references := getattr(element, "HasExternalReferences", None)) is not None or (
@@ -52,6 +58,11 @@ def get_references(element: ifcopenshell.entity_instance, should_inherit=True) -
def get_classification(reference: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
"""Get the IfcClassification that a classification reference belongs to
:param reference: An IfcClassificationReference
:return: IfcClassification
"""
if reference.is_a("IfcClassification"):
return reference
return get_classification(reference.ReferencedSource) if reference.ReferencedSource is not None else None