diff --git a/src/blenderbim/blenderbim/bim/module/pset/calc_quantity_function_mapper.py b/src/blenderbim/blenderbim/bim/module/pset/calc_quantity_function_mapper.py index 9220817331..e02e0f8332 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/calc_quantity_function_mapper.py +++ b/src/blenderbim/blenderbim/bim/module/pset/calc_quantity_function_mapper.py @@ -380,13 +380,13 @@ mapper = { 'Qto_WallBaseQuantities' : { 'Length' : "get_length", 'Width' : "get_width", - 'Height' : None, - 'GrossFootPrintArea' : None, - 'NetFootPrintArea' : None, - 'GrossSideArea' : None, - 'NetSideArea' : None, - 'GrossVolume' : None, - 'NetVolume' : None, + 'Height' : "get_height", + 'GrossFootprintArea' : "get_gross_footprint_area", + 'NetFootprintArea' : "get_net_footprint_area", + 'GrossSideArea' : { "function_name" : "get_half_lateral_area", "args" : ", exclude_end_areas = True, subtract_openings = False" }, + 'NetSideArea' : { "function_name" : "get_half_lateral_area", "args" : ", exclude_end_areas = True" }, + 'GrossVolume' : "get_gross_volume", + 'NetVolume' : "get_net_volume", 'GrossWeight' : None, 'NetWeight' : None, }, diff --git a/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py b/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py index 609ede209a..4ce8272c90 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/qto_calculator.py @@ -38,12 +38,22 @@ class QtoCalculator: for key in self.mapping_dict.keys(): for item in self.mapping_dict[key].keys(): if self.mapping_dict[key][item]: - self.mapping_dict[key][item] = eval("self." + self.mapping_dict[key][item]) + if isinstance(self.mapping_dict[key][item], str): + self.mapping_dict[key][item] = eval("self." + self.mapping_dict[key][item]) + if isinstance(self.mapping_dict[key][item], dict): + self.mapping_dict[key][item] = eval("self." + self.mapping_dict[key][item]["function_name"]) else: self.mapping_dict[key][item] = None def calculate_quantity(self, qto_name, quantity_name, obj): - return self.mapping_dict[qto_name][quantity_name](obj) + string = "self.mapping_dict[qto_name][quantity_name](obj" + if isinstance(mapper[qto_name][quantity_name], dict): + args = mapper[qto_name][quantity_name]["args"] + else: + args = "" + string += args + string += ")" + return eval(string) def guess_quantity(self, prop_name, alternative_prop_names, obj): prop_name = prop_name.lower() @@ -186,6 +196,18 @@ class QtoCalculator: area += polygon.area return area + def get_gross_footprint_area(self, o): + """_summary_: Returns the area of the footprint of the object, without related opening and excluding any holes + + :param blender-object o: blender object + :return float: footprint area""" + element = tool.Ifc.get_entity(o) + mesh = self.get_gross_element_mesh(element) + gross_obj = bpy.data.objects.new("GrossObj", mesh) + gross_footprint_area = self.get_net_footprint_area(gross_obj) + self.delete_obj(gross_obj) + return gross_footprint_area + def get_net_roofprint_area(self, o): # Is roofprint the right word? Couldn't think of anything better - vulevukusej """_summary_: Returns the area of the net roofprint of the object, excluding any holes @@ -228,7 +250,9 @@ class QtoCalculator: def get_net_volume(self, o): o_mesh = bmesh.new() o_mesh.from_mesh(o.data) - return o_mesh.calc_volume() + volume = o_mesh.calc_volume() + self.delete_bmesh(o_mesh) + return volume def get_gross_volume(self, o): element = tool.Ifc.get_entity(o) @@ -236,40 +260,13 @@ class QtoCalculator: print(f"Object {o.name} hasn't an IFC instance so gross volume is equal to net volume") return self.get_net_volume - settings = ifcopenshell.geom.settings() - settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, True) - shape = ifcopenshell.geom.create_shape(settings, element) - faces = shape.geometry.faces - verts = shape.geometry.verts - - mesh = bpy.data.meshes.new("myBeautifulMesh") - - num_vertices = len(verts) // 3 - total_faces = len(faces) - loop_start = range(0, total_faces, 3) - num_loops = total_faces // 3 - loop_total = [3] * num_loops - num_vertex_indices = len(faces) - - mesh.vertices.add(num_vertices) - mesh.vertices.foreach_set("co", verts) - mesh.loops.add(num_vertex_indices) - mesh.loops.foreach_set("vertex_index", faces) - mesh.polygons.add(num_loops) - mesh.polygons.foreach_set("loop_start", loop_start) - mesh.polygons.foreach_set("loop_total", loop_total) - mesh.update() - - # An alternative to foreach is - # mesh.from_pydata(grouped_verts, edges, grouped_faces) - # look at import_ifc.py in blenderbim - - bm = bmesh.new() - bm.from_mesh(mesh) + mesh = self.get_gross_element_mesh(element) + bm = self.get_bmesh_from_mesh(mesh) gross_volume = bm.calc_volume() - bm.free() + self.delete_bmesh(bm) + self.delete_mesh(mesh) return gross_volume @@ -340,7 +337,11 @@ class QtoCalculator: for opening in openings: opening_id = opening.RelatedOpeningElement.GlobalId ifc_opening_element = ifc.by_guid(opening_id) - bl_opening_obj = tool.Ifc.get_object(ifc_opening_element) + #bl_opening_obj = tool.Ifc.get_object(ifc_opening_element) + #mesh = bpy.data.meshes.new('myMesh') + mesh = self.get_gross_element_mesh(ifc_opening_element) + + bl_opening_obj = bpy.data.objects.new("MyObject", mesh) opening_type = ( ifc_opening_element.PredefinedType @@ -350,12 +351,19 @@ class QtoCalculator: if ignore_recesses and opening_type == "RECESS": continue + + bl_OBB_opening_object = self.get_OBB_object(bl_opening_obj) opening_area = self.get_lateral_area( - self.get_OBB_object(bl_opening_obj), angle_z1=angle_z1, angle_z2=angle_z2, exclude_end_areas=True + #self.get_OBB_object(bl_opening_obj), angle_z1=angle_z1, angle_z2=angle_z2, exclude_end_areas=True + bl_OBB_opening_object, angle_z1=angle_z1, angle_z2=angle_z2, exclude_end_areas=True ) if opening_area >= min_area: total_opening_area += opening_area + self.delete_obj(bl_opening_obj) + self.delete_mesh(mesh) + self.delete_obj(bl_OBB_opening_object) + return total_opening_area def get_lateral_area( @@ -401,6 +409,26 @@ class QtoCalculator: area += polygon.area return area + total_opening_area + def get_half_lateral_area( + self, + obj, + subtract_openings: bool = True, + exclude_end_areas: bool = False, + exclude_side_areas: bool = False, + angle_z1: int = 45, + angle_z2: int = 135, + ): + """_summary_: + :param blender-object obj: blender object, bpy.types.Object + :param bool subtract_openings: Toggle whether opening-areas should be subtracted, defaults to True + :param bool exclude_end_areas: , defaults to False + :param bool exclude_side_areas: , defaults to False + :param int angle_z1: Angle measured from the positive z-axis to the normal-vector of the area. Openings with a normal_vector lower than this value will be ignored, defaults to 45 + :param int angle_z2: Angle measured from the positive z-axis to the normal-vector of the area. Openings with a normal_vector greater than this value will be ignored, defaults to 135 + :return float: Lateral Area / 2 + """ + return self.get_lateral_area(obj, subtract_openings, exclude_end_areas, exclude_side_areas, angle_z1, angle_z2)/2 + def get_gross_top_area(self, obj, angle: int = 45): """_summary_: Returns the gross top area of the object. @@ -783,6 +811,54 @@ class QtoCalculator: polygon_tuples.append((x, y)) return Polygon(polygon_tuples) + def get_gross_element_mesh(self, element): + settings = ifcopenshell.geom.settings() + settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, True) + shape = ifcopenshell.geom.create_shape(settings, element) + faces = shape.geometry.faces + verts = shape.geometry.verts + + mesh = bpy.data.meshes.new("myBeautifulMesh") + + num_vertices = len(verts) // 3 + total_faces = len(faces) + loop_start = range(0, total_faces, 3) + num_loops = total_faces // 3 + loop_total = [3] * num_loops + num_vertex_indices = len(faces) + + mesh.vertices.add(num_vertices) + mesh.vertices.foreach_set("co", verts) + mesh.loops.add(num_vertex_indices) + mesh.loops.foreach_set("vertex_index", faces) + mesh.polygons.add(num_loops) + mesh.polygons.foreach_set("loop_start", loop_start) + mesh.polygons.foreach_set("loop_total", loop_total) + mesh.update() + + # An alternative to foreach is + # mesh.from_pydata(grouped_verts, edges, grouped_faces) + # look at import_ifc.py in blenderbim + + return mesh + + def get_bmesh_from_mesh(self, mesh): + bm = bmesh.new() + bm.from_mesh(mesh) + #bm.to_mesh(mesh) + return bm + + def delete_mesh(self, mesh): + mesh.user_clear() + bpy.data.meshes.remove(mesh) + + def delete_bmesh(self, bm): + bmesh.ops.delete(bm) + bm.free() + + def delete_obj(self, obj): + bpy.data.objects.remove(obj, do_unlink = True) + # # Following code is here temporarily to test newly created functions: #qto = QtoCalculator()