mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-29 16:22:53 +00:00
370 lines
14 KiB
Python
370 lines
14 KiB
Python
import bpy
|
|
import json
|
|
import ifcopenshell.api
|
|
import ifcopenshell.util.unit
|
|
import ifcopenshell.util.pset
|
|
import ifcopenshell.util.attribute
|
|
import blenderbim.bim.schema
|
|
from blenderbim.bim.ifc import IfcStore
|
|
from ifcopenshell.api.pset.data import Data
|
|
from ifcopenshell.api.cost.data import Data as CostData
|
|
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
|
|
|
|
|
|
def get_pset_props(context, obj, obj_type):
|
|
if obj_type == "Object":
|
|
obj = bpy.data.objects.get(obj)
|
|
return obj.PsetProperties
|
|
elif obj_type == "Material":
|
|
obj = bpy.data.materials.get(obj)
|
|
return obj.PsetProperties
|
|
elif obj_type == "Task":
|
|
return context.scene.TaskPsetProperties
|
|
elif obj_type == "Resource":
|
|
return context.scene.ResourcePsetProperties
|
|
elif obj_type == "Profile":
|
|
return context.scene.ProfilePsetProperties
|
|
|
|
|
|
def get_pset_obj_ifc_definition_id(context, obj, obj_type):
|
|
if obj_type == "Object":
|
|
obj = bpy.data.objects.get(obj)
|
|
return obj.BIMObjectProperties.ifc_definition_id
|
|
elif obj_type == "Material":
|
|
obj = bpy.data.materials.get(obj)
|
|
return obj.BIMObjectProperties.ifc_definition_id
|
|
elif obj_type == "Task":
|
|
return context.scene.BIMTaskTreeProperties.tasks[
|
|
context.scene.BIMWorkScheduleProperties.active_task_index
|
|
].ifc_definition_id
|
|
elif obj_type == "Resource":
|
|
return context.scene.BIMResourceTreeProperties.resources[
|
|
context.scene.BIMResourceProperties.active_resource_index
|
|
].ifc_definition_id
|
|
elif obj_type == "Profile":
|
|
return context.scene.BIMProfileProperties.profiles[
|
|
context.scene.BIMProfileProperties.active_profile_index
|
|
].ifc_definition_id
|
|
|
|
|
|
|
|
class TogglePsetExpansion(bpy.types.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"}
|
|
|
|
|
|
class EnablePsetEditing(bpy.types.Operator):
|
|
bl_idname = "bim.enable_pset_editing"
|
|
bl_label = "Enable Pset Editing"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
pset_id: bpy.props.IntProperty()
|
|
obj: bpy.props.StringProperty()
|
|
obj_type: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
self.props = get_pset_props(context, self.obj, self.obj_type)
|
|
|
|
while len(self.props.properties) > 0:
|
|
self.props.properties.remove(0)
|
|
|
|
data = Data.psets if self.pset_id in Data.psets else Data.qtos
|
|
pset_data = data[self.pset_id]
|
|
self.props.active_pset_name = pset_data["Name"]
|
|
|
|
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(pset_data["Name"])
|
|
|
|
if pset_template:
|
|
self.load_from_pset_template(pset_template, pset_data)
|
|
else:
|
|
self.load_from_pset_data(pset_data)
|
|
|
|
self.props.active_pset_id = self.pset_id
|
|
return {"FINISHED"}
|
|
|
|
def load_from_pset_template(self, pset_template, pset_data):
|
|
data = {Data.properties[p]["Name"]: Data.properties[p]["NominalValue"] for p in pset_data["Properties"]}
|
|
for prop_template in pset_template.HasPropertyTemplates:
|
|
if not prop_template.is_a("IfcSimplePropertyTemplate"):
|
|
continue # Other types not yet supported
|
|
|
|
if prop_template.TemplateType == "P_SINGLEVALUE":
|
|
try:
|
|
data_type = ifcopenshell.util.attribute.get_primitive_type(
|
|
IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
|
|
)
|
|
except:
|
|
# TODO: Occurs if the data type is something that exists in IFC4 and not in IFC2X3. To fully fix
|
|
# this we need to generate the IFC2X3 pset template definitions.
|
|
continue
|
|
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
|
|
data_type = "enum"
|
|
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
|
|
elif prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
|
|
data_type = "float"
|
|
elif prop_template.TemplateType == "Q_COUNT":
|
|
data_type = "integer"
|
|
else:
|
|
continue # Other types not yet supported
|
|
|
|
new = self.props.properties.add()
|
|
new.name = prop_template.Name
|
|
new.is_null = data.get(prop_template.Name, None) is None
|
|
new.data_type = data_type
|
|
|
|
if data_type == "string":
|
|
new.string_value = "" if new.is_null else data[prop_template.Name]
|
|
elif data_type == "integer":
|
|
new.int_value = 0 if new.is_null else data[prop_template.Name]
|
|
elif data_type == "float":
|
|
new.float_value = 0.0 if new.is_null else data[prop_template.Name]
|
|
elif data_type == "boolean":
|
|
new.bool_value = False if new.is_null else data[prop_template.Name]
|
|
elif data_type == "enum":
|
|
new.enum_items = json.dumps(enum_items)
|
|
if data.get(prop_template.Name):
|
|
new.enum_value = data[prop_template.Name]
|
|
|
|
def load_from_pset_data(self, pset_data):
|
|
for prop_id in pset_data["Properties"]:
|
|
prop = Data.properties[prop_id]
|
|
|
|
value = prop["NominalValue"]
|
|
if isinstance(value, str):
|
|
data_type = "string"
|
|
elif isinstance(value, float):
|
|
data_type = "float"
|
|
elif isinstance(value, bool):
|
|
data_type = "boolean"
|
|
elif isinstance(value, int):
|
|
data_type = "integer"
|
|
else:
|
|
data_type = "string"
|
|
value = str(value)
|
|
|
|
new = self.props.properties.add()
|
|
new.name = prop["Name"]
|
|
new.is_null = prop["NominalValue"] is None
|
|
new.data_type = data_type
|
|
|
|
if data_type == "string":
|
|
new.string_value = "" if new.is_null else value
|
|
elif data_type == "integer":
|
|
new.int_value = 0 if new.is_null else value
|
|
elif data_type == "float":
|
|
new.float_value = 0.0 if new.is_null else value
|
|
elif data_type == "boolean":
|
|
new.bool_value = False if new.is_null else value
|
|
|
|
|
|
class DisablePsetEditing(bpy.types.Operator):
|
|
bl_idname = "bim.disable_pset_editing"
|
|
bl_label = "Disable Pset Editing"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
obj: bpy.props.StringProperty()
|
|
obj_type: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
props = get_pset_props(context, self.obj, self.obj_type)
|
|
props.active_pset_id = 0
|
|
return {"FINISHED"}
|
|
|
|
|
|
class EditPset(bpy.types.Operator):
|
|
bl_idname = "bim.edit_pset"
|
|
bl_label = "Edit Pset"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
obj: bpy.props.StringProperty()
|
|
obj_type: bpy.props.StringProperty()
|
|
pset_id: bpy.props.IntProperty()
|
|
properties: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
return IfcStore.execute_ifc_operator(self, context)
|
|
|
|
def _execute(self, context):
|
|
self.file = IfcStore.get_file()
|
|
props = get_pset_props(context, self.obj, self.obj_type)
|
|
ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
|
|
properties = {}
|
|
|
|
pset_id = self.pset_id or props.active_pset_id
|
|
if self.properties:
|
|
properties = json.loads(self.properties)
|
|
else:
|
|
data = Data.psets if pset_id in Data.psets else Data.qtos
|
|
for prop in props.properties:
|
|
if prop.is_null:
|
|
properties[prop.name] = None
|
|
elif prop.data_type == "string":
|
|
properties[prop.name] = prop.string_value
|
|
elif prop.data_type == "boolean":
|
|
properties[prop.name] = prop.bool_value
|
|
elif prop.data_type == "integer":
|
|
properties[prop.name] = prop.int_value
|
|
elif prop.data_type == "float":
|
|
properties[prop.name] = prop.float_value
|
|
elif prop.data_type == "enum":
|
|
properties[prop.name] = prop.enum_value
|
|
|
|
if pset_id in Data.psets:
|
|
ifcopenshell.api.run(
|
|
"pset.edit_pset",
|
|
self.file,
|
|
**{
|
|
"pset": self.file.by_id(pset_id),
|
|
"name": props.active_pset_name,
|
|
"properties": properties,
|
|
"pset_template": blenderbim.bim.schema.ifc.psetqto.get_by_name(props.active_pset_name),
|
|
},
|
|
)
|
|
else:
|
|
for key, value in properties.items():
|
|
if isinstance(value, float):
|
|
properties[key] = round(value, 4)
|
|
ifcopenshell.api.run(
|
|
"pset.edit_qto",
|
|
self.file,
|
|
**{
|
|
"qto": self.file.by_id(pset_id),
|
|
"name": props.active_pset_name,
|
|
"properties": properties,
|
|
},
|
|
)
|
|
CostData.purge()
|
|
Data.load(IfcStore.get_file(), ifc_definition_id)
|
|
bpy.ops.bim.disable_pset_editing(obj=self.obj, obj_type=self.obj_type)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class RemovePset(bpy.types.Operator):
|
|
bl_idname = "bim.remove_pset"
|
|
bl_label = "Remove Pset"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
pset_id: bpy.props.IntProperty()
|
|
obj: bpy.props.StringProperty()
|
|
obj_type: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
return IfcStore.execute_ifc_operator(self, context)
|
|
|
|
def _execute(self, context):
|
|
self.file = IfcStore.get_file()
|
|
props = get_pset_props(context, self.obj, self.obj_type)
|
|
ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
|
|
ifcopenshell.api.run(
|
|
"pset.remove_pset",
|
|
self.file,
|
|
**{
|
|
"product": self.file.by_id(ifc_definition_id),
|
|
"pset": self.file.by_id(self.pset_id),
|
|
},
|
|
)
|
|
Data.load(IfcStore.get_file(), ifc_definition_id)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class AddPset(bpy.types.Operator):
|
|
bl_idname = "bim.add_pset"
|
|
bl_label = "Add Pset"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
obj: bpy.props.StringProperty()
|
|
obj_type: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
return IfcStore.execute_ifc_operator(self, context)
|
|
|
|
def _execute(self, context):
|
|
self.file = IfcStore.get_file()
|
|
props = get_pset_props(context, self.obj, self.obj_type)
|
|
ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
|
|
|
|
ifcopenshell.api.run(
|
|
"pset.add_pset",
|
|
self.file,
|
|
**{
|
|
"product": self.file.by_id(ifc_definition_id),
|
|
"name": props.pset_name,
|
|
},
|
|
)
|
|
Data.load(IfcStore.get_file(), ifc_definition_id)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class AddQto(bpy.types.Operator):
|
|
bl_idname = "bim.add_qto"
|
|
bl_label = "Add Qto"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
obj: bpy.props.StringProperty()
|
|
obj_type: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
return IfcStore.execute_ifc_operator(self, context)
|
|
|
|
def _execute(self, context):
|
|
self.file = IfcStore.get_file()
|
|
props = get_pset_props(context, self.obj, self.obj_type)
|
|
ifc_definition_id = get_pset_obj_ifc_definition_id(context, self.obj, self.obj_type)
|
|
ifcopenshell.api.run(
|
|
"pset.add_qto",
|
|
self.file,
|
|
**{
|
|
"product": self.file.by_id(ifc_definition_id),
|
|
"name": props.qto_name,
|
|
},
|
|
)
|
|
Data.load(IfcStore.get_file(), ifc_definition_id)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class GuessQuantity(bpy.types.Operator):
|
|
bl_idname = "bim.guess_quantity"
|
|
bl_label = "Guess Quantity"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
prop: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
self.qto_calculator = QtoCalculator()
|
|
obj = context.active_object
|
|
prop = obj.PsetProperties.properties.get(self.prop)
|
|
prop.float_value = self.guess_quantity(obj, context)
|
|
return {"FINISHED"}
|
|
|
|
def guess_quantity(self, obj, context):
|
|
quantity = self.qto_calculator.guess_quantity(self.prop, [p.name for p in obj.PsetProperties.properties], obj)
|
|
if "area" in self.prop.lower():
|
|
if context.scene.BIMProperties.area_unit:
|
|
prefix, name = self.get_prefix_name(context.scene.BIMProperties.area_unit)
|
|
quantity = ifcopenshell.util.unit.convert(quantity, None, "SQUARE_METRE", prefix, name)
|
|
elif "volume" in self.prop.lower():
|
|
if context.scene.BIMProperties.volume_unit:
|
|
prefix, name = self.get_prefix_name(context.scene.BIMProperties.volume_unit)
|
|
quantity = ifcopenshell.util.unit.convert(quantity, None, "CUBIC_METRE", prefix, name)
|
|
else:
|
|
prefix, name = self.get_blender_prefix_name(context)
|
|
quantity = ifcopenshell.util.unit.convert(quantity, None, "METRE", prefix, name)
|
|
return round(quantity, 3)
|
|
|
|
def get_prefix_name(self, value):
|
|
if "/" in value:
|
|
return value.split("/")
|
|
return None, value
|
|
|
|
def get_blender_prefix_name(self, context):
|
|
unit_settings = context.scene.unit_settings
|
|
if unit_settings.system == "IMPERIAL":
|
|
if unit_settings.length_unit == "INCHES":
|
|
return None, "inch"
|
|
elif unit_settings.length_unit == "FEET":
|
|
return None, "foot"
|
|
elif unit_settings.system == "METRIC":
|
|
if unit_settings.length_unit == "METERS":
|
|
return None, "METRE"
|
|
return unit_settings.length_unit[0 : -len("METERS")], "METRE"
|