structuralcodes 0.4.0__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of structuralcodes might be problematic. Click here for more details.
- structuralcodes/__init__.py +1 -1
- structuralcodes/codes/ec2_2004/__init__.py +2 -0
- structuralcodes/codes/ec2_2004/shear.py +44 -4
- structuralcodes/codes/mc2010/__init__.py +18 -0
- structuralcodes/codes/mc2010/_concrete_creep_and_shrinkage.py +2 -2
- structuralcodes/codes/mc2010/_concrete_punching.py +300 -388
- structuralcodes/core/base.py +116 -5
- structuralcodes/geometry/__init__.py +2 -8
- structuralcodes/geometry/_circular.py +3 -10
- structuralcodes/geometry/_geometry.py +58 -114
- structuralcodes/geometry/_rectangular.py +3 -10
- structuralcodes/geometry/_reinforcement.py +9 -14
- structuralcodes/geometry/profiles/__init__.py +19 -0
- structuralcodes/geometry/profiles/_base_profile.py +305 -0
- structuralcodes/geometry/profiles/_common_functions.py +194 -0
- structuralcodes/geometry/profiles/_he.py +192 -0
- structuralcodes/geometry/profiles/_ipe.py +130 -0
- structuralcodes/geometry/profiles/_ipn.py +329 -0
- structuralcodes/geometry/profiles/_ub.py +264 -0
- structuralcodes/geometry/profiles/_ubp.py +227 -0
- structuralcodes/geometry/profiles/_uc.py +276 -0
- structuralcodes/geometry/profiles/_upn.py +315 -0
- structuralcodes/materials/__init__.py +2 -1
- structuralcodes/materials/basic/__init__.py +11 -0
- structuralcodes/materials/basic/_elastic.py +69 -0
- structuralcodes/materials/basic/_elasticplastic.py +92 -0
- structuralcodes/materials/basic/_generic.py +43 -0
- structuralcodes/materials/concrete/__init__.py +3 -0
- structuralcodes/materials/concrete/_concrete.py +10 -1
- structuralcodes/materials/concrete/_concreteEC2_2004.py +14 -0
- structuralcodes/materials/concrete/_concreteEC2_2023.py +14 -0
- structuralcodes/materials/concrete/_concreteMC2010.py +19 -0
- structuralcodes/materials/constitutive_laws/__init__.py +3 -0
- structuralcodes/materials/constitutive_laws/_elasticplastic.py +2 -2
- structuralcodes/materials/constitutive_laws/_initial_strain.py +130 -0
- structuralcodes/materials/reinforcement/__init__.py +6 -0
- structuralcodes/materials/reinforcement/_reinforcement.py +10 -1
- structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py +15 -1
- structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py +15 -1
- structuralcodes/materials/reinforcement/_reinforcementMC2010.py +15 -1
- structuralcodes/sections/_generic.py +53 -14
- structuralcodes/sections/_rc_utils.py +15 -5
- structuralcodes/sections/section_integrators/__init__.py +3 -1
- structuralcodes/sections/section_integrators/_fiber_integrator.py +19 -11
- structuralcodes/sections/section_integrators/_marin_integrator.py +25 -20
- {structuralcodes-0.4.0.dist-info → structuralcodes-0.6.0.dist-info}/METADATA +2 -2
- structuralcodes-0.6.0.dist-info/RECORD +77 -0
- structuralcodes/geometry/_steel_sections.py +0 -2155
- structuralcodes-0.4.0.dist-info/RECORD +0 -63
- /structuralcodes/{sections/section_integrators → core}/_marin_integration.py +0 -0
- {structuralcodes-0.4.0.dist-info → structuralcodes-0.6.0.dist-info}/WHEEL +0 -0
- {structuralcodes-0.4.0.dist-info → structuralcodes-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,7 +6,7 @@ import typing as t
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
from shapely import Point
|
|
8
8
|
|
|
9
|
-
from structuralcodes.core.base import
|
|
9
|
+
from structuralcodes.core.base import Material
|
|
10
10
|
|
|
11
11
|
from ._geometry import CompoundGeometry, PointGeometry, SurfaceGeometry
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ def add_reinforcement(
|
|
|
15
15
|
geo: t.Union[SurfaceGeometry, CompoundGeometry],
|
|
16
16
|
coords: t.Tuple[float, float],
|
|
17
17
|
diameter: float,
|
|
18
|
-
material:
|
|
18
|
+
material: Material,
|
|
19
19
|
group_label: t.Optional[str] = None,
|
|
20
20
|
) -> CompoundGeometry:
|
|
21
21
|
"""Add a single bar given coordinate.
|
|
@@ -25,8 +25,7 @@ def add_reinforcement(
|
|
|
25
25
|
reinforcement.
|
|
26
26
|
coords (Tuple(float, float)): A tuple with cordinates of bar.
|
|
27
27
|
diameter (float): The diameter of the reinforcement.
|
|
28
|
-
material (
|
|
29
|
-
constitutive law for the behavior of the reinforcement.
|
|
28
|
+
material (Material): A material for the reinforcement.
|
|
30
29
|
group_label (Optional(str)): A label for grouping several objects
|
|
31
30
|
(default is None).
|
|
32
31
|
|
|
@@ -45,7 +44,7 @@ def add_reinforcement_line(
|
|
|
45
44
|
coords_i: t.Tuple[float, float],
|
|
46
45
|
coords_j: t.Tuple[float, float],
|
|
47
46
|
diameter: float,
|
|
48
|
-
material:
|
|
47
|
+
material: Material,
|
|
49
48
|
n: int = 0,
|
|
50
49
|
s: float = 0.0,
|
|
51
50
|
first: bool = True,
|
|
@@ -60,9 +59,8 @@ def add_reinforcement_line(
|
|
|
60
59
|
coords_i (Tuple(float, float)): Coordinates of the initial point of
|
|
61
60
|
line.
|
|
62
61
|
coords_j (Tuple(float, float)): Coordinates of the final point of line.
|
|
63
|
-
|
|
64
|
-
material (
|
|
65
|
-
constitutive law.
|
|
62
|
+
diameter (float): The diameter of the bars.
|
|
63
|
+
material (Material): A material for the reinforcement.
|
|
66
64
|
n (int): The number of bars to be distributed inside the line (default
|
|
67
65
|
= 0).
|
|
68
66
|
s (float): The distance between the bars (default = 0).
|
|
@@ -80,8 +78,6 @@ def add_reinforcement_line(
|
|
|
80
78
|
CompoundGeometry: A compound geometry with the original geometry and
|
|
81
79
|
the reinforcement.
|
|
82
80
|
"""
|
|
83
|
-
from math import floor
|
|
84
|
-
|
|
85
81
|
p1 = np.array(coords_i)
|
|
86
82
|
p2 = np.array(coords_j)
|
|
87
83
|
distance = np.linalg.norm(p2 - p1)
|
|
@@ -102,7 +98,7 @@ def add_reinforcement_line(
|
|
|
102
98
|
elif s > 0:
|
|
103
99
|
# Provided the spacing
|
|
104
100
|
# 1. Compute the number of bars
|
|
105
|
-
n = floor(distance / s) + 1
|
|
101
|
+
n = math.floor(distance / s) + 1
|
|
106
102
|
# 2. Distribute the bars centered in the segment
|
|
107
103
|
d = (n - 1) * s
|
|
108
104
|
p1 = p1 + v * (distance - d) / 2.0
|
|
@@ -130,7 +126,7 @@ def add_reinforcement_circle(
|
|
|
130
126
|
center: t.Tuple[float, float],
|
|
131
127
|
radius: float,
|
|
132
128
|
diameter: float,
|
|
133
|
-
material:
|
|
129
|
+
material: Material,
|
|
134
130
|
n: int = 0,
|
|
135
131
|
s: float = 0.0,
|
|
136
132
|
first: bool = True,
|
|
@@ -152,8 +148,7 @@ def add_reinforcement_circle(
|
|
|
152
148
|
radius (float): Radius of the circle line where reinforcement will be
|
|
153
149
|
added.
|
|
154
150
|
diameter (float): The diameter of the bars.
|
|
155
|
-
material (
|
|
156
|
-
constitutive law.
|
|
151
|
+
material (Material): A material for the reinforcement.
|
|
157
152
|
n (int): The number of bars to be distributed inside the line (default
|
|
158
153
|
= 0).
|
|
159
154
|
s (float): The distance between the bars (default = 0).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Main entry point for profiles."""
|
|
2
|
+
|
|
3
|
+
from ._he import HE
|
|
4
|
+
from ._ipe import IPE
|
|
5
|
+
from ._ipn import IPN
|
|
6
|
+
from ._ub import UB
|
|
7
|
+
from ._ubp import UBP
|
|
8
|
+
from ._uc import UC
|
|
9
|
+
from ._upn import UPN
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'HE',
|
|
13
|
+
'IPE',
|
|
14
|
+
'IPN',
|
|
15
|
+
'UB',
|
|
16
|
+
'UBP',
|
|
17
|
+
'UC',
|
|
18
|
+
'UPN',
|
|
19
|
+
]
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Base class for profiles."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from shapely import (
|
|
5
|
+
LinearRing,
|
|
6
|
+
LineString,
|
|
7
|
+
Polygon,
|
|
8
|
+
)
|
|
9
|
+
from shapely.affinity import rotate, translate
|
|
10
|
+
from shapely.ops import split
|
|
11
|
+
|
|
12
|
+
from structuralcodes.core._marin_integration import marin_integration
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseProfile:
|
|
16
|
+
"""Base class representing a profile.
|
|
17
|
+
|
|
18
|
+
Contains the common code for all profiles.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
"""Creates an empty base profile."""
|
|
23
|
+
self._polygon: Polygon = None
|
|
24
|
+
self._A: float = None
|
|
25
|
+
self._Iy: float = None
|
|
26
|
+
self._Iz: float = None
|
|
27
|
+
self._Icsi: float = None
|
|
28
|
+
self._Ieta: float = None
|
|
29
|
+
self._Iyz: float = None
|
|
30
|
+
self._Wely: float = None
|
|
31
|
+
self._Welz: float = None
|
|
32
|
+
self._iy: float = None
|
|
33
|
+
self._iz: float = None
|
|
34
|
+
self._Wply: float = None
|
|
35
|
+
self._Wplz: float = None
|
|
36
|
+
|
|
37
|
+
def _check_polygon_defined(self):
|
|
38
|
+
"""Just checks if polygon attribute is defined.
|
|
39
|
+
|
|
40
|
+
If the polygon is not defined (it should never happen), an exception
|
|
41
|
+
is Raised.
|
|
42
|
+
"""
|
|
43
|
+
# The polygon attribute should be already defined
|
|
44
|
+
if self._polygon is None:
|
|
45
|
+
raise RuntimeError(
|
|
46
|
+
'The polygon for some reason was not correctly defined.'
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def _find_plastic_neutral_axis_y(self) -> float:
|
|
50
|
+
"""Find posizion z of plastic neutral axes parallel to y.
|
|
51
|
+
|
|
52
|
+
We use bisection algorithm within the section limits.
|
|
53
|
+
"""
|
|
54
|
+
bounds = self.polygon.bounds
|
|
55
|
+
zmin, zmax = bounds[1], bounds[3]
|
|
56
|
+
|
|
57
|
+
zA = zmin
|
|
58
|
+
zB = zmax
|
|
59
|
+
|
|
60
|
+
daA = self._find_delta_area_above_minus_below(z=zA)
|
|
61
|
+
|
|
62
|
+
ITMAX = 200
|
|
63
|
+
it = 0
|
|
64
|
+
|
|
65
|
+
while (it < ITMAX) and (abs(zB - zA) > (zmax - zmin) * 1e-10):
|
|
66
|
+
zC = (zA + zB) / 2.0
|
|
67
|
+
daC = self._find_delta_area_above_minus_below(z=zC)
|
|
68
|
+
if abs(daC) < 1e-10:
|
|
69
|
+
break
|
|
70
|
+
if daA * daC < 0:
|
|
71
|
+
# The solution is between A and C
|
|
72
|
+
zB = zC
|
|
73
|
+
else:
|
|
74
|
+
# The solution is between C and B
|
|
75
|
+
zA = zC
|
|
76
|
+
daA = daC
|
|
77
|
+
it += 1
|
|
78
|
+
if it >= ITMAX:
|
|
79
|
+
s = f'Last iteration reached a unbalance of {daC}'
|
|
80
|
+
raise ValueError(f'Maximum number of iterations reached.\n{s}')
|
|
81
|
+
|
|
82
|
+
return zC
|
|
83
|
+
|
|
84
|
+
def _find_delta_area_above_minus_below(self, z: float) -> float:
|
|
85
|
+
"""Returns area difference between above and below parts.
|
|
86
|
+
|
|
87
|
+
Above and below parts are computed splitting the polygon with a line
|
|
88
|
+
parallel to Y axes at z coordinate.
|
|
89
|
+
"""
|
|
90
|
+
bounds = self._polygon.bounds
|
|
91
|
+
xmax = max(abs(bounds[0]), bounds[2])
|
|
92
|
+
line = LineString([[-xmax * 1.05, z], [xmax * 1.05, z]])
|
|
93
|
+
|
|
94
|
+
area_above = 0
|
|
95
|
+
area_below = 0
|
|
96
|
+
# divide polygons "above" and "below" line
|
|
97
|
+
if line.intersects(self._polygon):
|
|
98
|
+
result = split(self._polygon, line)
|
|
99
|
+
# divide polygons "above" and "below" line
|
|
100
|
+
for geom in result.geoms:
|
|
101
|
+
if LinearRing(
|
|
102
|
+
(line.coords[0], line.coords[1], geom.centroid.coords[0])
|
|
103
|
+
).is_ccw:
|
|
104
|
+
area_above += geom.area
|
|
105
|
+
else:
|
|
106
|
+
area_below += geom.area
|
|
107
|
+
else:
|
|
108
|
+
# not intersecting, all the polygon is above or below the line
|
|
109
|
+
geom = self.polygon
|
|
110
|
+
if LinearRing(
|
|
111
|
+
(line.coords[0], line.coords[1], geom.centroid.coords[0])
|
|
112
|
+
).is_ccw:
|
|
113
|
+
area_above += geom.area
|
|
114
|
+
else:
|
|
115
|
+
area_below += geom.area
|
|
116
|
+
return area_above - area_below
|
|
117
|
+
|
|
118
|
+
def _find_principals_direction_and_moments(self):
|
|
119
|
+
"""Computes principal direction and second area moments."""
|
|
120
|
+
eigres = np.linalg.eig(
|
|
121
|
+
np.array([[self.Iy, self.Iyz], [self.Iyz, self.Iz]])
|
|
122
|
+
)
|
|
123
|
+
max_idx = np.argmax(eigres[0])
|
|
124
|
+
min_idx = 0 if max_idx == 1 else 1
|
|
125
|
+
self._Icsi = eigres[0][max_idx]
|
|
126
|
+
self._Ieta = eigres[0][min_idx]
|
|
127
|
+
self._theta = np.arccos(
|
|
128
|
+
np.dot(np.array([1, 0]), eigres[1][:, max_idx])
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def A(self) -> float:
|
|
133
|
+
"""Returns area of profile."""
|
|
134
|
+
if self._A is None:
|
|
135
|
+
# Check if the polygon is defined
|
|
136
|
+
self._check_polygon_defined()
|
|
137
|
+
# Get the polygon coordinates:
|
|
138
|
+
xy = self._polygon.exterior.coords.xy
|
|
139
|
+
# Compute area
|
|
140
|
+
self._A = marin_integration(xy[0], xy[1], 0, 0)
|
|
141
|
+
return self._A
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def Iy(self) -> float:
|
|
145
|
+
"""Returns second moment of area around y axis."""
|
|
146
|
+
if self._Iy is None:
|
|
147
|
+
# Check if the polygon is defined
|
|
148
|
+
self._check_polygon_defined()
|
|
149
|
+
# Get the polygon coordinates:
|
|
150
|
+
xy = self._polygon.exterior.coords.xy
|
|
151
|
+
# Compute second moments of inertia
|
|
152
|
+
self._Iy = marin_integration(xy[0], xy[1], 0, 2)
|
|
153
|
+
return self._Iy
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def Iz(self) -> float:
|
|
157
|
+
"""Returns second moment of area around z axis."""
|
|
158
|
+
if self._Iz is None:
|
|
159
|
+
# Check if the polygon is defined
|
|
160
|
+
self._check_polygon_defined()
|
|
161
|
+
# Get the polygon coordinates:
|
|
162
|
+
xy = self._polygon.exterior.coords.xy
|
|
163
|
+
# Compute second moments of inertia
|
|
164
|
+
self._Iz = marin_integration(xy[0], xy[1], 2, 0)
|
|
165
|
+
return self._Iz
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def Iyz(self) -> float:
|
|
169
|
+
"""Returns product moment of inertia."""
|
|
170
|
+
if self._Iyz is None:
|
|
171
|
+
# Check if the polygon is defined
|
|
172
|
+
self._check_polygon_defined()
|
|
173
|
+
# Get the polygon coordinates:
|
|
174
|
+
xy = self._polygon.exterior.coords.xy
|
|
175
|
+
# Compute product moment of area
|
|
176
|
+
self._Iyz = marin_integration(xy[0], xy[1], 1, 1)
|
|
177
|
+
return self._Iyz
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def Icsi(self) -> float:
|
|
181
|
+
"""Returns second moment of area around principal csi axis.
|
|
182
|
+
|
|
183
|
+
It is assumed that Icsi is maximum second moment, while Ieta is the
|
|
184
|
+
minimum one.
|
|
185
|
+
"""
|
|
186
|
+
if self._Icsi is None:
|
|
187
|
+
self._find_principals_direction_and_moments()
|
|
188
|
+
return self._Icsi
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def Ieta(self) -> float:
|
|
192
|
+
"""Returns second moment of area around principal eta axis.
|
|
193
|
+
|
|
194
|
+
It is assumed that Icsi is maximum second moment, while Ieta is the
|
|
195
|
+
minimum one.
|
|
196
|
+
"""
|
|
197
|
+
if self._Ieta is None:
|
|
198
|
+
self._find_principals_direction_and_moments()
|
|
199
|
+
return self._Ieta
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def theta(self) -> float:
|
|
203
|
+
"""Returns angle between x and principal eta axis.
|
|
204
|
+
|
|
205
|
+
It is assumed that Icsi is maximum second moment, while Ieta is the
|
|
206
|
+
minimum one.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
float: The angle in radians.
|
|
210
|
+
"""
|
|
211
|
+
if self._theta is None:
|
|
212
|
+
self._find_principals_direction_and_moments()
|
|
213
|
+
return self._theta
|
|
214
|
+
|
|
215
|
+
def _compute_elastic_moduli(self):
|
|
216
|
+
"""Compute elastic moduli Wely and Welz."""
|
|
217
|
+
# Check if the polygon is defined
|
|
218
|
+
self._check_polygon_defined()
|
|
219
|
+
# For computing section modulus get bounds
|
|
220
|
+
bounds = self._polygon.bounds
|
|
221
|
+
xmax = max(abs(bounds[0]), bounds[2])
|
|
222
|
+
ymax = max(abs(bounds[1]), bounds[3])
|
|
223
|
+
# Then compute section modulus
|
|
224
|
+
self._Wely = self.Iy / ymax
|
|
225
|
+
self._Welz = self.Iz / xmax
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def Wely(self) -> float:
|
|
229
|
+
"""Returns section modulus in y direction."""
|
|
230
|
+
if self._Wely is None:
|
|
231
|
+
# Compute elastic moduli
|
|
232
|
+
self._compute_elastic_moduli()
|
|
233
|
+
return self._Wely
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def Welz(self) -> float:
|
|
237
|
+
"""Returns section modulus in z direction."""
|
|
238
|
+
if self._Welz is None:
|
|
239
|
+
# Compute elastic moduli
|
|
240
|
+
self._compute_elastic_moduli()
|
|
241
|
+
return self._Welz
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def Wply(self) -> float:
|
|
245
|
+
"""Returns plastic section modulus in y direction."""
|
|
246
|
+
if self._Wply is None:
|
|
247
|
+
# Check if the polygon is defined
|
|
248
|
+
self._check_polygon_defined()
|
|
249
|
+
# For computing section modulus get bounds
|
|
250
|
+
bounds = self._polygon.bounds
|
|
251
|
+
xmax = max(abs(bounds[0]), bounds[2])
|
|
252
|
+
# Compute plastic section modulus
|
|
253
|
+
# find plastic neutral axis parallel to y
|
|
254
|
+
self._Wply = 0
|
|
255
|
+
z_pna = self._find_plastic_neutral_axis_y()
|
|
256
|
+
poly = translate(self._polygon, xoff=0, yoff=-z_pna)
|
|
257
|
+
result = split(
|
|
258
|
+
poly,
|
|
259
|
+
LineString([[-xmax * 1.05, 0], [xmax * 1.05, 0]]),
|
|
260
|
+
)
|
|
261
|
+
for poly in result.geoms:
|
|
262
|
+
xy = poly.exterior.coords.xy
|
|
263
|
+
self._Wply += abs(marin_integration(xy[0], xy[1], 0, 1))
|
|
264
|
+
return self._Wply
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def Wplz(self) -> float:
|
|
268
|
+
"""Returns plastic section modulus in z direction."""
|
|
269
|
+
if self._Wplz is None:
|
|
270
|
+
# Check if the polygon is defined
|
|
271
|
+
self._check_polygon_defined()
|
|
272
|
+
# For computing section modulus get bounds
|
|
273
|
+
bounds = self._polygon.bounds
|
|
274
|
+
ymax = max(abs(bounds[1]), bounds[3])
|
|
275
|
+
# Compute plastic section modulus
|
|
276
|
+
# # find plastic neutral axis parallel to z
|
|
277
|
+
self._polygon = rotate(geom=self._polygon, angle=90, origin=(0, 0))
|
|
278
|
+
self._Wplz = 0
|
|
279
|
+
y_pna = self._find_plastic_neutral_axis_y()
|
|
280
|
+
poly = translate(self._polygon, xoff=0, yoff=-y_pna)
|
|
281
|
+
result = split(
|
|
282
|
+
poly,
|
|
283
|
+
LineString([[-ymax * 1.05, 0], [ymax * 1.05, 0]]),
|
|
284
|
+
)
|
|
285
|
+
for poly in result.geoms:
|
|
286
|
+
xy = poly.exterior.coords.xy
|
|
287
|
+
self._Wplz += abs(marin_integration(xy[0], xy[1], 0, 1))
|
|
288
|
+
self._polygon = rotate(
|
|
289
|
+
geom=self._polygon, angle=-90, origin=(0, 0)
|
|
290
|
+
)
|
|
291
|
+
return self._Wplz
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def iy(self) -> float:
|
|
295
|
+
"""Returns radius of inertia of profile."""
|
|
296
|
+
# Compute radius of inertia
|
|
297
|
+
self._iy = self._iy or (self.Iy / self.A) ** 0.5
|
|
298
|
+
return self._iy
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def iz(self) -> float:
|
|
302
|
+
"""Returns radius of inertia of profile."""
|
|
303
|
+
# Compute radius of inertia
|
|
304
|
+
self._iz = self._iz or (self.Iz / self.A) ** 0.5
|
|
305
|
+
return self._iz
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Common functions for creating profiles."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from shapely import (
|
|
5
|
+
LineString,
|
|
6
|
+
Point,
|
|
7
|
+
Polygon,
|
|
8
|
+
get_geometry,
|
|
9
|
+
polygonize,
|
|
10
|
+
set_precision,
|
|
11
|
+
)
|
|
12
|
+
from shapely.affinity import scale, translate
|
|
13
|
+
from shapely.geometry.polygon import orient
|
|
14
|
+
from shapely.ops import linemerge, unary_union
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _create_I_section(h: float, b: float, tw: float, tf: float, r: float):
|
|
18
|
+
"""Returns a polygon for a I section."""
|
|
19
|
+
# top flange
|
|
20
|
+
top_flange = Polygon(
|
|
21
|
+
[
|
|
22
|
+
(-b / 2, -h / 2),
|
|
23
|
+
(b / 2, -h / 2),
|
|
24
|
+
(b / 2, -h / 2 + tf),
|
|
25
|
+
(-b / 2, -h / 2 + tf),
|
|
26
|
+
]
|
|
27
|
+
)
|
|
28
|
+
# bottom flange
|
|
29
|
+
bottom_flange = translate(top_flange, xoff=0, yoff=h - tf)
|
|
30
|
+
web = Polygon(
|
|
31
|
+
[
|
|
32
|
+
(-tw / 2, -h / 2 + tf),
|
|
33
|
+
(tw / 2, -h / 2 + tf),
|
|
34
|
+
(tw / 2, h / 2 - tf),
|
|
35
|
+
(-tw / 2, h / 2 - tf),
|
|
36
|
+
]
|
|
37
|
+
)
|
|
38
|
+
# fillets
|
|
39
|
+
p = Point([tw / 2 + r, -h / 2 + tf + r]).buffer(r)
|
|
40
|
+
s = Polygon(
|
|
41
|
+
[
|
|
42
|
+
(tw / 2, -h / 2 + tf),
|
|
43
|
+
(tw / 2 + r, -h / 2 + tf),
|
|
44
|
+
(tw / 2 + r, -h / 2 + tf + r),
|
|
45
|
+
(tw / 2, -h / 2 + tf + r),
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
fillet = s.difference(p)
|
|
49
|
+
p = Point([-tw / 2 - r, -h / 2 + tf + r]).buffer(r)
|
|
50
|
+
s = Polygon(
|
|
51
|
+
[
|
|
52
|
+
(-tw / 2 - r, -h / 2 + tf),
|
|
53
|
+
(-tw / 2, -h / 2 + tf),
|
|
54
|
+
(-tw / 2, -h / 2 + tf + r),
|
|
55
|
+
(-tw / 2 - r, -h / 2 + tf + r),
|
|
56
|
+
]
|
|
57
|
+
)
|
|
58
|
+
fillet = s.difference(p).union(fillet)
|
|
59
|
+
fillet = translate(
|
|
60
|
+
scale(fillet, 1, -1), xoff=0, yoff=h - 2 * tf - r
|
|
61
|
+
).union(fillet)
|
|
62
|
+
# Create the geometry
|
|
63
|
+
geometries = [
|
|
64
|
+
set_precision(geometry, grid_size=1e-13)
|
|
65
|
+
for geometry in [fillet, top_flange, bottom_flange, web]
|
|
66
|
+
]
|
|
67
|
+
return orient(unary_union(geometries), 1)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _create_taper_I_section(
|
|
71
|
+
h: float,
|
|
72
|
+
b: float,
|
|
73
|
+
tw: float,
|
|
74
|
+
tf: float,
|
|
75
|
+
r1: float,
|
|
76
|
+
r2: float,
|
|
77
|
+
slope: float,
|
|
78
|
+
) -> Polygon:
|
|
79
|
+
"""Returns a shapely polygon representing a Taper Flange I Section."""
|
|
80
|
+
# Create first part of line
|
|
81
|
+
ls = [
|
|
82
|
+
set_precision(
|
|
83
|
+
LineString([[0, -h / 2], [b / 2, -h / 2]]), grid_size=1e-13
|
|
84
|
+
)
|
|
85
|
+
]
|
|
86
|
+
# Create first fillet
|
|
87
|
+
xy = np.array(
|
|
88
|
+
[
|
|
89
|
+
[b / 2, -h / 2],
|
|
90
|
+
[b / 2, -h / 2 + tf - (b / 4 * slope)],
|
|
91
|
+
[b / 4, -h / 2 + tf],
|
|
92
|
+
]
|
|
93
|
+
)
|
|
94
|
+
ls.append(
|
|
95
|
+
set_precision(
|
|
96
|
+
LineString(xy).offset_curve(r2).offset_curve(-r2), grid_size=1e-13
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
# Create second fillet
|
|
100
|
+
xy = np.array(
|
|
101
|
+
[
|
|
102
|
+
[b / 4, -h / 2 + tf],
|
|
103
|
+
[tw / 2, -h / 2 + tf + (b / 4 - tw / 2) * slope],
|
|
104
|
+
[tw / 2, 0],
|
|
105
|
+
]
|
|
106
|
+
)
|
|
107
|
+
ls.append(
|
|
108
|
+
set_precision(
|
|
109
|
+
LineString(xy).offset_curve(-r1).offset_curve(r1), grid_size=1e-13
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Merge filleted
|
|
114
|
+
merged_ls = linemerge(ls)
|
|
115
|
+
|
|
116
|
+
# mirror the parts
|
|
117
|
+
merged_ls = linemerge(
|
|
118
|
+
[
|
|
119
|
+
merged_ls,
|
|
120
|
+
translate(scale(merged_ls, -1, 1), -b / 2, 0),
|
|
121
|
+
]
|
|
122
|
+
)
|
|
123
|
+
merged_ls = linemerge(
|
|
124
|
+
[
|
|
125
|
+
merged_ls,
|
|
126
|
+
translate(scale(merged_ls, 1, -1), 0, h / 2),
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Create a polygon
|
|
131
|
+
poly = polygonize([merged_ls])
|
|
132
|
+
|
|
133
|
+
# Return the first and only polygon of this collection
|
|
134
|
+
return orient(get_geometry(poly, 0), 1)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _create_taper_U_section(
|
|
138
|
+
h: float,
|
|
139
|
+
b: float,
|
|
140
|
+
tw: float,
|
|
141
|
+
tf: float,
|
|
142
|
+
r1: float,
|
|
143
|
+
r2: float,
|
|
144
|
+
slope: float,
|
|
145
|
+
u: float,
|
|
146
|
+
) -> Polygon:
|
|
147
|
+
"""Returns a shapely polygon representing a Taper Flange U Section."""
|
|
148
|
+
# Create first part of line
|
|
149
|
+
ls = [
|
|
150
|
+
set_precision(
|
|
151
|
+
LineString([[0, h / 2], [0, 0], [b, 0]]), grid_size=1e-13
|
|
152
|
+
)
|
|
153
|
+
]
|
|
154
|
+
# Create first fillet
|
|
155
|
+
xy = np.array([[b, 0], [b, tf - slope * u], [b - u, tf]])
|
|
156
|
+
ls.append(
|
|
157
|
+
set_precision(
|
|
158
|
+
LineString(xy).offset_curve(r2).offset_curve(-r2), grid_size=1e-13
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
# Create second fillet
|
|
162
|
+
xy = np.array(
|
|
163
|
+
[
|
|
164
|
+
[b - u, tf],
|
|
165
|
+
[tw, tf + slope * (b - u - tw)],
|
|
166
|
+
[tw, h / 2],
|
|
167
|
+
]
|
|
168
|
+
)
|
|
169
|
+
ls.append(
|
|
170
|
+
set_precision(
|
|
171
|
+
LineString(xy).offset_curve(-r1).offset_curve(r1), grid_size=1e-13
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Merge filleted
|
|
176
|
+
merged_ls = linemerge(ls)
|
|
177
|
+
|
|
178
|
+
# mirror the parts
|
|
179
|
+
merged_ls = linemerge(
|
|
180
|
+
[
|
|
181
|
+
merged_ls,
|
|
182
|
+
translate(scale(merged_ls, 1, -1), 0, h / 2),
|
|
183
|
+
]
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Create a polygon
|
|
187
|
+
poly = polygonize([merged_ls])
|
|
188
|
+
|
|
189
|
+
# Get the first and only polygon of this collection
|
|
190
|
+
poly = get_geometry(poly, 0)
|
|
191
|
+
# Translate it to the centroid when returning
|
|
192
|
+
return orient(
|
|
193
|
+
translate(poly, xoff=-poly.centroid.x, yoff=-poly.centroid.y), 1
|
|
194
|
+
)
|