mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-08 17:01:40 +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.tool as tool
|
||||
import blenderbim.core.pset as core
|
||||
import blenderbim.core.qto as QtoCore
|
||||
import blenderbim.bim.module.pset.data
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
|
||||
|
||||
|
||||
class Operator:
|
||||
|
||||
@@ -20,16 +20,17 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AssignBaseQto,
|
||||
operator.CalculateCircleRadius,
|
||||
operator.CalculateEdgeLengths,
|
||||
operator.CalculateFaceAreas,
|
||||
operator.CalculateObjectVolumes,
|
||||
operator.ExecuteQtoMethod,
|
||||
operator.CalculateSingleQuantity,
|
||||
operator.PerformQuantityTakeOff,
|
||||
operator.QuantifyObjects,
|
||||
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:
|
||||
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
|
||||
:return float: Length
|
||||
def get_linear_length(o: bpy.types.Object) -> float:
|
||||
"""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
|
||||
y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length
|
||||
z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length
|
||||
return max(x, y, z)
|
||||
|
||||
|
||||
def get_length(o: bpy.types.Object, vg_index: Optional[int] = None, main_axis: str = "x") -> float:
|
||||
if vg_index is None:
|
||||
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)
|
||||
return length
|
||||
|
||||
|
||||
def get_stair_length(obj: bpy.types.Object) -> float:
|
||||
length = get_length(obj)
|
||||
height = get_height(obj)
|
||||
stair_length = math.sqrt(pow(length, 2) + pow(height, 2))
|
||||
return stair_length
|
||||
|
||||
|
||||
def get_net_stair_area(obj: bpy.types.Object) -> float:
|
||||
OBB_obj = get_OBB_object(obj)
|
||||
OBB_net_footprint_area = get_net_footprint_area(OBB_obj)
|
||||
return OBB_net_footprint_area
|
||||
|
||||
|
||||
def get_gross_stair_area(obj: bpy.types.Object) -> float:
|
||||
OBB_obj = get_OBB_object(obj)
|
||||
OBB_gross_footprint_area = get_gross_footprint_area(OBB_obj)
|
||||
return OBB_gross_footprint_area
|
||||
|
||||
|
||||
def get_parametric_axis(obj: bpy.types.Object) -> Literal["AXIS2", "AXIS3", None]:
|
||||
relating_type = ifcopenshell.util.element.get_type(tool.Ifc.get_entity(obj))
|
||||
if relating_type:
|
||||
@@ -105,6 +111,7 @@ def get_parametric_axis(obj: bpy.types.Object) -> Literal["AXIS2", "AXIS3", None
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def get_covering_gross_area(obj: bpy.types.Object) -> float:
|
||||
parametrix_axis = get_parametric_axis(obj)
|
||||
if not parametrix_axis:
|
||||
@@ -114,6 +121,7 @@ def get_covering_gross_area(obj: bpy.types.Object) -> float:
|
||||
elif parametrix_axis == "AXIS3":
|
||||
return get_gross_footprint_area(obj)
|
||||
|
||||
|
||||
def get_covering_net_area(obj: bpy.types.Object) -> float:
|
||||
parametrix_axis = get_parametric_axis(obj)
|
||||
if not parametrix_axis:
|
||||
@@ -123,6 +131,7 @@ def get_covering_net_area(obj: bpy.types.Object) -> float:
|
||||
elif parametrix_axis == "AXIS3":
|
||||
return get_net_footprint_area(obj)
|
||||
|
||||
|
||||
def get_covering_width(obj: bpy.types.Object) -> float:
|
||||
parametrix_axis = get_parametric_axis(obj)
|
||||
if not parametrix_axis:
|
||||
@@ -132,6 +141,7 @@ def get_covering_width(obj: bpy.types.Object) -> float:
|
||||
elif parametrix_axis == "AXIS3":
|
||||
return get_height(obj)
|
||||
|
||||
|
||||
def get_width(o: bpy.types.Object) -> float:
|
||||
"""_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
|
||||
return min(x, y)
|
||||
|
||||
|
||||
def get_height(o: bpy.types.Object) -> float:
|
||||
"""_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
|
||||
|
||||
|
||||
def get_opening_height(obj: bpy.types.Object) -> float:
|
||||
if is_opening_horizontal(obj):
|
||||
return get_width(obj)
|
||||
else:
|
||||
return get_height(obj)
|
||||
|
||||
|
||||
def get_opening_depth(obj: bpy.types.Object) -> float:
|
||||
if is_opening_horizontal(obj):
|
||||
return get_height(obj)
|
||||
else:
|
||||
return get_width(obj)
|
||||
|
||||
|
||||
def get_opening_mapping_area(obj: bpy.types.Object) -> float:
|
||||
if is_opening_horizontal(obj):
|
||||
return get_net_footprint_area(obj)
|
||||
else:
|
||||
return get_net_side_area(obj)
|
||||
|
||||
|
||||
def get_finish_ceiling_height(obj: bpy.types.Object) -> float:
|
||||
floor_height = get_finish_floor_height(obj)
|
||||
ceiling_height = get_ceiling_height(obj)
|
||||
finish_ceiling_height = ceiling_height - floor_height
|
||||
return finish_ceiling_height
|
||||
|
||||
|
||||
def get_max_global_z(obj: bpy.types.Object) -> float:
|
||||
z_values = [(obj.matrix_world @ Vector(co))[2] for co in obj.bound_box]
|
||||
return max(z_values)
|
||||
|
||||
|
||||
def get_min_global_z(obj: bpy.types.Object) -> float:
|
||||
z_values = [(obj.matrix_world @ Vector(co))[2] for co in obj.bound_box]
|
||||
return min(z_values)
|
||||
|
||||
|
||||
def get_finish_floor_height(obj: bpy.types.Object) -> float:
|
||||
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
|
||||
|
||||
|
||||
def get_ceiling_height(obj: bpy.types.Object) -> float:
|
||||
space_min_z_value = get_min_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
|
||||
|
||||
|
||||
def get_net_perimeter(o: bpy.types.Object) -> float:
|
||||
parsed_edges = []
|
||||
shared_edges = []
|
||||
@@ -234,6 +254,7 @@ def get_net_perimeter(o: bpy.types.Object) -> float:
|
||||
perimeter -= get_edge_key_distance(o, edge_key)
|
||||
return perimeter
|
||||
|
||||
|
||||
def get_gross_perimeter(o: bpy.types.Object) -> float:
|
||||
element = tool.Ifc.get_entity(o)
|
||||
mesh = get_gross_element_mesh(element)
|
||||
@@ -242,14 +263,17 @@ def get_gross_perimeter(o: bpy.types.Object) -> float:
|
||||
delete_obj(gross_obj)
|
||||
return gross_perimeter
|
||||
|
||||
|
||||
def get_space_net_perimeter(obj: bpy.types.Object) -> float:
|
||||
pass
|
||||
|
||||
|
||||
def get_rectangular_perimeter(obj: bpy.types.Object) -> float:
|
||||
length = get_length(obj, main_axis="x")
|
||||
height = get_height(obj)
|
||||
return (length + height) * 2
|
||||
|
||||
|
||||
def get_lowest_polygons(o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
|
||||
lowest_polygons = []
|
||||
lowest_z = None
|
||||
@@ -266,6 +290,7 @@ def get_lowest_polygons(o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
|
||||
lowest_z = z
|
||||
return lowest_polygons
|
||||
|
||||
|
||||
def get_highest_polygons(o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
|
||||
highest_polygons = []
|
||||
highest_z = None
|
||||
@@ -282,12 +307,15 @@ def get_highest_polygons(o: bpy.types.Object) -> list[bpy.types.MeshPolygon]:
|
||||
highest_z = z
|
||||
return highest_polygons
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def get_net_floor_area(obj: bpy.types.Object) -> float:
|
||||
decompositions = get_obj_decompositions(obj)
|
||||
if not decompositions:
|
||||
@@ -304,6 +332,7 @@ def get_net_floor_area(obj: bpy.types.Object) -> float:
|
||||
|
||||
return total_net_floor_area
|
||||
|
||||
|
||||
def get_gross_ceiling_area(obj: bpy.types.Object) -> float:
|
||||
decompositions = get_obj_decompositions(obj)
|
||||
if not decompositions:
|
||||
@@ -320,6 +349,7 @@ def get_gross_ceiling_area(obj: bpy.types.Object) -> float:
|
||||
|
||||
return total_gross_ceiling_area
|
||||
|
||||
|
||||
def get_net_ceiling_area(obj: bpy.types.Object) -> float:
|
||||
decompositions = get_obj_decompositions(obj)
|
||||
if not decompositions:
|
||||
@@ -340,6 +370,7 @@ def get_net_ceiling_area(obj: bpy.types.Object) -> float:
|
||||
|
||||
return total_net_ceiling_area
|
||||
|
||||
|
||||
def get_space_net_volume(obj: bpy.types.Object) -> float:
|
||||
decompositions = get_obj_decompositions(obj)
|
||||
if not decompositions:
|
||||
@@ -355,6 +386,7 @@ def get_space_net_volume(obj: bpy.types.Object) -> float:
|
||||
|
||||
return total_space_net_volume
|
||||
|
||||
|
||||
def get_net_footprint_area(o: bpy.types.Object) -> float:
|
||||
"""_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
|
||||
return area
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -382,6 +415,7 @@ def get_gross_footprint_area(o: bpy.types.Object) -> float:
|
||||
delete_mesh(mesh)
|
||||
return gross_footprint_area
|
||||
|
||||
|
||||
def get_net_roofprint_area(o: bpy.types.Object) -> float:
|
||||
# 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
|
||||
@@ -394,6 +428,7 @@ def get_net_roofprint_area(o: bpy.types.Object) -> float:
|
||||
area += polygon.area
|
||||
return area
|
||||
|
||||
|
||||
def get_side_area(o: bpy.types.Object) -> float:
|
||||
# There are a few dumb options for this, but this seems the dumbest
|
||||
# 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
|
||||
return max(x * z, y * z)
|
||||
|
||||
|
||||
def get_cross_section_area(obj: bpy.types.Object) -> float:
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
item = representation.Items[0]
|
||||
@@ -418,6 +454,7 @@ def get_cross_section_area(obj: bpy.types.Object) -> float:
|
||||
return area
|
||||
# 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:
|
||||
if vg_index is None:
|
||||
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
|
||||
return area
|
||||
|
||||
|
||||
def get_net_surface_area(obj: bpy.types.Object) -> float:
|
||||
return get_mesh_area(obj.data)
|
||||
|
||||
|
||||
def get_mesh_area(mesh: bpy.types.Mesh) -> float:
|
||||
area = 0
|
||||
for polygon in mesh.polygons:
|
||||
area += polygon.area
|
||||
return area
|
||||
|
||||
|
||||
def is_polygon_in_vg(polygon: bpy.types.MeshPolygon, vertices_in_vg: list[bpy.types.MeshVertex]) -> bool:
|
||||
for v in polygon.vertices:
|
||||
if v not in vertices_in_vg:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_net_volume(o: bpy.types.Object) -> float:
|
||||
o_mesh = bmesh.new()
|
||||
o_mesh.from_mesh(o.data)
|
||||
@@ -458,6 +499,7 @@ def get_net_volume(o: bpy.types.Object) -> float:
|
||||
o_mesh.free()
|
||||
return volume
|
||||
|
||||
|
||||
def get_gross_volume(o: bpy.types.Object) -> float:
|
||||
if not has_openings(o):
|
||||
return get_net_volume(o)
|
||||
@@ -473,17 +515,18 @@ def get_gross_volume(o: bpy.types.Object) -> float:
|
||||
|
||||
return gross_volume
|
||||
|
||||
def has_openings(
|
||||
obj: bpy.types.Object
|
||||
) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
|
||||
|
||||
def has_openings(obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
return element and getattr(element, "HasOpenings", [])
|
||||
|
||||
|
||||
def get_obj_decompositions(obj: bpy.types.Object) -> list[ifcopenshell.entity_instance]:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
decompositions = ifcopenshell.util.element.get_decomposition(element)
|
||||
return decompositions
|
||||
|
||||
|
||||
def get_gross_weight(obj: bpy.types.Object) -> Union[float, None]:
|
||||
obj_mass_density = get_obj_mass_density(obj)
|
||||
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
|
||||
return gross_weight
|
||||
|
||||
|
||||
def get_net_weight(obj: bpy.types.Object) -> Union[float, None]:
|
||||
obj_mass_density = get_obj_mass_density(obj)
|
||||
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
|
||||
return net_weight
|
||||
|
||||
|
||||
def get_obj_mass_density(obj: bpy.types.Object) -> Union[float, None]:
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
material = ifcopenshell.util.element.get_material(entity)
|
||||
@@ -546,6 +591,7 @@ def get_obj_mass_density(obj: bpy.types.Object) -> Union[float, None]:
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
def get_opening_type(opening: bpy.types.Object, obj: bpy.types.Object) -> Literal["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
|
||||
return "OPENING" if ray_intersections % 2 == 0 else "RECESS"
|
||||
|
||||
|
||||
def get_opening_area(
|
||||
|
||||
obj: bpy.types.Object,
|
||||
angle_z1: int = 45,
|
||||
angle_z2: int = 135,
|
||||
@@ -625,8 +671,8 @@ def get_opening_area(
|
||||
|
||||
return total_opening_area
|
||||
|
||||
|
||||
def get_lateral_area(
|
||||
|
||||
obj: bpy.types.Object,
|
||||
subtract_openings: bool = True,
|
||||
exclude_end_areas: bool = False,
|
||||
@@ -665,9 +711,7 @@ def get_lateral_area(
|
||||
top_axis = x_axis
|
||||
|
||||
area = 0
|
||||
total_opening_area = (
|
||||
0 if subtract_openings else get_opening_area(obj, angle_z1=angle_z1, angle_z2=angle_z2)
|
||||
)
|
||||
total_opening_area = 0 if subtract_openings else get_opening_area(obj, angle_z1=angle_z1, angle_z2=angle_z2)
|
||||
polygons = obj.data.polygons
|
||||
|
||||
for polygon in polygons:
|
||||
@@ -685,6 +729,7 @@ def get_lateral_area(
|
||||
area += polygon.area
|
||||
return area + total_opening_area
|
||||
|
||||
|
||||
def get_gross_side_area(obj: bpy.types.Object) -> float:
|
||||
if not has_openings(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
|
||||
|
||||
|
||||
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
|
||||
return net_side_area
|
||||
|
||||
|
||||
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)
|
||||
return outer_surface_area
|
||||
|
||||
|
||||
def get_end_area(obj: bpy.types.Object) -> float:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
gross_mesh = get_gross_element_mesh(element)
|
||||
@@ -715,6 +763,7 @@ def get_end_area(obj: bpy.types.Object) -> float:
|
||||
|
||||
return end_area
|
||||
|
||||
|
||||
def get_gross_top_area(obj: bpy.types.Object, angle: int = 45) -> float:
|
||||
"""_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
|
||||
return area + opening_area
|
||||
|
||||
|
||||
# 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:
|
||||
"""_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
|
||||
|
||||
|
||||
def get_projected_area(obj, projection_axis: AxisType = "z", is_gross: bool = True) -> float:
|
||||
"""_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
|
||||
|
||||
|
||||
def get_OBB_object(obj: bpy.types.Object) -> bpy.types.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
|
||||
|
||||
|
||||
def get_AABB_object(obj: bpy.types.Object) -> bpy.types.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
|
||||
|
||||
|
||||
def get_bisected_obj(
|
||||
|
||||
obj: bpy.types.Object,
|
||||
plane_co_pos: VectorTuple,
|
||||
plane_no_pos: VectorTuple,
|
||||
@@ -954,6 +1007,7 @@ def get_bisected_obj(
|
||||
|
||||
return bis_obj
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -970,6 +1024,7 @@ def get_total_contact_area(obj: bpy.types.Object, class_filter: list[str] = ["If
|
||||
|
||||
return total_contact_area
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -1019,6 +1074,7 @@ def get_touching_objects(obj: bpy.types.Object, class_filter: list[str]) -> list
|
||||
|
||||
return touching_objects
|
||||
|
||||
|
||||
def get_contact_area(object1: bpy.types.Object, object2: bpy.types.Object) -> float:
|
||||
"""_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)
|
||||
return total_area
|
||||
|
||||
|
||||
def get_intersection_between_polygons(
|
||||
|
||||
object1: bpy.types.Object,
|
||||
poly1: bpy.types.MeshPolygon,
|
||||
object2: bpy.types.Object,
|
||||
@@ -1083,9 +1139,8 @@ def get_intersection_between_polygons(
|
||||
# TopologicalError - Generated Geometry might be invalid
|
||||
return 0
|
||||
|
||||
def create_shapely_polygon(
|
||||
obj: bpy.types.Object, polygon: bpy.types.MeshPolygon, trans_matrix: Matrix
|
||||
) -> Polygon:
|
||||
|
||||
def create_shapely_polygon(obj: bpy.types.Object, polygon: bpy.types.MeshPolygon, trans_matrix: Matrix) -> Polygon:
|
||||
"""_summary_: Create a shapely polygon
|
||||
|
||||
:param blender-object obj: Blender Object
|
||||
@@ -1104,11 +1159,13 @@ def create_shapely_polygon(
|
||||
polygon_tuples.append((x, y))
|
||||
return Polygon(polygon_tuples)
|
||||
|
||||
|
||||
def get_gross_element_mesh(element: ifcopenshell.entity_instance) -> bpy.types.Mesh:
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, True)
|
||||
return create_mesh_from_shape(element, settings)
|
||||
|
||||
|
||||
def create_mesh_from_shape(
|
||||
element: ifcopenshell.entity_instance, settings: Optional[ifcopenshell.geom.settings] = None
|
||||
) -> bpy.types.Mesh:
|
||||
@@ -1138,11 +1195,13 @@ def create_mesh_from_shape(
|
||||
mesh.update()
|
||||
return mesh
|
||||
|
||||
|
||||
def get_bmesh_from_mesh(mesh: bpy.types.Mesh) -> bmesh.types.BMesh:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
return bm
|
||||
|
||||
|
||||
def get_object_main_axis(o: bpy.types.Object) -> AxisType:
|
||||
"""_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:
|
||||
return "x"
|
||||
|
||||
|
||||
def is_opening_horizontal(o: bpy.types.Object) -> bool:
|
||||
x = (Vector(o.bound_box[4]) - 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
|
||||
|
||||
|
||||
def delete_mesh(mesh: bpy.types.Mesh) -> None:
|
||||
mesh.user_clear()
|
||||
bpy.data.meshes.remove(mesh)
|
||||
|
||||
|
||||
def delete_obj(obj: bpy.types.Object) -> None:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
QtoData.is_loaded = False
|
||||
|
||||
|
||||
class QtoData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
@@ -29,9 +31,9 @@ class QtoData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"has_cost_item" : cls.has_cost_item(),
|
||||
"relating_cost_items" : cls.relating_cost_items(),
|
||||
}
|
||||
"has_cost_item": cls.has_cost_item(),
|
||||
"relating_cost_items": cls.relating_cost_items(),
|
||||
}
|
||||
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -53,23 +55,13 @@ class QtoData:
|
||||
for relating_cost_item in relating_cost_items:
|
||||
results.append(
|
||||
{
|
||||
'cost_item_id' : relating_cost_item['cost_item_id'],
|
||||
'cost_item_name' : relating_cost_item['cost_item_name'],
|
||||
'quantity_id' : relating_cost_item['quantity_id'],
|
||||
'quantity_name' : relating_cost_item['quantity_name'],
|
||||
'quantity_value' : relating_cost_item['quantity_value'],
|
||||
'quantity_type' : relating_cost_item['quantity_type'],
|
||||
"cost_item_id": relating_cost_item["cost_item_id"],
|
||||
"cost_item_name": relating_cost_item["cost_item_name"],
|
||||
"quantity_id": relating_cost_item["quantity_id"],
|
||||
"quantity_name": relating_cost_item["quantity_name"],
|
||||
"quantity_value": relating_cost_item["quantity_value"],
|
||||
"quantity_type": relating_cost_item["quantity_type"],
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import blenderbim.tool as tool
|
||||
import blenderbim.core.qto as core
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.qto import helper
|
||||
from blenderbim.bim.module.pset.qto_calculator import QtoCalculator
|
||||
|
||||
|
||||
class CalculateCircleRadius(bpy.types.Operator):
|
||||
@@ -85,104 +84,37 @@ class CalculateObjectVolumes(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExecuteQtoMethod(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_qto_method"
|
||||
bl_label = "Execute Qto Method"
|
||||
class CalculateSingleQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.calculate_single_quantity"
|
||||
bl_label = "Calculate Single Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@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"
|
||||
bl_description = "Calculate a single quantity using a function on the selected objects"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get() and context.selected_objects
|
||||
|
||||
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"}
|
||||
|
||||
|
||||
@@ -199,13 +131,14 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
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 = ifc5d.qto.get_rules("IFC4QtoBaseQuantities")
|
||||
rules = ifc5d.qto.rules[props.qto_rule]
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
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/>.
|
||||
|
||||
import bpy
|
||||
import ifc5d.qto
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
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):
|
||||
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_methods: EnumProperty(
|
||||
items=[
|
||||
("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")
|
||||
qto_name: StringProperty(name="Qto Name", default="My_Qto")
|
||||
prop_name: StringProperty(name="Prop Name", default="MyDimension")
|
||||
|
||||
@@ -20,8 +20,8 @@ import bpy
|
||||
from blenderbim.bim.module.qto.data import QtoData
|
||||
|
||||
|
||||
class BIM_PT_qto_utilities(bpy.types.Panel):
|
||||
bl_idname = "BIM_PT_qto_utilities"
|
||||
class BIM_PT_qto(bpy.types.Panel):
|
||||
bl_idname = "BIM_PT_qto"
|
||||
bl_label = "Quantity Take-off"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -31,9 +31,56 @@ class BIM_PT_qto_utilities(bpy.types.Panel):
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
def draw(self, context):
|
||||
if not QtoData.is_loaded:
|
||||
QtoData.load()
|
||||
layout = self.layout
|
||||
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
|
||||
props = context.scene.BIMQtoProperties
|
||||
|
||||
@@ -49,42 +96,44 @@ class BIM_PT_qto_utilities(bpy.types.Panel):
|
||||
row = layout.row(align=True)
|
||||
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)
|
||||
row.prop(props, "qto_name", text="")
|
||||
row.prop(props, "prop_name", text="")
|
||||
row.operator("bim.quantify_objects", icon="COPYDOWN", text="")
|
||||
class BIM_PT_qto_cost(bpy.types.Panel):
|
||||
bl_idname = "BIM_PT_qto_cost"
|
||||
bl_label = "Parametric Cost Relationships"
|
||||
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)
|
||||
row.operator("bim.assign_objects_base_qto")
|
||||
def draw(self, context):
|
||||
if not QtoData.is_loaded:
|
||||
QtoData.load()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.calculate_all_quantities", icon="MOD_EDGESPLIT")
|
||||
if not context.selected_objects:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Selected Object")
|
||||
return
|
||||
|
||||
if context.selected_objects:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"Relating Cost Item:")
|
||||
|
||||
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")
|
||||
if not QtoData.data["has_cost_item"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Related Cost Item")
|
||||
return
|
||||
|
||||
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/>.
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
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:
|
||||
result = qto.get_radius_of_selected_vertices(obj)
|
||||
qto.set_qto_result(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",
|
||||
"description": "This ruleset quantifies every single possible standardised base quantity in IFC4 using only IfcOpenShell as a geometry processor.",
|
||||
"calculators": {
|
||||
"IOSTriangulation": {
|
||||
"IfcOpenShell": {
|
||||
"IfcActuator": {
|
||||
"Qto_ActuatorBaseQuantities": {
|
||||
"GrossWeight": null
|
||||
|
||||
+44
-13
@@ -23,16 +23,20 @@ import ifcopenshell.api
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.selector
|
||||
import multiprocessing
|
||||
from typing import Optional
|
||||
from collections import namedtuple
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
def get_rules(name: str):
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
Function = namedtuple("Function", ["id", "name", "description"])
|
||||
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:
|
||||
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 = {}
|
||||
for calculator, queries in rules["calculators"].items():
|
||||
calculator = calculators[calculator]
|
||||
@@ -43,7 +47,7 @@ def quantify(ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_inst
|
||||
return results
|
||||
|
||||
|
||||
def edit_qtos(ifc_file, results):
|
||||
def edit_qtos(ifc_file, results) -> None:
|
||||
for element, qtos in results.items():
|
||||
for name, quantities in qtos.items():
|
||||
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)
|
||||
|
||||
|
||||
class IOSTriangulation:
|
||||
class IfcOpenShell:
|
||||
"""Calculates Model body context geometry using the default IfcOpenShell
|
||||
iterator on triangulation elements."""
|
||||
|
||||
@staticmethod
|
||||
def calculate(
|
||||
ifc_file: ifcopenshell.file,
|
||||
elements: set[ifcopenshell.entity_instance],
|
||||
qtos: dict,
|
||||
results: dict,
|
||||
):
|
||||
) -> None:
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.shape
|
||||
@@ -89,10 +96,10 @@ class IOSTriangulation:
|
||||
tasks = []
|
||||
|
||||
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:
|
||||
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:
|
||||
if iterator.initialize():
|
||||
@@ -108,13 +115,26 @@ class IOSTriangulation:
|
||||
break
|
||||
|
||||
@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)
|
||||
|
||||
@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:
|
||||
"""Calculates geometry based on currently loaded Blender objects."""
|
||||
|
||||
@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.bim.module.qto.calculator as calculator
|
||||
|
||||
@@ -132,5 +152,16 @@ class Blender:
|
||||
formula_function = formula_functions[formula] = getattr(calculator, formula)
|
||||
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