diff --git a/src/blenderbim/blenderbim/bim/module/material/data.py b/src/blenderbim/blenderbim/bim/module/material/data.py index 43983aab8b..9cb372bc9a 100644 --- a/src/blenderbim/blenderbim/bim/module/material/data.py +++ b/src/blenderbim/blenderbim/bim/module/material/data.py @@ -37,6 +37,7 @@ class MaterialsData: cls.data = { "total_materials": cls.total_materials(), "material_types": cls.material_types(), + "profiles": cls.profiles(), } cls.is_loaded = True @@ -72,6 +73,10 @@ class MaterialsData: (m, m, ifcopenshell.util.doc.get_entity_doc(version, m).get("description", "")) for m in material_types ] + @classmethod + def profiles(cls): + return [(str(p.id()), p.ProfileName or "Unnamed", "") for p in tool.Ifc.get().by_type("IfcProfileDef")] + class ObjectMaterialData: data = {} diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 164b68377f..3e83a79c4a 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -28,7 +28,6 @@ import blenderbim.core.material as core from blenderbim.bim.module.material.prop import purge as material_prop_purge from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.material.data import Data -from ifcopenshell.api.profile.data import Data as ProfileData class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator): @@ -84,7 +83,6 @@ class AssignParameterizedProfile(bpy.types.Operator): **{"material_profile": self.file.by_id(self.material_profile), "profile": profile}, ) Data.load_profiles() - ProfileData.load(self.file) bpy.ops.bim.enable_editing_material_set_item(obj=obj.name, material_set_item=self.material_profile) return {"FINISHED"} @@ -256,13 +254,11 @@ class AddProfile(bpy.types.Operator): ifcopenshell.api.run( "material.add_profile", self.file, - **{ - "profile_set": self.file.by_id(self.profile_set), - "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)), - }, + profile_set=self.file.by_id(self.profile_set), + material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material)), + profile=self.file.by_id(int(context.scene.BIMMaterialProperties.profiles)), ) Data.load_profiles() - ProfileData.load(self.file) return {"FINISHED"} @@ -281,7 +277,6 @@ class RemoveProfile(bpy.types.Operator): self.file = IfcStore.get_file() ifcopenshell.api.run("material.remove_profile", self.file, **{"profile": self.file.by_id(self.profile)}) Data.load_profiles() - ProfileData.load(self.file) return {"FINISHED"} @@ -341,7 +336,6 @@ class ReorderMaterialSetItem(bpy.types.Operator): Data.load_layers() elif material_set.is_a("IfcMaterialProfileSet"): Data.load_profiles() - ProfileData.load(self.file) elif material_set.is_a("IfcMaterialList"): Data.load_lists() return {"FINISHED"} @@ -586,6 +580,7 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): def execute(self, context): self.file = IfcStore.get_file() obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object + self.mprops = context.scene.BIMMaterialProperties self.props = obj.BIMObjectMaterialProperties self.props.active_material_set_item_id = self.material_set_item product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id] @@ -604,7 +599,8 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): self.load_set_item_attributes(material_set_item, material_set_item_data) if material_set_item.is_a("IfcMaterialProfile"): - self.load_profile_attributes(material_set_item, material_set_item_data) + if material_set_item.Profile: + self.mprops.profiles = str(material_set_item.Profile.id()) return {"FINISHED"} @@ -630,47 +626,6 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): 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() - - if not material_set_item_data["Profile"]: - return - - profile = self.file.by_id(material_set_item_data["Profile"]) - profile_data = ProfileData.profiles[material_set_item_data["Profile"]] - - for attribute in IfcStore.get_schema().declaration_by_name(profile.is_a()).all_attributes(): - data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) - if data_type == "entity": - continue - 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 - if data_type == "string": - new.string_value = "" if new.is_null else profile_data[attribute.name()] - elif data_type == "float": - new.float_value = 0.0 if new.is_null else profile_data[attribute.name()] - elif data_type == "integer": - new.int_value = 0 if new.is_null else profile_data[attribute.name()] - elif data_type == "boolean": - new.bool_value = False if new.is_null else profile_data[attribute.name()] - elif data_type == "enum": - new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute)) - 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. - if not new.is_optional: - new.is_null = False class DisableEditingMaterialSetItem(bpy.types.Operator): @@ -700,6 +655,7 @@ class EditMaterialSetItem(bpy.types.Operator): obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object self.file = IfcStore.get_file() props = obj.BIMObjectMaterialProperties + mprops = context.scene.BIMMaterialProperties product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id] attributes = blenderbim.bim.helper.export_attributes(props.material_set_item_attributes) @@ -727,19 +683,19 @@ class EditMaterialSetItem(bpy.types.Operator): ) Data.load_layers() elif product_data["type"] == "IfcMaterialProfileSet" or product_data["type"] == "IfcMaterialProfileSetUsage": - profile_attributes = blenderbim.bim.helper.export_attributes(props.material_set_item_profile_attributes) + profile_def = None + if mprops.profiles: + profile_def = tool.Ifc.get().by_id(int(mprops.profiles)) + ifcopenshell.api.run( "material.edit_profile", self.file, - **{ - "profile": self.file.by_id(self.material_set_item), - "attributes": attributes, - "profile_attributes": profile_attributes, - "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)), - }, + profile=self.file.by_id(self.material_set_item), + attributes=attributes, + profile_def=profile_def, + material=self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)), ) Data.load_profiles() - ProfileData.load(self.file) else: pass diff --git a/src/blenderbim/blenderbim/bim/module/material/prop.py b/src/blenderbim/blenderbim/bim/module/material/prop.py index 017ef6ddfa..7faf4c08ff 100644 --- a/src/blenderbim/blenderbim/bim/module/material/prop.py +++ b/src/blenderbim/blenderbim/bim/module/material/prop.py @@ -112,6 +112,12 @@ def get_material_types(self, context): return MaterialsData.data["material_types"] +def get_profiles(self, context): + if not MaterialsData.is_loaded: + MaterialsData.load() + return MaterialsData.data["profiles"] + + class Material(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") @@ -125,6 +131,7 @@ class BIMMaterialProperties(PropertyGroup): material_type: EnumProperty(items=get_material_types, name="Material Type") materials: CollectionProperty(name="Materials", type=Material) active_material_index: IntProperty(name="Active Material Index") + profiles: EnumProperty(items=get_profiles, name="Profiles") class BIMObjectMaterialProperties(PropertyGroup): @@ -135,9 +142,6 @@ class BIMObjectMaterialProperties(PropertyGroup): material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute) active_material_set_item_id: IntProperty(name="Active Material Set ID") material_set_item_attributes: CollectionProperty(name="Material Set Item Attributes", type=Attribute) - material_set_item_profile_attributes: CollectionProperty( - name="Material Set Item Profile Attributes", type=Attribute - ) material_set_item_material: EnumProperty(items=get_materials, name="Material") profile_classes: EnumProperty(items=get_profile_classes, name="Profile Classes") parameterized_profile_classes: EnumProperty( diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index 01391fa5f7..78f5999084 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -19,7 +19,6 @@ import blenderbim.bim.helper from bpy.types import Panel, UIList from ifcopenshell.api.material.data import Data -from ifcopenshell.api.profile.data import Data as ProfileData from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import draw_attributes from blenderbim.bim.helper import prop_with_search @@ -128,12 +127,11 @@ class BIM_PT_object_material(Panel): self.file = IfcStore.get_file() self.oprops = context.active_object.BIMObjectProperties self.props = context.active_object.BIMObjectMaterialProperties + self.mprops = context.scene.BIMMaterialProperties if not Data.is_loaded: Data.load(IfcStore.get_file()) if self.oprops.ifc_definition_id not in Data.products: Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id) - if not ProfileData.is_loaded: - ProfileData.load(self.file) self.product_data = Data.products[self.oprops.ifc_definition_id] if not ObjectMaterialData.data["materials"]: @@ -235,15 +233,13 @@ class BIM_PT_object_material(Panel): self.draw_read_only_set_ui() def draw_editable_set_ui(self): + blenderbim.bim.helper.draw_attributes(self.props.material_set_attributes, self.layout) blenderbim.bim.helper.draw_attributes(self.props.material_set_usage_attributes, self.layout) - for attribute in self.props.material_set_attributes: - row = self.layout.row(align=True) - row.prop(attribute, "string_value", text=attribute.name) - row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") row = self.layout.row(align=True) - prop_with_search(row, self.props, "material", text="") - + if self.set_item_name == "profile": + row.prop(self.mprops, "profiles", icon="ITALIC", text="") + prop_with_search(row, self.props, "material", icon="MATERIAL", text="") op = row.operator(f"bim.add_{self.set_item_name}", icon="ADD", text="") setattr(op, f"{self.set_item_name}_set", self.material_set_id) @@ -262,34 +258,18 @@ class BIM_PT_object_material(Panel): box = self.layout.box() row = box.row(align=True) - row.prop(self.props, "material_set_item_material", icon="MATERIAL") - op = row.operator("bim.edit_material_set_item", icon="CHECKMARK", text="") + op = row.operator("bim.edit_material_set_item", icon="CHECKMARK", text="Save Changes") op.material_set_item = set_item_id row.operator("bim.disable_editing_material_set_item", icon="CANCEL", text="") - draw_attributes(self.props.material_set_item_attributes, self.layout) + draw_attributes(self.props.material_set_item_attributes, box) + + row = box.row() + row.prop(self.props, "material_set_item_material", icon="MATERIAL", text="Material") if self.set_item_name == "profile": - self.draw_assign_profile_ui(box, item) - self.draw_editable_profile_ui(box, item) - - def draw_assign_profile_ui(self, layout, item): - row = layout.row(align=True) - row.prop(self.props, "profile_classes", text="") - if self.props.profile_classes == "IfcParameterizedProfileDef": - row.prop(self.props, "parameterized_profile_classes", text="") - op = row.operator( - "bim.assign_parameterized_profile", icon="GREASEPENCIL" if item["Profile"] else "ADD", text="" - ) - op.ifc_class = self.props.parameterized_profile_classes - op.material_profile = item["id"] - else: - # TODO: support non parametric profiles by showing a list of named profiles to select from, or an - # eyedropper to pick profile geometry from the scene - row.operator("bim.disable_editing_material_set_item", icon="CANCEL", text="") - - def draw_editable_profile_ui(self, layout, item): - draw_attributes(self.props.material_set_item_profile_attributes, self.layout) + row = box.row() + row.prop(self.mprops, "profiles", icon="ITALIC", text="Profile") def draw_read_only_set_item_ui(self, set_item_id, index, is_first=False, is_last=False): if self.product_data["type"] == "IfcMaterialList": diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py index 40c6f53db2..7c948e5213 100644 --- a/src/blenderbim/blenderbim/bim/module/model/profile.py +++ b/src/blenderbim/blenderbim/bim/module/model/profile.py @@ -199,6 +199,17 @@ class DumbProfileGenerator: class DumbProfileRegenerator: + def regenerate_from_profile_def(self, profile): + self.file = tool.Ifc.get() + objs = [] + if not profile: + return + for element in self.get_elements_using_profile(profile): + obj = tool.Ifc.get_object(element) + if obj: + objs.append(obj) + DumbProfileRecalculator().recalculate(objs) + def regenerate_from_profile(self, usecase_path, ifc_file, settings): self.file = ifc_file objs = [] diff --git a/src/blenderbim/blenderbim/bim/module/profile/operator.py b/src/blenderbim/blenderbim/bim/module/profile/operator.py index b72927661c..d3b1575ac8 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/operator.py +++ b/src/blenderbim/blenderbim/bim/module/profile/operator.py @@ -20,6 +20,7 @@ import bpy import ifcopenshell.api import blenderbim.bim.helper import blenderbim.tool as tool +import blenderbim.bim.module.model.profile as model_profile from blenderbim.bim.module.model.slab import DecorationsHandler @@ -84,6 +85,7 @@ class DisableEditingProfile(bpy.types.Operator): def execute(self, context): context.scene.BIMProfileProperties.active_profile_id = 0 + bpy.ops.bim.disable_editing_arbitrary_profile() return {"FINISHED"} @@ -123,6 +125,8 @@ class EnableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): + for obj in context.selected_objects: + obj.select_set(False) props = context.scene.BIMProfileProperties profile = tool.Ifc.get().by_id(props.active_profile_id) obj = tool.Model.import_profile(profile) @@ -140,9 +144,10 @@ class DisableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object - DecorationsHandler.uninstall() - bpy.ops.object.mode_set(mode="OBJECT") - bpy.data.objects.remove(obj) + if obj and obj.data and obj.data.BIMMeshProperties.is_profile: + DecorationsHandler.uninstall() + bpy.ops.object.mode_set(mode="OBJECT") + bpy.data.objects.remove(obj) class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator): @@ -172,6 +177,10 @@ class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator): bpy.data.objects.remove(obj) profile.ProfileType = old_profile.ProfileType profile.ProfileName = old_profile.ProfileName + for inverse in tool.Ifc.get().get_inverse(old_profile): + ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile) ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile) bpy.ops.bim.load_profiles() props.active_profile_id = profile.id() + + model_profile.DumbProfileRegenerator().regenerate_from_profile_def(profile) diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/add_profile.py b/src/ifcopenshell-python/ifcopenshell/api/material/add_profile.py index b2036c7f7e..1c54d5409c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/add_profile.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/add_profile.py @@ -20,13 +20,17 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = {"profile_set": None, "material": None} + self.settings = {"profile_set": None, "material": None, "profile": None} for key, value in settings.items(): self.settings[key] = value def execute(self): profiles = list(self.settings["profile_set"].MaterialProfiles or []) - profile = self.file.create_entity("IfcMaterialProfile", **{"Material": self.settings["material"]}) + profile = self.file.create_entity("IfcMaterialProfile") + if self.settings["material"]: + profile.Material = self.settings["material"] + if self.settings["profile"]: + profile.Profile = self.settings["profile"] profiles.append(profile) self.settings["profile_set"].MaterialProfiles = profiles return profile diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile.py b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile.py index 4728ac8c14..4d03fce765 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/edit_profile.py @@ -1,5 +1,5 @@ # IfcOpenShell - IFC toolkit and geometry engine -# Copyright (C) 2021 Dion Moult +# Copyright (C) 2021, 2022 Dion Moult # # This file is part of IfcOpenShell. # @@ -20,14 +20,14 @@ class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = {"profile": None, "attributes": {}, "profile_attributes": {}, "material": None} + self.settings = {"profile": None, "attributes": {}, "profile_def": None, "material": None} for key, value in settings.items(): self.settings[key] = value def execute(self): - # TODO: don't also edit the profile def in this usecase for name, value in self.settings["attributes"].items(): setattr(self.settings["profile"], name, value) - self.settings["profile"].Material = self.settings["material"] - for name, value in self.settings["profile_attributes"].items(): - setattr(self.settings["profile"].Profile, name, value) + if self.settings["material"]: + self.settings["profile"].Material = self.settings["material"] + if self.settings["profile_def"]: + self.settings["profile"].Profile = self.settings["profile_def"]