structuralcodes 0.5.0__tar.gz → 0.6.1__tar.gz

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.

Files changed (86) hide show
  1. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/PKG-INFO +2 -2
  2. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/pyproject.toml +1 -1
  3. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/__init__.py +1 -1
  4. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/ec2_2004/shear.py +3 -2
  5. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/mc2010/_concrete_creep_and_shrinkage.py +2 -2
  6. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/core/base.py +138 -12
  7. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/geometry/__init__.py +2 -8
  8. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/geometry/_geometry.py +103 -19
  9. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/geometry/_reinforcement.py +1 -3
  10. structuralcodes-0.6.1/structuralcodes/geometry/profiles/__init__.py +33 -0
  11. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_base_profile.py +305 -0
  12. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_common_functions.py +307 -0
  13. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_hd.py +374 -0
  14. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_he.py +192 -0
  15. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_hp.py +319 -0
  16. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_ipe.py +130 -0
  17. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_ipn.py +329 -0
  18. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_l.py +528 -0
  19. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_li.py +217 -0
  20. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_u.py +173 -0
  21. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_ub.py +1356 -0
  22. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_ubp.py +227 -0
  23. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_uc.py +276 -0
  24. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_upe.py +133 -0
  25. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_upn.py +315 -0
  26. structuralcodes-0.6.1/structuralcodes/geometry/profiles/_w.py +2157 -0
  27. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/basic/_elastic.py +18 -1
  28. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/basic/_elasticplastic.py +18 -1
  29. structuralcodes-0.6.1/structuralcodes/materials/basic/_generic.py +43 -0
  30. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/concrete/__init__.py +3 -0
  31. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/concrete/_concrete.py +10 -1
  32. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/concrete/_concreteEC2_2004.py +15 -1
  33. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/concrete/_concreteEC2_2023.py +15 -1
  34. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/concrete/_concreteMC2010.py +20 -1
  35. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/constitutive_laws/__init__.py +3 -0
  36. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/constitutive_laws/_elasticplastic.py +2 -2
  37. structuralcodes-0.6.1/structuralcodes/materials/constitutive_laws/_initial_strain.py +130 -0
  38. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/reinforcement/__init__.py +6 -0
  39. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/reinforcement/_reinforcement.py +10 -1
  40. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py +14 -0
  41. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py +14 -0
  42. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/reinforcement/_reinforcementMC2010.py +14 -0
  43. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/sections/section_integrators/__init__.py +3 -1
  44. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/sections/section_integrators/_marin_integrator.py +1 -1
  45. structuralcodes-0.5.0/structuralcodes/geometry/_steel_sections.py +0 -2155
  46. structuralcodes-0.5.0/structuralcodes/materials/basic/_generic.py +0 -26
  47. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/LICENSE +0 -0
  48. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/README.md +0 -0
  49. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/__init__.py +0 -0
  50. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/ec2_2004/__init__.py +0 -0
  51. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/ec2_2004/_concrete_creep_and_shrinkage.py +0 -0
  52. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/ec2_2004/_concrete_material_properties.py +0 -0
  53. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/ec2_2004/_reinforcement_material_properties.py +0 -0
  54. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py +0 -0
  55. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/ec2_2023/__init__.py +0 -0
  56. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/ec2_2023/_annexB_time_dependent.py +0 -0
  57. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/ec2_2023/_section5_materials.py +0 -0
  58. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/ec2_2023/_section9_sls.py +0 -0
  59. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/mc2010/__init__.py +0 -0
  60. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/mc2010/_concrete_interface_different_casting_times.py +0 -0
  61. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/mc2010/_concrete_material_properties.py +0 -0
  62. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/mc2010/_concrete_punching.py +0 -0
  63. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/mc2010/_concrete_shear.py +0 -0
  64. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/mc2010/_concrete_torsion.py +0 -0
  65. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/mc2010/_interface_concrete_steel_rebar.py +0 -0
  66. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/mc2010/_reinforcement_material_properties.py +0 -0
  67. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/codes/mc2020/__init__.py +0 -0
  68. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/core/__init__.py +0 -0
  69. {structuralcodes-0.5.0/structuralcodes/sections/section_integrators → structuralcodes-0.6.1/structuralcodes/core}/_marin_integration.py +0 -0
  70. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/core/_section_results.py +0 -0
  71. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/geometry/_circular.py +0 -0
  72. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/geometry/_rectangular.py +0 -0
  73. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/__init__.py +0 -0
  74. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/basic/__init__.py +0 -0
  75. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/constitutive_laws/_bilinearcompression.py +0 -0
  76. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/constitutive_laws/_elastic.py +0 -0
  77. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/constitutive_laws/_parabolarectangle.py +0 -0
  78. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/constitutive_laws/_popovics.py +0 -0
  79. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/constitutive_laws/_sargin.py +0 -0
  80. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/materials/constitutive_laws/_userdefined.py +0 -0
  81. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/sections/__init__.py +0 -0
  82. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/sections/_generic.py +0 -0
  83. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/sections/_rc_utils.py +0 -0
  84. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/sections/section_integrators/_factory.py +0 -0
  85. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/sections/section_integrators/_fiber_integrator.py +0 -0
  86. {structuralcodes-0.5.0 → structuralcodes-0.6.1}/structuralcodes/sections/section_integrators/_section_integrator.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structuralcodes
3
- Version: 0.5.0
3
+ Version: 0.6.1
4
4
  Summary: A Python package that contains models from structural design codes.
5
5
  Author-email: fib - International Federation for Structural Concrete <info@fib-international.org>
6
- Requires-Python: >=3.8
6
+ Requires-Python: >=3.9
7
7
  Description-Content-Type: text/markdown
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Operating System :: OS Independent
@@ -10,7 +10,7 @@ authors = [
10
10
  {name = "fib - International Federation for Structural Concrete", email = "info@fib-international.org"}
11
11
  ]
12
12
  readme = "README.md"
13
- requires-python = ">=3.8"
13
+ requires-python = ">=3.9"
14
14
  classifiers = [
15
15
  "Programming Language :: Python :: 3",
16
16
  "Operating System :: OS Independent"
@@ -3,7 +3,7 @@
3
3
  from . import codes, core, geometry, materials, sections
4
4
  from .codes import get_design_codes, set_design_code, set_national_annex
5
5
 
6
- __version__ = '0.5.0'
6
+ __version__ = '0.6.1'
7
7
 
8
8
  __all__ = [
9
9
  'set_design_code',
@@ -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
 
@@ -95,13 +95,13 @@ def _check_initial_stress(sigma: float, fcm: float) -> None:
95
95
  raise ValueError(
96
96
  'The stress level exceeds the range of application.'
97
97
  'Maximum allowable stress is 0.6*fcm. Current stress level '
98
- f'is {round(abs(sigma)/fcm, 3)}*fcm.'
98
+ f'is {round(abs(sigma) / fcm, 3)}*fcm.'
99
99
  )
100
100
  if abs(sigma) > 0.4 * fcm:
101
101
  warnings.warn(
102
102
  'Initial stress is too high to consider the '
103
103
  'concrete as an aging linear visco-elastic material: '
104
- f'sigma = {round(abs(sigma)/fcm,3)}*fcm > 0.4*fcm. Nonlinear'
104
+ f'sigma = {round(abs(sigma) / fcm, 3)}*fcm > 0.4*fcm. Nonlinear'
105
105
  ' creep calculations are performed according to subclause '
106
106
  '5.1.9.4.3 (d) of the fib Model Code 2010 to account for '
107
107
  'large compressive stresses.'
@@ -15,17 +15,44 @@ class Material(abc.ABC):
15
15
  """Abstract base class for materials."""
16
16
 
17
17
  _constitutive_law = None
18
-
19
- def __init__(self, density: float, name: t.Optional[str] = None) -> None:
18
+ _initial_strain: t.Optional[float] = None
19
+ _initial_stress: t.Optional[float] = None
20
+ _strain_compatibility: t.Optional[bool] = None
21
+
22
+ def __init__(
23
+ self,
24
+ density: float,
25
+ initial_strain: t.Optional[float] = None,
26
+ initial_stress: t.Optional[float] = None,
27
+ strain_compatibility: t.Optional[bool] = None,
28
+ name: t.Optional[str] = None,
29
+ ) -> None:
20
30
  """Initializes an instance of a new material.
21
31
 
22
32
  Args:
23
- density (float): density of the material in kg/m3
33
+ density (float): Density of the material in kg/m3.
24
34
 
25
35
  Keyword Args:
36
+ initial_strain (Optional[float]): Initial strain of the material.
37
+ initial_stress (Optional[float]): Initial stress of the material.
38
+ strain_compatibility (Optional[bool]): Only relevant if
39
+ initial_strain or initial_stress are different from zero. If
40
+ True, the material deforms with the geometry. If False, the
41
+ stress in the material upon loading is kept constant
42
+ corresponding to the initial strain.
26
43
  name (Optional[str]): descriptive name of the material
44
+
45
+ Raise:
46
+ ValueError: if both initial_strain and initial_stress are provided
27
47
  """
28
48
  self._density = abs(density)
49
+ if initial_strain is not None and initial_stress is not None:
50
+ raise ValueError(
51
+ 'Both initial_strain and initial_stress cannot be provided.'
52
+ )
53
+ self._initial_strain = initial_strain
54
+ self._initial_stress = initial_stress
55
+ self._strain_compatibility = strain_compatibility
29
56
  self._name = name if name is not None else 'Material'
30
57
 
31
58
  @property
@@ -43,6 +70,88 @@ class Material(abc.ABC):
43
70
  """Returns the density of the material in kg/m3."""
44
71
  return self._density
45
72
 
73
+ @property
74
+ def initial_strain(self):
75
+ """Returns the initial strain of the material."""
76
+ return self._initial_strain
77
+
78
+ @property
79
+ def initial_stress(self):
80
+ """Returns the initial stress of the material."""
81
+ return self._initial_stress
82
+
83
+ @property
84
+ def strain_compatibility(self):
85
+ """Returns the strain compatibility of the material.
86
+
87
+ If true (default), the strain compatibility is enforced
88
+ haveing the same strain as in all other materials of the
89
+ section at the same point. If false, the strain compatibility
90
+ is not enforced and the initial strain is applied to the section
91
+ independently.
92
+ """
93
+ return self._strain_compatibility
94
+
95
+ def _apply_initial_strain(self):
96
+ """Wraps the current constitutive law to apply initial strain."""
97
+ strain_compatibility = (
98
+ self._strain_compatibility
99
+ if self._strain_compatibility is not None
100
+ else True
101
+ )
102
+ if self._initial_stress is not None:
103
+ # Specified a stress, compute the strain from it
104
+ self._initial_strain_from_stress()
105
+ if self._initial_strain is not None:
106
+ # Lazy import to avoid circular dependency
107
+ from structuralcodes.materials.constitutive_laws import ( # noqa: PLC0415
108
+ InitialStrain,
109
+ )
110
+
111
+ if self._initial_stress is None:
112
+ # Compute the stress from the strain
113
+ self._initial_stress = self._constitutive_law.get_stress(
114
+ self._initial_strain
115
+ )
116
+
117
+ self._constitutive_law = InitialStrain(
118
+ self._constitutive_law,
119
+ self._initial_strain,
120
+ strain_compatibility,
121
+ )
122
+
123
+ def _initial_strain_from_stress(self):
124
+ """Computes the initial strain from the initial stress.
125
+
126
+ This function is called internally so it assumes that the
127
+ initial stress is not None
128
+ """
129
+ # Iteratively compute the initial strain that gives the desired
130
+ # initial stress. Note that the wrapped law can be nonlinear
131
+ tol = 1e-12
132
+ max_iter = 100
133
+ target_stress = self._initial_stress
134
+ strain = 0.0
135
+ stress = self._constitutive_law.get_stress(strain)
136
+ d_stress = target_stress - stress
137
+ num_iter = 0
138
+ while abs(d_stress) > tol and num_iter < max_iter:
139
+ tangent = self._constitutive_law.get_tangent(strain)
140
+ if tangent == 0:
141
+ raise ValueError(
142
+ 'Tangent modulus = 0 during initial strain computation.'
143
+ )
144
+ d_strain = d_stress / tangent
145
+ strain += d_strain
146
+ stress = self._constitutive_law.get_stress(strain)
147
+ d_stress = target_stress - stress
148
+ num_iter += 1
149
+
150
+ if abs(d_stress) > tol:
151
+ raise RuntimeError('Failed to converge for given initial stress.')
152
+
153
+ self._initial_strain = strain
154
+
46
155
 
47
156
  class ConstitutiveLaw(abc.ABC):
48
157
  """Abstract base class for constitutive laws."""
@@ -150,7 +259,9 @@ class ConstitutiveLaw(abc.ABC):
150
259
 
151
260
  eps = np.concatenate((eps_neg, eps_pos))
152
261
  sig = self.get_stress(eps)
153
- from structuralcodes.materials.constitutive_laws import UserDefined
262
+ from structuralcodes.materials.constitutive_laws import ( # noqa: PLC0415
263
+ UserDefined,
264
+ )
154
265
 
155
266
  return UserDefined(eps, sig)
156
267
 
@@ -176,14 +287,29 @@ class ConstitutiveLaw(abc.ABC):
176
287
  piecewise_law = self._discretize_law()
177
288
  return piecewise_law.__marin_tangent__(**kwargs)
178
289
 
179
- def get_secant(self, eps: float) -> float:
180
- """Method to return the
181
- secant at a given strain level.
182
- """
183
- if eps != 0:
184
- sig = self.get_stress(eps)
185
- return sig / eps
186
- return self.get_tangent(eps)
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
187
313
 
188
314
 
189
315
  class Section(abc.ABC):
@@ -1,5 +1,6 @@
1
1
  """Main entry point for geometry."""
2
2
 
3
+ from . import profiles
3
4
  from ._circular import CircularGeometry
4
5
  from ._geometry import (
5
6
  CompoundGeometry,
@@ -14,7 +15,6 @@ from ._reinforcement import (
14
15
  add_reinforcement_circle,
15
16
  add_reinforcement_line,
16
17
  )
17
- from ._steel_sections import HE, IPE, IPN, UB, UBP, UC, UPN
18
18
 
19
19
  __all__ = [
20
20
  'Geometry',
@@ -22,13 +22,7 @@ __all__ = [
22
22
  'SurfaceGeometry',
23
23
  'CompoundGeometry',
24
24
  'create_line_point_angle',
25
- 'IPE',
26
- 'HE',
27
- 'UB',
28
- 'UC',
29
- 'UBP',
30
- 'IPN',
31
- 'UPN',
25
+ 'profiles',
32
26
  'add_reinforcement',
33
27
  'add_reinforcement_line',
34
28
  'CircularGeometry',
@@ -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
 
@@ -79,6 +113,17 @@ class Geometry:
79
113
  'This method should be implemented by subclasses'
80
114
  )
81
115
 
116
+ def __add__(self, other: Geometry) -> CompoundGeometry:
117
+ """Add operator "+" for geometries.
118
+
119
+ Arguments:
120
+ other (Geometry): The other geometry to add.
121
+
122
+ Returns:
123
+ CompoundGeometry: A new CompoundGeometry.
124
+ """
125
+ return CompoundGeometry([self, other])
126
+
82
127
 
83
128
  class PointGeometry(Geometry):
84
129
  """Class for a point geometry with material.
@@ -218,6 +263,31 @@ class PointGeometry(Geometry):
218
263
  group_label=self._group_label,
219
264
  )
220
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
+
221
291
  @staticmethod
222
292
  def from_geometry(
223
293
  geo: PointGeometry,
@@ -470,17 +540,6 @@ class SurfaceGeometry(Geometry):
470
540
  # get the intersection
471
541
  return self.polygon.intersection(lines_polygon)
472
542
 
473
- def __add__(self, other: Geometry) -> CompoundGeometry:
474
- """Add operator "+" for geometries.
475
-
476
- Arguments:
477
- other (Geometry): The other geometry to add.
478
-
479
- Returns:
480
- CompoundGeometry: A new CompoundGeometry.
481
- """
482
- return CompoundGeometry([self, other])
483
-
484
543
  def __sub__(self, other: Geometry) -> SurfaceGeometry:
485
544
  """Add operator "-" for geometries.
486
545
 
@@ -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,16 +867,22 @@ class CompoundGeometry(Geometry):
789
867
  processed_geoms.append(pg.rotate(angle, point, use_radians))
790
868
  return CompoundGeometry(geometries=processed_geoms)
791
869
 
792
- def __add__(self, other: Geometry) -> CompoundGeometry:
793
- """Add operator "+" for geometries.
870
+ def mirror(self, axis: LineString) -> CompoundGeometry:
871
+ """Returns a new CompoundGeometry that is mirrored about the given
872
+ axis.
794
873
 
795
874
  Arguments:
796
- other (Geometry): The other geometry to add.
875
+ axis (LineString): The axis to mirror about.
797
876
 
798
877
  Returns:
799
- CompoundGeometry: A new CompoundGeometry.
878
+ CompoundGeometry: The mirrored CompoundGeometry.
800
879
  """
801
- return CompoundGeometry([self, other])
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)
802
886
 
803
887
  def __sub__(self, other: Geometry) -> CompoundGeometry:
804
888
  """Add operator "-" for geometries.
@@ -78,8 +78,6 @@ def add_reinforcement_line(
78
78
  CompoundGeometry: A compound geometry with the original geometry and
79
79
  the reinforcement.
80
80
  """
81
- from math import floor
82
-
83
81
  p1 = np.array(coords_i)
84
82
  p2 = np.array(coords_j)
85
83
  distance = np.linalg.norm(p2 - p1)
@@ -100,7 +98,7 @@ def add_reinforcement_line(
100
98
  elif s > 0:
101
99
  # Provided the spacing
102
100
  # 1. Compute the number of bars
103
- n = floor(distance / s) + 1
101
+ n = math.floor(distance / s) + 1
104
102
  # 2. Distribute the bars centered in the segment
105
103
  d = (n - 1) * s
106
104
  p1 = p1 + v * (distance - d) / 2.0
@@ -0,0 +1,33 @@
1
+ """Main entry point for profiles."""
2
+
3
+ from ._hd import HD
4
+ from ._he import HE
5
+ from ._hp import HP
6
+ from ._ipe import IPE
7
+ from ._ipn import IPN
8
+ from ._l import L
9
+ from ._li import LI
10
+ from ._u import U
11
+ from ._ub import UB
12
+ from ._ubp import UBP
13
+ from ._uc import UC
14
+ from ._upe import UPE
15
+ from ._upn import UPN
16
+ from ._w import W
17
+
18
+ __all__ = [
19
+ 'HE',
20
+ 'HP',
21
+ 'HD',
22
+ 'IPE',
23
+ 'IPN',
24
+ 'UB',
25
+ 'UBP',
26
+ 'UC',
27
+ 'UPN',
28
+ 'UPE',
29
+ 'U',
30
+ 'W',
31
+ 'L',
32
+ 'LI',
33
+ ]