mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 22:50:21 +00:00
cleanup
This commit is contained in:
committed by
Dion Moult
parent
aa21fe9060
commit
1918138f68
@@ -1,10 +1,8 @@
|
|||||||
from math import pi
|
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import bpy
|
import bpy
|
||||||
import gpu
|
import gpu
|
||||||
import blf
|
import blf
|
||||||
from bpy_extras import view3d_utils
|
|
||||||
from bpy.types import SpaceView3D
|
from bpy.types import SpaceView3D
|
||||||
from gpu_extras.batch import batch_for_shader
|
from gpu_extras.batch import batch_for_shader
|
||||||
from typing import Iterable, Union
|
from typing import Iterable, Union
|
||||||
@@ -29,7 +27,7 @@ class LoadsDecorator:
|
|||||||
)
|
)
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler, (), "WINDOW", "POST_VIEW"))
|
cls.handlers.append(SpaceView3D.draw_handler_add(handler, (), "WINDOW", "POST_VIEW"))
|
||||||
cls.decoration_data = ShaderInfo()
|
cls.decoration_data = ShaderInfo()
|
||||||
cls.update(context)
|
cls.update()
|
||||||
cls.is_installed = True
|
cls.is_installed = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -42,7 +40,7 @@ class LoadsDecorator:
|
|||||||
cls.is_installed = False
|
cls.is_installed = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update(cls, context:bpy.types.Context) -> None:
|
def update(cls) -> None:
|
||||||
cls.decoration_data.update()
|
cls.decoration_data.update()
|
||||||
cls.text_info = cls.decoration_data.text_info
|
cls.text_info = cls.decoration_data.text_info
|
||||||
cls.shader_info = cls.decoration_data.info
|
cls.shader_info = cls.decoration_data.info
|
||||||
@@ -83,9 +81,8 @@ class LoadsDecorator:
|
|||||||
#getting depth buffer info, code adapted from:
|
#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
|
#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()
|
framebuffer = gpu.state.active_framebuffer_get()
|
||||||
viewport_info = gpu.state.viewport_get()
|
width = context.region.width
|
||||||
width = viewport_info[2]
|
height = context.region.height
|
||||||
height = viewport_info[3]
|
|
||||||
depth_buffer = framebuffer.read_depth(0, 0, width, height)
|
depth_buffer = framebuffer.read_depth(0, 0, width, height)
|
||||||
depth_array = np.array(depth_buffer.to_list())
|
depth_array = np.array(depth_buffer.to_list())
|
||||||
|
|
||||||
@@ -95,7 +92,7 @@ class LoadsDecorator:
|
|||||||
self.depth_array = n / (f - (f - n) * depth_array) * (f - n)
|
self.depth_array = n / (f - (f - n) * depth_array) * (f - n)
|
||||||
|
|
||||||
for info in self.text_info:
|
for info in self.text_info:
|
||||||
text_position = self.location_3d_to_region_2d(info["position"],info["normal"],context)
|
text_position = self.location_3d_to_region_2d(info["position"],context)
|
||||||
if text_position is not None:
|
if text_position is not None:
|
||||||
font_id = 0
|
font_id = 0
|
||||||
blf.position(font_id, text_position[0], text_position[1], text_position[2])
|
blf.position(font_id, text_position[0], text_position[1], text_position[2])
|
||||||
@@ -103,13 +100,12 @@ class LoadsDecorator:
|
|||||||
blf.color(font_id, 0.9, 0.9, 0.9, 1.0)
|
blf.color(font_id, 0.9, 0.9, 0.9, 1.0)
|
||||||
blf.draw(font_id, info["text"])
|
blf.draw(font_id, info["text"])
|
||||||
|
|
||||||
def location_3d_to_region_2d(self, coord: Iterable, normal: 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.
|
"""Convert from 3D space to 2D screen space.
|
||||||
Filter out the text supposed to be hidden by 3D elements, using the depth array.
|
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"""
|
It also hides text that are distant from the camera view to avoid clutter"""
|
||||||
|
|
||||||
coord = Vector(coord)
|
coord = Vector(coord)
|
||||||
normal = Vector(normal)
|
|
||||||
rv3d = context.region_data
|
rv3d = context.region_data
|
||||||
perspective = rv3d.view_perspective
|
perspective = rv3d.view_perspective
|
||||||
view_matrix = rv3d.view_matrix
|
view_matrix = rv3d.view_matrix
|
||||||
|
|||||||
@@ -1,34 +1,67 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import gpu
|
|
||||||
import bmesh
|
import bmesh
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from math import sin
|
from math import sin
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api
|
import ifcopenshell.api
|
||||||
import ifcopenshell.util.attribute
|
import ifcopenshell.util.attribute
|
||||||
|
import ifcopenshell.util.unit as ifcunit
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.ifc import IfcStore
|
from bonsai.bim.ifc import IfcStore
|
||||||
from bonsai.bim.module.structural.shader import DecorationShader
|
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]]})
|
||||||
|
|
||||||
|
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]]})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ShaderInfo:
|
class ShaderInfo:
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.is_empty = True
|
self.is_empty = True
|
||||||
self.shader = DecorationShader()
|
self.shader = DecorationShader()
|
||||||
#self.shader_type = shader_type
|
self.curve_members: dict[str,MemberInfo] = {}
|
||||||
#self.args = {}
|
self.point_members:dict[str,MemberInfo] = {}
|
||||||
#self.indices = []
|
self.surface_members:dict[str,MemberInfo] = {}
|
||||||
self.curve_members = {}
|
|
||||||
self.point_members = {}
|
|
||||||
self.surface_members = {}
|
|
||||||
self.text_info = []
|
self.text_info = []
|
||||||
self.info = []
|
self.info = []
|
||||||
self.force_unit = ""
|
self.force_unit = ""
|
||||||
|
self.moment_unit = ""
|
||||||
self.linear_force_unit = ""
|
self.linear_force_unit = ""
|
||||||
|
self.linear_moment_unit = ""
|
||||||
self.planar_force_unit = ""
|
self.planar_force_unit = ""
|
||||||
|
|
||||||
def update(self):
|
def update(self) -> None:
|
||||||
self.info = []
|
self.info = []
|
||||||
self.text_info = []
|
self.text_info = []
|
||||||
self.curve_members = {}
|
self.curve_members = {}
|
||||||
@@ -42,7 +75,7 @@ class ShaderInfo:
|
|||||||
if len(self.info):
|
if len(self.info):
|
||||||
self.is_empty = False
|
self.is_empty = False
|
||||||
|
|
||||||
def get_force_units(self):
|
def get_force_units(self) -> None:
|
||||||
def get_unit_symbol(unit: ifcopenshell.entity_instance) -> str:
|
def get_unit_symbol(unit: ifcopenshell.entity_instance) -> str:
|
||||||
prefix_symbols = {
|
prefix_symbols = {
|
||||||
"EXA": "E",
|
"EXA": "E",
|
||||||
@@ -119,39 +152,77 @@ class ShaderInfo:
|
|||||||
symbol += unit_symbols.get(unit.Name.replace("METER", "METRE"), "?")
|
symbol += unit_symbols.get(unit.Name.replace("METER", "METRE"), "?")
|
||||||
return symbol
|
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")
|
force_units = [u for u in tool.Ifc.get().by_type("IfcNamedUnit")
|
||||||
if u.UnitType == "FORCEUNIT"]
|
if u.UnitType == "FORCEUNIT"]
|
||||||
linear_force_units = [u for u in tool.Ifc.get().by_type("IfcDerivedUnit")
|
linear_force_units = [u for u in tool.Ifc.get().by_type("IfcDerivedUnit")
|
||||||
if u.UnitType == "LINEARFORCEUNIT"]
|
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")
|
planar_force_units = [u for u in tool.Ifc.get().by_type("IfcDerivedUnit")
|
||||||
if u.UnitType == "PLANARFORCEUNIT"]
|
if u.UnitType == "PLANARFORCEUNIT"]
|
||||||
|
|
||||||
conversion_force_unit = [u for u in force_units if u.is_a("IfcConversionBasedUnit")]
|
conversion_force_unit = [u for u in force_units if u.is_a("IfcConversionBasedUnit")]
|
||||||
if len(conversion_force_unit) == 0:
|
if len(conversion_force_unit) == 0:
|
||||||
conversion_force_unit.append(force_units[0])
|
conversion_force_unit.append(force_units[0])
|
||||||
self.force_unit = get_unit_symbol(conversion_force_unit[0])
|
self.force_unit = ifcunit.get_unit_symbol(conversion_force_unit[0])
|
||||||
|
|
||||||
|
conversion_length_unit = [u for u in length_units if u.is_a("IfcConversionBasedUnit")]
|
||||||
|
if len(conversion_length_unit) == 0:
|
||||||
|
conversion_length_unit.append(length_units[0])
|
||||||
|
length_unit = ifcunit.get_unit_symbol(conversion_length_unit[0])
|
||||||
|
self.moment_unit = self.force_unit + "." + length_unit
|
||||||
|
|
||||||
first = ""
|
first = ""
|
||||||
second = ""
|
second = ""
|
||||||
for e in linear_force_units[0].Elements:
|
for e in linear_force_units[0].Elements:
|
||||||
if e.Unit.UnitType == "FORCEUNIT":
|
if e.Unit.UnitType == "FORCEUNIT":
|
||||||
first = get_unit_symbol(e.Unit)
|
first = ifcunit.get_unit_symbol(e.Unit)
|
||||||
if e.Unit.UnitType == "LENGTHUNIT":
|
if e.Unit.UnitType == "LENGTHUNIT":
|
||||||
second = get_unit_symbol(e.Unit)
|
second = ifcunit.get_unit_symbol(e.Unit)
|
||||||
self.linear_force_unit = first + "/" + second
|
self.linear_force_unit = first + "/" + second
|
||||||
|
|
||||||
|
first = ""
|
||||||
|
second = ""
|
||||||
|
for e in linear_moment_units[0].Elements:
|
||||||
|
if e.Unit.UnitType == "FORCEUNIT":
|
||||||
|
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
|
||||||
|
|
||||||
first = ""
|
first = ""
|
||||||
second = ""
|
second = ""
|
||||||
for e in planar_force_units[0].Elements:
|
for e in planar_force_units[0].Elements:
|
||||||
if e.Unit.UnitType == "FORCEUNIT":
|
if e.Unit.UnitType == "FORCEUNIT":
|
||||||
first = get_unit_symbol(e.Unit)
|
first = ifcunit.get_unit_symbol(e.Unit)
|
||||||
if e.Unit.UnitType == "LENGTHUNIT":
|
if e.Unit.UnitType == "LENGTHUNIT":
|
||||||
second = get_unit_symbol(e.Unit)+"2"
|
second = ifcunit.get_unit_symbol(e.Unit)+"2"
|
||||||
if e.Unit.UnitType == "AREAUNIT":
|
if e.Unit.UnitType == "AREAUNIT":
|
||||||
second = get_unit_symbol(e.Unit)
|
second = ifcunit.get_unit_symbol(e.Unit)
|
||||||
self.planar_force_unit = first + "/" + second
|
self.planar_force_unit = first + "/" + second
|
||||||
|
|
||||||
def get_strucutural_elements_and_activities(self):
|
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,element, activity, factor):
|
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
|
||||||
|
the second dict contais two keys, as follow:
|
||||||
|
{
|
||||||
|
"member": the strucutral member itself
|
||||||
|
"activities: list[(activity, factor)] for each activity applied to the member
|
||||||
|
}
|
||||||
|
dict_name: "point_members", "curve_members" or "surface_members"
|
||||||
|
element: IfcStructuralMember
|
||||||
|
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:
|
if dic is None:
|
||||||
return
|
return
|
||||||
@@ -165,8 +236,25 @@ class ShaderInfo:
|
|||||||
else:
|
else:
|
||||||
member["activities"].append((activity,factor))
|
member["activities"].append((activity,factor))
|
||||||
|
|
||||||
def recursive_subgroups(groups, rec_limit, activity_type, factor = 1):
|
def recursive_subgroups(groups: list[ifcopenshell.entity_instance],
|
||||||
if len(groups) == 0 or rec_limit == 0:
|
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
|
||||||
|
it also creates a list with all the activities applied to that member that are in the same
|
||||||
|
load group or subgroup (see populate_members_dict description)
|
||||||
|
|
||||||
|
groups: list of Ifc load case, load group or load combination
|
||||||
|
rec_limit: maximum number of recursions
|
||||||
|
activity_type: "Action" or "External Reaction"
|
||||||
|
factor: the factor applied to the group, default = 1
|
||||||
|
this factor will be multiplied by the group coefficient and the factor of a
|
||||||
|
IfcRelAssignsToGroupByFactor relationship of subgroups in load combinations
|
||||||
|
|
||||||
|
"""
|
||||||
|
if rec_limit == 0 or len(groups) == 0:
|
||||||
return None
|
return None
|
||||||
for group in groups:
|
for group in groups:
|
||||||
subgorups = []
|
subgorups = []
|
||||||
@@ -210,14 +298,14 @@ class ShaderInfo:
|
|||||||
groups = [file.by_id(group_definition_id)]
|
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):
|
def get_planar_loads(self) -> None:
|
||||||
|
"""get the necessary information to render the planar load representation in 3D and its text information"""
|
||||||
|
|
||||||
list_of_surfaces = self.surface_members
|
list_of_surfaces = self.surface_members
|
||||||
shader = self.shader.get("PLANAR LOAD")
|
shader = self.shader.new("PLANAR LOAD")
|
||||||
maximum = 0
|
maximum = 0
|
||||||
for value in list_of_surfaces.values():
|
for value in list_of_surfaces.values():
|
||||||
surf = value["member"]
|
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"]
|
activity_list = value["activities"]
|
||||||
if len(activity_list) == 0:
|
if len(activity_list) == 0:
|
||||||
continue
|
continue
|
||||||
@@ -246,7 +334,6 @@ class ShaderInfo:
|
|||||||
bm.verts.ensure_lookup_table()
|
bm.verts.ensure_lookup_table()
|
||||||
bm.faces.ensure_lookup_table()
|
bm.faces.ensure_lookup_table()
|
||||||
|
|
||||||
add_index = len(positions)
|
|
||||||
for v in bm.verts:
|
for v in bm.verts:
|
||||||
p1 = np.array(mat @ v.co)
|
p1 = np.array(mat @ v.co)
|
||||||
positions.append(p1)
|
positions.append(p1)
|
||||||
@@ -273,7 +360,6 @@ class ShaderInfo:
|
|||||||
|
|
||||||
self.text_info.append(
|
self.text_info.append(
|
||||||
{"position": mat @ center - Vector((orientation@values)*0.2/maximum),
|
{"position": mat @ center - Vector((orientation@values)*0.2/maximum),
|
||||||
"normal": Vector(mesh.polygons[0].normal),
|
|
||||||
"text": f'{values[2]:.5f} {self.planar_force_unit}'}
|
"text": f'{values[2]:.5f} {self.planar_force_unit}'}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -285,7 +371,13 @@ class ShaderInfo:
|
|||||||
"uniforms": [["color", (0.2,0,1,1)],["spacing", 0.2]]
|
"uniforms": [["color", (0.2,0,1,1)],["spacing", 0.2]]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def get_planar_loads_values(self, activity_list,element_rotation_matrix):
|
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
|
||||||
|
"""
|
||||||
values = np.zeros((3))
|
values = np.zeros((3))
|
||||||
for item in activity_list:
|
for item in activity_list:
|
||||||
activity = item[0]
|
activity = item[0]
|
||||||
@@ -301,14 +393,20 @@ class ShaderInfo:
|
|||||||
values += transform@temp
|
values += transform@temp
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def get_surface_member_rotation(self,surface_member):
|
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")
|
representation = ifcopenshell.util.representation.get_representation(surface_member, "Model")
|
||||||
repr_item = representation.Items[0]
|
repr_item = representation.Items[0]
|
||||||
placement = ifcopenshell.util.placement.get_axis2placement(repr_item.FaceSurface.Position)
|
placement = ifcopenshell.util.placement.get_axis2placement(repr_item.FaceSurface.Position)
|
||||||
rotation = placement[0:3,0:3]
|
rotation = placement[0:3,0:3]
|
||||||
return rotation
|
return rotation
|
||||||
|
|
||||||
def get_point_connection_rotation(self, point_connection):
|
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:
|
if point_connection.ConditionCoordinateSystem is not None:
|
||||||
placement = ifcopenshell.util.placement.get_axis2placement(point_connection.ConditionCoordinateSystem)
|
placement = ifcopenshell.util.placement.get_axis2placement(point_connection.ConditionCoordinateSystem)
|
||||||
else:
|
else:
|
||||||
@@ -316,7 +414,10 @@ class ShaderInfo:
|
|||||||
rotation = placement[0:3,0:3]
|
rotation = placement[0:3,0:3]
|
||||||
return rotation
|
return rotation
|
||||||
|
|
||||||
def get_curve_member_rotation(self, curve_member):
|
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
|
z = curve_member.Axis.DirectionRatios
|
||||||
edge = curve_member.Representation.Representations[0].Items[0]
|
edge = curve_member.Representation.Representations[0].Items[0]
|
||||||
origin = edge.EdgeStart.VertexGeometry.Coordinates
|
origin = edge.EdgeStart.VertexGeometry.Coordinates
|
||||||
@@ -326,7 +427,10 @@ class ShaderInfo:
|
|||||||
rotation = placement[0:3,0:3]
|
rotation = placement[0:3,0:3]
|
||||||
return rotation
|
return rotation
|
||||||
|
|
||||||
def get_activity_transform_matrix(self, activity, element_rotation_matrix):
|
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"
|
"provides the transformation matrix to convert between reference frames"
|
||||||
global_or_local = activity.GlobalOrLocal
|
global_or_local = activity.GlobalOrLocal
|
||||||
props = bpy.context.scene.BIMStructuralProperties
|
props = bpy.context.scene.BIMStructuralProperties
|
||||||
@@ -338,28 +442,30 @@ class ShaderInfo:
|
|||||||
transform_matrix = element_rotation_matrix
|
transform_matrix = element_rotation_matrix
|
||||||
return transform_matrix
|
return transform_matrix
|
||||||
|
|
||||||
def get_point_loads(self):
|
def get_point_loads(self) -> None:
|
||||||
list_of_point_connections = tool.Ifc.get().by_type("IfcStructuralPointConnection")
|
"""get the necessary information to render the point load representation in 3D and its text information"""
|
||||||
|
|
||||||
list_of_point_connections = self.point_members
|
list_of_point_connections = self.point_members
|
||||||
for value in list_of_point_connections.values():
|
for value in list_of_point_connections.values():
|
||||||
conn = value["member"]
|
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"]
|
activity_list = value["activities"]
|
||||||
if len(activity_list) == 0:
|
if len(activity_list) == 0:
|
||||||
continue
|
continue
|
||||||
blender_object = IfcStore.get_element(getattr(conn, 'GlobalId', None))
|
blender_object = IfcStore.get_element(getattr(conn, 'GlobalId', None))
|
||||||
if blender_object.type == 'MESH':
|
if blender_object.type == 'MESH':
|
||||||
conn_location = blender_object.matrix_world @ blender_object.data.vertices[0].co
|
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)
|
rotation = self.get_point_connection_rotation(conn)
|
||||||
loads = self.get_point_loads_values(activity_list, rotation)
|
loads = self.get_point_loads_values(activity_list, rotation)
|
||||||
self.get_point_shader_args(loads, conn_location, rotation)
|
self.get_point_shader_args(loads, conn_location, rotation)
|
||||||
|
|
||||||
def get_point_shader_args(self,loads, location, rotation):
|
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)
|
location = np.array(location)
|
||||||
indices = []
|
indices = []
|
||||||
text_info = []
|
|
||||||
direction_dict = {
|
direction_dict = {
|
||||||
"fx": (np.array((1,0,0)),np.array((0,1,0)),np.array((0,0,1))),
|
"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))),
|
"fy": (np.array((0,1,0)),np.array((1,0,0)),np.array((0,0,1))),
|
||||||
@@ -403,7 +509,7 @@ class ShaderInfo:
|
|||||||
c2 = (1,1,0)
|
c2 = (1,1,0)
|
||||||
c3 = (-1,1,0)
|
c3 = (-1,1,0)
|
||||||
coords_for_shader = [c1,c2,c3,c2,c3]
|
coords_for_shader = [c1,c2,c3,c2,c3]
|
||||||
shader = self.shader.get("SINGLE FORCE")
|
shader = self.shader.new("SINGLE FORCE")
|
||||||
self.info.append(
|
self.info.append(
|
||||||
{
|
{
|
||||||
"shader": shader,
|
"shader": shader,
|
||||||
@@ -414,7 +520,6 @@ class ShaderInfo:
|
|||||||
)
|
)
|
||||||
self.text_info.append(
|
self.text_info.append(
|
||||||
{"position": location + d1,
|
{"position": location + d1,
|
||||||
"normal": d2/np.linalg.norm(d2),
|
|
||||||
"text": f'{loads[i]:.2f} {self.force_unit}'}
|
"text": f'{loads[i]:.2f} {self.force_unit}'}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -428,7 +533,7 @@ class ShaderInfo:
|
|||||||
c2 = (1,1,0)
|
c2 = (1,1,0)
|
||||||
c3 = (1,-1,0)
|
c3 = (1,-1,0)
|
||||||
coords_for_shader = [c1,c2,c3]
|
coords_for_shader = [c1,c2,c3]
|
||||||
shader = self.shader.get("SINGLE MOMENT")
|
shader = self.shader.new("SINGLE MOMENT")
|
||||||
self.info.append(
|
self.info.append(
|
||||||
{
|
{
|
||||||
"shader": shader,
|
"shader": shader,
|
||||||
@@ -439,11 +544,13 @@ class ShaderInfo:
|
|||||||
)
|
)
|
||||||
self.text_info.append(
|
self.text_info.append(
|
||||||
{"position": location +0.25*(d1 + d2),
|
{"position": location +0.25*(d1 + d2),
|
||||||
"normal": d2/np.linalg.norm(d2),
|
"text": f'{loads[i]:.2f} {self.moment_unit}'}
|
||||||
"text": f'{loads[i]:.2f} {self.force_unit}'}#change to moment unit
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_point_loads_values(self,activity_list,element_rotation_matrix):
|
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)
|
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:
|
for item in activity_list:
|
||||||
@@ -463,33 +570,21 @@ class ShaderInfo:
|
|||||||
return result_list
|
return result_list
|
||||||
|
|
||||||
|
|
||||||
def get_linear_loads(self): #for now it only works for distributed loads
|
def get_linear_loads(self)-> None:
|
||||||
position = []
|
position = []
|
||||||
indices = []
|
indices = []
|
||||||
sin_quad_lin = []
|
sin_quad_lin = []
|
||||||
coords_for_shader = []
|
coords_for_shader = []
|
||||||
color = []
|
color = []
|
||||||
text_info = []
|
|
||||||
uniforms = []
|
|
||||||
info = []
|
info = []
|
||||||
|
maxforce = 0
|
||||||
|
|
||||||
list_of_curve_members = tool.Ifc.get().by_type("IfcStructuralCurveMember")
|
|
||||||
list_of_curve_members = self.curve_members
|
list_of_curve_members = self.curve_members
|
||||||
for value in list_of_curve_members.values():
|
for value in list_of_curve_members.values():
|
||||||
member = value["member"]
|
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"]
|
activity_list = value["activities"]
|
||||||
if len(activity_list) == 0:
|
if len(activity_list) == 0:
|
||||||
continue
|
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))
|
blender_object = IfcStore.get_element(getattr(member, 'GlobalId', None))
|
||||||
|
|
||||||
@@ -501,16 +596,11 @@ class ShaderInfo:
|
|||||||
z_axis = Vector(getattr(z_direction, 'DirectionRatios', None)).normalized()
|
z_axis = Vector(getattr(z_direction, 'DirectionRatios', None)).normalized()
|
||||||
y_axis = z_axis.cross(x_axis).normalized()
|
y_axis = z_axis.cross(x_axis).normalized()
|
||||||
z_axis = x_axis.cross(y_axis).normalized()
|
z_axis = x_axis.cross(y_axis).normalized()
|
||||||
rot = self.get_curve_member_rotation(member)
|
rotation = 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
|
props = bpy.context.scene.BIMStructuralProperties
|
||||||
reference_frame = props.reference_frame #make it a scene property so it can be changed in a panel
|
reference_frame = props.reference_frame
|
||||||
is_local = reference_frame == 'LOCAL_COORDS'
|
is_local = reference_frame == 'LOCAL_COORDS'
|
||||||
x_match = abs(Vector((1,0,0)).dot(x_axis)) > 0.99
|
x_match = abs(Vector((1,0,0)).dot(x_axis)) > 0.99
|
||||||
y_match = abs(Vector((0,1,0)).dot(x_axis)) > 0.99
|
y_match = abs(Vector((0,1,0)).dot(x_axis)) > 0.99
|
||||||
@@ -525,24 +615,28 @@ class ShaderInfo:
|
|||||||
}
|
}
|
||||||
match_dict = {'fx': x_match or is_local, 'fy': y_match, 'fz': z_match}
|
match_dict = {'fx': x_match or is_local, 'fy': y_match, 'fz': z_match}
|
||||||
member_length = Vector(end_co-start_co).length
|
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)
|
processed_loads = self.process_total_linear_loads(activity_list,rotation,member_length)
|
||||||
if len(point_loads):
|
linear_loads = processed_loads["linear loads"]
|
||||||
|
maxforce = max(maxforce,processed_loads["max linear load"])
|
||||||
|
point_loads = processed_loads["discrete loads"]
|
||||||
|
|
||||||
|
if len(point_loads) > 0:
|
||||||
for item in point_loads:
|
for item in point_loads:
|
||||||
for sub_item in item:
|
for sub_item in item:
|
||||||
pos = sub_item["pos"]
|
pos = sub_item["pos"]
|
||||||
values = sub_item["values"]
|
values = sub_item["values"]
|
||||||
pos_vector = start_co + x_axis*pos
|
pos_vector = start_co + x_axis*pos
|
||||||
self.get_point_shader_args(values,pos_vector)
|
self.get_point_shader_args(values,pos_vector,rotation)
|
||||||
if loads_dict is None:
|
if linear_loads is None:
|
||||||
continue
|
continue
|
||||||
keys = ["fx","fy","fz","mx","my","mz"]
|
keys = ["fx","fy","fz","mx","my","mz"]
|
||||||
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
polyline = loads_dict[key]["polyline"]
|
polyline = linear_loads[key]["polyline"]
|
||||||
sinus = loads_dict[key]["sinus"]
|
sinus = linear_loads[key]["sinus"]
|
||||||
quadratic = loads_dict[key]["quadratic"]
|
quadratic = linear_loads[key]["quadratic"]
|
||||||
constant = loads_dict[key]["constant"]
|
constant = linear_loads[key]["constant"]
|
||||||
direction = direction_dict[key] #depends on the key and on the frame of reference
|
direction = direction_dict[key]
|
||||||
color_axis = (0,0,1,1)
|
color_axis = (0,0,1,1)
|
||||||
if 'x' in key:
|
if 'x' in key:
|
||||||
color_axis = (1,0,0,1)
|
color_axis = (1,0,0,1)
|
||||||
@@ -550,47 +644,47 @@ class ShaderInfo:
|
|||||||
color_axis = (0,1,0,1)
|
color_axis = (0,1,0,1)
|
||||||
|
|
||||||
if 'f' in key:
|
if 'f' in key:
|
||||||
|
unit = self.linear_force_unit
|
||||||
if match_dict[key]:
|
if match_dict[key]:
|
||||||
shader = self.shader.get("PARALLEL DISTRIBUTED FORCE")
|
shader = self.shader.new("PARALLEL DISTRIBUTED FORCE")
|
||||||
else:
|
else:
|
||||||
shader = self.shader.get("PERPENDICULAR DISTRIBUTED FORCE")
|
shader = self.shader.new("PERPENDICULAR DISTRIBUTED FORCE")
|
||||||
else:
|
else:
|
||||||
shader = self.shader.get("DISTRIBUTED MOMENT")
|
unit = self.linear_moment_unit
|
||||||
|
shader = self.shader.new("DISTRIBUTED MOMENT")
|
||||||
|
|
||||||
addindex = len(position)
|
|
||||||
counter = 0
|
counter = 0
|
||||||
for i in range(len(polyline)-1):
|
for i in range(len(polyline)-1):
|
||||||
current = Vector(polyline[i]+[0])
|
current = Vector(polyline[i]+[0])
|
||||||
nextitem = Vector(polyline[i+1]+[0])
|
nextitem = Vector(polyline[i+1]+[0])
|
||||||
|
|
||||||
if any([current.y, nextitem.y,constant,quadratic,sinus]): #if there is load in the z direction
|
if any([current.y, nextitem.y,constant,quadratic,sinus]):
|
||||||
negative = -1*direction + start_co + x_axis*current.x
|
negative = -1*direction + start_co + x_axis*current.x
|
||||||
positive = direction + start_co + x_axis*current.x
|
positive = direction + start_co + x_axis*current.x
|
||||||
position.append(negative)
|
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))
|
sin_quad_lin.append((sinus, quadratic, current.y + constant))
|
||||||
color.append(color_axis)
|
color.append(color_axis)
|
||||||
#info to render load value
|
|
||||||
x = current.x/member_length
|
x = current.x/member_length
|
||||||
func = sin(x*3.1416)*sinus + (-4.*x*x+4.*x)*quadratic+constant+current.y
|
func = sin(x*3.1416)*sinus + (-4.*x*x+4.*x)*quadratic+constant+current.y
|
||||||
if func:
|
if func:
|
||||||
text_info.append(
|
self.text_info.append(
|
||||||
{"position": -1*direction*func/maxforce + start_co + x_axis*current.x,
|
{"position": -1*direction*func/maxforce + start_co + x_axis*current.x,
|
||||||
"normal": direction.cross(x_axis).normalized(),
|
"text": f'{func:.2f} {unit}'}
|
||||||
"text": f'{func:.2f} {self.linear_force_unit}'}
|
|
||||||
)
|
)
|
||||||
maxforce = max(maxforce,abs(func))
|
|
||||||
position.append(positive)
|
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))
|
sin_quad_lin.append((sinus, quadratic, current.y + constant))
|
||||||
color.append(color_axis)
|
color.append(color_axis)
|
||||||
|
|
||||||
indices.append((0 + counter + addindex,
|
indices.append((0 + counter,
|
||||||
1 + counter + addindex,
|
1 + counter,
|
||||||
2 + counter + addindex))
|
2 + counter))
|
||||||
indices.append((3 + counter + addindex,
|
indices.append((3 + counter,
|
||||||
2 + counter + addindex,
|
2 + counter,
|
||||||
1 + counter + addindex))
|
1 + counter))
|
||||||
if i == len(polyline)-2:
|
if i == len(polyline)-2:
|
||||||
negative = -1*direction + start_co + x_axis*nextitem.x
|
negative = -1*direction + start_co + x_axis*nextitem.x
|
||||||
positive = direction + start_co + x_axis*nextitem.x
|
positive = direction + start_co + x_axis*nextitem.x
|
||||||
@@ -598,23 +692,22 @@ class ShaderInfo:
|
|||||||
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))
|
sin_quad_lin.append((sinus, quadratic, nextitem.y + constant))
|
||||||
color.append(color_axis)
|
color.append(color_axis)
|
||||||
#info to render load value
|
|
||||||
x = nextitem.x/member_length
|
x = nextitem.x/member_length
|
||||||
func = sin(x*3.1416)*sinus + (-4.*x*x+4.*x)*quadratic+constant+nextitem.y
|
func = sin(x*3.1416)*sinus + (-4.*x*x+4.*x)*quadratic+constant+nextitem.y
|
||||||
if func:
|
if func:
|
||||||
text_info.append(
|
self.text_info.append(
|
||||||
{"position": -1*direction*func/maxforce + start_co + x_axis*nextitem.x,
|
{"position": -1*direction*func/maxforce + start_co + x_axis*nextitem.x,
|
||||||
"normal": direction.cross(x_axis).normalized(),
|
"text": f'{func:.2f} {unit}'}
|
||||||
"text": f'{func:.2f} {self.linear_force_unit}'}
|
|
||||||
)
|
)
|
||||||
maxforce = max(maxforce,abs(func))
|
|
||||||
position.append(positive)
|
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))
|
sin_quad_lin.append((sinus, quadratic, nextitem.y + constant))
|
||||||
color.append(color_axis)
|
color.append(color_axis)
|
||||||
|
|
||||||
counter += 2
|
counter += 2
|
||||||
if len(position):
|
if position:
|
||||||
self.info.append(
|
self.info.append(
|
||||||
{
|
{
|
||||||
"shader": shader,
|
"shader": shader,
|
||||||
@@ -630,34 +723,23 @@ class ShaderInfo:
|
|||||||
for info in self.info:
|
for info in self.info:
|
||||||
info["uniforms"][2][1] = maxforce
|
info["uniforms"][2][1] = maxforce
|
||||||
|
|
||||||
self.text_info = text_info
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
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)
|
loads_dict = self.parse_linear_loads_to_dict(activity_list,element_rotation_matrix)
|
||||||
const = loads_dict["constant force"]
|
const = loads_dict["constant force"]
|
||||||
quad = loads_dict["quadratic force"]
|
quad = loads_dict["quadratic force"]
|
||||||
sinus = loads_dict["sinus force"]
|
sinus = loads_dict["sinus force"]
|
||||||
loads = loads_dict["linear load configuration"]
|
loads = loads_dict["linear load configuration"]
|
||||||
unique_list = self.getuniquepositionlist(loads)
|
unique_list = self.getuniquepositionlist(loads)
|
||||||
final_list = []
|
final_list = []
|
||||||
return_value = None
|
distributed_loads = None
|
||||||
max_load = 0
|
max_load = 0
|
||||||
for pos in unique_list:
|
for pos in unique_list:
|
||||||
value = self.get_before_and_after(pos,loads)
|
value = self.get_before_and_after(pos,loads)
|
||||||
@@ -667,11 +749,11 @@ class ShaderInfo:
|
|||||||
final_list.append([pos]+value["before"])
|
final_list.append([pos]+value["before"])
|
||||||
final_list.append([pos]+value["after"])
|
final_list.append([pos]+value["after"])
|
||||||
|
|
||||||
if not len(final_list) and any(const+quad+sinus):
|
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([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])
|
final_list.append([member_length,0.0,0.0,0.0,0.0,0.0,0.0])
|
||||||
|
|
||||||
elif len(final_list):
|
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
|
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 = [[0.0,0.0,0.0,0.0,0.0,0.0,0.0]]+final_list
|
||||||
else:
|
else:
|
||||||
@@ -680,18 +762,11 @@ class ShaderInfo:
|
|||||||
final_list.append([member_length,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])
|
||||||
else:
|
else:
|
||||||
del final_list[-1]
|
del final_list[-1]
|
||||||
if len(final_list):
|
if len(final_list)>0:
|
||||||
array = np.array(final_list) #7xn -> ["pos","fx","fy","fz","mx","my","mz"]
|
array = np.array(final_list) #7xn -> ["pos","fx","fy","fz","mx","my","mz"]
|
||||||
keys = ["fx","fy","fz","mx","my","mz"]
|
keys = ["fx","fy","fz","mx","my","mz"]
|
||||||
polyline = {
|
polyline = []
|
||||||
"fx": [],
|
distributed_loads = {
|
||||||
"fy": [],
|
|
||||||
"fz": [],
|
|
||||||
"mx": [],
|
|
||||||
"my": [],
|
|
||||||
"mz": [],
|
|
||||||
}
|
|
||||||
return_value = {
|
|
||||||
"fx": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []},
|
"fx": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []},
|
||||||
"fy": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []},
|
"fy": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []},
|
||||||
"fz": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []},
|
"fz": {"constant": 0, "quadratic": 0,"sinus": 0,"polyline": []},
|
||||||
@@ -705,21 +780,21 @@ class ShaderInfo:
|
|||||||
any(item for item in array[:,component+1])):
|
any(item for item in array[:,component+1])):
|
||||||
|
|
||||||
for currentitem in final_list:
|
for currentitem in final_list:
|
||||||
polyline[key].append([currentitem[0],currentitem[component+1]])
|
polyline.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]))
|
max_load = max(max_load,abs(sinus[component]+quad[component]+const[component]+currentitem[component+1]))
|
||||||
inner_dict = return_value[key]
|
inner_dict = distributed_loads[key]
|
||||||
inner_dict["constant"] = const[component]
|
inner_dict["constant"] = const[component]
|
||||||
inner_dict["quadratic"] = quad[component]
|
inner_dict["quadratic"] = quad[component]
|
||||||
inner_dict["sinus"] = sinus[component]
|
inner_dict["sinus"] = sinus[component]
|
||||||
inner_dict["polyline"] = polyline[key]
|
inner_dict["polyline"] = polyline
|
||||||
return_value[key] = inner_dict
|
distributed_loads[key] = inner_dict
|
||||||
|
|
||||||
return return_value, max_load, 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):
|
def getuniquepositionlist(self, load_config_list:list[list[LoadConfigItem]])-> list[float]:
|
||||||
"""return an ordereded list of unique locations based on the load configuration list
|
"""return an ordereded list of unique locations based on the load configuration list
|
||||||
ex: load_config_list = [[{"pos":1.0,...},{"pos":3.0,...}],
|
ex: load_config_list = [[{"pos":1.0,...},{"pos":3.0,...}],
|
||||||
[{"pos":2.0,...},{"pos":3.0,...}],
|
[{"pos":2.0,...},{"pos":3.0,...}],
|
||||||
@@ -735,37 +810,41 @@ class ShaderInfo:
|
|||||||
unique.sort()
|
unique.sort()
|
||||||
return unique
|
return unique
|
||||||
|
|
||||||
def interp1d(self,l1,l2, pos):
|
def interp1d(self,l1:list[float],l2: list[float], pos:float) -> float:
|
||||||
""" 1d linear interpolation for the vector components"""
|
""" 1d linear interpolation for the vector components"""
|
||||||
fac = (l2[1]-l1[1])/(l2[0]-l1[0])
|
fac = (l2[1]-l1[1])/(l2[0]-l1[0])
|
||||||
v = l1[1] + fac*(pos-l1[0])
|
v = l1[1] + fac*(pos-l1[0])
|
||||||
return v
|
return v
|
||||||
|
|
||||||
def interpolate(self,pos,loadinfo,start,end,key):
|
def interpolate(self,pos: float,loadinfo:list[LoadConfigItem],start:int,end:int,key: str)-> np.ndarray:
|
||||||
""" interpolate the result vectors between load poits"""
|
""" interpolate the result vectors between load poits"""
|
||||||
result = Vector((0,0,0))
|
result = np.zeros(6)
|
||||||
for i in range(3):
|
for i in range(6):
|
||||||
value1 = [loadinfo[start]["pos"], loadinfo[start][key][i]] #[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]
|
value2= [loadinfo[end]["pos"], loadinfo[end][key][i]] # [position, force_component]
|
||||||
result[i] = self.interp1d(value1,value2, pos) # interpolated [position, force_component]
|
result[i] = self.interp1d(value1,value2, pos) # interpolated [position, force_component]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_before_and_after(self,pos,load_config_list):
|
def get_before_and_after(self,
|
||||||
""" get total values for forces and moments with polilyne distribution
|
pos: float,
|
||||||
before and after the position
|
load_config_list:list[list[LoadConfigItem]]
|
||||||
ex: load_config_list = [[{"pos":1.0,...,"forces":(1,0,0),...},{"pos":3.0,...,"forces":(3,0,0),...}],
|
) -> dict[str,list[float]]:
|
||||||
[{"pos":2.0,...,"forces":(1,0,0),...},{"pos":3.0,...,"forces":(1,0,0),...}],
|
""" get total values for forces and moments before and after the position
|
||||||
[{"pos":1.5,...,"forces":(1,0,0),...},{"pos":2.5,...,"forces":(1,0,0),...}]]
|
example:
|
||||||
pos = 2.0
|
pos = 2.0
|
||||||
return = {
|
load_config_list = [[{"pos":1.0, "descr":"start, "load values":[1,0,0,0,0,0]},
|
||||||
"before": (3,0,0,0,0,0), ->(fx, fy, fz, mx, my, mz)
|
{"pos":3.0, "descr":"end, "load values":[3,0,0,0,0,0]}],
|
||||||
" after": (4,0,0,0,0,0) ->(fx, fy, fz, mx, my, mz)
|
[{"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)
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
force_before = Vector((0,0,0))
|
load_before = np.zeros(6)
|
||||||
force_after = Vector((0,0,0))
|
load_after = np.zeros(6)
|
||||||
moment_before = Vector((0,0,0))
|
|
||||||
moment_after = Vector((0,0,0))
|
|
||||||
|
|
||||||
for config in load_config_list:
|
for config in load_config_list:
|
||||||
if pos < config[0]["pos"] or pos > config[-1]["pos"]:
|
if pos < config[0]["pos"] or pos > config[-1]["pos"]:
|
||||||
@@ -777,36 +856,32 @@ class ShaderInfo:
|
|||||||
break
|
break
|
||||||
if config[start]["pos"] == pos:
|
if config[start]["pos"] == pos:
|
||||||
if config[start]["descr"] in ['start','middle']:
|
if config[start]["descr"] in ['start','middle']:
|
||||||
force_after += config[start]["forces"]
|
load_after += config[start]["load values"]
|
||||||
moment_after += config[start]["moments"]
|
|
||||||
elif config[start]["descr"] in ['end','middle']:
|
elif config[start]["descr"] in ['end','middle']:
|
||||||
force_before += config[start]["forces"]
|
load_before += config[start]["load values"]
|
||||||
moment_before += config[start]["moments"]
|
|
||||||
|
|
||||||
elif config[end]["pos"] == pos:
|
elif config[end]["pos"] == pos:
|
||||||
if config[end]["descr"] in ['start','middle']:
|
if config[end]["descr"] in ['start','middle']:
|
||||||
force_after += config[end]["forces"]
|
load_after += config[end]["load values"]
|
||||||
moment_after += config[end]["moments"]
|
|
||||||
elif config[end]["descr"] in ['end','middle']:
|
elif config[end]["descr"] in ['end','middle']:
|
||||||
force_before += config[end]["forces"]
|
load_before += config[end]["load values"]
|
||||||
moment_before += config[end]["moments"]
|
|
||||||
|
|
||||||
elif end-start == 1:
|
elif end-start == 1:
|
||||||
force_before += self.interpolate(pos,config,start,end,"forces")
|
load_before += self.interpolate(pos,config,start,end,"load values")
|
||||||
force_after += self.interpolate(pos,config,start,end,"forces")
|
load_after += self.interpolate(pos,config,start,end,"load values")
|
||||||
moment_before += self.interpolate(pos,config,start,end,"moments")
|
|
||||||
moment_after += self.interpolate(pos,config,start,end,"moments")
|
|
||||||
start += 1
|
start += 1
|
||||||
end -=1
|
end -=1
|
||||||
return_value = {
|
return_value = {
|
||||||
"before": [force_before.x,force_before.y,force_before.z,
|
"before": load_before.tolist(),
|
||||||
moment_before.x,moment_before.y,moment_before.z],
|
"after": load_after.tolist()
|
||||||
"after": [force_after.x, force_after.y, force_after.z,
|
}
|
||||||
moment_after.x, moment_after.y, moment_after.z]
|
|
||||||
}
|
|
||||||
return return_value
|
return return_value
|
||||||
|
|
||||||
def get_loads_dict(self,activity_list,element_rotation_matrix):
|
def parse_linear_loads_to_dict(self,
|
||||||
|
activity_list: list[tuple[ifcopenshell.entity_instance,float]],
|
||||||
|
element_rotation_matrix: np.ndarray) -> ParsedLoad:
|
||||||
"""
|
"""
|
||||||
get load list
|
get load list
|
||||||
activity_list: list of IfcStructuralCurveAction or IfcStructuralCurveReaction
|
activity_list: list of IfcStructuralCurveAction or IfcStructuralCurveReaction
|
||||||
@@ -819,7 +894,7 @@ class ShaderInfo:
|
|||||||
quadratic distribution
|
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
|
sinus distribution
|
||||||
"lienar load configuration": list -> list of load configurations for linear
|
"linear load configuration": list -> list of load configurations for linear
|
||||||
and polyline distributions of linear loads
|
and polyline distributions of linear loads
|
||||||
}
|
}
|
||||||
description of "linear load configuration":
|
description of "linear load configuration":
|
||||||
@@ -830,48 +905,37 @@ class ShaderInfo:
|
|||||||
dict{
|
dict{
|
||||||
"pos": float, -> local position along curve length
|
"pos": float, -> local position along curve length
|
||||||
"descr": string, -> describe if the item is at the start, middle or end of the list
|
"descr": string, -> describe if the item is at the start, middle or end of the list
|
||||||
"forces": Vector, -> linear force applied at that point
|
"load values": Array, -> linear force applied at that point
|
||||||
"moments": Vector -> linear moment applied at that point
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
constant_force = Vector((0,0,0))
|
constant = np.zeros(6)
|
||||||
constant_moment = Vector((0,0,0))
|
quadratic = np.zeros(6)
|
||||||
quadratic_force = Vector((0,0,0))
|
sinus = np.zeros(6)
|
||||||
quadratic_moment = Vector((0,0,0))
|
|
||||||
sinus_force = Vector((0,0,0))
|
|
||||||
sinus_moment = Vector((0,0,0))
|
|
||||||
linear_load_configurations = []
|
linear_load_configurations = []
|
||||||
point_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_force_vector(load,transform_matrix,factor = 1.0):
|
def get_load_values(load,transform_matrix,factor = 1.0):
|
||||||
x = 0 if getattr(load, 'LinearForceX', 0) is None else getattr(load, 'LinearForceX', 0)
|
result = np.zeros(6)
|
||||||
y = 0 if getattr(load, 'LinearForceY', 0) is None else getattr(load, 'LinearForceY', 0)
|
keys = ['LinearForceX','LinearForceY','LinearForceZ','LinearMomentX','LinearMomentY','LinearMomentZ']
|
||||||
z = 0 if getattr(load, 'LinearForceZ', 0) is None else getattr(load, 'LinearForceZ', 0)
|
for i, key in enumerate(keys):
|
||||||
return transform_matrix @ (Vector((x,y,z))*factor)
|
value = 0 if getattr(load, key, 0) is None else getattr(load, key, 0)
|
||||||
|
result[i] += value*factor
|
||||||
def get_moment_vector(load,transform_matrix,factor = 1.0):
|
return transform_matrix @ result
|
||||||
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:
|
for item in activity_list:
|
||||||
activity = item[0]
|
activity = item[0]
|
||||||
factor = item[1]
|
factor = item[1]
|
||||||
load = activity.AppliedLoad
|
load = activity.AppliedLoad
|
||||||
global_or_local = activity.GlobalOrLocal
|
|
||||||
props = bpy.context.scene.BIMStructuralProperties
|
transform_3by3 = self.get_activity_transform_matrix(activity,element_rotation_matrix)
|
||||||
reference_frame = props.reference_frame #make it a scene property so it can be changed in a panel
|
transform_6by6 = np.zeros((6,6))
|
||||||
transform_matrix = Matrix()
|
transform_6by6[0:3,0:3] = transform_3by3
|
||||||
if reference_frame == 'LOCAL_COORDS' and global_or_local != reference_frame:
|
transform_6by6[3:6,3:6] = transform_3by3
|
||||||
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
|
#values for linear loads
|
||||||
if load.is_a('IfcStructuralLoadConfiguration'):
|
if load.is_a('IfcStructuralLoadConfiguration'):
|
||||||
locations = getattr(load, 'Locations', [])
|
locations = getattr(load, 'Locations', [])
|
||||||
@@ -880,8 +944,7 @@ class ShaderInfo:
|
|||||||
]
|
]
|
||||||
config_list = []
|
config_list = []
|
||||||
for i,l in enumerate(values):
|
for i,l in enumerate(values):
|
||||||
forcevalues = get_force_vector(l,transform_matrix,factor)
|
load_values = get_load_values(l,transform_6by6,factor)
|
||||||
momentvalues = get_moment_vector(l,transform_matrix,factor)
|
|
||||||
if i == 0:
|
if i == 0:
|
||||||
descr = 'start'
|
descr = 'start'
|
||||||
elif i == len(values)-1:
|
elif i == len(values)-1:
|
||||||
@@ -891,20 +954,20 @@ class ShaderInfo:
|
|||||||
config_list.append(
|
config_list.append(
|
||||||
{"pos": locations[i][0]*unit_scale,
|
{"pos": locations[i][0]*unit_scale,
|
||||||
"descr": descr,
|
"descr": descr,
|
||||||
"forces":forcevalues,
|
"load values":load_values}
|
||||||
"moments":momentvalues}
|
|
||||||
)
|
)
|
||||||
linear_load_configurations.append(config_list)
|
linear_load_configurations.append(config_list)
|
||||||
|
|
||||||
#load configurations with point loads
|
#load configurations with point loads
|
||||||
values = [l for l in getattr(load, 'Values', None)
|
values = [l for l in getattr(load, 'Values', None)
|
||||||
if l.is_a() == "IfcStructuralLoadSingleForce"
|
if l.is_a() == "IfcStructuralLoadSingleForce"
|
||||||
]
|
]
|
||||||
attr_list = ['ForceX','ForceY','ForceZ','MomentX','MomentY','MomentZ']
|
attr_list = ['ForceX','ForceY','ForceZ','MomentX','MomentY','MomentZ']
|
||||||
config_list = []
|
config_list = []
|
||||||
for i,load in enumerate(values):
|
for i,val in enumerate(values):
|
||||||
result_list = [0,0,0,0,0,0]
|
result_list = [0,0,0,0,0,0]
|
||||||
for j, attr in enumerate(attr_list):
|
for j, attr in enumerate(attr_list):
|
||||||
value = 0 if getattr(load, attr, 0) is None else getattr(load, attr, 0)
|
value = 0 if getattr(val, attr, 0) is None else getattr(val, attr, 0)
|
||||||
result_list[j] += value*factor
|
result_list[j] += value*factor
|
||||||
config_list.append(
|
config_list.append(
|
||||||
{"pos": locations[i][0]*unit_scale,
|
{"pos": locations[i][0]*unit_scale,
|
||||||
@@ -913,25 +976,19 @@ class ShaderInfo:
|
|||||||
point_load_configurations.append(config_list)
|
point_load_configurations.append(config_list)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
forcevalues = get_force_vector(load,transform_matrix,factor)
|
load_values = get_load_values(load,transform_6by6,factor)
|
||||||
momentvalues = get_moment_vector(load,transform_matrix,factor)
|
|
||||||
if 'CONST' == getattr(activity, 'PredefinedType', None) or activity.is_a('IfcStructuralLinearAction'):
|
if 'CONST' == getattr(activity, 'PredefinedType', None) or activity.is_a('IfcStructuralLinearAction'):
|
||||||
constant_force += forcevalues
|
constant += load_values
|
||||||
constant_moment += momentvalues
|
|
||||||
elif 'PARABOLA' == getattr(activity, 'PredefinedType', None):
|
elif 'PARABOLA' == getattr(activity, 'PredefinedType', None):
|
||||||
quadratic_force += forcevalues
|
quadratic += load_values
|
||||||
quadratic_moment += momentvalues
|
|
||||||
elif 'SINUS' == getattr(activity, 'PredefinedType', None):
|
elif 'SINUS' == getattr(activity, 'PredefinedType', None):
|
||||||
sinus_force += forcevalues
|
sinus += load_values
|
||||||
sinus_moment += momentvalues
|
|
||||||
return_value = {
|
return_value = {
|
||||||
"constant force": [constant_force.x,constant_force.y,constant_force.z,
|
"constant force": constant.tolist(),
|
||||||
constant_moment.x,constant_moment.y,constant_moment.z],
|
"quadratic force": quadratic.tolist(),
|
||||||
"quadratic force": [quadratic_force.x,quadratic_force.y,quadratic_force.z,
|
"sinus force": sinus.tolist(),
|
||||||
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,
|
"linear load configuration": linear_load_configurations,
|
||||||
"point load configuration": point_load_configurations
|
"point load configuration": point_load_configurations
|
||||||
}
|
}
|
||||||
return return_value
|
return return_value
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class DecorationShader:
|
|||||||
"shader for the load decorations"
|
"shader for the load decorations"
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
def get(self, pattern: str) -> gpu.types.GPUShader:
|
def new(self, pattern: str) -> gpu.types.GPUShader:
|
||||||
"""pattern: string description of the desired shader
|
"""pattern: string description of the desired shader
|
||||||
Possible values
|
Possible values
|
||||||
PERPENDICULAR DISTRIBUTED FORCE: pattern for distributed force
|
PERPENDICULAR DISTRIBUTED FORCE: pattern for distributed force
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ class StructuralTool(WorkSpaceTool):
|
|||||||
StructuralToolUI.draw(context, layout)
|
StructuralToolUI.draw(context, layout)
|
||||||
|
|
||||||
|
|
||||||
def add_layout_hotkey(layout, text, hotkey, description):
|
def add_layout_hotkey(layout: bpy.types.UILayout, text: str, hotkey: str, description: str) -> None:
|
||||||
args = ["structural", layout, text, hotkey, description]
|
args = ("structural", layout, text, hotkey, description)
|
||||||
tool.Blender.add_layout_hotkey_operator(*args)
|
tool.Blender.add_layout_hotkey_operator(*args)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user