from dataclasses import dataclass, field
from typing import Dict, Optional
from pint import Quantity
from mento.units import kN, kNm
[docs]
@dataclass
class Forces:
"""
A class to represent the forces acting on a structural element.
Attributes
----------
N_x : float
Axial force along the x-axis (default is 0 kN).
V_z : float
Shear force along the z-axis (default is 0 kN).
M_y : float
Bending moment about the y-axis (default is 0 kN*m).
unit_system : str
The unit system to use for displaying forces ('metric' or 'imperial').
Methods
-------
get_forces() -> dict
Returns the forces as a dictionary with keys 'N_x', 'V_z', and 'M_y'.
set_forces() -> None
Sets the forces of the object with the provided values.
compare_to(other: 'Forces', by: str = 'V_z') -> bool
Compares this force with another force based on a given attribute.
"""
_id: int = field(init=False, repr=False) # Instance ID, assigned internally
_last_id: int = field(default=0, init=False, repr=False) # Class variable to keep track of last assigned ID
label: Optional[str] = None
_N_x: Quantity = field(default=0 * kN) # Ensure you use the correct unit type here
_V_z: Quantity = field(default=0 * kN)
_M_y: Quantity = field(default=0 * kNm)
_M_x: Quantity = field(default=0 * kNm)
unit_system: str = field(default="metric") # Add unit_system as a field
def __init__(
self,
label: Optional[str] = None,
N_x: Quantity = 0 * kN,
V_z: Quantity = 0 * kN,
M_y: Quantity = 0 * kNm,
M_x: Quantity = 0 * kNm,
unit_system: str = "metric",
) -> None:
# Increment the class variable for the next unique ID
Forces._last_id += 1
self._id = Forces._last_id # Private ID assigned internally, unique per instance
# Initialize the label
self.label = label
self.unit_system = unit_system # Set the unit system
# Set the forces upon initialization
self.set_forces(N_x, V_z, M_y, M_x)
@property
def id(self) -> int:
"""Read-only property for accessing the unique ID of the instance."""
return self._id
@id.setter
def id(self, value) -> None: # type: ignore[no-untyped-def]
# Normalize error message across Python versions (3.10 vs 3.11+)
raise AttributeError("property 'id' of 'Forces' object has no setter")
@property
def N_x(self) -> Quantity:
"""Axial force along the x-axis (default is 0 kN)."""
if self.unit_system == "metric":
return self._N_x.to("kN")
else:
return self._N_x.to("kip")
@property
def V_z(self) -> Quantity:
"""Shear force along the z-axis (default is 0 kN)."""
if self.unit_system == "metric":
return self._V_z.to("kN")
else:
return self._V_z.to("kip")
@property
def M_y(self) -> Quantity:
"""Bending moment about the y-axis (default is 0 kN*m)."""
if self.unit_system == "metric":
return self._M_y.to("kN*m")
else:
return self._M_y.to("ft*kip")
@property
def M_x(self) -> Quantity:
"""Bending moment about the x-axis — used for biaxial punching shear (default is 0 kN*m)."""
if self.unit_system == "metric":
return self._M_x.to("kN*m")
else:
return self._M_x.to("ft*kip")
[docs]
def get_forces(self) -> Dict[str, Quantity]:
"""Returns the forces as a dictionary with keys 'N_x', 'V_z', 'M_y', and 'M_x'."""
if self.unit_system == "metric":
return {
"N_x": self._N_x.to("kN"),
"V_z": self._V_z.to("kN"),
"M_y": self._M_y.to("kN*m"),
"M_x": self._M_x.to("kN*m"),
}
else:
return {
"N_x": self._N_x.to("kip"),
"V_z": self._V_z.to("kip"),
"M_y": self._M_y.to("ft*kip"),
"M_x": self._M_x.to("ft*kip"),
}
[docs]
def set_forces(
self,
N_x: Quantity = 0 * kN,
V_z: Quantity = 0 * kN,
M_y: Quantity = 0 * kNm,
M_x: Quantity = 0 * kNm,
) -> None:
"""Sets the forces in the object."""
self._N_x = N_x
self._V_z = V_z
self._M_y = M_y
self._M_x = M_x
[docs]
def compare_to(self, other: "Forces", by: str = "V_z") -> bool:
"""Compares this force with another force based on a selected attribute.
Parameters
----------
other : Forces
Another Forces instance to compare with.
by : str
The attribute to compare by ('N_x', 'V_z', 'M_y', or 'M_x').
Returns
-------
bool
True if this force is greater than the other force by the selected attribute.
"""
if by not in ["N_x", "V_z", "M_y", "M_x"]:
raise ValueError("Comparison attribute must be one of 'N_x', 'V_z', 'M_y', or 'M_x'")
return getattr(self, by).magnitude > getattr(other, by).magnitude
def __str__(self) -> str:
base = f"Force ID: {self.id}, Label: {self.label}, N_x: {self.N_x}, V_z: {self.V_z}, M_y: {self.M_y}"
if self._M_x.magnitude != 0:
base += f", M_x: {self.M_x}"
return base