from typing import Any
import math
from .._base import _BaseEditor
deg_to_rad = math.pi / 180
[docs]
class LoadEditor(_BaseEditor):
"""Editor for applying loads to members, nodes, and panels.
Accessed via ``app.loads.load``. Supports self-weight, nodal forces,
uniform and trapezoidal bar loads, point loads along bars, and panel
(surface) loads.
"""
def __init__(self, raw_app: Any) -> None:
super().__init__(raw_app)
self._structure = self._raw.Project.Structure
self._labels = self._structure.Labels
self._cases = self._structure.Cases
self._case_cache: dict[str, Any] = {}
[docs]
def add_self_weight(
self, case_name: str, objects: int | str, factors: list = [0, 0, -1]
) -> None:
"""Adds self-weight to objects.
Parameters
----------
case_name: str
Loadcase name to which the load will be asigned.
objects: str
List of objects eg. "1 2 3" or "all"
factors: list
Factors in format [x, y, z] sign factor is assumed as a direction for the factor
"""
case = self._get_case_by_name(case_name)
record_index = case.Records.New(self._rbt.IRobotLoadRecordType(7))
record = self._rbt.IRobotLoadRecord(case.Records.Get(record_index))
load_factor_X = factors[0]
load_factor_Y = factors[1]
load_factor_Z = factors[2]
if load_factor_X != 0:
sign = math.copysign(1, load_factor_X)
record.SetValue(0, sign)
record.SetValue(3, abs(load_factor_X))
elif load_factor_Y != 0:
sign = math.copysign(1, load_factor_Y)
record.SetValue(1, sign)
record.SetValue(3, abs(load_factor_Y))
elif load_factor_Z != 0:
sign = math.copysign(1, load_factor_Z)
record.SetValue(2, sign)
record.SetValue(3, abs(load_factor_Z))
if objects.lower() == "all":
record.SetValue(15, 1) # Ensure 'whole structure' setting
record.Objects.FromText(str(objects))
else:
record.Objects.FromText(str(objects))
[docs]
def add_node_load(
self,
case_name: str,
objects: int | str,
loads: list,
rotations: list = [0, 0, 0],
) -> None:
"""Adds point load on a node.
Parameters
----------
case_name: str
Loadcase name to which the load will be asigned.
objects: str
List of objects eg. "1 2 3".
loads: list
Loads in format [Fx, Fy, Fz, Mx, My, Mz] in Newtons.
rotations: list[float,float,float])
Rotations angle in order: [alfa, beta, gamma] in degrees.
"""
case = self._get_case_by_name(case_name)
record_index = case.Records.New(self._rbt.IRobotLoadRecordType(0))
record = case.Records.Get(record_index)
record.Objects.FromText(str(objects))
record.SetValue(0, loads[0]) # Fx
record.SetValue(1, loads[1]) # Fy
record.SetValue(2, loads[2]) # Fz
record.SetValue(3, loads[3]) # Mx
record.SetValue(4, loads[4]) # My
record.SetValue(5, loads[5]) # Mz
record.SetValue(8, rotations[0] * deg_to_rad) # alfa
record.SetValue(9, rotations[1] * deg_to_rad) # beta
record.SetValue(10, rotations[2] * deg_to_rad) # gamma
[docs]
def add_point_load(
self,
case_name: str,
objects: int | str,
loads: list,
dis_x: float = 0,
dis_y: float = 0,
dis_z: float = 0,
rotations: list = [0, 0, 0],
coord_sys: int = 0,
calc_node: int = 1,
relabs: int = 0,
) -> None:
"""Adds point load on a member.
Parameters
----------
loads: list
Loads in format [Fx, Fy, Fz, Mx, My, Mz] in Newtons
case_name: str
Loadcase name to which the load will be asigned.
objects: str
List of objects eg. "1 2 3"
dis_x: float
Load position from start node of the member.
dis_y: float
Eccentricity distance in y direction in local system.
dis_z: float
Eccentricity distance in z direction in local system.
rotations: list[float,float,float]
Rotations angle in order: [alfa, beta, gamma] in degrees.
coord_sys: int
0 - global, 1 - local system
calc_node: int
Create a calculation node in the load position, 0 - no node. Defaults to 1.
relabs: int
0 - absolute, 1 - relative. Distance values measured along bar length.
"""
case = self._get_case_by_name(case_name)
record_index = case.Records.New(self._rbt.IRobotLoadRecordType(3))
record = case.Records.Get(record_index)
record.Objects.FromText(str(objects))
record.SetValue(0, loads[0]) # Fx
record.SetValue(1, loads[1]) # Fy
record.SetValue(2, loads[2]) # Fz
record.SetValue(3, loads[3]) # Mx
record.SetValue(4, loads[4]) # My
record.SetValue(5, loads[5]) # Mz
record.SetValue(8, rotations[0] * deg_to_rad) # alfa
record.SetValue(9, rotations[1] * deg_to_rad) # beta
record.SetValue(10, rotations[2] * deg_to_rad) # gamma
record.SetValue(11, coord_sys) # coordination system local/global
record.SetValue(12, calc_node) # calcnode
record.SetValue(13, relabs) # relabs
record.SetValue(6, dis_x) # disX
record.SetValue(21, dis_y) # dis_y
record.SetValue(22, dis_z) # diz_z
[docs]
def add_trapezoidal_load(
self,
case_name: str,
objects: int | str,
loads: list,
start_dist: float,
end_dist: float,
projected: int = 0,
rotations: list = [0, 0, 0],
coord_sys: int = 0,
) -> None:
"""Adds trapezoidal load (2p).
Parameters
----------
loads: list
Loads in format [Px1, Py1, Pz1, Px2, Py2, Pz2] in kN/m
case_name: str
Loadcase name to which the load will be asigned
objects: str
List of objects eg. "1 2 3".
start_dist: float
Distance of the load starting point from bar origin.
end_dist: float
Distance of the load end point from bar origin.
projected: int
0 -not projected, 1-projected.
rotations: list[float,float,float]
Rotations angle in order: [alfa, beta, gamma] in degrees.
coord_sys: int
0 - absolute, 1 - relative. Distance values measured along bar length.
"""
case = self._get_case_by_name(case_name)
record_index = case.Records.New(self._rbt.IRobotLoadRecordType(6))
record = case.Records.Get(record_index)
record.Objects.FromText(str(objects))
record.SetValue(0, loads[0]) # Px
record.SetValue(1, loads[1]) # Py
record.SetValue(2, loads[2]) # Pz
record.SetValue(3, loads[3]) # Px2
record.SetValue(4, loads[4]) # Py2
record.SetValue(5, loads[5]) # Pz2
record.SetValue(6, end_dist) # disx2
record.SetValue(7, start_dist) # disx
record.SetValue(8, rotations[0] * deg_to_rad) # alfa
record.SetValue(9, rotations[1] * deg_to_rad) # beta
record.SetValue(10, rotations[2] * deg_to_rad) # gamma
record.SetValue(12, projected) # projected
record.SetValue(13, coord_sys) # coord system
[docs]
def add_linear_load_on_edge(
self,
case_name: str,
objects: int | str,
loads: list,
coord_sys: int,
gamma: float,
) -> None:
"""Adds point load on a member
Parameters
----------
loads: list
Loads in format [Fx, Fy, Fz, Mx, My, Mz] in Newtons
case_name: str
Loadcase name to which the load will be asigned
objects: str
List of objects eg. "1 2 3"
coord_sys: int
0 - global, 1 - local system
gamma: float
Gamma rotation angle in degrees. Defaults to 0.
"""
case = self._get_case_by_name(case_name)
record_index = case.Records.New(self._rbt.IRobotLoadRecordType(69))
record = case.Records.Get(record_index)
record.Objects.FromText(str(objects))
record.SetValue(0, loads[0]) # Fx
record.SetValue(1, loads[1]) # Fy
record.SetValue(2, loads[2]) # Fz
record.SetValue(3, loads[3]) # Mx
record.SetValue(4, loads[4]) # My
record.SetValue(5, loads[5]) # Mz
record.SetValue(6, gamma * deg_to_rad) # gamma
record.SetValue(11, coord_sys) # localsystem
[docs]
def add_contour_load(
self,
loads: list,
case_name: str,
contour_points: list,
objects: str = None,
load_3p: bool = False,
p3_coordinates: list = None,
auto_detect: bool = False,
vector: tuple = None,
) -> None:
"""Adds load on contour.
Parameters
----------
loads: list
Loads in format [Px, Py, Pz], if 3p load then [Px1, Py1, Pz1, Px2, Py2, Pz2, Px3, Py3, Pz3]
case_name: str
Loadcase name to which the load will be asigned
contour_points: list
Contour points in given format [[x,y,z], [x,y,z], ...]
objects: str
List of objects eg. "1 2 3"
load_3p: bool, optional
Triggers if load 3p on contour type is required.
p3_coordinates: list, optional
List of corner points for load 3p contour in format [[x,y,z], [x,y,z], ...]
auto_detect: bool, optional
Trigger to auto detect panels for contour load distribution. Defaults to False.
vector: tuple, optional
Vector for force direction in format: (x,y,z). Defaults as downward (0,0,-1)
"""
case = self._get_case_by_name(case_name)
record_index = case.Records.New(self._rbt.IRobotLoadRecordType(28))
record = self._rbt.IRobotLoadRecordInContour(case.Records.Get(record_index))
if auto_detect or objects is None:
record.SetValue(-1, 1)
else:
record.Objects.FromText(objects)
if vector is None:
record.SetVector(0, 0, -1) # vector downward
else:
record.SetValue(vector)
# Assign contour loads
if not load_3p:
record.SetValue(0, loads[0]) # Px
record.SetValue(1, loads[1]) # Py
record.SetValue(2, loads[2]) # Pz
else:
record.SetValue(0, loads[0]) # Px1
record.SetValue(1, loads[1]) # Py1
record.SetValue(2, loads[2]) # Pz1
record.SetValue(3, loads[3]) # Px2
record.SetValue(4, loads[4]) # Py2
record.SetValue(5, loads[5]) # Pz2
record.SetValue(6, loads[6]) # Px3
record.SetValue(7, loads[7]) # Py3
record.SetValue(8, loads[8]) # Pz3
# Assign contour points
size = len(contour_points)
record.SetValue(13, size) # Assign size for point array I_ICRV_NPOINTS
n = 1 # Order number for each contour point
for point in contour_points:
record.SetContourPoint(n, point[0], point[1], point[2])
n += 1
if load_3p:
corner_number = 1 # Order number for coner point, it goes up to three.
for lpoint in p3_coordinates:
record.SetPoint(corner_number, lpoint[0], lpoint[1], lpoint[2])
corner_number += 1
[docs]
def add_planar_3p_load(
self,
case_name: str,
objects: int | str,
loads: list,
p1: list,
p2: list,
p3: list,
projected: int = 0,
coord_sys: int = 0,
) -> None:
"""Adds a planar 3-point (trapezoidal) load on panel/FE objects.
The load intensity is defined at three reference points, allowing a
linearly-varying distribution across the surface.
Parameters
----------
case_name : str
Loadcase name to which the load will be assigned.
objects : int | str
Panel/FE object selection, e.g. ``"1 2 3"`` or a single int.
loads : list
Nine load components in order:
``[Px1, Py1, Pz1, Px2, Py2, Pz2, Px3, Py3, Pz3]`` in Pa.
p1 : list
Coordinates ``[x, y, z]`` of reference point 1 (in metres).
p2 : list
Coordinates ``[x, y, z]`` of reference point 2 (in metres).
p3 : list
Coordinates ``[x, y, z]`` of reference point 3 (in metres).
projected : int, optional
``0`` – not projected, ``1`` – projected. Defaults to ``0``.
coord_sys : int, optional
``0`` – global, ``1`` – local. Defaults to ``0``.
"""
case = self._get_case_by_name(case_name)
record_index = case.Records.New(self._rbt.IRobotLoadRecordType(22))
record = case.Records.Get(record_index)
record.Objects.FromText(str(objects))
record.SetValue(0, loads[0]) # Px1
record.SetValue(1, loads[1]) # Py1
record.SetValue(2, loads[2]) # Pz1
record.SetValue(3, loads[3]) # Px2
record.SetValue(4, loads[4]) # Py2
record.SetValue(5, loads[5]) # Pz2
record.SetValue(6, loads[6]) # Px3
record.SetValue(7, loads[7]) # Py3
record.SetValue(8, loads[8]) # Pz3
record.SetPoint(1, p1[0], p1[1], p1[2])
record.SetPoint(2, p2[0], p2[1], p2[2])
record.SetPoint(3, p3[0], p3[1], p3[2])
record.SetValue(11, coord_sys) # coord system
record.SetValue(12, projected) # projected
def _get_case_by_name(self, case_name: str) -> Any:
"""Private function to get simple loadcase by name.
Results are cached after the first lookup. The cache is keyed by name
so repeated calls with the same case_name cost a single dict lookup
instead of a full COM collection scan.
Parameters
----------
case_name: str
Loadcase name
Returns
----------
IRobotCase
"""
if case_name in self._case_cache:
return self._case_cache[case_name]
cases = self._cases.GetAll()
for i in range(1, cases.Count + 1):
lcase = self._rbt.IRobotCase(cases.Get(i))
if lcase.Type == self._rbt.IRobotCaseType.I_CT_SIMPLE:
if lcase.Name == case_name:
result = self._rbt.IRobotSimpleCase(lcase)
self._case_cache[case_name] = result
return result
return None