diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index d4dcef13fe..eacc3f7676 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -58,6 +58,7 @@ class AddRepresentation(bpy.types.Operator): obj: bpy.props.StringProperty() context_id: bpy.props.IntProperty() ifc_representation_class: bpy.props.StringProperty() + profile_set: bpy.props.IntProperty() def execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object @@ -93,6 +94,7 @@ class AddRepresentation(bpy.types.Operator): "should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep, "should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation, "ifc_representation_class": self.ifc_representation_class, + "profile_set": self.file.by_id(self.profile_set) if self.profile_set else None } result = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data) diff --git a/src/blenderbim/blenderbim/bim/module/model/column.py b/src/blenderbim/blenderbim/bim/module/model/column.py new file mode 100644 index 0000000000..954375329c --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/model/column.py @@ -0,0 +1,129 @@ +import bpy +import bmesh +import math +import ifcopenshell +import ifcopenshell.util.type +import ifcopenshell.util.unit +import ifcopenshell.util.element +import mathutils.geometry +import blenderbim.bim.handler +from blenderbim.bim.ifc import IfcStore +from math import pi, degrees +from mathutils import Vector, Matrix +from ifcopenshell.api.pset.data import Data as PsetData +from ifcopenshell.api.material.data import Data as MaterialData +from blenderbim.bim.module.geometry.helper import Helper + + +def element_listener(element, obj): + blenderbim.bim.handler.subscribe_to(obj, "mode", mode_callback) + + +def mode_callback(obj, data): + for obj in bpy.context.selected_objects + [bpy.context.active_object]: + if ( + obj.mode != "EDIT" + or not obj.data + or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve)) + or not obj.BIMObjectProperties.ifc_definition_id + or not bpy.context.scene.BIMProjectProperties.is_authoring + ): + return + product = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id) + parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric") + if not parametric or parametric["Engine"] != "BlenderBIM.DumbColumn": + return + IfcStore.edited_objs.add(obj) + bm = bmesh.from_edit_mesh(obj.data) + bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) + bmesh.update_edit_mesh(obj.data) + bm.free() + + +def ensure_solid(usecase_path, ifc_file, settings): + product = ifc_file.by_id(settings["blender_object"].BIMObjectProperties.ifc_definition_id) + parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric") + if not parametric or parametric["Engine"] != "BlenderBIM.DumbColumn": + return + material = ifcopenshell.util.element.get_material(product) + if material and material.is_a("IfcMaterialProfileSet"): + settings["profile_set"] = material + else: + return + settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSet" + + +class DumbColumnGenerator: + def __init__(self, relating_type): + self.relating_type = relating_type + + def generate(self): + self.file = IfcStore.get_file() + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file()) + material = ifcopenshell.util.element.get_material(self.relating_type) + if material and material.is_a("IfcMaterialProfileSet"): + self.profile_set = material + else: + return + + self.collection = bpy.context.view_layer.active_layer_collection.collection + self.collection_obj = bpy.data.objects.get(self.collection.name) + self.length = 3 + self.rotation = 0 + self.location = Vector((0, 0, 0)) + return self.derive_from_cursor() + + def derive_from_cursor(self): + self.location = bpy.context.scene.cursor.location + return self.create_column() + + def create_column(self): + # A cube + verts = [ + Vector((-1, -1, -1)), + Vector((-1, -1, 1)), + Vector((-1, 1, -1)), + Vector((-1, 1, 1)), + Vector((1, -1, -1)), + Vector((1, -1, 1)), + Vector((1, 1, -1)), + Vector((1, 1, 1)), + ] + edges = [] + faces = [ + [0, 2, 3, 1], + [2, 3, 7, 6], + [4, 5, 7, 6], + [0, 1, 5, 4], + [1, 3, 7, 5], + [0, 2, 6, 4], + ] + + mesh = bpy.data.meshes.new(name="Dumb Column") + mesh.from_pydata(verts, edges, faces) + obj = bpy.data.objects.new("Column", mesh) + obj.name = "Column" + obj.location = self.location + if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id: + obj.location[2] = self.collection_obj.location[2] + self.collection.objects.link(obj) + bpy.ops.bim.assign_class( + obj=obj.name, ifc_class="IfcColumn", predefined_type="COLUMN", should_add_representation=False + ) + bpy.ops.bim.assign_type(relating_type=self.relating_type.id(), related_object=obj.name) + bpy.ops.bim.add_representation( + obj=obj.name, + context_id=ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id(), + ifc_representation_class="IfcExtrudedAreaSolid/IfcMaterialProfileSet", + profile_set=self.profile_set.id(), + ) + representation = ifcopenshell.util.representation.get_representation( + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), "Model", "Body", "MODEL_VIEW" + ) + bpy.ops.bim.switch_representation(obj=obj.name, ifc_definition_id=representation.id(), should_reload=True) + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric") + ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbColumn"}) + MaterialData.load(self.file) + obj.select_set(True) + return obj diff --git a/src/blenderbim/blenderbim/bim/module/model/handler.py b/src/blenderbim/blenderbim/bim/module/model/handler.py index 353f97d334..d86fdbef3f 100644 --- a/src/blenderbim/blenderbim/bim/module/model/handler.py +++ b/src/blenderbim/blenderbim/bim/module/model/handler.py @@ -1,7 +1,7 @@ import bpy import ifcopenshell import ifcopenshell.api -from blenderbim.bim.module.model import product, wall, slab +from blenderbim.bim.module.model import product, wall, slab, column from blenderbim.bim.ifc import IfcStore from bpy.app.handlers import persistent @@ -45,3 +45,8 @@ def load_post(*args): ifcopenshell.api.add_pre_listener( "type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type ) + + IfcStore.add_element_listener(column.element_listener) + ifcopenshell.api.add_pre_listener( + "geometry.add_representation", "BlenderBIM.DumbColumn.EnsureSolid", column.ensure_solid + ) diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index c809883aef..c7e808c7a0 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -3,7 +3,7 @@ import ifcopenshell import ifcopenshell.api import ifcopenshell.util.element import ifcopenshell.util.representation -from . import wall, slab +from . import wall, slab, column from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.pset.data import Data as PsetData from mathutils import Vector @@ -32,6 +32,10 @@ class AddTypeInstance(bpy.types.Operator): obj = slab.DumbSlabGenerator(self.file.by_id(int(relating_type))).generate() if obj: return {"FINISHED"} + elif ifc_class == "IfcColumnType": + obj = column.DumbColumnGenerator(self.file.by_id(int(relating_type))).generate() + if obj: + return {"FINISHED"} # A cube verts = [ Vector((-1, -1, -1)), diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index 6b3fa26db8..0ef6e43ecb 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -83,6 +83,7 @@ class AssignClass(bpy.types.Operator): predefined_type: bpy.props.StringProperty() userdefined_type: bpy.props.StringProperty() context_id: bpy.props.IntProperty() + should_add_representation: bpy.props.BoolProperty(default=True) ifc_representation_class: bpy.props.StringProperty() def execute(self, context): @@ -112,9 +113,10 @@ class AssignClass(bpy.types.Operator): obj.name = "{}/{}".format(product.is_a(), obj.name) IfcStore.link_element(product, obj) - bpy.ops.bim.add_representation( - obj=obj.name, context_id=self.context_id, ifc_representation_class=self.ifc_representation_class - ) + if self.should_add_representation: + bpy.ops.bim.add_representation( + obj=obj.name, context_id=self.context_id, ifc_representation_class=self.ifc_representation_class + ) if product.is_a("IfcElementType"): self.place_in_types_collection(obj) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py index 0730e27ec1..50eb81d6b2 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py @@ -30,7 +30,9 @@ class Usecase: # IfcExtrudedAreaSolid/IfcCircleProfileDef # IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef # IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids + # IfcExtrudedAreaSolid/IfcMaterialProfileSet "ifc_representation_class": None, # Whether to cast a mesh into a particular class + "profile_set": None, # The material profile set if the extrusion requires it } self.ifc_vertices = [] for key, value in settings.items(): @@ -217,6 +219,8 @@ class Usecase: return self.create_arbitrary_extrusion_representation() elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids": return self.create_arbitrary_void_extrusion_representation() + elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcMaterialProfileSet": + return self.create_material_profile_set_extrusion_representation() return self.create_mesh_representation() def create_camera_block_representation(self): @@ -404,6 +408,31 @@ class Usecase: [item], ) + def create_material_profile_set_extrusion_representation(self): + profile_def = ( + self.settings["profile_set"].CompositeProfile + or self.settings["profile_set"].MaterialProfiles[0].Profile + ) + position = None + if self.file.schema == "IFC2X3": + position = self.file.createIfcAxis2Placement3D( + self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)), + self.file.createIfcDirection((0.0, 0.0, 1.0)), + self.file.createIfcDirection((1.0, 0.0, 0.0)), + ) + item = self.file.createIfcExtrudedAreaSolid( + profile_def, + position, + self.file.createIfcDirection((0.0, 0.0, 1.0)), + self.convert_si_to_unit(self.settings["blender_object"].dimensions[2]), + ) + return self.file.createIfcShapeRepresentation( + self.settings["context"], + self.settings["context"].ContextIdentifier, + "SweptSolid", + [item], + ) + def create_mesh_representation(self): if self.file.schema == "IFC2X3" or self.settings["should_force_faceted_brep"]: return self.create_faceted_brep() diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py b/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py index 2b5a498061..ab74701846 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py @@ -38,7 +38,15 @@ class Usecase: material_set = self.file.create_entity(self.settings["type"]) self.create_material_association(material_set) elif self.settings["type"] == "IfcMaterialProfileSetUsage": - material_set = self.file.create_entity("IfcMaterialProfileSet") + element_type = ifcopenshell.util.element.get_type(self.settings["product"]) + if element_type: + element_type_material = ifcopenshell.util.element.get_material(element_type) + if element_type_material and element_type_material.is_a("IfcMaterialProfileSet"): + material_set = element_type_material + else: + material_set = self.file.create_entity("IfcMaterialProfileSet") + else: + material_set = self.file.create_entity("IfcMaterialProfileSet") material_set_usage = self.create_profile_set_usage(material_set) self.create_material_association(material_set_usage) elif self.settings["type"] == "IfcMaterialList": diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index c1218c9e5b..86af191a42 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -52,7 +52,9 @@ def get_properties(properties): def get_type(element): - if hasattr(element, "IsTypedBy") and element.IsTypedBy: + if element.is_a("IfcTypeObject"): + return element + elif hasattr(element, "IsTypedBy") and element.IsTypedBy: return element.IsTypedBy[0].RelatingType elif hasattr(element, "IsDefinedBy") and element.IsDefinedBy: # IFC2X3 for relationship in element.IsDefinedBy: