From c3b0ef2bc68dc5ceb89e1deeec91aed14f6e6970 Mon Sep 17 00:00:00 2001 From: robbievanleeuwen Date: Thu, 31 Oct 2024 01:41:08 +0000 Subject: [PATCH 1/3] Add docs, tests, get methods for yield moment --- docs/user_guide/results.rst | 2 + docs/user_guide/theory.rst | 5 ++ src/sectionproperties/analysis/section.py | 65 ++++++++++++++++++++++- src/sectionproperties/post/post.py | 10 +++- tests/analysis/test_yield_moment.py | 65 +++++++++++++++++++++++ 5 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 tests/analysis/test_yield_moment.py diff --git a/docs/user_guide/results.rst b/docs/user_guide/results.rst index 035e31f9..893669a7 100644 --- a/docs/user_guide/results.rst +++ b/docs/user_guide/results.rst @@ -99,10 +99,12 @@ Geometric Analysis ~sectionproperties.analysis.section.Section.get_c ~sectionproperties.analysis.section.Section.get_eic ~sectionproperties.analysis.section.Section.get_ez + ~sectionproperties.analysis.section.Section.get_my ~sectionproperties.analysis.section.Section.get_rc ~sectionproperties.analysis.section.Section.get_eip ~sectionproperties.analysis.section.Section.get_phi ~sectionproperties.analysis.section.Section.get_ezp + ~sectionproperties.analysis.section.Section.get_my_p ~sectionproperties.analysis.section.Section.get_rp ~sectionproperties.analysis.section.Section.get_nu_eff ~sectionproperties.analysis.section.Section.get_e_eff diff --git a/docs/user_guide/theory.rst b/docs/user_guide/theory.rst index bdcd084a..cbfd09c7 100644 --- a/docs/user_guide/theory.rst +++ b/docs/user_guide/theory.rst @@ -420,6 +420,11 @@ extreme (min. and max.) coordinates of the cross-section in the x and y-directio Z_{yy}^+ = \frac{I_{\overline{yy}}}{x_{max} - x_c} \\ Z_{yy}^- = \frac{I_{\overline{yy}}}{x_c - x_{min}} \\ +Yield Moments +~~~~~~~~~~~~~ + +TODO + .. _label-theory-plastic-section-moduli: Plastic Section Moduli diff --git a/src/sectionproperties/analysis/section.py b/src/sectionproperties/analysis/section.py index 0e680333..bd71eb55 100644 --- a/src/sectionproperties/analysis/section.py +++ b/src/sectionproperties/analysis/section.py @@ -202,6 +202,7 @@ def calculate_geometric_properties(self) -> None: - Centroidal section moduli - Radii of gyration - Principal axis properties + - Yield moments (composite only) """ def calculate_geom(progress: Progress | None = None) -> None: @@ -259,11 +260,21 @@ def calculate_geom(progress: Progress | None = None) -> None: ) self.section_props.e_eff = self.section_props.ea / self.section_props.area self.section_props.g_eff = self.section_props.ga / self.section_props.area + + # calculate derived properties self.section_props.calculate_elastic_centroid() self.section_props.calculate_centroidal_properties( node_list=self.mesh["vertices"] ) + # calculate yield moments + self.section_props.my_xx = 0.0 + self.section_props.my_yy = 0.0 + self.section_props.my_11 = 0.0 + self.section_props.my_22 = 0.0 + + # TODO: calculate yield moments + if progress and task is not None: msg = "[bold green]:white_check_mark: Geometric analysis complete" progress.update(task_id=task, description=msg) @@ -1120,7 +1131,7 @@ def calculate_plastic_properties( - Plastic centroids (centroidal and principal axes) - Plastic section moduli (centroidal and principal axes) - - Shape factors, non-composite only (centroidal and principal axe) + - Shape factors, non-composite only (centroidal and principal axes) """ # check that a geometric analysis has been performed if self.section_props.cx is None: @@ -2240,6 +2251,32 @@ def get_ez( self.section_props.zyy_minus / e_ref, ) + def get_my(self) -> tuple[float, float]: + """Returns the yield moment for bending about the centroidal axis. + + This is a composite only property, as such this can only be returned if material + properties have been applied to the cross-section. + + Returns: + Yield moment for bending about the centroidal ``x`` and ``y`` axes + (``my_xx``, ``my_yy``) + + Raises: + RuntimeError: If material properties have *not* been applied + RuntimeError: If a geometric analysis has not been performed + """ + if not self.is_composite(): + msg = "Attempting to get a composite only property for a geometric analysis" + msg += " (material properties have not been applied). Consider using" + msg += " get_z()." + raise RuntimeError(msg) + + if self.section_props.my_xx is None or self.section_props.my_yy is None: + msg = "Conduct a geometric analysis." + raise RuntimeError(msg) + + return (self.section_props.my_xx, self.section_props.my_yy) + def get_rc(self) -> tuple[float, float]: """Returns the cross-section centroidal radii of gyration. @@ -2418,6 +2455,32 @@ def get_ezp( self.section_props.z22_minus / e_ref, ) + def get_my_p(self) -> tuple[float, float]: + """Returns the yield moment for bending about the principal axis. + + This is a composite only property, as such this can only be returned if material + properties have been applied to the cross-section. + + Returns: + Yield moment for bending about the principal ``11`` and ``22`` axes + (``my_11``, ``my_22``) + + Raises: + RuntimeError: If material properties have *not* been applied + RuntimeError: If a geometric analysis has not been performed + """ + if not self.is_composite(): + msg = "Attempting to get a composite only property for a geometric analysis" + msg += " (material properties have not been applied). Consider using" + msg += " get_zp()." + raise RuntimeError(msg) + + if self.section_props.my_11 is None or self.section_props.my_22 is None: + msg = "Conduct a geometric analysis." + raise RuntimeError(msg) + + return (self.section_props.my_11, self.section_props.my_22) + def get_rp(self) -> tuple[float, float]: """Returns the cross-section principal radii of gyration. diff --git a/src/sectionproperties/post/post.py b/src/sectionproperties/post/post.py index f15896b5..7fd72f2b 100644 --- a/src/sectionproperties/post/post.py +++ b/src/sectionproperties/post/post.py @@ -72,6 +72,10 @@ class SectionProperties: negative extreme value of the 11-axis r11_c: Radius of gyration about the principal 11-axis. r22_c: Radius of gyration about the principal 22-axis. + my_xx: Yield moment about the x-axis + my_yy: Yield moment about the y-axis + my_11: Yield moment about the 11-axis + my_22: Yield moment about the 22-axis j: Torsion constant omega: Warping function psi_shear: Psi shear function @@ -165,6 +169,10 @@ class SectionProperties: r11_c: float | None = None r22_c: float | None = None j: float | None = None + my_xx: float | None = None + my_yy: float | None = None + my_11: float | None = None + my_22: float | None = None omega: npt.NDArray[np.float64] | None = None psi_shear: npt.NDArray[np.float64] | None = None phi_shear: npt.NDArray[np.float64] | None = None @@ -278,7 +286,7 @@ def calculate_centroidal_properties( else: self.phi = np.arctan2(self.ixx_c - self.i11_c, self.ixy_c) * 180 / np.pi - # initialise min, max variables TODO: check for `if xxx:` where xxx is float + # initialise min, max variables if self.phi is not None: x1, y2 = fea.principal_coordinate( phi=self.phi, diff --git a/tests/analysis/test_yield_moment.py b/tests/analysis/test_yield_moment.py new file mode 100644 index 00000000..5d5998c9 --- /dev/null +++ b/tests/analysis/test_yield_moment.py @@ -0,0 +1,65 @@ +"""Tests for the yield moment calculation.""" + +import pytest + +from sectionproperties.analysis import Section +from sectionproperties.pre import Material +from sectionproperties.pre.library import rectangular_section + +STEEL = Material( + name="Steel", + elastic_modulus=200e3, + poissons_ratio=0.3, + yield_strength=500, + density=7.85e-6, + color="grey", +) + + +def test_get_without_analysis(): + """Test for raising an error if a geometric analysis has not been performed.""" + geom = rectangular_section(d=50, b=50) + geom.create_mesh(mesh_sizes=0, coarse=True) + sec = Section(geometry=geom) + + with pytest.raises(RuntimeError, match="Conduct a geometric analysis."): + sec.get_my() + + with pytest.raises(RuntimeError, match="Conduct a geometric analysis."): + sec.get_my_p() + + +def test_non_composite(): + """Test for raising an error for non-composite analyses.""" + geom = rectangular_section(d=50, b=50) + geom.create_mesh(mesh_sizes=0, coarse=True) + sec = Section(geometry=geom) + sec.calculate_geometric_properties() + + with pytest.raises(RuntimeError, match="Attempting to get a composite only"): + sec.get_my() + + with pytest.raises(RuntimeError, match="Attempting to get a composite only"): + sec.get_my_p() + + +def test_rectangle(): + """Test the yield moment of a simple rectangle.""" + geom = rectangular_section(d=100, b=50, material=STEEL) + geom.create_mesh(mesh_sizes=0, coarse=True) + sec = Section(geometry=geom) + sec.calculate_geometric_properties() + + my_xx, my_yy = sec.get_my() + my_11, my_22 = sec.get_my() + + my_xx_calc = 50 * 100 * 100 / 4 * 500 + my_yy_calc = 100 * 50 * 50 / 4 * 500 + + assert my_xx == pytest.approx(my_xx_calc) + assert my_yy == pytest.approx(my_yy_calc) + assert my_11 == pytest.approx(my_xx_calc) + assert my_22 == pytest.approx(my_yy_calc) + + +# TODO: add more tests From b917aa441d8ae88e887ab6df8b68710a140f6b72 Mon Sep 17 00:00:00 2001 From: robbievanleeuwen Date: Thu, 31 Oct 2024 06:19:48 +0000 Subject: [PATCH 2/3] Implement yield moment, update theory, add to display_results(), add more tests --- docs/user_guide/theory.rst | 11 +- src/sectionproperties/analysis/section.py | 68 +++++++- src/sectionproperties/post/post.py | 18 ++ tests/analysis/test_yield_moment.py | 193 +++++++++++++++++++++- 4 files changed, 283 insertions(+), 7 deletions(-) diff --git a/docs/user_guide/theory.rst b/docs/user_guide/theory.rst index cbfd09c7..d78764e3 100644 --- a/docs/user_guide/theory.rst +++ b/docs/user_guide/theory.rst @@ -423,7 +423,16 @@ extreme (min. and max.) coordinates of the cross-section in the x and y-directio Yield Moments ~~~~~~~~~~~~~ -TODO +The yield moment is defined as the lowest bending moment that causes any point within +cross-section to reach the yield strength. Note that this implementation is purely +linear-elastic i.e. uses the linear-elastic modulus and bi-directional yield strength +only. + +``sectionproperties`` applies a unit bending moment, about each axis separately, and +determines the yield index for each point within the mesh. The yield index is defined as +the stress divided by the material yield strength. Through this method, a critical yield +index is determined (i.e. point which will yield first under bending) and the yield +moment calculated as the inverse of the critical yield index. .. _label-theory-plastic-section-moduli: diff --git a/src/sectionproperties/analysis/section.py b/src/sectionproperties/analysis/section.py index bd71eb55..74b59a51 100644 --- a/src/sectionproperties/analysis/section.py +++ b/src/sectionproperties/analysis/section.py @@ -273,7 +273,73 @@ def calculate_geom(progress: Progress | None = None) -> None: self.section_props.my_11 = 0.0 self.section_props.my_22 = 0.0 - # TODO: calculate yield moments + # calculate yield moments: + # 1) loop through each material group and through each element in the group + # 2) for each point, calculate the bending stress from a unit bending moment + # in each direction (mxx, myy, m11, m22) + # 3) from this, calculate the yield index for each point + # 4) get the largest yield index and scale the bending moment such that the + # yield index is 1 + + # initialise the yield indexes + yield_index = { + "mxx": 0.0, + "myy": 0.0, + "m11": 0.0, + "m22": 0.0, + } + + # get useful section properties + cx = self.section_props.cx + cy = self.section_props.cy + phi = self.section_props.phi + ixx = self.section_props.ixx_c + iyy = self.section_props.iyy_c + ixy = self.section_props.ixy_c + i11 = self.section_props.i11_c + i22 = self.section_props.i22_c + + if ixx is None or iyy is None or ixy is None or i11 is None or i22 is None: + msg = "Section properties failed to save." + raise RuntimeError(msg) + + # loop through each material group + for group in self.material_groups: + em = group.material.elastic_modulus + fy = group.material.yield_strength + + # loop through each element in the material group + for el in group.elements: + # loop through each node in the element + for coord in el.coords.transpose(): + # calculate coordinates wrt centroidal & principal axes + x = coord[0] - cx + y = coord[1] - cy + x11, y22 = fea.principal_coordinate(phi=phi, x=x, y=y) + + # calculate bending stresses due to unit moments + sig_mxx = em * ( + -ixy / (ixx * iyy - ixy**2) * x + + iyy / (ixx * iyy - ixy**2) * y + ) + sig_myy = em * ( + -ixx / (ixx * iyy - ixy**2) * x + + ixy / (ixx * iyy - ixy**2) * y + ) + sig_m11 = em / i11 * y22 + sig_m22 = -em / i22 * x11 + + # update yield indexes + yield_index["mxx"] = max(yield_index["mxx"], abs(sig_mxx / fy)) + yield_index["myy"] = max(yield_index["myy"], abs(sig_myy / fy)) + yield_index["m11"] = max(yield_index["m11"], abs(sig_m11 / fy)) + yield_index["m22"] = max(yield_index["m22"], abs(sig_m22 / fy)) + + # calculate yield moments + self.section_props.my_xx = 1.0 / yield_index["mxx"] + self.section_props.my_yy = 1.0 / yield_index["myy"] + self.section_props.my_11 = 1.0 / yield_index["m11"] + self.section_props.my_22 = 1.0 / yield_index["m22"] if progress and task is not None: msg = "[bold green]:white_check_mark: Geometric analysis complete" diff --git a/src/sectionproperties/post/post.py b/src/sectionproperties/post/post.py index 7fd72f2b..4d7abb8a 100644 --- a/src/sectionproperties/post/post.py +++ b/src/sectionproperties/post/post.py @@ -670,6 +670,15 @@ def print_results( except RuntimeError: pass + # print cross-section my + if is_composite: + try: + my_xx, my_yy = section.get_my() + table.add_row("my_xx", f"{my_xx:>{fmt}}") + table.add_row("my_yy-", f"{my_yy:>{fmt}}") + except RuntimeError: + pass + # print cross-section rc try: rx, ry = section.get_rc() @@ -721,6 +730,15 @@ def print_results( except RuntimeError: pass + # print cross-section my_p + if is_composite: + try: + my_11, my_22 = section.get_my_p() + table.add_row("my_11", f"{my_11:>{fmt}}") + table.add_row("my_22-", f"{my_22:>{fmt}}") + except RuntimeError: + pass + # print cross-section rp try: r11, r22 = section.get_rp() diff --git a/tests/analysis/test_yield_moment.py b/tests/analysis/test_yield_moment.py index 5d5998c9..5911e92c 100644 --- a/tests/analysis/test_yield_moment.py +++ b/tests/analysis/test_yield_moment.py @@ -1,10 +1,12 @@ """Tests for the yield moment calculation.""" +import numpy as np import pytest from sectionproperties.analysis import Section +from sectionproperties.post.stress_post import StressPost from sectionproperties.pre import Material -from sectionproperties.pre.library import rectangular_section +from sectionproperties.pre.library import i_section, rectangular_section STEEL = Material( name="Steel", @@ -18,7 +20,7 @@ def test_get_without_analysis(): """Test for raising an error if a geometric analysis has not been performed.""" - geom = rectangular_section(d=50, b=50) + geom = rectangular_section(d=50, b=50, material=STEEL) geom.create_mesh(mesh_sizes=0, coarse=True) sec = Section(geometry=geom) @@ -52,14 +54,195 @@ def test_rectangle(): my_xx, my_yy = sec.get_my() my_11, my_22 = sec.get_my() + my_xx_calc = 50 * 100 * 100 / 6 * 500 + my_yy_calc = 100 * 50 * 50 / 6 * 500 + + # compare with theoretical values + assert my_xx == pytest.approx(my_xx_calc) + assert my_yy == pytest.approx(my_yy_calc) + assert my_11 == pytest.approx(my_xx_calc) + assert my_22 == pytest.approx(my_yy_calc) + + # compare with stress analysis + stress = sec.calculate_stress(mxx=my_xx, myy=my_yy, m11=my_11, m22=my_22) + assert check_yield_index(stress, "sig_zz_mxx") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_myy") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0) + + +def test_rectangle_rotated(): + """Test the yield moment of a simple rotated rectangle.""" + geom = rectangular_section(d=100, b=50, material=STEEL) + geom.rotate_section(angle=45.0) + geom.create_mesh(mesh_sizes=0, coarse=True) + sec = Section(geometry=geom) + sec.calculate_geometric_properties() + + my_11, my_22 = sec.get_my() + my_xx_calc = 50 * 100 * 100 / 6 * 500 + my_yy_calc = 100 * 50 * 50 / 6 * 500 + + # compare with theoretical values + assert my_11 == pytest.approx(my_xx_calc) + assert my_22 == pytest.approx(my_yy_calc) + + # compare with stress analysis + stress = sec.calculate_stress(m11=my_11, m22=my_22) + assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0) + + +def test_isection(): + """Test the yield moment of an isection.""" + geom = i_section(d=200, b=100, t_f=10, t_w=5, r=12, n_r=8, material=STEEL) + geom.create_mesh(mesh_sizes=0, coarse=True) + sec = Section(geometry=geom) + sec.calculate_geometric_properties() + + my_xx, my_yy = sec.get_my() + my_11, my_22 = sec.get_my() + ezxx, _, ezyy, _ = sec.get_ez() + my_xx_calc = ezxx / 200e3 * 500 + my_yy_calc = ezyy / 200e3 * 500 - my_xx_calc = 50 * 100 * 100 / 4 * 500 - my_yy_calc = 100 * 50 * 50 / 4 * 500 + # compare with theoretical values + assert my_xx == pytest.approx(my_xx_calc) + assert my_yy == pytest.approx(my_yy_calc) + assert my_11 == pytest.approx(my_xx_calc) + assert my_22 == pytest.approx(my_yy_calc) + + # compare with stress analysis + stress = sec.calculate_stress(mxx=my_xx, myy=my_yy, m11=my_11, m22=my_22) + assert check_yield_index(stress, "sig_zz_mxx") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_myy") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0) + + +def test_rectangle_composite(): + """Test the yield moment of a composite rectangular section.""" + mat1 = Material("a", 2, 0, 2, 1, "b") + mat2 = Material("b", 1, 0, 1, 1, "r") + rect1 = rectangular_section(d=20, b=20, material=mat1) + rect2 = rectangular_section(d=20, b=20, material=mat2).align_to(rect1, "top") + rect3 = rectangular_section(d=20, b=20, material=mat1).align_to(rect2, "top") + geom = rect1 + rect2 + rect3 + geom.create_mesh(mesh_sizes=0, coarse=True) + sec = Section(geom) + sec.calculate_geometric_properties() + + my_xx, my_yy = sec.get_my() + my_11, my_22 = sec.get_my() + eixx_calc = (20 * 20**3 / 12) + 2 * 2 * ((20 * 20**3 / 12) + (20 * 20 * 20**2)) + eiyy_calc = 5 * (20 * 20**3 / 12) + # myield = f * Ieff / y + my_xx_calc = 2 * (eixx_calc / 2) / 30 + my_yy_calc = 1 * eiyy_calc / 10 + + # compare with theoretical values assert my_xx == pytest.approx(my_xx_calc) assert my_yy == pytest.approx(my_yy_calc) assert my_11 == pytest.approx(my_xx_calc) assert my_22 == pytest.approx(my_yy_calc) + # compare with stress analysis + stress = sec.calculate_stress(mxx=my_xx, myy=my_yy, m11=my_11, m22=my_22) + assert check_yield_index(stress, "sig_zz_mxx") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_myy") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0) + + +def test_composite_example(): + """Tests the composite example from the docs, compares to stress analysis.""" + timber = Material( + name="Timber", + elastic_modulus=8e3, + poissons_ratio=0.35, + yield_strength=20, + density=0.78e-6, + color="burlywood", + ) + + ub = i_section(d=304, b=165, t_f=10.2, t_w=6.1, r=11.4, n_r=8, material=STEEL) + panel = rectangular_section(d=100, b=600, material=timber) + panel = panel.align_center(align_to=ub).align_to(other=ub, on="top") + geom = ub + panel + geom.create_mesh(mesh_sizes=[10, 500]) + sec = Section(geometry=geom) + sec.calculate_geometric_properties() + + # compare with stress analysis + my_xx, my_yy = sec.get_my() + my_11, my_22 = sec.get_my() + stress = sec.calculate_stress(mxx=my_xx, myy=my_yy, m11=my_11, m22=my_22) + assert check_yield_index(stress, "sig_zz_mxx") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_myy") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0) + + +def test_yield_internal(): + """Test where the internal material yields first.""" + mat1 = Material( + name="Material_1", + elastic_modulus=0.75, + poissons_ratio=0, + yield_strength=1, + density=1, + color="gold", + ) + mat2 = Material( + name="Material_2", + elastic_modulus=1, + poissons_ratio=0, + yield_strength=1, + density=1, + color="blue", + ) + mat3 = Material( + name="Material 3", + elastic_modulus=3, + poissons_ratio=0, + yield_strength=1, + density=1, + color="red", + ) + + sq1 = rectangular_section(d=100, b=100, material=mat1).align_center() + sq2 = rectangular_section(d=75, b=75, material=mat2).align_center() + sq3 = rectangular_section(d=50, b=50, material=mat3).align_center() + hole = rectangular_section(d=25, b=25).align_center() + compound = (sq1 - sq2) + (sq2 - sq3) + (sq3 - hole) + compound.create_mesh(10) + sec = Section(compound) + sec.calculate_geometric_properties() + + # compare with stress analysis + my_xx, my_yy = sec.get_my() + my_11, my_22 = sec.get_my() + stress = sec.calculate_stress(mxx=my_xx, myy=my_yy, m11=my_11, m22=my_22) + assert check_yield_index(stress, "sig_zz_mxx") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_myy") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0) + assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0) + + +def check_yield_index(stress: StressPost, key: str) -> float: + """Returns the largest yield index. + + Given the output from StressPost object and a dict key representing the type of + stress, returns the largest yield index. + """ + yield_index = 0.0 + + for idx, mat_dict in enumerate(stress.get_stress()): + fy = stress.material_groups[idx].material.yield_strength + sigs = mat_dict[key] + sig_max = max(np.absolute(sigs)) + + yield_index = max(yield_index, sig_max / fy) -# TODO: add more tests + return yield_index From 4e949efc35101e48ca38a55d2321cb2316540b39 Mon Sep 17 00:00:00 2001 From: robbievanleeuwen Date: Thu, 31 Oct 2024 06:38:04 +0000 Subject: [PATCH 3/3] Update features list --- docs/user_guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user_guide.rst b/docs/user_guide.rst index f2637ab5..0cba972a 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -52,7 +52,7 @@ Cross-Section Analysis * ☑ Second moments of area * ☑ Elastic section moduli - * ☐ Yield moment + * ☑ Yield moment * ☑ Radii of gyration * ☑ Plastic centroid * ☑ Plastic section moduli @@ -62,7 +62,7 @@ Cross-Section Analysis * ☑ Second moments of area * ☑ Elastic section moduli - * ☐ Yield moment + * ☑ Yield moment * ☑ Radii of gyration * ☑ Plastic centroid * ☑ Plastic section moduli