Material profile sets UI cleanup and now lets you choose from a profile in your library instead of creating new profiles on the fly all the time

This commit is contained in:
Dion Moult
2022-11-01 23:38:59 +11:00
parent a7f9d548cf
commit 2043a629c7
8 changed files with 74 additions and 105 deletions
@@ -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 = {}
@@ -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
@@ -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(
@@ -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":
@@ -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 = []
@@ -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)
@@ -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
@@ -1,5 +1,5 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2021, 2022 Dion Moult <dion@thinkmoult.com>
#
# 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"]