Skip to content

Commit 388b367

Browse files
Merge pull request #480 from robbievanleeuwen/feature/composite-shape-factors
Add shape factors for composite analyses
2 parents 416caf2 + 5c53a28 commit 388b367

File tree

6 files changed

+116
-79
lines changed

6 files changed

+116
-79
lines changed

docs/user_guide/results.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ Plastic Analysis
136136
~sectionproperties.analysis.section.Section.get_pc_p
137137
~sectionproperties.analysis.section.Section.get_mp
138138
~sectionproperties.analysis.section.Section.get_mp_p
139+
~sectionproperties.analysis.section.Section.get_sf
140+
~sectionproperties.analysis.section.Section.get_sf_p
139141

140142
.. _label-material-affects-results:
141143

src/sectionproperties/analysis/plastic_section.py

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -204,46 +204,51 @@ def calculate_plastic_properties(
204204
if progress and task is not None:
205205
progress.update(task_id=task, advance=1)
206206

207-
# if there are no materials specified, calculate shape factors
207+
# calculate shape factors
208+
props = section.section_props
209+
210+
if (
211+
props.sxx is None
212+
or props.syy is None
213+
or props.s11 is None
214+
or props.s22 is None
215+
):
216+
msg = "Property save failed."
217+
raise RuntimeError(msg)
218+
219+
# for a geometric only analysis sf = s / z
208220
if list(set(section.materials)) == [pre.DEFAULT_MATERIAL]:
209221
if (
210-
section.section_props.zxx_plus
211-
and section.section_props.zxx_minus
212-
and section.section_props.zyy_plus
213-
and section.section_props.zyy_minus
222+
props.zxx_plus
223+
and props.zxx_minus
224+
and props.zyy_plus
225+
and props.zyy_minus
214226
):
215-
section.section_props.sf_xx_plus = (
216-
section.section_props.sxx / section.section_props.zxx_plus
217-
)
218-
section.section_props.sf_xx_minus = (
219-
section.section_props.sxx / section.section_props.zxx_minus
220-
)
221-
section.section_props.sf_yy_plus = (
222-
section.section_props.syy / section.section_props.zyy_plus
223-
)
224-
section.section_props.sf_yy_minus = (
225-
section.section_props.syy / section.section_props.zyy_minus
226-
)
227+
props.sf_xx_plus = props.sxx / props.zxx_plus
228+
props.sf_xx_minus = props.sxx / props.zxx_minus
229+
props.sf_yy_plus = props.syy / props.zyy_plus
230+
props.sf_yy_minus = props.syy / props.zyy_minus
227231
if (
228-
section.section_props.s11
229-
and section.section_props.s22
230-
and section.section_props.z11_plus
231-
and section.section_props.z11_minus
232-
and section.section_props.z22_plus
233-
and section.section_props.z22_minus
232+
props.z11_plus
233+
and props.z11_minus
234+
and props.z22_plus
235+
and props.z22_minus
234236
):
235-
section.section_props.sf_11_plus = (
236-
section.section_props.s11 / section.section_props.z11_plus
237-
)
238-
section.section_props.sf_11_minus = (
239-
section.section_props.s11 / section.section_props.z11_minus
240-
)
241-
section.section_props.sf_22_plus = (
242-
section.section_props.s22 / section.section_props.z22_plus
243-
)
244-
section.section_props.sf_22_minus = (
245-
section.section_props.s22 / section.section_props.z22_minus
246-
)
237+
props.sf_11_plus = props.s11 / props.z11_plus
238+
props.sf_11_minus = props.s11 / props.z11_minus
239+
props.sf_22_plus = props.s22 / props.z22_plus
240+
props.sf_22_minus = props.s22 / props.z22_minus
241+
else:
242+
if props.my_xx and props.my_yy:
243+
props.sf_xx_plus = props.sxx / props.my_xx
244+
props.sf_xx_minus = props.sf_xx_plus
245+
props.sf_yy_plus = props.syy / props.my_yy
246+
props.sf_yy_minus = props.sf_yy_plus
247+
if props.my_11 and props.my_22:
248+
props.sf_11_plus = props.s11 / props.my_11
249+
props.sf_11_minus = props.sf_11_plus
250+
props.sf_22_plus = props.s22 / props.my_22
251+
props.sf_22_minus = props.sf_22_plus
247252

248253
if progress and task is not None:
249254
progress.update(

src/sectionproperties/analysis/section.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,7 +1184,8 @@ def calculate_plastic_properties(
11841184
Note:
11851185
If materials are specified, the values calculated for the plastic section
11861186
moduli are displayed as plastic moments (i.e :math:`M_p = f_y S`) and the
1187-
shape factors are not calculated.
1187+
shape factors are calculated as the ratio between the plastic moment and the
1188+
yield moment.
11881189
11891190
Warning:
11901191
The geometric properties must be calculated prior to the calculation of the
@@ -1197,7 +1198,7 @@ def calculate_plastic_properties(
11971198
11981199
- Plastic centroids (centroidal and principal axes)
11991200
- Plastic section moduli (centroidal and principal axes)
1200-
- Shape factors, non-composite only (centroidal and principal axes)
1201+
- Shape factors (centroidal and principal axes)
12011202
"""
12021203
# check that a geometric analysis has been performed
12031204
if self.section_props.cx is None:
@@ -3167,22 +3168,25 @@ def get_mp_p(self) -> tuple[float, float]:
31673168
def get_sf(self) -> tuple[float, float, float, float]:
31683169
"""Returns the cross-section centroidal axis shape factors.
31693170
3170-
This is a geometric only property, as such this can only be returned if material
3171-
properties have *not* been applied to the cross-section.
3171+
For a geometric-only analysis, the shape factor is defined as the ratio between
3172+
the plastic section modulus and the elastic section modulus. For a composite
3173+
analysis the shape factors is defined as the ratio between the plastic moment
3174+
and the yield moment.
3175+
3176+
.. note::
3177+
For composite analyses, the ``plus`` values will always equal the ``minus``
3178+
values as the yield moment occurs when the first fibre reaches its yield
3179+
strength. For geometric-only analyses, the elastic section moduli is
3180+
calculated with respect to the top and bottom fibres (i.e. ``plus`` and
3181+
``minus``).
31723182
31733183
Returns:
31743184
Centroidal axis shape factors with respect to the top and bottom fibres
31753185
(``sf_xx_plus``, ``sf_xx_minus``, ``sf_yy_plus``, ``sf_yy_minus``)
31763186
31773187
Raises:
3178-
RuntimeError: If material properties have been applied
31793188
RuntimeError: If a plastic analysis has not been performed
31803189
"""
3181-
if self.is_composite():
3182-
msg = "Attempting to get a geometric only property for a composite analysis"
3183-
msg += " (material properties have been applied)."
3184-
raise RuntimeError(msg)
3185-
31863190
if (
31873191
self.section_props.sf_xx_plus is None
31883192
or self.section_props.sf_xx_minus is None
@@ -3202,22 +3206,25 @@ def get_sf(self) -> tuple[float, float, float, float]:
32023206
def get_sf_p(self) -> tuple[float, float, float, float]:
32033207
"""Returns the cross-section principal axis shape factors.
32043208
3205-
This is a geometric only property, as such this can only be returned if material
3206-
properties have *not* been applied to the cross-section.
3209+
For a geometric-only analysis, the shape factor is defined as the ratio between
3210+
the plastic section modulus and the elastic section modulus. For a composite
3211+
analysis the shape factors is defined as the ratio between the plastic moment
3212+
and the yield moment.
3213+
3214+
.. note::
3215+
For composite analyses, the ``plus`` values will always equal the ``minus``
3216+
values as the yield moment occurs when the first fibre reaches its yield
3217+
strength. For geometric-only analyses, the elastic section moduli is
3218+
calculated with respect to the top and bottom fibres (i.e. ``plus`` and
3219+
``minus``).
32073220
32083221
Returns:
32093222
Principal bending axis shape factors with respect to the top and bottom
32103223
fibres (``sf_11_plus``, ``sf_11_minus``, ``sf_22_plus``, ``sf_22_minus``)
32113224
32123225
Raises:
3213-
RuntimeError: If material properties have been applied
32143226
RuntimeError: If a plastic analysis has not been performed
32153227
"""
3216-
if self.is_composite():
3217-
msg = "Attempting to get a geometric only property for a composite analysis"
3218-
msg += " (material properties have been applied)."
3219-
raise RuntimeError(msg)
3220-
32213228
if (
32223229
self.section_props.sf_11_plus is None
32233230
or self.section_props.sf_11_minus is None

src/sectionproperties/post/post.py

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,7 @@ def print_results(
675675
try:
676676
my_xx, my_yy = section.get_my()
677677
table.add_row("my_xx", f"{my_xx:>{fmt}}")
678-
table.add_row("my_yy-", f"{my_yy:>{fmt}}")
678+
table.add_row("my_yy", f"{my_yy:>{fmt}}")
679679
except RuntimeError:
680680
pass
681681

@@ -735,7 +735,7 @@ def print_results(
735735
try:
736736
my_11, my_22 = section.get_my_p()
737737
table.add_row("my_11", f"{my_11:>{fmt}}")
738-
table.add_row("my_22-", f"{my_22:>{fmt}}")
738+
table.add_row("my_22", f"{my_22:>{fmt}}")
739739
except RuntimeError:
740740
pass
741741

@@ -913,26 +913,24 @@ def print_results(
913913
pass
914914

915915
# print cross-section sf
916-
if not is_composite:
917-
try:
918-
sf_xx_plus, sf_xx_minus, sf_yy_plus, sf_yy_minus = section.get_sf()
919-
table.add_row("sf_xx+", f"{sf_xx_plus:>{fmt}}")
920-
table.add_row("sf_xx-", f"{sf_xx_minus:>{fmt}}")
921-
table.add_row("sf_yy+", f"{sf_yy_plus:>{fmt}}")
922-
table.add_row("sf_yy-", f"{sf_yy_minus:>{fmt}}")
923-
except RuntimeError:
924-
pass
916+
try:
917+
sf_xx_plus, sf_xx_minus, sf_yy_plus, sf_yy_minus = section.get_sf()
918+
table.add_row("sf_xx+", f"{sf_xx_plus:>{fmt}}")
919+
table.add_row("sf_xx-", f"{sf_xx_minus:>{fmt}}")
920+
table.add_row("sf_yy+", f"{sf_yy_plus:>{fmt}}")
921+
table.add_row("sf_yy-", f"{sf_yy_minus:>{fmt}}")
922+
except RuntimeError:
923+
pass
925924

926925
# print cross-section sf_p
927-
if not is_composite:
928-
try:
929-
sf_11_plus, sf_11_minus, sf_22_plus, sf_22_minus = section.get_sf_p()
930-
table.add_row("sf_11+", f"{sf_11_plus:>{fmt}}")
931-
table.add_row("sf_11-", f"{sf_11_minus:>{fmt}}")
932-
table.add_row("sf_22+", f"{sf_22_plus:>{fmt}}")
933-
table.add_row("sf_22-", f"{sf_22_minus:>{fmt}}")
934-
except RuntimeError:
935-
pass
926+
try:
927+
sf_11_plus, sf_11_minus, sf_22_plus, sf_22_minus = section.get_sf_p()
928+
table.add_row("sf_11+", f"{sf_11_plus:>{fmt}}")
929+
table.add_row("sf_11-", f"{sf_11_minus:>{fmt}}")
930+
table.add_row("sf_22+", f"{sf_22_plus:>{fmt}}")
931+
table.add_row("sf_22-", f"{sf_22_minus:>{fmt}}")
932+
except RuntimeError:
933+
pass
936934

937935
console = Console()
938936
console.print(table)

tests/analysis/test_yield_moment.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ def test_rectangle():
7070
assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0)
7171
assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0)
7272

73+
# check shape factors
74+
sec.calculate_plastic_properties()
75+
sf_xx, _, sf_yy, _ = sec.get_sf()
76+
sf_11, _, sf_22, _ = sec.get_sf_p()
77+
assert sf_xx == pytest.approx(1.5)
78+
assert sf_yy == pytest.approx(1.5)
79+
assert sf_11 == pytest.approx(1.5)
80+
assert sf_22 == pytest.approx(1.5)
81+
7382

7483
def test_rectangle_rotated():
7584
"""Test the yield moment of a simple rotated rectangle."""
@@ -92,6 +101,12 @@ def test_rectangle_rotated():
92101
assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0)
93102
assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0)
94103

104+
# check shape factors
105+
sec.calculate_plastic_properties()
106+
sf_11, _, sf_22, _ = sec.get_sf_p()
107+
assert sf_11 == pytest.approx(1.5)
108+
assert sf_22 == pytest.approx(1.5)
109+
95110

96111
def test_isection():
97112
"""Test the yield moment of an isection."""
@@ -119,6 +134,22 @@ def test_isection():
119134
assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0)
120135
assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0)
121136

137+
# check that shape factors with a geometric-only analysis match the composite
138+
sec.calculate_plastic_properties()
139+
sf_xx_c, _, sf_yy_c, _ = sec.get_sf()
140+
141+
geom = i_section(d=200, b=100, t_f=10, t_w=5, r=12, n_r=8)
142+
geom.create_mesh(mesh_sizes=0, coarse=True)
143+
sec = Section(geometry=geom)
144+
sec.calculate_geometric_properties()
145+
sec.calculate_plastic_properties()
146+
147+
sf_xx_plus, sf_xx_minus, sf_yy_plus, sf_yy_minus = sec.get_sf()
148+
assert sf_xx_c == pytest.approx(sf_xx_plus)
149+
assert sf_xx_c == pytest.approx(sf_xx_minus)
150+
assert sf_yy_c == pytest.approx(sf_yy_plus)
151+
assert sf_yy_c == pytest.approx(sf_yy_minus)
152+
122153

123154
def test_rectangle_composite():
124155
"""Test the yield moment of a composite rectangular section."""

tests/post/test_get_results.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -798,19 +798,13 @@ def test_get_plastic(sec_no_mat_and_mat):
798798
assert sf_yy_plus == pytest.approx(1.5)
799799
assert sf_yy_minus == pytest.approx(1.5)
800800

801-
with pytest.raises(RuntimeError):
802-
rect_mat.get_sf()
803-
804801
# check sf_p
805802
sf_11_plus, sf_11_minus, sf_22_plus, sf_22_minus = rect_no_mat.get_sf_p()
806803
assert sf_11_plus == pytest.approx(1.5)
807804
assert sf_11_minus == pytest.approx(1.5)
808805
assert sf_22_plus == pytest.approx(1.5)
809806
assert sf_22_minus == pytest.approx(1.5)
810807

811-
with pytest.raises(RuntimeError):
812-
rect_mat.get_sf_p()
813-
814808

815809
def test_get_effective_material():
816810
"""Tests effective material properties."""

0 commit comments

Comments
 (0)