Skip to content

Commit 2aa2ad3

Browse files
Merge pull request #111 from mihdicaballero/punching
Punching phase 0 and 1
2 parents 4196ff8 + 6013ddf commit 2aa2ad3

8 files changed

Lines changed: 884 additions & 75 deletions

File tree

mento/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@
6666
"ACI_318_19_beam",
6767
"BeamSettings",
6868
"BeamSummary",
69+
"Column",
70+
"PunchingSlab",
71+
"Opening",
72+
"Capital",
73+
"PunchingNode",
6974
]
7075

7176
if TYPE_CHECKING:
@@ -83,6 +88,8 @@
8388
from mento.node import Node
8489
from mento.results import DocumentBuilder, Formatter, TablePrinter
8590
from mento.summary import BeamSummary
91+
from mento.column import Column
92+
from mento.punching import Capital, Opening, PunchingNode, PunchingSlab
8693

8794

8895
def __getattr__(name: str) -> object:
@@ -103,6 +110,11 @@ def __getattr__(name: str) -> object:
103110
"EN_1992_2004_beam": "codes",
104111
"ACI_318_19_beam": "codes",
105112
"BeamSummary": "summary",
113+
"Column": "column",
114+
"PunchingSlab": "punching",
115+
"Opening": "punching",
116+
"Capital": "punching",
117+
"PunchingNode": "punching",
106118
}
107119

108120
if name in module_mapping:

mento/column.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass, field
4+
from typing import Literal, Optional, TYPE_CHECKING
5+
6+
from mento.units import cm
7+
8+
if TYPE_CHECKING:
9+
from pint import Quantity
10+
11+
12+
@dataclass
13+
class Column:
14+
"""
15+
Column geometry for punching shear analysis.
16+
17+
Parameters
18+
----------
19+
shape : "rectangular" | "circular"
20+
position : "interior" | "edge" | "corner"
21+
b : column width in x-direction (or diameter for circular)
22+
h : column width in y-direction (ignored for circular)
23+
edge_distance_x : distance from column centroid to free slab edge in x
24+
Required for edge and corner columns.
25+
edge_distance_y : distance from column centroid to free slab edge in y
26+
Required for corner columns.
27+
"""
28+
29+
shape: Literal["rectangular", "circular"]
30+
position: Literal["interior", "edge", "corner"]
31+
b: Quantity = field(default=0 * cm)
32+
h: Quantity = field(default=0 * cm)
33+
edge_distance_x: Optional[Quantity] = field(default=None)
34+
edge_distance_y: Optional[Quantity] = field(default=None)
35+
36+
def __post_init__(self) -> None:
37+
if self.shape not in ("rectangular", "circular"):
38+
raise ValueError(f"shape must be 'rectangular' or 'circular', got {self.shape!r}")
39+
if self.position not in ("interior", "edge", "corner"):
40+
raise ValueError(f"position must be 'interior', 'edge', or 'corner', got {self.position!r}")
41+
if self.position in ("edge", "corner") and self.edge_distance_x is None:
42+
raise ValueError(f"edge_distance_x is required for '{self.position}' columns")
43+
if self.position == "corner" and self.edge_distance_y is None:
44+
raise ValueError("edge_distance_y is required for corner columns")
45+
46+
def __repr__(self) -> str:
47+
shape_str = self.shape.capitalize()
48+
pos_str = self.position.capitalize()
49+
b_cm = self.b.to("cm").magnitude
50+
if self.shape == "rectangular":
51+
h_cm = self.h.to("cm").magnitude
52+
dims = f"b={b_cm:.1f} cm, h={h_cm:.1f} cm"
53+
else:
54+
dims = f"d={b_cm:.1f} cm"
55+
return f"Column({shape_str}, {pos_str}, {dims})"

mento/forces.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class Forces:
3737
_N_x: Quantity = field(default=0 * kN) # Ensure you use the correct unit type here
3838
_V_z: Quantity = field(default=0 * kN)
3939
_M_y: Quantity = field(default=0 * kNm)
40+
_M_x: Quantity = field(default=0 * kNm)
4041
unit_system: str = field(default="metric") # Add unit_system as a field
4142

4243
def __init__(
@@ -45,6 +46,7 @@ def __init__(
4546
N_x: Quantity = 0 * kN,
4647
V_z: Quantity = 0 * kN,
4748
M_y: Quantity = 0 * kNm,
49+
M_x: Quantity = 0 * kNm,
4850
unit_system: str = "metric",
4951
) -> None:
5052
# Increment the class variable for the next unique ID
@@ -56,7 +58,7 @@ def __init__(
5658
self.unit_system = unit_system # Set the unit system
5759

5860
# Set the forces upon initialization
59-
self.set_forces(N_x, V_z, M_y)
61+
self.set_forces(N_x, V_z, M_y, M_x)
6062

6163
@property
6264
def id(self) -> int:
@@ -92,26 +94,43 @@ def M_y(self) -> Quantity:
9294
else:
9395
return self._M_y.to("ft*kip")
9496

97+
@property
98+
def M_x(self) -> Quantity:
99+
"""Bending moment about the x-axis — used for biaxial punching shear (default is 0 kN*m)."""
100+
if self.unit_system == "metric":
101+
return self._M_x.to("kN*m")
102+
else:
103+
return self._M_x.to("ft*kip")
104+
95105
def get_forces(self) -> Dict[str, Quantity]:
96-
"""Returns the forces as a dictionary with keys 'N_x', 'V_z', and 'M_y'."""
106+
"""Returns the forces as a dictionary with keys 'N_x', 'V_z', 'M_y', and 'M_x'."""
97107
if self.unit_system == "metric":
98108
return {
99109
"N_x": self._N_x.to("kN"),
100110
"V_z": self._V_z.to("kN"),
101111
"M_y": self._M_y.to("kN*m"),
112+
"M_x": self._M_x.to("kN*m"),
102113
}
103114
else:
104115
return {
105116
"N_x": self._N_x.to("kip"),
106117
"V_z": self._V_z.to("kip"),
107118
"M_y": self._M_y.to("ft*kip"),
119+
"M_x": self._M_x.to("ft*kip"),
108120
}
109121

110-
def set_forces(self, N_x: Quantity = 0 * kN, V_z: Quantity = 0 * kN, M_y: Quantity = 0 * kNm) -> None:
122+
def set_forces(
123+
self,
124+
N_x: Quantity = 0 * kN,
125+
V_z: Quantity = 0 * kN,
126+
M_y: Quantity = 0 * kNm,
127+
M_x: Quantity = 0 * kNm,
128+
) -> None:
111129
"""Sets the forces in the object."""
112130
self._N_x = N_x
113131
self._V_z = V_z
114132
self._M_y = M_y
133+
self._M_x = M_x
115134

116135
def compare_to(self, other: "Forces", by: str = "V_z") -> bool:
117136
"""Compares this force with another force based on a selected attribute.
@@ -121,16 +140,19 @@ def compare_to(self, other: "Forces", by: str = "V_z") -> bool:
121140
other : Forces
122141
Another Forces instance to compare with.
123142
by : str
124-
The attribute to compare by ('N_x', 'V_z', or 'M_y').
143+
The attribute to compare by ('N_x', 'V_z', 'M_y', or 'M_x').
125144
126145
Returns
127146
-------
128147
bool
129148
True if this force is greater than the other force by the selected attribute.
130149
"""
131-
if by not in ["N_x", "V_z", "M_y"]:
132-
raise ValueError("Comparison attribute must be one of 'N_x', 'V_z', or 'M_y'")
150+
if by not in ["N_x", "V_z", "M_y", "M_x"]:
151+
raise ValueError("Comparison attribute must be one of 'N_x', 'V_z', 'M_y', or 'M_x'")
133152
return getattr(self, by).magnitude > getattr(other, by).magnitude
134153

135154
def __str__(self) -> str:
136-
return f"Force ID: {self.id}, Label: {self.label}, " f"N_x: {self.N_x}, V_z: {self.V_z}, M_y: {self.M_y}"
155+
base = f"Force ID: {self.id}, Label: {self.label}, N_x: {self.N_x}, V_z: {self.V_z}, M_y: {self.M_y}"
156+
if self._M_x.magnitude != 0:
157+
base += f", M_x: {self.M_x}"
158+
return base

0 commit comments

Comments
 (0)