From 897d7a6b2d4298331b35d951b7c88b44faf09ec3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 16 Aug 2021 10:41:14 +1000 Subject: [PATCH] Half complete "add window" tool prototype --- .../bim/module/georeference/operator.py | 1 + .../blenderbim/bim/module/model/opening.py | 99 +++++++++++++++---- .../blenderbim/bim/module/model/product.py | 78 +++++++++------ .../blenderbim/bim/module/model/workspace.py | 7 +- .../blenderbim/bim/module/patch/helper.py | 2 + 5 files changed, 134 insertions(+), 53 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/georeference/operator.py b/src/blenderbim/blenderbim/bim/module/georeference/operator.py index 020537c687..571ce07fa4 100644 --- a/src/blenderbim/blenderbim/bim/module/georeference/operator.py +++ b/src/blenderbim/blenderbim/bim/module/georeference/operator.py @@ -128,6 +128,7 @@ class EditGeoreferencing(bpy.types.Operator): def export_attributes(self, attributes, prop): if not prop.is_null and prop.data_type == "string": + # We store our floats as string to prevent single precision data loss attributes[prop.name] = float(prop.string_value) return True diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py index 9c0c482e3c..c7bd2008e7 100644 --- a/src/blenderbim/blenderbim/bim/module/model/opening.py +++ b/src/blenderbim/blenderbim/bim/module/model/opening.py @@ -5,6 +5,7 @@ import ifcopenshell import ifcopenshell.util.representation from blenderbim.bim.ifc import IfcStore from math import pi +from mathutils import Vector from bpy.types import Operator from bpy.props import FloatProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add @@ -56,40 +57,96 @@ class AddElementOpening(bpy.types.Operator): bl_idname = "bim.add_element_opening" bl_label = "Add Element Opening" bl_options = {"REGISTER", "UNDO"} + voided_building_element: bpy.props.StringProperty() + filling_building_element: bpy.props.StringProperty() def execute(self, context): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - selected_objs = context.selected_objects - if len(selected_objs) == 0 or not context.active_object: + voided_obj = self.get_voided_building_element(context) + filling_obj = self.get_filling_building_element(context) + + if not voided_obj: return {"FINISHED"} - obj = context.active_object - if not obj.BIMObjectProperties.ifc_definition_id: - return {"FINISHED"} - element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id) - local_location = obj.matrix_world.inverted() @ context.scene.cursor.location - raycast = obj.closest_point_on_mesh(local_location, distance=0.01) + + element = IfcStore.get_file().by_id(voided_obj.BIMObjectProperties.ifc_definition_id) + local_location = voided_obj.matrix_world.inverted() @ context.scene.cursor.location + raycast = voided_obj.closest_point_on_mesh(local_location, distance=0.01) if not raycast[0]: return {"FINISHED"} - # The opening shall be based on the smallest bounding dimension of the element - dimension = min(obj.dimensions) - bpy.ops.mesh.primitive_cube_add(size=dimension * 2) - opening = context.selected_objects[0] + if filling_obj: + opening = self.generate_opening_from_filling(filling_obj) + else: + # The opening shall be based on the smallest bounding dimension of the element + dimension = min(voided_obj.dimensions) + bpy.ops.mesh.primitive_cube_add(size=dimension * 2) + opening = context.selected_objects[0] - # Place the opening in the middle of the element - global_location = obj.matrix_world @ raycast[1] - normal = raycast[2] - normal.negate() - global_normal = obj.matrix_world.to_quaternion() @ normal - opening.location = global_location + (global_normal * (dimension / 2)) + # Place the opening in the middle of the element + global_location = voided_obj.matrix_world @ raycast[1] + normal = raycast[2] + normal.negate() + global_normal = voided_obj.matrix_world.to_quaternion() @ normal + opening.location = global_location + (global_normal * (dimension / 2)) + opening.rotation_euler = voided_obj.rotation_euler + opening.name = "Opening" - opening.rotation_euler = obj.rotation_euler - opening.name = "Opening" - bpy.ops.bim.add_opening(opening=opening.name, obj=obj.name) + bpy.ops.bim.add_opening(opening=opening.name, obj=voided_obj.name) return {"FINISHED"} + def get_voided_building_element(self, context): + obj = None + if self.voided_building_element: + obj = bpy.data.objects.get(self.voided_building_element) + else: + total_selected = len(context.selected_objects) + if total_selected == 1 or total_selected == 2: + obj = context.active_object + if obj and obj.BIMObjectProperties.ifc_definition_id: + return obj + + def get_filling_building_element(self, context): + obj = None + if self.filling_building_element: + obj = bpy.data.objects.get(self.filling_building_element) + else: + total_selected = len(context.selected_objects) + if total_selected == 2: + if context.selected_objects[0] == context.active_object: + obj = context.selected_objects[1] + else: + obj = context.selected_objects[0] + if obj and obj.BIMObjectProperties.ifc_definition_id: + return obj + + def generate_opening_from_filling(self, filling_obj): + x, y, z = filling_obj.dimensions + verts = [ + Vector((0, 0, 0)), + Vector((0, 0, z)), + Vector((0, y, 0)), + Vector((0, y, z)), + Vector((x, 0, 0)), + Vector((x, 0, z)), + Vector((x, y, 0)), + Vector((x, y, z)), + ] + edges = [] + faces = [ + [0, 1, 3, 2], + [2, 3, 7, 6], + [6, 7, 5, 4], + [4, 5, 1, 0], + [2, 6, 4, 0], + [7, 3, 1, 5], + ] + mesh = bpy.data.meshes.new(name="Opening") + mesh.from_pydata(verts, edges, faces) + obj = bpy.data.objects.new("Opening", mesh) + obj.matrix_world = filling_obj.matrix_world + return obj def add_object(self, context): diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index 0226167c5f..eae81c5cc5 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -51,37 +51,26 @@ class AddTypeInstance(bpy.types.Operator): tprops = context.scene.BIMTypeProperties ifc_class = self.ifc_class or tprops.ifc_class relating_type_id = self.relating_type or tprops.relating_type + if not ifc_class or not relating_type_id: return {"FINISHED"} + self.file = IfcStore.get_file() instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0] relating_type = self.file.by_id(int(relating_type_id)) material = ifcopenshell.util.element.get_material(relating_type) - if material.is_a("IfcMaterialProfileSet"): - obj = profile.DumbProfileGenerator(relating_type).generate() - if obj: + + if material and material.is_a("IfcMaterialProfileSet"): + if profile.DumbProfileGenerator(relating_type).generate(): return {"FINISHED"} - elif material.is_a("IfcMaterialLayerSet"): - layer_set_direction = None - - parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric") - if parametric: - layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction) - if layer_set_direction is None: - if ifc_class in ["IfcSlabType", "IfcRoofType", "IfcRampType", "IfcPlateType"]: - layer_set_direction = "AXIS3" - else: - layer_set_direction = "AXIS2" - - if layer_set_direction == "AXIS3": - obj = slab.DumbSlabGenerator(relating_type).generate() - elif layer_set_direction == "AXIS2": - obj = wall.DumbWallGenerator(relating_type).generate() - else: - obj = None # Dumb block generator? Eh? :) - - if obj: + elif material and material.is_a("IfcMaterialLayerSet"): + if self.generate_layered_element(ifc_class, relating_type): return {"FINISHED"} + + building_obj = None + if len(context.selected_objects) == 1 and context.active_object: + building_obj = context.active_object + # A cube verts = [ Vector((-1, -1, -1)), @@ -95,12 +84,12 @@ class AddTypeInstance(bpy.types.Operator): ] edges = [] faces = [ - [0, 2, 3, 1], + [0, 1, 3, 2], [2, 3, 7, 6], - [4, 5, 7, 6], - [0, 1, 5, 4], - [1, 3, 7, 5], - [0, 2, 6, 4], + [6, 7, 5, 4], + [4, 5, 1, 0], + [2, 6, 4, 0], + [7, 3, 1, 5], ] mesh = bpy.data.meshes.new(name="Instance") mesh.from_pydata(verts, edges, faces) @@ -111,10 +100,39 @@ class AddTypeInstance(bpy.types.Operator): collection_obj = bpy.data.objects.get(collection.name) bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class) bpy.ops.bim.assign_type(relating_type=int(tprops.relating_type), related_object=obj.name) - if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id: - obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box]) + + if building_obj: + if instance_class == "IfcWindow": + # TODO For now we are hardcoding windows as a prototype + bpy.ops.bim.add_element_opening( + voided_building_element=building_obj.name, filling_building_element=obj.name + ) + else: + if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id: + obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box]) return {"FINISHED"} + def generate_layered_element(self, ifc_class, relating_type): + layer_set_direction = None + + parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric") + if parametric: + layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction) + if layer_set_direction is None: + if ifc_class in ["IfcSlabType", "IfcRoofType", "IfcRampType", "IfcPlateType"]: + layer_set_direction = "AXIS3" + else: + layer_set_direction = "AXIS2" + + if layer_set_direction == "AXIS3": + if slab.DumbSlabGenerator(relating_type).generate(): + return True + elif layer_set_direction == "AXIS2": + if wall.DumbWallGenerator(relating_type).generate(): + return True + else: + pass # Dumb block generator? Eh? :) + class AlignProduct(bpy.types.Operator): bl_idname = "bim.align_product" diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index 6bf9b0ac2a..82d633f507 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -17,7 +17,7 @@ class BimTool(WorkSpaceTool): bl_keymap = ( # ("bim.wall_tool_op", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}), # ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": []}), - ("bim.add_type_instance", {"type": "A", "value": "PRESS", "shift": True}, {"properties": []}), + ("bim.hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}), ("bim.hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}), ("bim.join_wall", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("join_type", "L")]}), ("bim.join_wall", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("join_type", "V")]}), @@ -113,6 +113,9 @@ class Hotkey(bpy.types.Operator): getattr(self, f"hotkey_{self.hotkey}")() return {"FINISHED"} + def hotkey_S_A(self): + bpy.ops.bim.add_type_instance() + def hotkey_S_C(self): if self.props.ifc_class == "IfcWallType": bpy.ops.bim.align_wall(align_type="CENTERLINE") @@ -138,7 +141,7 @@ class Hotkey(bpy.types.Operator): bpy.ops.bim.align_product(align_type="NEGATIVE") def hotkey_S_O(self): - bpy.ops.bim.add_element_opening() + bpy.ops.bim.add_element_opening(voided_building_element="", filling_building_element="") def hotkey_A_D(self): bpy.ops.bim.toggle_decomposition_parenting() diff --git a/src/blenderbim/blenderbim/bim/module/patch/helper.py b/src/blenderbim/blenderbim/bim/module/patch/helper.py index f5712065b1..24a11c441d 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/helper.py +++ b/src/blenderbim/blenderbim/bim/module/patch/helper.py @@ -28,6 +28,8 @@ def extract_docs( print(e) except ModuleNotFoundError as e: print(f"Error : IFCPatch {str(submodule)} could not complete because : {str(e)}") + except: + print(f"Error : IFCPatch {str(submodule)} could not load") def _extract_docs(cls, method_name, boilerplate_args): inputs = collections.OrderedDict()