mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-17 22:11:36 +00:00
typing
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user