Dynamic Attribute Info (#1990)

* Add info mode toggle in addon prefs

* Add documentation property to Attribute

* Add webbrowser open operator

* Populate attribute doc on import

* Add attribute doc popup operator

* use path_resolve instead of eval

* Rename operator

* Add description field

* Panel UI is managed by operator

* Revert "Panel UI is managed by operator"

This reverts commit a7bbd06865.

* Revert "Revert "Panel UI is managed by operator""

This reverts commit d62115bf63.

* Add info popup to ifc class panel in object properties

* Implement fetching doc for non-ifc classes from jsonlike format

* Draw doc from non ifc classes

* Add utility function to draw info button on a given layout

* Add info buttons in the create project UI

* Display entity descriptions as tooltips in enum items

* Move predefined types fetching to data class

* Remove leftover prop code

* Use tool to get schema
This commit is contained in:
Gorgious56
2022-10-18 23:17:31 +02:00
committed by GitHub
parent b70bf5ed33
commit ec1fbabd00
8 changed files with 94 additions and 30 deletions
@@ -94,6 +94,7 @@ classes = [
operator.SelectSchemaDir, operator.SelectSchemaDir,
operator.SelectURIAttribute, operator.SelectURIAttribute,
operator.EditBlenderCollection, operator.EditBlenderCollection,
operator.BIM_OT_open_webbrowser,
prop.StrProperty, prop.StrProperty,
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
prop.ObjProperty, prop.ObjProperty,
+21
View File
@@ -23,9 +23,11 @@ import math
import zipfile import zipfile
import ifcopenshell import ifcopenshell
import ifcopenshell.util.attribute import ifcopenshell.util.attribute
from ifcopenshell.util.doc import get_entity_doc, get_attribute_doc, get_property_set_doc, get_property_doc
from mathutils import geometry from mathutils import geometry
from mathutils import Vector from mathutils import Vector
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
def draw_attributes(props, layout, copy_operator=None): def draw_attributes(props, layout, copy_operator=None):
@@ -124,6 +126,25 @@ def prop_with_search(layout, data, prop_name, **kwargs):
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM") op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
op.prop_name = prop_name op.prop_name = prop_name
if bpy.context.preferences.addons["blenderbim"].preferences.info_mode:
schema = tool.Ifc.schema()
if schema is not None:
schema = str(schema)
schema = next(identifier for identifier in IfcStore.schema_identifiers if identifier in schema)
docs = {}
entity = getattr(data, prop_name)
if entity:
try:
docs = get_entity_doc(schema, entity)
except KeyError:
# TODO : support attributes, pset, etc.
pass
op_row = row.row(align=True)
url = docs.get("spec_url", "")
url_op = op_row.operator("bim.open_webbrowser", icon="URL", text="")
url_op.url = url
op_row.enabled = bool(url)
def get_enum_items(data, prop_name, context): def get_enum_items(data, prop_name, context):
# Retrieve items from a dynamic EnumProperty, which is otherwise not supported # Retrieve items from a dynamic EnumProperty, which is otherwise not supported
@@ -21,6 +21,7 @@ import bpy
import ifcopenshell.util.element import ifcopenshell.util.element
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import get_ifc_entity_description, get_predefined_type_descriptions
def refresh(): def refresh():
@@ -42,6 +43,7 @@ class IfcClassData:
cls.data["has_entity"] = cls.has_entity() cls.data["has_entity"] = cls.has_entity()
cls.data["name"] = cls.name() cls.data["name"] = cls.name()
cls.data["ifc_class"] = cls.ifc_class() cls.data["ifc_class"] = cls.ifc_class()
cls.data["ifc_predefined_types"] = cls.ifc_predefined_types()
@classmethod @classmethod
def ifc_products(cls): def ifc_products(cls):
@@ -65,7 +67,7 @@ class IfcClassData:
"IfcAnnotation", "IfcAnnotation",
"IfcRelSpaceBoundary", "IfcRelSpaceBoundary",
] ]
return [(e, e, "") for e in products] return [(e, e, get_ifc_entity_description(e)) for e in products]
@classmethod @classmethod
def ifc_classes(cls): def ifc_classes(cls):
@@ -75,7 +77,21 @@ class IfcClassData:
names = [d.name() for d in declarations] names = [d.name() for d in declarations]
if ifc_product == "IfcElementType": if ifc_product == "IfcElementType":
names.extend(("IfcDoorStyle", "IfcWindowStyle")) names.extend(("IfcDoorStyle", "IfcWindowStyle"))
return [(c, c, "") for c in sorted(names)]
return [(c, c, get_ifc_entity_description(c)) 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)
for attribute in declaration.attributes():
if attribute.name() == "PredefinedType":
declared_type = attribute.type_of_attribute().declared_type()
descriptions = get_predefined_type_descriptions(declared_type.name())
types_enum.extend([(e, e, descriptions.get(e, "")) for e in declared_type.enumeration_items()])
break
return types_enum
@classmethod @classmethod
def ifc_classes_suggestions(cls): def ifc_classes_suggestions(cls):
@@ -20,7 +20,6 @@ import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.util.schema import ifcopenshell.util.schema
from blenderbim.bim.module.root.data import IfcClassData from blenderbim.bim.module.root.data import IfcClassData
from blenderbim.bim.ifc import IfcStore
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -33,29 +32,15 @@ from bpy.props import (
CollectionProperty, CollectionProperty,
) )
types_enum = []
classes_enum = []
def purge(): def purge():
global types_enum pass
types_enum = []
global classes_enum
classes_enum = []
def getIfcPredefinedTypes(self, context): def get_ifc_predefined_types(self, context):
global types_enum if not IfcClassData.is_loaded:
file = IfcStore.get_file() IfcClassData.load()
if len(types_enum) < 1 and file: return IfcClassData.data["ifc_predefined_types"]
declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class)
for attribute in declaration.attributes():
if attribute.name() == "PredefinedType":
types_enum.extend(
[(e, e, "") for e in attribute.type_of_attribute().declared_type().enumeration_items()]
)
break
return types_enum
def refresh_classes(self, context): def refresh_classes(self, context):
@@ -64,10 +49,9 @@ def refresh_classes(self, context):
context.scene.BIMRootProperties.ifc_class = enum[0][0] context.scene.BIMRootProperties.ifc_class = enum[0][0]
def refreshPredefinedTypes(self, context): def refresh_predefined_types(self, context):
global types_enum IfcClassData.load()
types_enum.clear() enum = get_ifc_predefined_types(self, context)
enum = getIfcPredefinedTypes(self, context)
if enum: if enum:
context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0] context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0]
@@ -103,8 +87,8 @@ def get_contexts(self, context):
class BIMRootProperties(PropertyGroup): class BIMRootProperties(PropertyGroup):
contexts: EnumProperty(items=get_contexts, name="Contexts") contexts: EnumProperty(items=get_contexts, name="Contexts")
ifc_product: EnumProperty(items=get_ifc_products, name="Products", update=refresh_classes) ifc_product: EnumProperty(items=get_ifc_products, name="Products", update=refresh_classes)
ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refreshPredefinedTypes) ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refresh_predefined_types)
ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None) ifc_predefined_type: EnumProperty(items=get_ifc_predefined_types, name="Predefined Type", default=None)
ifc_userdefined_type: StringProperty(name="Userdefined Type") ifc_userdefined_type: StringProperty(name="Userdefined Type")
getter_enum_suggestions = { getter_enum_suggestions = {
@@ -54,7 +54,7 @@ class BIM_PT_class(Panel):
row.operator("bim.disable_reassign_class", icon="CANCEL", text="") row.operator("bim.disable_reassign_class", icon="CANCEL", text="")
self.draw_class_dropdowns( self.draw_class_dropdowns(
context, context,
root_prop.getIfcPredefinedTypes(context.scene.BIMRootProperties, context), root_prop.get_ifc_predefined_types(context.scene.BIMRootProperties, context),
is_reassigning_class=True, is_reassigning_class=True,
) )
else: else:
@@ -67,7 +67,7 @@ class BIM_PT_class(Panel):
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcRoot"): if IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcRoot"):
row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="") row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="")
else: else:
ifc_predefined_types = root_prop.getIfcPredefinedTypes(context.scene.BIMRootProperties, context) ifc_predefined_types = root_prop.get_ifc_predefined_types(context.scene.BIMRootProperties, context)
self.draw_class_dropdowns(context, ifc_predefined_types) self.draw_class_dropdowns(context, ifc_predefined_types)
row = self.layout.row(align=True) row = self.layout.row(align=True)
op = row.operator("bim.assign_class") op = row.operator("bim.assign_class")
+14
View File
@@ -429,6 +429,20 @@ class RemoveIfcFile(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class BIM_OT_open_webbrowser(bpy.types.Operator):
bl_idname = "bim.open_webbrowser"
bl_description = "Open the URL in your Web Browser"
bl_label = "Open URL"
url: bpy.props.StringProperty()
def execute(self, context):
import webbrowser
webbrowser.open(self.url)
return {"FINISHED"}
class SelectExternalMaterialDir(bpy.types.Operator): class SelectExternalMaterialDir(bpy.types.Operator):
bl_idname = "bim.select_external_material_dir" bl_idname = "bim.select_external_material_dir"
bl_label = "Select Material File" bl_label = "Select Material File"
+24
View File
@@ -16,15 +16,18 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from pathlib import Path
import os import os
import bpy import bpy
import json import json
import importlib import importlib
import ifcopenshell import ifcopenshell
import ifcopenshell.util.pset import ifcopenshell.util.pset
from ifcopenshell.util.doc import get_entity_doc, get_attribute_doc, get_property_set_doc, get_property_doc
import blenderbim.bim.handler import blenderbim.bim.handler
import blenderbim.bim.schema import blenderbim.bim.schema
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
import blenderbim.tool as tool
from collections import defaultdict from collections import defaultdict
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
@@ -40,6 +43,11 @@ from bpy.props import (
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
BASE_MODULE_PATH = Path(__file__).parent
DESCRIPTION_FILES = {
"PredefinedType": BASE_MODULE_PATH / "schema" / "enum_descriptions.json",
}
materialpsetnames_enum = [] materialpsetnames_enum = []
@@ -96,6 +104,22 @@ def cache_string(s):
cache_string.data = {} cache_string.data = {}
def get_ifc_entity_description(ifc_entity):
schema = tool.Ifc.get_schema()
if schema is not None:
schema = str(schema)
schema = next(identifier for identifier in IfcStore.schema_identifiers if identifier in schema)
docs = get_entity_doc(schema, ifc_entity)
description = docs.get("description", "")
return description
return ""
def get_predefined_type_descriptions(ifc_class_enum):
with open(DESCRIPTION_FILES["PredefinedType"], "r") as fi:
docs = json.load(fi)
return docs.get(ifc_class_enum, None) or {}
def getAttributeEnumValues(prop, context): def getAttributeEnumValues(prop, context):
# Support weird buildingSMART dictionary mappings which behave like enums # Support weird buildingSMART dictionary mappings which behave like enums
+4
View File
@@ -115,6 +115,9 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False name="Should Make A Cha-Ching Sound When Project Costs Updates", default=False
) )
lock_grids_on_import: BoolProperty(name="Will lock grids upon import", default=True) lock_grids_on_import: BoolProperty(name="Will lock grids upon import", default=True)
info_mode: BoolProperty(
default=True, name="Info Mode", description="Display additional helpful tooltips and information"
)
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
@@ -174,6 +177,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row = layout.row() row = layout.row()
row.operator("bim.configure_visibility") row.operator("bim.configure_visibility")
layout.row().prop(self, "info_mode")
def ifc_units(self, context): def ifc_units(self, context):