From aa21fe906093502d722943b5b3b1df0ca2f608ef Mon Sep 17 00:00:00 2001 From: Lucas Nascimento Date: Fri, 13 Dec 2024 17:07:53 -0300 Subject: [PATCH] separate shader file from decoration data --- .../bonsai/bim/module/structural/decorator.py | 2 +- .../module/structural/load_decoration_data.py | 937 +++++++++++++++ .../bonsai/bim/module/structural/shader.py | 1013 +---------------- .../bonsai/bim/module/structural/workspace.py | 4 +- 4 files changed, 1000 insertions(+), 956 deletions(-) create mode 100644 src/bonsai/bonsai/bim/module/structural/load_decoration_data.py diff --git a/src/bonsai/bonsai/bim/module/structural/decorator.py b/src/bonsai/bonsai/bim/module/structural/decorator.py index f65a0e5999..fcf9056a5f 100644 --- a/src/bonsai/bonsai/bim/module/structural/decorator.py +++ b/src/bonsai/bonsai/bim/module/structural/decorator.py @@ -8,7 +8,7 @@ from bpy_extras import view3d_utils from bpy.types import SpaceView3D from gpu_extras.batch import batch_for_shader from typing import Iterable, Union -from bonsai.bim.module.structural.shader import ShaderInfo +from bonsai.bim.module.structural.load_decoration_data import ShaderInfo class LoadsDecorator: """Decorator to show strucutural loads in 3D""" diff --git a/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py b/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py new file mode 100644 index 0000000000..bed81cf736 --- /dev/null +++ b/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py @@ -0,0 +1,937 @@ +import bpy +import gpu +import bmesh +import numpy as np +from math import sin +from mathutils import Vector, Matrix +import ifcopenshell +import ifcopenshell.api +import ifcopenshell.util.attribute +import bonsai.tool as tool +from bonsai.bim.ifc import IfcStore +from bonsai.bim.module.structural.shader import DecorationShader + + +class ShaderInfo: + def __init__(self): + self.is_empty = True + self.shader = DecorationShader() + #self.shader_type = shader_type + #self.args = {} + #self.indices = [] + self.curve_members = {} + self.point_members = {} + self.surface_members = {} + self.text_info = [] + self.info = [] + self.force_unit = "" + self.linear_force_unit = "" + self.planar_force_unit = "" + + def update(self): + self.info = [] + self.text_info = [] + self.curve_members = {} + self.point_members = {} + self.surface_members = {} + self.get_force_units() + self.get_strucutural_elements_and_activities() + self.get_linear_loads() + self.get_point_loads() + self.get_planar_loads() + if len(self.info): + self.is_empty = False + + def get_force_units(self): + def get_unit_symbol(unit: ifcopenshell.entity_instance) -> str: + prefix_symbols = { + "EXA": "E", + "PETA": "P", + "TERA": "T", + "GIGA": "G", + "MEGA": "M", + "KILO": "k", + "HECTO": "h", + "DECA": "da", + "DECI": "d", + "CENTI": "c", + "MILLI": "m", + "MICRO": "μ", + "NANO": "n", + "PICO": "p", + "FEMTO": "f", + "ATTO": "a", + } + + unit_symbols = { + # si units + "CUBIC_METRE": "m3", + "GRAM": "g", + "SECOND": "s", + "SQUARE_METRE": "m2", + "METRE": "m", + "NEWTON": "N", + "PASCAL": "Pa", + # conversion based units + "pound-force": "lbf", + 'pound-force per square inch': "psi", + "thou": "th", + "inch": "in", + "foot": "ft", + "yard": "yd", + "mile": "mi", + "square thou": "th2", + "square inch": "in2", + "square foot": "ft2", + "square yard": "yd2", + "acre": "ac", + "square mile": "mi2", + "cubic thou": "th3", + "cubic inch": "in3", + "cubic foot": "ft3", + "cubic yard": "yd3", + "cubic mile": "mi3", + "litre": "L", + "fluid ounce UK": "fl oz", + "fluid ounce US": "fl oz", + "pint UK": "pt", + "pint US": "pt", + "gallon UK": "gal", + "gallon US": "gal", + "degree": "°", + "ounce": "oz", + "pound": "lb", + "ton UK": "ton", + "ton US": "ton", + "lbf": "lbf", + "kip": "kip", + "psi": "psi", + "ksi": "ksi", + "minute": "min", + "hour": "hr", + "day": "day", + "btu": "btu", + "fahrenheit": "°F", + } + symbol = "" + if unit.is_a("IfcSIUnit"): + symbol += prefix_symbols.get(unit.Prefix, "") + symbol += unit_symbols.get(unit.Name.replace("METER", "METRE"), "?") + return symbol + + force_units = [u for u in tool.Ifc.get().by_type("IfcNamedUnit") + if u.UnitType == "FORCEUNIT"] + linear_force_units = [u for u in tool.Ifc.get().by_type("IfcDerivedUnit") + if u.UnitType == "LINEARFORCEUNIT"] + planar_force_units = [u for u in tool.Ifc.get().by_type("IfcDerivedUnit") + if u.UnitType == "PLANARFORCEUNIT"] + + conversion_force_unit = [u for u in force_units if u.is_a("IfcConversionBasedUnit")] + if len(conversion_force_unit) == 0: + conversion_force_unit.append(force_units[0]) + self.force_unit = get_unit_symbol(conversion_force_unit[0]) + first = "" + second = "" + for e in linear_force_units[0].Elements: + if e.Unit.UnitType == "FORCEUNIT": + first = get_unit_symbol(e.Unit) + if e.Unit.UnitType == "LENGTHUNIT": + second = get_unit_symbol(e.Unit) + self.linear_force_unit = first + "/" + second + first = "" + second = "" + for e in planar_force_units[0].Elements: + if e.Unit.UnitType == "FORCEUNIT": + first = get_unit_symbol(e.Unit) + if e.Unit.UnitType == "LENGTHUNIT": + second = get_unit_symbol(e.Unit)+"2" + if e.Unit.UnitType == "AREAUNIT": + second = get_unit_symbol(e.Unit) + self.planar_force_unit = first + "/" + second + + def get_strucutural_elements_and_activities(self): + + def populate_members_dict(dict_name,element, activity, factor): + dic = getattr(self,dict_name,None) + if dic is None: + return + member = dic.get(element.GlobalId) + if member is None: + dic.update({ + element.GlobalId: { + "member": element, + "activities": [(activity,factor)]} + }) + else: + member["activities"].append((activity,factor)) + + def recursive_subgroups(groups, rec_limit, activity_type, factor = 1): + if len(groups) == 0 or rec_limit == 0: + return None + for group in groups: + subgorups = [] + activities = [] + relationship = [rel for rel in group.IsGroupedBy] + coef = getattr(group, 'Coefficient', 1.0) + group_coef = coef if coef is not None else 1.0 + rel_factor = 1.0 + + for rel in relationship: + if rel.is_a("IfcRelAssignsToGroupByFactor"): + rel_factor = rel.Factor if rel.Factor is not None else 1.0 + objects = rel.RelatedObjects + subgorups = [sg for sg in objects if sg.is_a("IfcStructuralLoadGroup")] + activities = [a for a in objects if a.is_a("IfcStructuralActivity")] + factor = factor*group_coef*rel_factor + for activity in activities: + if len(activity.AssignedToStructuralItem): + element = activity.AssignedToStructuralItem[0].RelatingElement + if element is not None: + if activity_type == "Action": + if element.is_a("IfcStructuralCurveMember"): + populate_members_dict("curve_members",element,activity,factor) + elif element.is_a("IfcStructuralPointConnection"): + populate_members_dict("point_members",element,activity,factor) + elif element.is_a("IfcStructuralSurfaceMember"): + populate_members_dict("surface_members",element,activity,factor) + + elif activity_type == "External Reaction" and getattr(element,"AppliedCondition",None) is not None: + if element.is_a("IfcStructuralCurveMember"): + populate_members_dict("curve_members",element,activity,factor) + elif element.is_a("IfcStructuralPointConnection"): + populate_members_dict("point_members",element,activity,factor) + elif element.is_a("IfcStructuralSurfaceMember"): + populate_members_dict("surface_members",element,activity,factor) + recursive_subgroups(subgorups,rec_limit-1,activity_type,factor=factor) + + props = bpy.context.scene.BIMStructuralProperties + group_definition_id = int(props.load_group_to_show) + file = IfcStore.get_file() + groups = [file.by_id(group_definition_id)] + recursive_subgroups(groups,10,props.activity_type) + + def get_planar_loads(self): + list_of_surfaces = self.surface_members + shader = self.shader.get("PLANAR LOAD") + maximum = 0 + for value in list_of_surfaces.values(): + surf = value["member"] + activity_list = [getattr(a, 'RelatedStructuralActivity', None) for a in getattr(surf, 'AssignedStructuralActivity', None) + if getattr(a, 'RelatedStructuralActivity', None).is_a() in ['IfcStructuralPlanarAction','IfcStructuralSurfaceAction']] + activity_list = value["activities"] + if len(activity_list) == 0: + continue + rotation = self.get_surface_member_rotation(surf) + values = self.get_planar_loads_values(activity_list,rotation) + if maximum == 0: + maximum = max([abs(float(i)) for i in values]) + if maximum == 0: + continue + props = bpy.context.scene.BIMStructuralProperties + reference_frame = props.reference_frame + orientation = np.eye(3) + if reference_frame == "LOCAL_COORDS": + orientation = rotation + blender_object: bpy.types.Object = IfcStore.get_element(getattr(surf, 'GlobalId', None)) + mat = blender_object.matrix_world + mesh: bpy.types.Mesh = blender_object.data + + positions = [] + indices = [] + coord = [] + bm = bmesh.new() + bm.from_mesh(mesh) + bmesh.ops.triangulate(bm, faces = bm.faces) + bm.edges.ensure_lookup_table() + bm.verts.ensure_lookup_table() + bm.faces.ensure_lookup_table() + + add_index = len(positions) + for v in bm.verts: + p1 = np.array(mat @ v.co) + positions.append(p1) + p2 = p1 - (orientation@values)*0.2/maximum + positions.append(p2) + coord.append((float(p1[0]+p1[1]),0,1)) + coord.append((float(p1[0]+p1[1]),1,1)) + for e in bm.edges: + if len(e.link_faces) > 1: + continue + indices.append((2*e.verts[0].index, + 2*e.verts[0].index+1, + 2*e.verts[1].index)) + indices.append((2*e.verts[0].index+1, + 2*e.verts[1].index, + 2*e.verts[1].index+1)) + for p in bm.faces: + indices.append((2*p.verts[0].index+1, + 2*p.verts[1].index+1, + 2*p.verts[2].index+1)) + bmesh.ops.dissolve_limit(bm, angle_limit = 0.01, verts = bm.verts, edges = bm.edges) + bm.faces.ensure_lookup_table() + center = bm.faces[0].calc_center_bounds() + + self.text_info.append( + {"position": mat @ center - Vector((orientation@values)*0.2/maximum), + "normal": Vector(mesh.polygons[0].normal), + "text": f'{values[2]:.5f} {self.planar_force_unit}'} + ) + + self.info.append( + { + "shader": shader, + "args": {"position": positions, "coord": coord}, + "indices": indices, + "uniforms": [["color", (0.2,0,1,1)],["spacing", 0.2]] + } + ) + def get_planar_loads_values(self, activity_list,element_rotation_matrix): + values = np.zeros((3)) + for item in activity_list: + activity = item[0] + factor = item[1] + load = activity.AppliedLoad + temp = np.zeros((3)) + if load is not None and load.is_a("IfcStructuralLoadPlanarForce"): + temp[0] = load.PlanarForceX if load.PlanarForceX is not None else 0 + temp[1] = load.PlanarForceY if load.PlanarForceY is not None else 0 + temp[2] = load.PlanarForceZ if load.PlanarForceZ is not None else 0 + temp = temp*factor + transform = self.get_activity_transform_matrix(activity,element_rotation_matrix) + values += transform@temp + return values + + def get_surface_member_rotation(self,surface_member): + representation = ifcopenshell.util.representation.get_representation(surface_member, "Model") + repr_item = representation.Items[0] + placement = ifcopenshell.util.placement.get_axis2placement(repr_item.FaceSurface.Position) + rotation = placement[0:3,0:3] + return rotation + + def get_point_connection_rotation(self, point_connection): + if point_connection.ConditionCoordinateSystem is not None: + placement = ifcopenshell.util.placement.get_axis2placement(point_connection.ConditionCoordinateSystem) + else: + placement = np.eye(4) + rotation = placement[0:3,0:3] + return rotation + + def get_curve_member_rotation(self, curve_member): + z = curve_member.Axis.DirectionRatios + edge = curve_member.Representation.Representations[0].Items[0] + origin = edge.EdgeStart.VertexGeometry.Coordinates + end = edge.EdgeEnd.VertexGeometry.Coordinates + x = [c2 - c1 for c1, c2 in zip(origin, end)] + placement = ifcopenshell.util.placement.a2p(origin,z,x) + rotation = placement[0:3,0:3] + return rotation + + def get_activity_transform_matrix(self, activity, element_rotation_matrix): + "provides the transformation matrix to convert between reference frames" + global_or_local = activity.GlobalOrLocal + props = bpy.context.scene.BIMStructuralProperties + reference_frame = props.reference_frame + transform_matrix = np.eye(3) + if reference_frame == 'LOCAL_COORDS' and global_or_local != reference_frame: + transform_matrix = np.linalg.inv(element_rotation_matrix) + elif reference_frame == 'GLOBAL_COORDS' and global_or_local != reference_frame: + transform_matrix = element_rotation_matrix + return transform_matrix + + def get_point_loads(self): + list_of_point_connections = tool.Ifc.get().by_type("IfcStructuralPointConnection") + list_of_point_connections = self.point_members + for value in list_of_point_connections.values(): + conn = value["member"] + activity_list = [getattr(a, 'RelatedStructuralActivity', None) for a in getattr(conn, 'AssignedStructuralActivity', None) + if getattr(a, 'RelatedStructuralActivity', None).is_a() in ['IfcStructuralPointAction']] + activity_list = value["activities"] + if len(activity_list) == 0: + continue + blender_object = IfcStore.get_element(getattr(conn, 'GlobalId', None)) + if blender_object.type == 'MESH': + conn_location = blender_object.matrix_world @ blender_object.data.vertices[0].co + #get local coordinates of the connection + rotation = self.get_point_connection_rotation(conn) + loads = self.get_point_loads_values(activity_list, rotation) + self.get_point_shader_args(loads, conn_location, rotation) + + def get_point_shader_args(self,loads, location, rotation): + location = np.array(location) + indices = [] + text_info = [] + direction_dict = { + "fx": (np.array((1,0,0)),np.array((0,1,0)),np.array((0,0,1))), + "fy": (np.array((0,1,0)),np.array((1,0,0)),np.array((0,0,1))), + "fz": (np.array((0,0,1)),np.array((0,1,0)),np.array((1,0,0))), + "mx": (np.array((0,1,0)),np.array((0,0,1))), + "my": (np.array((1,0,0)),np.array((0,0,1))), + "mz": (np.array((1,0,0)),np.array((0,1,0))), + } + keys = ["fx","fy","fz","mx","my","mz"] + props = bpy.context.scene.BIMStructuralProperties + reference_frame = props.reference_frame + if reference_frame == "LOCAL_COORDS": + for key in keys: + tup = direction_dict[key] + li = [] + for item in tup: + li.append(rotation@item) + direction_dict[key] = li + + for i, key in enumerate(keys): + if loads[i] == 0: + continue + color = (1,0,0,1) + if i in [1,4]: + color = (0,1,0,1) + elif i in [2,5]: + color = (0,0,1,1) + d1 = -(direction_dict[key][0]*loads[i]) + d1 = d1/np.linalg.norm(d1) + if i < 3: + d2 = direction_dict[key][1] + d3 = direction_dict[key][2] + p1 = location + p2 = location + d1 + d2 + p3 = location + d1 - d2 + p4 = location + d1 + d3 + p5 = location + d1 - d3 + position = [p1,p2,p3,p4,p5] + indices = [(0,1,2),(0,3,4)] + c1 = (0,0,0) + c2 = (1,1,0) + c3 = (-1,1,0) + coords_for_shader = [c1,c2,c3,c2,c3] + shader = self.shader.get("SINGLE FORCE") + self.info.append( + { + "shader": shader, + "args": {"position": position,"coord": coords_for_shader}, + "indices": indices, + "uniforms": [["color", color],["spacing", 0.2]] + } + ) + self.text_info.append( + {"position": location + d1, + "normal": d2/np.linalg.norm(d2), + "text": f'{loads[i]:.2f} {self.force_unit}'} + ) + else: + d2 = d2 = direction_dict[key][1] + p1 = location - d2 + p2 = location + d1 + d2 + p3 = location - d1 + d2 + position = [p1,p2,p3] + indices = [(0,1,2)] + c1 = (-1,0,0) + c2 = (1,1,0) + c3 = (1,-1,0) + coords_for_shader = [c1,c2,c3] + shader = self.shader.get("SINGLE MOMENT") + self.info.append( + { + "shader": shader, + "args": {"position": position,"coord": coords_for_shader}, + "indices": indices, + "uniforms": [["color", color]] + } + ) + self.text_info.append( + {"position": location +0.25*(d1 + d2), + "normal": d2/np.linalg.norm(d2), + "text": f'{loads[i]:.2f} {self.force_unit}'}#change to moment unit + ) + + def get_point_loads_values(self,activity_list,element_rotation_matrix): + result_list = np.zeros(6) + attr_list = ['ForceX','ForceY','ForceZ','MomentX','MomentY','MomentZ'] + for item in activity_list: + activity = item[0] + factor = item[1] + load = activity.AppliedLoad + temp = np.zeros(6) + for i, attr in enumerate(attr_list): + value = 0 if getattr(load, attr, 0) is None else getattr(load, attr, 0) + temp[i] += value*factor + transform_3 = self.get_activity_transform_matrix(activity,element_rotation_matrix) + transform_6 = np.zeros((6,6)) + transform_6[0:3,0:3] = transform_3 + transform_6[3:6,3:6] = transform_3 + result_list += transform_6@temp + + return result_list + + + def get_linear_loads(self): #for now it only works for distributed loads + position = [] + indices = [] + sin_quad_lin = [] + coords_for_shader = [] + color = [] + text_info = [] + uniforms = [] + info = [] + + list_of_curve_members = tool.Ifc.get().by_type("IfcStructuralCurveMember") + list_of_curve_members = self.curve_members + for value in list_of_curve_members.values(): + member = value["member"] + activity_list = [getattr(a, 'RelatedStructuralActivity', None) for a in getattr(member, 'AssignedStructuralActivity', None) + if getattr(a, 'RelatedStructuralActivity', None).is_a() in ['IfcStructuralCurveAction','IfcStructuralLinearAction']] + activity_list = value["activities"] + if len(activity_list) == 0: + continue + # member is a structural curve member + # get Axis attribute from member -> (IFCDIRECTION) + # get Representation attribute from member -> (IFCPRODUCTDEFINITIONSHAPE) + # get Representations attribute from Representation -> (IFCTOPOLOGYREPRESENTATION) + # get Items attribute from Representations -> (IFCEDGE) + # get EdgeStart attribute from Items -> (IFCVERTEX) + # get EdgeEnd attribure from Items -> (IFCVERTEX) + # using blender just get the global coordinates of the first and second vertex in the mesh + + blender_object = IfcStore.get_element(getattr(member, 'GlobalId', None)) + + start_co = blender_object.matrix_world @ blender_object.data.vertices[0].co + end_co = blender_object.matrix_world @ blender_object.data.vertices[1].co + x_axis = Vector(end_co-start_co).normalized() + z_direction = getattr(member, 'Axis') + #local coordinates + z_axis = Vector(getattr(z_direction, 'DirectionRatios', None)).normalized() + y_axis = z_axis.cross(x_axis).normalized() + z_axis = x_axis.cross(y_axis).normalized() + rot = self.get_curve_member_rotation(member) + global_to_local = Matrix(((x_axis.x,y_axis.x,z_axis.x), + (x_axis.y,y_axis.y,z_axis.y), + (x_axis.z,y_axis.z,z_axis.z), + )) + global_to_local = Matrix(rot) + + #get shader args for each direction + props = bpy.context.scene.BIMStructuralProperties + reference_frame = props.reference_frame #make it a scene property so it can be changed in a panel + is_local = reference_frame == 'LOCAL_COORDS' + x_match = abs(Vector((1,0,0)).dot(x_axis)) > 0.99 + y_match = abs(Vector((0,1,0)).dot(x_axis)) > 0.99 + z_match = abs(Vector((0,0,1)).dot(x_axis)) > 0.99 + direction_dict = { + "fx": y_axis+z_axis if is_local else Vector((1,0,0)) if not x_match else Vector((0,1,1)), + "fy": y_axis if is_local else Vector((0,1,0)) if not y_match else Vector((1,0,1)), + "fz": z_axis if is_local else Vector((0,0,1)) if not z_match else Vector((1,1,0)), + "mx": z_axis-y_axis if is_local or x_match else Vector((1,0,0)).cross(x_axis), + "my": z_axis if is_local else Vector((-1,0,1)) if y_match else Vector((0,1,0)).cross(x_axis).normalized(), + "mz": y_axis if is_local else Vector((-1,1,0)) if z_match else Vector((0,0,1)).cross(x_axis).normalized() + } + match_dict = {'fx': x_match or is_local, 'fy': y_match, 'fz': z_match} + member_length = Vector(end_co-start_co).length + loads_dict, maxforce, point_loads = self.get_loads_per_direction(activity_list,global_to_local,member_length) + if len(point_loads): + for item in point_loads: + for sub_item in item: + pos = sub_item["pos"] + values = sub_item["values"] + pos_vector = start_co + x_axis*pos + self.get_point_shader_args(values,pos_vector) + if loads_dict is None: + continue + keys = ["fx","fy","fz","mx","my","mz"] + + for key in keys: + polyline = loads_dict[key]["polyline"] + sinus = loads_dict[key]["sinus"] + quadratic = loads_dict[key]["quadratic"] + constant = loads_dict[key]["constant"] + direction = direction_dict[key] #depends on the key and on the frame of reference + color_axis = (0,0,1,1) + if 'x' in key: + color_axis = (1,0,0,1) + if 'y' in key: + color_axis = (0,1,0,1) + + if 'f' in key: + if match_dict[key]: + shader = self.shader.get("PARALLEL DISTRIBUTED FORCE") + else: + shader = self.shader.get("PERPENDICULAR DISTRIBUTED FORCE") + else: + shader = self.shader.get("DISTRIBUTED MOMENT") + + addindex = len(position) + counter = 0 + for i in range(len(polyline)-1): + current = Vector(polyline[i]+[0]) + nextitem = Vector(polyline[i+1]+[0]) + + if any([current.y, nextitem.y,constant,quadratic,sinus]): #if there is load in the z direction + negative = -1*direction + start_co + x_axis*current.x + positive = direction + start_co + x_axis*current.x + position.append(negative) + coords_for_shader.append((current.x, 1.0,member_length)) + sin_quad_lin.append((sinus, quadratic, current.y + constant)) + color.append(color_axis) + #info to render load value + x = current.x/member_length + func = sin(x*3.1416)*sinus + (-4.*x*x+4.*x)*quadratic+constant+current.y + if func: + text_info.append( + {"position": -1*direction*func/maxforce + start_co + x_axis*current.x, + "normal": direction.cross(x_axis).normalized(), + "text": f'{func:.2f} {self.linear_force_unit}'} + ) + maxforce = max(maxforce,abs(func)) + position.append(positive) + coords_for_shader.append((current[0],-1.0,member_length)) + sin_quad_lin.append((sinus, quadratic, current.y + constant)) + color.append(color_axis) + + indices.append((0 + counter + addindex, + 1 + counter + addindex, + 2 + counter + addindex)) + indices.append((3 + counter + addindex, + 2 + counter + addindex, + 1 + counter + addindex)) + if i == len(polyline)-2: + negative = -1*direction + start_co + x_axis*nextitem.x + positive = direction + start_co + x_axis*nextitem.x + position.append(negative) + coords_for_shader.append((nextitem.x, 1.0,member_length)) + sin_quad_lin.append((sinus, quadratic, nextitem.y + constant)) + color.append(color_axis) + #info to render load value + x = nextitem.x/member_length + func = sin(x*3.1416)*sinus + (-4.*x*x+4.*x)*quadratic+constant+nextitem.y + if func: + text_info.append( + {"position": -1*direction*func/maxforce + start_co + x_axis*nextitem.x, + "normal": direction.cross(x_axis).normalized(), + "text": f'{func:.2f} {self.linear_force_unit}'} + ) + maxforce = max(maxforce,abs(func)) + position.append(positive) + coords_for_shader.append((nextitem.x,-1.0,member_length)) + sin_quad_lin.append((sinus, quadratic, nextitem.y + constant)) + color.append(color_axis) + + counter += 2 + if len(position): + self.info.append( + { + "shader": shader, + "args": {"position": position, "sin_quad_lin_forces": sin_quad_lin,"coord": coords_for_shader}, + "indices": indices, + "uniforms": [["color", color_axis],["spacing", 0.2],["maxload",maxforce]] + } + ) + position = [] + sin_quad_lin = [] + coords_for_shader = [] + indices = [] + for info in self.info: + info["uniforms"][2][1] = maxforce + + self.text_info = text_info + + + def get_loads_per_direction(self,activity_list,global_to_local,member_length): + """ returns a dict with values for applied loads in each direction + return = { + "fx": values_in_this_direction + "fy": values_in_this_direction + "fz": values_in_this_direction + "mx": values_in_this_direction + "my": values_in_this_direction + "mz": values_in_this_direction + } + values_in_this_direction = { + "constant": float, + "quadratic": float, + "sinus": float, + "polyline": list[(position: float, load: float),...] + } + """ + loads_dict = self.get_loads_dict(activity_list,global_to_local) + const = loads_dict["constant force"] + quad = loads_dict["quadratic force"] + sinus = loads_dict["sinus force"] + loads = loads_dict["linear load configuration"] + unique_list = self.getuniquepositionlist(loads) + final_list = [] + return_value = None + max_load = 0 + for pos in unique_list: + value = self.get_before_and_after(pos,loads) + if value["before"] == value["after"]: + final_list.append([pos]+value["before"]) + else: + final_list.append([pos]+value["before"]) + final_list.append([pos]+value["after"]) + + if not len(final_list) and any(const+quad+sinus): + final_list.append([0.0,0.0,0.0,0.0,0.0,0.0,0.0]) + final_list.append([member_length,0.0,0.0,0.0,0.0,0.0,0.0]) + + elif len(final_list): + if final_list[0][0] and any(const+quad+sinus): #if first item location is not 0 append an item at the zero + final_list = [[0.0,0.0,0.0,0.0,0.0,0.0,0.0]]+final_list + else: + del final_list[0] + if abs(final_list[-1][0] - member_length) > 0.01 and any(const+quad+sinus): + final_list.append([member_length,0.0,0.0,0.0,0.0,0.0,0.0]) + else: + del final_list[-1] + if len(final_list): + array = np.array(final_list) #7xn -> ["pos","fx","fy","fz","mx","my","mz"] + keys = ["fx","fy","fz","mx","my","mz"] + polyline = { + "fx": [], + "fy": [], + "fz": [], + "mx": [], + "my": [], + "mz": [], + } + return_value = { + "fx": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, + "fy": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, + "fz": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, + "mx": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, + "my": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, + "mz": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, + } + max_load = 0 + for component, key in enumerate(keys): + if(any([sinus[component], quad[component], const[component]]) or + any(item for item in array[:,component+1])): + + for currentitem in final_list: + polyline[key].append([currentitem[0],currentitem[component+1]]) + x = currentitem[0]/member_length + func = sin(x*3.1416)*sinus[component] + (-4.*x*x+4.*x)*quad[component]+const[component]+currentitem[component+1] + max_load = max(max_load,abs(sinus[component]+quad[component]+const[component]+currentitem[component+1])) + inner_dict = return_value[key] + inner_dict["constant"] = const[component] + inner_dict["quadratic"] = quad[component] + inner_dict["sinus"] = sinus[component] + inner_dict["polyline"] = polyline[key] + return_value[key] = inner_dict + + return return_value, max_load, loads_dict["point load configuration"] + + + def getuniquepositionlist(self, load_config_list): + """return an ordereded list of unique locations based on the load configuration list + ex: load_config_list = [[{"pos":1.0,...},{"pos":3.0,...}], + [{"pos":2.0,...},{"pos":3.0,...}], + [{"pos":1.5,...},{"pos":2.5,...}]] + return = [1.0, 1.5, 2.0, 2.5, 3.0] + """ + unique = [] + for config in load_config_list: + for info in config: + if info["pos"] in unique: + continue + unique.append(info["pos"]) + unique.sort() + return unique + + def interp1d(self,l1,l2, pos): + """ 1d linear interpolation for the vector components""" + fac = (l2[1]-l1[1])/(l2[0]-l1[0]) + v = l1[1] + fac*(pos-l1[0]) + return v + + def interpolate(self,pos,loadinfo,start,end,key): + """ interpolate the result vectors between load poits""" + result = Vector((0,0,0)) + for i in range(3): + value1 = [loadinfo[start]["pos"], loadinfo[start][key][i]] #[position, force_component] + value2= [loadinfo[end]["pos"], loadinfo[end][key][i]] # [position, force_component] + result[i] = self.interp1d(value1,value2, pos) # interpolated [position, force_component] + return result + + def get_before_and_after(self,pos,load_config_list): + """ get total values for forces and moments with polilyne distribution + before and after the position + ex: load_config_list = [[{"pos":1.0,...,"forces":(1,0,0),...},{"pos":3.0,...,"forces":(3,0,0),...}], + [{"pos":2.0,...,"forces":(1,0,0),...},{"pos":3.0,...,"forces":(1,0,0),...}], + [{"pos":1.5,...,"forces":(1,0,0),...},{"pos":2.5,...,"forces":(1,0,0),...}]] + pos = 2.0 + return = { + "before": (3,0,0,0,0,0), ->(fx, fy, fz, mx, my, mz) + " after": (4,0,0,0,0,0) ->(fx, fy, fz, mx, my, mz) + } + """ + force_before = Vector((0,0,0)) + force_after = Vector((0,0,0)) + moment_before = Vector((0,0,0)) + moment_after = Vector((0,0,0)) + + for config in load_config_list: + if pos < config[0]["pos"] or pos > config[-1]["pos"]: + continue + start = 0 + end = len(config)-1 + while end-start > 0: + if pos < config[start]["pos"] or pos > config[end]["pos"]: + break + if config[start]["pos"] == pos: + if config[start]["descr"] in ['start','middle']: + force_after += config[start]["forces"] + moment_after += config[start]["moments"] + elif config[start]["descr"] in ['end','middle']: + force_before += config[start]["forces"] + moment_before += config[start]["moments"] + + elif config[end]["pos"] == pos: + if config[end]["descr"] in ['start','middle']: + force_after += config[end]["forces"] + moment_after += config[end]["moments"] + elif config[end]["descr"] in ['end','middle']: + force_before += config[end]["forces"] + moment_before += config[end]["moments"] + + elif end-start == 1: + force_before += self.interpolate(pos,config,start,end,"forces") + force_after += self.interpolate(pos,config,start,end,"forces") + moment_before += self.interpolate(pos,config,start,end,"moments") + moment_after += self.interpolate(pos,config,start,end,"moments") + start += 1 + end -=1 + return_value = { + "before": [force_before.x,force_before.y,force_before.z, + moment_before.x,moment_before.y,moment_before.z], + "after": [force_after.x, force_after.y, force_after.z, + moment_after.x, moment_after.y, moment_after.z] + } + return return_value + + def get_loads_dict(self,activity_list,element_rotation_matrix): + """ + get load list + activity_list: list of IfcStructuralCurveAction or IfcStructuralCurveReaction + applied in the structural curve member + global_to_local: transformation matrix from global coordinates to local coordinetes + return: dict{ + "constant force": (fx,fy,fz,mx,my,mz), -> sum of linear loads applied with + constant distribution + "quadratic force": (fx,fy,fz,mx,my,mz), -> sum of linear loads applied with + quadratic distribution + "sinus force": (fx,fy,fz,mx,my,mz), -> sum of linear loads applied with + sinus distribution + "lienar load configuration": list -> list of load configurations for linear + and polyline distributions of linear loads + } + description of "linear load configuration": + list[ -> one item (list)for each IfcStructuralCurveAction applied in the member + with IfcStructuralLoadConfiguration as the applied load + list[ -> one item (dict) for each item found in the + Locations attribute of IfcLoadConfiguration + dict{ + "pos": float, -> local position along curve length + "descr": string, -> describe if the item is at the start, middle or end of the list + "forces": Vector, -> linear force applied at that point + "moments": Vector -> linear moment applied at that point + } + ] + ] + """ + constant_force = Vector((0,0,0)) + constant_moment = Vector((0,0,0)) + quadratic_force = Vector((0,0,0)) + quadratic_moment = Vector((0,0,0)) + sinus_force = Vector((0,0,0)) + sinus_moment = Vector((0,0,0)) + linear_load_configurations = [] + point_load_configurations = [] + + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get(),"LENGTHUNIT") + + def get_force_vector(load,transform_matrix,factor = 1.0): + x = 0 if getattr(load, 'LinearForceX', 0) is None else getattr(load, 'LinearForceX', 0) + y = 0 if getattr(load, 'LinearForceY', 0) is None else getattr(load, 'LinearForceY', 0) + z = 0 if getattr(load, 'LinearForceZ', 0) is None else getattr(load, 'LinearForceZ', 0) + return transform_matrix @ (Vector((x,y,z))*factor) + + def get_moment_vector(load,transform_matrix,factor = 1.0): + x = 0 if getattr(load, 'LinearMomentX', 0) is None else getattr(load, 'LinearMomentX', 0) + y = 0 if getattr(load, 'LinearMomentY', 0) is None else getattr(load, 'LinearMomentY', 0) + z = 0 if getattr(load, 'LinearMomentZ', 0) is None else getattr(load, 'LinearMomentZ', 0) + return transform_matrix @ (Vector((x,y,z))*factor) + + for item in activity_list: + activity = item[0] + factor = item[1] + load = activity.AppliedLoad + global_or_local = activity.GlobalOrLocal + props = bpy.context.scene.BIMStructuralProperties + reference_frame = props.reference_frame #make it a scene property so it can be changed in a panel + transform_matrix = Matrix() + if reference_frame == 'LOCAL_COORDS' and global_or_local != reference_frame: + transform_matrix = element_rotation_matrix + transform_matrix.invert() + elif reference_frame == 'GLOBAL_COORDS' and global_or_local != reference_frame: + transform_matrix = element_rotation_matrix + #values for linear loads + if load.is_a('IfcStructuralLoadConfiguration'): + locations = getattr(load, 'Locations', []) + values = [l for l in getattr(load, 'Values', None) + if l.is_a() == "IfcStructuralLoadLinearForce" + ] + config_list = [] + for i,l in enumerate(values): + forcevalues = get_force_vector(l,transform_matrix,factor) + momentvalues = get_moment_vector(l,transform_matrix,factor) + if i == 0: + descr = 'start' + elif i == len(values)-1: + descr = 'end' + else: + descr = 'middle' + config_list.append( + {"pos": locations[i][0]*unit_scale, + "descr": descr, + "forces":forcevalues, + "moments":momentvalues} + ) + linear_load_configurations.append(config_list) + #load configurations with point loads + values = [l for l in getattr(load, 'Values', None) + if l.is_a() == "IfcStructuralLoadSingleForce" + ] + attr_list = ['ForceX','ForceY','ForceZ','MomentX','MomentY','MomentZ'] + config_list = [] + for i,load in enumerate(values): + result_list = [0,0,0,0,0,0] + for j, attr in enumerate(attr_list): + value = 0 if getattr(load, attr, 0) is None else getattr(load, attr, 0) + result_list[j] += value*factor + config_list.append( + {"pos": locations[i][0]*unit_scale, + "values": result_list} + ) + point_load_configurations.append(config_list) + + else: + forcevalues = get_force_vector(load,transform_matrix,factor) + momentvalues = get_moment_vector(load,transform_matrix,factor) + if 'CONST' == getattr(activity, 'PredefinedType', None) or activity.is_a('IfcStructuralLinearAction'): + constant_force += forcevalues + constant_moment += momentvalues + elif 'PARABOLA' == getattr(activity, 'PredefinedType', None): + quadratic_force += forcevalues + quadratic_moment += momentvalues + elif 'SINUS' == getattr(activity, 'PredefinedType', None): + sinus_force += forcevalues + sinus_moment += momentvalues + return_value = { + "constant force": [constant_force.x,constant_force.y,constant_force.z, + constant_moment.x,constant_moment.y,constant_moment.z], + "quadratic force": [quadratic_force.x,quadratic_force.y,quadratic_force.z, + quadratic_moment.x,quadratic_moment.y,quadratic_moment.z], + "sinus force": [sinus_force.x,sinus_force.y,sinus_force.z, + sinus_moment.x,sinus_moment.y,sinus_moment.z], + "linear load configuration": linear_load_configurations, + "point load configuration": point_load_configurations + } + return return_value diff --git a/src/bonsai/bonsai/bim/module/structural/shader.py b/src/bonsai/bonsai/bim/module/structural/shader.py index 626cc9f3a9..881b97cc49 100644 --- a/src/bonsai/bonsai/bim/module/structural/shader.py +++ b/src/bonsai/bonsai/bim/module/structural/shader.py @@ -1,223 +1,57 @@ -import bpy + import gpu -import bmesh -import numpy as np -from math import sin -from mathutils import Vector, Matrix -import ifcopenshell -import ifcopenshell.api -import ifcopenshell.util.attribute -import bonsai.tool as tool -from bonsai.bim.ifc import IfcStore - -class ShaderInfo: +class DecorationShader: + "shader for the load decorations" def __init__(self): - self.is_empty = True - #self.shader = None - #self.shader_type = shader_type - #self.args = {} - #self.indices = [] - self.curve_members = {} - self.point_members = {} - self.surface_members = {} - self.text_info = [] - self.info = [] - self.force_unit = "" - self.linear_force_unit = "" - self.planar_force_unit = "" - - def update(self): - self.info = [] - self.text_info = [] - self.curve_members = {} - self.point_members = {} - self.surface_members = {} - self.get_force_units() - self.get_strucutural_elements_and_activities() - self.get_linear_loads() - self.get_point_loads() - self.get_planar_loads_2() - if len(self.info): - self.is_empty = False - - def get_force_units(self): - def get_unit_symbol(unit: ifcopenshell.entity_instance) -> str: - prefix_symbols = { - "EXA": "E", - "PETA": "P", - "TERA": "T", - "GIGA": "G", - "MEGA": "M", - "KILO": "k", - "HECTO": "h", - "DECA": "da", - "DECI": "d", - "CENTI": "c", - "MILLI": "m", - "MICRO": "μ", - "NANO": "n", - "PICO": "p", - "FEMTO": "f", - "ATTO": "a", - } + pass + def get(self, pattern: str) -> gpu.types.GPUShader: + """pattern: string description of the desired shader + Possible values + PERPENDICULAR DISTRIBUTED FORCE: pattern for distributed force + perpendicular to the curve member axis + PARALLEL DISTRIBUTED FORCE: pattern for distributed force + along the curve member axis + DISTRIBUTED MOMENT: pattern for distributed moment in curve members + SINGLE FORCE: pattern for single forces + SINGLE MOMENT: pattern for single moments + PLANAR LOAD: pattern for planar loads + """ + valid_patterns = {"PERPENDICULAR DISTRIBUTED FORCE", + "PARALLEL DISTRIBUTED FORCE", + "DISTRIBUTED MOMENT", + "SINGLE FORCE", + "SINGLE MOMENT", + "PLANAR LOAD" + } + if pattern not in valid_patterns: + raise ValueError("""pattern must be one of: + PERPENDICULAR DISTRIBUTED FORCE + PARALLEL DISTRIBUTED FORCE, + DISTRIBUTED MOMENT, + SINGLE FORCE, + SINGLE MOMENT, + PLANAR LOAD""") + if "DISTRIBUTED" in pattern.upper(): + shader = self.get_linear_shader(pattern) + return shader + elif "SINGLE" in pattern.upper(): + shader = self.get_point_shader(pattern) + return shader + elif "PLANAR" in pattern.upper(): + shader = self.get_planar_shader() + return shader - unit_symbols = { - # si units - "CUBIC_METRE": "m3", - "GRAM": "g", - "SECOND": "s", - "SQUARE_METRE": "m2", - "METRE": "m", - "NEWTON": "N", - "PASCAL": "Pa", - # conversion based units - "pound-force": "lbf", - 'pound-force per square inch': "psi", - "thou": "th", - "inch": "in", - "foot": "ft", - "yard": "yd", - "mile": "mi", - "square thou": "th2", - "square inch": "in2", - "square foot": "ft2", - "square yard": "yd2", - "acre": "ac", - "square mile": "mi2", - "cubic thou": "th3", - "cubic inch": "in3", - "cubic foot": "ft3", - "cubic yard": "yd3", - "cubic mile": "mi3", - "litre": "L", - "fluid ounce UK": "fl oz", - "fluid ounce US": "fl oz", - "pint UK": "pt", - "pint US": "pt", - "gallon UK": "gal", - "gallon US": "gal", - "degree": "°", - "ounce": "oz", - "pound": "lb", - "ton UK": "ton", - "ton US": "ton", - "lbf": "lbf", - "kip": "kip", - "psi": "psi", - "ksi": "ksi", - "minute": "min", - "hour": "hr", - "day": "day", - "btu": "btu", - "fahrenheit": "°F", - } - symbol = "" - if unit.is_a("IfcSIUnit"): - symbol += prefix_symbols.get(unit.Prefix, "") - symbol += unit_symbols.get(unit.Name.replace("METER", "METRE"), "?") - return symbol - - force_units = [u for u in tool.Ifc.get().by_type("IfcNamedUnit") - if u.UnitType == "FORCEUNIT"] - linear_force_units = [u for u in tool.Ifc.get().by_type("IfcDerivedUnit") - if u.UnitType == "LINEARFORCEUNIT"] - planar_force_units = [u for u in tool.Ifc.get().by_type("IfcDerivedUnit") - if u.UnitType == "PLANARFORCEUNIT"] - - conversion_force_unit = [u for u in force_units if u.is_a("IfcConversionBasedUnit")] - if len(conversion_force_unit) == 0: - conversion_force_unit.append(force_units[0]) - self.force_unit = get_unit_symbol(conversion_force_unit[0]) - first = "" - second = "" - for e in linear_force_units[0].Elements: - if e.Unit.UnitType == "FORCEUNIT": - first = get_unit_symbol(e.Unit) - if e.Unit.UnitType == "LENGTHUNIT": - second = get_unit_symbol(e.Unit) - self.linear_force_unit = first + "/" + second - first = "" - second = "" - for e in planar_force_units[0].Elements: - if e.Unit.UnitType == "FORCEUNIT": - first = get_unit_symbol(e.Unit) - if e.Unit.UnitType == "LENGTHUNIT": - second = get_unit_symbol(e.Unit)+"2" - if e.Unit.UnitType == "AREAUNIT": - second = get_unit_symbol(e.Unit) - self.planar_force_unit = first + "/" + second - - def get_strucutural_elements_and_activities(self): - - def populate_members_dict(dict_name,element, activity, factor): - dic = getattr(self,dict_name,None) - if dic is None: - return - member = dic.get(element.GlobalId) - if member is None: - dic.update({ - element.GlobalId: { - "member": element, - "activities": [(activity,factor)]} - }) - else: - member["activities"].append((activity,factor)) - - def recursive_subgroups(groups, rec_limit, activity_type, factor = 1): - if len(groups) == 0 or rec_limit == 0: - return None - for group in groups: - subgorups = [] - activities = [] - relationship = [rel for rel in group.IsGroupedBy] - coef = getattr(group, 'Coefficient', 1.0) - group_coef = coef if coef is not None else 1.0 - rel_factor = 1.0 - - for rel in relationship: - if rel.is_a("IfcRelAssignsToGroupByFactor"): - rel_factor = rel.Factor if rel.Factor is not None else 1.0 - objects = rel.RelatedObjects - subgorups = [sg for sg in objects if sg.is_a("IfcStructuralLoadGroup")] - activities = [a for a in objects if a.is_a("IfcStructuralActivity")] - factor = factor*group_coef*rel_factor - for activity in activities: - if len(activity.AssignedToStructuralItem): - element = activity.AssignedToStructuralItem[0].RelatingElement - if element is not None: - if activity_type == "Action": - if element.is_a("IfcStructuralCurveMember"): - populate_members_dict("curve_members",element,activity,factor) - elif element.is_a("IfcStructuralPointConnection"): - populate_members_dict("point_members",element,activity,factor) - elif element.is_a("IfcStructuralSurfaceMember"): - populate_members_dict("surface_members",element,activity,factor) - - elif activity_type == "External Reaction" and getattr(element,"AppliedCondition",None) is not None: - if element.is_a("IfcStructuralCurveMember"): - populate_members_dict("curve_members",element,activity,factor) - elif element.is_a("IfcStructuralPointConnection"): - populate_members_dict("point_members",element,activity,factor) - elif element.is_a("IfcStructuralSurfaceMember"): - populate_members_dict("surface_members",element,activity,factor) - recursive_subgroups(subgorups,rec_limit-1,activity_type,factor=factor) - - props = bpy.context.scene.BIMStructuralProperties - group_definition_id = int(props.load_group_to_show) - file = IfcStore.get_file() - groups = [file.by_id(group_definition_id)] - recursive_subgroups(groups,10,props.activity_type) - - - def get_shader(self, pattern): - """ param: pattern: type of pattern in ["force", "force match", "moment"] - return: shader""" - #if self.shader_type == "DistributedLoad": + def get_linear_shader(self, pattern: str) -> gpu.types.GPUShader: + """pattern: type of pattern + PERPENDICULAR DISTRIBUTED FORCE + PARALLEL DISTRIBUTED FORCE, + DISTRIBUTED MOMENT, + """ vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") vert_out.smooth('VEC3', "forces") vert_out.smooth('VEC3', "co") - + shader_info = gpu.types.GPUShaderCreateInfo() shader_info.push_constant('MAT4', "viewProjectionMatrix") shader_info.push_constant('VEC4', "color") @@ -239,8 +73,8 @@ class ShaderInfo: " forces = sin_quad_lin_forces;" "}" ) - - if pattern == "force": + + if pattern == "PERPENDICULAR DISTRIBUTED FORCE": shader_info.fragment_source( "void main()" "{" @@ -268,7 +102,7 @@ class ShaderInfo: "}" ) - if pattern == "force match": + elif pattern == "PARALLEL DISTRIBUTED FORCE": shader_info.fragment_source( "void main()" "{" @@ -297,7 +131,7 @@ class ShaderInfo: "}" ) - if pattern == "moment": + elif pattern == "DISTRIBUTED MOMENT": shader_info.fragment_source( "void main()" "{" @@ -340,9 +174,10 @@ class ShaderInfo: del shader_info return shader - def get_point_shader(self, pattern): - """ param: pattern: type of pattern in ["arrow", "circ arrow"] - return: shader""" + def get_point_shader(self, pattern: str) -> gpu.types.GPUShader: + """ param: pattern: type of pattern + SINGLE FORCE, + SINGLE MOMENT""" vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") vert_out.smooth('VEC3', "co") @@ -365,7 +200,7 @@ class ShaderInfo: "}" ) - if pattern == "arrow": + if pattern == "SINGLE FORCE": shader_info.fragment_source( "void main()" "{" @@ -377,7 +212,7 @@ class ShaderInfo: "}" ) - if pattern == "circ arrow": + elif pattern == "SINGLE MOMENT": shader_info.fragment_source( "void main()" "{" @@ -395,10 +230,8 @@ class ShaderInfo: del shader_info return shader - def get_planar_shader(self): - """ param: pattern: type of pattern in ["force", "force match", "moment"] - return: shader""" - #if self.shader_type == "DistributedLoad": + def get_planar_shader(self) -> gpu.types.GPUShader: + """shader for planar loads""" vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") vert_out.smooth('VEC3', "co") @@ -439,735 +272,9 @@ class ShaderInfo: "float d = clamp(0.2*y+(top+b+c)*mask,0.0,0.4);" "FragColor = vec4(color.xyz,d*color.w);" "}" - )#"float d = clamp(0.1+top+b+c,0.0,0.4);" + ) shader = gpu.shader.create_from_info(shader_info) del vert_out del shader_info return shader - - def get_planar_loads_2(self): - list_of_surfaces = self.surface_members - shader = self.get_planar_shader() - maximum = 0 - for value in list_of_surfaces.values(): - surf = value["member"] - activity_list = [getattr(a, 'RelatedStructuralActivity', None) for a in getattr(surf, 'AssignedStructuralActivity', None) - if getattr(a, 'RelatedStructuralActivity', None).is_a() in ['IfcStructuralPlanarAction','IfcStructuralSurfaceAction']] - activity_list = value["activities"] - if len(activity_list) == 0: - continue - rotation = self.get_surface_member_rotation(surf) - values = self.get_planar_loads_values(activity_list,rotation) - if maximum == 0: - maximum = max([abs(float(i)) for i in values]) - if maximum == 0: - continue - props = bpy.context.scene.BIMStructuralProperties - reference_frame = props.reference_frame - orientation = np.eye(3) - if reference_frame == "LOCAL_COORDS": - orientation = rotation - blender_object: bpy.types.Object = IfcStore.get_element(getattr(surf, 'GlobalId', None)) - mat = blender_object.matrix_world - mesh: bpy.types.Mesh = blender_object.data - - positions = [] - indices = [] - coord = [] - bm = bmesh.new() - bm.from_mesh(mesh) - bmesh.ops.triangulate(bm, faces = bm.faces) - bm.edges.ensure_lookup_table() - bm.verts.ensure_lookup_table() - bm.faces.ensure_lookup_table() - - add_index = len(positions) - for v in bm.verts: - p1 = np.array(mat @ v.co) - positions.append(p1) - p2 = p1 - (orientation@values)*0.2/maximum - positions.append(p2) - coord.append((float(p1[0]+p1[1]),0,1)) - coord.append((float(p1[0]+p1[1]),1,1)) - for e in bm.edges: - if len(e.link_faces) > 1: - continue - indices.append((2*e.verts[0].index, - 2*e.verts[0].index+1, - 2*e.verts[1].index)) - indices.append((2*e.verts[0].index+1, - 2*e.verts[1].index, - 2*e.verts[1].index+1)) - for p in bm.faces: - indices.append((2*p.verts[0].index+1, - 2*p.verts[1].index+1, - 2*p.verts[2].index+1)) - bmesh.ops.dissolve_limit(bm, angle_limit = 0.01, verts = bm.verts, edges = bm.edges) - bm.faces.ensure_lookup_table() - center = bm.faces[0].calc_center_bounds() - - self.text_info.append( - {"position": mat @ center - Vector((orientation@values)*0.2/maximum), - "normal": Vector(mesh.polygons[0].normal), - "text": f'{values[2]:.5f} {self.planar_force_unit}'} - ) - - self.info.append( - { - "shader": shader, - "args": {"position": positions, "coord": coord}, - "indices": indices, - "uniforms": [["color", (0.2,0,1,1)],["spacing", 0.2]] - } - ) - def get_planar_loads_values(self, activity_list,element_rotation_matrix): - values = np.zeros((3)) - for item in activity_list: - activity = item[0] - factor = item[1] - load = activity.AppliedLoad - temp = np.zeros((3)) - if load is not None and load.is_a("IfcStructuralLoadPlanarForce"): - temp[0] = load.PlanarForceX if load.PlanarForceX is not None else 0 - temp[1] = load.PlanarForceY if load.PlanarForceY is not None else 0 - temp[2] = load.PlanarForceZ if load.PlanarForceZ is not None else 0 - temp = temp*factor - transform = self.get_activity_transform_matrix(activity,element_rotation_matrix) - values += transform@temp - return values - - def get_surface_member_rotation(self,surface_member): - representation = ifcopenshell.util.representation.get_representation(surface_member, "Model") - repr_item = representation.Items[0] - placement = ifcopenshell.util.placement.get_axis2placement(repr_item.FaceSurface.Position) - rotation = placement[0:3,0:3] - return rotation - - def get_point_connection_rotation(self, point_connection): - if point_connection.ConditionCoordinateSystem is not None: - placement = ifcopenshell.util.placement.get_axis2placement(point_connection.ConditionCoordinateSystem) - else: - placement = np.eye(4) - rotation = placement[0:3,0:3] - return rotation - - def get_curve_member_rotation(self, curve_member): - z = curve_member.Axis.DirectionRatios - edge = curve_member.Representation.Representations[0].Items[0] - origin = edge.EdgeStart.VertexGeometry.Coordinates - end = edge.EdgeEnd.VertexGeometry.Coordinates - x = [c2 - c1 for c1, c2 in zip(origin, end)] - placement = ifcopenshell.util.placement.a2p(origin,z,x) - rotation = placement[0:3,0:3] - return rotation - - def get_activity_transform_matrix(self, activity, element_rotation_matrix): - "provides the transformation matrix to convert between reference frames" - global_or_local = activity.GlobalOrLocal - props = bpy.context.scene.BIMStructuralProperties - reference_frame = props.reference_frame - transform_matrix = np.eye(3) - if reference_frame == 'LOCAL_COORDS' and global_or_local != reference_frame: - transform_matrix = np.linalg.inv(element_rotation_matrix) - elif reference_frame == 'GLOBAL_COORDS' and global_or_local != reference_frame: - transform_matrix = element_rotation_matrix - return transform_matrix - - def get_point_loads(self): - list_of_point_connections = tool.Ifc.get().by_type("IfcStructuralPointConnection") - list_of_point_connections = self.point_members - for value in list_of_point_connections.values(): - conn = value["member"] - activity_list = [getattr(a, 'RelatedStructuralActivity', None) for a in getattr(conn, 'AssignedStructuralActivity', None) - if getattr(a, 'RelatedStructuralActivity', None).is_a() in ['IfcStructuralPointAction']] - activity_list = value["activities"] - if len(activity_list) == 0: - continue - blender_object = IfcStore.get_element(getattr(conn, 'GlobalId', None)) - if blender_object.type == 'MESH': - conn_location = blender_object.matrix_world @ blender_object.data.vertices[0].co - #get local coordinates of the connection - rotation = self.get_point_connection_rotation(conn) - loads = self.get_point_loads_values(activity_list, rotation) - self.get_point_shader_args(loads, conn_location, rotation) - - def get_point_shader_args(self,loads, location, rotation): - location = np.array(location) - indices = [] - text_info = [] - direction_dict = { - "fx": (np.array((1,0,0)),np.array((0,1,0)),np.array((0,0,1))), - "fy": (np.array((0,1,0)),np.array((1,0,0)),np.array((0,0,1))), - "fz": (np.array((0,0,1)),np.array((0,1,0)),np.array((1,0,0))), - "mx": (np.array((0,1,0)),np.array((0,0,1))), - "my": (np.array((1,0,0)),np.array((0,0,1))), - "mz": (np.array((1,0,0)),np.array((0,1,0))), - } - keys = ["fx","fy","fz","mx","my","mz"] - props = bpy.context.scene.BIMStructuralProperties - reference_frame = props.reference_frame - if reference_frame == "LOCAL_COORDS": - for key in keys: - tup = direction_dict[key] - li = [] - for item in tup: - li.append(rotation@item) - direction_dict[key] = li - - for i, key in enumerate(keys): - if loads[i] == 0: - continue - color = (1,0,0,1) - if i in [1,4]: - color = (0,1,0,1) - elif i in [2,5]: - color = (0,0,1,1) - d1 = -(direction_dict[key][0]*loads[i]) - d1 = d1/np.linalg.norm(d1) - if i < 3: - d2 = direction_dict[key][1] - d3 = direction_dict[key][2] - p1 = location - p2 = location + d1 + d2 - p3 = location + d1 - d2 - p4 = location + d1 + d3 - p5 = location + d1 - d3 - position = [p1,p2,p3,p4,p5] - indices = [(0,1,2),(0,3,4)] - c1 = (0,0,0) - c2 = (1,1,0) - c3 = (-1,1,0) - coords_for_shader = [c1,c2,c3,c2,c3] - shader = self.get_point_shader("arrow") - self.info.append( - { - "shader": shader, - "args": {"position": position,"coord": coords_for_shader}, - "indices": indices, - "uniforms": [["color", color],["spacing", 0.2]] - } - ) - self.text_info.append( - {"position": location + d1, - "normal": d2/np.linalg.norm(d2), - "text": f'{loads[i]:.2f} {self.force_unit}'} - ) - else: - d2 = d2 = direction_dict[key][1] - p1 = location - d2 - p2 = location + d1 + d2 - p3 = location - d1 + d2 - position = [p1,p2,p3] - indices = [(0,1,2)] - c1 = (-1,0,0) - c2 = (1,1,0) - c3 = (1,-1,0) - coords_for_shader = [c1,c2,c3] - shader = self.get_point_shader("circ arrow") - self.info.append( - { - "shader": shader, - "args": {"position": position,"coord": coords_for_shader}, - "indices": indices, - "uniforms": [["color", color]] - } - ) - self.text_info.append( - {"position": location +0.25*(d1 + d2), - "normal": d2/np.linalg.norm(d2), - "text": f'{loads[i]:.2f} {self.force_unit}'}#change to moment unit - ) - - def get_point_loads_values(self,activity_list,element_rotation_matrix): - result_list = np.zeros(6) - attr_list = ['ForceX','ForceY','ForceZ','MomentX','MomentY','MomentZ'] - for item in activity_list: - activity = item[0] - factor = item[1] - load = activity.AppliedLoad - temp = np.zeros(6) - for i, attr in enumerate(attr_list): - value = 0 if getattr(load, attr, 0) is None else getattr(load, attr, 0) - temp[i] += value*factor - transform_3 = self.get_activity_transform_matrix(activity,element_rotation_matrix) - transform_6 = np.zeros((6,6)) - transform_6[0:3,0:3] = transform_3 - transform_6[3:6,3:6] = transform_3 - result_list += transform_6@temp - - return result_list - - - def get_linear_loads(self): #for now it only works for distributed loads - position = [] - indices = [] - sin_quad_lin = [] - coords_for_shader = [] - color = [] - text_info = [] - uniforms = [] - info = [] - - list_of_curve_members = tool.Ifc.get().by_type("IfcStructuralCurveMember") - list_of_curve_members = self.curve_members - for value in list_of_curve_members.values(): - member = value["member"] - activity_list = [getattr(a, 'RelatedStructuralActivity', None) for a in getattr(member, 'AssignedStructuralActivity', None) - if getattr(a, 'RelatedStructuralActivity', None).is_a() in ['IfcStructuralCurveAction','IfcStructuralLinearAction']] - activity_list = value["activities"] - if len(activity_list) == 0: - continue - # member is a structural curve member - # get Axis attribute from member -> (IFCDIRECTION) - # get Representation attribute from member -> (IFCPRODUCTDEFINITIONSHAPE) - # get Representations attribute from Representation -> (IFCTOPOLOGYREPRESENTATION) - # get Items attribute from Representations -> (IFCEDGE) - # get EdgeStart attribute from Items -> (IFCVERTEX) - # get EdgeEnd attribure from Items -> (IFCVERTEX) - # using blender just get the global coordinates of the first and second vertex in the mesh - - blender_object = IfcStore.get_element(getattr(member, 'GlobalId', None)) - - start_co = blender_object.matrix_world @ blender_object.data.vertices[0].co - end_co = blender_object.matrix_world @ blender_object.data.vertices[1].co - x_axis = Vector(end_co-start_co).normalized() - z_direction = getattr(member, 'Axis') - #local coordinates - z_axis = Vector(getattr(z_direction, 'DirectionRatios', None)).normalized() - y_axis = z_axis.cross(x_axis).normalized() - z_axis = x_axis.cross(y_axis).normalized() - rot = self.get_curve_member_rotation(member) - global_to_local = Matrix(((x_axis.x,y_axis.x,z_axis.x), - (x_axis.y,y_axis.y,z_axis.y), - (x_axis.z,y_axis.z,z_axis.z), - )) - global_to_local = Matrix(rot) - - #get shader args for each direction - props = bpy.context.scene.BIMStructuralProperties - reference_frame = props.reference_frame #make it a scene property so it can be changed in a panel - is_local = reference_frame == 'LOCAL_COORDS' - x_match = abs(Vector((1,0,0)).dot(x_axis)) > 0.99 - y_match = abs(Vector((0,1,0)).dot(x_axis)) > 0.99 - z_match = abs(Vector((0,0,1)).dot(x_axis)) > 0.99 - direction_dict = { - "fx": y_axis+z_axis if is_local else Vector((1,0,0)) if not x_match else Vector((0,1,1)), - "fy": y_axis if is_local else Vector((0,1,0)) if not y_match else Vector((1,0,1)), - "fz": z_axis if is_local else Vector((0,0,1)) if not z_match else Vector((1,1,0)), - "mx": z_axis-y_axis if is_local or x_match else Vector((1,0,0)).cross(x_axis), - "my": z_axis if is_local else Vector((-1,0,1)) if y_match else Vector((0,1,0)).cross(x_axis).normalized(), - "mz": y_axis if is_local else Vector((-1,1,0)) if z_match else Vector((0,0,1)).cross(x_axis).normalized() - } - match_dict = {'fx': x_match or is_local, 'fy': y_match, 'fz': z_match} - member_length = Vector(end_co-start_co).length - loads_dict, maxforce, point_loads = self.get_loads_per_direction(activity_list,global_to_local,member_length) - if len(point_loads): - for item in point_loads: - for sub_item in item: - pos = sub_item["pos"] - values = sub_item["values"] - pos_vector = start_co + x_axis*pos - self.get_point_shader_args(values,pos_vector) - if loads_dict is None: - continue - keys = ["fx","fy","fz","mx","my","mz"] - - for key in keys: - polyline = loads_dict[key]["polyline"] - sinus = loads_dict[key]["sinus"] - quadratic = loads_dict[key]["quadratic"] - constant = loads_dict[key]["constant"] - direction = direction_dict[key] #depends on the key and on the frame of reference - color_axis = (0,0,1,1) - if 'x' in key: - color_axis = (1,0,0,1) - if 'y' in key: - color_axis = (0,1,0,1) - - if 'f' in key: - if match_dict[key]: - shader = self.get_shader("force match") - else: - shader = self.get_shader("force") - else: - shader = self.get_shader("moment") - - addindex = len(position) - counter = 0 - for i in range(len(polyline)-1): - current = Vector(polyline[i]+[0]) - nextitem = Vector(polyline[i+1]+[0]) - - if any([current.y, nextitem.y,constant,quadratic,sinus]): #if there is load in the z direction - negative = -1*direction + start_co + x_axis*current.x - positive = direction + start_co + x_axis*current.x - position.append(negative) - coords_for_shader.append((current.x, 1.0,member_length)) - sin_quad_lin.append((sinus, quadratic, current.y + constant)) - color.append(color_axis) - #info to render load value - x = current.x/member_length - func = sin(x*3.1416)*sinus + (-4.*x*x+4.*x)*quadratic+constant+current.y - if func: - text_info.append( - {"position": -1*direction*func/maxforce + start_co + x_axis*current.x, - "normal": direction.cross(x_axis).normalized(), - "text": f'{func:.2f} {self.linear_force_unit}'} - ) - maxforce = max(maxforce,abs(func)) - position.append(positive) - coords_for_shader.append((current[0],-1.0,member_length)) - sin_quad_lin.append((sinus, quadratic, current.y + constant)) - color.append(color_axis) - - indices.append((0 + counter + addindex, - 1 + counter + addindex, - 2 + counter + addindex)) - indices.append((3 + counter + addindex, - 2 + counter + addindex, - 1 + counter + addindex)) - if i == len(polyline)-2: - negative = -1*direction + start_co + x_axis*nextitem.x - positive = direction + start_co + x_axis*nextitem.x - position.append(negative) - coords_for_shader.append((nextitem.x, 1.0,member_length)) - sin_quad_lin.append((sinus, quadratic, nextitem.y + constant)) - color.append(color_axis) - #info to render load value - x = nextitem.x/member_length - func = sin(x*3.1416)*sinus + (-4.*x*x+4.*x)*quadratic+constant+nextitem.y - if func: - text_info.append( - {"position": -1*direction*func/maxforce + start_co + x_axis*nextitem.x, - "normal": direction.cross(x_axis).normalized(), - "text": f'{func:.2f} {self.linear_force_unit}'} - ) - maxforce = max(maxforce,abs(func)) - position.append(positive) - coords_for_shader.append((nextitem.x,-1.0,member_length)) - sin_quad_lin.append((sinus, quadratic, nextitem.y + constant)) - color.append(color_axis) - - counter += 2 - if len(position): - self.info.append( - { - "shader": shader, - "args": {"position": position, "sin_quad_lin_forces": sin_quad_lin,"coord": coords_for_shader}, - "indices": indices, - "uniforms": [["color", color_axis],["spacing", 0.2],["maxload",maxforce]] - } - ) - position = [] - sin_quad_lin = [] - coords_for_shader = [] - indices = [] - for info in self.info: - info["uniforms"][2][1] = maxforce - - self.text_info = text_info - - - def get_loads_per_direction(self,activity_list,global_to_local,member_length): - """ returns a dict with values for applied loads in each direction - return = { - "fx": values_in_this_direction - "fy": values_in_this_direction - "fz": values_in_this_direction - "mx": values_in_this_direction - "my": values_in_this_direction - "mz": values_in_this_direction - } - values_in_this_direction = { - "constant": float, - "quadratic": float, - "sinus": float, - "polyline": list[(position: float, load: float),...] - } - """ - loads_dict = self.get_loads_dict(activity_list,global_to_local) - const = loads_dict["constant force"] - quad = loads_dict["quadratic force"] - sinus = loads_dict["sinus force"] - loads = loads_dict["linear load configuration"] - unique_list = self.getuniquepositionlist(loads) - final_list = [] - return_value = None - max_load = 0 - for pos in unique_list: - value = self.get_before_and_after(pos,loads) - if value["before"] == value["after"]: - final_list.append([pos]+value["before"]) - else: - final_list.append([pos]+value["before"]) - final_list.append([pos]+value["after"]) - - if not len(final_list) and any(const+quad+sinus): - final_list.append([0.0,0.0,0.0,0.0,0.0,0.0,0.0]) - final_list.append([member_length,0.0,0.0,0.0,0.0,0.0,0.0]) - - elif len(final_list): - if final_list[0][0] and any(const+quad+sinus): #if first item location is not 0 append an item at the zero - final_list = [[0.0,0.0,0.0,0.0,0.0,0.0,0.0]]+final_list - else: - del final_list[0] - if abs(final_list[-1][0] - member_length) > 0.01 and any(const+quad+sinus): - final_list.append([member_length,0.0,0.0,0.0,0.0,0.0,0.0]) - else: - del final_list[-1] - if len(final_list): - array = np.array(final_list) #7xn -> ["pos","fx","fy","fz","mx","my","mz"] - keys = ["fx","fy","fz","mx","my","mz"] - polyline = { - "fx": [], - "fy": [], - "fz": [], - "mx": [], - "my": [], - "mz": [], - } - return_value = { - "fx": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, - "fy": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, - "fz": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, - "mx": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, - "my": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, - "mz": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []}, - } - max_load = 0 - for component, key in enumerate(keys): - if(any([sinus[component], quad[component], const[component]]) or - any(item for item in array[:,component+1])): - - for currentitem in final_list: - polyline[key].append([currentitem[0],currentitem[component+1]]) - x = currentitem[0]/member_length - func = sin(x*3.1416)*sinus[component] + (-4.*x*x+4.*x)*quad[component]+const[component]+currentitem[component+1] - max_load = max(max_load,abs(sinus[component]+quad[component]+const[component]+currentitem[component+1])) - inner_dict = return_value[key] - inner_dict["constant"] = const[component] - inner_dict["quadratic"] = quad[component] - inner_dict["sinus"] = sinus[component] - inner_dict["polyline"] = polyline[key] - return_value[key] = inner_dict - - return return_value, max_load, loads_dict["point load configuration"] - - - def getuniquepositionlist(self, load_config_list): - """return an ordereded list of unique locations based on the load configuration list - ex: load_config_list = [[{"pos":1.0,...},{"pos":3.0,...}], - [{"pos":2.0,...},{"pos":3.0,...}], - [{"pos":1.5,...},{"pos":2.5,...}]] - return = [1.0, 1.5, 2.0, 2.5, 3.0] - """ - unique = [] - for config in load_config_list: - for info in config: - if info["pos"] in unique: - continue - unique.append(info["pos"]) - unique.sort() - return unique - - def interp1d(self,l1,l2, pos): - """ 1d linear interpolation for the vector components""" - fac = (l2[1]-l1[1])/(l2[0]-l1[0]) - v = l1[1] + fac*(pos-l1[0]) - return v - - def interpolate(self,pos,loadinfo,start,end,key): - """ interpolate the result vectors between load poits""" - result = Vector((0,0,0)) - for i in range(3): - value1 = [loadinfo[start]["pos"], loadinfo[start][key][i]] #[position, force_component] - value2= [loadinfo[end]["pos"], loadinfo[end][key][i]] # [position, force_component] - result[i] = self.interp1d(value1,value2, pos) # interpolated [position, force_component] - return result - - def get_before_and_after(self,pos,load_config_list): - """ get total values for forces and moments with polilyne distribution - before and after the position - ex: load_config_list = [[{"pos":1.0,...,"forces":(1,0,0),...},{"pos":3.0,...,"forces":(3,0,0),...}], - [{"pos":2.0,...,"forces":(1,0,0),...},{"pos":3.0,...,"forces":(1,0,0),...}], - [{"pos":1.5,...,"forces":(1,0,0),...},{"pos":2.5,...,"forces":(1,0,0),...}]] - pos = 2.0 - return = { - "before": (3,0,0,0,0,0), ->(fx, fy, fz, mx, my, mz) - " after": (4,0,0,0,0,0) ->(fx, fy, fz, mx, my, mz) - } - """ - force_before = Vector((0,0,0)) - force_after = Vector((0,0,0)) - moment_before = Vector((0,0,0)) - moment_after = Vector((0,0,0)) - - for config in load_config_list: - if pos < config[0]["pos"] or pos > config[-1]["pos"]: - continue - start = 0 - end = len(config)-1 - while end-start > 0: - if pos < config[start]["pos"] or pos > config[end]["pos"]: - break - if config[start]["pos"] == pos: - if config[start]["descr"] in ['start','middle']: - force_after += config[start]["forces"] - moment_after += config[start]["moments"] - elif config[start]["descr"] in ['end','middle']: - force_before += config[start]["forces"] - moment_before += config[start]["moments"] - - elif config[end]["pos"] == pos: - if config[end]["descr"] in ['start','middle']: - force_after += config[end]["forces"] - moment_after += config[end]["moments"] - elif config[end]["descr"] in ['end','middle']: - force_before += config[end]["forces"] - moment_before += config[end]["moments"] - - elif end-start == 1: - force_before += self.interpolate(pos,config,start,end,"forces") - force_after += self.interpolate(pos,config,start,end,"forces") - moment_before += self.interpolate(pos,config,start,end,"moments") - moment_after += self.interpolate(pos,config,start,end,"moments") - start += 1 - end -=1 - return_value = { - "before": [force_before.x,force_before.y,force_before.z, - moment_before.x,moment_before.y,moment_before.z], - "after": [force_after.x, force_after.y, force_after.z, - moment_after.x, moment_after.y, moment_after.z] - } - return return_value - - def get_loads_dict(self,activity_list,element_rotation_matrix): - """ - get load list - activity_list: list of IfcStructuralCurveAction or IfcStructuralCurveReaction - applied in the structural curve member - global_to_local: transformation matrix from global coordinates to local coordinetes - return: dict{ - "constant force": (fx,fy,fz,mx,my,mz), -> sum of linear loads applied with - constant distribution - "quadratic force": (fx,fy,fz,mx,my,mz), -> sum of linear loads applied with - quadratic distribution - "sinus force": (fx,fy,fz,mx,my,mz), -> sum of linear loads applied with - sinus distribution - "lienar load configuration": list -> list of load configurations for linear - and polyline distributions of linear loads - } - description of "linear load configuration": - list[ -> one item (list)for each IfcStructuralCurveAction applied in the member - with IfcStructuralLoadConfiguration as the applied load - list[ -> one item (dict) for each item found in the - Locations attribute of IfcLoadConfiguration - dict{ - "pos": float, -> local position along curve length - "descr": string, -> describe if the item is at the start, middle or end of the list - "forces": Vector, -> linear force applied at that point - "moments": Vector -> linear moment applied at that point - } - ] - ] - """ - constant_force = Vector((0,0,0)) - constant_moment = Vector((0,0,0)) - quadratic_force = Vector((0,0,0)) - quadratic_moment = Vector((0,0,0)) - sinus_force = Vector((0,0,0)) - sinus_moment = Vector((0,0,0)) - linear_load_configurations = [] - point_load_configurations = [] - - unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get(),"LENGTHUNIT") - - def get_force_vector(load,transform_matrix,factor = 1.0): - x = 0 if getattr(load, 'LinearForceX', 0) is None else getattr(load, 'LinearForceX', 0) - y = 0 if getattr(load, 'LinearForceY', 0) is None else getattr(load, 'LinearForceY', 0) - z = 0 if getattr(load, 'LinearForceZ', 0) is None else getattr(load, 'LinearForceZ', 0) - return transform_matrix @ (Vector((x,y,z))*factor) - - def get_moment_vector(load,transform_matrix,factor = 1.0): - x = 0 if getattr(load, 'LinearMomentX', 0) is None else getattr(load, 'LinearMomentX', 0) - y = 0 if getattr(load, 'LinearMomentY', 0) is None else getattr(load, 'LinearMomentY', 0) - z = 0 if getattr(load, 'LinearMomentZ', 0) is None else getattr(load, 'LinearMomentZ', 0) - return transform_matrix @ (Vector((x,y,z))*factor) - - for item in activity_list: - activity = item[0] - factor = item[1] - load = activity.AppliedLoad - global_or_local = activity.GlobalOrLocal - props = bpy.context.scene.BIMStructuralProperties - reference_frame = props.reference_frame #make it a scene property so it can be changed in a panel - transform_matrix = Matrix() - if reference_frame == 'LOCAL_COORDS' and global_or_local != reference_frame: - transform_matrix = element_rotation_matrix - transform_matrix.invert() - elif reference_frame == 'GLOBAL_COORDS' and global_or_local != reference_frame: - transform_matrix = element_rotation_matrix - #values for linear loads - if load.is_a('IfcStructuralLoadConfiguration'): - locations = getattr(load, 'Locations', []) - values = [l for l in getattr(load, 'Values', None) - if l.is_a() == "IfcStructuralLoadLinearForce" - ] - config_list = [] - for i,l in enumerate(values): - forcevalues = get_force_vector(l,transform_matrix,factor) - momentvalues = get_moment_vector(l,transform_matrix,factor) - if i == 0: - descr = 'start' - elif i == len(values)-1: - descr = 'end' - else: - descr = 'middle' - config_list.append( - {"pos": locations[i][0]*unit_scale, - "descr": descr, - "forces":forcevalues, - "moments":momentvalues} - ) - linear_load_configurations.append(config_list) - #load configurations with point loads - values = [l for l in getattr(load, 'Values', None) - if l.is_a() == "IfcStructuralLoadSingleForce" - ] - attr_list = ['ForceX','ForceY','ForceZ','MomentX','MomentY','MomentZ'] - config_list = [] - for i,load in enumerate(values): - result_list = [0,0,0,0,0,0] - for j, attr in enumerate(attr_list): - value = 0 if getattr(load, attr, 0) is None else getattr(load, attr, 0) - result_list[j] += value*factor - config_list.append( - {"pos": locations[i][0]*unit_scale, - "values": result_list} - ) - point_load_configurations.append(config_list) - - else: - forcevalues = get_force_vector(load,transform_matrix,factor) - momentvalues = get_moment_vector(load,transform_matrix,factor) - if 'CONST' == getattr(activity, 'PredefinedType', None) or activity.is_a('IfcStructuralLinearAction'): - constant_force += forcevalues - constant_moment += momentvalues - elif 'PARABOLA' == getattr(activity, 'PredefinedType', None): - quadratic_force += forcevalues - quadratic_moment += momentvalues - elif 'SINUS' == getattr(activity, 'PredefinedType', None): - sinus_force += forcevalues - sinus_moment += momentvalues - return_value = { - "constant force": [constant_force.x,constant_force.y,constant_force.z, - constant_moment.x,constant_moment.y,constant_moment.z], - "quadratic force": [quadratic_force.x,quadratic_force.y,quadratic_force.z, - quadratic_moment.x,quadratic_moment.y,quadratic_moment.z], - "sinus force": [sinus_force.x,sinus_force.y,sinus_force.z, - sinus_moment.x,sinus_moment.y,sinus_moment.z], - "linear load configuration": linear_load_configurations, - "point load configuration": point_load_configurations - } - return return_value diff --git a/src/bonsai/bonsai/bim/module/structural/workspace.py b/src/bonsai/bonsai/bim/module/structural/workspace.py index cec8115a2d..1f4717b9d4 100644 --- a/src/bonsai/bonsai/bim/module/structural/workspace.py +++ b/src/bonsai/bonsai/bim/module/structural/workspace.py @@ -41,8 +41,8 @@ class StructuralTool(WorkSpaceTool): StructuralToolUI.draw(context, layout) -def add_layout_hotkey(layout: bpy.types.UILayout, text: str, hotkey: str, description: str) -> None: - args = ("structural", layout, text, hotkey, description) +def add_layout_hotkey(layout, text, hotkey, description): + args = ["structural", layout, text, hotkey, description] tool.Blender.add_layout_hotkey_operator(*args)