Compare commits

...

1 Commits

Author SHA1 Message Date
Richard Brice cffb933efb Gets the pick list for bSDD properties with Allowed Values working 2026-08-11 14:47:44 -07:00
3 changed files with 64 additions and 4 deletions
+40 -3
View File
@@ -170,7 +170,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
psets.setdefault(pset, {})
predefined_value = prop.get("predefinedValue")
if predefined_value:
if predefined_value and predefined_value != "None":
possible_values = [predefined_value]
else:
possible_values = prop.get("allowedValues", []) or []
@@ -369,7 +369,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
data = cls.bsdd_properties[bsdd_prop.uri]
predefined_value = data.get("predefinedValue")
if predefined_value:
if predefined_value and predefined_value != "None":
possible_values = [predefined_value]
else:
possible_values = data.get("allowedValues", []) or []
@@ -401,7 +401,13 @@ class Bsdd(bonsai.core.tool.Bsdd):
uris.add(uri)
psets = set()
for uri in uris:
if not (bsdd_class := cls.bsdd_classes.get(uri, None)):
try:
# Cache may not be populated yet (e.g. a fresh session that never
# browsed this class), so fetch on a cache miss instead of skipping.
bsdd_class = cls.get_bsdd_class(uri)
except Exception:
continue
if not bsdd_class:
continue
for class_pset in bsdd_class.get("classProperties", []):
if not (pset_name := class_pset.get("propertySet", None)):
@@ -409,6 +415,37 @@ class Bsdd(bonsai.core.tool.Bsdd):
psets.add((uri, bsdd_class["name"], pset_name))
return psets
@classmethod
def get_bsdd_pset_property_values(
cls, element: ifcopenshell.entity_instance, pset_name: str
) -> dict[str, list[str]]:
"""Map bSDD property code -> allowed/predefined values for properties bSDD says
are applicable to `element` (via its classification references) under `pset_name`.
Used to recognise a Pset property as bSDD-sourced even outside the dedicated
bSDD add-property flow (e.g. a Pset created in a previous session), so it can
still be edited as a picklist.
"""
result: dict[str, list[str]] = {}
for uri, class_name, applicable_pset_name in cls.get_applicable_psets(element):
if applicable_pset_name != pset_name:
continue
bsdd_class = cls.get_bsdd_class(uri)
for class_prop in bsdd_class.get("classProperties", []):
if class_prop.get("propertySet") != pset_name:
continue
code = class_prop.get("propertyCode")
if not code or code in result:
continue
predefined_value = class_prop.get("predefinedValue")
if predefined_value and predefined_value != "None":
result[code] = [predefined_value]
continue
allowed_values = class_prop.get("allowedValues", []) or []
if allowed_values:
result[code] = [v["value"] for v in allowed_values]
return result
@classmethod
def is_applicable(cls, pset_uri: str, element: ifcopenshell.entity_instance) -> bool:
uris = set()
+23
View File
@@ -223,6 +223,18 @@ class Pset(bonsai.core.tool.Pset):
elif pset.is_a("IfcMaterialProperties") or pset.is_a("IfcProfileProperties"):
pset_props = pset.Properties
# If this Pset's owning element is classified against a bSDD class that defines
# this Pset, recognise properties matching bSDD property codes as picklists,
# even though the Pset wasn't necessarily created through the bSDD add-property UI.
bsdd_allowed_values: dict[str, list[str]] = {}
if pset.is_a("IfcPropertySet"):
elements = ifcopenshell.util.element.get_elements_by_pset(pset)
if elements:
try:
bsdd_allowed_values = tool.Bsdd.get_bsdd_pset_property_values(next(iter(elements)), pset.Name)
except Exception:
pass
prop_templates: dict[str, ifcopenshell.entity_instance] = {}
if pset_template:
prop_templates = {prop.Name: prop for prop in pset_template.HasPropertyTemplates}
@@ -286,6 +298,17 @@ class Pset(bonsai.core.tool.Pset):
metadata.set_value(metadata.get_value_default() if metadata.is_null else value)
process_prop_description(metadata)
if prop.is_a("IfcPropertySingleValue") and (possible_values := bsdd_allowed_values.get(prop.Name)):
str_value = None if value is None else str(value)
if str_value is not None and str_value not in possible_values:
# Preserve a legacy/imported value that doesn't match the current
# bSDD enumeration instead of silently dropping it.
possible_values = [*possible_values, str_value]
metadata.enum_items = json.dumps(possible_values)
metadata.data_type = "enum"
if str_value is not None:
metadata.enum_value = str_value
@classmethod
def get_prop_template_primitive_type(cls, prop_template: ifcopenshell.entity_instance) -> str:
if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
+1 -1
View File
@@ -1026,7 +1026,7 @@ def apply_ifc_classification_properties(
psets = ifcopenshell.util.element.get_psets(element)
for prop in classificationProperties:
predefinedValue = prop.get("predefinedValue")
if not predefinedValue or prop.get("propertyDomainName") != "IFC":
if not predefinedValue or predefinedValue == "None" or prop.get("propertyDomainName") != "IFC":
continue
pset = psets.get(prop["propertySet"])
if pset: