diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index dcea0f979e..578bd2ae10 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -95,6 +95,7 @@ classes = [ operator.SelectURIAttribute, operator.EditBlenderCollection, operator.BIM_OT_open_webbrowser, + operator.BIM_OT_show_description, prop.StrProperty, operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty prop.ObjProperty, diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index dcf1ea36d6..64bd0e4634 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -23,8 +23,10 @@ import math import zipfile import ifcopenshell import ifcopenshell.util.attribute +from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc from mathutils import geometry from mathutils import Vector +import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore @@ -83,6 +85,7 @@ def import_attribute(attribute, props, data, callback=None): new.is_null = data[attribute.name()] is None new.is_optional = attribute.optional() new.data_type = data_type if isinstance(data_type, str) else "" + new.ifc_class = data["type"] is_handled_by_callback = callback(attribute.name(), new, data) if callback else None if is_handled_by_callback: @@ -100,9 +103,38 @@ def import_attribute(attribute, props, data, callback=None): elif data_type == "float": new.float_value = 0.0 if new.is_null else data[attribute.name()] elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - if data[attribute.name()]: - new.enum_value = data[attribute.name()] + enum_items = ifcopenshell.util.attribute.get_enum_items(attribute) + new.enum_items = json.dumps(enum_items) + add_attribute_enum_items_descriptions(new, enum_items) + if data[new.name]: + new.enum_value = data[new.name] + add_attribute_description(new) + + +def add_attribute_enum_items_descriptions(attribute_blender, enum_items): + attribute_blender.enum_descriptions.clear() + if isinstance(enum_items, dict): + enum_items = enum_items.keys() + version = tool.Ifc.get_schema() + for enum_item in enum_items: + new_enum_description = attribute_blender.enum_descriptions.add() + try: + description = get_predefined_type_doc(version, attribute_blender.ifc_class, enum_item) + except KeyError: # TODO this only supports predefined type enums. Add support for other types of enums ? + description = "" + new_enum_description.name = description + + +def add_attribute_description(attribute_blender): + if not attribute_blender.name: + return + version = tool.Ifc.get_schema() + try: + description = get_attribute_doc(version, attribute_blender.ifc_class, attribute_blender.name) + except RuntimeError: # It's not an Entity Attribute. Let's try a Property Set attribute. + description = get_property_doc(version, attribute_blender.ifc_class, attribute_blender.name).get("description") + if description: + attribute_blender.description = description def export_attributes(props, callback=None): @@ -119,11 +151,14 @@ def prop_with_search(layout, data, prop_name, **kwargs): # kwargs are layout.prop arguments (text, icon, etc.) row = layout.row(align=True) row.prop(data, prop_name, **kwargs) - if len(get_enum_items(data, prop_name)) > 10: - # Magick courtesy of https://blender.stackexchange.com/a/203443/86891 - row.context_pointer_set(name="data", data=data) - op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM") - op.prop_name = prop_name + try: + if len(get_enum_items(data, prop_name)) > 10: + # Magick courtesy of https://blender.stackexchange.com/a/203443/86891 + row.context_pointer_set(name="data", data=data) + op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM") + op.prop_name = prop_name + except TypeError: # Prop is not iterable + pass def get_enum_items(data, prop_name, context=None): diff --git a/src/blenderbim/blenderbim/bim/module/constraint/prop.py b/src/blenderbim/blenderbim/bim/module/constraint/prop.py index 003164345e..7f9fe99a13 100644 --- a/src/blenderbim/blenderbim/bim/module/constraint/prop.py +++ b/src/blenderbim/blenderbim/bim/module/constraint/prop.py @@ -17,7 +17,9 @@ # along with BlenderBIM Add-on. If not, see . import bpy -from blenderbim.bim.prop import StrProperty, Attribute, get_ifc_entity_description +from ifcopenshell.util.doc import get_entity_doc +import blenderbim.tool as tool +from blenderbim.bim.prop import Attribute from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -32,7 +34,8 @@ from bpy.props import ( def get_available_constraint_types(self, context): - return [(c, c, get_ifc_entity_description(c)) for c in ["IfcObjective"]] + version = tool.Ifc.get_schema() + return [(c, c, get_entity_doc(version, c).get("description", "")) for c in ["IfcObjective"]] class Constraint(PropertyGroup): diff --git a/src/blenderbim/blenderbim/bim/module/material/data.py b/src/blenderbim/blenderbim/bim/module/material/data.py index 37171d2867..43983aab8b 100644 --- a/src/blenderbim/blenderbim/bim/module/material/data.py +++ b/src/blenderbim/blenderbim/bim/module/material/data.py @@ -21,7 +21,6 @@ import bpy import ifcopenshell import ifcopenshell.util.schema import blenderbim.tool as tool -from blenderbim.bim.prop import get_ifc_entity_description def refresh(): @@ -66,9 +65,12 @@ class MaterialsData: "IfcMaterialProfileSet", "IfcMaterialList", ] - if tool.Ifc.get_schema() == "IFC2X3": + version = tool.Ifc.get_schema() + if version == "IFC2X3": material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"] - return [(m, m, get_ifc_entity_description(m)) for m in material_types] + return [ + (m, m, ifcopenshell.util.doc.get_entity_doc(version, m).get("description", "")) for m in material_types + ] class ObjectMaterialData: diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 2c148012f3..164b68377f 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -617,6 +617,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): continue if attribute.name() in material_set_item_data: new = self.props.material_set_item_attributes.add() + new.ifc_class = material_set_item.is_a() new.name = attribute.name() new.is_null = material_set_item_data[attribute.name()] is None new.data_type = data_type @@ -628,7 +629,8 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): new.int_value = 0 if new.is_null else material_set_item_data[attribute.name()] elif data_type == "boolean": new.bool_value = False if new.is_null else material_set_item_data[attribute.name()] - + blenderbim.bim.helper.add_attribute_description(new) + def load_profile_attributes(self, material_set_item, material_set_item_data): self.props.material_set_item_profile_attributes.clear() @@ -645,6 +647,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): if attribute.name() in profile_data: new = self.props.material_set_item_profile_attributes.add() new.name = attribute.name() + new.ifc_class = profile.is_a() new.is_null = profile_data[attribute.name()] is None new.is_optional = attribute.optional() new.data_type = data_type @@ -661,6 +664,8 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): if profile_data[attribute.name()]: new.enum_value = profile_data[attribute.name()] + blenderbim.bim.helper.add_attribute_description(new) + # Force null to be false if the attribute is mandatory because when we first assign a profile, all of # its fields are null (which is illegal). # TODO: find a better solution. diff --git a/src/blenderbim/blenderbim/bim/module/material/prop.py b/src/blenderbim/blenderbim/bim/module/material/prop.py index cd9e2f4e6f..017ef6ddfa 100644 --- a/src/blenderbim/blenderbim/bim/module/material/prop.py +++ b/src/blenderbim/blenderbim/bim/module/material/prop.py @@ -17,10 +17,12 @@ # along with BlenderBIM Add-on. If not, see . import bpy +from ifcopenshell.util.doc import get_entity_doc from ifcopenshell.api.material.data import Data +import blenderbim.tool as tool from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData from blenderbim.bim.ifc import IfcStore -from blenderbim.bim.prop import StrProperty, Attribute, get_ifc_entity_description +from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -47,28 +49,31 @@ def purge(): parameterizedprofileclasses_enum = [] -def getProfileClasses(self, context): +def get_profile_classes(self, context): global profileclasses_enum if len(profileclasses_enum) == 0 and IfcStore.get_schema(): + version = tool.Ifc.get_schema() profileclasses_enum.clear() profileclasses_enum = [ - (t.name(), t.name(), "") for t in IfcStore.get_schema().declaration_by_name("IfcProfileDef").subtypes() + (t.name(), t.name(), get_entity_doc(version, t.name()).get("description", "")) + for t in IfcStore.get_schema().declaration_by_name("IfcProfileDef").subtypes() ] return profileclasses_enum -def getParameterizedProfileClasses(self, context): +def get_parameterized_profile_classes(self, context): global parameterizedprofileclasses_enum if len(parameterizedprofileclasses_enum) == 0 and IfcStore.get_schema(): + version = tool.Ifc.get_schema() parameterizedprofileclasses_enum.clear() parameterizedprofileclasses_enum = [ - (t.name(), t.name(), "") + (t.name(), t.name(), get_entity_doc(version, t.name()).get("description", "")) for t in IfcStore.get_schema().declaration_by_name("IfcParameterizedProfileDef").subtypes() ] for ifc_class in parameterizedprofileclasses_enum: parameterizedprofileclasses_enum.extend( [ - (t.name(), t.name(), "") + (t.name(), t.name(), get_entity_doc(version, t.name()).get("description", "")) for t in IfcStore.get_schema().declaration_by_name(ifc_class[0]).subtypes() or [] ] ) @@ -93,10 +98,11 @@ def get_object_material_types(self, context): "IfcMaterialProfileSetUsage", "IfcMaterialList", ] - if IfcStore.get_file().schema == "IFC2X3": + version = tool.Ifc.get_schema() + if version == "IFC2X3": material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialLayerSetUsage", "IfcMaterialList"] materialtypes_enum.clear() - materialtypes_enum = [(m, m, get_ifc_entity_description(m)) for m in material_types] + materialtypes_enum = [(m, m, get_entity_doc(version, m).get("description", "")) for m in material_types] return materialtypes_enum @@ -133,7 +139,7 @@ class BIMObjectMaterialProperties(PropertyGroup): name="Material Set Item Profile Attributes", type=Attribute ) material_set_item_material: EnumProperty(items=get_materials, name="Material") - profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes") + profile_classes: EnumProperty(items=get_profile_classes, name="Profile Classes") parameterized_profile_classes: EnumProperty( - items=getParameterizedProfileClasses, name="Parameterized Profile Classes" + items=get_parameterized_profile_classes, name="Parameterized Profile Classes" ) diff --git a/src/blenderbim/blenderbim/bim/module/model/data.py b/src/blenderbim/blenderbim/bim/module/model/data.py index 9aec066036..b9e39ad631 100644 --- a/src/blenderbim/blenderbim/bim/module/model/data.py +++ b/src/blenderbim/blenderbim/bim/module/model/data.py @@ -22,11 +22,10 @@ import json import functools import ifcopenshell import ifcopenshell.util.element +from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.model.root import ConstrTypeEntityNotFound -from blenderbim.bim.prop import get_ifc_entity_description, get_predefined_type_description - def refresh(): AuthoringData.is_loaded = False @@ -64,17 +63,19 @@ class AuthoringData: declarations = ifcopenshell.util.schema.get_subtypes(declaration) names = [d.name() for d in declarations] names.extend(("IfcDoorStyle", "IfcWindowStyle")) - return [(c, c, get_ifc_entity_description(c)) for c in sorted(names)] + version = tool.Ifc.get_schema() + return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)] @classmethod def type_predefined_type(cls): results = [] declaration = tool.Ifc().schema().declaration_by_name(cls.props.type_class) + version = tool.Ifc.get_schema() for attribute in declaration.attributes(): if attribute.name() == "PredefinedType": results.extend( [ - (e, e, get_predefined_type_description(cls.props.type_class, e)) + (e, e, get_predefined_type_doc(version, cls.props.type_class, e)) for e in attribute.type_of_attribute().declared_type().enumeration_items() ] ) diff --git a/src/blenderbim/blenderbim/bim/module/owner/prop.py b/src/blenderbim/blenderbim/bim/module/owner/prop.py index 239c69dbf6..1884a4f719 100644 --- a/src/blenderbim/blenderbim/bim/module/owner/prop.py +++ b/src/blenderbim/blenderbim/bim/module/owner/prop.py @@ -17,7 +17,9 @@ # along with BlenderBIM Add-on. If not, see . import bpy -from blenderbim.bim.prop import StrProperty, Attribute, get_ifc_entity_description +from ifcopenshell.util.doc import get_entity_doc +import blenderbim.tool as tool +from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.module.owner.data import OwnerData, ActorData, ObjectActorData from bpy.types import PropertyGroup from bpy.props import ( @@ -65,17 +67,19 @@ def update_actor_class(self, context): def get_actor_class_enum(self, context): + version = tool.Ifc.get_schema() return [ - ("IfcActor", "Actor", get_ifc_entity_description("IfcActor")), - ("IfcOccupant", "Occupant", get_ifc_entity_description("IfcOccupant")), + ("IfcActor", "Actor", get_entity_doc(version, "IfcActor").get("description", "")), + ("IfcOccupant", "Occupant", get_entity_doc(version, "IfcOccupant").get("description", "")), ] def get_actor_type_enum(self, context): + version = tool.Ifc.get_schema() return [ - ("IfcPerson", "Person", get_ifc_entity_description("IfcPerson")), - ("IfcOrganization", "Organisation", get_ifc_entity_description("IfcOrganization")), - ("IfcPersonAndOrganization", "User", get_ifc_entity_description("IfcPersonAndOrganization")), + ("IfcPerson", "Person", get_entity_doc(version, "IfcPerson").get("description", "")), + ("IfcOrganization", "Organisation", get_entity_doc(version, "IfcOrganization").get("description", "")), + ("IfcPersonAndOrganization", "User", get_entity_doc(version, "IfcPersonAndOrganization").get("description", "")), ] @@ -96,8 +100,14 @@ class BIMOwnerProperties(PropertyGroup): facsimile_numbers: CollectionProperty(type=StrProperty, name="Facsimile Numbers") electronic_mail_addresses: CollectionProperty(type=StrProperty, name="Emails") messaging_ids: CollectionProperty(type=StrProperty, name="IMs") - user_person: EnumProperty(items=get_user_person, name="Person") - user_organisation: EnumProperty(items=get_user_organisation, name="Organisation") + user_person: EnumProperty( + items=get_user_person, name="Person", description="This entity represents an individual human being." + ) + user_organisation: EnumProperty( + items=get_user_organisation, + name="Organisation", + description="A named and structured grouping with a corporate identity.", + ) active_user_id: IntProperty(name="Active User Id") active_actor_id: IntProperty(name="Active Actor Id") actor_attributes: CollectionProperty(name="Actor Attributes", type=Attribute) @@ -111,5 +121,7 @@ class BIMOwnerProperties(PropertyGroup): name="Actor Type", update=update_actor_type, ) - the_actor: EnumProperty(items=get_the_actor, name="Actor") + the_actor: EnumProperty( + items=get_the_actor, name="Actor", description="This entity represents an individual human being." + ) actor: EnumProperty(items=get_actor, name="Actor") diff --git a/src/blenderbim/blenderbim/bim/module/pset/data.py b/src/blenderbim/blenderbim/bim/module/pset/data.py index 78e6350e7a..aa4c81fe6a 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/data.py +++ b/src/blenderbim/blenderbim/bim/module/pset/data.py @@ -173,4 +173,8 @@ class AddEditCustomPropertiesData: @classmethod def primary_measure_type(cls): schema = tool.Ifc.schema() - return [(t, t, "") for t in sorted([d.name() for d in schema.declarations() if hasattr(d, "declared_type")])] + version = tool.Ifc.get_schema() + return [ + (t, t, ifcopenshell.util.doc.get_type_doc(version, t).get("description", "")) + for t in sorted([d.name() for d in schema.declarations() if hasattr(d, "declared_type")]) + ] diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index d3d6044b5c..d7ed392c0f 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -128,6 +128,9 @@ class EnablePsetEditing(bpy.types.Operator): elif metadata.data_type == "boolean": metadata.bool_value = False if metadata.is_null else data[prop_template.Name] + metadata.ifc_class = pset_template.Name + blenderbim.bim.helper.add_attribute_description(metadata) + def get_data_type(self, prop_template): if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]: return "float" diff --git a/src/blenderbim/blenderbim/bim/module/pset/prop.py b/src/blenderbim/blenderbim/bim/module/pset/prop.py index 97d6677629..083ba585a9 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/prop.py +++ b/src/blenderbim/blenderbim/bim/module/pset/prop.py @@ -21,6 +21,7 @@ import blenderbim.bim.schema from blenderbim.bim.prop import Attribute, StrProperty import ifcopenshell from ifcopenshell.api.pset.data import Data +import blenderbim.tool as tool from blenderbim.bim.module.pset.data import AddEditCustomPropertiesData from blenderbim.bim.ifc import IfcStore from bpy.types import PropertyGroup @@ -47,6 +48,15 @@ def purge(): qtonames = {} +def blender_formatted_enum_from_psets(psets): + enum_items = [] + version = tool.Ifc.get_schema() + for pset in psets: + doc = ifcopenshell.util.doc.get_property_set_doc(version, pset.Name) or {} + enum_items.append((pset.Name, pset.Name, doc.get("description", ""))) + return enum_items + + def get_pset_names(self, context): global psetnames obj = context.active_object @@ -58,67 +68,67 @@ def get_pset_names(self, context): ifc_class = element.is_a() if ifc_class not in psetnames: psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) - psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets] + psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) assigned_names = [Data.psets[p]["Name"] for p in Data.products[obj.BIMObjectProperties.ifc_definition_id]["psets"]] return [p for p in psetnames[ifc_class] if p[0] not in assigned_names] -def getMaterialPsetNames(self, context): +def get_material_pset_names(self, context): global psetnames ifc_class = "IfcMaterial" if ifc_class not in psetnames: psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) - psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets] + psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) return psetnames[ifc_class] -def getTaskQtoNames(self, context): +def get_task_qto_names(self, context): global qtonames ifc_class = "IfcTask" if ifc_class not in qtonames: psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True) - qtonames[ifc_class] = [(p.Name, p.Name, "") for p in psets] + qtonames[ifc_class] = blender_formatted_enum_from_psets(psets) return qtonames[ifc_class] -def getResourcePsetNames(self, context): +def get_resource_pset_names(self, context): global psetnames rprops = context.scene.BIMResourceProperties rtprops = context.scene.BIMResourceTreeProperties ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a() if ifc_class not in psetnames: psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) - psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets] + psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) return psetnames[ifc_class] -def getResourceQtoNames(self, context): +def get_resource_qto_names(self, context): global qtonames rprops = context.scene.BIMResourceProperties rtprops = context.scene.BIMResourceTreeProperties ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a() if ifc_class not in qtonames: psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True) - qtonames[ifc_class] = [(p.Name, p.Name, "") for p in psets] + qtonames[ifc_class] = blender_formatted_enum_from_psets(psets) return qtonames[ifc_class] -def getProfilePsetNames(self, context): +def get_profile_pset_names(self, context): global psetnames pprops = context.scene.BIMProfileProperties ifc_class = IfcStore.get_file().by_id(pprops.profiles[pprops.active_profile_index].ifc_definition_id).is_a() if ifc_class not in psetnames: psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) - psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets] + psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) return psetnames[ifc_class] -def getWorkSchedulePsetNames(self, context): +def get_work_schedule_pset_names(self, context): global psetnames ifc_class = "IfcWorkSchedule" if ifc_class not in psetnames: psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True) - psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets] + psetnames[ifc_class] = blender_formatted_enum_from_psets(psets) return psetnames[ifc_class] @@ -128,11 +138,17 @@ def get_qto_names(self, context): ifc_class = context.active_object.name.split("/")[0] if ifc_class not in qtonames: psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True) - qtonames[ifc_class] = [(p.Name, p.Name, "") for p in psets] + qtonames[ifc_class] = blender_formatted_enum_from_psets(psets) return qtonames[ifc_class] return [] +def get_template_type(self, context): + version = tool.Ifc.get_schema() + for t in ("IfcPropertySingleValue", "IfcPropertyEnumeratedValue"): + yield (t, t, ifcopenshell.util.doc.get_entity_doc(version, t).get("description", "")) + + def get_primary_measure_type(self, context): if not AddEditCustomPropertiesData.is_loaded: AddEditCustomPropertiesData.load() @@ -163,36 +179,36 @@ class MaterialPsetProperties(PropertyGroup): active_pset_id: IntProperty(name="Active Pset ID") active_pset_name: StringProperty(name="Pset Name") properties: CollectionProperty(name="Properties", type=IfcProperty) - pset_name: EnumProperty(items=getMaterialPsetNames, name="Pset Name") + pset_name: EnumProperty(items=get_material_pset_names, name="Pset Name") class TaskPsetProperties(PropertyGroup): active_pset_id: IntProperty(name="Active Pset ID") active_pset_name: StringProperty(name="Pset Name") properties: CollectionProperty(name="Properties", type=IfcProperty) - qto_name: EnumProperty(items=getTaskQtoNames, name="Qto Name") + qto_name: EnumProperty(items=get_task_qto_names, name="Qto Name") class ResourcePsetProperties(PropertyGroup): active_pset_id: IntProperty(name="Active Pset ID") active_pset_name: StringProperty(name="Pset Name") properties: CollectionProperty(name="Properties", type=IfcProperty) - pset_name: EnumProperty(items=getResourcePsetNames, name="Pset Name") - qto_name: EnumProperty(items=getResourceQtoNames, name="Qto Name") + pset_name: EnumProperty(items=get_resource_pset_names, name="Pset Name") + qto_name: EnumProperty(items=get_resource_qto_names, name="Qto Name") class ProfilePsetProperties(PropertyGroup): active_pset_id: IntProperty(name="Active Pset ID") active_pset_name: StringProperty(name="Pset Name") properties: CollectionProperty(name="Properties", type=IfcProperty) - pset_name: EnumProperty(items=getProfilePsetNames, name="Pset Name") + pset_name: EnumProperty(items=get_profile_pset_names, name="Pset Name") class WorkSchedulePsetProperties(PropertyGroup): active_pset_id: IntProperty(name="Active Pset ID") active_pset_name: StringProperty(name="Pset Name") properties: CollectionProperty(name="Properties", type=IfcProperty) - pset_name: EnumProperty(items=getWorkSchedulePsetNames, name="Pset Name") + pset_name: EnumProperty(items=get_work_schedule_pset_names, name="Pset Name") class RenameProperties(PropertyGroup): @@ -209,13 +225,7 @@ class AddEditProperties(PropertyGroup): int_value: IntProperty(name="Value") float_value: FloatProperty(name="Value") primary_measure_type: EnumProperty(items=get_primary_measure_type, name="Primary Measure Type") - template_type: EnumProperty( - items=[ - ("IfcPropertySingleValue", "IfcPropertySingleValue", "IfcPropertySingleValue"), - ("IfcPropertyEnumeratedValue", "IfcPropertyEnumeratedValue", "IfcPropertyEnumeratedValue"), - ], - name="Template Type", - ) + template_type: EnumProperty(items=get_template_type, name="Template Type") enum_values: CollectionProperty(name="Enum Values", type=Attribute) def get_value_name(self): diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/data.py b/src/blenderbim/blenderbim/bim/module/pset_template/data.py index 70cdf93dee..8af3a19bbb 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/data.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/data.py @@ -43,7 +43,11 @@ class PsetTemplatesData: @classmethod def primary_measure_type(cls): schema = tool.Ifc.schema() - return [(t, t, "") for t in sorted([d.name() for d in schema.declarations() if hasattr(d, "declared_type")])] + version = tool.Ifc.get_schema() + return [ + (t, t, ifcopenshell.util.doc.get_type_doc(version, t).get("description", "")) + for t in sorted([d.name() for d in schema.declarations() if hasattr(d, "declared_type")]) + ] @classmethod def pset_template_files(cls): diff --git a/src/blenderbim/blenderbim/bim/module/pset_template/prop.py b/src/blenderbim/blenderbim/bim/module/pset_template/prop.py index 23fef7863c..d2821ca01a 100644 --- a/src/blenderbim/blenderbim/bim/module/pset_template/prop.py +++ b/src/blenderbim/blenderbim/bim/module/pset_template/prop.py @@ -19,6 +19,7 @@ import os import bpy import ifcopenshell +from ifcopenshell.util.doc import get_attribute_doc from blenderbim.bim.module.pset_template.data import PsetTemplatesData from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.ifc import IfcStore @@ -111,11 +112,27 @@ def get_template_type(self, context): class PsetTemplate(PropertyGroup): - global_id: StringProperty(name="Global ID") - name: StringProperty(name="Name") - description: StringProperty(name="Description") - template_type: EnumProperty(items=get_template_type, name="Template Type") - applicable_entity: StringProperty(name="Applicable Entity") + global_id: StringProperty( + name="Global ID", + description=get_attribute_doc("IFC4", "IfcPropertySetTemplate", "GlobalId"), + ) + name: StringProperty( + name="Name", + description=get_attribute_doc("IFC4", "IfcPropertySetTemplate", "Name"), + ) + description: StringProperty( + name="Description", + description=get_attribute_doc("IFC4", "IfcPropertySetTemplate", "Description"), + ) + template_type: EnumProperty( + items=get_template_type, + name="Template Type", + description=get_attribute_doc("IFC4", "IfcPropertySetTemplate", "TemplateType"), + ) + applicable_entity: StringProperty( + name="Applicable Entity", + description=get_attribute_doc("IFC4", "IfcPropertySetTemplate", "ApplicableEntity"), + ) class EnumerationValues(PropertyGroup): @@ -126,9 +143,18 @@ class EnumerationValues(PropertyGroup): class PropTemplate(PropertyGroup): - global_id: StringProperty(name="Global ID") - name: StringProperty(name="Name") - description: StringProperty(name="Description") + global_id: StringProperty( + name="Global ID", + description=get_attribute_doc("IFC4", "IfcPropertyTemplate", "GlobalId"), + ) + name: StringProperty( + name="Name", + description=get_attribute_doc("IFC4", "IfcPropertyTemplate", "Name"), + ) + description: StringProperty( + name="Description", + description=get_attribute_doc("IFC4", "IfcPropertyTemplate", "Description"), + ) primary_measure_type: EnumProperty(items=get_primary_measure_type, name="Primary Measure Type") template_type: EnumProperty( items=[("P_SINGLEVALUE", "P_SINGLEVALUE", ""), ("P_ENUMERATEDVALUE", "P_ENUMERATEDVALUE", "")], diff --git a/src/blenderbim/blenderbim/bim/module/root/data.py b/src/blenderbim/blenderbim/bim/module/root/data.py index c91da4dfa6..b2cdccf9b5 100644 --- a/src/blenderbim/blenderbim/bim/module/root/data.py +++ b/src/blenderbim/blenderbim/bim/module/root/data.py @@ -19,9 +19,9 @@ from collections import defaultdict import bpy import ifcopenshell.util.element +from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore -from blenderbim.bim.prop import get_ifc_entity_description, get_predefined_type_description def refresh(): @@ -57,7 +57,8 @@ class IfcClassData: "IfcAnnotation", "IfcRelSpaceBoundary", ] - if tool.Ifc.get_schema() == "IFC2X3": + version = tool.Ifc.get_schema() + if version == "IFC2X3": products = [ "IfcElement", "IfcElementType", @@ -67,7 +68,7 @@ class IfcClassData: "IfcAnnotation", "IfcRelSpaceBoundary", ] - return [(e, e, get_ifc_entity_description(e)) for e in products] + return [(e, e, get_entity_doc(version, e).get("description", "")) for e in products] @classmethod def ifc_classes(cls): @@ -77,18 +78,20 @@ class IfcClassData: names = [d.name() for d in declarations] if ifc_product == "IfcElementType": names.extend(("IfcDoorStyle", "IfcWindowStyle")) - return [(c, c, get_ifc_entity_description(c)) for c in sorted(names)] + version = tool.Ifc.get_schema() + return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)] @classmethod def ifc_predefined_types(cls): types_enum = [] ifc_class = bpy.context.scene.BIMRootProperties.ifc_class declaration = tool.Ifc.schema().declaration_by_name(ifc_class) + version = tool.Ifc.get_schema() for attribute in declaration.attributes(): if attribute.name() == "PredefinedType": types_enum.extend( [ - (e, e, get_predefined_type_description(ifc_class, e)) + (e, e, get_predefined_type_doc(version, ifc_class, e)) for e in attribute.type_of_attribute().declared_type().enumeration_items() ] ) diff --git a/src/blenderbim/blenderbim/bim/module/structural/prop.py b/src/blenderbim/blenderbim/bim/module/structural/prop.py index 3397157687..ac1a6a0f6d 100644 --- a/src/blenderbim/blenderbim/bim/module/structural/prop.py +++ b/src/blenderbim/blenderbim/bim/module/structural/prop.py @@ -16,11 +16,13 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . -import bpy -from blenderbim.bim.ifc import IfcStore from math import radians -from blenderbim.bim.prop import StrProperty, Attribute, get_ifc_entity_description +import bpy from ifcopenshell.api.structural.data import Data +from ifcopenshell.util.doc import get_entity_doc +import blenderbim.tool as tool +from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.prop import StrProperty, Attribute from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -89,8 +91,12 @@ def get_structural_load_types(self, context): file = IfcStore.get_file() if len(structuralloadtypes_enum) < 1 and file: declaration = IfcStore.get_schema().declaration_by_name("IfcStructuralLoadStatic") + version = tool.Ifc.get_schema() structuralloadtypes_enum.extend( - [(d.name(), d.name(), get_ifc_entity_description(d.name())) for d in declaration.subtypes()] + [ + (d.name(), d.name(), get_entity_doc(version, d.name()).get("description", "")) + for d in declaration.subtypes() + ] ) return structuralloadtypes_enum @@ -99,8 +105,10 @@ def get_boundary_condition_types(self, context): file = IfcStore.get_file() if file: declaration = IfcStore.get_schema().declaration_by_name("IfcBoundaryCondition") + version = tool.Ifc.get_schema() boundaryconditiontypes_enum = [ - (d.name(), d.name(), get_ifc_entity_description(d.name())) for d in declaration.subtypes() + (d.name(), d.name(), get_entity_doc(version, d.name()).get("description", "")) + for d in declaration.subtypes() ] return boundaryconditiontypes_enum return [] diff --git a/src/blenderbim/blenderbim/bim/module/style/data.py b/src/blenderbim/blenderbim/bim/module/style/data.py index df9da618a1..7801122228 100644 --- a/src/blenderbim/blenderbim/bim/module/style/data.py +++ b/src/blenderbim/blenderbim/bim/module/style/data.py @@ -17,9 +17,9 @@ # along with BlenderBIM Add-on. If not, see . import bpy -import blenderbim.tool as tool import ifcopenshell -from blenderbim.bim.prop import get_ifc_entity_description +from ifcopenshell.util.doc import get_entity_doc +import blenderbim.tool as tool def refresh(): @@ -39,7 +39,10 @@ class StylesData: def style_types(cls): declaration = tool.Ifc.schema().declaration_by_name("IfcPresentationStyle") declarations = ifcopenshell.util.schema.get_subtypes(declaration) - return [(c, c, get_ifc_entity_description(c)) for c in sorted([d.name() for d in declarations])] + version = tool.Ifc.get_schema() + return [ + (c, c, get_entity_doc(version, c).get("description", "")) for c in sorted([d.name() for d in declarations]) + ] @classmethod def total_styles(cls): diff --git a/src/blenderbim/blenderbim/bim/module/system/data.py b/src/blenderbim/blenderbim/bim/module/system/data.py index 244972f75d..22060e9c42 100644 --- a/src/blenderbim/blenderbim/bim/module/system/data.py +++ b/src/blenderbim/blenderbim/bim/module/system/data.py @@ -19,8 +19,8 @@ import bpy import ifcopenshell import ifcopenshell.util.schema +from ifcopenshell.util.doc import get_entity_doc import blenderbim.tool as tool -from blenderbim.bim.prop import get_ifc_entity_description def refresh(): @@ -45,9 +45,10 @@ class SystemData: def system_class(cls): declaration = tool.Ifc.schema().declaration_by_name("IfcSystem") declarations = ifcopenshell.util.schema.get_subtypes(declaration) + version = tool.Ifc.get_schema() # We're only interested in systems for services. Not sure why IFC groups these together. return [ - (c, c, get_ifc_entity_description(c)) + (c, c, get_entity_doc(version, c).get("description", "")) for c in sorted([d.name() for d in declarations]) if c not in ("IfcZone", "IfcStructuralAnalysisModel") ] diff --git a/src/blenderbim/blenderbim/bim/module/type/data.py b/src/blenderbim/blenderbim/bim/module/type/data.py index 0a5470479f..1d23c3363f 100644 --- a/src/blenderbim/blenderbim/bim/module/type/data.py +++ b/src/blenderbim/blenderbim/bim/module/type/data.py @@ -19,8 +19,8 @@ import bpy import ifcopenshell.util.type import ifcopenshell.util.element +from ifcopenshell.util.doc import get_entity_doc import blenderbim.tool as tool -from blenderbim.bim.prop import get_ifc_entity_description def refresh(): @@ -54,8 +54,9 @@ class TypeData: element = tool.Ifc.get_entity(obj) if not element: return [] - types = ifcopenshell.util.type.get_applicable_types(element.is_a(), schema=tool.Ifc.get_schema()) - results.extend((t, t, get_ifc_entity_description(t)) for t in types) + version = tool.Ifc.get_schema() + types = ifcopenshell.util.type.get_applicable_types(element.is_a(), schema=version) + results.extend((t, t, get_entity_doc(version, t).get("description", "")) for t in types) return results @classmethod @@ -87,7 +88,4 @@ class TypeData: element = tool.Ifc.get_entity(bpy.context.active_object) element_type = ifcopenshell.util.element.get_type(element) if element_type: - return { - "id": element_type.id(), - "name":f"{element_type.is_a()}/{element_type.Name or 'Unnamed'}" - } + return {"id": element_type.id(), "name": f"{element_type.is_a()}/{element_type.Name or 'Unnamed'}"} diff --git a/src/blenderbim/blenderbim/bim/module/unit/data.py b/src/blenderbim/blenderbim/bim/module/unit/data.py index 7b75b59eac..95383dbdf9 100644 --- a/src/blenderbim/blenderbim/bim/module/unit/data.py +++ b/src/blenderbim/blenderbim/bim/module/unit/data.py @@ -20,8 +20,8 @@ import ifcopenshell import ifcopenshell.util.unit import ifcopenshell.util.schema import ifcopenshell.util.attribute +from ifcopenshell.util.doc import get_entity_doc import blenderbim.tool as tool -from blenderbim.bim.prop import get_ifc_entity_description def refresh(): @@ -45,8 +45,20 @@ class UnitsData: @classmethod def unit_classes(cls): declarations = ifcopenshell.util.schema.get_subtypes(tool.Ifc.schema().declaration_by_name("IfcNamedUnit")) - results = [(c, c, get_ifc_entity_description(c)) for c in sorted([d.name() for d in declarations])] - results.extend([("IfcDerivedUnit", "IfcDerivedUnit", ""), ("IfcMonetaryUnit", "IfcMonetaryUnit", "")]) + version = tool.Ifc.get_schema() + results = [ + (c, c, get_entity_doc(version, c).get("description", "")) for c in sorted([d.name() for d in declarations]) + ] + results.extend( + [ + ("IfcDerivedUnit", "IfcDerivedUnit", get_entity_doc(version, "IfcDerivedUnit").get("description", "")), + ( + "IfcMonetaryUnit", + "IfcMonetaryUnit", + get_entity_doc(version, "IfcMonetaryUnit").get("description", ""), + ), + ] + ) return results @classmethod diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 5097257704..e34a0a9c05 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -19,6 +19,7 @@ import os import bpy import json +import textwrap import time import logging import webbrowser @@ -683,3 +684,31 @@ class EditBlenderCollection(bpy.types.Operator): else: getattr(context.bim_prop_group, self.collection).remove(self.index) return {"FINISHED"} + + +class BIM_OT_show_description(bpy.types.Operator): + bl_idname = "bim.show_description" + bl_label = "Description" + attr_name: bpy.props.StringProperty() + description: bpy.props.StringProperty() + url: bpy.props.StringProperty() + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self, width=450) + + def execute(self, context): + return {"FINISHED"} + + def draw(self, context): + layout = self.layout + wrapper = textwrap.TextWrapper(width=80) + for line in wrapper.wrap(self.attr_name + " : " + self.description): + layout.label(text=line) + if self.url: + url_op = layout.operator("bim.open_webbrowser", icon="URL", text="Online IFC Documentation") + url_op.url = self.url + + @classmethod + def description(cls, context, properties): + return properties.description diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index f94b79f62e..b75059332b 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -106,40 +106,6 @@ def cache_string(s): cache_string.data = {} -def get_ifc_entity_docs(ifc_entity): - schema = tool.Ifc.get_schema() - if schema is not None: - return get_entity_doc(schema, ifc_entity) - - -def get_ifc_entity_description(ifc_entity): - docs = get_ifc_entity_docs(ifc_entity) - return docs.get("description", "") if docs is not None else "" - - -def get_ifc_entity_doc_url(ifc_entity): - docs = get_ifc_entity_docs(ifc_entity) - return docs.get("spec_url", "") if docs is not None else "" - - -def get_predefined_type_description(entity, predefined_type): - schema = tool.Ifc.get_schema() - if schema is not None: - return get_predefined_type_doc(schema, entity, predefined_type) - - -def get_attribute_description(entity, attribute): - schema = tool.Ifc.get_schema() - if schema is not None: - return get_attribute_doc(schema, entity, attribute) - - -def get_property_set_description(pset): - schema = tool.Ifc.get_schema() - if schema is not None: - return get_property_set_doc(schema, pset) - - def get_attribute_enum_values(prop, context): # Support weird buildingSMART dictionary mappings which behave like enums items = [] @@ -164,6 +130,9 @@ def get_attribute_enum_values(prop, context): ) ) + if prop.enum_descriptions: + items = [(identifier, name, prop.enum_descriptions[i].name) for i, (identifier, name, _) in enumerate(items)] + return items @@ -227,13 +196,17 @@ def update_attribute_value(self, context): class Attribute(PropertyGroup): + tooltip = "`Right Click > IFC Description` to read the attribute description and online documentation" name: StringProperty(name="Name") + description: StringProperty(name="Description") + ifc_class: StringProperty(name="Ifc Class") data_type: StringProperty(name="Data Type") - string_value: StringProperty(name="Value", update=update_attribute_value) - bool_value: BoolProperty(name="Value", update=update_attribute_value) - int_value: IntProperty(name="Value", update=update_attribute_value) - float_value: FloatProperty(name="Value", update=update_attribute_value) + string_value: StringProperty(name="Value", update=update_attribute_value, description=tooltip) + bool_value: BoolProperty(name="Value", update=update_attribute_value, description=tooltip) + int_value: IntProperty(name="Value", update=update_attribute_value, description=tooltip) + float_value: FloatProperty(name="Value", update=update_attribute_value, description=tooltip) enum_items: StringProperty(name="Value") + enum_descriptions: CollectionProperty(type=StrProperty) enum_value: EnumProperty(items=get_attribute_enum_values, name="Value", update=update_attribute_value) is_null: BoolProperty(name="Is Null") is_optional: BoolProperty(name="Is Optional") diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index adbc4a8e4c..c40cb18b9a 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -17,14 +17,20 @@ # along with BlenderBIM Add-on. If not, see . import os -import bpy from pathlib import Path -from . import ifc +import bpy from bpy.types import Panel from bpy.props import StringProperty, IntProperty, BoolProperty -from blenderbim.bim.helper import IfcHeaderExtractor -from blenderbim.bim.prop import get_ifc_entity_doc_url +from ifcopenshell.util.doc import ( + get_entity_doc, + get_property_set_doc, + get_type_doc, + get_attribute_doc, +) +from . import ifc import blenderbim.tool as tool +from blenderbim.bim.helper import IfcHeaderExtractor +from blenderbim.bim.prop import Attribute class IFCFileSelector: @@ -397,14 +403,44 @@ def draw_custom_context_menu(self, context): prop_value = getattr(prop, prop_name, None) if prop_value is None: return - try: - url = get_ifc_entity_doc_url(prop_value) - except KeyError: - # TODO : support attributes, pset, etc. - pass - else: - if url: - layout = self.layout + version = tool.Ifc.get_schema() + layout = self.layout + + if isinstance(context.button_pointer, Attribute): + description = getattr(context.button_pointer, "description", None) + ifc_class = getattr(context.button_pointer, "ifc_class", "") + if ifc_class: + try: + url = get_entity_doc(version, context.button_pointer.ifc_class).get("spec_url", "") + except RuntimeError: # It's not an Entity Attribute. Let's try a Property Set attribute. + url = get_property_set_doc(version, context.button_pointer.ifc_class).get("spec_url", "") + if description: layout.separator() - url_op = layout.operator("bim.open_webbrowser", icon="URL", text="Online IFC Documentation") - url_op.url = url + op_description = layout.operator("bim.show_description", text="IFC Description", icon="INFO") + op_description.attr_name = getattr(context.button_pointer, "name", "") + op_description.description = description + op_description.url = url + else: + # Ugly but we can't know which type of data is under the cursor so we test everything until it clicks + try: + docs = get_entity_doc(version, prop_value) + if docs is None: + raise RuntimeError + except (RuntimeError, AttributeError): + try: + docs = get_type_doc(version, prop_value) + if docs is None: + raise RuntimeError + except (RuntimeError, AttributeError): + try: + docs = get_property_set_doc(version, prop_value) + if docs is None: + raise RuntimeError + except (RuntimeError, AttributeError): + pass + if docs: + url = docs.get("spec_url", "") + if url: + layout.separator() + url_op = layout.operator("bim.open_webbrowser", icon="URL", text="Online IFC Documentation") + url_op.url = url