from __future__ import annotations
from dataclasses import dataclass, field
from typing import List, Optional, TYPE_CHECKING
import warnings
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.tri as tri
import matplotlib.cm as cm
from matplotlib.colors import CenteredNorm
from matplotlib.collections import PatchCollection
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
from mpl_toolkits import mplot3d
from scipy.interpolate import interp1d
from rich.console import Console
from rich.table import Table
from concreteproperties.post import plotting_context
from sectionproperties.pre.geometry import CompoundGeometry
if TYPE_CHECKING:
import matplotlib
from concreteproperties.concrete_section import ConcreteSection
from concreteproperties.analysis_section import AnalysisSection
from sectionproperties.pre.geometry import Geometry
[docs]@dataclass
class ConcreteProperties:
"""Class for storing gross concrete section properties.
All properties with an `e_` preceding the property are multiplied by the elastic
modulus. In order to obtain transformed properties, call the
:meth:`~concreteproperties.concrete_section.ConcreteSection.get_transformed_gross_properties`
method.
"""
# section areas
total_area: float = 0
concrete_area: float = 0
steel_area: float = 0
e_a: float = 0
# section mass
mass: float = 0
# section perimeter
perimeter: float = 0
# first moments of area
e_qx: float = 0
e_qy: float = 0
# centroids
cx: float = 0
cy: float = 0
# second moments of area
e_ixx_g: float = 0
e_iyy_g: float = 0
e_ixy_g: float = 0
e_ixx_c: float = 0
e_iyy_c: float = 0
e_ixy_c: float = 0
e_i11: float = 0
e_i22: float = 0
# principal axis angle
phi: float = 0
# section moduli
e_zxx_plus: float = 0
e_zxx_minus: float = 0
e_zyy_plus: float = 0
e_zyy_minus: float = 0
e_z11_plus: float = 0
e_z11_minus: float = 0
e_z22_plus: float = 0
e_z22_minus: float = 0
# plastic properties
squash_load: float = 0
tensile_load: float = 0
axial_pc_x: float = 0
axial_pc_y: float = 0
conc_ultimate_strain: float = 0
[docs] def print_results(
self,
fmt: Optional[str] = "8.6e",
):
"""Prints the gross concrete section properties to the terminal.
:param fmt: Number format
:type fmt: Optional[str]
"""
table = Table(title="Gross Concrete Section Properties")
table.add_column("Property", justify="left", style="cyan", no_wrap=True)
table.add_column("Value", justify="right", style="green")
table.add_row("Total Area", "{:>{fmt}}".format(self.total_area, fmt=fmt))
table.add_row("Concrete Area", "{:>{fmt}}".format(self.concrete_area, fmt=fmt))
table.add_row("Steel Area", "{:>{fmt}}".format(self.steel_area, fmt=fmt))
table.add_row("Axial Rigidity (EA)", "{:>{fmt}}".format(self.e_a, fmt=fmt))
table.add_row("Mass (per unit length)", "{:>{fmt}}".format(self.mass, fmt=fmt))
table.add_row("Perimeter", "{:>{fmt}}".format(self.perimeter, fmt=fmt))
table.add_row("E.Qx", "{:>{fmt}}".format(self.e_qx, fmt=fmt))
table.add_row("E.Qy", "{:>{fmt}}".format(self.e_qy, fmt=fmt))
table.add_row("x-Centroid", "{:>{fmt}}".format(self.cx, fmt=fmt))
table.add_row("y-Centroid", "{:>{fmt}}".format(self.cy, fmt=fmt))
table.add_row("E.Ixx_g", "{:>{fmt}}".format(self.e_ixx_g, fmt=fmt))
table.add_row("E.Iyy_g", "{:>{fmt}}".format(self.e_iyy_g, fmt=fmt))
table.add_row("E.Ixy_g", "{:>{fmt}}".format(self.e_ixy_g, fmt=fmt))
table.add_row("E.Ixx_c", "{:>{fmt}}".format(self.e_ixx_c, fmt=fmt))
table.add_row("E.Iyy_c", "{:>{fmt}}".format(self.e_iyy_c, fmt=fmt))
table.add_row("E.Ixy_c", "{:>{fmt}}".format(self.e_ixy_c, fmt=fmt))
table.add_row("E.I11", "{:>{fmt}}".format(self.e_i11, fmt=fmt))
table.add_row("E.I22", "{:>{fmt}}".format(self.e_i22, fmt=fmt))
table.add_row("Principal Axis Angle", "{:>{fmt}}".format(self.phi, fmt=fmt))
table.add_row("E.Zxx+", "{:>{fmt}}".format(self.e_zxx_plus, fmt=fmt))
table.add_row("E.Zxx-", "{:>{fmt}}".format(self.e_zxx_minus, fmt=fmt))
table.add_row("E.Zyy+", "{:>{fmt}}".format(self.e_zyy_plus, fmt=fmt))
table.add_row("E.Zyy-", "{:>{fmt}}".format(self.e_zyy_minus, fmt=fmt))
table.add_row("E.Z11+", "{:>{fmt}}".format(self.e_z11_plus, fmt=fmt))
table.add_row("E.Z11-", "{:>{fmt}}".format(self.e_z11_minus, fmt=fmt))
table.add_row("E.Z22+", "{:>{fmt}}".format(self.e_z22_plus, fmt=fmt))
table.add_row("E.Z22-", "{:>{fmt}}".format(self.e_z22_minus, fmt=fmt))
table.add_row("Squash Load", "{:>{fmt}}".format(self.squash_load, fmt=fmt))
table.add_row("Tensile Load", "{:>{fmt}}".format(self.tensile_load, fmt=fmt))
table.add_row(
"x-Axial Plastic Centroid", "{:>{fmt}}".format(self.axial_pc_x, fmt=fmt)
)
table.add_row(
"y-Axial Plastic Centroid", "{:>{fmt}}".format(self.axial_pc_y, fmt=fmt)
)
table.add_row(
"Ultimate Concrete Strain",
"{:>{fmt}}".format(self.conc_ultimate_strain, fmt=fmt),
)
console = Console()
console.print(table)
[docs]@dataclass
class CrackedResults:
r"""Class for storing cracked concrete section properties.
All properties with an `e_` preceding the property are multiplied by the elastic
modulus. In order to obtain transformed properties, call the
:meth:`~concreteproperties.results.CrackedResults.calculate_transformed_properties`
method.
:param float theta: Angle (in radians) the neutral axis makes with the horizontal
axis (:math:`-\pi \leq \theta \leq \pi`)
"""
theta: float
m_cr: float = 0
d_nc: float = 0
cracked_geometries: List[Geometry] = field(default_factory=list, repr=False)
e_a_cr: float = 0
e_qx_cr: float = 0
e_qy_cr: float = 0
cx: float = 0
cy: float = 0
e_ixx_g_cr: float = 0
e_iyy_g_cr: float = 0
e_ixy_g_cr: float = 0
e_ixx_c_cr: float = 0
e_iyy_c_cr: float = 0
e_ixy_c_cr: float = 0
e_iuu_cr: float = 0
# transformed properties
elastic_modulus_ref: float = None
a_cr: float = None
qx_cr: float = None
qy_cr: float = None
ixx_g_cr: float = None
iyy_g_cr: float = None
ixy_g_cr: float = None
ixx_c_cr: float = None
iyy_c_cr: float = None
ixy_c_cr: float = None
iuu_cr: float = None
[docs] def plot_cracked_geometries(
self,
title: Optional[str] = "Cracked Geometries",
**kwargs,
) -> matplotlib.axes.Axes:
"""Plots the geometries that remain (are in compression or are steel) after a
cracked analysis.
:param title: Plot title
:type title: Optional[str]
:param kwargs: Passed to
:meth:`~sectionproperties.pre.geometry.CompoundGeometry.plot_geometry`
:return: Matplotlib axes object
:rtype: :class:`matplotlib.axes.Axes`
"""
return CompoundGeometry(self.cracked_geometries).plot_geometry(
title=title, **kwargs
)
[docs] def print_results(
self,
fmt: Optional[str] = "8.6e",
):
"""Prints the cracked concrete section properties to the terminal.
:param fmt: Number format
:type fmt: Optional[str]
"""
table = Table(title="Cracked Concrete Section Properties")
table.add_column("Property", justify="left", style="cyan", no_wrap=True)
table.add_column("Value", justify="right", style="green")
table.add_row("theta", "{:>{fmt}}".format(self.theta, fmt=fmt))
if self.elastic_modulus_ref:
table.add_row(
"E_ref", "{:>{fmt}}".format(self.elastic_modulus_ref, fmt=fmt)
)
table.add_row("M_cr", "{:>{fmt}}".format(self.m_cr, fmt=fmt))
table.add_row("d_nc", "{:>{fmt}}".format(self.d_nc, fmt=fmt))
if self.a_cr:
table.add_row("A_cr", "{:>{fmt}}".format(self.a_cr, fmt=fmt))
table.add_row("E.A_cr", "{:>{fmt}}".format(self.e_a_cr, fmt=fmt))
if self.qx_cr:
table.add_row("Qx_cr", "{:>{fmt}}".format(self.qx_cr, fmt=fmt))
table.add_row("Qy_cr", "{:>{fmt}}".format(self.qy_cr, fmt=fmt))
table.add_row("E.Qx_cr", "{:>{fmt}}".format(self.e_qx_cr, fmt=fmt))
table.add_row("E.Qy_cr", "{:>{fmt}}".format(self.e_qy_cr, fmt=fmt))
table.add_row("x-Centroid", "{:>{fmt}}".format(self.cx, fmt=fmt))
table.add_row("y-Centroid", "{:>{fmt}}".format(self.cy, fmt=fmt))
if self.ixx_g_cr:
table.add_row("Ixx_g_cr", "{:>{fmt}}".format(self.ixx_g_cr, fmt=fmt))
table.add_row("Iyy_g_cr", "{:>{fmt}}".format(self.iyy_g_cr, fmt=fmt))
table.add_row("Ixy_g_cr", "{:>{fmt}}".format(self.ixy_g_cr, fmt=fmt))
table.add_row("Ixx_c_cr", "{:>{fmt}}".format(self.ixx_c_cr, fmt=fmt))
table.add_row("Iyy_c_cr", "{:>{fmt}}".format(self.iyy_c_cr, fmt=fmt))
table.add_row("Ixy_c_cr", "{:>{fmt}}".format(self.ixy_c_cr, fmt=fmt))
table.add_row("Iuu_cr", "{:>{fmt}}".format(self.iuu_cr, fmt=fmt))
table.add_row("E.Ixx_g_cr", "{:>{fmt}}".format(self.e_ixx_g_cr, fmt=fmt))
table.add_row("E.Iyy_g_cr", "{:>{fmt}}".format(self.e_iyy_g_cr, fmt=fmt))
table.add_row("E.Ixy_g_cr", "{:>{fmt}}".format(self.e_ixy_g_cr, fmt=fmt))
table.add_row("E.Ixx_c_cr", "{:>{fmt}}".format(self.e_ixx_c_cr, fmt=fmt))
table.add_row("E.Iyy_c_cr", "{:>{fmt}}".format(self.e_iyy_c_cr, fmt=fmt))
table.add_row("E.Ixy_c_cr", "{:>{fmt}}".format(self.e_ixy_c_cr, fmt=fmt))
table.add_row("E.Iuu_cr", "{:>{fmt}}".format(self.e_iuu_cr, fmt=fmt))
console = Console()
console.print(table)
[docs]@dataclass
class MomentCurvatureResults:
r"""Class for storing moment curvature results.
:param float theta: Angle (in radians) the neutral axis makes with the horizontal
axis (:math:`-\pi \leq \theta \leq \pi`)
:var kappa: List of curvatures
:vartype kappa: List[float]
:var moment: List of bending moments
:vartype moment: List[float]
:var failure_geometry: Geometry object of the region of the cross-section that
failed, ending the moment curvature analysis
:vartype failure_geometry: :class:`sectionproperties.pre.geometry.Geometry`
"""
# results
theta: float
kappa: List[float] = field(default_factory=list)
moment: List[float] = field(default_factory=list)
failure_geometry: Geometry = field(init=False)
# for analysis
_n_i: float = field(default=0, repr=False)
_m_i: float = field(default=0, repr=False)
_failure: bool = field(default=False, repr=False)
def __post_init__(
self,
):
self.kappa.append(0)
self.moment.append(0)
[docs] def plot_results(
self,
m_scale: Optional[float] = 1e-6,
**kwargs,
) -> matplotlib.axes.Axes:
"""Plots the moment curvature results.
:param m_scale: Scaling factor to apply to bending moment
:type m_scale: Optional[float]
:param kwargs: Passed to :func:`~concreteproperties.post.plotting_context`
:return: Matplotlib axes object
:rtype: :class:`matplotlib.axes.Axes`
"""
# scale moments
moments = np.array(self.moment) * m_scale
# create plot and setup the plot
with plotting_context(title="Moment-Curvature", **kwargs) as (
fig,
ax,
):
ax.plot(self.kappa, moments, "o-", markersize=3)
plt.xlabel("Curvature")
plt.ylabel("Moment")
plt.grid(True)
return ax
[docs] @staticmethod
def plot_multiple_results(
moment_curvature_results: List[MomentCurvatureResults],
labels: List[str],
m_scale: Optional[float] = 1e-6,
**kwargs,
) -> matplotlib.axes.Axes:
"""Plots multiple moment curvature results.
:param moment_curvature_results: List of moment curvature results objects
:type moment_interaction_results:
List[:class:`~concreteproperties.results.MomentCurvatureResults`]
:param labels: List of labels for each moment curvature diagram
:type labels: List[str]
:param float m_scale: Scaling factor to apply to bending moment
:type m_scale: Optional[float]
:param kwargs: Passed to :func:`~concreteproperties.post.plotting_context`
:return: Matplotlib axes object
:rtype: :class:`matplotlib.axes.Axes`
"""
# create plot and setup the plot
with plotting_context(title="Moment-Curvature", **kwargs) as (
fig,
ax,
):
# for each M-k curve
for idx, mk_result in enumerate(moment_curvature_results):
# scale results
kappas = np.array(mk_result.kappa)
moments = np.array(mk_result.moment) * m_scale
ax.plot(kappas, moments, "o-", label=labels[idx], markersize=3)
plt.xlabel("Curvature")
plt.ylabel("Moment")
plt.grid(True)
# if there is more than one curve show legend
if idx > 0:
ax.legend(loc="center left", bbox_to_anchor=(1, 0.5))
return ax
[docs] def plot_failure_geometry(
self,
title: Optional[str] = "Failure Geometry",
**kwargs,
) -> matplotlib.axes.Axes:
"""Plots the geometry that fails in the moment curvature analysis.
:param title: Plot title
:type title: Optional[str]
:param kwargs: Passed to
:meth:`~sectionproperties.pre.geometry.CompoundGeometry.plot_geometry`
:return: Matplotlib axes object
:rtype: :class:`matplotlib.axes.Axes`
"""
return self.failure_geometry.plot_geometry(title=title, **kwargs)
[docs] def get_curvature(
self,
moment: float,
) -> float:
"""Given a moment, uses the moment-curvature results to interpolate a curvature.
Raises a ValueError if supplied moment is outside bounds of moment-curvature
results.
:param float moment: Bending moment at which to obtain curvature
:return: Curvature
:rtype: float
"""
# check moment is within bounds of results
m_min = min(self.moment)
m_max = max(self.moment)
if moment > m_max or moment < m_min:
raise ValueError(
"moment must be within the bounds of the moment-curvature results."
)
f_kappa = interp1d(
x=self.moment,
y=self.kappa,
kind="linear",
)
return float(f_kappa(moment))
[docs]@dataclass
class UltimateBendingResults:
r"""Class for storing ultimate bending results.
:param float theta: Angle (in radians) the neutral axis makes with the horizontal
axis (:math:`-\pi \leq \theta \leq \pi`)
:var float d_n: Ultimate neutral axis depth
:var float k_u: Neutral axis parameter *(d_n / d)*
:var float n: Resultant axial force
:var float m_x: Resultant bending moment about the x-axis
:var float m_y: Resultant bending moment about the y-axis
:var float m_u: Resultant bending moment about the u-axis
"""
# bending angle
theta: float
# ultimate neutral axis depth
d_n: float = None
k_u: float = None
# resultant actions
n: float = None
m_x: float = None
m_y: float = None
m_u: float = None
[docs] def print_results(
self,
fmt: Optional[str] = "8.6e",
):
"""Prints the ultimate bending results to the terminal.
:param fmt: Number format
:type fmt: Optional[str]
"""
table = Table(title="Ultimate Bending Results")
table.add_column("Property", justify="left", style="cyan", no_wrap=True)
table.add_column("Value", justify="right", style="green")
table.add_row("Bending Angle - theta", "{:>{fmt}}".format(self.theta, fmt=fmt))
table.add_row("Neutral Axis Depth - d_n", "{:>{fmt}}".format(self.d_n, fmt=fmt))
table.add_row(
"Neutral Axis Parameter- k_u", "{:>{fmt}}".format(self.k_u, fmt=fmt)
)
table.add_row("Axial Force", "{:>{fmt}}".format(self.n, fmt=fmt))
table.add_row("Bending Capacity - m_x", "{:>{fmt}}".format(self.m_x, fmt=fmt))
table.add_row("Bending Capacity - m_y", "{:>{fmt}}".format(self.m_y, fmt=fmt))
table.add_row("Bending Capacity - m_u", "{:>{fmt}}".format(self.m_u, fmt=fmt))
console = Console()
console.print(table)
[docs]@dataclass
class MomentInteractionResults:
"""Class for storing moment interaction results.
:var n: List of axial forces
:vartype n: List[float]
:var m: List of bending moments
:vartype m: List[float]
"""
n: List[float] = field(default_factory=list)
m: List[float] = field(default_factory=list)
[docs] def plot_diagram(
self,
n_scale: Optional[float] = 1e-3,
m_scale: Optional[float] = 1e-6,
**kwargs,
) -> matplotlib.axes.Axes:
"""Plots a moment interaction diagram.
:param n_scale: Scaling factor to apply to axial force
:type n_scale: Optional[float]
:param n_scale: Scaling factor to apply to axial force
:type m_scale: Optional[float]
:param kwargs: Passed to :func:`~concreteproperties.post.plotting_context`
:return: Matplotlib axes object
:rtype: :class:`matplotlib.axes.Axes`
"""
# create plot and setup the plot
with plotting_context(title="Moment Interaction Diagram", **kwargs) as (
fig,
ax,
):
# scale results
forces = np.array(self.n) * n_scale
moments = np.array(self.m) * m_scale
ax.plot(moments, forces, "o-", markersize=3)
plt.xlabel("Bending Moment")
plt.ylabel("Axial Force")
plt.grid(True)
return ax
[docs] @staticmethod
def plot_multiple_diagrams(
moment_interaction_results: List[MomentInteractionResults],
labels: List[str],
n_scale: Optional[float] = 1e-3,
m_scale: Optional[float] = 1e-6,
**kwargs,
) -> matplotlib.axes.Axes:
"""Plots multiple moment interaction diagrams.
:param moment_interaction_results: List of moment interaction results objects
:type moment_interaction_results:
List[:class:`~concreteproperties.results.MomentInteractionResults`]
:param labels: List of labels for each moment interaction diagram
:type labels: List[str]
:param float n_scale: Scaling factor to apply to axial force
:type n_scale: Optional[float]
:param float m_scale: Scaling factor to apply to bending moment
:type m_scale: Optional[float]
:param kwargs: Passed to :func:`~concreteproperties.post.plotting_context`
:return: Matplotlib axes object
:rtype: :class:`matplotlib.axes.Axes`
"""
# create plot and setup the plot
with plotting_context(title="Moment Interaction Diagram", **kwargs) as (
fig,
ax,
):
# for each M-N curve
for idx, mi_result in enumerate(moment_interaction_results):
# scale results
forces = np.array(mi_result.n) * n_scale
moments = np.array(mi_result.m) * m_scale
ax.plot(moments, forces, "o-", label=labels[idx], markersize=3)
plt.xlabel("Bending Moment")
plt.ylabel("Axial Force")
plt.grid(True)
# if there is more than one curve show legend
if idx > 0:
ax.legend(loc="center left", bbox_to_anchor=(1, 0.5))
return ax
[docs]@dataclass
class BiaxialBendingResults:
"""Class for storing biaxial bending results.
:param float n: Net axial force
:var m_x: List of bending moments about the x-axis
:vartype m_x: List[float]
:var m_y: List of bending moments about the y-axis
:vartype m_y: List[float]
"""
n: float
m_x: List[float] = field(default_factory=list)
m_y: List[float] = field(default_factory=list)
[docs] def plot_diagram(
self,
m_scale: Optional[float] = 1e-6,
**kwargs,
) -> matplotlib.axes.Axes:
"""Plots a biaxial bending diagram.
:param m_scale: Scaling factor to apply to bending moment
:type m_scale: Optional[float]
:param kwargs: Passed to :func:`~concreteproperties.post.plotting_context`
:return: Matplotlib axes object
:rtype: :class:`matplotlib.axes.Axes`
"""
# create plot and setup the plot
with plotting_context(
title=f"Biaxial Bending Diagram, $N = {self.n:.3e}$", **kwargs
) as (
fig,
ax,
):
# scale results
m_x = np.array(self.m_x) * m_scale
m_y = np.array(self.m_y) * m_scale
ax.plot(m_x, m_y, "o-", markersize=3)
plt.xlabel("Bending Moment $M_x$")
plt.ylabel("Bending Moment $M_y$")
plt.grid(True)
return ax
[docs] @staticmethod
def plot_multiple_diagrams(
biaxial_bending_results: List[BiaxialBendingResults],
n_scale: Optional[float] = 1e-3,
m_scale: Optional[float] = 1e-6,
) -> matplotlib.axes.Axes:
"""Plots multiple biaxial bending diagrams in a 3D plot.
:param biaxial_bending_results: List of biaxial bending results objects
:type biaxial_bending_results:
List[:class:`~concreteproperties.results.BiaxialBendingResults`]
:param float n_scale: Scaling factor to apply to axial force
:type n_scale: Optional[float]
:param float m_scale: Scaling factor to apply to bending moment
:type m_scale: Optional[float]
:return: Matplotlib axes object
:rtype: :class:`matplotlib.axes.Axes`
"""
# make 3d plot
fig = plt.figure()
ax = plt.axes(projection="3d")
# for each curve
for bb_result in biaxial_bending_results:
# scale results
n_list = bb_result.n * n_scale * np.ones(len(bb_result.m_x))
m_x_list = np.array(bb_result.m_x) * m_scale
m_y_list = np.array(bb_result.m_y) * m_scale
ax.plot3D(m_x_list, m_y_list, n_list, "-")
ax.set_xlabel("Bending Moment $M_x$")
ax.set_ylabel("Bending Moment $M_y$")
ax.set_zlabel("Axial Force $N$")
plt.show()
return ax
[docs]@dataclass
class StressResult:
"""Class for storing stress results.
:var concrete_analysis_sections: List of concrete analysis section objects
present in the stress analysis, which can be visualised by calling the
:meth:`~concreteproperties.analysis_section.AnalysisSection.plot_mesh` or
:meth:`~concreteproperties.analysis_section.AnalysisSection.plot_shape`
:vartype concrete_analysis_sections:
List[:class:`~concreteproperties.analysis_section.AnalysisSection`]
:var concrete_stresses: List of concrete stresses at the nodes of each concrete
analysis section
:vartype concrete_stresses: List[:class:`numpy.ndarray`]
:var concrete_forces: List of net forces for each concrete analysis section and its
lever arm to the neutral axis (``force``, ``lever_arm``)
:vartype concrete_forces: List[Tuple[float]]
:var steel_geometries: List of steel geometry objects present in the stress analysis
:vartype steel_geometries: List[:class:`sectionproperties.pre.geometry.Geometry`]
:var steel_stresses: List of steel stresses for each steel geometry
:vartype steel_stresses: List[float]
:var steel_strains: List of steel strains for each steel geometry
:vartype steel_strains: List[float]
:var steel_forces: List of net forces for each steel geometry and its lever arm to
the neutral axis (``force``, ``lever_arm``)
:vartype steel_forces: List[Tuple[float]]
"""
concrete_section: ConcreteSection
concrete_analysis_sections: List[AnalysisSection]
concrete_stresses: List[np.ndarray]
concrete_forces: List[Tuple[float]]
steel_geometries: List[Geometry]
steel_stresses: List[float]
steel_strains: List[float]
steel_forces: List[Tuple[float]]
[docs] def plot_stress(
self,
title: Optional[str] = "Stress",
conc_cmap: Optional[str] = "RdGy",
steel_cmap: Optional[str] = "bwr",
**kwargs,
) -> matplotlib.axes.Axes:
"""Plots concrete and steel stresses on a concrete section.
:param title: Plot title
:type title: Optional[str]
:param conc_cmap: Colour map for the concrete stress
:type conc_cmap: Optional[str]
:param steel_cmap: Colour map for the steel stress
:type steel_cmap: Optional[str]
:param kwargs: Passed to :func:`~concreteproperties.post.plotting_context`
:return: Matplotlib axes object
:rtype: :class:`matplotlib.axes.Axes`
"""
with plotting_context(
title=title,
**dict(
kwargs, nrows=1, ncols=3, gridspec_kw={"width_ratios": [1, 0.08, 0.08]}
),
) as (fig, ax):
# plot background
self.concrete_section.plot_section(
background=True, **dict(kwargs, ax=fig.axes[0])
)
# set up the colormaps
cmap_conc = cm.get_cmap(name=conc_cmap)
cmap_steel = cm.get_cmap(name=steel_cmap)
# determine minimum and maximum stress values for the contour list
# add tolerance for plotting stress blocks
conc_sig_min = min([min(x) for x in self.concrete_stresses]) - 1e-12
conc_sig_max = max([max(x) for x in self.concrete_stresses]) + 1e-12
steel_sig_min = min(self.steel_stresses)
steel_sig_max = max(self.steel_stresses)
# set up ticks
v_conc = np.linspace(conc_sig_min, conc_sig_max, 15, endpoint=True)
v_steel = np.linspace(steel_sig_min, steel_sig_max, 15, endpoint=True)
if np.isclose(v_conc[0], v_conc[-1], atol=1e-12):
v_conc = 15
ticks_conc = None
else:
ticks_conc = v_conc
if np.isclose(v_steel[0], v_steel[-1], atol=1e-12):
ticks_steel = None
steel_tick_same = True
else:
ticks_steel = v_steel
steel_tick_same = False
# plot the concrete stresses
for idx, sig in enumerate(self.concrete_stresses):
# check region has a force
if abs(self.concrete_forces[idx][0]) > 1e-8:
# create triangulation
triang = tri.Triangulation(
self.concrete_analysis_sections[idx].mesh_nodes[:, 0],
self.concrete_analysis_sections[idx].mesh_nodes[:, 1],
self.concrete_analysis_sections[idx].mesh_elements[:, 0:3],
)
# plot the filled contour
trictr = fig.axes[0].tricontourf(
triang, sig, v_conc, cmap=cmap_conc, norm=CenteredNorm()
)
# plot a zero stress contour, supressing warning
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore",
message="No contour levels were found within the data range.",
)
# set zero stress for neutral axis contour
zero_level = 0
if min(sig) > 0:
if min(sig) < 1e-3:
zero_level = min(sig) + 1e-12
if max(sig) < 0:
if max(sig) > -1e-3:
zero_level = max(sig) - 1e-12
if min(sig) == 0:
zero_level = 1e-12
if max(sig) == 0:
zero_level = -1e-12
CS = fig.axes[0].tricontour(
triang, sig, [zero_level], linewidths=1, linestyles="dashed"
)
# plot the steel stresses
steel_patches = []
colours = []
for idx, sig in enumerate(self.steel_stresses):
steel_patches.append(
mpatches.Polygon(
xy=list(
self.concrete_section.steel_geometries[
idx
].geom.exterior.coords
)
)
)
colours.append(sig)
patch = PatchCollection(steel_patches, cmap=cmap_steel)
patch.set_array(colours)
if steel_tick_same:
patch.set_clim([0.99 * v_steel[0], 1.01 * v_steel[-1]])
fig.axes[0].add_collection(patch)
# add the colour bars
fig.colorbar(
trictr,
label="Concrete Stress",
format="%.2e",
ticks=ticks_conc,
cax=fig.axes[1],
)
fig.colorbar(
patch,
label="Steel Stress",
format="%.2e",
ticks=ticks_steel,
cax=fig.axes[2],
)
ax.set_aspect("equal", anchor="C")
return ax
[docs] def sum_forces(
self,
) -> float:
"""Returns the sum of the internal forces.
:return: Sum of internal forces
:rtype: float
"""
force_sum = 0
# sum concrete forces
for conc_force in self.concrete_forces:
force_sum += conc_force[0]
# sum steel forces
for steel_force in self.steel_forces:
force_sum += steel_force[0]
return force_sum
[docs] def sum_moments(
self,
) -> float:
"""Returns the sum of the internal moments.
:return: Sum of internal moments
:rtype: float
"""
moment_sum = 0
# sum concrete forces
for conc_force in self.concrete_forces:
moment_sum += conc_force[0] * conc_force[1]
# sum steel forces
for steel_force in self.steel_forces:
moment_sum += steel_force[0] * steel_force[1]
return moment_sum