diff --git a/docs/examples/advanced/advanced_plot.ipynb b/docs/examples/advanced/advanced_plot.ipynb index 5cef84f4..a8bb8231 100644 --- a/docs/examples/advanced/advanced_plot.ipynb +++ b/docs/examples/advanced/advanced_plot.ipynb @@ -59,7 +59,7 @@ "metadata": {}, "outputs": [], "source": [ - "from sectionproperties.analysis.section import Section\n", + "from sectionproperties.analysis import Section\n", "\n", "geom.create_mesh(mesh_sizes=[5])\n", "sec = Section(geometry=geom)" diff --git a/docs/examples/advanced/rectangle_torsion.ipynb b/docs/examples/advanced/rectangle_torsion.ipynb index 13329cf1..6a286965 100644 --- a/docs/examples/advanced/rectangle_torsion.ipynb +++ b/docs/examples/advanced/rectangle_torsion.ipynb @@ -57,7 +57,7 @@ "outputs": [], "source": [ "from sectionproperties.pre.library import rectangular_section\n", - "from sectionproperties.analysis.section import Section\n", + "from sectionproperties.analysis import Section\n", "\n", "for ar in aspect_ratios:\n", " # calculate rectangle dimensions\n", diff --git a/docs/examples/analysis/stress_analysis.ipynb b/docs/examples/analysis/stress_analysis.ipynb index a95de3dd..e38079b3 100644 --- a/docs/examples/analysis/stress_analysis.ipynb +++ b/docs/examples/analysis/stress_analysis.ipynb @@ -40,7 +40,7 @@ "outputs": [], "source": [ "from sectionproperties.pre.library import rectangular_hollow_section\n", - "from sectionproperties.analysis.section import Section\n", + "from sectionproperties.analysis import Section\n", "\n", "geom = rectangular_hollow_section(d=100, b=150, t=6, r_out=15, n_r=8)\n", "geom.create_mesh(mesh_sizes=[2])\n", diff --git a/docs/examples/geometry/advanced_geometry.ipynb b/docs/examples/geometry/advanced_geometry.ipynb index 19829d97..d5a22aa1 100644 --- a/docs/examples/geometry/advanced_geometry.ipynb +++ b/docs/examples/geometry/advanced_geometry.ipynb @@ -89,7 +89,7 @@ "metadata": {}, "outputs": [], "source": [ - "from sectionproperties.pre.pre import Material\n", + "from sectionproperties.pre import Material\n", "\n", "# just some differing properties\n", "mat1 = Material(\n", diff --git a/docs/examples/materials/composite_analysis.ipynb b/docs/examples/materials/composite_analysis.ipynb index 2945a144..2c0f5036 100644 --- a/docs/examples/materials/composite_analysis.ipynb +++ b/docs/examples/materials/composite_analysis.ipynb @@ -396,6 +396,24 @@ "source": [ "stress.plot_stress(stress=\"vm\")" ] + }, + { + "cell_type": "markdown", + "id": "7586b6c6-0efc-4583-84f9-c5c9acd4a85d", + "metadata": {}, + "source": [ + "We can plot only a specific list of materials by including the `material_list` argument. In the above plot it is difficult to see the stress in the timber so we set `material_list=[timber]`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "417186a7-9e3a-412b-ac60-8704f04b8400", + "metadata": {}, + "outputs": [], + "source": [ + "stress.plot_stress(stress=\"vm\", material_list=[timber])" + ] } ], "metadata": { diff --git a/docs/examples/results/get_stress.ipynb b/docs/examples/results/get_stress.ipynb index d78f6dda..e8f99ca3 100644 --- a/docs/examples/results/get_stress.ipynb +++ b/docs/examples/results/get_stress.ipynb @@ -34,7 +34,7 @@ "outputs": [], "source": [ "from sectionproperties.pre.library import rectangular_hollow_section\n", - "from sectionproperties.analysis.section import Section\n", + "from sectionproperties.analysis import Section\n", "\n", "geom = rectangular_hollow_section(d=100, b=150, t=6, r_out=15, n_r=8)\n", "geom.create_mesh(mesh_sizes=3)\n", diff --git a/docs/examples/results/plot_centroids.ipynb b/docs/examples/results/plot_centroids.ipynb index 1182f887..4ee0c28e 100644 --- a/docs/examples/results/plot_centroids.ipynb +++ b/docs/examples/results/plot_centroids.ipynb @@ -70,7 +70,9 @@ "id": "fd9ea032-9e34-49ce-a700-7705b127d768", "metadata": {}, "source": [ - "## Warping Analysis" + "## Warping Analysis\n", + "\n", + "Note that the title and transparency can be changed by specifying `title` and `alpha` respectively." ] }, { @@ -81,7 +83,7 @@ "outputs": [], "source": [ "sec.calculate_warping_properties()\n", - "sec.plot_centroids()" + "sec.plot_centroids(title=\"Geometric & Warping Centroids\", alpha=0.2)" ] }, { diff --git a/docs/examples/results/plot_stress.ipynb b/docs/examples/results/plot_stress.ipynb index 8ab91565..7d05ea83 100644 --- a/docs/examples/results/plot_stress.ipynb +++ b/docs/examples/results/plot_stress.ipynb @@ -122,6 +122,14 @@ "stress.plot_stress(stress=\"myy_zz\")" ] }, + { + "cell_type": "markdown", + "id": "45167d4b-825e-480b-8dae-b0dd67a06d14", + "metadata": {}, + "source": [ + "The colour bar number formatting can be changed by providing a formatting string `fmt`. The below plot displays numbers to two decimal places." + ] + }, { "cell_type": "code", "execution_count": null, @@ -129,7 +137,7 @@ "metadata": {}, "outputs": [], "source": [ - "stress.plot_stress(stress=\"vy_zx\")" + "stress.plot_stress(stress=\"vy_zx\", fmt=\"{x:.2f}\")" ] }, { @@ -186,6 +194,14 @@ "### Combined Stress Plots" ] }, + { + "cell_type": "markdown", + "id": "1f8e2cf8-dd59-4959-9612-da6cc9f62af9", + "metadata": {}, + "source": [ + "Note that the title and transparency can be changed by specifying `title` and `alpha` respectively." + ] + }, { "cell_type": "code", "execution_count": null, @@ -193,7 +209,7 @@ "metadata": {}, "outputs": [], "source": [ - "stress.plot_stress(stress=\"zz\")" + "stress.plot_stress(stress=\"zz\", title=\"Normal Stress\", alpha=0.2)" ] }, { @@ -206,6 +222,14 @@ "stress.plot_stress(stress=\"zxy\", cmap=\"viridis\", normalize=False)" ] }, + { + "cell_type": "markdown", + "id": "54962f64-1267-4b99-b395-7dddd1657e89", + "metadata": {}, + "source": [ + "The `stress_limits` argument can be used to modify the default limits of the colour bar. Say you wanted to identify regions in which the 11-principal stress exceeded 80 MPa, by setting `stress_limits=(0, 80)`, the regions with stresses outside these limits will not be plotted. The colour bar label can also be modified by setting `colorbar_label`." + ] + }, { "cell_type": "code", "execution_count": null, @@ -213,7 +237,14 @@ "metadata": {}, "outputs": [], "source": [ - "stress.plot_stress(stress=\"11\", cmap=\"viridis\", normalize=False)" + "stress.plot_stress(\n", + " stress=\"11\",\n", + " cmap=\"viridis\",\n", + " stress_limits=(0, 80),\n", + " normalize=False,\n", + " fmt=\"{x:.2f}\",\n", + " colorbar_label=\"Principal Stress [MPa]\",\n", + ")" ] }, { @@ -278,7 +309,9 @@ "metadata": {}, "outputs": [], "source": [ - "stress.plot_stress_vector(stress=\"vy_zxy\", cmap=\"viridis\", normalize=False)" + "stress.plot_stress_vector(\n", + " stress=\"vy_zxy\", cmap=\"viridis\", normalize=False, fmt=\"{x:.2f}\"\n", + ")" ] }, { @@ -288,7 +321,9 @@ "metadata": {}, "outputs": [], "source": [ - "stress.plot_stress_vector(stress=\"zxy\", cmap=\"viridis\", normalize=False)" + "stress.plot_stress_vector(\n", + " stress=\"zxy\", cmap=\"viridis\", normalize=False, colorbar_label=\"Stress [MPa]\"\n", + ")" ] }, { @@ -310,6 +345,100 @@ "source": [ "stress.plot_mohrs_circles(x=0, y=0)" ] + }, + { + "cell_type": "markdown", + "id": "e99d8c55-c0d1-48d1-9c21-e6477c7fefbd", + "metadata": {}, + "source": [ + "## Plot Specific Materials\n", + "\n", + "It is possible to plot the stress contours of a specific list of materials, this is done by providing a desired list of `Material` objects to `material_list`.\n", + "\n", + "The following example will generate a composite section and showcase this feature." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2355f5eb-a5b9-492e-b675-e40e000a86d4", + "metadata": {}, + "outputs": [], + "source": [ + "from sectionproperties.pre import Material\n", + "from sectionproperties.pre.library import rectangular_section\n", + "\n", + "mat_a = Material(\"a\", 1, 0, 1, 1, color=\"b\")\n", + "mat_b = Material(\"b\", 10, 0, 1, 1, color=\"g\")\n", + "mat_c = Material(\"c\", 5, 0, 1, 1, color=\"r\")\n", + "mat_d = Material(\"d\", 2, 0, 1, 1, color=\"y\")\n", + "\n", + "a = rectangular_section(20, 20, mat_a)\n", + "b = rectangular_section(20, 20, mat_b).align_to(a, \"right\")\n", + "c = rectangular_section(20, 20, mat_c).align_to(a, \"top\")\n", + "d = rectangular_section(20, 20, mat_d).align_to(a, \"top\").align_to(a, \"right\")\n", + "geom = a + b + c + d\n", + "geom.create_mesh(10)\n", + "sec = Section(geom)\n", + "sec.plot_mesh()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8730184-8246-47b3-86c5-2435dc49756a", + "metadata": {}, + "outputs": [], + "source": [ + "sec.calculate_geometric_properties()\n", + "stress = sec.calculate_stress(n=10e3, mxx=1e6)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d096d57-9753-4e1d-b827-fd030ae7d827", + "metadata": {}, + "outputs": [], + "source": [ + "stress.plot_stress(stress=\"m_zz\")" + ] + }, + { + "cell_type": "markdown", + "id": "ae80f616-fe41-4722-b0f2-f4429aa03573", + "metadata": {}, + "source": [ + "Suppose we want a higher fidelity understanding of the stress in material A (bottom left), we can set `material_list=[mat_a]` to only display material A in the stress plot." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c78d69dd-7f44-4705-a783-f00da1318612", + "metadata": {}, + "outputs": [], + "source": [ + "stress.plot_stress(stress=\"m_zz\", material_list=[mat_a])" + ] + }, + { + "cell_type": "markdown", + "id": "9396cf6f-1a5f-4cfa-aa89-b59d77e85ff9", + "metadata": {}, + "source": [ + "We can also show both material A and C in the same plot using the same functionality." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c699c36-e9fb-4f80-a05d-654ee2dafd91", + "metadata": {}, + "outputs": [], + "source": [ + "stress.plot_stress(stress=\"m_zz\", material_list=[mat_a, mat_c])" + ] } ], "metadata": { diff --git a/src/sectionproperties/analysis/section.py b/src/sectionproperties/analysis/section.py index a1259d31..4071876f 100644 --- a/src/sectionproperties/analysis/section.py +++ b/src/sectionproperties/analysis/section.py @@ -42,8 +42,8 @@ class Section: geometry (Geometry | CompoundGeometry): Cross-section geometry materials (list[Material]): List of materials in the geometry material_groups (list[MaterialGroup]): List of - class:`~sectionproperties.pre.pre.MaterialGroup` s, which contain the finite - elements and stress results (if applicable) for each defined material + class:`~sectionproperties.pre.pre.MaterialGroup` objects, which contain the + finite elements and stress results (if applicable) for each defined material mesh (dict[str, Any]): Finite element mesh generated by ``triangle`` num_nodes (int): Number of nodes in the finite element mesh elements (list[Tri6]): List of finite element objects describing the @@ -1600,15 +1600,17 @@ def plot_mesh( def plot_centroids( self, + alpha: float = 0.5, title: str = "Centroids", **kwargs, ): - """Plots the calculated centroids over the mesh. + r"""Plots the calculated centroids over the mesh. Plots the elastic centroid, the shear centre, the plastic centroids and the principal axis, if they have been calculated, on top of the finite element mesh. Args: + alpha: Transparency of the mesh outlines: :math:`0 \leq \alpha \leq 1` title: Plot title kwargs: Passed to :func:`~sectionproperties.post.post.plotting_context` @@ -1641,7 +1643,7 @@ def plot_centroids( assert ax # plot the finite element mesh - self.plot_mesh(**dict(kwargs, ax=ax)) + self.plot_mesh(alpha=alpha, **dict(kwargs, ax=ax)) # if the elastic centroid has been calculated try: diff --git a/src/sectionproperties/post/stress_post.py b/src/sectionproperties/post/stress_post.py index 530d5b86..66a121b2 100644 --- a/src/sectionproperties/post/stress_post.py +++ b/src/sectionproperties/post/stress_post.py @@ -19,6 +19,7 @@ if TYPE_CHECKING: from sectionproperties.analysis.section import MaterialGroup, Section + from sectionproperties.pre.pre import Material class StressPost: @@ -57,7 +58,12 @@ def plot_stress( stress: str, title: str | None = None, cmap: str = "coolwarm", + stress_limits: tuple[float, float] | None = None, normalize: bool = True, + fmt: str = "{x:.4e}", + colorbar_label: str = "Stress", + alpha: float = 0.5, + material_list: list[Material] | None = None, **kwargs, ) -> matplotlib.axes.Axes: r"""Plots filled stress contours over the finite element mesh. @@ -68,8 +74,16 @@ def plot_stress( cmap: Matplotlib color map, see https://matplotlib.org/stable/tutorials/colors/colormaps.html for more detail + stress_limits: Custom colorbar stress limits (`sig_min`, `sig_max`), values + outside these limits will appear as white normalize: If set to True, ``CenteredNorm`` is used to scale the colormap, if set to False, the default linear scaling is used + fmt: Number formatting string, see + https://docs.python.org/3/library/string.html + colorbar_label: Colorbar label + alpha: Transparency of the mesh outlines: :math:`0 \leq \alpha \leq 1` + material_list: If specified, only plots materials present in the list. If + set to `None`, plots all materials. kwargs: Passed to :func:`~sectionproperties.post.post.plotting_context` Raises: @@ -267,11 +281,17 @@ def plot_stress( }, } - # populate stresses + # populate stresses and plotted material groups sigs = [] + plotted_material_groups = [] for group in self.material_groups: + # if we are limiting materials to plot, check material is in list + if material_list and group.material not in material_list: + continue + sigs.append(getattr(group.stress_result, stress_dict[stress]["attribute"])) + plotted_material_groups.append(group) # apply title if not title: @@ -290,8 +310,12 @@ def plot_stress( ) # determine minimum and maximum stress values for the contour list - sig_min = min([min(x) for x in sigs]) - sig_max = max([max(x) for x in sigs]) + if stress_limits is None: + sig_min = min([min(x) for x in sigs]) + sig_max = max([max(x) for x in sigs]) + else: + sig_min = stress_limits[0] + sig_max = stress_limits[1] v = np.linspace(start=sig_min, stop=sig_max, num=15, endpoint=True) @@ -306,11 +330,15 @@ def plot_stress( if normalize: norm = CenteredNorm() - # plot the filled contour, looping through the materials - for i, sig in enumerate(sigs): + # plot the filled contour, looping through the plotted material groups + for group, sig in zip(plotted_material_groups, sigs): + # if we are limiting materials to plot, check material is in list + if material_list and group.material not in material_list: + continue + # create and set the mask for the current material mask_array = np.ones(shape=len(self.section.elements), dtype=bool) - mask_array[self.material_groups[i].el_ids] = False + mask_array[group.el_ids] = False triang.set_mask(mask_array) # plot the filled contour @@ -323,11 +351,15 @@ def plot_stress( if trictr: fig.colorbar( - mappable=trictr, label="Stress", format="%.4e", ticks=ticks, cax=cax + mappable=trictr, + label=colorbar_label, + format=fmt, + ticks=ticks, + cax=cax, ) # plot the finite element mesh - self.section.plot_mesh(materials=False, **dict(kwargs, ax=ax)) + self.section.plot_mesh(alpha=alpha, materials=False, **dict(kwargs, ax=ax)) if ax: return ax @@ -340,6 +372,9 @@ def plot_stress_vector( title: str | None = None, cmap: str = "YlOrBr", normalize: bool = False, + fmt: str = "{x:.4e}", + colorbar_label: str = "Stress", + alpha: float = 0.2, **kwargs, ) -> matplotlib.axes.Axes: r"""Plots stress vectors over the finite element mesh. @@ -352,6 +387,10 @@ def plot_stress_vector( detail normalize: If set to True, ``CenteredNorm`` is used to scale the colormap, if set to False, the default linear scaling is used + fmt: Number formatting string, see + https://docs.python.org/3/library/string.html + colorbar_label: Colorbar label + alpha: Transparency of the mesh outlines: :math:`0 \leq \alpha \leq 1` kwargs: Passed to :func:`~sectionproperties.post.post.plotting_context` Raises: @@ -476,11 +515,11 @@ def plot_stress_vector( cax = divider.append_axes(position="right", size="5%", pad=0.1) fig.colorbar( - mappable=quiv, label="Stress", format="%.4e", ticks=v1, cax=cax + mappable=quiv, label=colorbar_label, format=fmt, ticks=v1, cax=cax ) # plot the finite element mesh - self.section.plot_mesh(materials=False, **dict(kwargs, ax=ax)) + self.section.plot_mesh(alpha=alpha, materials=False, **dict(kwargs, ax=ax)) if ax: return ax