mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 23:36:20 +00:00
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 commita7bbd06865. * Revert "Revert "Panel UI is managed by operator"" This reverts commitd62115bf63. * 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:
@@ -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,
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user