mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Reimplement manual qty calculator using autodetected calculator functions and redo qto UI
This commit is contained in:
@@ -27,10 +27,8 @@ import blenderbim.bim.helper
|
|||||||
import blenderbim.bim.handler
|
import blenderbim.bim.handler
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
import blenderbim.core.pset as core
|
import blenderbim.core.pset as core
|
||||||
import blenderbim.core.qto as QtoCore
|
|
||||||
import blenderbim.bim.module.pset.data
|
import blenderbim.bim.module.pset.data
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
|
|
||||||
|
|
||||||
|
|
||||||
class Operator:
|
class Operator:
|
||||||
|
|||||||
@@ -20,16 +20,17 @@ import bpy
|
|||||||
from . import ui, prop, operator
|
from . import ui, prop, operator
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
operator.AssignBaseQto,
|
|
||||||
operator.CalculateCircleRadius,
|
operator.CalculateCircleRadius,
|
||||||
operator.CalculateEdgeLengths,
|
operator.CalculateEdgeLengths,
|
||||||
operator.CalculateFaceAreas,
|
operator.CalculateFaceAreas,
|
||||||
operator.CalculateObjectVolumes,
|
operator.CalculateObjectVolumes,
|
||||||
operator.ExecuteQtoMethod,
|
operator.CalculateSingleQuantity,
|
||||||
operator.PerformQuantityTakeOff,
|
operator.PerformQuantityTakeOff,
|
||||||
operator.QuantifyObjects,
|
|
||||||
prop.BIMQtoProperties,
|
prop.BIMQtoProperties,
|
||||||
ui.BIM_PT_qto_utilities,
|
ui.BIM_PT_qto,
|
||||||
|
ui.BIM_PT_qto_manual,
|
||||||
|
ui.BIM_PT_qto_simple,
|
||||||
|
ui.BIM_PT_qto_cost,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,17 +38,19 @@ VectorTuple = tuple[float, float, float]
|
|||||||
def get_units(o: bpy.types.Object, vg_index: int) -> int:
|
def get_units(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(o: bpy.types.Object) -> float:
|
|
||||||
"""_summary_: Returns the length of the longest edge of the object bounding box
|
|
||||||
|
|
||||||
:param blender-object o: Blender Object
|
def get_linear_length(o: bpy.types.Object) -> float:
|
||||||
:return float: Length
|
"""Returns the length of the longest edge of the object bounding box
|
||||||
|
|
||||||
|
:param o: Blender Object
|
||||||
|
:return: Length
|
||||||
"""
|
"""
|
||||||
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 max(x, y, z)
|
return max(x, y, z)
|
||||||
|
|
||||||
|
|
||||||
def get_length(o: bpy.types.Object, vg_index: Optional[int] = None, main_axis: str = "x") -> float:
|
def get_length(o: bpy.types.Object, vg_index: Optional[int] = None, main_axis: str = "x") -> 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
|
||||||
@@ -74,22 +76,26 @@ def get_length(o: bpy.types.Object, vg_index: Optional[int] = None, main_axis: s
|
|||||||
length += get_edge_distance(o, e)
|
length += get_edge_distance(o, e)
|
||||||
return length
|
return length
|
||||||
|
|
||||||
|
|
||||||
def get_stair_length(obj: bpy.types.Object) -> float:
|
def get_stair_length(obj: bpy.types.Object) -> float:
|
||||||
length = get_length(obj)
|
length = get_length(obj)
|
||||||
height = get_height(obj)
|
height = 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(obj: bpy.types.Object) -> float:
|
def get_net_stair_area(obj: bpy.types.Object) -> float:
|
||||||
OBB_obj = get_OBB_object(obj)
|
OBB_obj = get_OBB_object(obj)
|
||||||
OBB_net_footprint_area = get_net_footprint_area(OBB_obj)
|
OBB_net_footprint_area = get_net_footprint_area(OBB_obj)
|
||||||
return OBB_net_footprint_area
|
return OBB_net_footprint_area
|
||||||
|
|
||||||
|
|
||||||
def get_gross_stair_area(obj: bpy.types.Object) -> float:
|
def get_gross_stair_area(obj: bpy.types.Object) -> float:
|
||||||
OBB_obj = get_OBB_object(obj)
|
OBB_obj = get_OBB_object(obj)
|
||||||
OBB_gross_footprint_area = get_gross_footprint_area(OBB_obj)
|
OBB_gross_footprint_area = get_gross_footprint_area(OBB_obj)
|
||||||
return OBB_gross_footprint_area
|
return OBB_gross_footprint_area
|
||||||
|
|
||||||
|
|
||||||
def get_parametric_axis(obj: bpy.types.Object) -> Literal["AXIS2", "AXIS3", None]:
|
def get_parametric_axis(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:
|
||||||
@@ -105,6 +111,7 @@ def get_parametric_axis(obj: bpy.types.Object) -> Literal["AXIS2", "AXIS3", None
|
|||||||
return None
|
return None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_covering_gross_area(obj: bpy.types.Object) -> float:
|
def get_covering_gross_area(obj: bpy.types.Object) -> float:
|
||||||
parametrix_axis = get_parametric_axis(obj)
|
parametrix_axis = get_parametric_axis(obj)
|
||||||
if not parametrix_axis:
|
if not parametrix_axis:
|
||||||
@@ -114,6 +121,7 @@ def get_covering_gross_area(obj: bpy.types.Object) -> float:
|
|||||||
elif parametrix_axis == "AXIS3":
|
elif parametrix_axis == "AXIS3":
|
||||||
return get_gross_footprint_area(obj)
|
return get_gross_footprint_area(obj)
|
||||||
|
|
||||||
|
|
||||||
def get_covering_net_area(obj: bpy.types.Object) -> float:
|
def get_covering_net_area(obj: bpy.types.Object) -> float:
|
||||||
parametrix_axis = get_parametric_axis(obj)
|
parametrix_axis = get_parametric_axis(obj)
|
||||||
if not parametrix_axis:
|
if not parametrix_axis:
|
||||||
@@ -123,6 +131,7 @@ def get_covering_net_area(obj: bpy.types.Object) -> float:
|
|||||||
elif parametrix_axis == "AXIS3":
|
elif parametrix_axis == "AXIS3":
|
||||||
return get_net_footprint_area(obj)
|
return get_net_footprint_area(obj)
|
||||||
|
|
||||||
|
|
||||||
def get_covering_width(obj: bpy.types.Object) -> float:
|
def get_covering_width(obj: bpy.types.Object) -> float:
|
||||||
parametrix_axis = get_parametric_axis(obj)
|
parametrix_axis = get_parametric_axis(obj)
|
||||||
if not parametrix_axis:
|
if not parametrix_axis:
|
||||||
@@ -132,6 +141,7 @@ def get_covering_width(obj: bpy.types.Object) -> float:
|
|||||||
elif parametrix_axis == "AXIS3":
|
elif parametrix_axis == "AXIS3":
|
||||||
return get_height(obj)
|
return get_height(obj)
|
||||||
|
|
||||||
|
|
||||||
def get_width(o: bpy.types.Object) -> float:
|
def get_width(o: bpy.types.Object) -> float:
|
||||||
"""_summary_: Returns the width of the object bounding box
|
"""_summary_: Returns the width of the object bounding box
|
||||||
|
|
||||||
@@ -142,6 +152,7 @@ def get_width(o: bpy.types.Object) -> float:
|
|||||||
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(o: bpy.types.Object) -> float:
|
def get_height(o: bpy.types.Object) -> float:
|
||||||
"""_summary_: Returns the height of the object bounding box
|
"""_summary_: Returns the height of the object bounding box
|
||||||
|
|
||||||
@@ -150,38 +161,45 @@ def get_height(o: bpy.types.Object) -> float:
|
|||||||
"""
|
"""
|
||||||
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(obj: bpy.types.Object) -> float:
|
def get_opening_height(obj: bpy.types.Object) -> float:
|
||||||
if is_opening_horizontal(obj):
|
if is_opening_horizontal(obj):
|
||||||
return get_width(obj)
|
return get_width(obj)
|
||||||
else:
|
else:
|
||||||
return get_height(obj)
|
return get_height(obj)
|
||||||
|
|
||||||
|
|
||||||
def get_opening_depth(obj: bpy.types.Object) -> float:
|
def get_opening_depth(obj: bpy.types.Object) -> float:
|
||||||
if is_opening_horizontal(obj):
|
if is_opening_horizontal(obj):
|
||||||
return get_height(obj)
|
return get_height(obj)
|
||||||
else:
|
else:
|
||||||
return get_width(obj)
|
return get_width(obj)
|
||||||
|
|
||||||
|
|
||||||
def get_opening_mapping_area(obj: bpy.types.Object) -> float:
|
def get_opening_mapping_area(obj: bpy.types.Object) -> float:
|
||||||
if is_opening_horizontal(obj):
|
if is_opening_horizontal(obj):
|
||||||
return get_net_footprint_area(obj)
|
return get_net_footprint_area(obj)
|
||||||
else:
|
else:
|
||||||
return get_net_side_area(obj)
|
return get_net_side_area(obj)
|
||||||
|
|
||||||
|
|
||||||
def get_finish_ceiling_height(obj: bpy.types.Object) -> float:
|
def get_finish_ceiling_height(obj: bpy.types.Object) -> float:
|
||||||
floor_height = get_finish_floor_height(obj)
|
floor_height = get_finish_floor_height(obj)
|
||||||
ceiling_height = get_ceiling_height(obj)
|
ceiling_height = get_ceiling_height(obj)
|
||||||
finish_ceiling_height = ceiling_height - floor_height
|
finish_ceiling_height = ceiling_height - floor_height
|
||||||
return finish_ceiling_height
|
return finish_ceiling_height
|
||||||
|
|
||||||
|
|
||||||
def get_max_global_z(obj: bpy.types.Object) -> float:
|
def get_max_global_z(obj: bpy.types.Object) -> float:
|
||||||
z_values = [(obj.matrix_world @ Vector(co))[2] for co in obj.bound_box]
|
z_values = [(obj.matrix_world @ Vector(co))[2] for co in obj.bound_box]
|
||||||
return max(z_values)
|
return max(z_values)
|
||||||
|
|
||||||
|
|
||||||
def get_min_global_z(obj: bpy.types.Object) -> float:
|
def get_min_global_z(obj: bpy.types.Object) -> float:
|
||||||
z_values = [(obj.matrix_world @ Vector(co))[2] for co in obj.bound_box]
|
z_values = [(obj.matrix_world @ Vector(co))[2] for co in obj.bound_box]
|
||||||
return min(z_values)
|
return min(z_values)
|
||||||
|
|
||||||
|
|
||||||
def get_finish_floor_height(obj: bpy.types.Object) -> float:
|
def get_finish_floor_height(obj: bpy.types.Object) -> float:
|
||||||
space_min_z_value = get_min_global_z(obj)
|
space_min_z_value = get_min_global_z(obj)
|
||||||
|
|
||||||
@@ -200,6 +218,7 @@ def get_finish_floor_height(obj: bpy.types.Object) -> float:
|
|||||||
|
|
||||||
return flooring_max_z_value - space_min_z_value
|
return flooring_max_z_value - space_min_z_value
|
||||||
|
|
||||||
|
|
||||||
def get_ceiling_height(obj: bpy.types.Object) -> float:
|
def get_ceiling_height(obj: bpy.types.Object) -> float:
|
||||||
space_min_z_value = get_min_global_z(obj)
|
space_min_z_value = get_min_global_z(obj)
|
||||||
space_max_z_value = get_max_global_z(obj)
|
space_max_z_value = get_max_global_z(obj)
|
||||||
@@ -219,6 +238,7 @@ def get_ceiling_height(obj: bpy.types.Object) -> float:
|
|||||||
|
|
||||||
return ceiling_min_z_value - space_min_z_value
|
return ceiling_min_z_value - space_min_z_value
|
||||||
|
|
||||||
|
|
||||||
def get_net_perimeter(o: bpy.types.Object) -> float:
|
def get_net_perimeter(o: bpy.types.Object) -> float:
|
||||||
parsed_edges = []
|
parsed_edges = []
|
||||||
shared_edges = []
|
shared_edges = []
|
||||||
@@ -234,6 +254,7 @@ def get_net_perimeter(o: bpy.types.Object) -> float:
|
|||||||
perimeter -= get_edge_key_distance(o, edge_key)
|
perimeter -= get_edge_key_distance(o, edge_key)
|
||||||
return perimeter
|
return perimeter
|
||||||
|
|
||||||
|
|
||||||
def get_gross_perimeter(o: bpy.types.Object) -> float:
|
def get_gross_perimeter(o: bpy.types.Object) -> float:
|
||||||
element = tool.Ifc.get_entity(o)
|
element = tool.Ifc.get_entity(o)
|
||||||
mesh = get_gross_element_mesh(element)
|
mesh = get_gross_element_mesh(element)
|
||||||
@@ -242,14 +263,17 @@ def get_gross_perimeter(o: bpy.types.Object) -> float:
|
|||||||
delete_obj(gross_obj)
|
delete_obj(gross_obj)
|
||||||
return gross_perimeter
|
return gross_perimeter
|
||||||
|
|
||||||
|
|
||||||
def get_space_net_perimeter(obj: bpy.types.Object) -> float:
|
def get_space_net_perimeter(obj: bpy.types.Object) -> float:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_rectangular_perimeter(obj: bpy.types.Object) -> float:
|
def get_rectangular_perimeter(obj: bpy.types.Object) -> float:
|
||||||
length = get_length(obj, main_axis="x")
|
length = get_length(obj, main_axis="x")
|
||||||
height = get_height(obj)
|
height = get_height(obj)
|
||||||
return (length + height) * 2
|
return (length + height) * 2
|
||||||
|
|
||||||
|
|
||||||
def get_lowest_polygons(o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
|
def get_lowest_polygons(o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
|
||||||
lowest_polygons = []
|
lowest_polygons = []
|
||||||
lowest_z = None
|
lowest_z = None
|
||||||
@@ -266,6 +290,7 @@ def get_lowest_polygons(o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
|
|||||||
lowest_z = z
|
lowest_z = z
|
||||||
return lowest_polygons
|
return lowest_polygons
|
||||||
|
|
||||||
|
|
||||||
def get_highest_polygons(o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
|
def get_highest_polygons(o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
|
||||||
highest_polygons = []
|
highest_polygons = []
|
||||||
highest_z = None
|
highest_z = None
|
||||||
@@ -282,12 +307,15 @@ def get_highest_polygons(o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
|
|||||||
highest_z = z
|
highest_z = z
|
||||||
return highest_polygons
|
return highest_polygons
|
||||||
|
|
||||||
|
|
||||||
def get_edge_key_distance(obj: bpy.types.Object, edge_key: tuple[int, int]) -> float:
|
def get_edge_key_distance(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(obj: bpy.types.Object, edge: bpy.types.MeshEdge) -> float:
|
def get_edge_distance(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(obj: bpy.types.Object) -> float:
|
def get_net_floor_area(obj: bpy.types.Object) -> float:
|
||||||
decompositions = get_obj_decompositions(obj)
|
decompositions = get_obj_decompositions(obj)
|
||||||
if not decompositions:
|
if not decompositions:
|
||||||
@@ -304,6 +332,7 @@ def get_net_floor_area(obj: bpy.types.Object) -> float:
|
|||||||
|
|
||||||
return total_net_floor_area
|
return total_net_floor_area
|
||||||
|
|
||||||
|
|
||||||
def get_gross_ceiling_area(obj: bpy.types.Object) -> float:
|
def get_gross_ceiling_area(obj: bpy.types.Object) -> float:
|
||||||
decompositions = get_obj_decompositions(obj)
|
decompositions = get_obj_decompositions(obj)
|
||||||
if not decompositions:
|
if not decompositions:
|
||||||
@@ -320,6 +349,7 @@ def get_gross_ceiling_area(obj: bpy.types.Object) -> float:
|
|||||||
|
|
||||||
return total_gross_ceiling_area
|
return total_gross_ceiling_area
|
||||||
|
|
||||||
|
|
||||||
def get_net_ceiling_area(obj: bpy.types.Object) -> float:
|
def get_net_ceiling_area(obj: bpy.types.Object) -> float:
|
||||||
decompositions = get_obj_decompositions(obj)
|
decompositions = get_obj_decompositions(obj)
|
||||||
if not decompositions:
|
if not decompositions:
|
||||||
@@ -340,6 +370,7 @@ def get_net_ceiling_area(obj: bpy.types.Object) -> float:
|
|||||||
|
|
||||||
return total_net_ceiling_area
|
return total_net_ceiling_area
|
||||||
|
|
||||||
|
|
||||||
def get_space_net_volume(obj: bpy.types.Object) -> float:
|
def get_space_net_volume(obj: bpy.types.Object) -> float:
|
||||||
decompositions = get_obj_decompositions(obj)
|
decompositions = get_obj_decompositions(obj)
|
||||||
if not decompositions:
|
if not decompositions:
|
||||||
@@ -355,6 +386,7 @@ def get_space_net_volume(obj: bpy.types.Object) -> float:
|
|||||||
|
|
||||||
return total_space_net_volume
|
return total_space_net_volume
|
||||||
|
|
||||||
|
|
||||||
def get_net_footprint_area(o: bpy.types.Object) -> float:
|
def get_net_footprint_area(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
|
||||||
|
|
||||||
@@ -366,6 +398,7 @@ def get_net_footprint_area(o: bpy.types.Object) -> float:
|
|||||||
area += polygon.area
|
area += polygon.area
|
||||||
return area
|
return area
|
||||||
|
|
||||||
|
|
||||||
def get_gross_footprint_area(o: bpy.types.Object) -> float:
|
def get_gross_footprint_area(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
|
||||||
|
|
||||||
@@ -382,6 +415,7 @@ def get_gross_footprint_area(o: bpy.types.Object) -> float:
|
|||||||
delete_mesh(mesh)
|
delete_mesh(mesh)
|
||||||
return gross_footprint_area
|
return gross_footprint_area
|
||||||
|
|
||||||
|
|
||||||
def get_net_roofprint_area(o: bpy.types.Object) -> float:
|
def get_net_roofprint_area(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
|
||||||
@@ -394,6 +428,7 @@ def get_net_roofprint_area(o: bpy.types.Object) -> float:
|
|||||||
area += polygon.area
|
area += polygon.area
|
||||||
return area
|
return area
|
||||||
|
|
||||||
|
|
||||||
def get_side_area(o: bpy.types.Object) -> float:
|
def get_side_area(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.
|
||||||
@@ -402,6 +437,7 @@ def get_side_area(o: bpy.types.Object) -> float:
|
|||||||
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(obj: bpy.types.Object) -> float:
|
def get_cross_section_area(obj: bpy.types.Object) -> float:
|
||||||
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]
|
||||||
@@ -418,6 +454,7 @@ def get_cross_section_area(obj: bpy.types.Object) -> float:
|
|||||||
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(o: bpy.types.Object, vg_index: Optional[int] = None) -> float:
|
def get_gross_surface_area(o: bpy.types.Object, vg_index: Optional[int] = None) -> float:
|
||||||
if vg_index is None:
|
if vg_index is None:
|
||||||
if not has_openings(o):
|
if not has_openings(o):
|
||||||
@@ -436,21 +473,25 @@ def get_gross_surface_area(o: bpy.types.Object, vg_index: Optional[int] = None)
|
|||||||
area += polygon.area
|
area += polygon.area
|
||||||
return area
|
return area
|
||||||
|
|
||||||
|
|
||||||
def get_net_surface_area(obj: bpy.types.Object) -> float:
|
def get_net_surface_area(obj: bpy.types.Object) -> float:
|
||||||
return get_mesh_area(obj.data)
|
return get_mesh_area(obj.data)
|
||||||
|
|
||||||
|
|
||||||
def get_mesh_area(mesh: bpy.types.Mesh) -> float:
|
def get_mesh_area(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(polygon: bpy.types.MeshPolygon, vertices_in_vg: list[bpy.types.MeshVertex]) -> bool:
|
def is_polygon_in_vg(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(o: bpy.types.Object) -> float:
|
def get_net_volume(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)
|
||||||
@@ -458,6 +499,7 @@ def get_net_volume(o: bpy.types.Object) -> float:
|
|||||||
o_mesh.free()
|
o_mesh.free()
|
||||||
return volume
|
return volume
|
||||||
|
|
||||||
|
|
||||||
def get_gross_volume(o: bpy.types.Object) -> float:
|
def get_gross_volume(o: bpy.types.Object) -> float:
|
||||||
if not has_openings(o):
|
if not has_openings(o):
|
||||||
return get_net_volume(o)
|
return get_net_volume(o)
|
||||||
@@ -473,17 +515,18 @@ def get_gross_volume(o: bpy.types.Object) -> float:
|
|||||||
|
|
||||||
return gross_volume
|
return gross_volume
|
||||||
|
|
||||||
def has_openings(
|
|
||||||
obj: bpy.types.Object
|
def has_openings(obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
|
||||||
) -> 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(obj: bpy.types.Object) -> list[ifcopenshell.entity_instance]:
|
def get_obj_decompositions(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(obj: bpy.types.Object) -> Union[float, None]:
|
def get_gross_weight(obj: bpy.types.Object) -> Union[float, None]:
|
||||||
obj_mass_density = get_obj_mass_density(obj)
|
obj_mass_density = get_obj_mass_density(obj)
|
||||||
if not obj_mass_density:
|
if not obj_mass_density:
|
||||||
@@ -492,6 +535,7 @@ def get_gross_weight(obj: bpy.types.Object) -> Union[float, None]:
|
|||||||
gross_weight = obj_mass_density * gross_volume
|
gross_weight = obj_mass_density * gross_volume
|
||||||
return gross_weight
|
return gross_weight
|
||||||
|
|
||||||
|
|
||||||
def get_net_weight(obj: bpy.types.Object) -> Union[float, None]:
|
def get_net_weight(obj: bpy.types.Object) -> Union[float, None]:
|
||||||
obj_mass_density = get_obj_mass_density(obj)
|
obj_mass_density = get_obj_mass_density(obj)
|
||||||
if not obj_mass_density:
|
if not obj_mass_density:
|
||||||
@@ -500,6 +544,7 @@ def get_net_weight(obj: bpy.types.Object) -> Union[float, None]:
|
|||||||
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(obj: bpy.types.Object) -> Union[float, None]:
|
def get_obj_mass_density(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)
|
||||||
@@ -546,6 +591,7 @@ def get_obj_mass_density(obj: bpy.types.Object) -> Union[float, None]:
|
|||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def get_opening_type(opening: bpy.types.Object, obj: bpy.types.Object) -> Literal["OPENING", "RECESS"]:
|
def get_opening_type(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
|
||||||
|
|
||||||
@@ -565,8 +611,8 @@ def get_opening_type(opening: bpy.types.Object, obj: bpy.types.Object) -> Litera
|
|||||||
# If an odd number of face-normal vectors intersect with the object, then the void is a recess, otherwise it's an opening
|
# If an odd number of face-normal vectors intersect with the object, then the void is a recess, otherwise it's an opening
|
||||||
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(
|
||||||
obj: bpy.types.Object,
|
obj: bpy.types.Object,
|
||||||
angle_z1: int = 45,
|
angle_z1: int = 45,
|
||||||
angle_z2: int = 135,
|
angle_z2: int = 135,
|
||||||
@@ -625,8 +671,8 @@ def get_opening_area(
|
|||||||
|
|
||||||
return total_opening_area
|
return total_opening_area
|
||||||
|
|
||||||
def get_lateral_area(
|
|
||||||
|
|
||||||
|
def get_lateral_area(
|
||||||
obj: bpy.types.Object,
|
obj: bpy.types.Object,
|
||||||
subtract_openings: bool = True,
|
subtract_openings: bool = True,
|
||||||
exclude_end_areas: bool = False,
|
exclude_end_areas: bool = False,
|
||||||
@@ -665,9 +711,7 @@ def get_lateral_area(
|
|||||||
top_axis = x_axis
|
top_axis = x_axis
|
||||||
|
|
||||||
area = 0
|
area = 0
|
||||||
total_opening_area = (
|
total_opening_area = 0 if subtract_openings else get_opening_area(obj, angle_z1=angle_z1, angle_z2=angle_z2)
|
||||||
0 if subtract_openings else get_opening_area(obj, angle_z1=angle_z1, angle_z2=angle_z2)
|
|
||||||
)
|
|
||||||
polygons = obj.data.polygons
|
polygons = obj.data.polygons
|
||||||
|
|
||||||
for polygon in polygons:
|
for polygon in polygons:
|
||||||
@@ -685,6 +729,7 @@ def get_lateral_area(
|
|||||||
area += polygon.area
|
area += polygon.area
|
||||||
return area + total_opening_area
|
return area + total_opening_area
|
||||||
|
|
||||||
|
|
||||||
def get_gross_side_area(obj: bpy.types.Object) -> float:
|
def get_gross_side_area(obj: bpy.types.Object) -> float:
|
||||||
if not has_openings(obj):
|
if not has_openings(obj):
|
||||||
return get_net_side_area(obj)
|
return get_net_side_area(obj)
|
||||||
@@ -693,14 +738,17 @@ def get_gross_side_area(obj: bpy.types.Object) -> float:
|
|||||||
|
|
||||||
return gross_side_area
|
return gross_side_area
|
||||||
|
|
||||||
|
|
||||||
def get_net_side_area(obj: bpy.types.Object) -> float:
|
def get_net_side_area(obj: bpy.types.Object) -> float:
|
||||||
net_side_area = get_lateral_area(obj, exclude_end_areas=True, main_axis="x") / 2
|
net_side_area = 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(obj: bpy.types.Object) -> float:
|
def get_outer_surface_area(obj: bpy.types.Object) -> float:
|
||||||
outer_surface_area = get_lateral_area(obj, exclude_end_areas=True, angle_z1=0, angle_z2=360)
|
outer_surface_area = 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(obj: bpy.types.Object) -> float:
|
def get_end_area(obj: bpy.types.Object) -> float:
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
gross_mesh = get_gross_element_mesh(element)
|
gross_mesh = get_gross_element_mesh(element)
|
||||||
@@ -715,6 +763,7 @@ def get_end_area(obj: bpy.types.Object) -> float:
|
|||||||
|
|
||||||
return end_area
|
return end_area
|
||||||
|
|
||||||
|
|
||||||
def get_gross_top_area(obj: bpy.types.Object, angle: int = 45) -> float:
|
def get_gross_top_area(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.
|
||||||
|
|
||||||
@@ -748,6 +797,7 @@ def get_gross_top_area(obj: bpy.types.Object, angle: int = 45) -> float:
|
|||||||
area += polygon.area
|
area += polygon.area
|
||||||
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(obj: bpy.types.Object, angle: int = 45, ignore_internal: bool = True) -> float:
|
def get_net_top_area(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.
|
||||||
@@ -775,6 +825,7 @@ def get_net_top_area(obj: bpy.types.Object, angle: int = 45, ignore_internal: bo
|
|||||||
|
|
||||||
return area
|
return area
|
||||||
|
|
||||||
|
|
||||||
def get_projected_area(obj, projection_axis: AxisType = "z", is_gross: bool = True) -> float:
|
def get_projected_area(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.
|
||||||
|
|
||||||
@@ -814,6 +865,7 @@ def get_projected_area(obj, projection_axis: AxisType = "z", is_gross: bool = Tr
|
|||||||
return projected_polygon.area + void_area
|
return projected_polygon.area + void_area
|
||||||
return projected_polygon.area
|
return projected_polygon.area
|
||||||
|
|
||||||
|
|
||||||
def get_OBB_object(obj: bpy.types.Object) -> bpy.types.Object:
|
def get_OBB_object(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.
|
||||||
|
|
||||||
@@ -855,6 +907,7 @@ def get_OBB_object(obj: bpy.types.Object) -> bpy.types.Object:
|
|||||||
|
|
||||||
return new_OBB_object
|
return new_OBB_object
|
||||||
|
|
||||||
|
|
||||||
def get_AABB_object(obj: bpy.types.Object) -> bpy.types.Object:
|
def get_AABB_object(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.
|
||||||
|
|
||||||
@@ -909,8 +962,8 @@ def get_AABB_object(obj: bpy.types.Object) -> bpy.types.Object:
|
|||||||
|
|
||||||
return new_AABB_object
|
return new_AABB_object
|
||||||
|
|
||||||
def get_bisected_obj(
|
|
||||||
|
|
||||||
|
def get_bisected_obj(
|
||||||
obj: bpy.types.Object,
|
obj: bpy.types.Object,
|
||||||
plane_co_pos: VectorTuple,
|
plane_co_pos: VectorTuple,
|
||||||
plane_no_pos: VectorTuple,
|
plane_no_pos: VectorTuple,
|
||||||
@@ -954,6 +1007,7 @@ def get_bisected_obj(
|
|||||||
|
|
||||||
return bis_obj
|
return bis_obj
|
||||||
|
|
||||||
|
|
||||||
def get_total_contact_area(obj: bpy.types.Object, class_filter: list[str] = ["IfcElement"]) -> float:
|
def get_total_contact_area(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.
|
||||||
|
|
||||||
@@ -970,6 +1024,7 @@ def get_total_contact_area(obj: bpy.types.Object, class_filter: list[str] = ["If
|
|||||||
|
|
||||||
return total_contact_area
|
return total_contact_area
|
||||||
|
|
||||||
|
|
||||||
def get_touching_objects(obj: bpy.types.Object, class_filter: list[str]) -> list[bpy.types.Object]:
|
def get_touching_objects(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.
|
||||||
|
|
||||||
@@ -1019,6 +1074,7 @@ def get_touching_objects(obj: bpy.types.Object, class_filter: list[str]) -> list
|
|||||||
|
|
||||||
return touching_objects
|
return touching_objects
|
||||||
|
|
||||||
|
|
||||||
def get_contact_area(object1: bpy.types.Object, object2: bpy.types.Object) -> float:
|
def get_contact_area(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.
|
||||||
|
|
||||||
@@ -1034,8 +1090,8 @@ def get_contact_area(object1: bpy.types.Object, object2: bpy.types.Object) -> fl
|
|||||||
total_area += get_intersection_between_polygons(object1, poly1, object2, poly2)
|
total_area += get_intersection_between_polygons(object1, poly1, object2, poly2)
|
||||||
return total_area
|
return total_area
|
||||||
|
|
||||||
def get_intersection_between_polygons(
|
|
||||||
|
|
||||||
|
def get_intersection_between_polygons(
|
||||||
object1: bpy.types.Object,
|
object1: bpy.types.Object,
|
||||||
poly1: bpy.types.MeshPolygon,
|
poly1: bpy.types.MeshPolygon,
|
||||||
object2: bpy.types.Object,
|
object2: bpy.types.Object,
|
||||||
@@ -1083,9 +1139,8 @@ def get_intersection_between_polygons(
|
|||||||
# TopologicalError - Generated Geometry might be invalid
|
# TopologicalError - Generated Geometry might be invalid
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def create_shapely_polygon(
|
|
||||||
obj: bpy.types.Object, polygon: bpy.types.MeshPolygon, trans_matrix: Matrix
|
def create_shapely_polygon(obj: bpy.types.Object, polygon: bpy.types.MeshPolygon, trans_matrix: Matrix) -> Polygon:
|
||||||
) -> Polygon:
|
|
||||||
"""_summary_: Create a shapely polygon
|
"""_summary_: Create a shapely polygon
|
||||||
|
|
||||||
:param blender-object obj: Blender Object
|
:param blender-object obj: Blender Object
|
||||||
@@ -1104,11 +1159,13 @@ def create_shapely_polygon(
|
|||||||
polygon_tuples.append((x, y))
|
polygon_tuples.append((x, y))
|
||||||
return Polygon(polygon_tuples)
|
return Polygon(polygon_tuples)
|
||||||
|
|
||||||
|
|
||||||
def get_gross_element_mesh(element: ifcopenshell.entity_instance) -> bpy.types.Mesh:
|
def get_gross_element_mesh(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 create_mesh_from_shape(element, settings)
|
return create_mesh_from_shape(element, settings)
|
||||||
|
|
||||||
|
|
||||||
def create_mesh_from_shape(
|
def create_mesh_from_shape(
|
||||||
element: ifcopenshell.entity_instance, settings: Optional[ifcopenshell.geom.settings] = None
|
element: ifcopenshell.entity_instance, settings: Optional[ifcopenshell.geom.settings] = None
|
||||||
) -> bpy.types.Mesh:
|
) -> bpy.types.Mesh:
|
||||||
@@ -1138,11 +1195,13 @@ def create_mesh_from_shape(
|
|||||||
mesh.update()
|
mesh.update()
|
||||||
return mesh
|
return mesh
|
||||||
|
|
||||||
|
|
||||||
def get_bmesh_from_mesh(mesh: bpy.types.Mesh) -> bmesh.types.BMesh:
|
def get_bmesh_from_mesh(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(o: bpy.types.Object) -> AxisType:
|
def get_object_main_axis(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.
|
||||||
|
|
||||||
@@ -1162,6 +1221,7 @@ def get_object_main_axis(o: bpy.types.Object) -> AxisType:
|
|||||||
else:
|
else:
|
||||||
return "x"
|
return "x"
|
||||||
|
|
||||||
|
|
||||||
def is_opening_horizontal(o: bpy.types.Object) -> bool:
|
def is_opening_horizontal(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
|
||||||
@@ -1169,10 +1229,12 @@ def is_opening_horizontal(o: bpy.types.Object) -> bool:
|
|||||||
|
|
||||||
return z < x and z < y
|
return z < x and z < y
|
||||||
|
|
||||||
|
|
||||||
def delete_mesh(mesh: bpy.types.Mesh) -> None:
|
def delete_mesh(mesh: bpy.types.Mesh) -> None:
|
||||||
mesh.user_clear()
|
mesh.user_clear()
|
||||||
bpy.data.meshes.remove(mesh)
|
bpy.data.meshes.remove(mesh)
|
||||||
|
|
||||||
|
|
||||||
def delete_obj(obj: bpy.types.Object) -> None:
|
def delete_obj(obj: bpy.types.Object) -> None:
|
||||||
bpy.data.objects.remove(obj, do_unlink=True)
|
bpy.data.objects.remove(obj, do_unlink=True)
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,11 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
|
|
||||||
|
|
||||||
def refresh():
|
def refresh():
|
||||||
QtoData.is_loaded = False
|
QtoData.is_loaded = False
|
||||||
|
|
||||||
|
|
||||||
class QtoData:
|
class QtoData:
|
||||||
data = {}
|
data = {}
|
||||||
is_loaded = False
|
is_loaded = False
|
||||||
@@ -29,9 +31,9 @@ class QtoData:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def load(cls):
|
def load(cls):
|
||||||
cls.data = {
|
cls.data = {
|
||||||
"has_cost_item" : cls.has_cost_item(),
|
"has_cost_item": cls.has_cost_item(),
|
||||||
"relating_cost_items" : cls.relating_cost_items(),
|
"relating_cost_items": cls.relating_cost_items(),
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.is_loaded = True
|
cls.is_loaded = True
|
||||||
|
|
||||||
@@ -53,23 +55,13 @@ class QtoData:
|
|||||||
for relating_cost_item in relating_cost_items:
|
for relating_cost_item in relating_cost_items:
|
||||||
results.append(
|
results.append(
|
||||||
{
|
{
|
||||||
'cost_item_id' : relating_cost_item['cost_item_id'],
|
"cost_item_id": relating_cost_item["cost_item_id"],
|
||||||
'cost_item_name' : relating_cost_item['cost_item_name'],
|
"cost_item_name": relating_cost_item["cost_item_name"],
|
||||||
'quantity_id' : relating_cost_item['quantity_id'],
|
"quantity_id": relating_cost_item["quantity_id"],
|
||||||
'quantity_name' : relating_cost_item['quantity_name'],
|
"quantity_name": relating_cost_item["quantity_name"],
|
||||||
'quantity_value' : relating_cost_item['quantity_value'],
|
"quantity_value": relating_cost_item["quantity_value"],
|
||||||
'quantity_type' : relating_cost_item['quantity_type'],
|
"quantity_type": relating_cost_item["quantity_type"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import blenderbim.tool as tool
|
|||||||
import blenderbim.core.qto as core
|
import blenderbim.core.qto as core
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from blenderbim.bim.module.qto import helper
|
from blenderbim.bim.module.qto import helper
|
||||||
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
|
|
||||||
|
|
||||||
|
|
||||||
class CalculateCircleRadius(bpy.types.Operator):
|
class CalculateCircleRadius(bpy.types.Operator):
|
||||||
@@ -85,104 +84,37 @@ class CalculateObjectVolumes(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ExecuteQtoMethod(bpy.types.Operator):
|
class CalculateSingleQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.execute_qto_method"
|
bl_idname = "bim.calculate_single_quantity"
|
||||||
bl_label = "Execute Qto Method"
|
bl_label = "Calculate Single Quantity"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
bl_description = "Calculate a single quantity using a function on the selected objects"
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return context.selected_objects
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
selected_mesh_objects = [o for o in context.selected_objects if o.type == "MESH"]
|
|
||||||
props = context.scene.BIMQtoProperties
|
|
||||||
result = 0
|
|
||||||
if props.qto_methods == "HEIGHT":
|
|
||||||
for obj in selected_mesh_objects:
|
|
||||||
result += helper.calculate_height(obj)
|
|
||||||
elif props.qto_methods == "VOLUME":
|
|
||||||
result = helper.calculate_volumes(selected_mesh_objects, context)
|
|
||||||
elif props.qto_methods == "FORMWORK":
|
|
||||||
result = helper.calculate_formwork_area(selected_mesh_objects, context)
|
|
||||||
elif props.qto_methods == "SIDE_FORMWORK":
|
|
||||||
result = helper.calculate_side_formwork_area(selected_mesh_objects, context)
|
|
||||||
elif props.qto_methods == "NetFootprintArea":
|
|
||||||
result = QtoCalculator().get_net_footprint_area(selected_mesh_objects[0])
|
|
||||||
elif props.qto_methods == "NetRoofprintArea":
|
|
||||||
result = QtoCalculator().get_net_roofprint_area(selected_mesh_objects[0])
|
|
||||||
elif props.qto_methods == "LateralArea":
|
|
||||||
result = QtoCalculator().get_lateral_area(selected_mesh_objects[0])
|
|
||||||
elif props.qto_methods == "TotalSurfaceArea":
|
|
||||||
result = QtoCalculator().get_total_surface_area(selected_mesh_objects[0])
|
|
||||||
elif props.qto_methods == "OpeningArea":
|
|
||||||
result = QtoCalculator().get_opening_area(selected_mesh_objects[0])
|
|
||||||
elif props.qto_methods == "GrossTopArea":
|
|
||||||
result = QtoCalculator().get_gross_top_area(selected_mesh_objects[0])
|
|
||||||
elif props.qto_methods == "NetTopArea":
|
|
||||||
result = QtoCalculator().get_net_top_area(selected_mesh_objects[0])
|
|
||||||
elif props.qto_methods == "ProjectedArea":
|
|
||||||
result = QtoCalculator().get_projected_area(selected_mesh_objects[0])
|
|
||||||
elif props.qto_methods == "TotalContactArea":
|
|
||||||
result = QtoCalculator().get_total_contact_area(selected_mesh_objects[0])
|
|
||||||
elif props.qto_methods == "ContactArea":
|
|
||||||
result = QtoCalculator().get_contact_area(selected_mesh_objects[0], selected_mesh_objects[1])
|
|
||||||
props.qto_result = str(round(result, 3))
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class QuantifyObjects(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.quantify_objects"
|
|
||||||
bl_label = "Quantify Objects"
|
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return IfcStore.get_file() and context.selected_objects
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
return IfcStore.execute_ifc_operator(self, context)
|
|
||||||
|
|
||||||
def _execute(self, context):
|
|
||||||
props = context.scene.BIMQtoProperties
|
|
||||||
self.file = IfcStore.get_file()
|
|
||||||
for obj in (o for o in context.selected_objects if o.type == "MESH"):
|
|
||||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
|
||||||
continue
|
|
||||||
result = 0
|
|
||||||
if props.qto_methods == "HEIGHT":
|
|
||||||
result = helper.calculate_height(obj)
|
|
||||||
elif props.qto_methods == "VOLUME":
|
|
||||||
result = helper.calculate_volumes([obj], context)
|
|
||||||
elif props.qto_methods == "FORMWORK":
|
|
||||||
result = helper.calculate_formwork_area([obj], context)
|
|
||||||
elif props.qto_methods == "SIDE_FORMWORK":
|
|
||||||
result = helper.calculate_side_formwork_area([obj], context)
|
|
||||||
if not result:
|
|
||||||
continue
|
|
||||||
result = round(result, 3)
|
|
||||||
qto = ifcopenshell.api.run(
|
|
||||||
"pset.add_qto",
|
|
||||||
self.file,
|
|
||||||
product=self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
|
|
||||||
name=props.qto_name,
|
|
||||||
)
|
|
||||||
ifcopenshell.api.run("pset.edit_qto", self.file, qto=qto, properties={props.prop_name: result})
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class AssignBaseQto(bpy.types.Operator, tool.Ifc.Operator):
|
|
||||||
bl_idname = "bim.assign_objects_base_qto"
|
|
||||||
bl_label = "Assign IFC Object Quantity Set"
|
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
|
||||||
bl_description = "Assign IFC quantity set to selected object"
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return tool.Ifc.get() and context.selected_objects
|
return tool.Ifc.get() and context.selected_objects
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
core.assign_objects_base_qto(tool.Ifc, tool.Qto, selected_objects=context.selected_objects)
|
import ifc5d.qto
|
||||||
|
|
||||||
|
props = context.scene.BIMQtoProperties
|
||||||
|
elements = set()
|
||||||
|
for obj in context.selected_objects:
|
||||||
|
element = tool.Ifc.get_entity(obj)
|
||||||
|
if element:
|
||||||
|
elements.add(element)
|
||||||
|
|
||||||
|
rules = {
|
||||||
|
"calculators": {
|
||||||
|
props.calculator: {
|
||||||
|
"IfcProduct": {props.qto_name: {props.prop_name: props.calculator_function}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ifc_file = tool.Ifc.get()
|
||||||
|
results = ifc5d.qto.quantify(ifc_file, elements, rules)
|
||||||
|
ifc5d.qto.edit_qtos(ifc_file, results)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -199,13 +131,14 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
import ifc5d.qto
|
import ifc5d.qto
|
||||||
|
|
||||||
|
props = context.scene.BIMQtoProperties
|
||||||
elements = set()
|
elements = set()
|
||||||
for obj in context.selected_objects:
|
for obj in context.selected_objects:
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
if element:
|
if element:
|
||||||
elements.add(element)
|
elements.add(element)
|
||||||
|
|
||||||
rules = ifc5d.qto.get_rules("IFC4QtoBaseQuantities")
|
rules = ifc5d.qto.rules[props.qto_rule]
|
||||||
|
|
||||||
ifc_file = tool.Ifc.get()
|
ifc_file = tool.Ifc.get()
|
||||||
results = ifc5d.qto.quantify(ifc_file, elements, rules)
|
results = ifc5d.qto.quantify(ifc_file, elements, rules)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import ifc5d.qto
|
||||||
from blenderbim.bim.prop import StrProperty, Attribute
|
from blenderbim.bim.prop import StrProperty, Attribute
|
||||||
from bpy.types import PropertyGroup
|
from bpy.types import PropertyGroup
|
||||||
from bpy.props import (
|
from bpy.props import (
|
||||||
@@ -31,34 +32,32 @@ from bpy.props import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_qto_rule(self, context):
|
||||||
|
results = []
|
||||||
|
for rule_id, rule in ifc5d.qto.rules.items():
|
||||||
|
results.append((rule_id, rule["name"], rule["description"]))
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def get_calculator(self, context):
|
||||||
|
results = []
|
||||||
|
for name, calculator in ifc5d.qto.calculators.items():
|
||||||
|
results.append((name, name, calculator.__doc__))
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def get_calculator_function(self, context):
|
||||||
|
calculator = ifc5d.qto.calculators[self.calculator]
|
||||||
|
results = []
|
||||||
|
for function in calculator.get_functions():
|
||||||
|
results.append((function.id, function.name, function.description))
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
class BIMQtoProperties(PropertyGroup):
|
class BIMQtoProperties(PropertyGroup):
|
||||||
|
qto_rule: EnumProperty(items=get_qto_rule, name="Qto Rule")
|
||||||
|
calculator: EnumProperty(items=get_calculator, name="Calculator")
|
||||||
|
calculator_function: EnumProperty(items=get_calculator_function, name="Calculator Function")
|
||||||
qto_result: StringProperty(default="", name="Qto Result")
|
qto_result: StringProperty(default="", name="Qto Result")
|
||||||
qto_methods: EnumProperty(
|
qto_name: StringProperty(name="Qto Name", default="My_Qto")
|
||||||
items=[
|
prop_name: StringProperty(name="Prop Name", default="MyDimension")
|
||||||
("HEIGHT", "Height", "Calculate the Z height of an object"),
|
|
||||||
("VOLUME", "Volume", "Calculate the volume of an object"),
|
|
||||||
(
|
|
||||||
"FORMWORK",
|
|
||||||
"Formwork",
|
|
||||||
"Calculate the exposed formwork for all bottoms and sides (e.g. for beams and slabs) of one or more objects",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"SIDE_FORMWORK",
|
|
||||||
"Side Formwork",
|
|
||||||
"Calculate the exposed formwork for all sides only (e.g. for columns) of one or more objects",
|
|
||||||
),
|
|
||||||
("NetFootprintArea", "Net footprint area", "Calculate the net footprint area"),
|
|
||||||
("NetRoofprintArea", "Net roofprint area", "Calculate the net roofprint area"),
|
|
||||||
("LateralArea", "Lateral area", "Calculate the lateral area"),
|
|
||||||
("TotalSurfaceArea", "Total surface area", "Calculate the total surface area"),
|
|
||||||
("OpeningArea", "Opening area", "Calculate the opening area"),
|
|
||||||
("GrossTopArea", "Gross top area", "Calculate the gross top area"),
|
|
||||||
("NetTopArea", "Net top area", "Calculate the net top area"),
|
|
||||||
("ProjectedArea", "Projected area", "Calculate the projected area"),
|
|
||||||
("TotalContactArea", "Total contact area", "Get the total contact area"),
|
|
||||||
("ContactArea", "Contact area between two objects", "Get the contact area")
|
|
||||||
],
|
|
||||||
name="Qto Methods",
|
|
||||||
)
|
|
||||||
qto_name: StringProperty(name="Qto Name")
|
|
||||||
prop_name: StringProperty(name="Prop Name")
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import bpy
|
|||||||
from blenderbim.bim.module.qto.data import QtoData
|
from blenderbim.bim.module.qto.data import QtoData
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_qto_utilities(bpy.types.Panel):
|
class BIM_PT_qto(bpy.types.Panel):
|
||||||
bl_idname = "BIM_PT_qto_utilities"
|
bl_idname = "BIM_PT_qto"
|
||||||
bl_label = "Quantity Take-off"
|
bl_label = "Quantity Take-off"
|
||||||
bl_options = {"DEFAULT_CLOSED"}
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
bl_space_type = "PROPERTIES"
|
bl_space_type = "PROPERTIES"
|
||||||
@@ -31,9 +31,56 @@ class BIM_PT_qto_utilities(bpy.types.Panel):
|
|||||||
bl_options = {"HIDE_HEADER"}
|
bl_options = {"HIDE_HEADER"}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
if not QtoData.is_loaded:
|
layout = self.layout
|
||||||
QtoData.load()
|
props = context.scene.BIMQtoProperties
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
if context.selected_objects:
|
||||||
|
row.label(text=f"Quantifying {len(context.selected_objects)} Selected Objects", icon="MOD_EDGESPLIT")
|
||||||
|
else:
|
||||||
|
row.label(text="Quantifying All Objects", icon="MOD_EDGESPLIT")
|
||||||
|
row = layout.row()
|
||||||
|
row.prop(props, "qto_rule", text="")
|
||||||
|
row = layout.row()
|
||||||
|
row.operator("bim.perform_quantity_take_off")
|
||||||
|
|
||||||
|
|
||||||
|
class BIM_PT_qto_manual(bpy.types.Panel):
|
||||||
|
bl_idname = "BIM_PT_qto_manual"
|
||||||
|
bl_label = "Manual Quantification"
|
||||||
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
|
bl_space_type = "PROPERTIES"
|
||||||
|
bl_region_type = "WINDOW"
|
||||||
|
bl_context = "scene"
|
||||||
|
bl_parent_id = "BIM_PT_tab_qto"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
props = context.scene.BIMQtoProperties
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row.prop(props, "calculator")
|
||||||
|
row = layout.row()
|
||||||
|
row.prop(props, "calculator_function", text="Function")
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(props, "qto_name", text="")
|
||||||
|
row.prop(props, "prop_name", text="")
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row.operator("bim.calculate_single_quantity")
|
||||||
|
|
||||||
|
|
||||||
|
class BIM_PT_qto_simple(bpy.types.Panel):
|
||||||
|
bl_idname = "BIM_PT_qto_simple"
|
||||||
|
bl_label = "Simple Quantity Calculator"
|
||||||
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
|
bl_space_type = "PROPERTIES"
|
||||||
|
bl_region_type = "WINDOW"
|
||||||
|
bl_context = "scene"
|
||||||
|
bl_parent_id = "BIM_PT_tab_qto"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
props = context.scene.BIMQtoProperties
|
props = context.scene.BIMQtoProperties
|
||||||
|
|
||||||
@@ -49,42 +96,44 @@ class BIM_PT_qto_utilities(bpy.types.Panel):
|
|||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator("bim.calculate_object_volumes")
|
row.operator("bim.calculate_object_volumes")
|
||||||
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.prop(props, "qto_methods", text="")
|
|
||||||
row.operator("bim.execute_qto_method", icon="PROPERTIES", text="")
|
|
||||||
|
|
||||||
row = layout.row(align=True)
|
class BIM_PT_qto_cost(bpy.types.Panel):
|
||||||
row.prop(props, "qto_name", text="")
|
bl_idname = "BIM_PT_qto_cost"
|
||||||
row.prop(props, "prop_name", text="")
|
bl_label = "Parametric Cost Relationships"
|
||||||
row.operator("bim.quantify_objects", icon="COPYDOWN", text="")
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
|
bl_space_type = "PROPERTIES"
|
||||||
|
bl_region_type = "WINDOW"
|
||||||
|
bl_context = "scene"
|
||||||
|
bl_parent_id = "BIM_PT_tab_qto"
|
||||||
|
|
||||||
row = layout.row(align=True)
|
def draw(self, context):
|
||||||
row.operator("bim.assign_objects_base_qto")
|
if not QtoData.is_loaded:
|
||||||
|
QtoData.load()
|
||||||
|
|
||||||
row = layout.row(align=True)
|
if not context.selected_objects:
|
||||||
row.operator("bim.calculate_all_quantities", icon="MOD_EDGESPLIT")
|
row = self.layout.row()
|
||||||
|
row.label(text="No Selected Object")
|
||||||
|
return
|
||||||
|
|
||||||
if context.selected_objects:
|
if not QtoData.data["has_cost_item"]:
|
||||||
row = layout.row(align=True)
|
row = self.layout.row()
|
||||||
row.label(text=f"Relating Cost Item:")
|
row.label(text="No Related Cost Item")
|
||||||
|
return
|
||||||
if QtoData.data['has_cost_item']:
|
|
||||||
for relating_cost_item in QtoData.data['relating_cost_items']:
|
|
||||||
row.label(text=f"\n")
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.label(text=f"Cost item name:")
|
|
||||||
row.label(text=f"{relating_cost_item['cost_item_name']}")
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.label(text=f"Quantity name:")
|
|
||||||
row.label(text=f"{relating_cost_item['quantity_name']}")
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.label(text=f"Quantity value:")
|
|
||||||
row.label(text=f"{relating_cost_item['quantity_value']}")
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.label(text=f"Quantity type:")
|
|
||||||
row.label(text=f"{relating_cost_item['quantity_type']}")
|
|
||||||
row = layout.row(align=True)
|
|
||||||
else:
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.label(text = f"No cost item related")
|
|
||||||
|
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.label(text="Relating Cost Item:")
|
||||||
|
for relating_cost_item in QtoData.data["relating_cost_items"]:
|
||||||
|
row.label(text="\n")
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.label(text="Cost item name:")
|
||||||
|
row.label(text=f"{relating_cost_item['cost_item_name']}")
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.label(text="Quantity name:")
|
||||||
|
row.label(text=f"{relating_cost_item['quantity_name']}")
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.label(text="Quantity value:")
|
||||||
|
row.label(text=f"{relating_cost_item['quantity_value']}")
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.label(text="Quantity type:")
|
||||||
|
row.label(text=f"{relating_cost_item['quantity_type']}")
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
|||||||
@@ -17,35 +17,14 @@
|
|||||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import bpy
|
import bpy
|
||||||
import ifcopenshell
|
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_circle_radius(qto: tool.Qto, obj: bpy.types.Object) -> float:
|
def calculate_circle_radius(qto: tool.Qto, obj: bpy.types.Object) -> float:
|
||||||
result = qto.get_radius_of_selected_vertices(obj)
|
result = qto.get_radius_of_selected_vertices(obj)
|
||||||
qto.set_qto_result(result)
|
qto.set_qto_result(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def assign_objects_base_qto(ifc: tool.Ifc, qto: tool.Qto, selected_objects: list[bpy.types.Object]) -> None:
|
|
||||||
for obj in selected_objects:
|
|
||||||
assign_object_base_qto(ifc, qto, obj)
|
|
||||||
|
|
||||||
|
|
||||||
def assign_object_base_qto(ifc: tool.Ifc, qto: tool.Qto, obj: bpy.types.Object) -> None:
|
|
||||||
product = ifc.get_entity(obj)
|
|
||||||
if not product:
|
|
||||||
return
|
|
||||||
base_quantity_name = qto.get_applicable_base_quantity_name(product)
|
|
||||||
if not base_quantity_name:
|
|
||||||
return
|
|
||||||
ifc.run(
|
|
||||||
"pset.add_qto",
|
|
||||||
product=product,
|
|
||||||
name=base_quantity_name,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "IFC4 Base Quantities - IfcOpenShell",
|
"name": "IFC4 Base Quantities - IfcOpenShell",
|
||||||
"description": "This ruleset quantifies every single possible standardised base quantity in IFC4 using only IfcOpenShell as a geometry processor.",
|
"description": "This ruleset quantifies every single possible standardised base quantity in IFC4 using only IfcOpenShell as a geometry processor.",
|
||||||
"calculators": {
|
"calculators": {
|
||||||
"IOSTriangulation": {
|
"IfcOpenShell": {
|
||||||
"IfcActuator": {
|
"IfcActuator": {
|
||||||
"Qto_ActuatorBaseQuantities": {
|
"Qto_ActuatorBaseQuantities": {
|
||||||
"GrossWeight": null
|
"GrossWeight": null
|
||||||
|
|||||||
+44
-13
@@ -23,16 +23,20 @@ import ifcopenshell.api
|
|||||||
import ifcopenshell.api.pset
|
import ifcopenshell.api.pset
|
||||||
import ifcopenshell.util.selector
|
import ifcopenshell.util.selector
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
from typing import Optional
|
from collections import namedtuple
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
|
||||||
def get_rules(name: str):
|
Function = namedtuple("Function", ["id", "name", "description"])
|
||||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
rules = {}
|
||||||
|
|
||||||
|
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
for name in ("IFC4QtoBaseQuantities", "IFC4QtoBaseQuantitiesBlender"):
|
||||||
with open(os.path.join(cwd, name + ".json"), "r") as f:
|
with open(os.path.join(cwd, name + ".json"), "r") as f:
|
||||||
return json.load(f)
|
rules[name] = json.load(f)
|
||||||
|
|
||||||
|
|
||||||
def quantify(ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_instance], rules: dict):
|
def quantify(ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_instance], rules: dict) -> dict:
|
||||||
results = {}
|
results = {}
|
||||||
for calculator, queries in rules["calculators"].items():
|
for calculator, queries in rules["calculators"].items():
|
||||||
calculator = calculators[calculator]
|
calculator = calculators[calculator]
|
||||||
@@ -43,7 +47,7 @@ def quantify(ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_inst
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
def edit_qtos(ifc_file, results):
|
def edit_qtos(ifc_file, results) -> None:
|
||||||
for element, qtos in results.items():
|
for element, qtos in results.items():
|
||||||
for name, quantities in qtos.items():
|
for name, quantities in qtos.items():
|
||||||
qto = ifcopenshell.util.element.get_pset(element, name, should_inherit=False)
|
qto = ifcopenshell.util.element.get_pset(element, name, should_inherit=False)
|
||||||
@@ -54,14 +58,17 @@ def edit_qtos(ifc_file, results):
|
|||||||
ifcopenshell.api.pset.edit_qto(ifc_file, qto=qto, properties=quantities)
|
ifcopenshell.api.pset.edit_qto(ifc_file, qto=qto, properties=quantities)
|
||||||
|
|
||||||
|
|
||||||
class IOSTriangulation:
|
class IfcOpenShell:
|
||||||
|
"""Calculates Model body context geometry using the default IfcOpenShell
|
||||||
|
iterator on triangulation elements."""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def calculate(
|
def calculate(
|
||||||
ifc_file: ifcopenshell.file,
|
ifc_file: ifcopenshell.file,
|
||||||
elements: set[ifcopenshell.entity_instance],
|
elements: set[ifcopenshell.entity_instance],
|
||||||
qtos: dict,
|
qtos: dict,
|
||||||
results: dict,
|
results: dict,
|
||||||
):
|
) -> None:
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.geom
|
import ifcopenshell.geom
|
||||||
import ifcopenshell.util.shape
|
import ifcopenshell.util.shape
|
||||||
@@ -89,10 +96,10 @@ class IOSTriangulation:
|
|||||||
tasks = []
|
tasks = []
|
||||||
|
|
||||||
if gross_qtos:
|
if gross_qtos:
|
||||||
tasks.append((IOSTriangulation.create_iterator(ifc_file, gross_settings, elements), gross_qtos))
|
tasks.append((IfcOpenShell.create_iterator(ifc_file, gross_settings, list(elements)), gross_qtos))
|
||||||
|
|
||||||
if net_qtos:
|
if net_qtos:
|
||||||
tasks.append((IOSTriangulation.create_iterator(ifc_file, net_settings, elements), net_qtos))
|
tasks.append((IfcOpenShell.create_iterator(ifc_file, net_settings, list(elements)), net_qtos))
|
||||||
|
|
||||||
for iterator, qtos in tasks:
|
for iterator, qtos in tasks:
|
||||||
if iterator.initialize():
|
if iterator.initialize():
|
||||||
@@ -108,13 +115,26 @@ class IOSTriangulation:
|
|||||||
break
|
break
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_iterator(ifc_file, settings, elements):
|
def create_iterator(
|
||||||
|
ifc_file: ifcopenshell.file, settings: ifcopenshell.geom.settings, elements: list[ifcopenshell.entity_instance]
|
||||||
|
) -> ifcopenshell.geom.iterator:
|
||||||
return ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=elements)
|
return ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=elements)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_functions() -> list[Function]:
|
||||||
|
return [
|
||||||
|
Function("get_volume", "Volume", "Calculates the volume of a manifold shape"),
|
||||||
|
Function("get_x", "X Length", "Calculates the length along the local X axis"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Blender:
|
class Blender:
|
||||||
|
"""Calculates geometry based on currently loaded Blender objects."""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def calculate(ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_instance], qtos: dict, results: dict):
|
def calculate(
|
||||||
|
ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_instance], qtos: dict, results: dict
|
||||||
|
) -> None:
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
import blenderbim.bim.module.qto.calculator as calculator
|
import blenderbim.bim.module.qto.calculator as calculator
|
||||||
|
|
||||||
@@ -132,5 +152,16 @@ class Blender:
|
|||||||
formula_function = formula_functions[formula] = getattr(calculator, formula)
|
formula_function = formula_functions[formula] = getattr(calculator, formula)
|
||||||
results[element][name][quantity] = formula_function(obj)
|
results[element][name][quantity] = formula_function(obj)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_functions() -> list[Function]:
|
||||||
|
return [
|
||||||
|
Function(
|
||||||
|
"get_linear_length",
|
||||||
|
"Maximum Bounding Length",
|
||||||
|
"Calculates the length of the maximum local bounding box",
|
||||||
|
),
|
||||||
|
Function("get_length", "Length", "Calculates the length assumed as the maximum of the local X or Y axis"),
|
||||||
|
]
|
||||||
|
|
||||||
calculators = {"Blender": Blender, "IOSTriangulation": IOSTriangulation}
|
|
||||||
|
calculators = {"Blender": Blender, "IfcOpenShell": IfcOpenShell}
|
||||||
|
|||||||
Reference in New Issue
Block a user