You can now assign parameterized profiles to material profiles. Thanks Jesusbill!

This commit is contained in:
Dion Moult
2021-04-08 21:03:37 +10:00
parent 70fcbfa990
commit 5cea988911
8 changed files with 204 additions and 8 deletions
@@ -10,6 +10,7 @@ classes = (
operator.RemoveConstituent,
operator.AddProfile,
operator.RemoveProfile,
operator.AssignParameterizedProfile,
operator.AddLayer,
operator.RemoveLayer,
operator.ReorderMaterialSetItem,
@@ -1,8 +1,36 @@
import bpy
import json
import ifcopenshell.api
import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
class AssignParameterizedProfile(bpy.types.Operator):
bl_idname = "bim.assign_parameterized_profile"
bl_label = "Assign Parameterized Profile"
ifc_class: bpy.props.StringProperty()
material_profile: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
profile = ifcopenshell.api.run(
"profile.add_parameterized_profile",
self.file,
**{"ifc_class": self.ifc_class},
)
ifcopenshell.api.run(
"material.assign_profile",
self.file,
**{"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"}
class AddMaterial(bpy.types.Operator):
@@ -370,8 +398,8 @@ 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 bpy.context.active_object
props = obj.BIMObjectMaterialProperties
props.active_material_set_item_id = self.material_set_item
self.props = obj.BIMObjectMaterialProperties
self.props.active_material_set_item_id = self.material_set_item
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
material_set_item = self.file.by_id(self.material_set_item)
@@ -384,17 +412,24 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
else:
material_set_item_data = {}
props.material_set_item_material = str(material_set_item_data["Material"])
self.props.material_set_item_material = str(material_set_item_data["Material"])
while len(props.material_set_item_attributes) > 0:
props.material_set_item_attributes.remove(0)
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)
return {"FINISHED"}
def load_set_item_attributes(self, material_set_item, material_set_item_data):
while len(self.props.material_set_item_attributes) > 0:
self.props.material_set_item_attributes.remove(0)
for attribute in IfcStore.get_schema().declaration_by_name(material_set_item.is_a()).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
if attribute.name() in material_set_item_data:
new = props.material_set_item_attributes.add()
new = self.props.material_set_item_attributes.add()
new.name = attribute.name()
new.is_null = material_set_item_data[attribute.name()] is None
new.data_type = data_type
@@ -406,7 +441,45 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
new.int_value = 0 if new.is_null else material_set_item_data[attribute.name()]
elif data_type == "boolean":
new.bool_value = False if new.is_null else material_set_item_data[attribute.name()]
return {"FINISHED"}
def load_profile_attributes(self, material_set_item, material_set_item_data):
while len(self.props.material_set_item_profile_attributes) > 0:
self.props.material_set_item_profile_attributes.remove(0)
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.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()]
# 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):
@@ -468,16 +541,31 @@ class EditMaterialSetItem(bpy.types.Operator):
)
Data.load_layers()
elif product_data["type"] == "IfcMaterialProfileSet":
profile_attributes = {}
for attribute in props.material_set_item_profile_attributes:
if attribute.data_type == "string":
value = attribute.string_value
elif attribute.data_type == "float":
value = attribute.float_value
elif attribute.data_type == "integer":
value = attribute.int_value
elif attribute.data_type == "boolean":
value = attribute.bool_value
elif attribute.data_type == "enum":
value = attribute.enum_value
profile_attributes[attribute.name] = None if attribute.is_null else value
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)),
},
)
Data.load_profiles()
ProfileData.load(self.file)
else:
pass
@@ -1,5 +1,5 @@
import bpy
import blenderbim.bim.schema # refactor
import blenderbim.bim.schema # refactor
from ifcopenshell.api.material.data import Data
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
@@ -17,6 +17,29 @@ from bpy.props import (
materials_enum = []
materialtypes_enum = []
profileclasses_enum = []
parameterizedprofileclasses_enum = []
def getProfileClasses(self, context):
global profileclasses_enum
if len(profileclasses_enum) == 0 and IfcStore.get_schema():
profileclasses_enum.clear()
profileclasses_enum = [
(t.name(), t.name(), "") for t in IfcStore.get_schema().declaration_by_name("IfcProfileDef").subtypes()
]
return profileclasses_enum
def getParameterizedProfileClasses(self, context):
global parameterizedprofileclasses_enum
if len(parameterizedprofileclasses_enum) == 0 and IfcStore.get_schema():
parameterizedprofileclasses_enum.clear()
parameterizedprofileclasses_enum = [
(t.name(), t.name(), "")
for t in IfcStore.get_schema().declaration_by_name("IfcParameterizedProfileDef").subtypes()
]
return parameterizedprofileclasses_enum
def getMaterials(self, context):
@@ -52,4 +75,9 @@ 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=getMaterials, name="Material")
profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes")
parameterized_profile_classes: EnumProperty(
items=getParameterizedProfileClasses, name="Parameterized Profile Classes"
)
@@ -1,5 +1,6 @@
from bpy.types import Panel
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
from blenderbim.bim.ifc import IfcStore
@@ -46,6 +47,8 @@ class BIM_PT_object_material(Panel):
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 Data.materials:
@@ -172,6 +175,39 @@ class BIM_PT_object_material(Panel):
row.prop(attribute, "bool_value", text=attribute.name)
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
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="X", text="")
def draw_editable_profile_ui(self, layout, item):
for attribute in self.props.material_set_item_profile_attributes:
row = layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_value", text=attribute.name)
elif attribute.data_type == "float":
row.prop(attribute, "float_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
def draw_read_only_set_item_ui(self, set_item_id, index, is_first=False, is_last=False):
if self.product_data["type"] == "IfcMaterialList":
item = Data.materials[set_item_id]
@@ -0,0 +1,14 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"material_profile": None, "profile": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if (
self.settings["material_profile"].Profile
and len(self.file.get_inverse(self.settings["material_profile"].Profile)) == 1
):
self.file.remove(self.settings["material_profile"].Profile)
self.settings["material_profile"].Profile = self.settings["profile"]
@@ -4,6 +4,7 @@ class Usecase():
self.settings = {
"profile": None,
"attributes": {},
"profile_attributes": {},
"material": None
}
for key, value in settings.items():
@@ -13,3 +14,5 @@ class 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)
@@ -0,0 +1,9 @@
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"ifc_class": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
return self.file.create_entity(self.settings["ifc_class"])
@@ -0,0 +1,17 @@
class Data:
is_loaded = False
profiles = {}
@classmethod
def purge(cls):
cls.is_loaded = False
cls.profiles = {}
@classmethod
def load(cls, file):
if not file:
return
cls.profiles = {}
for profile in file.by_type("IfcProfileDef"):
cls.profiles[profile.id()] = profile.get_info()
cls.is_loaded = True