diff --git a/src/bonsai/bonsai/bim/module/structural/data.py b/src/bonsai/bonsai/bim/module/structural/data.py index bef238f92e..9a55a826fb 100644 --- a/src/bonsai/bonsai/bim/module/structural/data.py +++ b/src/bonsai/bonsai/bim/module/structural/data.py @@ -32,6 +32,7 @@ def refresh(): BoundaryConditionsData.is_loaded = False LoadGroupDecorationData.is_loaded = False + class LoadGroupDecorationData: data = {} is_loaded = False @@ -40,15 +41,16 @@ class LoadGroupDecorationData: def load(cls): cls.data = {"load groups to show": cls.load_groups_to_show()} cls.is_loaded = True - + @classmethod def load_groups_to_show(cls): ret = [] - abrv = {"LOAD_CASE": "L.Case: ", - "LOAD_COMBINATION": "L.Comb: ", - "LOAD_GROUP": "L.Gr: ", - "USERDEFINED": "U.Def: ", - "NOTDEFINED": "N.Def: " + abrv = { + "LOAD_CASE": "L.Case: ", + "LOAD_COMBINATION": "L.Comb: ", + "LOAD_GROUP": "L.Gr: ", + "USERDEFINED": "U.Def: ", + "NOTDEFINED": "N.Def: ", } models = tool.Ifc.get().by_type("IfcStructuralAnalysisModel") m = models[0] @@ -56,21 +58,21 @@ class LoadGroupDecorationData: if props.activity_type == "Action": groups = m.LoadedBy or [] for g in groups: - ret.append((str(g.id()),". "+abrv[g.PredefinedType]+" "+g.Name,"")) + ret.append((str(g.id()), ". " + abrv[g.PredefinedType] + " " + g.Name, "")) related_objects = [rel.RelatedObjects for rel in g.IsGroupedBy] for item in related_objects: for subgoup in [sg for sg in item if sg.is_a("IfcStructuralLoadGroup")]: - ret.append((str(subgoup.id()),". "+abrv[subgoup.PredefinedType]+subgoup.Name,"")) + ret.append((str(subgoup.id()), ". " + abrv[subgoup.PredefinedType] + subgoup.Name, "")) if props.activity_type == "External Reaction": groups = m.HasResults or [] for g in groups: result_name = g.ResultForLoadGroup.Name or "" group_name = g.Name or "" - ret.append((str(g.id()), group_name + " " + result_name,"")) + ret.append((str(g.id()), group_name + " " + result_name, "")) if len(ret) == 0: - ret.append(("","","")) + ret.append(("", "", "")) return ret diff --git a/src/bonsai/bonsai/bim/module/structural/decorator.py b/src/bonsai/bonsai/bim/module/structural/decorator.py index 840c6bea31..f17c643d34 100644 --- a/src/bonsai/bonsai/bim/module/structural/decorator.py +++ b/src/bonsai/bonsai/bim/module/structural/decorator.py @@ -26,8 +26,10 @@ from gpu_extras.batch import batch_for_shader from typing import Iterable, Union from bonsai.bim.module.structural.load_decoration_data import ShaderInfo + class LoadsDecorator: """Decorator to show strucutural loads in 3D""" + is_installed = False handlers = [] decoration_data = None @@ -36,20 +38,20 @@ class LoadsDecorator: depth_array = None @classmethod - def install(cls, context: bpy.types.Context)-> None: + def install(cls, context: bpy.types.Context) -> None: if cls.is_installed: cls.uninstall() handler = cls() cls.handlers.append( SpaceView3D.draw_handler_add(handler.draw_load_values, ((context,)), "WINDOW", "POST_PIXEL") - ) + ) cls.handlers.append(SpaceView3D.draw_handler_add(handler, (), "WINDOW", "POST_VIEW")) cls.decoration_data = ShaderInfo() cls.update() cls.is_installed = True @classmethod - def uninstall(cls)-> None: + def uninstall(cls) -> None: for handler in cls.handlers: try: SpaceView3D.draw_handler_remove(handler, "WINDOW") @@ -68,8 +70,8 @@ class LoadsDecorator: # set open gl configurations original_blend = gpu.state.blend_get() original_depth_test = gpu.state.depth_test_get() - gpu.state.blend_set('ALPHA') - gpu.state.depth_test_set('LESS_EQUAL') + gpu.state.blend_set("ALPHA") + gpu.state.depth_test_set("LESS_EQUAL") self.draw_batch() @@ -80,11 +82,11 @@ class LoadsDecorator: def draw_batch(self) -> None: """draw the 3D representation of loads""" if not self.decoration_data.is_empty: - for info in self.shader_info: + for info in self.shader_info: shader = info["shader"] args = info["args"] indices = info["indices"] - batch = batch_for_shader(shader, 'TRIS', args, indices=indices) + batch = batch_for_shader(shader, "TRIS", args, indices=indices) matrix = bpy.context.region_data.perspective_matrix shader.bind() shader.uniform_float("viewProjectionMatrix", matrix) @@ -96,10 +98,10 @@ class LoadsDecorator: def draw_load_values(self, context: bpy.types.Context) -> None: """draw text representing the load values""" - #getting depth buffer info, code adapted from: - #https://blender.stackexchange.com/questions/177185/is-there-a-way-to-render-depth-buffer-into-a-texture-with-gpu-bgl-python-modules + # getting depth buffer info, code adapted from: + # https://blender.stackexchange.com/questions/177185/is-there-a-way-to-render-depth-buffer-into-a-texture-with-gpu-bgl-python-modules framebuffer = gpu.state.active_framebuffer_get() - width = context.region.width + width = context.region.width height = context.region.height depth_buffer = framebuffer.read_depth(0, 0, width, height) depth_array = np.array(depth_buffer.to_list()) @@ -110,7 +112,7 @@ class LoadsDecorator: self.depth_array = n / (f - (f - n) * depth_array) * (f - n) for info in self.text_info: - text_position = self.location_3d_to_region_2d(info["position"],context) + text_position = self.location_3d_to_region_2d(info["position"], context) if text_position is not None: font_id = 0 blf.position(font_id, text_position[0], text_position[1], text_position[2]) @@ -118,7 +120,7 @@ class LoadsDecorator: blf.color(font_id, 0.9, 0.9, 0.9, 1.0) blf.draw(font_id, info["text"]) - def location_3d_to_region_2d(self, coord: Iterable, context: bpy.types.Context) -> Union[Vector,None]: + def location_3d_to_region_2d(self, coord: Iterable, context: bpy.types.Context) -> Union[Vector, None]: """Convert from 3D space to 2D screen space. Filter out the text supposed to be hidden by 3D elements, using the depth array. It also hides text that are distant from the camera view to avoid clutter""" @@ -129,18 +131,26 @@ class LoadsDecorator: view_matrix = rv3d.view_matrix point_view_space = view_matrix @ coord - - if perspective == 'ORTHO' or -10 < point_view_space.z < 0: + if perspective == "ORTHO" or -10 < point_view_space.z < 0: prj = rv3d.perspective_matrix @ Vector((coord[0], coord[1], coord[2], 1.0)) width_half = context.region.width / 2.0 height_half = context.region.height / 2.0 - coord_2d = Vector((width_half + width_half * (prj.x / prj.w), - height_half + height_half * (prj.y / prj.w), - point_view_space.z)) - if coord_2d[0] < 0 or coord_2d[0]> context.region.width or coord_2d[1] > context.region.height or coord_2d[1] < 0: + coord_2d = Vector( + ( + width_half + width_half * (prj.x / prj.w), + height_half + height_half * (prj.y / prj.w), + point_view_space.z, + ) + ) + if ( + coord_2d[0] < 0 + or coord_2d[0] > context.region.width + or coord_2d[1] > context.region.height + or coord_2d[1] < 0 + ): return None depth = self.depth_array[int(coord_2d[1])][int(coord_2d[0])] - if -0.98*point_view_space.z > depth: + if -0.98 * point_view_space.z > depth: return None return coord_2d return None diff --git a/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py b/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py index 119d0227a9..5a9817b7f9 100644 --- a/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py +++ b/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py @@ -30,47 +30,44 @@ from bonsai.bim.ifc import IfcStore from bonsai.bim.module.structural.shader import DecorationShader from typing import Literal, TypedDict, Iterable -MemberInfo = TypedDict("MemberInfo", - {"member": ifcopenshell.entity_instance, - "activities": list[tuple[ifcopenshell.entity_instance,float]]}) +MemberInfo = TypedDict( + "MemberInfo", + {"member": ifcopenshell.entity_instance, "activities": list[tuple[ifcopenshell.entity_instance, float]]}, +) -LoadConfigItem = TypedDict("LoadConfigItem", - {"pos": float, - "descr": Literal["start","end", "middle"], - "load values":np.ndarray}) +LoadConfigItem = TypedDict( + "LoadConfigItem", {"pos": float, "descr": Literal["start", "end", "middle"], "load values": np.ndarray} +) -DiscreteConfigItem = TypedDict("DiscreteConfigItem", - {"pos": float, - "values": list[float]}) - -ParsedLoad = TypedDict("ParsedLoad", - {"constant force": list[float], - "quadratic force": list[float], - "sinus force": list[float], - "linear load configuration": list[list[LoadConfigItem]], - "point load configuration": list[list[DiscreteConfigItem]] - }) -LoadByDirection = TypedDict("LoadByDirection", - {"constant": float, - "quadratic": float, - "sinus": float, - "polyline":list[list[float]]}) - -ProcessedLoad = TypedDict("ProcessedLoad", - {"linear loads":LoadByDirection, - "max linear load": float, - "discrete loads": list[list[DiscreteConfigItem]]}) +DiscreteConfigItem = TypedDict("DiscreteConfigItem", {"pos": float, "values": list[float]}) +ParsedLoad = TypedDict( + "ParsedLoad", + { + "constant force": list[float], + "quadratic force": list[float], + "sinus force": list[float], + "linear load configuration": list[list[LoadConfigItem]], + "point load configuration": list[list[DiscreteConfigItem]], + }, +) +LoadByDirection = TypedDict( + "LoadByDirection", {"constant": float, "quadratic": float, "sinus": float, "polyline": list[list[float]]} +) +ProcessedLoad = TypedDict( + "ProcessedLoad", + {"linear loads": LoadByDirection, "max linear load": float, "discrete loads": list[list[DiscreteConfigItem]]}, +) class ShaderInfo: def __init__(self) -> None: self.is_empty = True self.shader = DecorationShader() - self.curve_members: dict[str,MemberInfo] = {} - self.point_members:dict[str,MemberInfo] = {} - self.surface_members:dict[str,MemberInfo] = {} + self.curve_members: dict[str, MemberInfo] = {} + self.point_members: dict[str, MemberInfo] = {} + self.surface_members: dict[str, MemberInfo] = {} self.text_info = [] self.info = [] self.force_unit = "" @@ -78,7 +75,7 @@ class ShaderInfo: self.linear_force_unit = "" self.linear_moment_unit = "" self.planar_force_unit = "" - + def update(self) -> None: self.info = [] self.text_info = [] @@ -92,7 +89,7 @@ class ShaderInfo: self.get_planar_loads() if len(self.info): self.is_empty = False - + def get_force_units(self) -> None: def get_unit_symbol(unit: ifcopenshell.entity_instance) -> str: prefix_symbols = { @@ -125,7 +122,7 @@ class ShaderInfo: "PASCAL": "Pa", # conversion based units "pound-force": "lbf", - 'pound-force per square inch': "psi", + "pound-force per square inch": "psi", "thou": "th", "inch": "in", "foot": "ft", @@ -169,18 +166,13 @@ class ShaderInfo: symbol += prefix_symbols.get(unit.Prefix, "") symbol += unit_symbols.get(unit.Name.replace("METER", "METRE"), "?") return symbol - - length_units = [u for u in tool.Ifc.get().by_type("IfcNamedUnit") - if u.UnitType == "LENGTHUNIT"] - 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"] - linear_moment_units = [u for u in tool.Ifc.get().by_type("IfcDerivedUnit") - if u.UnitType == "LINEARMOMENTUNIT"] - planar_force_units = [u for u in tool.Ifc.get().by_type("IfcDerivedUnit") - if u.UnitType == "PLANARFORCEUNIT"] - + + length_units = [u for u in tool.Ifc.get().by_type("IfcNamedUnit") if u.UnitType == "LENGTHUNIT"] + 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"] + linear_moment_units = [u for u in tool.Ifc.get().by_type("IfcDerivedUnit") if u.UnitType == "LINEARMOMENTUNIT"] + 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]) @@ -208,7 +200,7 @@ class ShaderInfo: first = ifcunit.get_unit_symbol(e.Unit) if e.Unit.UnitType == "LENGTHUNIT": second = ifcunit.get_unit_symbol(e.Unit) - self.linear_moment_unit = first + "." + second + "/" + second + self.linear_moment_unit = first + "." + second + "/" + second first = "" second = "" @@ -216,18 +208,20 @@ class ShaderInfo: if e.Unit.UnitType == "FORCEUNIT": first = ifcunit.get_unit_symbol(e.Unit) if e.Unit.UnitType == "LENGTHUNIT": - second = ifcunit.get_unit_symbol(e.Unit)+"2" + second = ifcunit.get_unit_symbol(e.Unit) + "2" if e.Unit.UnitType == "AREAUNIT": second = ifcunit.get_unit_symbol(e.Unit) self.planar_force_unit = first + "/" + second - + def get_strucutural_elements_and_activities(self) -> None: """fills self.point_members, self.curve_members and self.surface_members dictionaries""" - def populate_members_dict(dict_name: Literal["point_members", "curve_members", "surface_members"], - element: ifcopenshell.entity_instance, - activity: ifcopenshell.entity_instance, - factor: float) -> None: + def populate_members_dict( + dict_name: Literal["point_members", "curve_members", "surface_members"], + element: ifcopenshell.entity_instance, + activity: ifcopenshell.entity_instance, + factor: float, + ) -> None: """ fills self.point_members, self.curve_members and self.surface_members dictionaries thoses dicts will contain the strucutural member global id as key and a second dict as value @@ -241,23 +235,21 @@ class ShaderInfo: activity: IfcStructuralActivity factor: float to multiply the loads values in the structural activity """ - dic = getattr(self,dict_name,None) + 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)]} - }) + dic.update({element.GlobalId: {"member": element, "activities": [(activity, factor)]}}) else: - member["activities"].append((activity,factor)) + member["activities"].append((activity, factor)) - def recursive_subgroups(groups: list[ifcopenshell.entity_instance], - rec_limit: int, - activity_type: Literal["Action", "External Reaction"], - factor: float = 1) -> None: + def recursive_subgroups( + groups: list[ifcopenshell.entity_instance], + rec_limit: int, + activity_type: Literal["Action", "External Reaction"], + factor: float = 1, + ) -> None: """ Recursively fills self.point_members, self.curve_members and self.surface_members dictionaries with the structural members to wich the activities in the load group and its subgroups are applied @@ -278,8 +270,8 @@ class ShaderInfo: 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 + 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: @@ -288,33 +280,36 @@ class ShaderInfo: 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 + 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) + populate_members_dict("curve_members", element, activity, factor) elif element.is_a("IfcStructuralPointConnection"): - populate_members_dict("point_members",element,activity,factor) + populate_members_dict("point_members", element, activity, factor) elif element.is_a("IfcStructuralSurfaceMember"): - populate_members_dict("surface_members",element,activity,factor) + populate_members_dict("surface_members", element, activity, factor) - elif activity_type == "External Reaction" and getattr(element,"AppliedCondition",None) is not None: + 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) + populate_members_dict("curve_members", element, activity, factor) elif element.is_a("IfcStructuralPointConnection"): - populate_members_dict("point_members",element,activity,factor) + 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) - + 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) + recursive_subgroups(groups, 10, props.activity_type) def get_planar_loads(self) -> None: """get the necessary information to render the planar load representation in 3D and its text information""" @@ -328,7 +323,7 @@ class ShaderInfo: if len(activity_list) == 0: continue rotation = self.get_surface_member_rotation(surf) - values = self.get_planar_loads_values(activity_list,rotation) + values = self.get_planar_loads_values(activity_list, rotation) if maximum == 0: maximum = max([abs(float(i)) for i in values]) if maximum == 0: @@ -338,16 +333,16 @@ class ShaderInfo: orientation = np.eye(3) if reference_frame == "LOCAL_COORDS": orientation = rotation - blender_object: bpy.types.Object = IfcStore.get_element(getattr(surf, 'GlobalId', None)) + 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) + bmesh.ops.triangulate(bm, faces=bm.faces) bm.edges.ensure_lookup_table() bm.verts.ensure_lookup_table() bm.faces.ensure_lookup_table() @@ -355,43 +350,40 @@ class ShaderInfo: for v in bm.verts: p1 = np.array(mat @ v.co) positions.append(p1) - p2 = p1 - (orientation@values)*0.2/maximum + 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)) + 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)) + 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) + 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), - "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]] - } + { + "position": mat @ center - Vector((orientation @ values) * 0.2 / maximum), + "text": f"{values[2]:.5f} {self.planar_force_unit}", + } ) - def get_planar_loads_values(self, - activity_list: list[tuple[ifcopenshell.entity_instance,float]], - element_rotation_matrix: np.ndarray) -> np.ndarray: + + 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: list[tuple[ifcopenshell.entity_instance, float]], element_rotation_matrix: np.ndarray + ) -> np.ndarray: """ returns a numpy array with the sum of the values of structural activities applied loads in each direction, multiplied by the factors in load combinations @@ -406,57 +398,50 @@ class ShaderInfo: 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 + 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: ifcopenshell.entity_instance - ) -> np.ndarray: + def get_surface_member_rotation(self, surface_member: ifcopenshell.entity_instance) -> np.ndarray: """returns the rotation matrix of a structural 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] + rotation = placement[0:3, 0:3] return rotation - def get_point_connection_rotation(self, - point_connection: ifcopenshell.entity_instance - ) -> np.ndarray: + def get_point_connection_rotation(self, point_connection: ifcopenshell.entity_instance) -> np.ndarray: """returns the rotation matrix of a structural 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] + rotation = placement[0:3, 0:3] return rotation - - def get_curve_member_rotation(self, - curve_member: ifcopenshell.entity_instance - ) -> np.ndarray: + + def get_curve_member_rotation(self, curve_member: ifcopenshell.entity_instance) -> np.ndarray: """returns the rotation matrix of a structural surface 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] + placement = ifcopenshell.util.placement.a2p(origin, z, x) + rotation = placement[0:3, 0:3] return rotation - - def get_activity_transform_matrix(self, - activity: ifcopenshell.entity_instance, - element_rotation_matrix: np.ndarray - ) -> np.ndarray: + + def get_activity_transform_matrix( + self, activity: ifcopenshell.entity_instance, element_rotation_matrix: np.ndarray + ) -> np.ndarray: "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: + 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: + elif reference_frame == "GLOBAL_COORDS" and global_or_local != reference_frame: transform_matrix = element_rotation_matrix return transform_matrix @@ -469,30 +454,26 @@ class ShaderInfo: activity_list = value["activities"] if len(activity_list) == 0: continue - blender_object = IfcStore.get_element(getattr(conn, 'GlobalId', None)) - if blender_object.type == 'MESH': + 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 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: Iterable, - location: np.ndarray, - rotation: np.ndarray - ) -> None: + def get_point_shader_args(self, loads: Iterable, location: np.ndarray, rotation: np.ndarray) -> None: """get the args to the point shader""" location = np.array(location) indices = [] 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"] + "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": @@ -500,19 +481,19 @@ class ShaderInfo: tup = direction_dict[key] li = [] for item in tup: - li.append(rotation@item) + 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) + 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] @@ -521,56 +502,52 @@ class ShaderInfo: 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] + 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.new("SINGLE FORCE") self.info.append( { "shader": shader, - "args": {"position": position,"coord": coords_for_shader}, + "args": {"position": position, "coord": coords_for_shader}, "indices": indices, - "uniforms": [["color", color],["spacing", 0.2]] + "uniforms": [["color", color], ["spacing", 0.2]], } ) - self.text_info.append( - {"position": location + d1, - "text": f'{loads[i]:.2f} {self.force_unit}'} - ) + self.text_info.append({"position": location + d1, "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] + 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.new("SINGLE MOMENT") self.info.append( { "shader": shader, - "args": {"position": position,"coord": coords_for_shader}, + "args": {"position": position, "coord": coords_for_shader}, "indices": indices, - "uniforms": [["color", color]] + "uniforms": [["color", color]], } ) self.text_info.append( - {"position": location +0.25*(d1 + d2), - "text": f'{loads[i]:.2f} {self.moment_unit}'} - ) + {"position": location + 0.25 * (d1 + d2), "text": f"{loads[i]:.2f} {self.moment_unit}"} + ) - def get_point_loads_values(self, - activity_list: list[tuple[ifcopenshell.entity_instance,float]], - element_rotation_matrix: np.ndarray) -> np.ndarray: + def get_point_loads_values( + self, activity_list: list[tuple[ifcopenshell.entity_instance, float]], element_rotation_matrix: np.ndarray + ) -> np.ndarray: """returns a numpy array with the sum of the point load values""" result_list = np.zeros(6) - attr_list = ['ForceX','ForceY','ForceZ','MomentX','MomentY','MomentZ'] + attr_list = ["ForceX", "ForceY", "ForceZ", "MomentX", "MomentY", "MomentZ"] for item in activity_list: activity = item[0] factor = item[1] @@ -578,17 +555,16 @@ class ShaderInfo: 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 + 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)-> None: + def get_linear_loads(self) -> None: position = [] indices = [] sin_quad_lin = [] @@ -596,46 +572,53 @@ class ShaderInfo: color = [] info = [] maxforce = 0 - + list_of_curve_members = self.curve_members for value in list_of_curve_members.values(): member = value["member"] activity_list = value["activities"] if len(activity_list) == 0: continue - - blender_object = IfcStore.get_element(getattr(member, 'GlobalId', None)) - + + 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() + 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() rotation = self.get_curve_member_rotation(member) - - + props = bpy.context.scene.BIMStructuralProperties reference_frame = props.reference_frame - 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 + 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() + "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 - processed_loads = self.process_total_linear_loads(activity_list,rotation,member_length) + match_dict = {"fx": x_match or is_local, "fy": y_match, "fz": z_match} + member_length = Vector(end_co - start_co).length + processed_loads = self.process_total_linear_loads(activity_list, rotation, member_length) linear_loads = processed_loads["linear loads"] - maxforce = max(maxforce,processed_loads["max linear load"]) + maxforce = max(maxforce, processed_loads["max linear load"]) point_loads = processed_loads["discrete loads"] if len(point_loads) > 0: @@ -643,11 +626,11 @@ class ShaderInfo: 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,rotation) + pos_vector = start_co + x_axis * pos + self.get_point_shader_args(values, pos_vector, rotation) if linear_loads is None: continue - keys = ["fx","fy","fz","mx","my","mz"] + keys = ["fx", "fy", "fz", "mx", "my", "mz"] for key in keys: polyline = linear_loads[key]["polyline"] @@ -655,13 +638,13 @@ class ShaderInfo: quadratic = linear_loads[key]["quadratic"] constant = linear_loads[key]["constant"] direction = direction_dict[key] - 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: + 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: unit = self.linear_force_unit if match_dict[key]: shader = self.shader.new("PARALLEL DISTRIBUTED FORCE") @@ -672,68 +655,74 @@ class ShaderInfo: shader = self.shader.new("DISTRIBUTED MOMENT") counter = 0 - for i in range(len(polyline)-1): - current = Vector(polyline[i]+[0]) - nextitem = Vector(polyline[i+1]+[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]): - negative = -1*direction + start_co + x_axis*current.x - positive = direction + start_co + x_axis*current.x + if any([current.y, nextitem.y, constant, quadratic, sinus]): + 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)) + coords_for_shader.append((current.x, 1.0, member_length)) sin_quad_lin.append((sinus, quadratic, current.y + constant)) color.append(color_axis) - - x = current.x/member_length - func = sin(x*3.1416)*sinus + (-4.*x*x+4.*x)*quadratic+constant+current.y + + x = current.x / member_length + func = sin(x * 3.1416) * sinus + (-4.0 * x * x + 4.0 * x) * quadratic + constant + current.y if func: self.text_info.append( - {"position": -1*direction*func/maxforce + start_co + x_axis*current.x, - "text": f'{func:.2f} {unit}'} - ) - + { + "position": -1 * direction * func / maxforce + start_co + x_axis * current.x, + "text": f"{func:.2f} {unit}", + } + ) + position.append(positive) - coords_for_shader.append((current[0],-1.0,member_length)) + 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, - 1 + counter, - 2 + counter)) - indices.append((3 + counter, - 2 + counter, - 1 + counter)) - if i == len(polyline)-2: - negative = -1*direction + start_co + x_axis*nextitem.x - positive = direction + start_co + x_axis*nextitem.x + indices.append((0 + counter, 1 + counter, 2 + counter)) + indices.append((3 + counter, 2 + counter, 1 + counter)) + 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)) + coords_for_shader.append((nextitem.x, 1.0, member_length)) sin_quad_lin.append((sinus, quadratic, nextitem.y + constant)) color.append(color_axis) - - x = nextitem.x/member_length - func = sin(x*3.1416)*sinus + (-4.*x*x+4.*x)*quadratic+constant+nextitem.y + + x = nextitem.x / member_length + func = ( + sin(x * 3.1416) * sinus + (-4.0 * x * x + 4.0 * x) * quadratic + constant + nextitem.y + ) if func: self.text_info.append( - {"position": -1*direction*func/maxforce + start_co + x_axis*nextitem.x, - "text": f'{func:.2f} {unit}'} - ) - + { + "position": -1 * direction * func / maxforce + start_co + x_axis * nextitem.x, + "text": f"{func:.2f} {unit}", + } + ) + position.append(positive) - coords_for_shader.append((nextitem.x,-1.0,member_length)) + 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 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]] - } - ) + { + "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 = [] @@ -741,16 +730,17 @@ class ShaderInfo: for info in self.info: info["uniforms"][2][1] = maxforce - - def process_total_linear_loads(self, - activity_list: list[tuple[ifcopenshell.entity_instance,float]], - element_rotation_matrix: np.ndarray, - member_length: float) -> ProcessedLoad: - """ returns a dict with total values for applied loads in each direction + def process_total_linear_loads( + self, + activity_list: list[tuple[ifcopenshell.entity_instance, float]], + element_rotation_matrix: np.ndarray, + member_length: float, + ) -> ProcessedLoad: + """returns a dict with total values for applied loads in each direction along with the maximum value for the loads in the member and the discrete loads applied - + """ - loads_dict = self.parse_linear_loads_to_dict(activity_list,element_rotation_matrix) + loads_dict = self.parse_linear_loads_to_dict(activity_list, element_rotation_matrix) const = loads_dict["constant force"] quad = loads_dict["quadratic force"] sinus = loads_dict["sinus force"] @@ -760,46 +750,52 @@ class ShaderInfo: distributed_loads = None max_load = 0 for pos in unique_list: - value = self.get_before_and_after(pos,loads) + value = self.get_before_and_after(pos, loads) if value["before"] == value["after"]: - final_list.append([pos]+value["before"]) + final_list.append([pos] + value["before"]) else: - final_list.append([pos]+value["before"]) - final_list.append([pos]+value["after"]) - - if len(final_list) == 0 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)>0: - 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 + final_list.append([pos] + value["before"]) + final_list.append([pos] + value["after"]) + + if len(final_list) == 0 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) > 0: + 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]) + 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)>0: - array = np.array(final_list) #7xn -> ["pos","fx","fy","fz","mx","my","mz"] - keys = ["fx","fy","fz","mx","my","mz"] + if len(final_list) > 0: + array = np.array(final_list) # 7xn -> ["pos","fx","fy","fz","mx","my","mz"] + keys = ["fx", "fy", "fz", "mx", "my", "mz"] polyline = [] distributed_loads = { - "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": []}, - } + "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])): + if any([sinus[component], quad[component], const[component]]) or any( + item for item in array[:, component + 1] + ): for currentitem in final_list: - polyline.append([currentitem[0],currentitem[component+1]]) - max_load = max(max_load,abs(sinus[component]+quad[component]+const[component]+currentitem[component+1])) + polyline.append([currentitem[0], currentitem[component + 1]]) + max_load = max( + max_load, + abs(sinus[component] + quad[component] + const[component] + currentitem[component + 1]), + ) inner_dict = distributed_loads[key] inner_dict["constant"] = const[component] inner_dict["quadratic"] = quad[component] @@ -807,18 +803,19 @@ class ShaderInfo: inner_dict["polyline"] = polyline distributed_loads[key] = inner_dict - return {"linear loads":distributed_loads, - "max linear load": max_load, - "discrete loads": loads_dict["point load configuration"]} + return { + "linear loads": distributed_loads, + "max linear load": max_load, + "discrete loads": loads_dict["point load configuration"], + } - - def getuniquepositionlist(self, load_config_list:list[list[LoadConfigItem]])-> list[float]: + def getuniquepositionlist(self, load_config_list: list[list[LoadConfigItem]]) -> list[float]: """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] - """ + 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: @@ -828,39 +825,36 @@ class ShaderInfo: unique.sort() return unique - def interp1d(self,l1:list[float],l2: list[float], pos:float) -> float: - """ 1d linear interpolation for the vector components""" - fac = (l2[1]-l1[1])/(l2[0]-l1[0]) - v = l1[1] + fac*(pos-l1[0]) + def interp1d(self, l1: list[float], l2: list[float], pos: float) -> float: + """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: float,loadinfo:list[LoadConfigItem],start:int,end:int,key: str)-> np.ndarray: - """ interpolate the result vectors between load poits""" + def interpolate(self, pos: float, loadinfo: list[LoadConfigItem], start: int, end: int, key: str) -> np.ndarray: + """interpolate the result vectors between load poits""" result = np.zeros(6) for i in range(6): - 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] + 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: float, - load_config_list:list[list[LoadConfigItem]] - ) -> dict[str,list[float]]: - """ get total values for forces and moments before and after the position - example: - pos = 2.0 - load_config_list = [[{"pos":1.0, "descr":"start, "load values":[1,0,0,0,0,0]}, - {"pos":3.0, "descr":"end, "load values":[3,0,0,0,0,0]}], - [{"pos":2.0, "descr":"start, "load values":[1,0,0,0,0,0]}, - {"pos":3.0, "descr":"end, "load values":[1,0,0,0,0,0]}], - [{"pos":1.5, "descr":"start, "load values":[1,0,0,0,0,0]}, - {"pos":2.5, "descr":"end, "load values":[1,0,0,0,0,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) - } - """ + def get_before_and_after(self, pos: float, load_config_list: list[list[LoadConfigItem]]) -> dict[str, list[float]]: + """get total values for forces and moments before and after the position + example: + pos = 2.0 + load_config_list = [[{"pos":1.0, "descr":"start, "load values":[1,0,0,0,0,0]}, + {"pos":3.0, "descr":"end, "load values":[3,0,0,0,0,0]}], + [{"pos":2.0, "descr":"start, "load values":[1,0,0,0,0,0]}, + {"pos":3.0, "descr":"end, "load values":[1,0,0,0,0,0]}], + [{"pos":1.5, "descr":"start, "load values":[1,0,0,0,0,0]}, + {"pos":2.5, "descr":"end, "load values":[1,0,0,0,0,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) + } + """ load_before = np.zeros(6) load_after = np.zeros(6) @@ -868,49 +862,46 @@ class ShaderInfo: if pos < config[0]["pos"] or pos > config[-1]["pos"]: continue start = 0 - end = len(config)-1 - while end-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']: + if config[start]["descr"] in ["start", "middle"]: load_after += config[start]["load values"] - elif config[start]["descr"] in ['end','middle']: + elif config[start]["descr"] in ["end", "middle"]: load_before += config[start]["load values"] elif config[end]["pos"] == pos: - if config[end]["descr"] in ['start','middle']: + if config[end]["descr"] in ["start", "middle"]: load_after += config[end]["load values"] - elif config[end]["descr"] in ['end','middle']: + elif config[end]["descr"] in ["end", "middle"]: load_before += config[end]["load values"] - elif end-start == 1: - load_before += self.interpolate(pos,config,start,end,"load values") - load_after += self.interpolate(pos,config,start,end,"load values") + elif end - start == 1: + load_before += self.interpolate(pos, config, start, end, "load values") + load_after += self.interpolate(pos, config, start, end, "load values") start += 1 - end -=1 - return_value = { - "before": load_before.tolist(), - "after": load_after.tolist() - } + end -= 1 + return_value = {"before": load_before.tolist(), "after": load_after.tolist()} return return_value - def parse_linear_loads_to_dict(self, - activity_list: list[tuple[ifcopenshell.entity_instance,float]], - element_rotation_matrix: np.ndarray) -> ParsedLoad: + def parse_linear_loads_to_dict( + self, activity_list: list[tuple[ifcopenshell.entity_instance, float]], element_rotation_matrix: np.ndarray + ) -> ParsedLoad: """ get load list - activity_list: list of IfcStructuralCurveAction or IfcStructuralCurveReaction + 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 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 force": (fx,fy,fz,mx,my,mz), -> sum of linear loads applied with sinus distribution "linear load configuration": list -> list of load configurations for linear and polyline distributions of linear loads @@ -918,7 +909,7 @@ class ShaderInfo: 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 + list[ -> one item (dict) for each item found in the Locations attribute of IfcLoadConfiguration dict{ "pos": float, -> local position along curve length @@ -927,86 +918,76 @@ class ShaderInfo: } ] ] - """ + """ constant = np.zeros(6) quadratic = np.zeros(6) sinus = np.zeros(6) linear_load_configurations = [] point_load_configurations = [] - unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get(),"LENGTHUNIT") + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get(), "LENGTHUNIT") - def get_load_values(load,transform_matrix,factor = 1.0): + def get_load_values(load, transform_matrix, factor=1.0): result = np.zeros(6) - keys = ['LinearForceX','LinearForceY','LinearForceZ','LinearMomentX','LinearMomentY','LinearMomentZ'] + keys = ["LinearForceX", "LinearForceY", "LinearForceZ", "LinearMomentX", "LinearMomentY", "LinearMomentZ"] for i, key in enumerate(keys): value = 0 if getattr(load, key, 0) is None else getattr(load, key, 0) - result[i] += value*factor + result[i] += value * factor return transform_matrix @ result for item in activity_list: activity = item[0] factor = item[1] load = activity.AppliedLoad - - transform_3by3 = self.get_activity_transform_matrix(activity,element_rotation_matrix) - transform_6by6 = np.zeros((6,6)) - transform_6by6[0:3,0:3] = transform_3by3 - transform_6by6[3:6,3:6] = transform_3by3 - - #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" - ] + + transform_3by3 = self.get_activity_transform_matrix(activity, element_rotation_matrix) + transform_6by6 = np.zeros((6, 6)) + transform_6by6[0:3, 0:3] = transform_3by3 + transform_6by6[3:6, 3:6] = transform_3by3 + + # 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): - load_values = get_load_values(l,transform_6by6,factor) + for i, l in enumerate(values): + load_values = get_load_values(l, transform_6by6, factor) if i == 0: - descr = 'start' - elif i == len(values)-1: - descr = 'end' + descr = "start" + elif i == len(values) - 1: + descr = "end" else: - descr = 'middle' + descr = "middle" config_list.append( - {"pos": locations[i][0]*unit_scale, - "descr": descr, - "load values":load_values} + {"pos": locations[i][0] * unit_scale, "descr": descr, "load values": load_values} ) 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'] + # 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,val in enumerate(values): - result_list = [0,0,0,0,0,0] + for i, val in enumerate(values): + result_list = [0, 0, 0, 0, 0, 0] for j, attr in enumerate(attr_list): value = 0 if getattr(val, attr, 0) is None else getattr(val, attr, 0) - result_list[j] += value*factor - config_list.append( - {"pos": locations[i][0]*unit_scale, - "values": result_list} - ) + result_list[j] += value * factor + config_list.append({"pos": locations[i][0] * unit_scale, "values": result_list}) point_load_configurations.append(config_list) else: - load_values = get_load_values(load,transform_6by6,factor) - if 'CONST' == getattr(activity, 'PredefinedType', None) or activity.is_a('IfcStructuralLinearAction'): + load_values = get_load_values(load, transform_6by6, factor) + if "CONST" == getattr(activity, "PredefinedType", None) or activity.is_a("IfcStructuralLinearAction"): constant += load_values - elif 'PARABOLA' == getattr(activity, 'PredefinedType', None): + elif "PARABOLA" == getattr(activity, "PredefinedType", None): quadratic += load_values - elif 'SINUS' == getattr(activity, 'PredefinedType', None): + elif "SINUS" == getattr(activity, "PredefinedType", None): sinus += load_values return_value = { - "constant force": constant.tolist(), - "quadratic force": quadratic.tolist(), - "sinus force": sinus.tolist(), - "linear load configuration": linear_load_configurations, - "point load configuration": point_load_configurations - } + "constant force": constant.tolist(), + "quadratic force": quadratic.tolist(), + "sinus force": sinus.tolist(), + "linear load configuration": linear_load_configurations, + "point load configuration": point_load_configurations, + } return return_value - diff --git a/src/bonsai/bonsai/bim/module/structural/operator.py b/src/bonsai/bonsai/bim/module/structural/operator.py index 62e2dffe1f..69ce579a2b 100644 --- a/src/bonsai/bonsai/bim/module/structural/operator.py +++ b/src/bonsai/bonsai/bim/module/structural/operator.py @@ -29,24 +29,26 @@ from mathutils import Vector, Matrix from bonsai.bim.ifc import IfcStore from bonsai.bim.module.structural.decorator import LoadsDecorator + class ShowLoads(bpy.types.Operator): """Draw decorations to show strucutural actions in 3d view""" + bl_idname = "bim.show_loads" bl_label = "Show loads in 3D View" def modal(self, context, event): - if event.type == 'F5': + if event.type == "F5": LoadsDecorator.update() for area in context.screen.areas: - if area.type == 'VIEW_3D': + if area.type == "VIEW_3D": area.tag_redraw() - if event.type == 'ESC': + if event.type == "ESC": LoadsDecorator.uninstall() for area in context.screen.areas: - if area.type == 'VIEW_3D': + if area.type == "VIEW_3D": area.tag_redraw() - return {'FINISHED'} - return {'PASS_THROUGH'} + return {"FINISHED"} + return {"PASS_THROUGH"} def invoke(self, context, event): collection = bpy.data.collections["IfcStructuralItem"] @@ -60,10 +62,10 @@ class ShowLoads(bpy.types.Operator): context.window.cursor_modal_restore() context.window_manager.modal_handler_add(self) for area in context.screen.areas: - if area.type == 'VIEW_3D': + if area.type == "VIEW_3D": area.tag_redraw() - return {'RUNNING_MODAL'} + return {"RUNNING_MODAL"} class AddStructuralMemberConnection(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/bonsai/bonsai/bim/module/structural/prop.py b/src/bonsai/bonsai/bim/module/structural/prop.py index 11d2da1a2f..64d8c1bed0 100644 --- a/src/bonsai/bonsai/bim/module/structural/prop.py +++ b/src/bonsai/bonsai/bim/module/structural/prop.py @@ -21,7 +21,12 @@ import bpy import bonsai.tool as tool from bonsai.bim.ifc import IfcStore from bonsai.bim.prop import StrProperty, Attribute -from bonsai.bim.module.structural.data import StructuralLoadCasesData, StructuralLoadsData, BoundaryConditionsData, LoadGroupDecorationData +from bonsai.bim.module.structural.data import ( + StructuralLoadCasesData, + StructuralLoadsData, + BoundaryConditionsData, + LoadGroupDecorationData, +) from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -34,14 +39,17 @@ from bpy.props import ( CollectionProperty, ) + def get_load_groups_to_show(self, context): if not LoadGroupDecorationData.is_loaded: LoadGroupDecorationData.load() return LoadGroupDecorationData.data["load groups to show"] + def update_activity_type(self, context): LoadGroupDecorationData.is_loaded = False + def get_applicable_structural_load_types(self, context): if not StructuralLoadCasesData.is_loaded: StructuralLoadCasesData.load() @@ -162,13 +170,22 @@ class BIMStructuralProperties(PropertyGroup): show_loads: BoolProperty(name="Show Loads", default=False) update_load_repr: BoolProperty(name="Update Load Representation", default=False) enable_repr_auto_update: BoolProperty(name="Auto Update", default=False) - reference_frame: EnumProperty(items=[("GLOBAL_COORDS","Global","Show loads in global reference frame"), - ("LOCAL_COORDS","Local","Show loads in local reference frame")], name= "Reference Frame") - activity_type: EnumProperty(items=[("Action","Actions","Show actions loads"), - ("External Reaction","External Reactions","Show reactions on boundary conditions"), - ("Internal Reactions","Internal Reactions","Show internal reactions on members")], - name="Activity Type", - update=update_activity_type) + reference_frame: EnumProperty( + items=[ + ("GLOBAL_COORDS", "Global", "Show loads in global reference frame"), + ("LOCAL_COORDS", "Local", "Show loads in local reference frame"), + ], + name="Reference Frame", + ) + activity_type: EnumProperty( + items=[ + ("Action", "Actions", "Show actions loads"), + ("External Reaction", "External Reactions", "Show reactions on boundary conditions"), + ("Internal Reactions", "Internal Reactions", "Show internal reactions on members"), + ], + name="Activity Type", + update=update_activity_type, + ) load_group_to_show: EnumProperty(items=get_load_groups_to_show, name="Load Groups") diff --git a/src/bonsai/bonsai/bim/module/structural/shader.py b/src/bonsai/bonsai/bim/module/structural/shader.py index 29e710fafe..c866819bc0 100644 --- a/src/bonsai/bonsai/bim/module/structural/shader.py +++ b/src/bonsai/bonsai/bim/module/structural/shader.py @@ -17,38 +17,49 @@ # along with Bonsai. If not, see . import gpu +from typing import Literal class DecorationShader: "shader for the load decorations" + def __init__(self): pass - def new(self, pattern: str) -> gpu.types.GPUShader: + + def new(self, pattern: Literal["PERPENDICULAR DISTRIBUTED FORCE", + "PARALLEL DISTRIBUTED FORCE", + "DISTRIBUTED MOMENT", + "SINGLE FORCE", + "SINGLE MOMENT", + "PLANAR LOAD",]) -> gpu.types.GPUShader: """pattern: string description of the desired shader Possible values - PERPENDICULAR DISTRIBUTED FORCE: pattern for distributed force + PERPENDICULAR DISTRIBUTED FORCE: pattern for distributed force perpendicular to the curve member axis - PARALLEL DISTRIBUTED FORCE: pattern for distributed force + 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" - } + 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: + raise ValueError( + """pattern must be one of: PERPENDICULAR DISTRIBUTED FORCE PARALLEL DISTRIBUTED FORCE, DISTRIBUTED MOMENT, SINGLE FORCE, SINGLE MOMENT, - PLANAR LOAD""") + PLANAR LOAD""" + ) if "DISTRIBUTED" in pattern.upper(): shader = self.get_linear_shader(pattern) return shader @@ -59,29 +70,31 @@ class DecorationShader: shader = self.get_planar_shader() return shader - def get_linear_shader(self, pattern: str) -> gpu.types.GPUShader: + def get_linear_shader(self, pattern: Literal["PERPENDICULAR DISTRIBUTED FORCE", + "PARALLEL DISTRIBUTED FORCE", + "DISTRIBUTED MOMENT"]) -> 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") + 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") - shader_info.push_constant('FLOAT', "spacing") - shader_info.push_constant('FLOAT', "maxload") - - shader_info.vertex_in(0, 'VEC3', "position") - shader_info.vertex_in(1, 'VEC3', "sin_quad_lin_forces") - shader_info.vertex_in(2, 'VEC3', "coord") - + shader_info.push_constant("MAT4", "viewProjectionMatrix") + shader_info.push_constant("VEC4", "color") + shader_info.push_constant("FLOAT", "spacing") + shader_info.push_constant("FLOAT", "maxload") + + shader_info.vertex_in(0, "VEC3", "position") + shader_info.vertex_in(1, "VEC3", "sin_quad_lin_forces") + shader_info.vertex_in(2, "VEC3", "coord") + shader_info.vertex_out(vert_out) - shader_info.fragment_out(0, 'VEC4', "FragColor") - + shader_info.fragment_out(0, "VEC4", "FragColor") + shader_info.vertex_source( "void main()" "{" @@ -90,207 +103,185 @@ class DecorationShader: " forces = sin_quad_lin_forces;" "}" ) - + if pattern == "PERPENDICULAR DISTRIBUTED FORCE": shader_info.fragment_source( - "void main()" - "{" - "float x = co.x;" - "float y = co.y;" - "float abs_y = abs(y);" - - "float a = abs(mod(x,spacing)-0.5*spacing)*5.0;" - "float b = step(a,abs_y)*(step(abs_y,1.2*spacing));" - "float c = step(0.8*spacing,mod(x+0.4*spacing,spacing))*(step(1.2*spacing,abs_y));" - - "float sinvalue = forces.x;" - "float quadraticvalue = forces.y;" - "float linearvalue = forces.z;" - "x = co.x/co.z;" - "float f = (sin(x*3.1416)*sinvalue" - "+(-4.*x*x+4.*x)*quadraticvalue" - "+linearvalue)/maxload;" - "float mask = step(0.,y)*step(y,f)+step(y,0.)*step(f,y);" - - "float top = step(abs(y-f),0.2*1.2*spacing);" - "float d = clamp(top+b+c,0.0,0.9)*mask;" - "if (d == 0.0) discard;" - "FragColor = vec4(color.xyz,d*color.w);" - "}" - ) + "void main()" + "{" + "float x = co.x;" + "float y = co.y;" + "float abs_y = abs(y);" + "float a = abs(mod(x,spacing)-0.5*spacing)*5.0;" + "float b = step(a,abs_y)*(step(abs_y,1.2*spacing));" + "float c = step(0.8*spacing,mod(x+0.4*spacing,spacing))*(step(1.2*spacing,abs_y));" + "float sinvalue = forces.x;" + "float quadraticvalue = forces.y;" + "float linearvalue = forces.z;" + "x = co.x/co.z;" + "float f = (sin(x*3.1416)*sinvalue" + "+(-4.*x*x+4.*x)*quadraticvalue" + "+linearvalue)/maxload;" + "float mask = step(0.,y)*step(y,f)+step(y,0.)*step(f,y);" + "float top = step(abs(y-f),0.2*1.2*spacing);" + "float d = clamp(top+b+c,0.0,0.9)*mask;" + "if (d == 0.0) discard;" + "FragColor = vec4(color.xyz,d*color.w);" + "}" + ) elif pattern == "PARALLEL DISTRIBUTED FORCE": shader_info.fragment_source( - "void main()" - "{" - "float y = co.y;" - "float x = step(0.,y)*(co.z-co.x)+step(y,0.)*(co.x);" - "float abs_y = abs(y);" - - "float a = abs(mod(abs_y,spacing)-0.5*spacing)*5.0;" - "float a2 = mod(x,3.0*spacing);" - "float b = step(a,a2)*step(a2,1.2*spacing);" - "float c = step(0.8*spacing,mod(abs_y+0.4*spacing,spacing))" - "*(step(1.2*spacing,a2))*step(a2,2.5*spacing);" - "float sinvalue = forces.x;" - "float quadraticvalue = forces.y;" - "float linearvalue = forces.z;" - "x = co.x/co.z;" - "float f = (sin(x*3.1416)*sinvalue" - "+(-4.*x*x+4.*x)*quadraticvalue" - "+linearvalue)/maxload;" - "float mask = step(0.,y)*step(y,f)+step(y,0.)*step(f,y);" - - "float top = step(abs(y-f),0.2*1.2*spacing);" - "float d = clamp(top+b+c,0.0,0.9)*mask;" - "if (d == 0.0) discard;" - "FragColor = vec4(color.xyz,d*color.w);" - "}" - ) + "void main()" + "{" + "float y = co.y;" + "float x = step(0.,y)*(co.z-co.x)+step(y,0.)*(co.x);" + "float abs_y = abs(y);" + "float a = abs(mod(abs_y,spacing)-0.5*spacing)*5.0;" + "float a2 = mod(x,3.0*spacing);" + "float b = step(a,a2)*step(a2,1.2*spacing);" + "float c = step(0.8*spacing,mod(abs_y+0.4*spacing,spacing))" + "*(step(1.2*spacing,a2))*step(a2,2.5*spacing);" + "float sinvalue = forces.x;" + "float quadraticvalue = forces.y;" + "float linearvalue = forces.z;" + "x = co.x/co.z;" + "float f = (sin(x*3.1416)*sinvalue" + "+(-4.*x*x+4.*x)*quadraticvalue" + "+linearvalue)/maxload;" + "float mask = step(0.,y)*step(y,f)+step(y,0.)*step(f,y);" + "float top = step(abs(y-f),0.2*1.2*spacing);" + "float d = clamp(top+b+c,0.0,0.9)*mask;" + "if (d == 0.0) discard;" + "FragColor = vec4(color.xyz,d*color.w);" + "}" + ) elif pattern == "DISTRIBUTED MOMENT": shader_info.fragment_source( - "void main()" - "{" - "float x = step(co.y,0.)*(co.x)+step(0.,co.y)*(co.z-co.x);" - "float y = step(co.y,-0.00001)*(co.y)+step(0.,co.y)*(0.-co.y);" - "x = mod((0.5/spacing)*x,1.4)-0.7;" - "y = mod((0.5/spacing)*y,1.4)-0.7;" - "float abs_y = abs(y);" - "vec2 st = vec2(1.9*x,y);" - "vec2 orig = vec2(0.,0.);" - - "float circ = step(distance(st,orig),0.33)*step(0.27,distance(st,orig));" - "float tri_mask = step(st.y,st.x)+step(-st.x,st.y);" - "float circ_arrow = step(st.x,4.0*st.y-0.75)*step(0.25*st.y-0.34,st.x)*(1.-tri_mask);" + "void main()" + "{" + "float x = step(co.y,0.)*(co.x)+step(0.,co.y)*(co.z-co.x);" + "float y = step(co.y,-0.00001)*(co.y)+step(0.,co.y)*(0.-co.y);" + "x = mod((0.5/spacing)*x,1.4)-0.7;" + "y = mod((0.5/spacing)*y,1.4)-0.7;" + "float abs_y = abs(y);" + "vec2 st = vec2(1.9*x,y);" + "vec2 orig = vec2(0.,0.);" + "float circ = step(distance(st,orig),0.33)*step(0.27,distance(st,orig));" + "float tri_mask = step(st.y,st.x)+step(-st.x,st.y);" + "float circ_arrow = step(st.x,4.0*st.y-0.75)*step(0.25*st.y-0.34,st.x)*(1.-tri_mask);" + "float circmask = step(distance(st,orig),0.1)+step(0.5,distance(st,orig))+step(st.x,0.);" + "float body = step(-0.03,st.y)*step(st.y,0.03)*step(-0.3,x)*step(x,0.576);" + "float body_arrow = step(-0.5+3.*st.y,x)*step(-0.5-3.*st.y,x)*step(x,-0.3);" + "float d = clamp(circmask*(body+body_arrow)+circ_arrow+circ*tri_mask,0.,1.);" + "float sinvalue = forces.x;" + "float quadraticvalue = forces.y;" + "float linearvalue = forces.z;" + "x = co.x/co.z;" + "float f = (sin(x*3.1416)*sinvalue" + "+(-4.*x*x+4.*x)*quadraticvalue" + "+linearvalue)/maxload;" + "float mask = step(0.,co.y)*step(co.y,f)+step(co.y,0.)*step(f,co.y);" + "float top = step(abs(co.y-f),0.2*1.2*spacing);" + "d = clamp(top+d,0.0,0.9)*mask;" + "if (d == 0.0) discard;" + "FragColor = vec4(color.xyz,d*color.w);" + "}" + ) - "float circmask = step(distance(st,orig),0.1)+step(0.5,distance(st,orig))+step(st.x,0.);" - "float body = step(-0.03,st.y)*step(st.y,0.03)*step(-0.3,x)*step(x,0.576);" - "float body_arrow = step(-0.5+3.*st.y,x)*step(-0.5-3.*st.y,x)*step(x,-0.3);" - "float d = clamp(circmask*(body+body_arrow)+circ_arrow+circ*tri_mask,0.,1.);" - - "float sinvalue = forces.x;" - "float quadraticvalue = forces.y;" - "float linearvalue = forces.z;" - "x = co.x/co.z;" - "float f = (sin(x*3.1416)*sinvalue" - "+(-4.*x*x+4.*x)*quadraticvalue" - "+linearvalue)/maxload;" - "float mask = step(0.,co.y)*step(co.y,f)+step(co.y,0.)*step(f,co.y);" - - "float top = step(abs(co.y-f),0.2*1.2*spacing);" - - "d = clamp(top+d,0.0,0.9)*mask;" - "if (d == 0.0) discard;" - "FragColor = vec4(color.xyz,d*color.w);" - "}" - ) - shader = gpu.shader.create_from_info(shader_info) del vert_out del shader_info return shader - - def get_point_shader(self, pattern: str) -> gpu.types.GPUShader: - """ param: pattern: type of pattern + + def get_point_shader(self, pattern: Literal["SINGLE FORCE","SINGLE MOMENT"]) -> gpu.types.GPUShader: + """param: pattern: type of pattern SINGLE FORCE, SINGLE MOMENT""" vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") - vert_out.smooth('VEC3', "co") - + vert_out.smooth("VEC3", "co") + shader_info = gpu.types.GPUShaderCreateInfo() - shader_info.push_constant('MAT4', "viewProjectionMatrix") - shader_info.push_constant('VEC4', "color") - shader_info.push_constant('FLOAT', "spacing") - - shader_info.vertex_in(0, 'VEC3', "position") - shader_info.vertex_in(1, 'VEC3', "coord") - + shader_info.push_constant("MAT4", "viewProjectionMatrix") + shader_info.push_constant("VEC4", "color") + shader_info.push_constant("FLOAT", "spacing") + + shader_info.vertex_in(0, "VEC3", "position") + shader_info.vertex_in(1, "VEC3", "coord") + shader_info.vertex_out(vert_out) - shader_info.fragment_out(0, 'VEC4', "FragColor") - + shader_info.fragment_out(0, "VEC4", "FragColor") + shader_info.vertex_source( - "void main()" - "{" - " gl_Position = viewProjectionMatrix * vec4(position, 1.0f);" - " co = coord;" - "}" + "void main()" "{" " gl_Position = viewProjectionMatrix * vec4(position, 1.0f);" " co = coord;" "}" ) - + if pattern == "SINGLE FORCE": shader_info.fragment_source( - "void main()" - "{" - "float body = step(abs(co.x),0.2*spacing)*step(2.*spacing,co.y);" - "float arrow = step(3.5*abs(co.x)+0.02,abs(co.y))*step(co.y,2.*spacing)*step(0.,co.y);" - "float d = clamp(body+arrow,0.0,0.5);" - "if (d == 0.0) discard;" - "FragColor = vec4(color.xyz,d*color.w);" - "}" - ) - + "void main()" + "{" + "float body = step(abs(co.x),0.2*spacing)*step(2.*spacing,co.y);" + "float arrow = step(3.5*abs(co.x)+0.02,abs(co.y))*step(co.y,2.*spacing)*step(0.,co.y);" + "float d = clamp(body+arrow,0.0,0.5);" + "if (d == 0.0) discard;" + "FragColor = vec4(color.xyz,d*color.w);" + "}" + ) + elif pattern == "SINGLE MOMENT": shader_info.fragment_source( - "void main()" - "{" - "float circ = step(distance(co.xy,vec2(0.,0.)),0.33)*step(0.27,distance(co.xy,vec2(0.,0.)));" - "float mask = step(co.y,co.x)+step(-co.x,co.y);" - "float circ_arrow = step(co.x,4.0*co.y-0.75)*step(0.25*co.y-0.34,co.x)*(1.-mask);" - "float d = clamp(circ_arrow+circ*mask,0.0,0.5);" - "if (d == 0.0) discard;" - "FragColor = vec4(color.xyz,d*color.w);" - "}" - ) - + "void main()" + "{" + "float circ = step(distance(co.xy,vec2(0.,0.)),0.33)*step(0.27,distance(co.xy,vec2(0.,0.)));" + "float mask = step(co.y,co.x)+step(-co.x,co.y);" + "float circ_arrow = step(co.x,4.0*co.y-0.75)*step(0.25*co.y-0.34,co.x)*(1.-mask);" + "float d = clamp(circ_arrow+circ*mask,0.0,0.5);" + "if (d == 0.0) discard;" + "FragColor = vec4(color.xyz,d*color.w);" + "}" + ) + shader = gpu.shader.create_from_info(shader_info) del vert_out del shader_info return shader - + def get_planar_shader(self) -> gpu.types.GPUShader: """shader for planar loads""" vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") - vert_out.smooth('VEC3', "co") - + vert_out.smooth("VEC3", "co") + shader_info = gpu.types.GPUShaderCreateInfo() - shader_info.push_constant('MAT4', "viewProjectionMatrix") - shader_info.push_constant('VEC4', "color") - shader_info.push_constant('FLOAT', "spacing") - - shader_info.vertex_in(0, 'VEC3', "position") - shader_info.vertex_in(1, 'VEC3', "coord") - + shader_info.push_constant("MAT4", "viewProjectionMatrix") + shader_info.push_constant("VEC4", "color") + shader_info.push_constant("FLOAT", "spacing") + + shader_info.vertex_in(0, "VEC3", "position") + shader_info.vertex_in(1, "VEC3", "coord") + shader_info.vertex_out(vert_out) - shader_info.fragment_out(0, 'VEC4', "FragColor") - + shader_info.fragment_out(0, "VEC4", "FragColor") + shader_info.vertex_source( + "void main()" "{" " gl_Position = viewProjectionMatrix * vec4(position, 1.0f);" " co = coord;" "}" + ) + + shader_info.fragment_source( "void main()" "{" - " gl_Position = viewProjectionMatrix * vec4(position, 1.0f);" - " co = coord;" - "}" - ) - - shader_info.fragment_source( - "void main()" - "{" "float x = co.x;" "float y = co.y;" "float abs_y = abs(y);" - "float a = abs(mod(x,spacing)-0.5*spacing)*5.0;" "float b = step(a,abs_y)*(step(abs_y,1.2*spacing));" "float c = step(0.8*spacing,mod(x+0.4*spacing,spacing))*(step(1.2*spacing,abs_y));" - - "float mask = step(0.,y)*step(y,0.98)+step(y,0.)*step(0.98,y);" - "float top = step(abs(y-0.98),0.2*1.2*spacing);" "float d = clamp(0.2*y+(top+b+c)*mask,0.0,0.4);" "FragColor = vec4(color.xyz,d*color.w);" - "}" + "}" ) - + shader = gpu.shader.create_from_info(shader_info) del vert_out del shader_info diff --git a/src/bonsai/bonsai/bim/module/structural/ui.py b/src/bonsai/bonsai/bim/module/structural/ui.py index 23e075613c..fdea8b074c 100644 --- a/src/bonsai/bonsai/bim/module/structural/ui.py +++ b/src/bonsai/bonsai/bim/module/structural/ui.py @@ -444,6 +444,7 @@ class BIM_UL_structural_activities(UIList): row.label(text=item.name) row.label(text=item.applied_load_class) + class BIM_PT_show_structural_activities(Panel): bl_label = "Show Loads" bl_idname = "BIM_PT_show_structural_activities" @@ -463,16 +464,16 @@ class BIM_PT_show_structural_activities(Panel): row = self.layout.row(align=True) row.operator( - "bim.show_loads", - text="Show loads" , - icon="HIDE_OFF", - ) + "bim.show_loads", + text="Show loads", + icon="HIDE_OFF", + ) row = self.layout.row(align=True) - row.prop(self.props,"reference_frame") + row.prop(self.props, "reference_frame") row = self.layout.row(align=True) - row.prop(self.props,"activity_type") + row.prop(self.props, "activity_type") row = self.layout.row(align=True) - row.prop(self.props,"load_group_to_show") + row.prop(self.props, "load_group_to_show") class BIM_PT_structural_loads(Panel):