From 58ee049a8f484ae1d88b94c438b7dfb5e54d95f3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 25 Oct 2021 16:21:40 +1100 Subject: [PATCH] New feature to show inherited element type psets. --- .../blenderbim/bim/module/pset/data.py | 157 ++++++++++++++++++ .../blenderbim/bim/module/pset/operator.py | 13 +- .../blenderbim/bim/module/pset/ui.py | 135 ++++++--------- 3 files changed, 219 insertions(+), 86 deletions(-) create mode 100644 src/blenderbim/blenderbim/bim/module/pset/data.py diff --git a/src/blenderbim/blenderbim/bim/module/pset/data.py b/src/blenderbim/blenderbim/bim/module/pset/data.py new file mode 100644 index 0000000000..bf190fd9c1 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/pset/data.py @@ -0,0 +1,157 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BlenderBIM Add-on is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BlenderBIM Add-on. If not, see . + +import bpy +import ifcopenshell +import blenderbim.tool as tool + + +# TODO: Should this cache belong here? Dunno. Maybe. +is_expanded = {} + + +def refresh(): + ObjectPsetsData.is_loaded = False + ObjectQtosData.is_loaded = False + MaterialPsetsData.is_loaded = False + TaskQtosData.is_loaded = False + ResourceQtosData.is_loaded = False + ResourcePsetsData.is_loaded = False + ProfilePsetsData.is_loaded = False + WorkSchedulePsetsData.is_loaded = False + + +class Data: + @classmethod + def psetqtos(cls, element, psets_only=False, qtos_only=False): + results = [] + psetqtos = ifcopenshell.util.element.get_psets(element, psets_only=psets_only, qtos_only=qtos_only) + for name, data in psetqtos.items(): + results.append( + { + "id": data["id"], + "Name": name, + "is_expanded": is_expanded.get(data["id"], True), + "Properties": [{"Name": k, "NominalValue": v} for k, v in data.items() if k != "id"], + } + ) + return sorted(results, key=lambda v: v["Name"]) + + +class ObjectPsetsData(Data): + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = { + "psets": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), psets_only=True), + "inherited_psets": cls.inherited_psets(), + } + cls.is_loaded = True + + @classmethod + def inherited_psets(cls): + element = tool.Ifc.get_entity(bpy.context.active_object) + if element.is_a("IfcTypeObject"): + return + element_type = ifcopenshell.util.element.get_type(element) + return cls.psetqtos(element_type) + + +class ObjectQtosData(Data): + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = {"qtos": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object), qtos_only=True)} + cls.is_loaded = True + + +class MaterialPsetsData(Data): + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = {"psets": cls.psetqtos(tool.Ifc.get_entity(bpy.context.active_object.active_material))} + cls.is_loaded = True + + +class TaskQtosData(Data): + data = {} + is_loaded = False + + @classmethod + def load(cls): + wprops = bpy.context.scene.BIMWorkScheduleProperties + tprops = bpy.context.scene.BIMTaskTreeProperties + ifc_definition_id = tprops.tasks[wprops.active_task_index].ifc_definition_id + cls.data = {"qtos": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), qtos_only=True)} + cls.is_loaded = True + + +class ResourceQtosData(Data): + data = {} + is_loaded = False + + @classmethod + def load(cls): + rprops = bpy.context.scene.BIMResourceProperties + rtprops = bpy.context.scene.BIMResourceTreeProperties + ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id + cls.data = {"qtos": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), qtos_only=True)} + cls.is_loaded = True + + +class ResourcePsetsData(Data): + data = {} + is_loaded = False + + @classmethod + def load(cls): + rprops = bpy.context.scene.BIMResourceProperties + rtprops = bpy.context.scene.BIMResourceTreeProperties + ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id + cls.data = {"psets": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), psets_only=True)} + cls.is_loaded = True + + +class ProfilePsetsData(Data): + data = {} + is_loaded = False + + @classmethod + def load(cls): + pprops = bpy.context.scene.BIMProfileProperties + ifc_definition_id = pprops.profiles[pprops.active_profile_index].ifc_definition_id + cls.data = {"psets": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), psets_only=True)} + cls.is_loaded = True + + +class WorkSchedulePsetsData(Data): + data = {} + is_loaded = False + + @classmethod + def load(cls): + props = bpy.context.scene.WorkSchedulePsetProperties + ifc_definition_id = bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id + cls.data = {"psets": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), psets_only=True)} + cls.is_loaded = True diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index f30f0e77fe..de603fa683 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -23,8 +23,10 @@ import ifcopenshell.util.unit import ifcopenshell.util.pset import ifcopenshell.util.attribute import blenderbim.bim.schema +import blenderbim.bim.handler import blenderbim.tool as tool import blenderbim.core.pset as core +import blenderbim.bim.module.pset.data from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.pset.data import Data from ifcopenshell.api.cost.data import Data as CostData @@ -78,16 +80,15 @@ def get_pset_obj_ifc_definition_id(context, obj, obj_type): return context.scene.BIMWorkScheduleProperties.active_work_schedule_id -class TogglePsetExpansion(bpy.types.Operator): +class TogglePsetExpansion(bpy.types.Operator, Operator): bl_idname = "bim.toggle_pset_expansion" bl_label = "Toggle Pset Expansion" pset_id: bpy.props.IntProperty() - def execute(self, context): - obj = context.active_object - data = Data.psets if self.pset_id in Data.psets else Data.qtos - data[self.pset_id]["is_expanded"] = not data[self.pset_id]["is_expanded"] - return {"FINISHED"} + def _execute(self, context): + blenderbim.bim.module.pset.data.is_expanded[ + self.pset_id + ] = not blenderbim.bim.module.pset.data.is_expanded.setdefault(self.pset_id, True) class EnablePsetEditing(bpy.types.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/pset/ui.py b/src/blenderbim/blenderbim/bim/module/pset/ui.py index 27e423c3c2..a7247daf0f 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/ui.py +++ b/src/blenderbim/blenderbim/bim/module/pset/ui.py @@ -17,9 +17,18 @@ # along with BlenderBIM Add-on. If not, see . from bpy.types import Panel -from ifcopenshell.api.pset.data import Data from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import draw_attribute +from blenderbim.bim.module.pset.data import ( + ObjectPsetsData, + ObjectQtosData, + MaterialPsetsData, + TaskQtosData, + ResourceQtosData, + ResourcePsetsData, + ProfilePsetsData, + WorkSchedulePsetsData, +) def get_active_pset_obj_name(context, obj_type): @@ -68,8 +77,7 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type): draw_psetqto_editable_ui(box, props, prop) else: has_props_displayed = False - for prop_id in pset["Properties"]: - prop = Data.properties[prop_id] + for prop in pset["Properties"]: if context.preferences.addons["blenderbim"].preferences.should_hide_empty_props and ( prop["NominalValue"] is None or prop["NominalValue"] == "" ): @@ -121,33 +129,26 @@ class BIM_PT_object_psets(Panel): return False if not IfcStore.get_element(props.ifc_definition_id): return False - if props.ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), props.ifc_definition_id) - if not Data.products[props.ifc_definition_id]: - return False return True def draw(self, context): - oprops = context.active_object.BIMObjectProperties + if not ObjectPsetsData.is_loaded: + ObjectPsetsData.load() + props = context.active_object.PsetProperties - if not oprops.ifc_definition_id: - return - if oprops.ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), oprops.ifc_definition_id) row = self.layout.row(align=True) row.prop(props, "pset_name", text="") op = row.operator("bim.add_pset", icon="ADD", text="") op.obj = context.active_object.name op.obj_type = "Object" - psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[oprops.ifc_definition_id]["psets"]] - for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]): - draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Object") + for pset in ObjectPsetsData.data["psets"]: + draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object") - # TODO reimplement. See #1222. - # if props.relating_type and props.relating_type.PsetProperties.psets: - # self.layout.label(text="Inherited Psets:") - # self.draw_psets_ui(props.relating_type.PsetProperties, enabled=False) + if ObjectPsetsData.data["inherited_psets"]: + self.layout.label(text="Inherited Psets:") + for pset in ObjectPsetsData.data["inherited_psets"]: + draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object") class BIM_PT_object_qtos(Panel): @@ -166,28 +167,21 @@ class BIM_PT_object_qtos(Panel): return False if not IfcStore.get_element(props.ifc_definition_id): return False - if props.ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), props.ifc_definition_id) - if not Data.products[props.ifc_definition_id]: - return False return True def draw(self, context): - oprops = context.active_object.BIMObjectProperties + if not ObjectQtosData.is_loaded: + ObjectQtosData.load() + props = context.active_object.PsetProperties - if not oprops.ifc_definition_id: - return - if oprops.ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), oprops.ifc_definition_id) row = self.layout.row(align=True) row.prop(props, "qto_name", text="") op = row.operator("bim.add_qto", icon="ADD", text="") op.obj = context.active_object.name op.obj_type = "Object" - qtos = [(qto_id, Data.qtos[qto_id]) for qto_id in Data.products[oprops.ifc_definition_id]["qtos"]] - for qto_id, qto in sorted(qtos, key=lambda v: v[1]["Name"]): - draw_psetqto_ui(context, qto_id, qto, props, self.layout, "Object") + for qto in ObjectQtosData.data["qtos"]: + draw_psetqto_ui(context, qto["id"], qto, props, self.layout, "Object") class BIM_PT_material_psets(Panel): @@ -209,28 +203,21 @@ class BIM_PT_material_psets(Panel): file = IfcStore.get_file() if not file or file.schema == "IFC2X3": return False # We don't support material psets in IFC2X3 because they suck - if props.ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), props.ifc_definition_id) - if not Data.products[props.ifc_definition_id]: - return False return True def draw(self, context): - oprops = context.active_object.active_material.BIMObjectProperties + if not MaterialPsetsData.is_loaded: + MaterialPsetsData.load() + props = context.active_object.active_material.PsetProperties - if not oprops.ifc_definition_id: - return - if oprops.ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), oprops.ifc_definition_id) row = self.layout.row(align=True) row.prop(props, "pset_name", text="") op = row.operator("bim.add_pset", icon="ADD", text="") op.obj = context.active_object.active_material.name op.obj_type = "Material" - psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[oprops.ifc_definition_id]["psets"]] - for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]): - draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Material") + for pset in MaterialPsetsData.data["psets"]: + draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Material") class BIM_PT_task_qtos(Panel): @@ -253,20 +240,17 @@ class BIM_PT_task_qtos(Panel): return False def draw(self, context): + if not TaskQtosData.is_loaded: + TaskQtosData.load() + props = context.scene.TaskPsetProperties - wprops = context.scene.BIMWorkScheduleProperties - tprops = context.scene.BIMTaskTreeProperties - ifc_definition_id = tprops.tasks[wprops.active_task_index].ifc_definition_id - if ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), ifc_definition_id) row = self.layout.row(align=True) row.prop(props, "qto_name", text="") op = row.operator("bim.add_qto", icon="ADD", text="") op.obj_type = "Task" - qtos = [(qto_id, Data.qtos[qto_id]) for qto_id in Data.products[ifc_definition_id]["qtos"]] - for qto_id, qto in sorted(qtos, key=lambda v: v[1]["Name"]): - draw_psetqto_ui(context, qto_id, qto, props, self.layout, "Task") + for qto in TaskQtosData.data["qtos"]: + draw_psetqto_ui(context, qto["id"], qto, props, self.layout, "Task") class BIM_PT_resource_qtos(Panel): @@ -287,20 +271,17 @@ class BIM_PT_resource_qtos(Panel): return False def draw(self, context): + if not ResourceQtosData.is_loaded: + ResourceQtosData.load() + props = context.scene.ResourcePsetProperties - rprops = context.scene.BIMResourceProperties - rtprops = context.scene.BIMResourceTreeProperties - ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id - if ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), ifc_definition_id) row = self.layout.row(align=True) row.prop(props, "qto_name", text="") op = row.operator("bim.add_qto", icon="ADD", text="") op.obj_type = "Resource" - qtos = [(qto_id, Data.qtos[qto_id]) for qto_id in Data.products[ifc_definition_id]["qtos"]] - for qto_id, qto in sorted(qtos, key=lambda v: v[1]["Name"]): - draw_psetqto_ui(context, qto_id, qto, props, self.layout, "Resource") + for qto in ResourceQtosData.data["qtos"]: + draw_psetqto_ui(context, qto["id"], qto, props, self.layout, "Resource") class BIM_PT_resource_psets(Panel): @@ -321,20 +302,17 @@ class BIM_PT_resource_psets(Panel): return False def draw(self, context): + if not ResourcePsetsData.is_loaded: + ResourcePsetsData.load() + props = context.scene.ResourcePsetProperties - rprops = context.scene.BIMResourceProperties - rtprops = context.scene.BIMResourceTreeProperties - ifc_definition_id = rtprops.resources[rprops.active_resource_index].ifc_definition_id - if ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), ifc_definition_id) row = self.layout.row(align=True) row.prop(props, "pset_name", text="") op = row.operator("bim.add_pset", icon="ADD", text="") op.obj_type = "Resource" - psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[ifc_definition_id]["psets"]] - for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]): - draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Resource") + for pset in ResourcePsetsData.data["psets"]: + draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Resource") class BIM_PT_profile_psets(Panel): @@ -357,19 +335,17 @@ class BIM_PT_profile_psets(Panel): return False def draw(self, context): + if not ProfilePsetsData.is_loaded: + ProfilePsetsData.load() + props = context.scene.ProfilePsetProperties - pprops = context.scene.BIMProfileProperties - ifc_definition_id = pprops.profiles[pprops.active_profile_index].ifc_definition_id - if ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), ifc_definition_id) row = self.layout.row(align=True) row.prop(props, "pset_name", text="") op = row.operator("bim.add_pset", icon="ADD", text="") op.obj_type = "Profile" - psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[ifc_definition_id]["psets"]] - for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]): - draw_psetqto_ui(context, pset_id, pset, props, self.layout, "Profile") + for pset in ProfilePsetsData.data["psets"]: + draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Profile") class BIM_PT_work_schedule_psets(Panel): @@ -388,15 +364,14 @@ class BIM_PT_work_schedule_psets(Panel): return True def draw(self, context): + if not WorkSchedulePsetsData.is_loaded: + WorkSchedulePsetsData.load() + props = context.scene.WorkSchedulePsetProperties - ifc_definition_id = context.scene.BIMWorkScheduleProperties.active_work_schedule_id - if ifc_definition_id not in Data.products: - Data.load(IfcStore.get_file(), ifc_definition_id) row = self.layout.row(align=True) row.prop(props, "pset_name", text="") op = row.operator("bim.add_pset", icon="ADD", text="") op.obj_type = "WorkSchedule" - psets = [(pset_id, Data.psets[pset_id]) for pset_id in Data.products[ifc_definition_id]["psets"]] - for pset_id, pset in sorted(psets, key=lambda v: v[1]["Name"]): - draw_psetqto_ui(context, pset_id, pset, props, self.layout, "WorkSchedule") + for pset in WorkSchedulePsetsData.data["psets"]: + draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "WorkSchedule")