mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
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:
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user