diff --git a/docs/examples/geometry/section_library.ipynb b/docs/examples/geometry/section_library.ipynb index d3a38e9a..1c9abd3c 100644 --- a/docs/examples/geometry/section_library.ipynb +++ b/docs/examples/geometry/section_library.ipynb @@ -282,6 +282,256 @@ "cell_type": "markdown", "id": "26", "metadata": {}, + "source": [ + "## Rectangular Timber CLT Section\n", + "\n", + "The following example calculates the geometric properties of a rectangular timber crosslaminated section." + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "### Import Modules\n", + "\n", + "We start by importing the [timber_rectangular_section()](../../gen/sectionproperties.pre.library.timber_sections.timber_rectangular_section.rst#sectionproperties.pre.library.timber_sections.timber_rectangular_section) function from the section library, and the [Material()](../../gen/sectionproperties.pre.pre.Material.rst#sectionproperties.pre.pre.Material) object to define our timber material." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "from sectionproperties.analysis import Section\n", + "from sectionproperties.pre import Material\n", + "from sectionproperties.pre.library import clt_rectangular_section" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "### Create Geometry\n", + "\n", + "Create a 120 deep by 1000 wide crosslaminated timber slab.\n", + "\n", + "The following material properties are used:\n", + "\n", + "**SPF-Timber - Parallel-to-grain**\n", + " \n", + "- Elastic modulus = 9500 MPa\n", + "- Poisson's ratio = 0.35\n", + "- Density = 4400 kg/m$^3$\n", + "- Yield Strengh = 5.5 MPa\n", + "\n", + "**SPF-Timber - Perpendicular-to-grain**\n", + " \n", + "- Elastic modulus = 317 MPa\n", + "- Poisson's ratio = 0.35\n", + "- Density = 4400 kg/m$^3$\n", + "- Yield Strengh = 5.5 MPa" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "timber0 = Material(\n", + " name=\"Timber0\",\n", + " elastic_modulus=9.5e3,\n", + " poissons_ratio=0.35,\n", + " density=4.4e-7,\n", + " yield_strength=5.5,\n", + " color=\"burlywood\",\n", + ")\n", + "\n", + "timber90 = Material(\n", + " name=\"Timber90\",\n", + " elastic_modulus=317,\n", + " poissons_ratio=0.35,\n", + " density=4.4e-7,\n", + " yield_strength=5.5,\n", + " color=\"orange\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "### Create the geometry - Major (x-) axis bending" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "geom_maj = clt_rectangular_section(\n", + " d=[40, 40, 40], lay_orient=[timber0, timber90, timber0], b=1000\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "#### Create Mesh and ``Section`` object\n", + "\n", + "Create a mesh with a mesh size of 200 mm$^2$ and plot the mesh." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "geom_maj.create_mesh(mesh_sizes=[200])\n", + "sec_maj = Section(geometry=geom_maj)\n", + "sec_maj.plot_mesh()" + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "#### Perform an Analysis\n", + "\n", + "We perform only a geometric analysis on the timber CLT section." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36", + "metadata": {}, + "outputs": [], + "source": [ + "sec_maj.calculate_geometric_properties()" + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "#### Calculate Gross Effective Moment of Inertia\n", + "\n", + "We can calculate the gross effective moment of inertia by obtaining the flexural rigidity ($\\sum E.I$) of the section and dividing it by the elastic modulus of the reference timber (i.e. Timber0)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "ei_maj = sec_maj.get_eic(e_ref=timber0)\n", + "print(f\"I_eff,x,major = {ei_maj[0]:.3e} mm4\")" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "### Create the geometry - Minor (z-) axis bending" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "geom_min = clt_rectangular_section(\n", + " d=[40, 40, 40], lay_orient=[timber90, timber0, timber90], b=1000\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "#### Create Mesh and ``Section`` object\n", + "\n", + "Create a mesh with a mesh size of 200 mm$^2$ and plot the mesh." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "geom_min.create_mesh(mesh_sizes=[200])\n", + "sec_min = Section(geometry=geom_min)\n", + "sec_min.plot_mesh()" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "#### Perform an Analysis\n", + "\n", + "We perform only a geometric analysis on the timber CLT section." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "sec_min.calculate_geometric_properties()" + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": {}, + "source": [ + "#### Calculate Gross Effective Moment of Inertia\n", + "\n", + "We can calculate the gross effective moment of inertia by obtaining the flexural rigidity ($\\sum E.I$) of the section and dividing it by the elastic modulus of the reference timber (i.e. Timber0)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "ei_min = sec_min.get_eic(e_ref=timber0)\n", + "print(f\"I_eff,x,minor = {ei_min[0]:.3e} mm4\")" + ] + }, + { + "cell_type": "markdown", + "id": "47", + "metadata": {}, "source": [ "## Rectangular Concrete Section\n", "\n", @@ -290,7 +540,7 @@ }, { "cell_type": "markdown", - "id": "27", + "id": "48", "metadata": {}, "source": [ "### Import Modules\n", @@ -301,7 +551,7 @@ { "cell_type": "code", "execution_count": null, - "id": "28", + "id": "49", "metadata": {}, "outputs": [], "source": [ @@ -311,7 +561,7 @@ }, { "cell_type": "markdown", - "id": "29", + "id": "50", "metadata": {}, "source": [ "### Create Geometry\n", @@ -344,7 +594,7 @@ { "cell_type": "code", "execution_count": null, - "id": "30", + "id": "51", "metadata": {}, "outputs": [], "source": [ @@ -392,7 +642,7 @@ }, { "cell_type": "markdown", - "id": "31", + "id": "52", "metadata": {}, "source": [ "### Create Mesh and ``Section`` object\n", @@ -403,7 +653,7 @@ { "cell_type": "code", "execution_count": null, - "id": "32", + "id": "53", "metadata": {}, "outputs": [], "source": [ @@ -414,7 +664,7 @@ }, { "cell_type": "markdown", - "id": "33", + "id": "54", "metadata": {}, "source": [ "### Perform an Analysis\n", @@ -425,7 +675,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34", + "id": "55", "metadata": {}, "outputs": [], "source": [ @@ -434,7 +684,7 @@ }, { "cell_type": "markdown", - "id": "35", + "id": "56", "metadata": {}, "source": [ "### Calculate Gross Effective Moment of Inertia\n", @@ -445,13 +695,13 @@ { "cell_type": "code", "execution_count": null, - "id": "36", + "id": "57", "metadata": {}, "outputs": [], "source": [ "ei = sec.get_eic(e_ref=concrete)\n", - "print(f\"I_eff = {ei[0]:.3e} mm6\")\n", - "print(f\"I_rec = {(300 * 600**3 / 12):.3e} mm6\")" + "print(f\"I_eff = {ei[0]:.3e} mm4\")\n", + "print(f\"I_rec = {(300 * 600**3 / 12):.3e} mm4\")" ] } ], @@ -471,7 +721,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.17" + "version": "3.12.8" } }, "nbformat": 4, diff --git a/docs/user_guide/geometry.rst b/docs/user_guide/geometry.rst index 930b8a62..938905a8 100644 --- a/docs/user_guide/geometry.rst +++ b/docs/user_guide/geometry.rst @@ -315,6 +315,16 @@ Concrete Sections ~sectionproperties.pre.library.concrete_sections.double_lift_core_b ~sectionproperties.pre.library.concrete_sections.stairwell +.. _label-timber-library: + +Timber Section +"""""""""""""" + +.. autosummary:: + :nosignatures: + + ~sectionproperties.pre.library.timber_sections.clt_rectangular_section + .. _label-bridge-library: Bridge Sections diff --git a/src/sectionproperties/pre/library/__init__.py b/src/sectionproperties/pre/library/__init__.py index d089e06f..a8c4cafd 100644 --- a/src/sectionproperties/pre/library/__init__.py +++ b/src/sectionproperties/pre/library/__init__.py @@ -70,3 +70,4 @@ tee_section, zed_section, ) +from sectionproperties.pre.library.timber_sections import clt_rectangular_section diff --git a/src/sectionproperties/pre/library/timber_sections.py b/src/sectionproperties/pre/library/timber_sections.py new file mode 100644 index 00000000..a11b3364 --- /dev/null +++ b/src/sectionproperties/pre/library/timber_sections.py @@ -0,0 +1,49 @@ +"""Timber sections library.""" + +from __future__ import annotations + +import sectionproperties.pre.geometry as geometry +import sectionproperties.pre.library.primitive_sections as primitive_sections +import sectionproperties.pre.pre as pre + + +def clt_rectangular_section( + d: list[float], lay_orient: list[pre.Material], b: float +) -> geometry.CompoundGeometry: + """Constructs a timber rectangular section. + + Constructs a timber rectangular section of depth ``d`` and width ``b``. + + .. note:: + + Args: + d: Timber layer section thickness + lay_orient: A list of materials for each layer from top to bottom + defined by the user. + b: Timber section width + + Raises: + ValueError: Geometry generation failed + + Returns: + Timber rectangular section geometry + + Example: + The following example creates a 120mm CLT cross-section. + """ + layer_geom: list[geometry.Geometry] = [] + for idx in range(len(d)): + di = float(d[idx]) + layer = lay_orient[idx] + + timb_mat = layer + + # create rectangular timber geometry + layer = primitive_sections.rectangular_section(d=di, b=b, material=timb_mat) + offset = -d[idx] * (idx + 1) + layer = layer.shift_section(y_offset=offset) + + layer_geom.append(layer) + + # create compound geometry + return geometry.CompoundGeometry(geoms=layer_geom) diff --git a/tests/section_library/test_timber_sections.py b/tests/section_library/test_timber_sections.py new file mode 100644 index 00000000..c6068176 --- /dev/null +++ b/tests/section_library/test_timber_sections.py @@ -0,0 +1,66 @@ +"""Tests for the timber sections library.""" + +from __future__ import annotations + +import pytest +import pytest_check as check + +import sectionproperties.pre.library.timber_sections as ts +import sectionproperties.pre.pre as pre + +r_tol = 1e-6 + + +# material setup +@pytest.fixture +def get_materials() -> tuple[pre.Material, pre.Material]: + """Creates a timber material parallel and perpendicular-to-grain. + + Returns: + Material objects + """ + timb_mat0 = pre.Material( + name="Timber E0", + elastic_modulus=9.5e3, + poissons_ratio=0.35, + density=4.4e-7, + yield_strength=5.5, + color="burlywood", + ) + + timb_mat90 = pre.Material( + name="Timber90", + elastic_modulus=317, + poissons_ratio=0.35, + density=4.4e-7, + yield_strength=5.5, + color="orange", + ) + + return timb_mat0, timb_mat90 + + +def test_timber_clt_rectangular_section(get_materials): + """Tests the timber clt_rectangular_section() method.""" + timber0, timber90 = get_materials + + rect = ts.clt_rectangular_section( + d=[40, 40, 40], lay_orient=[timber0, timber90, timber0], b=1000 + ) + + # check geometry is created correctly + timb0_area = 0 + timb90_area = 0 + + for geom in rect.geoms: + if geom.material == timber0: + timb0_area += geom.calculate_area() + elif geom.material == timber90: + timb90_area += geom.calculate_area() + + actual_timb0_area = 2 * 40 * 1000 + actual_timb90_area = 40 * 1000 + + # check areas + check.almost_equal(timb0_area, actual_timb0_area) + check.almost_equal(timb90_area, actual_timb90_area)