This commit is contained in:
Andrej730
2024-03-26 12:36:13 +05:00
parent b15e33e5e9
commit 06d823b9d8
7 changed files with 173 additions and 120 deletions
@@ -25,8 +25,16 @@ from shapely.geometry import Polygon
from shapely.ops import unary_union from shapely.ops import unary_union
import blenderbim.tool as tool import blenderbim.tool as tool
import ifcopenshell import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.util.element
from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper
import blenderbim.bim import blenderbim.bim
from typing import Literal, Union, Optional
AxisType = Literal["x", "y", "z"]
VectorTuple = tuple[float, float, float]
QuanityTypes = Literal["Q_LENGTH", "Q_AREA", "Q_VOLUME"]
class QtoCalculator: class QtoCalculator:
@@ -45,7 +53,7 @@ class QtoCalculator:
else: else:
self.mapping_dict[key][item] = None self.mapping_dict[key][item] = None
def calculate_quantity(self, qto_name, quantity_name, obj): def calculate_quantity(self, qto_name: str, quantity_name: str, obj: bpy.types.Object) -> float:
"""calculates the value of the quantity in the project units""" """calculates the value of the quantity in the project units"""
string = "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): if isinstance(mapper[qto_name][quantity_name], dict):
@@ -54,11 +62,13 @@ class QtoCalculator:
args = "" args = ""
string += args string += args
string += ")" string += ")"
value = eval(string) value: float = eval(string)
return tool.Qto.convert_to_project_units(value, qto_name, quantity_name) or value return tool.Qto.convert_to_project_units(value, qto_name, quantity_name) or value
def guess_quantity(self, prop_name, alternative_prop_names, obj): def guess_quantity(
self, prop_name: str, alternative_prop_names: list[str], obj: bpy.types.Object
) -> Union[float, None]:
"""guess the value of the quantity by name, returns the value in the project units""" """guess the value of the quantity by name, returns the value in the project units"""
prop_name = prop_name.lower() prop_name = prop_name.lower()
alternative_prop_names = [p.lower() for p in alternative_prop_names] alternative_prop_names = [p.lower() for p in alternative_prop_names]
@@ -89,7 +99,7 @@ class QtoCalculator:
if value is None: if value is None:
return return
unit_type_keywords = { unit_type_keywords: dict[str, QuanityTypes] = {
"length": "Q_LENGTH", "length": "Q_LENGTH",
"width": "Q_LENGTH", "width": "Q_LENGTH",
"height": "Q_LENGTH", "height": "Q_LENGTH",
@@ -102,10 +112,10 @@ class QtoCalculator:
unit_type = next(unit_type_keywords[k] for k in unit_type_keywords if k in prop_name) unit_type = next(unit_type_keywords[k] for k in unit_type_keywords if k in prop_name)
return tool.Qto.convert_to_project_units(value, quantity_type=unit_type) or value return tool.Qto.convert_to_project_units(value, quantity_type=unit_type) or value
def get_units(self, o, vg_index): def get_units(self, o: bpy.types.Object, vg_index: int) -> int:
return len([v for v in o.data.vertices if vg_index in [g.group for g in v.groups]]) return len([v for v in o.data.vertices if vg_index in [g.group for g in v.groups]])
def get_linear_length(self, o): def get_linear_length(self, o: bpy.types.Object) -> float:
"""_summary_: Returns the length of the longest edge of the object bounding box """_summary_: Returns the length of the longest edge of the object bounding box
:param blender-object o: Blender Object :param blender-object o: Blender Object
@@ -116,7 +126,7 @@ class QtoCalculator:
z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
return max(x, y, z) return max(x, y, z)
def get_length(self, o, vg_index=None, main_axis: str = ""): def get_length(self, o: bpy.types.Object, vg_index: Optional[int] = None, main_axis: str = "") -> float:
if vg_index is None: if vg_index is None:
x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
@@ -141,23 +151,23 @@ class QtoCalculator:
length += self.get_edge_distance(o, e) length += self.get_edge_distance(o, e)
return length return length
def get_stair_length(self, obj): def get_stair_length(self, obj: bpy.types.Object) -> float:
length = self.get_length(obj) length = self.get_length(obj)
height = self.get_height(obj) height = self.get_height(obj)
stair_length = math.sqrt(pow(length, 2) + pow(height, 2)) stair_length = math.sqrt(pow(length, 2) + pow(height, 2))
return stair_length return stair_length
def get_net_stair_area(self, obj): def get_net_stair_area(self, obj: bpy.types.Object) -> float:
OBB_obj = self.get_OBB_object(obj) OBB_obj = self.get_OBB_object(obj)
OBB_net_footprint_area = self.get_net_footprint_area(OBB_obj) OBB_net_footprint_area = self.get_net_footprint_area(OBB_obj)
return OBB_net_footprint_area return OBB_net_footprint_area
def get_gross_stair_area(self, obj): def get_gross_stair_area(self, obj: bpy.types.Object) -> float:
OBB_obj = self.get_OBB_object(obj) OBB_obj = self.get_OBB_object(obj)
OBB_gross_footprint_area = self.get_gross_footprint_area(OBB_obj) OBB_gross_footprint_area = self.get_gross_footprint_area(OBB_obj)
return OBB_gross_footprint_area return OBB_gross_footprint_area
def get_parametric_axis(self, obj): def get_parametric_axis(self, obj: bpy.types.Object) -> Literal["AXIS2", "AXIS3", None]:
relating_type = ifcopenshell.util.element.get_type(tool.Ifc.get_entity(obj)) relating_type = ifcopenshell.util.element.get_type(tool.Ifc.get_entity(obj))
if relating_type: if relating_type:
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric") parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
@@ -172,7 +182,7 @@ class QtoCalculator:
return None return None
return None return None
def get_covering_gross_area(self, obj): def get_covering_gross_area(self, obj: bpy.types.Object) -> float:
get_parametric_axis = self.get_parametric_axis(obj) get_parametric_axis = self.get_parametric_axis(obj)
if not get_parametric_axis: if not get_parametric_axis:
return self.get_gross_footprint_area(obj) return self.get_gross_footprint_area(obj)
@@ -181,7 +191,7 @@ class QtoCalculator:
elif get_parametric_axis == "AXIS3": elif get_parametric_axis == "AXIS3":
return self.get_gross_footprint_area(obj) return self.get_gross_footprint_area(obj)
def get_covering_net_area(self, obj): def get_covering_net_area(self, obj: bpy.types.Object) -> float:
get_parametric_axis = self.get_parametric_axis(obj) get_parametric_axis = self.get_parametric_axis(obj)
if not get_parametric_axis: if not get_parametric_axis:
return self.get_net_footprint_area(obj) return self.get_net_footprint_area(obj)
@@ -190,7 +200,7 @@ class QtoCalculator:
elif get_parametric_axis == "AXIS3": elif get_parametric_axis == "AXIS3":
return self.get_net_footprint_area(obj) return self.get_net_footprint_area(obj)
def get_covering_width(self, obj): def get_covering_width(self, obj: bpy.types.Object) -> float:
get_parametric_axis = self.get_parametric_axis(obj) get_parametric_axis = self.get_parametric_axis(obj)
if not get_parametric_axis: if not get_parametric_axis:
return self.get_height(obj) return self.get_height(obj)
@@ -199,7 +209,7 @@ class QtoCalculator:
elif get_parametric_axis == "AXIS3": elif get_parametric_axis == "AXIS3":
return self.get_height(obj) return self.get_height(obj)
def get_width(self, o): def get_width(self, o: bpy.types.Object) -> float:
"""_summary_: Returns the width of the object bounding box """_summary_: Returns the width of the object bounding box
:param blender-object o: blender object :param blender-object o: blender object
@@ -209,7 +219,7 @@ class QtoCalculator:
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
return min(x, y) return min(x, y)
def get_height(self, o): def get_height(self, o: bpy.types.Object) -> float:
"""_summary_: Returns the height of the object bounding box """_summary_: Returns the height of the object bounding box
:param blender-object o: blender object :param blender-object o: blender object
@@ -217,32 +227,32 @@ class QtoCalculator:
""" """
return (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length return (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
def get_opening_height(self, obj): def get_opening_height(self, obj: bpy.types.Object) -> float:
if self.is_opening_horizontal(obj): if self.is_opening_horizontal(obj):
return self.get_width(obj) return self.get_width(obj)
else: else:
return self.get_height(obj) return self.get_height(obj)
def get_opening_depth(self, obj): def get_opening_depth(self, obj: bpy.types.Object) -> float:
if self.is_opening_horizontal(obj): if self.is_opening_horizontal(obj):
return self.get_height(obj) return self.get_height(obj)
else: else:
return self.get_width(obj) return self.get_width(obj)
def get_opening_mapping_area(self, obj): def get_opening_mapping_area(self, obj: bpy.types.Object) -> float:
if self.is_opening_horizontal(obj): if self.is_opening_horizontal(obj):
return self.get_net_footprint_area(obj) return self.get_net_footprint_area(obj)
else: else:
return self.get_net_side_area(obj) return self.get_net_side_area(obj)
def get_finish_ceiling_height(self, obj): def get_finish_ceiling_height(self, obj: bpy.types.Object) -> float:
space_height = self.get_height(obj) space_height = self.get_height(obj)
floor_height = self.get_finish_floor_height(obj) floor_height = self.get_finish_floor_height(obj)
ceiling_height = self.get_ceiling_height(obj) ceiling_height = self.get_ceiling_height(obj)
finish_ceiling_height = space_height - floor_height - ceiling_height finish_ceiling_height = space_height - floor_height - ceiling_height
return finish_ceiling_height return finish_ceiling_height
def get_finish_floor_height(self, obj): def get_finish_floor_height(self, obj: bpy.types.Object) -> float:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
decompositions = ifcopenshell.util.element.get_decomposition(element) decompositions = ifcopenshell.util.element.get_decomposition(element)
finish_floor_height = 0 finish_floor_height = 0
@@ -258,7 +268,7 @@ class QtoCalculator:
return finish_floor_height return finish_floor_height
def get_ceiling_height(self, obj): def get_ceiling_height(self, obj: bpy.types.Object) -> float:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
decompositions = ifcopenshell.util.element.get_decomposition(element) decompositions = ifcopenshell.util.element.get_decomposition(element)
finish_ceiling_height = 0 finish_ceiling_height = 0
@@ -274,7 +284,7 @@ class QtoCalculator:
return finish_ceiling_height return finish_ceiling_height
def get_net_perimeter(self, o): def get_net_perimeter(self, o: bpy.types.Object) -> float:
parsed_edges = [] parsed_edges = []
shared_edges = [] shared_edges = []
perimeter = 0 perimeter = 0
@@ -289,7 +299,7 @@ class QtoCalculator:
perimeter -= self.get_edge_key_distance(o, edge_key) perimeter -= self.get_edge_key_distance(o, edge_key)
return perimeter return perimeter
def get_gross_perimeter(self, o): def get_gross_perimeter(self, o: bpy.types.Object) -> float:
element = tool.Ifc.get_entity(o) element = tool.Ifc.get_entity(o)
mesh = self.get_gross_element_mesh(element) mesh = self.get_gross_element_mesh(element)
gross_obj = bpy.data.objects.new("GrossObj", mesh) gross_obj = bpy.data.objects.new("GrossObj", mesh)
@@ -297,15 +307,15 @@ class QtoCalculator:
self.delete_obj(gross_obj) self.delete_obj(gross_obj)
return gross_perimeter return gross_perimeter
def get_space_net_perimeter(self, obj): def get_space_net_perimeter(self, obj: bpy.types.Object) -> float:
pass pass
def get_rectangular_perimeter(self, obj): def get_rectangular_perimeter(self, obj: bpy.types.Object) -> float:
length = self.get_length(obj, main_axis="x") length = self.get_length(obj, main_axis="x")
height = self.get_height(obj) height = self.get_height(obj)
return (length + height) * 2 return (length + height) * 2
def get_lowest_polygons(self, o): def get_lowest_polygons(self, o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
lowest_polygons = [] lowest_polygons = []
lowest_z = None lowest_z = None
for polygon in o.data.polygons: for polygon in o.data.polygons:
@@ -321,7 +331,7 @@ class QtoCalculator:
lowest_z = z lowest_z = z
return lowest_polygons return lowest_polygons
def get_highest_polygons(self, o): def get_highest_polygons(self, o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
highest_polygons = [] highest_polygons = []
highest_z = None highest_z = None
for polygon in o.data.polygons: for polygon in o.data.polygons:
@@ -337,13 +347,13 @@ class QtoCalculator:
highest_z = z highest_z = z
return highest_polygons return highest_polygons
def get_edge_key_distance(self, obj, edge_key): def get_edge_key_distance(self, obj: bpy.types.Object, edge_key: tuple[int, int]) -> float:
return (obj.data.vertices[edge_key[1]].co - obj.data.vertices[edge_key[0]].co).length return (obj.data.vertices[edge_key[1]].co - obj.data.vertices[edge_key[0]].co).length
def get_edge_distance(self, obj, edge): def get_edge_distance(self, obj: bpy.types.Object, edge: bpy.types.MeshEdge) -> float:
return (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length return (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length
def get_net_floor_area(self, obj): def get_net_floor_area(self, obj: bpy.types.Object) -> float:
decompositions = self.get_obj_decompositions(obj) decompositions = self.get_obj_decompositions(obj)
if not decompositions: if not decompositions:
return self.get_gross_footprint_area(obj) return self.get_gross_footprint_area(obj)
@@ -359,7 +369,7 @@ class QtoCalculator:
return total_net_floor_area return total_net_floor_area
def get_gross_ceiling_area(self, obj): def get_gross_ceiling_area(self, obj: bpy.types.Object) -> float:
decompositions = self.get_obj_decompositions(obj) decompositions = self.get_obj_decompositions(obj)
if not decompositions: if not decompositions:
return self.get_gross_top_area(obj) return self.get_gross_top_area(obj)
@@ -375,7 +385,7 @@ class QtoCalculator:
return total_gross_ceiling_area return total_gross_ceiling_area
def get_net_ceiling_area(self, obj): def get_net_ceiling_area(self, obj: bpy.types.Object) -> float:
decompositions = self.get_obj_decompositions(obj) decompositions = self.get_obj_decompositions(obj)
if not decompositions: if not decompositions:
return self.get_net_top_area(obj) return self.get_net_top_area(obj)
@@ -395,7 +405,7 @@ class QtoCalculator:
return total_net_ceiling_area return total_net_ceiling_area
def get_space_net_volume(self, obj): def get_space_net_volume(self, obj: bpy.types.Object) -> float:
decompositions = self.get_obj_decompositions(obj) decompositions = self.get_obj_decompositions(obj)
if not decompositions: if not decompositions:
return self.get_gross_volume(obj) return self.get_gross_volume(obj)
@@ -410,7 +420,7 @@ class QtoCalculator:
return total_space_net_volume return total_space_net_volume
def get_net_footprint_area(self, o): def get_net_footprint_area(self, o: bpy.types.Object) -> float:
"""_summary_: Returns the area of the footprint of the object, excluding any holes """_summary_: Returns the area of the footprint of the object, excluding any holes
:param blender-object o: blender object :param blender-object o: blender object
@@ -421,7 +431,7 @@ class QtoCalculator:
area += polygon.area area += polygon.area
return area return area
def get_gross_footprint_area(self, o): def get_gross_footprint_area(self, o: bpy.types.Object) -> float:
"""_summary_: Returns the area of the footprint of the object, without related opening and excluding any holes """_summary_: Returns the area of the footprint of the object, without related opening and excluding any holes
:param blender-object o: blender object :param blender-object o: blender object
@@ -437,7 +447,7 @@ class QtoCalculator:
self.delete_mesh(mesh) self.delete_mesh(mesh)
return gross_footprint_area return gross_footprint_area
def get_net_roofprint_area(self, o): def get_net_roofprint_area(self, o: bpy.types.Object) -> float:
# Is roofprint the right word? Couldn't think of anything better - vulevukusej # 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 """_summary_: Returns the area of the net roofprint of the object, excluding any holes
@@ -449,7 +459,7 @@ class QtoCalculator:
area += polygon.area area += polygon.area
return area return area
def get_side_area(self, o): def get_side_area(self, o: bpy.types.Object) -> float:
# There are a few dumb options for this, but this seems the dumbest # There are a few dumb options for this, but this seems the dumbest
# until I get more practical experience on what works best. # until I get more practical experience on what works best.
x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
@@ -457,8 +467,7 @@ class QtoCalculator:
z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
return max(x * z, y * z) return max(x * z, y * z)
def get_cross_section_area(self, obj): def get_cross_section_area(self, obj: bpy.types.Object) -> float:
element = tool.Ifc.get_entity(obj)
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
item = representation.Items[0] item = representation.Items[0]
while True: while True:
@@ -474,7 +483,7 @@ class QtoCalculator:
return area return area
# TODO handle other types of sections, and then fall back to mesh parsing # TODO handle other types of sections, and then fall back to mesh parsing
def get_gross_surface_area(self, o, vg_index=None): def get_gross_surface_area(self, o: bpy.types.Object, vg_index: Optional[int] = None) -> float:
if vg_index is None: if vg_index is None:
if not self.has_openings(o): if not self.has_openings(o):
return self.get_net_surface_area(o) return self.get_net_surface_area(o)
@@ -492,29 +501,29 @@ class QtoCalculator:
area += polygon.area area += polygon.area
return area return area
def get_net_surface_area(self, obj): def get_net_surface_area(self, obj: bpy.types.Object) -> float:
return self.get_mesh_area(obj.data) return self.get_mesh_area(obj.data)
def get_mesh_area(self, mesh): def get_mesh_area(self, mesh: bpy.types.Mesh) -> float:
area = 0 area = 0
for polygon in mesh.polygons: for polygon in mesh.polygons:
area += polygon.area area += polygon.area
return area return area
def is_polygon_in_vg(self, polygon, vertices_in_vg): def is_polygon_in_vg(self, polygon: bpy.types.MeshPolygon, vertices_in_vg: list[bpy.types.MeshVertex]) -> bool:
for v in polygon.vertices: for v in polygon.vertices:
if v not in vertices_in_vg: if v not in vertices_in_vg:
return False return False
return True return True
def get_net_volume(self, o): def get_net_volume(self, o: bpy.types.Object) -> float:
o_mesh = bmesh.new() o_mesh = bmesh.new()
o_mesh.from_mesh(o.data) o_mesh.from_mesh(o.data)
volume = o_mesh.calc_volume() volume = o_mesh.calc_volume()
o_mesh.free() o_mesh.free()
return volume return volume
def get_gross_volume(self, o): def get_gross_volume(self, o: bpy.types.Object) -> float:
if not self.has_openings(o): if not self.has_openings(o):
return self.get_net_volume(o) return self.get_net_volume(o)
@@ -529,16 +538,18 @@ class QtoCalculator:
return gross_volume return gross_volume
def has_openings(self, obj): def has_openings(
self, obj: bpy.types.Object
) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
return element and getattr(element, "HasOpenings", []) return element and getattr(element, "HasOpenings", [])
def get_obj_decompositions(self, obj): def get_obj_decompositions(self, obj: bpy.types.Object) -> list[ifcopenshell.entity_instance]:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
decompositions = ifcopenshell.util.element.get_decomposition(element) decompositions = ifcopenshell.util.element.get_decomposition(element)
return decompositions return decompositions
def get_gross_weight(self, obj): def get_gross_weight(self, obj: bpy.types.Object) -> Union[float, None]:
obj_mass_density = self.get_obj_mass_density(obj) obj_mass_density = self.get_obj_mass_density(obj)
if not obj_mass_density: if not obj_mass_density:
return return
@@ -546,7 +557,7 @@ class QtoCalculator:
gross_weight = obj_mass_density * gross_volume gross_weight = obj_mass_density * gross_volume
return gross_weight return gross_weight
def get_net_weight(self, obj): def get_net_weight(self, obj: bpy.types.Object) -> Union[float, None]:
obj_mass_density = self.get_obj_mass_density(obj) obj_mass_density = self.get_obj_mass_density(obj)
if not obj_mass_density: if not obj_mass_density:
return return
@@ -554,7 +565,7 @@ class QtoCalculator:
net_weight = obj_mass_density * net_volume net_weight = obj_mass_density * net_volume
return net_weight return net_weight
def get_obj_mass_density(self, obj): def get_obj_mass_density(self, obj: bpy.types.Object) -> Union[float, None]:
entity = tool.Ifc.get_entity(obj) entity = tool.Ifc.get_entity(obj)
material = ifcopenshell.util.element.get_material(entity) material = ifcopenshell.util.element.get_material(entity)
if material is None: if material is None:
@@ -629,7 +640,7 @@ class QtoCalculator:
# volume += v1.dot(v2.cross(v3)) / 6.0 # volume += v1.dot(v2.cross(v3)) / 6.0
# return volume # return volume
def get_opening_type(self, opening, obj): def get_opening_type(self, opening: bpy.types.Object, obj: bpy.types.Object) -> Literal["OPENING", "RECESS"]:
"""_summary_: Returns the opening type - OPENING / RECESS """_summary_: Returns the opening type - OPENING / RECESS
:param blender-object opening: blender opening object :param blender-object opening: blender opening object
@@ -649,14 +660,22 @@ class QtoCalculator:
return "OPENING" if ray_intersections % 2 == 0 else "RECESS" return "OPENING" if ray_intersections % 2 == 0 else "RECESS"
def get_opening_area( def get_opening_area(
self, obj, angle_z1: int = 45, angle_z2: int = 135, min_area: int = 0, ignore_recesses: bool = False self,
): obj: bpy.types.Object,
angle_z1: int = 45,
angle_z2: int = 135,
min_area: int = 0,
ignore_recesses: bool = False,
) -> float:
"""_summary_: Returns the lateral area of the openings in the object. """_summary_: Returns the lateral area of the openings in the object.
:param obj: blender object :param obj: blender object
:param int angle_z1: Angle measured from the positive z-axis to the normal-vector of the opening area. Openings with a normal_vector lower than this value will be ignored, defaults to 45 :param int angle_z1: Angle measured from the positive z-axis to the normal-vector of the opening area.
:param int angle_z2: Angle measured from the positive z-axis to the normal-vector of the opening area. Openings with a normal_vector greater than this value will be ignored,defaults to 135 Openings with a normal_vector lower than this value will be ignored, defaults to 45
:param float min_area: Minimum opening area to consider. Values lower than this will be ignored, defaults to 0 :param int angle_z2: Angle measured from the positive z-axis to the normal-vector of the opening area.
Openings with a normal_vector greater than this value will be ignored,defaults to 135
:param float min_area: Minimum opening area to consider. Values lower than this will be ignored,
defaults to 0
:param bool ignore_recesses: Toggle whether recess areas should be considered, defaults to False :param bool ignore_recesses: Toggle whether recess areas should be considered, defaults to False
:return float: Opening Area :return float: Opening Area
""" """
@@ -702,14 +721,14 @@ class QtoCalculator:
def get_lateral_area( def get_lateral_area(
self, self,
obj, obj: bpy.types.Object,
subtract_openings: bool = True, subtract_openings: bool = True,
exclude_end_areas: bool = False, exclude_end_areas: bool = False,
exclude_side_areas: bool = False, exclude_side_areas: bool = False,
angle_z1: int = 45, angle_z1: int = 45,
angle_z2: int = 135, angle_z2: int = 135,
main_axis: str = "", main_axis: str = "",
): ) -> float:
"""_summary_ """_summary_
:param blender-object obj: blender object, bpy.types.Object :param blender-object obj: blender object, bpy.types.Object
@@ -760,7 +779,7 @@ class QtoCalculator:
area += polygon.area area += polygon.area
return area + total_opening_area return area + total_opening_area
def get_gross_side_area(self, obj): def get_gross_side_area(self, obj: bpy.types.Object) -> float:
if not self.has_openings(obj): if not self.has_openings(obj):
return self.get_net_side_area(obj) return self.get_net_side_area(obj)
@@ -768,15 +787,15 @@ class QtoCalculator:
return gross_side_area return gross_side_area
def get_net_side_area(self, obj): def get_net_side_area(self, obj: bpy.types.Object) -> float:
net_side_area = self.get_lateral_area(obj, exclude_end_areas=True, main_axis="x") / 2 net_side_area = self.get_lateral_area(obj, exclude_end_areas=True, main_axis="x") / 2
return net_side_area return net_side_area
def get_outer_surface_area(self, obj): def get_outer_surface_area(self, obj: bpy.types.Object) -> float:
outer_surface_area = self.get_lateral_area(obj, exclude_end_areas=True, angle_z1=0, angle_z2=360) outer_surface_area = self.get_lateral_area(obj, exclude_end_areas=True, angle_z1=0, angle_z2=360)
return outer_surface_area return outer_surface_area
def get_end_area(self, obj): def get_end_area(self, obj: bpy.types.Object) -> float:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
gross_mesh = self.get_gross_element_mesh(element) gross_mesh = self.get_gross_element_mesh(element)
gross_obj = bpy.data.objects.new("MyObject", gross_mesh) gross_obj = bpy.data.objects.new("MyObject", gross_mesh)
@@ -790,7 +809,7 @@ class QtoCalculator:
return end_area return end_area
def get_gross_top_area(self, obj, angle: int = 45): def get_gross_top_area(self, obj: bpy.types.Object, angle: int = 45) -> float:
"""_summary_: Returns the gross top area of the object. """_summary_: Returns the gross top area of the object.
:param blender-object obj: blender object :param blender-object obj: blender object
@@ -827,12 +846,14 @@ class QtoCalculator:
return area + opening_area return area + opening_area
# curently net top area is larger then projected area, because its taking into account internal polygons, or window sills # curently net top area is larger then projected area, because its taking into account internal polygons, or window sills
def get_net_top_area(self, obj, angle: int = 45, ignore_internal: bool = True): def get_net_top_area(self, obj: bpy.types.Object, angle: int = 45, ignore_internal: bool = True) -> float:
"""_summary_: Returns the net top area of the object. """_summary_: Returns the net top area of the object.
:param blender-object obj: blender object :param blender-object obj: blender object
:param int angle: Angle measured from the positive z-axis to the normal-vector of the area. Values lower than this will be ignored, defaults to 45 :param int angle: Angle measured from the positive z-axis to the normal-vector of the area.
:param bool ignore_internal: Toggle whether internal areas should be subtracted (Like window sills), defaults to True Values lower than this will be ignored, defaults to 45
:param bool ignore_internal: Toggle whether internal areas should be subtracted (Like window sills),
defaults to True
:return float: Net Top Area :return float: Net Top Area
""" """
z_axis = (0, 0, 1) z_axis = (0, 0, 1)
@@ -852,11 +873,11 @@ class QtoCalculator:
return area return area
def get_projected_area(self, obj, projection_axis: str = "z", is_gross: bool = True): def get_projected_area(self, obj, projection_axis: AxisType = "z", is_gross: bool = True) -> float:
"""_summary_: Returns the projected area of the object. """_summary_: Returns the projected area of the object.
:param blender-object obj: blender object :param blender-object obj: blender object
:param str projection_axis: Axis to project the area onto. Can be "X", "Y" or "Z" :param str projection_axis: Axis to project the area onto. Can be "x", "y" or "z"
:param bool is_gross: if True, the projected area will include openings, if False, the projected area will exclude openings :param bool is_gross: if True, the projected area will include openings, if False, the projected area will exclude openings
:return float: Projected Area :return float: Projected Area
""" """
@@ -891,7 +912,7 @@ class QtoCalculator:
return projected_polygon.area + void_area return projected_polygon.area + void_area
return projected_polygon.area return projected_polygon.area
def get_OBB_object(self, obj): def get_OBB_object(self, obj: bpy.types.Object) -> bpy.types.Object:
"""_summary_: Returns the Oriented-Bounding-Box (OBB) of the object. """_summary_: Returns the Oriented-Bounding-Box (OBB) of the object.
:param blender-object obj: Blender Object :param blender-object obj: Blender Object
@@ -932,7 +953,7 @@ class QtoCalculator:
return new_OBB_object return new_OBB_object
def get_AABB_object(self, obj): def get_AABB_object(self, obj: bpy.types.Object) -> bpy.types.Object:
"""_summary_: Returns the Axis-Aligned-Bounding-Box (AABB) of the object. """_summary_: Returns the Axis-Aligned-Bounding-Box (AABB) of the object.
:param blender-object obj: Blender Object :param blender-object obj: Blender Object
@@ -988,12 +1009,12 @@ class QtoCalculator:
def get_bisected_obj( def get_bisected_obj(
self, self,
obj, obj: bpy.types.Object,
plane_co_pos, plane_co_pos: VectorTuple,
plane_no_pos, plane_no_pos: VectorTuple,
plane_co_neg, plane_co_neg: VectorTuple,
plane_no_neg, plane_no_neg: VectorTuple,
): ) -> bpy.types.Object:
"""_summary_: Returns the object bisected by two planes. """_summary_: Returns the object bisected by two planes.
:param blender-object obj: Blender Object :param blender-object obj: Blender Object
@@ -1031,11 +1052,12 @@ class QtoCalculator:
return bis_obj return bis_obj
def get_total_contact_area(self, obj, class_filter: str = ["IfcElement"]): def get_total_contact_area(self, obj: bpy.types.Object, class_filter: list[str] = ["IfcElement"]) -> float:
"""_summary_: Returns the total contact area of the object with other objects. """_summary_: Returns the total contact area of the object with other objects.
:param blender-object obj: Blender Object :param blender-object obj: Blender Object
:param list [] class_filter: A list of classes used to filter the objects to be considered for the calculation. Example: ["IfcWall"] or ["IfcWall", "IfcSlab"] :param list [] class_filter: A list of classes used to filter the objects
to be considered for the calculation. Example: ["IfcWall"] or ["IfcWall", "IfcSlab"]
:return float: Total contact area of the object with other objects. :return float: Total contact area of the object with other objects.
""" """
total_contact_area = 0 total_contact_area = 0
@@ -1046,11 +1068,12 @@ class QtoCalculator:
return total_contact_area return total_contact_area
def get_touching_objects(self, obj, class_filter): def get_touching_objects(self, obj: bpy.types.Object, class_filter: list[str]) -> list[bpy.types.Object]:
"""_summary_: Returns a list of objects that are touching the object. """_summary_: Returns a list of objects that are touching the object.
:param blender-object obj: Blender Object :param blender-object obj: Blender Object
:param list [] class_filter: A list of classes used to filter the objects to be considered for the calculation. Example: ["IfcWall"] or ["IfcWall", "IfcSlab"] :param list [] class_filter: A list of classes used to filter the objects
to be considered for the calculation. Example: ["IfcWall"] or ["IfcWall", "IfcSlab"]
:return list: List of touching objects :return list: List of touching objects
""" """
# rotate the object ever so slightly, otherwise bvhtree.overlap won't work properly. https://blender.stackexchange.com/a/275244/130742 # rotate the object ever so slightly, otherwise bvhtree.overlap won't work properly. https://blender.stackexchange.com/a/275244/130742
@@ -1094,7 +1117,7 @@ class QtoCalculator:
return touching_objects return touching_objects
def get_contact_area(self, object1, object2): def get_contact_area(self, object1: bpy.types.Object, object2: bpy.types.Object) -> float:
"""_summary_: Returns the contact area between two objects. """_summary_: Returns the contact area between two objects.
:param blender-object obj: Blender Object :param blender-object obj: Blender Object
@@ -1109,7 +1132,13 @@ class QtoCalculator:
total_area += self.get_intersection_between_polygons(object1, poly1, object2, poly2) total_area += self.get_intersection_between_polygons(object1, poly1, object2, poly2)
return total_area return total_area
def get_intersection_between_polygons(self, object1, poly1, object2, poly2): def get_intersection_between_polygons(
self,
object1: bpy.types.Object,
poly1: bpy.types.MeshPolygon,
object2: bpy.types.Object,
poly2: bpy.types.MeshPolygon,
) -> float:
"""_summary_: Returns the intersection between two polygons. """_summary_: Returns the intersection between two polygons.
:param blender-object object1: Blender Object :param blender-object object1: Blender Object
@@ -1152,7 +1181,9 @@ class QtoCalculator:
# TopologicalError - Generated Geometry might be invalid # TopologicalError - Generated Geometry might be invalid
return 0 return 0
def create_shapely_polygon(self, obj, polygon, trans_matrix): def create_shapely_polygon(
self, obj: bpy.types.Object, polygon: bpy.types.MeshPolygon, trans_matrix: Matrix
) -> Polygon:
"""_summary_: Create a shapely polygon """_summary_: Create a shapely polygon
:param blender-object obj: Blender Object :param blender-object obj: Blender Object
@@ -1171,12 +1202,14 @@ class QtoCalculator:
polygon_tuples.append((x, y)) polygon_tuples.append((x, y))
return Polygon(polygon_tuples) return Polygon(polygon_tuples)
def get_gross_element_mesh(self, element): def get_gross_element_mesh(self, element: ifcopenshell.entity_instance) -> bpy.types.Mesh:
settings = ifcopenshell.geom.settings() settings = ifcopenshell.geom.settings()
settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, True) settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, True)
return self.create_mesh_from_shape(element, settings) return self.create_mesh_from_shape(element, settings)
def create_mesh_from_shape(self, element, settings=None): def create_mesh_from_shape(
self, element: ifcopenshell.entity_instance, settings: Optional[ifcopenshell.geom.settings] = None
) -> bpy.types.Mesh:
if settings is None: if settings is None:
settings = ifcopenshell.geom.settings() settings = ifcopenshell.geom.settings()
shape = ifcopenshell.geom.create_shape(settings, element) shape = ifcopenshell.geom.create_shape(settings, element)
@@ -1203,12 +1236,12 @@ class QtoCalculator:
mesh.update() mesh.update()
return mesh return mesh
def get_bmesh_from_mesh(self, mesh): def get_bmesh_from_mesh(self, mesh: bpy.types.Mesh) -> bmesh.types.BMesh:
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(mesh) bm.from_mesh(mesh)
return bm return bm
def get_object_main_axis(self, o): def get_object_main_axis(self, o: bpy.types.Object) -> AxisType:
"""_summary_: Returns the main object axis. Useful for profile-defined objects. """_summary_: Returns the main object axis. Useful for profile-defined objects.
:param blender-object o: Blender Object :param blender-object o: Blender Object
@@ -1227,18 +1260,18 @@ class QtoCalculator:
else: else:
return "x" return "x"
def is_opening_horizontal(self, o): def is_opening_horizontal(self, o: bpy.types.Object) -> bool:
x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
return z < x and z < y return z < x and z < y
def delete_mesh(self, mesh): def delete_mesh(self, mesh: bpy.types.Mesh) -> None:
mesh.user_clear() mesh.user_clear()
bpy.data.meshes.remove(mesh) bpy.data.meshes.remove(mesh)
def delete_obj(self, obj): def delete_obj(self, obj: bpy.types.Object) -> None:
bpy.data.objects.remove(obj, do_unlink=True) bpy.data.objects.remove(obj, do_unlink=True)
+1 -1
View File
@@ -79,7 +79,7 @@ class Ifc(blenderbim.core.tool.Ifc):
return IfcStore.get_schema() return IfcStore.get_schema()
@classmethod @classmethod
def get_entity(cls, obj: bpy.types.Object) -> ifcopenshell.entity_instance: def get_entity(cls, obj: IFC_CONNECTED_TYPE) -> ifcopenshell.entity_instance:
ifc = IfcStore.get_file() ifc = IfcStore.get_file()
props = getattr(obj, "BIMObjectProperties", None) props = getattr(obj, "BIMObjectProperties", None)
if ifc and props and props.ifc_definition_id: if ifc and props and props.ifc_definition_id:
+33 -18
View File
@@ -23,14 +23,17 @@ import blenderbim.tool as tool
import ifcopenshell import ifcopenshell
from mathutils import Vector from mathutils import Vector
from ifcopenshell import util from ifcopenshell import util
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator import ifcopenshell.util.unit
import ifcopenshell.util.element
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator, QuanityTypes
from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper from blenderbim.bim.module.pset.calc_quantity_function_mapper import mapper
import blenderbim.bim.schema import blenderbim.bim.schema
from typing import Optional, Union, Literal
class Qto(blenderbim.core.tool.Qto): class Qto(blenderbim.core.tool.Qto):
@classmethod @classmethod
def get_radius_of_selected_vertices(cls, obj): def get_radius_of_selected_vertices(cls, obj: bpy.types.Object) -> float:
selected_verts = [v.co for v in obj.data.vertices if v.select] selected_verts = [v.co for v in obj.data.vertices if v.select]
total = Vector() total = Vector()
for v in selected_verts: for v in selected_verts:
@@ -39,16 +42,16 @@ class Qto(blenderbim.core.tool.Qto):
return max([(v - circle_center).length for v in selected_verts]) return max([(v - circle_center).length for v in selected_verts])
@classmethod @classmethod
def set_qto_result(cls, result): def set_qto_result(cls, result: float) -> None:
bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3)) bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
@classmethod @classmethod
def add_object_base_qto(cls, obj): def add_object_base_qto(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
product = tool.Ifc.get_entity(obj) product = tool.Ifc.get_entity(obj)
return cls.add_product_base_qto(product) return cls.add_product_base_qto(product)
@classmethod @classmethod
def add_product_base_qto(cls, product): def add_product_base_qto(cls, product: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
base_quantity_name = cls.get_applicable_base_quantity_name(product) base_quantity_name = cls.get_applicable_base_quantity_name(product)
if base_quantity_name: if base_quantity_name:
return tool.Ifc.run( return tool.Ifc.run(
@@ -58,7 +61,7 @@ class Qto(blenderbim.core.tool.Qto):
) )
@classmethod @classmethod
def get_applicable_quantity_names(cls, qto_name): def get_applicable_quantity_names(cls, qto_name: str) -> list[str]:
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(qto_name) pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(qto_name)
return ( return (
[property.Name for property in pset_template.HasPropertyTemplates] [property.Name for property in pset_template.HasPropertyTemplates]
@@ -67,7 +70,9 @@ class Qto(blenderbim.core.tool.Qto):
) )
@classmethod @classmethod
def get_applicable_base_quantity_name(cls, product=None): def get_applicable_base_quantity_name(
cls, product: Optional[ifcopenshell.entity_instance] = None
) -> Union[str, None]:
if not product: if not product:
return return
applicable_qto_names = blenderbim.bim.schema.ifc.psetqto.get_applicable_names( applicable_qto_names = blenderbim.bim.schema.ifc.psetqto.get_applicable_names(
@@ -76,19 +81,23 @@ class Qto(blenderbim.core.tool.Qto):
return next((qto_name for qto_name in applicable_qto_names if "Qto_" in qto_name and "Base" in qto_name), None) return next((qto_name for qto_name in applicable_qto_names if "Qto_" in qto_name and "Base" in qto_name), None)
@classmethod @classmethod
def get_new_calculated_quantity(cls, qto_name, quantity_name, obj): def get_new_calculated_quantity(cls, qto_name: str, quantity_name: str, obj: bpy.types.Object) -> float:
return QtoCalculator().calculate_quantity(qto_name, quantity_name, obj) return QtoCalculator().calculate_quantity(qto_name, quantity_name, obj)
@classmethod @classmethod
def get_new_guessed_quantity(cls, obj, quantity_name, alternative_prop_names): def get_new_guessed_quantity(
cls, obj: bpy.types.Object, quantity_name: str, alternative_prop_names: list[str]
) -> Union[float, None]:
return QtoCalculator().guess_quantity(quantity_name, alternative_prop_names, obj) return QtoCalculator().guess_quantity(quantity_name, alternative_prop_names, obj)
@classmethod @classmethod
def get_rounded_value(cls, new_quantity): def get_rounded_value(cls, new_quantity: float) -> float:
return round(new_quantity, 3) return round(new_quantity, 3)
@classmethod @classmethod
def get_calculated_object_quantities(cls, calculator, qto_name, obj): def get_calculated_object_quantities(
cls, calculator: QtoCalculator, qto_name: str, obj: bpy.types.Object
) -> dict[str, float]:
return { return {
quantity_name: cls.get_rounded_value(calculator.calculate_quantity(qto_name, quantity_name, obj)) quantity_name: cls.get_rounded_value(calculator.calculate_quantity(qto_name, quantity_name, obj))
for quantity_name in cls.get_applicable_quantity_names(qto_name) or [] for quantity_name in cls.get_applicable_quantity_names(qto_name) or []
@@ -97,15 +106,19 @@ class Qto(blenderbim.core.tool.Qto):
} }
@classmethod @classmethod
def has_calculator(cls, qto_name, quantity_name): def has_calculator(cls, qto_name: str, quantity_name: str) -> bool:
return bool(mapper.get(qto_name, {}).get(quantity_name, None)) return bool(mapper.get(qto_name, {}).get(quantity_name, None))
@classmethod @classmethod
def convert_to_project_units(cls, value, qto_name=None, quantity_name=None, quantity_type=None): def convert_to_project_units(
cls,
value: float,
qto_name: Optional[str] = None,
quantity_name: Optional[str] = None,
quantity_type: Optional[QuanityTypes] = None,
) -> Union[float, None]:
"""You can either specify `quantity_type` or provide `qto_name/quantity_name` """You can either specify `quantity_type` or provide `qto_name/quantity_name`
to let method figure the `quantity_type` from the templates to let method figure the `quantity_type` from the templates
`quantity_type` values are `Q_LENGTH`, `Q_AREA`, `Q_VOLUME`
""" """
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
quantity_to_unit_types = { quantity_to_unit_types = {
@@ -135,7 +148,9 @@ class Qto(blenderbim.core.tool.Qto):
return value return value
@classmethod @classmethod
def get_guessed_quantities(cls, obj, pset_qto_properties): def get_guessed_quantities(
cls, obj: bpy.types.Object, pset_qto_properties: list[ifcopenshell.entity_instance]
) -> dict[str, float]:
calculated_quantities = {} calculated_quantities = {}
for pset_qto_property in pset_qto_properties: for pset_qto_property in pset_qto_properties:
quantity_name = pset_qto_property.get_info()["Name"] quantity_name = pset_qto_property.get_info()["Name"]
@@ -153,7 +168,7 @@ class Qto(blenderbim.core.tool.Qto):
return calculated_quantities return calculated_quantities
@classmethod @classmethod
def get_base_qto(cls, product): def get_base_qto(cls, product: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
if not hasattr(product, "IsDefinedBy"): if not hasattr(product, "IsDefinedBy"):
return return
for rel in product.IsDefinedBy or []: for rel in product.IsDefinedBy or []:
@@ -166,7 +181,7 @@ class Qto(blenderbim.core.tool.Qto):
return rel.RelatingPropertyDefinition return rel.RelatingPropertyDefinition
@classmethod @classmethod
def get_related_cost_item_quantities(cls, product): def get_related_cost_item_quantities(cls, product: ifcopenshell.entity_instance) -> list[dict]:
"""_summary_: Returns the related cost item and related quantities of the product """_summary_: Returns the related cost item and related quantities of the product
:param ifc-instance product: ifc instance :param ifc-instance product: ifc instance
+2
View File
@@ -21,6 +21,8 @@ import ifcopenshell
import ifcopenshell.util.representation import ifcopenshell.util.representation
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.core.geometry import blenderbim.core.geometry
import blenderbim.core.material
import blenderbim.core.style
import blenderbim.tool as tool import blenderbim.tool as tool
from mathutils import Vector from mathutils import Vector
from blenderbim.bim.module.model.opening import FilledOpeningGenerator from blenderbim.bim.module.model.opening import FilledOpeningGenerator
+1 -1
View File
@@ -96,7 +96,7 @@ class Style(blenderbim.core.tool.Style):
return obj.name return obj.name
@classmethod @classmethod
def get_style(cls, obj): def get_style(cls, obj: bpy.types.Material) -> Union[ifcopenshell.entity_instance, None]:
if obj.BIMMaterialProperties.ifc_style_id: if obj.BIMMaterialProperties.ifc_style_id:
try: try:
return tool.Ifc.get().by_id(obj.BIMMaterialProperties.ifc_style_id) return tool.Ifc.get().by_id(obj.BIMMaterialProperties.ifc_style_id)
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>. # along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import re import re
import pathlib import pathlib
import ifcopenshell import ifcopenshell
@@ -25,10 +26,10 @@ from ifcopenshell.entity_instance import entity_instance
from functools import lru_cache from functools import lru_cache
from typing import List, Generator, Optional from typing import List, Generator, Optional
templates = {} templates: dict[str, PsetQto] = {}
def get_template(schema): def get_template(schema: str) -> PsetQto:
global templates global templates
if schema not in templates: if schema not in templates:
templates[schema] = PsetQto(schema) templates[schema] = PsetQto(schema)
@@ -36,11 +37,13 @@ def get_template(schema):
class PsetQto: class PsetQto:
# fmt: off
templates_path = { templates_path = {
"IFC2X3": "Pset_IFC2X3.ifc", "IFC2X3": "Pset_IFC2X3.ifc",
"IFC4": "Pset_IFC4_ADD2.ifc", "IFC4": "Pset_IFC4_ADD2.ifc",
"IFC4X3": "Pset_IFC4X3.ifc" "IFC4X3": "Pset_IFC4X3.ifc"
} }
# fmt: on
def __init__(self, schema: str, templates=None) -> None: def __init__(self, schema: str, templates=None) -> None:
self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema) self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema)
@@ -18,7 +18,7 @@
from fractions import Fraction from fractions import Fraction
from math import pi from math import pi
from typing import Tuple, Iterable, Any, Union, Literal from typing import Tuple, Iterable, Any, Union, Literal, Optional
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
@@ -525,7 +525,7 @@ def convert_unit(value, from_unit, to_unit):
) )
def convert(value, from_prefix, from_unit, to_prefix, to_unit): def convert(value: float, from_prefix: Optional[str], from_unit: str, to_prefix: Optional[str], to_unit: str) -> float:
"""Converts between length, area, and volume units """Converts between length, area, and volume units
In this case, you manually specify the names and (optionally) prefixes to In this case, you manually specify the names and (optionally) prefixes to
@@ -534,12 +534,12 @@ def convert(value, from_prefix, from_unit, to_prefix, to_unit):
:param value: The numeric value you want to convert :param value: The numeric value you want to convert
:type value: float :type value: float
:param from_prefix: A prefix from IfcSIPrefix. Can be None. :param from_prefix: A prefix from IfcSIPrefix. Can be None
:type from_prefix: str,optional :type from_prefix: str, optional
:param from_unit: IfcSIUnitName or IfcConversionBasedUnit.Name :param from_unit: IfcSIUnitName or IfcConversionBasedUnit.Name
:type from_unit: str :type from_unit: str
:param to_prefix: A prefix from IfcSIPrefix. Can be None. :param to_prefix: A prefix from IfcSIPrefix. Can be None
:type to_prefix: str,optional :type to_prefix: str, optional
:param to_unit: IfcSIUnitName or IfcConversionBasedUnit.Name :param to_unit: IfcSIUnitName or IfcConversionBasedUnit.Name
:type to_unit: str :type to_unit: str
:return: The converted value. :return: The converted value.
@@ -566,7 +566,7 @@ def convert(value, from_prefix, from_unit, to_prefix, to_unit):
return value return value
def calculate_unit_scale(ifc_file, unit_type="LENGTHUNIT"): def calculate_unit_scale(ifc_file: ifcopenshell.file, unit_type: str = "LENGTHUNIT") -> float:
"""Returns a unit scale factor to convert to and from IFC project units and SI units. """Returns a unit scale factor to convert to and from IFC project units and SI units.
Example: Example: