structuralcodes 0.6.0__py3-none-any.whl → 0.6.1__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/shear.py +3 -2
- structuralcodes/core/base.py +23 -8
- structuralcodes/geometry/_geometry.py +98 -3
- structuralcodes/geometry/profiles/__init__.py +14 -0
- structuralcodes/geometry/profiles/_common_functions.py +114 -1
- structuralcodes/geometry/profiles/_hd.py +374 -0
- structuralcodes/geometry/profiles/_hp.py +319 -0
- structuralcodes/geometry/profiles/_l.py +528 -0
- structuralcodes/geometry/profiles/_li.py +217 -0
- structuralcodes/geometry/profiles/_u.py +173 -0
- structuralcodes/geometry/profiles/_ub.py +1214 -122
- structuralcodes/geometry/profiles/_upe.py +133 -0
- structuralcodes/geometry/profiles/_w.py +2157 -0
- structuralcodes/materials/concrete/_concreteEC2_2004.py +1 -1
- structuralcodes/materials/concrete/_concreteEC2_2023.py +1 -1
- structuralcodes/materials/concrete/_concreteMC2010.py +1 -1
- {structuralcodes-0.6.0.dist-info → structuralcodes-0.6.1.dist-info}/METADATA +1 -1
- {structuralcodes-0.6.0.dist-info → structuralcodes-0.6.1.dist-info}/RECORD +21 -14
- {structuralcodes-0.6.0.dist-info → structuralcodes-0.6.1.dist-info}/WHEEL +0 -0
- {structuralcodes-0.6.0.dist-info → structuralcodes-0.6.1.dist-info}/licenses/LICENSE +0 -0
structuralcodes/__init__.py
CHANGED
|
@@ -203,14 +203,15 @@ def VRdc(
|
|
|
203
203
|
float: The concrete shear resistance in MPa.
|
|
204
204
|
"""
|
|
205
205
|
CRdc = CRdc or 0.18 / gamma_c
|
|
206
|
-
return (
|
|
206
|
+
return max(
|
|
207
207
|
max(
|
|
208
208
|
CRdc * _k(d) * (100 * _rho_L(Asl, bw, d) * fck) ** (1.0 / 3.0)
|
|
209
209
|
+ k1 * _sigma_cp(NEd, Ac, fcd), # VRdc
|
|
210
210
|
vmin(fck, d) + k1 * _sigma_cp(NEd, Ac, fcd), # VRdcmin
|
|
211
211
|
)
|
|
212
212
|
* bw
|
|
213
|
-
* d
|
|
213
|
+
* d,
|
|
214
|
+
0,
|
|
214
215
|
)
|
|
215
216
|
|
|
216
217
|
|
structuralcodes/core/base.py
CHANGED
|
@@ -287,14 +287,29 @@ class ConstitutiveLaw(abc.ABC):
|
|
|
287
287
|
piecewise_law = self._discretize_law()
|
|
288
288
|
return piecewise_law.__marin_tangent__(**kwargs)
|
|
289
289
|
|
|
290
|
-
def get_secant(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
"""
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
290
|
+
def get_secant(
|
|
291
|
+
self, eps: t.Union[float, ArrayLike]
|
|
292
|
+
) -> t.Union[float, ArrayLike]:
|
|
293
|
+
"""Method to return the secant at a given strain level."""
|
|
294
|
+
# Adjust eps if it is not scalar
|
|
295
|
+
eps = eps if np.isscalar(eps) else np.atleast_1d(eps)
|
|
296
|
+
|
|
297
|
+
# Calculate secant for scalar eps
|
|
298
|
+
if np.isscalar(eps):
|
|
299
|
+
if eps != 0:
|
|
300
|
+
sig = self.get_stress(eps)
|
|
301
|
+
return sig / eps
|
|
302
|
+
return self.get_tangent(eps)
|
|
303
|
+
|
|
304
|
+
# Calculate secant for array eps
|
|
305
|
+
secant = np.zeros_like(eps)
|
|
306
|
+
strain_is_zero = eps == 0
|
|
307
|
+
strain_is_nonzero = eps != 0
|
|
308
|
+
secant[strain_is_zero] = self.get_tangent(eps[strain_is_zero])
|
|
309
|
+
secant[strain_is_nonzero] = (
|
|
310
|
+
self.get_stress(eps[strain_is_nonzero]) / eps[strain_is_nonzero]
|
|
311
|
+
)
|
|
312
|
+
return secant
|
|
298
313
|
|
|
299
314
|
|
|
300
315
|
class Section(abc.ABC):
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations # To have clean hints of ArrayLike in docs
|
|
|
4
4
|
|
|
5
5
|
import typing as t
|
|
6
6
|
import warnings
|
|
7
|
+
from math import atan2
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
9
10
|
from numpy.typing import ArrayLike
|
|
@@ -23,6 +24,39 @@ from structuralcodes.materials.basic import ElasticMaterial
|
|
|
23
24
|
from structuralcodes.materials.concrete import Concrete
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
def _mirror_about_axis_matrix(axis: LineString) -> np.ndarray:
|
|
28
|
+
if not isinstance(axis, LineString):
|
|
29
|
+
raise TypeError('axis should be a shapely LineString object')
|
|
30
|
+
|
|
31
|
+
(x1, y1), (x2, y2) = axis.coords
|
|
32
|
+
|
|
33
|
+
# angle of the line with respect to the horizontal axis
|
|
34
|
+
dx, dy = x2 - x1, y2 - y1
|
|
35
|
+
theta = atan2(dy, dx)
|
|
36
|
+
|
|
37
|
+
# Translation matrix T (move line start to origin)
|
|
38
|
+
T = np.array([[1, 0, -x1], [0, 1, -y1], [0, 0, 1]])
|
|
39
|
+
|
|
40
|
+
# Rotation matrix R (align line with x-axis)
|
|
41
|
+
R = np.array(
|
|
42
|
+
[
|
|
43
|
+
[np.cos(theta), np.sin(theta), 0],
|
|
44
|
+
[-np.sin(theta), np.cos(theta), 0],
|
|
45
|
+
[0, 0, 1],
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Mirror across x-axis
|
|
50
|
+
M = np.array([[1, 0, 0], [0, -1, 0], [0, 0, 1]])
|
|
51
|
+
|
|
52
|
+
# Inverses of T and R
|
|
53
|
+
T_inv = np.linalg.inv(T)
|
|
54
|
+
R_inv = np.linalg.inv(R)
|
|
55
|
+
|
|
56
|
+
# Final transformation matrix
|
|
57
|
+
return T_inv @ R_inv @ M @ R @ T
|
|
58
|
+
|
|
59
|
+
|
|
26
60
|
class Geometry:
|
|
27
61
|
"""Base class for a geometry object."""
|
|
28
62
|
|
|
@@ -229,6 +263,31 @@ class PointGeometry(Geometry):
|
|
|
229
263
|
group_label=self._group_label,
|
|
230
264
|
)
|
|
231
265
|
|
|
266
|
+
def mirror(self, axis: LineString) -> PointGeometry:
|
|
267
|
+
"""Returns a new PointGeometry that is mirrored with respect to the
|
|
268
|
+
axis.
|
|
269
|
+
|
|
270
|
+
Arguments:
|
|
271
|
+
axis (LineString): The axis to mirror about.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
PointGeometry: The mirrored PointGeometry.
|
|
275
|
+
"""
|
|
276
|
+
if not isinstance(axis, LineString):
|
|
277
|
+
raise TypeError('axis should be a shapely LineString object')
|
|
278
|
+
|
|
279
|
+
# Build the transformation matrix
|
|
280
|
+
A = _mirror_about_axis_matrix(axis)
|
|
281
|
+
# Apply the transformation to the point
|
|
282
|
+
params = [A[0, 0], A[0, 1], A[1, 0], A[1, 1], A[0, 2], A[1, 2]]
|
|
283
|
+
return PointGeometry(
|
|
284
|
+
point=affinity.affine_transform(self._point, params),
|
|
285
|
+
diameter=self._diameter,
|
|
286
|
+
material=self._material,
|
|
287
|
+
name=self._name,
|
|
288
|
+
group_label=self._group_label,
|
|
289
|
+
)
|
|
290
|
+
|
|
232
291
|
@staticmethod
|
|
233
292
|
def from_geometry(
|
|
234
293
|
geo: PointGeometry,
|
|
@@ -552,6 +611,28 @@ class SurfaceGeometry(Geometry):
|
|
|
552
611
|
concrete=self.concrete,
|
|
553
612
|
)
|
|
554
613
|
|
|
614
|
+
def mirror(self, axis: LineString) -> SurfaceGeometry:
|
|
615
|
+
"""Returns a new SurfaceGeometry that is mirrored about the given axis.
|
|
616
|
+
|
|
617
|
+
Arguments:
|
|
618
|
+
axis (LineString): The axis to mirror about.
|
|
619
|
+
|
|
620
|
+
Returns:
|
|
621
|
+
SurfaceGeometry: The mirrored SurfaceGeometry.
|
|
622
|
+
"""
|
|
623
|
+
if not isinstance(axis, LineString):
|
|
624
|
+
raise TypeError('axis should be a shapely LineString object')
|
|
625
|
+
# Build the transformation matrix
|
|
626
|
+
A = _mirror_about_axis_matrix(axis)
|
|
627
|
+
# Apply transformation matrix A
|
|
628
|
+
# Apply the transformation to the polygon
|
|
629
|
+
params = [A[0, 0], A[0, 1], A[1, 0], A[1, 1], A[0, 2], A[1, 2]]
|
|
630
|
+
return SurfaceGeometry(
|
|
631
|
+
poly=affinity.affine_transform(self.polygon, params),
|
|
632
|
+
material=self.material,
|
|
633
|
+
concrete=self.concrete,
|
|
634
|
+
)
|
|
635
|
+
|
|
555
636
|
@staticmethod
|
|
556
637
|
def from_geometry(
|
|
557
638
|
geo: SurfaceGeometry,
|
|
@@ -595,9 +676,6 @@ class SurfaceGeometry(Geometry):
|
|
|
595
676
|
# from_surface_geometry
|
|
596
677
|
# from_dxf
|
|
597
678
|
# from_ascii
|
|
598
|
-
# ...
|
|
599
|
-
# we could also add methods wrapping shapely function, like:
|
|
600
|
-
# mirror, translation, rotation, etc.
|
|
601
679
|
|
|
602
680
|
|
|
603
681
|
def _process_geometries_multipolygon(
|
|
@@ -789,6 +867,23 @@ class CompoundGeometry(Geometry):
|
|
|
789
867
|
processed_geoms.append(pg.rotate(angle, point, use_radians))
|
|
790
868
|
return CompoundGeometry(geometries=processed_geoms)
|
|
791
869
|
|
|
870
|
+
def mirror(self, axis: LineString) -> CompoundGeometry:
|
|
871
|
+
"""Returns a new CompoundGeometry that is mirrored about the given
|
|
872
|
+
axis.
|
|
873
|
+
|
|
874
|
+
Arguments:
|
|
875
|
+
axis (LineString): The axis to mirror about.
|
|
876
|
+
|
|
877
|
+
Returns:
|
|
878
|
+
CompoundGeometry: The mirrored CompoundGeometry.
|
|
879
|
+
"""
|
|
880
|
+
processed_geoms = []
|
|
881
|
+
for g in self.geometries:
|
|
882
|
+
processed_geoms.append(g.mirror(axis))
|
|
883
|
+
for pg in self.point_geometries:
|
|
884
|
+
processed_geoms.append(pg.mirror(axis))
|
|
885
|
+
return CompoundGeometry(geometries=processed_geoms)
|
|
886
|
+
|
|
792
887
|
def __sub__(self, other: Geometry) -> CompoundGeometry:
|
|
793
888
|
"""Add operator "-" for geometries.
|
|
794
889
|
|
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
"""Main entry point for profiles."""
|
|
2
2
|
|
|
3
|
+
from ._hd import HD
|
|
3
4
|
from ._he import HE
|
|
5
|
+
from ._hp import HP
|
|
4
6
|
from ._ipe import IPE
|
|
5
7
|
from ._ipn import IPN
|
|
8
|
+
from ._l import L
|
|
9
|
+
from ._li import LI
|
|
10
|
+
from ._u import U
|
|
6
11
|
from ._ub import UB
|
|
7
12
|
from ._ubp import UBP
|
|
8
13
|
from ._uc import UC
|
|
14
|
+
from ._upe import UPE
|
|
9
15
|
from ._upn import UPN
|
|
16
|
+
from ._w import W
|
|
10
17
|
|
|
11
18
|
__all__ = [
|
|
12
19
|
'HE',
|
|
20
|
+
'HP',
|
|
21
|
+
'HD',
|
|
13
22
|
'IPE',
|
|
14
23
|
'IPN',
|
|
15
24
|
'UB',
|
|
16
25
|
'UBP',
|
|
17
26
|
'UC',
|
|
18
27
|
'UPN',
|
|
28
|
+
'UPE',
|
|
29
|
+
'U',
|
|
30
|
+
'W',
|
|
31
|
+
'L',
|
|
32
|
+
'LI',
|
|
19
33
|
]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Common functions for creating profiles."""
|
|
2
2
|
|
|
3
|
+
import math
|
|
4
|
+
|
|
3
5
|
import numpy as np
|
|
4
6
|
from shapely import (
|
|
5
7
|
LineString,
|
|
@@ -59,9 +61,17 @@ def _create_I_section(h: float, b: float, tw: float, tf: float, r: float):
|
|
|
59
61
|
fillet = translate(
|
|
60
62
|
scale(fillet, 1, -1), xoff=0, yoff=h - 2 * tf - r
|
|
61
63
|
).union(fillet)
|
|
64
|
+
|
|
65
|
+
# Estimate grid_size value
|
|
66
|
+
# Tentative geometry (due to approximations can be a MultiPolygon)
|
|
67
|
+
geom_trial = unary_union([fillet, top_flange, bottom_flange, web])
|
|
68
|
+
# minx, miny, maxx, maxy
|
|
69
|
+
bounds = geom_trial.bounds
|
|
70
|
+
min_size = min(bounds[2] - bounds[0], bounds[3] - bounds[1])
|
|
71
|
+
grid_size = 10 ** int(math.floor(math.log10(abs(min_size)))) * 1e-12
|
|
62
72
|
# Create the geometry
|
|
63
73
|
geometries = [
|
|
64
|
-
set_precision(geometry, grid_size=
|
|
74
|
+
set_precision(geometry, grid_size=grid_size)
|
|
65
75
|
for geometry in [fillet, top_flange, bottom_flange, web]
|
|
66
76
|
]
|
|
67
77
|
return orient(unary_union(geometries), 1)
|
|
@@ -192,3 +202,106 @@ def _create_taper_U_section(
|
|
|
192
202
|
return orient(
|
|
193
203
|
translate(poly, xoff=-poly.centroid.x, yoff=-poly.centroid.y), 1
|
|
194
204
|
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _create_parallel_U_section(
|
|
208
|
+
h: float,
|
|
209
|
+
b: float,
|
|
210
|
+
tw: float,
|
|
211
|
+
tf: float,
|
|
212
|
+
r: float,
|
|
213
|
+
) -> Polygon:
|
|
214
|
+
"""Returns a shapely polygon representing a Parallel Flange U Section."""
|
|
215
|
+
# top flange
|
|
216
|
+
top_flange = Polygon(
|
|
217
|
+
[
|
|
218
|
+
(0, h / 2 - tf),
|
|
219
|
+
(b, h / 2 - tf),
|
|
220
|
+
(b, h / 2),
|
|
221
|
+
(0, h / 2),
|
|
222
|
+
]
|
|
223
|
+
)
|
|
224
|
+
# bottom flange
|
|
225
|
+
bottom_flange = translate(top_flange, xoff=0, yoff=-h + tf)
|
|
226
|
+
web = Polygon(
|
|
227
|
+
[
|
|
228
|
+
(0, -h / 2 + tf),
|
|
229
|
+
(tw, -h / 2 + tf),
|
|
230
|
+
(tw, h / 2 - tf),
|
|
231
|
+
(0, h / 2 - tf),
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
# fillets
|
|
235
|
+
p = Point([tw + r, -h / 2 + tf + r]).buffer(r)
|
|
236
|
+
s = Polygon(
|
|
237
|
+
[
|
|
238
|
+
(tw, -h / 2 + tf),
|
|
239
|
+
(tw + r, -h / 2 + tf),
|
|
240
|
+
(tw + r, -h / 2 + tf + r),
|
|
241
|
+
(tw, -h / 2 + tf + r),
|
|
242
|
+
]
|
|
243
|
+
)
|
|
244
|
+
fillet = s.difference(p)
|
|
245
|
+
fillet = translate(
|
|
246
|
+
scale(fillet, 1, -1), xoff=0, yoff=h - 2 * tf - r
|
|
247
|
+
).union(fillet)
|
|
248
|
+
# Estimate grid_size value
|
|
249
|
+
# Tentative geometry (due to approximations can be a MultiPolygon)
|
|
250
|
+
geom_trial = unary_union([fillet, top_flange, bottom_flange, web])
|
|
251
|
+
# minx, miny, maxx, maxy
|
|
252
|
+
bounds = geom_trial.bounds
|
|
253
|
+
min_size = min(bounds[2] - bounds[0], bounds[3] - bounds[1])
|
|
254
|
+
grid_size = 10 ** int(math.floor(math.log10(abs(min_size)))) * 1e-12
|
|
255
|
+
# Create the geometry
|
|
256
|
+
geometries = [
|
|
257
|
+
set_precision(geometry, grid_size=grid_size)
|
|
258
|
+
for geometry in [fillet, top_flange, bottom_flange, web]
|
|
259
|
+
]
|
|
260
|
+
geometry = orient(unary_union(geometries), 1)
|
|
261
|
+
# Return the geometry centered at the origin
|
|
262
|
+
return translate(
|
|
263
|
+
geometry, xoff=-geometry.centroid.x, yoff=-geometry.centroid.y
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _create_L_section(
|
|
268
|
+
h: float, b: float, t1: float, t2: float, r1: float, r2: float
|
|
269
|
+
) -> Polygon:
|
|
270
|
+
"""Returns a shapely polygon representing a L Section."""
|
|
271
|
+
# Create first part of line
|
|
272
|
+
ls = [set_precision(LineString([[0, h], [0, 0], [b, 0]]), grid_size=1e-13)]
|
|
273
|
+
# Create fillet
|
|
274
|
+
xy = np.array([[b, 0], [b, t1], [b / 2, t1]])
|
|
275
|
+
ls.append(
|
|
276
|
+
set_precision(
|
|
277
|
+
LineString(xy).offset_curve(r2).offset_curve(-r2), grid_size=1e-13
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
# Create second fillet
|
|
281
|
+
xy = np.array([[b / 2, t1], [t2, t1], [t2, h / 2]])
|
|
282
|
+
ls.append(
|
|
283
|
+
set_precision(
|
|
284
|
+
LineString(xy).offset_curve(-r1).offset_curve(r1), grid_size=1e-13
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
# Last fillet
|
|
288
|
+
xy = np.array([[t2, h / 2], [t2, h], [0, h]])
|
|
289
|
+
ls.append(
|
|
290
|
+
set_precision(
|
|
291
|
+
LineString(xy).offset_curve(r2).offset_curve(-r2), grid_size=1e-13
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
# Merge filleted
|
|
295
|
+
merged_ls = linemerge(ls)
|
|
296
|
+
|
|
297
|
+
merged_ls
|
|
298
|
+
|
|
299
|
+
# Create a polygon
|
|
300
|
+
poly = polygonize([merged_ls])
|
|
301
|
+
|
|
302
|
+
# Return the first and only polygon of this collection
|
|
303
|
+
poly = get_geometry(poly, 0)
|
|
304
|
+
# Translate it to the centroid when returning
|
|
305
|
+
return orient(
|
|
306
|
+
translate(poly, xoff=-poly.centroid.x, yoff=-poly.centroid.y), 1
|
|
307
|
+
)
|