diff --git a/docs/examples/area_properties.ipynb b/docs/examples/area_properties.ipynb index a826b6a0..d5a4df83 100644 --- a/docs/examples/area_properties.ipynb +++ b/docs/examples/area_properties.ipynb @@ -132,7 +132,7 @@ "outputs": [], "source": [ "gross_props = conc_sec.get_gross_properties()\n", - "gross_props.print_results(fmt=\".3e\")" + "gross_props.print_results()" ] }, { @@ -153,7 +153,7 @@ "outputs": [], "source": [ "transformed_props = conc_sec.get_transformed_gross_properties(elastic_modulus=30.1e3)\n", - "transformed_props.print_results(fmt=\".3e\")" + "transformed_props.print_results()" ] } ], @@ -173,7 +173,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/docs/examples/as3600.ipynb b/docs/examples/as3600.ipynb index 158576da..589dcc1d 100644 --- a/docs/examples/as3600.ipynb +++ b/docs/examples/as3600.ipynb @@ -27,7 +27,8 @@ " RectangularStressBlock,\n", ")\n", "from concreteproperties.design_codes import AS3600\n", - "from concreteproperties.results import MomentInteractionResults" + "from concreteproperties.post import si_kn_m, si_n_mm\n", + "from concreteproperties.results import BiaxialBendingResults, MomentInteractionResults" ] }, { @@ -68,8 +69,12 @@ "source": [ "print(concrete.name)\n", "print(f\"Density = {concrete.density} kg/mm^3\")\n", - "concrete.stress_strain_profile.plot_stress_strain(title=\"Service Profile\")\n", - "concrete.ultimate_stress_strain_profile.plot_stress_strain(title=\"Ultimate Profile\")\n", + "concrete.stress_strain_profile.plot_stress_strain(\n", + " title=\"Service Profile\", eng=True, units=si_n_mm\n", + ")\n", + "concrete.ultimate_stress_strain_profile.plot_stress_strain(\n", + " title=\"Ultimate Profile\", eng=True, units=si_n_mm\n", + ")\n", "print(\n", " f\"Concrete Flexural Tensile Strength: {concrete.flexural_tensile_strength:.2f} MPa\"\n", ")" @@ -92,7 +97,7 @@ "source": [ "print(steel.name)\n", "print(f\"Density = {steel.density} kg/mm^3\")\n", - "steel.stress_strain_profile.plot_stress_strain()" + "steel.stress_strain_profile.plot_stress_strain(eng=True, units=si_n_mm)" ] }, { @@ -240,7 +245,10 @@ "outputs": [], "source": [ "MomentInteractionResults.plot_multiple_diagrams(\n", - " [f_mi_res, mi_res], [\"Factored\", \"Unfactored\"], fmt=\"-\"\n", + " [mi_res, f_mi_res],\n", + " [\"Unfactored\", \"Factored\"],\n", + " fmt=\"-\",\n", + " units=si_kn_m,\n", ")" ] }, @@ -294,7 +302,11 @@ "n_cases = len(n_stars)\n", "\n", "# plot moment interaction diagram\n", - "ax = f_mi_res.plot_diagram(fmt=\"k-\", render=False)\n", + "ax = f_mi_res.plot_diagram(\n", + " fmt=\"k-\",\n", + " units=si_kn_m,\n", + " render=False,\n", + ")\n", "\n", "# check to see if combination is within diagram and plot result\n", "for idx in range(n_cases):\n", @@ -356,6 +368,7 @@ " [f_mi_res, f_mi_res_bil, f_mi_res_par],\n", " [\"Rectangular\", \"Bilinear\", \"Parabolic\"],\n", " fmt=\"-\",\n", + " units=si_kn_m,\n", ")" ] }, @@ -400,15 +413,19 @@ "outputs": [], "source": [ "# plot case 1\n", - "ax = f_bb_res1.plot_diagram(fmt=\"x-\", render=False)\n", - "bb_res1.plot_diagram(fmt=\"o-\", ax=ax)\n", - "plt.show()\n", + "BiaxialBendingResults.plot_multiple_diagrams_2d(\n", + " [bb_res1, f_bb_res1],\n", + " labels=[\"Unfactored\", \"Factored\"],\n", + " units=si_kn_m,\n", + ")\n", "print(f\"Average phi = {np.mean(phis1):.3f}\")\n", "\n", "# plot case 2\n", - "ax = f_bb_res2.plot_diagram(fmt=\"x-\", render=False)\n", - "bb_res2.plot_diagram(fmt=\"o-\", ax=ax)\n", - "plt.show()\n", + "BiaxialBendingResults.plot_multiple_diagrams_2d(\n", + " [bb_res2, f_bb_res2],\n", + " labels=[\"Unfactored\", \"Factored\"],\n", + " units=si_kn_m,\n", + ")\n", "print(f\"Average phi = {np.mean(phis2):.3f}\")" ] }, @@ -437,7 +454,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.7" }, "vscode": { "interpreter": { diff --git a/docs/examples/biaxial_bending.ipynb b/docs/examples/biaxial_bending.ipynb index bbc9f407..1ac2a66e 100644 --- a/docs/examples/biaxial_bending.ipynb +++ b/docs/examples/biaxial_bending.ipynb @@ -35,6 +35,7 @@ " SteelElasticPlastic,\n", " add_bar_rectangular_array,\n", ")\n", + "from concreteproperties.post import si_kn_m\n", "from concreteproperties.results import BiaxialBendingResults" ] }, @@ -149,7 +150,7 @@ "metadata": {}, "outputs": [], "source": [ - "bb_res.plot_diagram()" + "bb_res.plot_diagram(eng=True, units=si_kn_m)" ] }, { @@ -168,7 +169,7 @@ "outputs": [], "source": [ "bb_res = conc_sec.biaxial_bending_diagram(n=1000e3, n_points=24, progress_bar=False)\n", - "bb_res.plot_diagram()" + "bb_res.plot_diagram(eng=True, units=si_kn_m)" ] }, { @@ -189,8 +190,8 @@ "source": [ "mi_x = conc_sec.moment_interaction_diagram(progress_bar=False)\n", "mi_y = conc_sec.moment_interaction_diagram(theta=np.pi / 2, progress_bar=False)\n", - "mi_x.plot_diagram()\n", - "mi_y.plot_diagram(moment=\"m_y\")\n", + "mi_x.plot_diagram(eng=True)\n", + "mi_y.plot_diagram(moment=\"m_y\", eng=True)\n", "print(f\"Decompression point for M_x is N = {mi_x.results[1].n / 1e3:.2f} kN\")\n", "print(f\"Decompression point for M_y is N = {mi_y.results[1].n / 1e3:.2f} kN\")" ] @@ -232,7 +233,11 @@ "metadata": {}, "outputs": [], "source": [ - "BiaxialBendingResults.plot_multiple_diagrams_3d(biaxial_results)" + "BiaxialBendingResults.plot_multiple_diagrams_3d(\n", + " biaxial_results,\n", + " eng=True,\n", + " units=si_kn_m,\n", + ")" ] }, { @@ -250,7 +255,12 @@ "metadata": {}, "outputs": [], "source": [ - "BiaxialBendingResults.plot_multiple_diagrams_2d(biaxial_results, fmt=\"o-\")" + "BiaxialBendingResults.plot_multiple_diagrams_2d(\n", + " biaxial_results,\n", + " fmt=\"o-\",\n", + " eng=True,\n", + " units=si_kn_m,\n", + ")" ] }, { @@ -268,16 +278,14 @@ "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "labels = [f\"N = {bb_res.n / 1e3:.0f} kN\" for bb_rs in biaxial_results[::2]]\n", + "labels = [f\"N = {bb_res.n / 1e3:.0f} kN\" for bb_res in biaxial_results[::2]]\n", "\n", "ax = BiaxialBendingResults.plot_multiple_diagrams_2d(\n", - " biaxial_results[::2], fmt=\"-\", labels=labels, render=False\n", - ")\n", - "ax.set_xlabel(\"Bending Moment $M_x$ [kN.m]\")\n", - "ax.set_ylabel(\"Bending Moment $M_y$ [kN.m]\")\n", - "plt.show()" + " biaxial_results[::2],\n", + " fmt=\"-\",\n", + " labels=labels,\n", + " units=si_kn_m,\n", + ")" ] } ], diff --git a/docs/examples/composite_section.ipynb b/docs/examples/composite_section.ipynb index e5eb7579..8711d8b2 100644 --- a/docs/examples/composite_section.ipynb +++ b/docs/examples/composite_section.ipynb @@ -42,7 +42,8 @@ " SteelElasticPlastic,\n", " add_bar_circular_array,\n", " add_bar_rectangular_array,\n", - ")" + ")\n", + "from concreteproperties.post import si_kn_m, si_n_mm" ] }, { @@ -170,7 +171,7 @@ "outputs": [], "source": [ "el_stress = conc_sec.calculate_uncracked_stress(m_x=100e6)\n", - "el_stress.plot_stress()" + "el_stress.plot_stress(units=si_n_mm)" ] }, { @@ -191,7 +192,7 @@ "source": [ "cr_res = conc_sec.calculate_cracked_properties()\n", "cr_stress = conc_sec.calculate_cracked_stress(cracked_results=cr_res, m=500e6)\n", - "cr_stress.plot_stress()" + "cr_stress.plot_stress(units=si_n_mm)" ] }, { @@ -220,7 +221,7 @@ "metadata": {}, "outputs": [], "source": [ - "mk_res.plot_results(fmt=\"kx-\")" + "mk_res.plot_results(fmt=\"kx-\", eng=True, units=si_kn_m)" ] }, { @@ -237,7 +238,7 @@ "serv_stress = conc_sec.calculate_service_stress(\n", " moment_curvature_results=mk_res, m=None, kappa=2e-5\n", ")\n", - "serv_stress.plot_stress()" + "serv_stress.plot_stress(units=si_n_mm)" ] }, { @@ -258,8 +259,8 @@ "source": [ "ult_res_x = conc_sec.ultimate_bending_capacity()\n", "ult_res_y = conc_sec.ultimate_bending_capacity(theta=np.pi / 2)\n", - "ult_res_x.print_results()\n", - "ult_res_y.print_results()" + "ult_res_x.print_results(units=si_kn_m)\n", + "ult_res_y.print_results(units=si_kn_m)" ] }, { @@ -270,7 +271,7 @@ "outputs": [], "source": [ "mi_res = conc_sec.moment_interaction_diagram(progress_bar=False)\n", - "mi_res.plot_diagram()" + "mi_res.plot_diagram(units=si_kn_m)" ] }, { @@ -281,7 +282,7 @@ "outputs": [], "source": [ "bb_res = conc_sec.biaxial_bending_diagram(n_points=24, progress_bar=False)\n", - "bb_res.plot_diagram()" + "bb_res.plot_diagram(units=si_kn_m)" ] }, { @@ -293,8 +294,8 @@ "source": [ "ult_stress_x = conc_sec.calculate_ultimate_stress(ult_res_x)\n", "ult_stress_y = conc_sec.calculate_ultimate_stress(ult_res_y)\n", - "ult_stress_x.plot_stress()\n", - "ult_stress_y.plot_stress()" + "ult_stress_x.plot_stress(units=si_n_mm)\n", + "ult_stress_y.plot_stress(units=si_n_mm)" ] }, { @@ -400,8 +401,8 @@ "source": [ "el_stress_comp = conc_sec_comp.calculate_uncracked_stress(m_x=10e6)\n", "el_stress_conc = conc_sec_conc.calculate_uncracked_stress(m_x=10e6)\n", - "el_stress_comp.plot_stress()\n", - "el_stress_conc.plot_stress()" + "el_stress_comp.plot_stress(units=si_n_mm)\n", + "el_stress_conc.plot_stress(units=si_n_mm)" ] }, { @@ -428,8 +429,8 @@ "cr_stress_conc = conc_sec_conc.calculate_cracked_stress(\n", " cracked_results=cr_res_conc, m=50e6\n", ")\n", - "cr_stress_comp.plot_stress()\n", - "cr_stress_conc.plot_stress()" + "cr_stress_comp.plot_stress(units=si_n_mm)\n", + "cr_stress_conc.plot_stress(units=si_n_mm)" ] }, { @@ -467,6 +468,8 @@ " moment_curvature_results=[mk_res_comp, mk_res_conc],\n", " labels=[\"Composite\", \"Concrete\"],\n", " fmt=\"x-\",\n", + " eng=True,\n", + " units=si_kn_m,\n", ")" ] }, @@ -500,6 +503,7 @@ " moment_interaction_results=[mi_res_comp, mi_res_conc],\n", " labels=[\"Composite\", \"Concrete\"],\n", " fmt=\"x-\",\n", + " units=si_kn_m,\n", ")" ] }, @@ -632,7 +636,7 @@ "metadata": {}, "outputs": [], "source": [ - "mk_res.plot_results()" + "mk_res.plot_results(eng=True, units=si_kn_m)" ] }, { @@ -666,9 +670,9 @@ "metadata": {}, "outputs": [], "source": [ - "el_stress.plot_stress()\n", - "yield_stress.plot_stress()\n", - "ult_stress.plot_stress()" + "el_stress.plot_stress(units=si_n_mm)\n", + "yield_stress.plot_stress(units=si_n_mm)\n", + "ult_stress.plot_stress(units=si_n_mm)" ] }, { @@ -687,8 +691,8 @@ "metadata": {}, "outputs": [], "source": [ - "ult_res = conc_sec.ultimate_bending_capacity(theta=np.pi / 2)\n", - "ult_res.print_results()" + "ult_res = conc_sec.ultimate_bending_capacity()\n", + "ult_res.print_results(units=si_kn_m)" ] }, { diff --git a/docs/examples/cracked_properties.ipynb b/docs/examples/cracked_properties.ipynb index 9c32c0a8..b81dd2b2 100644 --- a/docs/examples/cracked_properties.ipynb +++ b/docs/examples/cracked_properties.ipynb @@ -174,11 +174,15 @@ "metadata": {}, "outputs": [], "source": [ + "from concreteproperties.post import si_kn_m, si_n_mm\n", + "\n", + "si_n_mm.radians = False # show degrees\n", + "\n", "cracked_res_sag.calculate_transformed_properties(elastic_modulus=32.8e3)\n", "cracked_res_hog.calculate_transformed_properties(elastic_modulus=32.8e3)\n", "\n", - "cracked_res_sag.print_results()\n", - "cracked_res_hog.print_results()" + "cracked_res_sag.print_results(units=si_kn_m)\n", + "cracked_res_hog.print_results(units=si_n_mm)" ] }, { @@ -256,7 +260,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/docs/examples/moment_curvature.ipynb b/docs/examples/moment_curvature.ipynb index 9f5549c3..77de0bde 100644 --- a/docs/examples/moment_curvature.ipynb +++ b/docs/examples/moment_curvature.ipynb @@ -33,6 +33,7 @@ " SteelBar,\n", " add_bar_rectangular_array,\n", ")\n", + "from concreteproperties.post import si_kn_m, si_n_mm\n", "from concreteproperties.results import MomentCurvatureResults" ] }, @@ -140,7 +141,9 @@ "outputs": [], "source": [ "for conc in conc_material_list:\n", - " conc.stress_strain_profile.plot_stress_strain(title=conc.name)" + " conc.stress_strain_profile.plot_stress_strain(\n", + " title=conc.name, eng=True, units=si_n_mm\n", + " )" ] }, { @@ -150,7 +153,9 @@ "metadata": {}, "outputs": [], "source": [ - "steel.stress_strain_profile.plot_stress_strain(title=steel.name)" + "steel.stress_strain_profile.plot_stress_strain(\n", + " title=steel.name, eng=True, units=si_n_mm\n", + ")" ] }, { @@ -256,7 +261,11 @@ "outputs": [], "source": [ "MomentCurvatureResults.plot_multiple_results(\n", - " moment_curvature_results=moment_curvature_results, labels=labels, fmt=\"-\"\n", + " moment_curvature_results=moment_curvature_results,\n", + " labels=labels,\n", + " fmt=\"-\",\n", + " eng=True,\n", + " units=si_kn_m,\n", ")" ] }, @@ -294,7 +303,11 @@ "outputs": [], "source": [ "MomentCurvatureResults.plot_multiple_results(\n", - " moment_curvature_results=moment_curvature_results[1:], labels=labels[1:], fmt=\"-\"\n", + " moment_curvature_results=moment_curvature_results[1:],\n", + " labels=labels[1:],\n", + " fmt=\"-\",\n", + " eng=True,\n", + " units=si_kn_m,\n", ")" ] }, @@ -440,7 +453,7 @@ "metadata": {}, "outputs": [], "source": [ - "res1.plot_results(fmt=\"x-\")\n", + "res1.plot_results()\n", "print(f\"Number of calculations = {len(res1.kappa)}\")\n", "print(f\"Failure curvature = {res1.kappa[-1]:.4e}\")" ] @@ -479,7 +492,7 @@ "metadata": {}, "outputs": [], "source": [ - "res2.plot_results(fmt=\"x-\")\n", + "res2.plot_results()\n", "print(f\"Number of calculations = {len(res2.kappa)}\")\n", "print(f\"Failure curvature = {res2.kappa[-1]:.4e}\")" ] @@ -529,7 +542,7 @@ "metadata": {}, "outputs": [], "source": [ - "res3.plot_results(fmt=\"x-\")\n", + "res3.plot_results()\n", "print(f\"Number of calculations = {len(res3.kappa)}\")\n", "print(f\"Failure curvature = {res3.kappa[-1]:.4e}\")" ] @@ -670,6 +683,8 @@ " moment_curvature_results=[res_n0, res_n1, res_nt],\n", " labels=[\"$N=0$ kN\", \"$N=0.2f'cA_g$\", \"$N=-1000$ kN\"],\n", " fmt=\"-\",\n", + " eng=True,\n", + " units=si_kn_m,\n", ")" ] }, diff --git a/docs/examples/moment_interaction.ipynb b/docs/examples/moment_interaction.ipynb index 92d015b7..b434d583 100644 --- a/docs/examples/moment_interaction.ipynb +++ b/docs/examples/moment_interaction.ipynb @@ -34,6 +34,7 @@ " SteelBar,\n", " SteelElasticPlastic,\n", ")\n", + "from concreteproperties.post import si_kn_m, si_n_mm\n", "from concreteproperties.results import MomentInteractionResults" ] }, @@ -148,7 +149,7 @@ "metadata": {}, "outputs": [], "source": [ - "mi_res.plot_diagram()" + "mi_res.plot_diagram(eng=True, units=si_n_mm)" ] }, { @@ -167,7 +168,7 @@ "outputs": [], "source": [ "mi_res = conc_sec.moment_interaction_diagram(theta=np.pi / 2, progress_bar=False)\n", - "mi_res.plot_diagram(moment=\"m_y\")" + "mi_res.plot_diagram(moment=\"m_y\", units=si_kn_m)" ] }, { @@ -222,7 +223,10 @@ "source": [ "# plot all the diagrams on one image\n", "MomentInteractionResults.plot_multiple_diagrams(\n", - " moment_interaction_results=mi_results, labels=labels, fmt=\"-\"\n", + " moment_interaction_results=mi_results,\n", + " labels=labels,\n", + " fmt=\"-\",\n", + " units=si_kn_m,\n", ")" ] }, @@ -265,6 +269,8 @@ " moment_interaction_results=[mi_res_pos, mi_res_neg],\n", " labels=[\"Positive\", \"Negative\"],\n", " fmt=\"-\",\n", + " eng=True,\n", + " units=si_n_mm,\n", ")" ] }, @@ -380,7 +386,13 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "ax = mi_res.plot_diagram(fmt=\"-kx\", labels=True, label_offset=True, render=False)\n", + "ax = mi_res.plot_diagram(\n", + " fmt=\"-kx\",\n", + " labels=True,\n", + " label_offset=True,\n", + " units=si_kn_m,\n", + " render=False,\n", + ")\n", "\n", "# reset axis limits to ensure labels are within plot\n", "ax.set_xlim(-20, 850)\n", diff --git a/docs/examples/nzs3101.ipynb b/docs/examples/nzs3101.ipynb index 5d579668..15f51c73 100644 --- a/docs/examples/nzs3101.ipynb +++ b/docs/examples/nzs3101.ipynb @@ -27,6 +27,7 @@ " RectangularStressBlock,\n", ")\n", "from concreteproperties.design_codes import NZS3101\n", + "from concreteproperties.post import si_kn_m, si_n_mm\n", "from concreteproperties.results import (\n", " MomentCurvatureResults,\n", " MomentInteractionResults,\n", @@ -75,10 +76,16 @@ "ult_comp = concrete_40.ultimate_stress_strain_profile.get_ultimate_compressive_strain()\n", "print(f\"Ultimate Compressive Strain: {ult_comp:.4f}\")\n", "concrete_40.stress_strain_profile.plot_stress_strain(\n", - " title=\"Concrete Serviceability Stress-Strain Profile\", fmt=\"-r\"\n", + " title=\"Concrete Serviceability Stress-Strain Profile\",\n", + " fmt=\"-r\",\n", + " eng=True,\n", + " units=si_n_mm,\n", ")\n", "concrete_40.ultimate_stress_strain_profile.plot_stress_strain(\n", - " title=\"Concrete Ultimate Stress-Strain Profile\", fmt=\"-r\"\n", + " title=\"Concrete Ultimate Stress-Strain Profile\",\n", + " fmt=\"-r\",\n", + " eng=True,\n", + " units=si_n_mm,\n", ")" ] }, @@ -103,7 +110,10 @@ "print(f\"Ultimate Tensile Strain = {ult_strain}\")\n", "print(f\"Overstrength Factor = {steel_300e.phi_os}\")\n", "steel_300e.stress_strain_profile.plot_stress_strain(\n", - " title=\"Steel Stress-Strain Profile\", fmt=\"-r\"\n", + " title=\"Steel Stress-Strain Profile\",\n", + " fmt=\"-r\",\n", + " eng=True,\n", + " units=si_n_mm,\n", ")" ] }, @@ -458,6 +468,7 @@ " fmt=\"or\",\n", " render=False,\n", " moment=\"m_xy\",\n", + " units=si_kn_m,\n", ")\n", "plt.gca().lines[0].set_linestyle(\"solid\")\n", "plt.gca().lines[0].set_marker(\"\")\n", @@ -490,7 +501,7 @@ "n_cases = len(n_stars)\n", "\n", "# plot moment interaction diagram\n", - "ax = f_mi_res.plot_diagram(fmt=\"-r\", render=False)\n", + "ax = f_mi_res.plot_diagram(fmt=\"-r\", render=False, units=si_kn_m)\n", "\n", "# check to see if combination is within diagram and plot result\n", "for idx in range(n_cases):\n", @@ -541,12 +552,12 @@ "outputs": [], "source": [ "# plot case 1\n", - "ax = f_bb_res1.plot_diagram(fmt=\"-r\", render=False)\n", + "ax = f_bb_res1.plot_diagram(fmt=\"-r\", render=False, units=si_kn_m)\n", "ax.plot(M_o_T, 0, \"sk\")\n", "plt.show()\n", "\n", "# plot case 2\n", - "ax = f_bb_res2.plot_diagram(fmt=\"-r\", render=False)\n", + "ax = f_bb_res2.plot_diagram(fmt=\"-r\", render=False, units=si_kn_m)\n", "ax.plot(M_o_C, 0, \"ok\")\n", "plt.show()" ] @@ -703,7 +714,10 @@ ")\n", "\n", "concrete_25_prob.stress_strain_profile.plot_stress_strain(\n", - " title=\"Mander Unconfined Stress-Strain Profile\", fmt=\"-r\"\n", + " title=\"Mander Unconfined Stress-Strain Profile\",\n", + " fmt=\"-r\",\n", + " eng=True,\n", + " units=si_n_mm,\n", ")" ] }, @@ -801,7 +815,12 @@ "fail_mat = moment_curvature_results.failure_geometry.material.name\n", "print(f\"The failure material is:-\\n{fail_mat}\")\n", "\n", - "MomentCurvatureResults.plot_results(moment_curvature_results, fmt=\"-r\")\n", + "MomentCurvatureResults.plot_results(\n", + " moment_curvature_results,\n", + " fmt=\"-r\",\n", + " eng=True,\n", + " units=si_kn_m,\n", + ")\n", "mcr = conc_sec.calculate_cracked_properties(theta=0).m_cr / 1e6\n", "print(f\"Cracking moment is M_cr = {mcr:.2f} kNm\")" ] diff --git a/docs/examples/prestressed_section.ipynb b/docs/examples/prestressed_section.ipynb index 43936fb6..277c9928 100644 --- a/docs/examples/prestressed_section.ipynb +++ b/docs/examples/prestressed_section.ipynb @@ -37,6 +37,7 @@ " StrandHardening,\n", " add_bar_rectangular_array,\n", ")\n", + "from concreteproperties.post import si_kn_m, si_n_mm\n", "from concreteproperties.results import MomentCurvatureResults" ] }, @@ -140,8 +141,10 @@ "outputs": [], "source": [ "# combine both stress-strain profiles on the same plot\n", - "ax = concrete_service.stress_strain_profile.plot_stress_strain(render=False)\n", - "concrete_service.ultimate_stress_strain_profile.plot_stress_strain(ax=ax)\n", + "ax = concrete_service.stress_strain_profile.plot_stress_strain(\n", + " units=si_n_mm, render=False\n", + ")\n", + "concrete_service.ultimate_stress_strain_profile.plot_stress_strain(units=si_n_mm, ax=ax)\n", "ax.lines[0].set_label(\"Service\")\n", "ax.lines[1].set_label(\"Ultimate\")\n", "ax.legend(loc=\"center left\", bbox_to_anchor=(1, 0.5))\n", @@ -157,8 +160,10 @@ "outputs": [], "source": [ "# combine both stress-strain profiles on the same plot\n", - "ax = strand_service.stress_strain_profile.plot_stress_strain(render=False)\n", - "strand_ultimate.stress_strain_profile.plot_stress_strain(ax=ax)\n", + "ax = strand_service.stress_strain_profile.plot_stress_strain(\n", + " eng=True, units=si_kn_m, render=False\n", + ")\n", + "strand_ultimate.stress_strain_profile.plot_stress_strain(eng=True, units=si_kn_m, ax=ax)\n", "ax.lines[0].set_label(\"Service\")\n", "ax.lines[1].set_label(\"Ultimate\")\n", "ax.legend(loc=\"center left\", bbox_to_anchor=(1, 0.5))\n", @@ -270,7 +275,7 @@ "source": [ "gross_props_serv = conc_sec_serv.get_gross_properties()\n", "gross_props_ult = conc_sec_ult.get_gross_properties()\n", - "gross_props_serv.print_results(fmt=\".3e\")" + "gross_props_serv.print_results(units=si_n_mm)" ] }, { @@ -356,7 +361,7 @@ "source": [ "# stress due to prestressing only\n", "uncr_stress_p = conc_sec_serv.calculate_uncracked_stress()\n", - "uncr_stress_p.plot_stress()" + "uncr_stress_p.plot_stress(units=si_n_mm)" ] }, { @@ -376,9 +381,9 @@ "source": [ "cr_p_serv = conc_sec_serv.calculate_cracked_properties(m_ext=0)\n", "cr_p_ult = conc_sec_ult.calculate_cracked_properties(m_ext=0)\n", - "cr_p_serv.print_results(fmt=\".3e\")\n", + "cr_p_serv.print_results(units=si_kn_m)\n", "cr_stress_p_serv = conc_sec_serv.calculate_cracked_stress(cracked_results=cr_p_serv)\n", - "cr_stress_p_serv.plot_stress()" + "cr_stress_p_serv.plot_stress(units=si_n_mm)" ] }, { @@ -422,7 +427,7 @@ "source": [ "# stress due to G_sw + P at t = 0\n", "uncr_stress_t0 = conc_sec_serv.calculate_uncracked_stress(m=m_g_sw)\n", - "uncr_stress_t0.plot_stress()" + "uncr_stress_t0.plot_stress(units=si_n_mm)" ] }, { @@ -443,7 +448,7 @@ "source": [ "# stress due to G + Q + P at t = inf\n", "uncr_stress_tinf = conc_sec_ult.calculate_uncracked_stress(m=m_g_sw + m_g_si + m_q)\n", - "uncr_stress_tinf.plot_stress()" + "uncr_stress_tinf.plot_stress(units=si_n_mm)" ] }, { @@ -471,7 +476,7 @@ "outputs": [], "source": [ "ult_res = conc_sec_ult.ultimate_bending_capacity()\n", - "ult_res.print_results()" + "ult_res.print_results(units=si_kn_m)" ] }, { @@ -503,7 +508,7 @@ "outputs": [], "source": [ "ult_stress = conc_sec_ult.calculate_ultimate_stress(ultimate_results=ult_res)\n", - "ult_stress.plot_stress()" + "ult_stress.plot_stress(units=si_n_mm)" ] }, { @@ -550,6 +555,8 @@ " moment_curvature_results=[mk_serv, mk_ult],\n", " labels=[\"Service\", \"Ultimate\"],\n", " fmt=\"-\",\n", + " eng=True,\n", + " units=si_kn_m,\n", ")" ] }, @@ -575,8 +582,8 @@ "serv_stress_u_1 = conc_sec_ult.calculate_service_stress(\n", " moment_curvature_results=mk_ult, m=None, kappa=mk_ult.kappa[0]\n", ")\n", - "serv_stress_s_1.plot_stress()\n", - "serv_stress_u_1.plot_stress()" + "serv_stress_s_1.plot_stress(units=si_n_mm)\n", + "serv_stress_u_1.plot_stress(units=si_n_mm)" ] }, { @@ -617,7 +624,7 @@ "id": "42", "metadata": {}, "source": [ - "Another point of interest on the moment curvature diagram is around 8000 kN.m. At this magnitude of external moment, the section in the service analysis remains uncracked (just below the flexural tensile strenght). However, the section in the ultimate analysis has started to crack and the bending stiffness has begun to soften. Note also the resulting increase in concrete compressive stress to compensate." + "Another point of interest on the moment curvature diagram is around 8000 kN.m. At this magnitude of external moment, the section in the service analysis remains uncracked (just below the flexural tensile strength). However, the section in the ultimate analysis has started to crack and the bending stiffness has begun to soften. Note also the resulting increase in concrete compressive stress to compensate." ] }, { @@ -633,8 +640,8 @@ "serv_stress_u_2 = conc_sec_ult.calculate_service_stress(\n", " moment_curvature_results=mk_ult, m=8000e6\n", ")\n", - "serv_stress_s_2.plot_stress()\n", - "serv_stress_u_2.plot_stress()" + "serv_stress_s_2.plot_stress(units=si_n_mm)\n", + "serv_stress_u_2.plot_stress(units=si_n_mm)" ] } ], @@ -654,7 +661,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.7" }, "vscode": { "interpreter": { diff --git a/docs/examples/stress_analysis.ipynb b/docs/examples/stress_analysis.ipynb index 080c7714..673ceac9 100644 --- a/docs/examples/stress_analysis.ipynb +++ b/docs/examples/stress_analysis.ipynb @@ -34,7 +34,8 @@ " RectangularStressBlock,\n", " SteelBar,\n", " SteelElasticPlastic,\n", - ")" + ")\n", + "from concreteproperties.post import si_kn_m, si_n_mm" ] }, { @@ -157,7 +158,7 @@ }, "outputs": [], "source": [ - "uncr_stress_res_1.plot_stress()" + "uncr_stress_res_1.plot_stress(units=si_n_mm)" ] }, { @@ -167,7 +168,7 @@ "metadata": {}, "outputs": [], "source": [ - "uncr_stress_res_2.plot_stress()" + "uncr_stress_res_2.plot_stress(eng=True, units=si_kn_m)" ] }, { @@ -382,7 +383,7 @@ "metadata": {}, "outputs": [], "source": [ - "mk_res.plot_results()" + "mk_res.plot_results(eng=True, units=si_kn_m)" ] }, { @@ -436,7 +437,7 @@ "\n", " # create plot title and plot stress\n", " label = f\"Moment = {m_stress / 1e6:.0f} kN.m\"\n", - " service_stress_res.plot_stress(title=label)" + " service_stress_res.plot_stress(title=label, units=si_n_mm)" ] }, { @@ -464,7 +465,7 @@ "outputs": [], "source": [ "mi_res = conc_sec.moment_interaction_diagram(progress_bar=False)\n", - "mi_res.plot_diagram()" + "mi_res.plot_diagram(units=si_kn_m)" ] }, { @@ -520,9 +521,9 @@ "metadata": {}, "outputs": [], "source": [ - "ultimate_stress_pure.plot_stress(title=\"Pure Bending\")\n", - "ultimate_stress_bal.plot_stress(title=\"Balanced\")\n", - "ultimate_stress_decomp.plot_stress(title=\"Decompression\")" + "ultimate_stress_pure.plot_stress(title=\"Pure Bending\", units=si_n_mm)\n", + "ultimate_stress_bal.plot_stress(title=\"Balanced\", units=si_n_mm)\n", + "ultimate_stress_decomp.plot_stress(title=\"Decompression\", units=si_n_mm)" ] }, { diff --git a/docs/examples/ultimate_bending.ipynb b/docs/examples/ultimate_bending.ipynb index ad9379ea..20f9869e 100644 --- a/docs/examples/ultimate_bending.ipynb +++ b/docs/examples/ultimate_bending.ipynb @@ -32,7 +32,8 @@ " ConcreteSection,\n", " SteelBar,\n", " add_bar_rectangular_array,\n", - ")" + ")\n", + "from concreteproperties.post import si_kn_m, si_n_mm" ] }, { @@ -184,7 +185,7 @@ "metadata": {}, "outputs": [], "source": [ - "sag_res.print_results()" + "sag_res.print_results(units=si_kn_m)" ] }, { @@ -194,7 +195,8 @@ "metadata": {}, "outputs": [], "source": [ - "hog_res.print_results()" + "si_n_mm.radians = False # display angles in degrees\n", + "hog_res.print_results(units=si_n_mm)" ] }, { diff --git a/docs/user_guide/design_codes/as3600.rst b/docs/user_guide/design_codes/as3600.rst index 9d239b3f..6c87ee34 100644 --- a/docs/user_guide/design_codes/as3600.rst +++ b/docs/user_guide/design_codes/as3600.rst @@ -21,11 +21,14 @@ created it must be assigned to the design code:: .. automethod:: concreteproperties.design_codes.as3600.AS3600.assign_concrete_section :noindex: -.. note:: +.. warning:: - To maintain unit consistency, the cross-section dimensions should be entered in - *[mm]*. + To maintain unit consistency, length dimensions must be entered in *[mm]*, force + dimensions entered in *[N]* and density dimensions entered |dunits|. +.. |dunits| raw:: html + + [kg/mm3] Creating Material Properties ---------------------------- diff --git a/docs/user_guide/results.rst b/docs/user_guide/results.rst index 59f934fb..c90728c4 100644 --- a/docs/user_guide/results.rst +++ b/docs/user_guide/results.rst @@ -40,9 +40,8 @@ attributes. The transformed gross area properties can be printed to the terminal calling the :meth:`~concreteproperties.results.TransformedGrossProperties.print_results` method. -.. autoclass:: concreteproperties.results.TransformedGrossProperties() +.. automethod:: concreteproperties.results.TransformedGrossProperties.print_results() :noindex: - :members: .. seealso:: For an application of the above, see the example @@ -68,7 +67,7 @@ returns a :class:`~concreteproperties.results.CrackedResults` object. bending. Calling -:meth:`~concreteproperties.results.TransformedGrossProperties.calculate_transformed_properties` +:meth:`~concreteproperties.results.CrackedResults.calculate_transformed_properties` on a :class:`~concreteproperties.results.CrackedResults` object stores the transformed cracked properties as attributes within the current object. @@ -167,3 +166,49 @@ stress results. .. seealso:: For an application of the above, see the example :ref:`/examples/stress_analysis.ipynb`. + + +Units +----- + +Most of the above methods take an optional ``units`` argument as a +:class:`~concreteproperties.post.UnitDisplay()` object. This argument allows results to +be scaled and unit labels applied to numbers/plot axes. + +.. autoclass:: concreteproperties.post.UnitDisplay() + :noindex: + +For example, if the model data is specified in ``[N]`` and ``[mm]``, results can be +expressed using ``[kN]`` and ``[m]`` by creating an appropriate ``UnitDisplay`` object: + +.. code-block:: python + + from concreteproperties.post import UnitDisplay + + kn_m = UnitDisplay(si_kn_m = UnitDisplay( + length="m", force="kN", mass="kg", length_factor=1e3, force_factor=1e3 + ) + +Note that stresses will automatically be displayed as kilopascals using the above +unit system as ``[kPa]`` is equivalent to |kpa|. Similarly, results in ``[N.mm]``, +``[MPa]`` etc. can be automatically displayed by creating a ``UnitDisplay`` object that +represents the base unit system: + +.. |kpa| raw:: html + + [kN/m2] + +.. code-block:: python + + from concreteproperties.post import UnitDisplay + + n_mm = UnitDisplay(length="mm", force="N", mass="kg") + +Note that the above two unit systems are baked into ``concreteproperties`` in the +``post`` module with the ``si_n_mm`` and ``si_kn_m`` objects: + +.. code-block:: python + + from concreteproperties.post import si_kn_m, si_n_mm + +``concreteproperties`` welcomes the contribution of further unit systems! diff --git a/pyproject.toml b/pyproject.toml index 77d4d10e..e518a4ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dependencies = [ "cytriangle~=1.0.3", "rich[jupyter]~=13.9.3", "more-itertools~=10.5.0", + "quantiphy~=2.20", ] [project.urls] @@ -58,6 +59,7 @@ docs = [ "ipython==8.29.0", "nbsphinx==0.9.5", "nbconvert==7.16.4", + "sphinx-autobuild==2024.10.03", "sphinx-copybutton==0.5.2", "sphinxext-opengraph==0.9.1", ] diff --git a/src/concreteproperties/concrete_section.py b/src/concreteproperties/concrete_section.py index ed855e9e..7f8e58f9 100644 --- a/src/concreteproperties/concrete_section.py +++ b/src/concreteproperties/concrete_section.py @@ -17,12 +17,14 @@ import concreteproperties.utils as utils from concreteproperties.analysis_section import AnalysisSection from concreteproperties.material import Concrete, SteelStrand -from concreteproperties.post import plotting_context +from concreteproperties.post import DEFAULT_UNITS, plotting_context from concreteproperties.pre import CPGeom, CPGeomConcrete if TYPE_CHECKING: import matplotlib.axes + from concreteproperties.post import UnitDisplay + class ConcreteSection: """Class for a reinforced concrete section.""" @@ -32,6 +34,7 @@ def __init__( geometry: sp_geom.CompoundGeometry, moment_centroid: tuple[float, float] | None = None, geometric_centroid_override: bool = False, + default_units: UnitDisplay | None = None, ) -> None: """Inits the ConcreteSection class. @@ -45,6 +48,8 @@ def __init__( geometric_centroid_override: If set to True, sets ``moment_centroid`` to the geometric centroid i.e. material properties applied (useful for composite section analysis). Defaults to ``False``. + default_units: Default unit system to use for formatting results. Defaults + to ``None``. Raises: ValueError: If steel strand materials are detected, use a @@ -52,6 +57,10 @@ def __init__( """ self.compound_geometry = geometry + # assign unitless unit if no default_unit applied + units = DEFAULT_UNITS if default_units is None else default_units + self.default_units = units + # check overlapping regions polygons = [sec_geom.geom for sec_geom in self.compound_geometry.geoms] overlapped_regions = sp_geom.check_geometry_overlaps(polygons) @@ -100,7 +109,7 @@ def __init__( self.all_geometries.append(cp_geom) # calculate gross properties - self.gross_properties = res.GrossProperties() + self.gross_properties = res.GrossProperties(default_units=self.default_units) self.calculate_gross_area_properties() # set moment centroid @@ -298,7 +307,9 @@ def get_transformed_gross_properties( Transformed concrete properties object """ return res.TransformedGrossProperties( - concrete_properties=self.gross_properties, elastic_modulus=elastic_modulus + default_units=self.default_units, + concrete_properties=self.gross_properties, + elastic_modulus=elastic_modulus, ) def calculate_cracked_properties( @@ -317,7 +328,9 @@ def calculate_cracked_properties( Returns: Cracked results object """ - cracked_results = res.CrackedResults(theta=theta) + cracked_results = res.CrackedResults( + default_units=self.default_units, theta=theta + ) cracked_results.m_cr = self.calculate_cracking_moment(theta=theta) # set neutral axis depth limits @@ -625,7 +638,9 @@ def moment_curvature_analysis( Moment curvature results object """ # initialise variables - moment_curvature = res.MomentCurvatureResults(theta=theta, n_target=n) + moment_curvature = res.MomentCurvatureResults( + default_units=self.default_units, theta=theta, n_target=n + ) # function that performs moment curvature analysis def mcurve(kappa_inc=kappa_inc, progress=None): @@ -935,7 +950,9 @@ def ultimate_bending_capacity( b = 6 * d_t # neutral axis at sufficiently large tensile fibre # initialise ultimate bending results - ultimate_results = res.UltimateBendingResults(theta=theta) + ultimate_results = res.UltimateBendingResults( + default_units=self.default_units, theta=theta + ) # find neutral axis that gives convergence of the axial force try: @@ -1007,7 +1024,9 @@ def calculate_ultimate_section_actions( Ultimate bending results object """ if ultimate_results is None: - ultimate_results = res.UltimateBendingResults(theta=0) + ultimate_results = res.UltimateBendingResults( + default_units=self.default_units, theta=0 + ) # calculate extreme fibre in global coordinates extreme_fibre, _ = utils.calculate_extreme_fibre( @@ -1160,7 +1179,7 @@ def moment_interaction_diagram( Args: theta: Angle (in radians) the neutral axis makes with the horizontal axis - (:math:`-\pi \leq \theta \leq \pi`) + (:math:`-\pi \leq \theta \leq \pi`). Defaults to ``0``. limits: List of control points that define the start and end of the interaction diagram. List length must equal two. The default limits range from concrete decompression strain to zero curvature tension, i.e. @@ -1238,7 +1257,9 @@ def moment_interaction_diagram( labels = labels * (len(control_points) + 2) # initialise results - mi_results = res.MomentInteractionResults() + mi_results = res.MomentInteractionResults( + default_units=self.default_units, + ) # generate list of neutral axis depths/axial forces to analyse # if we are spacing by axial force @@ -1246,11 +1267,15 @@ def moment_interaction_diagram( # get axial force of the limits start_res = self.calculate_ultimate_section_actions( d_n=limits_dn[0], - ultimate_results=res.UltimateBendingResults(theta=theta), + ultimate_results=res.UltimateBendingResults( + default_units=self.default_units, theta=theta + ), ) end_res = self.calculate_ultimate_section_actions( d_n=limits_dn[1], - ultimate_results=res.UltimateBendingResults(theta=theta), + ultimate_results=res.UltimateBendingResults( + default_units=self.default_units, theta=theta + ), ) # generate list of axial forces @@ -1279,12 +1304,16 @@ def micurve(progress=None): if idx == 0: ult_res = self.calculate_ultimate_section_actions( d_n=limits_dn[0], - ultimate_results=res.UltimateBendingResults(theta=theta), + ultimate_results=res.UltimateBendingResults( + default_units=self.default_units, theta=theta + ), ) elif idx == len(analysis_list) - 1: ult_res = self.calculate_ultimate_section_actions( d_n=limits_dn[1], - ultimate_results=res.UltimateBendingResults(theta=theta), + ultimate_results=res.UltimateBendingResults( + default_units=self.default_units, theta=theta + ), ) else: ult_res = self.ultimate_bending_capacity( @@ -1294,7 +1323,9 @@ def micurve(progress=None): else: ult_res = self.calculate_ultimate_section_actions( d_n=analysis_point, - ultimate_results=res.UltimateBendingResults(theta=theta), + ultimate_results=res.UltimateBendingResults( + default_units=self.default_units, theta=theta + ), ) # add labels for limits @@ -1315,7 +1346,9 @@ def micurve(progress=None): for idx, d_n in enumerate(add_cp_dn): ult_res = self.calculate_ultimate_section_actions( d_n=d_n, - ultimate_results=res.UltimateBendingResults(theta=theta), + ultimate_results=res.UltimateBendingResults( + default_units=self.default_units, theta=theta + ), ) # add label @@ -1395,6 +1428,7 @@ def micurve(progress=None): mi_results.results.insert( 0, # insertion index res.UltimateBendingResults( + default_units=self.default_units, theta=theta, d_n=inf, k_u=0, @@ -1429,7 +1463,7 @@ def biaxial_bending_diagram( Biaxial bending results """ # initialise results - bb_results = res.BiaxialBendingResults(n=n) + bb_results = res.BiaxialBendingResults(default_units=self.default_units, n=n) # calculate d_theta d_theta = 2 * np.pi / n_points @@ -1591,6 +1625,7 @@ def calculate_uncracked_stress( lumped_reinf_geoms.append(lumped_geom) return res.StressResult( + default_units=self.default_units, concrete_section=self, concrete_analysis_sections=conc_sections, concrete_stresses=conc_sigs, @@ -1729,6 +1764,7 @@ def calculate_cracked_stress( lumped_reinf_geoms.append(lumped_geom) return res.StressResult( + default_units=self.default_units, concrete_section=self, concrete_analysis_sections=conc_sections, concrete_stresses=conc_sigs, @@ -1776,7 +1812,9 @@ def calculate_service_stress( # initialise variables mk = res.MomentCurvatureResults( - theta=theta, n_target=moment_curvature_results.n_target + default_units=self.default_units, + theta=theta, + n_target=moment_curvature_results.n_target, ) # find neutral axis that gives convergence of the axial force @@ -1878,6 +1916,7 @@ def calculate_service_stress( lumped_reinf_geoms.append(lumped_geom) return res.StressResult( + default_units=self.default_units, concrete_section=self, concrete_analysis_sections=conc_sections, concrete_stresses=conc_sigs, @@ -2003,6 +2042,7 @@ def calculate_ultimate_stress( lumped_reinf_geoms.append(lumped_geom) return res.StressResult( + default_units=self.default_units, concrete_section=self, concrete_analysis_sections=conc_sections, concrete_stresses=conc_sigs, diff --git a/src/concreteproperties/design_codes/as3600.py b/src/concreteproperties/design_codes/as3600.py index f447e268..8f29b47d 100644 --- a/src/concreteproperties/design_codes/as3600.py +++ b/src/concreteproperties/design_codes/as3600.py @@ -15,6 +15,7 @@ import concreteproperties.stress_strain_profile as ssp from concreteproperties.design_codes.design_code import DesignCode from concreteproperties.material import Concrete, SteelBar +from concreteproperties.post import DEFAULT_UNITS, si_n_mm from concreteproperties.utils import AnalysisError, create_known_progress if TYPE_CHECKING: @@ -56,6 +57,11 @@ def assign_concrete_section( msg = "Meshed reinforcement is not supported in this design code." raise ValueError(msg) + # assign default units if not provided + if self.concrete_section.default_units is DEFAULT_UNITS: + self.concrete_section.default_units = si_n_mm + self.concrete_section.gross_properties.default_units = si_n_mm + # determine reinforcement class self.reinforcement_class = "N" @@ -82,7 +88,7 @@ def create_concrete_material( .. admonition:: Material assumptions - - *Density*: 2400 kg/m\ :sup:`3` + - *Density*: 2400 kg/m\ :sup:`3` (2.4 x 10\ :sup:`-6` kg/mm\ :sup:`3`) - *Elastic modulus*: Interpolated from Table 3.1.2 @@ -333,7 +339,10 @@ def get_n_ub( # calculate axial force at balanced load balanced_res = self.concrete_section.calculate_ultimate_section_actions( - d_n=d_nb, ultimate_results=res.UltimateBendingResults(theta=theta) + d_n=d_nb, + ultimate_results=res.UltimateBendingResults( + default_units=self.concrete_section.default_units, theta=theta + ), ) return balanced_res.n @@ -419,6 +428,7 @@ def non_linear_phi(phi_guess): squash = f_mi_res.results[0] decomp = f_mi_res.results[1] ult_res = res.UltimateBendingResults( + default_units=self.concrete_section.default_units, theta=theta, d_n=inf, k_u=0, @@ -437,6 +447,7 @@ def non_linear_phi(phi_guess): factor = n_design / n_tensile pure = f_mi_res.results[-2] ult_res = res.UltimateBendingResults( + default_units=self.concrete_section.default_units, theta=theta, d_n=inf, k_u=0, @@ -537,6 +548,7 @@ def moment_interaction_diagram( # pyright: ignore [reportIncompatibleMethodOver mi_res.results.insert( 0, res.UltimateBendingResults( + default_units=self.concrete_section.default_units, theta=theta, d_n=inf, k_u=0, @@ -550,6 +562,7 @@ def moment_interaction_diagram( # pyright: ignore [reportIncompatibleMethodOver # add tensile load mi_res.results.append( res.UltimateBendingResults( + default_units=self.concrete_section.default_units, theta=theta, d_n=0, k_u=0, @@ -606,7 +619,9 @@ def biaxial_bending_diagram( # pyright: ignore [reportIncompatibleMethodOverrid factors (``factored_results``, ``phis``) """ # initialise results - f_bb_res = res.BiaxialBendingResults(n=n_design) + f_bb_res = res.BiaxialBendingResults( + default_units=self.concrete_section.default_units, n=n_design + ) phis = [] # calculate d_theta diff --git a/src/concreteproperties/design_codes/nzs3101.py b/src/concreteproperties/design_codes/nzs3101.py index 585a36a8..1c52813f 100644 --- a/src/concreteproperties/design_codes/nzs3101.py +++ b/src/concreteproperties/design_codes/nzs3101.py @@ -14,6 +14,7 @@ import concreteproperties.utils as utils from concreteproperties.design_codes.design_code import DesignCode from concreteproperties.material import Concrete, SteelBar +from concreteproperties.post import si_n_mm if TYPE_CHECKING: from concreteproperties.concrete_section import ConcreteSection @@ -104,6 +105,11 @@ def assign_concrete_section( # assign section type self.section_type = section_type + # assign default units if not provided + if self.concrete_section.default_units.length == "": + self.concrete_section.default_units = si_n_mm + self.concrete_section.gross_properties.default_units = si_n_mm + # check to make sure there are no meshed reinforcement regions if self.concrete_section.reinf_geometries_meshed: msg = f"Meshed reinforcement is not supported in the {self.analysis_code} " @@ -377,7 +383,7 @@ def prob_compressive_strength( Calculate the probable compressive strength of concrete in accordance with NZSEE C5 assessement guidelines C5.4.2.2. - Taken as the nominal 28-day compressive strenght of the concrete specified for + Taken as the nominal 28-day compressive strength of the concrete specified for the original construciton, multiplied by 1.5 for strengths less than or equal to 40 MPa, and 1.4 for strengths greater than 40 MPa. @@ -729,7 +735,7 @@ def check_axial_limits( raise ValueError(msg) def check_f_y_limit(self) -> None: - """Checks the reinforcement strenghts are within limits. + """Checks the reinforcement strengths are within limits. Checks that the specified steel reinforcement strengths for all defined steel geometries comply with NZS3101:2006 CL 5.3.3. @@ -774,7 +780,7 @@ def check_f_c_limits( self, pphr_class: str, ) -> None: - """Checks the concrete strenght complies with the PPHR classification. + """Checks the concrete strength complies with the PPHR classification. Checks that a valid Potential Plastic Hinge Region (PPHR) classification has been specified, and that the specified compressive strengths for all defined @@ -1836,7 +1842,9 @@ def biaxial_bending_diagram( # pyright: ignore [reportIncompatibleMethodOverrid self.check_f_y_limit() # initialise results - f_bb_res = res.BiaxialBendingResults(n=n_design) + f_bb_res = res.BiaxialBendingResults( + default_units=self.concrete_section.default_units, n=n_design + ) # list to store phis phis = [] diff --git a/src/concreteproperties/post.py b/src/concreteproperties/post.py index 81dfe523..9ff2e9c6 100644 --- a/src/concreteproperties/post.py +++ b/src/concreteproperties/post.py @@ -3,9 +3,12 @@ from __future__ import annotations import contextlib +from dataclasses import dataclass from typing import TYPE_CHECKING import matplotlib.pyplot as plt +import numpy as np +from quantiphy import Quantity if TYPE_CHECKING: import matplotlib.axes @@ -100,3 +103,243 @@ def plotting_context( else: plt.draw() plt.pause(0.001) + + +def string_formatter( + value: float, + eng: bool, + prec: int, + scale: float = 1.0, +) -> str: + """Formats a float using engineering or fixed notation. + + Args: + value: Number to format + eng: If set to ``True``, formats with engineering notation. If set to ``False``, + formats with fixed notation. + prec: The desired precision (i.e. one plus this value is the desired number of + digits) + scale: Factor by which to scale the value. Defaults to ``1.0``. + + Returns: + Formatted string + """ + q = Quantity(value) + form = "eng" if eng else "fixed" + val_fmt = q.render( + form=form, + show_units=False, + prec=prec, + scale=scale, + strip_zeros=False, + ) + spl = val_fmt.split("e") + + # if there is an exponent, render as 'x 10^n' + if eng and len(spl) > 1: + num = spl[0] + exp = spl[1] + return f"{num} x 10^{exp}" + else: + return val_fmt + + +def string_formatter_plots( + value: float, + prec: int, +) -> str: + """Formats a float using engineering notation for plotting. + + Args: + value: Number to format + prec: The desired precision (i.e. one plus this value is the desired number of + digits) + + Returns: + Formatted string + """ + q = Quantity(value) + return q.render( + form="eng", show_units=False, prec=prec, strip_zeros=True, strip_radix=True + ) + + +def string_formatter_stress( + value: float, + eng: bool, + prec: int, +) -> str: + """Formats a float using engineering notation for stress plotting. + + Args: + value: Number to format + eng: If set to ``True``, formats with engineering notation. If set to ``False``, + formats with fixed notation. + prec: The desired precision (i.e. one plus this value is the desired number of + digits) + + Returns: + Formatted string + """ + q = Quantity(value) + form = "eng" if eng else "fixed" + val_fmt = q.render( + form=form, show_units=False, prec=prec, strip_zeros=False, strip_radix=False + ) + + # ensure there is an e0 + if "e" not in val_fmt and eng: + val_fmt += "e0" + + return val_fmt + + +@dataclass +class UnitDisplay: + """Class for displaying units in ``concreteproperties``. + + Attributes: + length: Length unit string + force: Force unit string + mass: Mass unit string + radians: If set to ``True``, displays angles in radians, otherwise displays + angles in degrees. Defaults to ``True``. + length_factor: Factor by which the ``length`` unit differs from the base units. + Defaults to ``1.0``. + force_factor: Factor by which the ``force`` unit differs from the base units. + Defaults to ``1.0``. + mass_factor: Factor by which the ``mass`` unit differs from the base units. + Defaults to ``1.0``. + """ + + length: str + force: str + mass: str + radians: bool = True + length_factor: float = 1.0 + force_factor: float = 1.0 + mass_factor: float = 1.0 + + @property + def length_unit(self) -> str: + """Returns the length unit string.""" + return self.length if self.length == "" else f" {self.length}" + + @property + def length_scale(self) -> float: + """Returns the length scale.""" + return 1 / self.length_factor + + @property + def force_unit(self) -> str: + """Returns the force unit string.""" + return self.force if self.force == "" else f" {self.force}" + + @property + def force_scale(self) -> float: + """Returns the force scale.""" + return 1 / self.force_factor + + @property + def mass_unit(self) -> str: + """Returns the mass unit string.""" + return self.mass if self.mass == "" else f" {self.mass}" + + @property + def mass_scale(self) -> float: + """Returns the mass scale.""" + return 1 / self.mass_factor + + @property + def angle_unit(self) -> str: + """Returns the angle unit string.""" + return " rads" if self.radians else " degs" + + @property + def angle_scale(self) -> float: + """Returns the angle scale.""" + return 1 if self.radians else 180.0 / np.pi + + @property + def area_unit(self) -> str: + """Returns the area unit string.""" + return self.length if self.length == "" else f" {self.length}^2" + + @property + def area_scale(self) -> float: + """Returns the area scale.""" + return 1 / self.length_factor / self.length_factor + + @property + def mass_per_length_unit(self) -> str: + """Returns the mass/length unit string.""" + return self.mass if self.mass == "" else f" {self.mass}/{self.length}" + + @property + def mass_per_length_scale(self) -> float: + """Returns the mass/length scale.""" + return 1 / self.mass_factor * self.length_factor + + @property + def moment_unit(self) -> str: + """Returns the moment unit string.""" + return self.length if self.length == "" else f" {self.force}.{self.length}" + + @property + def moment_scale(self) -> float: + """Returns the moment scale.""" + return 1 / self.force_factor / self.length_factor + + @property + def flex_rig_unit(self) -> str: + """Returns the flexural rigidity unit string.""" + return self.length if self.length == "" else f" {self.force}.{self.length}^2" + + @property + def flex_rig_scale(self) -> float: + """Returns the flexural rigidity scale.""" + return 1 / self.force_factor / self.length_factor / self.length_factor + + @property + def stress_unit(self) -> str: + """Returns the stress unit string.""" + if self.length == "mm" and self.force == "N": + return " MPa" + elif self.length == "m" and self.force == "kN": + return " kPa" + elif self.length == "": + return "" + else: + return f" {self.force}/{self.length}^2" + + @property + def stress_scale(self) -> float: + """Returns the stress scale.""" + return 1 / self.force_factor * self.length_factor * self.length_factor + + @property + def length_3_unit(self) -> str: + """Returns the length^3 unit string.""" + return self.length if self.length == "" else f" {self.length}^3" + + @property + def length_3_scale(self) -> float: + """Returns the length^3 scale.""" + return 1 / self.length_factor**3 + + @property + def length_4_unit(self) -> str: + """Returns the length^4 unit string.""" + return self.length if self.length == "" else f" {self.length}^4" + + @property + def length_4_scale(self) -> float: + """Returns the length^4 scale.""" + return 1 / self.length_factor**4 + + +DEFAULT_UNITS = UnitDisplay(length="", force="", mass="") +si_n_mm = UnitDisplay(length="mm", force="N", mass="kg") +si_kn_m = UnitDisplay( + length="m", force="kN", mass="kg", length_factor=1e3, force_factor=1e3 +) diff --git a/src/concreteproperties/prestressed_section.py b/src/concreteproperties/prestressed_section.py index 21496361..442cb7ae 100644 --- a/src/concreteproperties/prestressed_section.py +++ b/src/concreteproperties/prestressed_section.py @@ -18,6 +18,8 @@ if TYPE_CHECKING: import sectionproperties.pre.geometry as sp_geom + from concreteproperties.post import UnitDisplay + class PrestressedSection(ConcreteSection): """Class for a prestressed concrete section. @@ -38,6 +40,7 @@ def __init__( geometry: sp_geom.CompoundGeometry, moment_centroid: tuple[float, float] | None = None, geometric_centroid_override: bool = True, + default_units: UnitDisplay | None = None, ) -> None: """Inits the ConcreteSection class. @@ -51,6 +54,8 @@ def __init__( geometric_centroid_override: If set to True, sets ``moment_centroid`` to the geometric centroid i.e. material properties applied. Defaults to ``True``. + default_units: Default unit system to use for formatting results. Defaults + to ``None``. Raises: ValueError: If the section is not symmetric about the y-axis @@ -60,6 +65,7 @@ def __init__( geometry=geometry, moment_centroid=moment_centroid, geometric_centroid_override=geometric_centroid_override, + default_units=default_units, ) # check symmetry about y-axis @@ -129,6 +135,7 @@ def calculate_cracked_properties( # pyright: ignore [reportIncompatibleMethodOv # initialise cracked results object cracked_results = res.CrackedResults( + default_units=self.default_units, theta=0, n=self.gross_properties.n_prestress + n_ext, m=m_ext, @@ -351,7 +358,9 @@ def moment_curvature_analysis( # pyright: ignore [reportIncompatibleMethodOverr # determine initial curvature that gives zero moment def find_intial_curvature(kappa0): # initialise moment curvature result - mk_res = res.MomentCurvatureResults(theta=theta, n_target=n) + mk_res = res.MomentCurvatureResults( + default_units=self.default_units, theta=theta, n_target=n + ) # find neutral axis that gives convergence of axial force brentq( @@ -541,6 +550,7 @@ def calculate_uncracked_stress( # pyright: ignore [reportIncompatibleMethodOver lumped_reinf_geoms.append(lumped_geom) return res.StressResult( + default_units=self.default_units, concrete_section=self, concrete_analysis_sections=conc_sections, concrete_stresses=conc_sigs, @@ -671,6 +681,7 @@ def calculate_cracked_stress( # pyright: ignore [reportIncompatibleMethodOverri lumped_reinf_geoms.append(lumped_geom) return res.StressResult( + default_units=self.default_units, concrete_section=self, concrete_analysis_sections=conc_sections, concrete_stresses=conc_sigs, @@ -720,7 +731,9 @@ def calculate_service_stress( # initialise variables mk = res.MomentCurvatureResults( - theta=0, n_target=moment_curvature_results.n_target + default_units=self.default_units, + theta=0, + n_target=moment_curvature_results.n_target, ) # find neutral axis that gives convergence of the axial force @@ -836,6 +849,7 @@ def calculate_service_stress( lumped_reinf_geoms.append(lumped_geom) return res.StressResult( + default_units=self.default_units, concrete_section=self, concrete_analysis_sections=conc_sections, concrete_stresses=conc_sigs, @@ -979,6 +993,7 @@ def calculate_ultimate_stress( lumped_reinf_geoms.append(lumped_geom) return res.StressResult( + default_units=self.default_units, concrete_section=self, concrete_analysis_sections=conc_sections, concrete_stresses=conc_sigs, diff --git a/src/concreteproperties/results.py b/src/concreteproperties/results.py index e465c445..d5f277af 100644 --- a/src/concreteproperties/results.py +++ b/src/concreteproperties/results.py @@ -14,17 +14,25 @@ import numpy as np from matplotlib.collections import PatchCollection from matplotlib.colors import CenteredNorm +from matplotlib.ticker import FuncFormatter from rich.console import Console from rich.table import Table from scipy.interpolate import interp1d from sectionproperties.pre.geometry import CompoundGeometry from shapely import Point, Polygon -from concreteproperties.post import plotting_context +from concreteproperties.post import ( + DEFAULT_UNITS, + plotting_context, + string_formatter, + string_formatter_plots, + string_formatter_stress, +) if TYPE_CHECKING: from concreteproperties.analysis_section import AnalysisSection from concreteproperties.concrete_section import ConcreteSection + from concreteproperties.post import UnitDisplay from concreteproperties.pre import CPGeom @@ -38,6 +46,9 @@ class GrossProperties: method. """ + # units + default_units: UnitDisplay + # section areas total_area: float = 0 concrete_area: float = 0 @@ -94,69 +105,282 @@ class GrossProperties: def print_results( self, - fmt: str = "8.6e", + eng: bool = True, + prec: int = 3, + units: UnitDisplay | None = None, ) -> None: """Prints the gross concrete section properties to the terminal. Args: - fmt: Number format. Defaults to ``"8.6e"``. + eng: If set to ``True``, formats with engineering notation. If set to + ``False``, formats with fixed notation. Defaults to ``True``. + prec: The desired precision (i.e. one plus this value is the desired number + of digits). Defaults to ``3``. + units: Unit system to display. Defaults to ``None``. """ + # setup table 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)) + # assign default unit if no units provided + if units is None: + units = self.default_units + + # add table rows + table.add_row( + "Total Area", + string_formatter( + value=self.total_area, eng=eng, prec=prec, scale=units.area_scale + ) + + units.area_unit, + ) + table.add_row( + "Concrete Area", + string_formatter( + value=self.concrete_area, eng=eng, prec=prec, scale=units.area_scale + ) + + units.area_unit, + ) if self.reinf_meshed_area: table.add_row( "Meshed Reinforcement Area", - "{:>{fmt}}".format(self.reinf_meshed_area, fmt=fmt), + string_formatter( + value=self.reinf_meshed_area, + eng=eng, + prec=prec, + scale=units.area_scale, + ) + + units.area_unit, ) table.add_row( "Lumped Reinforcement Area", - "{:>{fmt}}".format(self.reinf_lumped_area, fmt=fmt), + string_formatter( + value=self.reinf_lumped_area, eng=eng, prec=prec, scale=units.area_scale + ) + + units.area_unit, ) if self.strand_area: - table.add_row("Strand Area", "{:>{fmt}}".format(self.strand_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("x-Centroid (Gross)", "{:>{fmt}}".format(self.cx_gross, fmt=fmt)) - table.add_row("y-Centroid (Gross)", "{:>{fmt}}".format(self.cy_gross, 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( + "Strand Area", + string_formatter( + value=self.strand_area, eng=eng, prec=prec, scale=units.area_scale + ) + + units.area_unit, + ) + + table.add_row( + "Axial Rigidity (EA)", + string_formatter( + value=self.e_a, eng=eng, prec=prec, scale=units.force_scale + ) + + units.force_unit, + ) + table.add_row( + "Mass (per unit length)", + string_formatter( + value=self.mass, eng=eng, prec=prec, scale=units.mass_per_length_scale + ) + + units.mass_per_length_unit, + ) + table.add_row( + "Perimeter", + string_formatter( + value=self.perimeter, eng=eng, prec=prec, scale=units.length_scale + ) + + units.length_unit, + end_section=True, + ) + table.add_row( + "E.Qx", + string_formatter( + value=self.e_qx, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "E.Qy", + string_formatter( + value=self.e_qy, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "x-Centroid", + string_formatter( + value=self.cx, eng=eng, prec=prec, scale=units.length_scale + ) + + units.length_unit, + ) + table.add_row( + "y-Centroid", + string_formatter( + value=self.cy, eng=eng, prec=prec, scale=units.length_scale + ) + + units.length_unit, + ) + table.add_row( + "x-Centroid (Gross)", + string_formatter( + value=self.cx_gross, eng=eng, prec=prec, scale=units.length_scale + ) + + units.length_unit, + ) + table.add_row( + "y-Centroid (Gross)", + string_formatter( + value=self.cy_gross, eng=eng, prec=prec, scale=units.length_scale + ) + + units.length_unit, + end_section=True, + ) + table.add_row( + "E.Ixx_g", + string_formatter( + value=self.e_ixx_g, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.Iyy_g", + string_formatter( + value=self.e_iyy_g, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.Ixy_g", + string_formatter( + value=self.e_ixy_g, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.Ixx_c", + string_formatter( + value=self.e_ixx_c, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.Iyy_c", + string_formatter( + value=self.e_iyy_c, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.Ixy_c", + string_formatter( + value=self.e_ixy_c, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.I11", + string_formatter( + value=self.e_i11, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.I22", + string_formatter( + value=self.e_i22, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + + table.add_row( + "Principal Axis Angle", + string_formatter( + value=self.phi, eng=eng, prec=prec, scale=units.angle_scale + ) + + units.angle_unit, + end_section=True, + ) + table.add_row( + "E.Zxx+", + string_formatter( + value=self.e_zxx_plus, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "E.Zxx-", + string_formatter( + value=self.e_zxx_minus, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "E.Zyy+", + string_formatter( + value=self.e_zyy_plus, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "E.Zyy-", + string_formatter( + value=self.e_zyy_minus, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "E.Z11+", + string_formatter( + value=self.e_z11_plus, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "E.Z11-", + string_formatter( + value=self.e_z11_minus, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "E.Z22+", + string_formatter( + value=self.e_z22_plus, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "E.Z22-", + string_formatter( + value=self.e_z22_minus, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + end_section=True, + ) table.add_row( "Ultimate Concrete Strain", - "{:>{fmt}}".format(self.conc_ultimate_strain, fmt=fmt), + string_formatter(value=self.conc_ultimate_strain, eng=eng, prec=prec), + end_section=True, ) # add prestressed results if they exist if self.n_prestress: - table.add_row("n_prestress", "{:>{fmt}}".format(self.n_prestress, fmt=fmt)) - table.add_row("m_prestress", "{:>{fmt}}".format(self.m_prestress, fmt=fmt)) + table.add_row( + "n_prestress", + string_formatter( + value=self.n_prestress, eng=eng, prec=prec, scale=units.force_scale + ) + + units.force_unit, + ) + table.add_row( + "m_prestress", + string_formatter( + value=self.m_prestress, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) console = Console() console.print(table) @@ -167,10 +391,14 @@ class TransformedGrossProperties: """Class for storing transformed gross concrete section properties. Args: - concrete_properties: Concrete properties object + default_units: Default units to use for reporting + concrete_properties: Gross properties object elastic_modulus: Reference elastic modulus """ + # units + default_units: UnitDisplay + concrete_properties: GrossProperties = field(repr=False) elastic_modulus: float @@ -225,37 +453,171 @@ def __post_init__(self) -> None: def print_results( self, - fmt: str = "8.6e", + eng: bool = True, + prec: int = 3, + units: UnitDisplay | None = None, ) -> None: """Prints the transformed gross concrete section properties to the terminal. Args: - fmt: Number format. Defaults to ``"8.6e"``. + eng: If set to ``True``, formats with engineering notation. If set to + ``False``, formats with fixed notation. Defaults to ``True``. + prec: The desired precision (i.e. one plus this value is the desired number + of digits). Defaults to ``3``. + units: Unit system to display. Defaults to ``None``. """ + # setup table table = Table(title="Transformed 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("E_ref", "{:>{fmt}}".format(self.elastic_modulus, fmt=fmt)) - table.add_row("Area", "{:>{fmt}}".format(self.area, fmt=fmt)) - table.add_row("Qx", "{:>{fmt}}".format(self.qx, fmt=fmt)) - table.add_row("Qy", "{:>{fmt}}".format(self.qy, fmt=fmt)) - table.add_row("Ixx_g", "{:>{fmt}}".format(self.ixx_g, fmt=fmt)) - table.add_row("Iyy_g", "{:>{fmt}}".format(self.iyy_g, fmt=fmt)) - table.add_row("Ixy_g", "{:>{fmt}}".format(self.ixy_g, fmt=fmt)) - table.add_row("Ixx_c", "{:>{fmt}}".format(self.ixx_c, fmt=fmt)) - table.add_row("Iyy_c", "{:>{fmt}}".format(self.iyy_c, fmt=fmt)) - table.add_row("Ixy_c", "{:>{fmt}}".format(self.ixy_c, fmt=fmt)) - table.add_row("I11", "{:>{fmt}}".format(self.i11, fmt=fmt)) - table.add_row("I22", "{:>{fmt}}".format(self.i22, fmt=fmt)) - table.add_row("Zxx+", "{:>{fmt}}".format(self.zxx_plus, fmt=fmt)) - table.add_row("Zxx-", "{:>{fmt}}".format(self.zxx_minus, fmt=fmt)) - table.add_row("Zyy+", "{:>{fmt}}".format(self.zyy_plus, fmt=fmt)) - table.add_row("Zyy-", "{:>{fmt}}".format(self.zyy_minus, fmt=fmt)) - table.add_row("Z11+", "{:>{fmt}}".format(self.z11_plus, fmt=fmt)) - table.add_row("Z11-", "{:>{fmt}}".format(self.z11_minus, fmt=fmt)) - table.add_row("Z22+", "{:>{fmt}}".format(self.z22_plus, fmt=fmt)) - table.add_row("Z22-", "{:>{fmt}}".format(self.z22_minus, fmt=fmt)) + # assign default unit if no units provided + if units is None: + units = self.default_units + + # add table rows + table.add_row( + "E_ref", + string_formatter( + value=self.elastic_modulus, eng=eng, prec=prec, scale=units.stress_scale + ) + + units.stress_unit, + ) + table.add_row( + "Area", + string_formatter( + value=self.area, eng=eng, prec=prec, scale=units.area_scale + ) + + units.area_unit, + end_section=True, + ) + table.add_row( + "Qx", + string_formatter( + value=self.qx, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) + table.add_row( + "Qy", + string_formatter( + value=self.qy, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) + table.add_row( + "Ixx_g", + string_formatter( + value=self.ixx_g, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "Iyy_g", + string_formatter( + value=self.iyy_g, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "Ixy_g", + string_formatter( + value=self.ixy_g, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "Ixx_c", + string_formatter( + value=self.ixx_c, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "Iyy_c", + string_formatter( + value=self.iyy_c, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "Ixy_c", + string_formatter( + value=self.ixy_c, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "I11", + string_formatter( + value=self.i11, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "I22", + string_formatter( + value=self.i22, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + end_section=True, + ) + table.add_row( + "Zxx+", + string_formatter( + value=self.zxx_plus, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) + table.add_row( + "Zxx-", + string_formatter( + value=self.zxx_minus, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) + table.add_row( + "Zyy+", + string_formatter( + value=self.zyy_plus, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) + table.add_row( + "Zyy-", + string_formatter( + value=self.zyy_minus, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) + table.add_row( + "Z11+", + string_formatter( + value=self.z11_plus, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) + table.add_row( + "Z11-", + string_formatter( + value=self.z11_minus, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) + table.add_row( + "Z22+", + string_formatter( + value=self.z22_plus, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) + table.add_row( + "Z22-", + string_formatter( + value=self.z22_minus, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) console = Console() console.print(table) @@ -271,10 +633,14 @@ class CrackedResults: method. Args: + default_units: Default units to use for reporting theta: Angle (in radians) the neutral axis makes with the horizontal axis (:math:`-\pi \leq \theta \leq \pi`) """ + # units + default_units: UnitDisplay + theta: float n: float = 0 m: float = 0 @@ -374,69 +740,315 @@ def plot_cracked_geometries( def print_results( self, - fmt: str = "8.6e", + eng: bool = True, + prec: int = 3, + units: UnitDisplay | None = None, ) -> None: - """Prints the cracked concrete section properties to the terminal. + """Prints cracked concrete section properties to the terminal. + + If ``calculate_transformed_properties()`` has been called, also prints the + transformed properties. Args: - fmt: Number format. Defaults to ``"8.6e"``. + eng: If set to ``True``, formats with engineering notation. If set to + ``False``, formats with fixed notation. Defaults to ``True``. + prec: The desired precision (i.e. one plus this value is the desired number + of digits). Defaults to ``3``.. + units: Unit system to display. Defaults to ``None``. """ + # setup table 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)) - table.add_row("n", "{:>{fmt}}".format(self.n, fmt=fmt)) - table.add_row("m", "{:>{fmt}}".format(self.m, fmt=fmt)) + # assign default unit if no units provided + if units is None: + units = self.default_units + + # add table rows + table.add_row( + "theta", + string_formatter( + value=self.theta, eng=eng, prec=prec, scale=units.angle_scale + ) + + units.angle_unit, + ) + + # only show n & m if one is non-zero + if self.n != 0 or self.m != 0: + table.add_row( + "n", + string_formatter( + value=self.n, eng=eng, prec=prec, scale=units.force_scale + ) + + units.force_unit, + ) + table.add_row( + "m", + string_formatter( + value=self.m, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) - if self.elastic_modulus_ref: + if self.elastic_modulus_ref is not None: table.add_row( - "E_ref", "{:>{fmt}}".format(self.elastic_modulus_ref, fmt=fmt) + "E_ref", + string_formatter( + value=self.elastic_modulus_ref, + eng=eng, + prec=prec, + scale=units.stress_scale, + ) + + units.stress_unit, ) + table.add_section() + if isinstance(self.m_cr, tuple): - table.add_row("m_cr_pos", "{:>{fmt}}".format(self.m_cr[0], fmt=fmt)) - table.add_row("m_cr_neg", "{:>{fmt}}".format(self.m_cr[1], fmt=fmt)) + table.add_row( + "m_cr_pos", + string_formatter( + value=self.m_cr[0], eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "m_cr_neg", + string_formatter( + value=self.m_cr[1], eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) else: - 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("I11_cr", "{:>{fmt}}".format(self.i11_cr, fmt=fmt)) - table.add_row("I22_cr", "{:>{fmt}}".format(self.i22_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)) - table.add_row("E.I11_cr", "{:>{fmt}}".format(self.e_i11_cr, fmt=fmt)) - table.add_row("E.I22_cr", "{:>{fmt}}".format(self.e_i22_cr, fmt=fmt)) - table.add_row("phi_cr", "{:>{fmt}}".format(self.phi_cr, fmt=fmt)) + table.add_row( + "m_cr", + string_formatter( + value=self.m_cr, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + + table.add_row( + "d_nc", + string_formatter( + value=self.d_nc, eng=eng, prec=prec, scale=units.length_scale + ) + + units.length_unit, + ) + + if self.a_cr is not None: + table.add_row( + "A_cr", + string_formatter( + value=self.a_cr, eng=eng, prec=prec, scale=units.area_scale + ) + + units.area_unit, + ) + + table.add_row( + "E.A_cr", + string_formatter( + value=self.e_a_cr, eng=eng, prec=prec, scale=units.force_scale + ) + + units.force_unit, + end_section=True, + ) + + if self.qx_cr is not None and self.qy_cr is not None: + table.add_row( + "Qx_cr", + string_formatter( + value=self.qx_cr, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) + table.add_row( + "Qy_cr", + string_formatter( + value=self.qy_cr, eng=eng, prec=prec, scale=units.length_3_scale + ) + + units.length_3_unit, + ) + + table.add_row( + "E.Qx_cr", + string_formatter( + value=self.e_qx_cr, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "E.Qy_cr", + string_formatter( + value=self.e_qy_cr, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "x-Centroid", + string_formatter( + value=self.cx, eng=eng, prec=prec, scale=units.length_scale + ) + + units.length_unit, + ) + table.add_row( + "y-Centroid", + string_formatter( + value=self.cy, eng=eng, prec=prec, scale=units.length_scale + ) + + units.length_unit, + end_section=True, + ) + + if ( + self.ixx_g_cr is not None + and self.iyy_g_cr is not None + and self.ixy_g_cr is not None + and self.ixx_c_cr is not None + and self.iyy_c_cr is not None + and self.ixy_c_cr is not None + and self.iuu_cr is not None + and self.i11_cr is not None + and self.i22_cr is not None + ): + table.add_row( + "Ixx_g_cr", + string_formatter( + value=self.ixx_g_cr, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "Iyy_g_cr", + string_formatter( + value=self.iyy_g_cr, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "Ixy_g_cr", + string_formatter( + value=self.ixy_g_cr, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "Ixx_c_cr", + string_formatter( + value=self.ixx_c_cr, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "Iyy_c_cr", + string_formatter( + value=self.iyy_c_cr, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "Ixy_c_cr", + string_formatter( + value=self.ixy_c_cr, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "Iuu_cr", + string_formatter( + value=self.iuu_cr, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "I11_cr", + string_formatter( + value=self.i11_cr, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + ) + table.add_row( + "I22_cr", + string_formatter( + value=self.i22_cr, eng=eng, prec=prec, scale=units.length_4_scale + ) + + units.length_4_unit, + end_section=True, + ) + + table.add_row( + "E.Ixx_g_cr", + string_formatter( + value=self.e_ixx_g_cr, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.Iyy_g_cr", + string_formatter( + value=self.e_iyy_g_cr, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.Ixy_g_cr", + string_formatter( + value=self.e_ixy_g_cr, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.Ixx_c_cr", + string_formatter( + value=self.e_ixx_c_cr, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.Iyy_c_cr", + string_formatter( + value=self.e_iyy_c_cr, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.Ixy_c_cr", + string_formatter( + value=self.e_ixy_c_cr, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.Iuu_cr", + string_formatter( + value=self.e_iuu_cr, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.I11_cr", + string_formatter( + value=self.e_i11_cr, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + ) + table.add_row( + "E.I22_cr", + string_formatter( + value=self.e_i22_cr, eng=eng, prec=prec, scale=units.flex_rig_scale + ) + + units.flex_rig_unit, + end_section=True, + ) + + table.add_row( + "phi_cr", + string_formatter( + value=self.phi_cr, eng=eng, prec=prec, scale=units.angle_scale + ) + + units.angle_unit, + ) console = Console() console.print(table) @@ -447,6 +1059,7 @@ class MomentCurvatureResults: r"""Class for storing moment curvature results. Args: + default_units: Default units to use for reporting theta: Angle (in radians) the neutral axis makes with the horizontal n_target: Target axial force axis (:math:`-\pi \leq \theta \leq \pi`) kappa: List of curvatures @@ -461,6 +1074,9 @@ class MomentCurvatureResults: indicates failure. """ + # units + default_units: UnitDisplay + # results theta: float n_target: float @@ -482,32 +1098,55 @@ class MomentCurvatureResults: def plot_results( self, - m_scale: float = 1e-6, fmt: str = "o-", + eng: bool = False, + prec: int = 2, + units: UnitDisplay | None = None, **kwargs, ) -> matplotlib.axes.Axes: """Plots the moment curvature results. Args: - m_scale: Scaling factor to apply to bending moment. Defaults ``1e-6``. fmt: Plot format string. Defaults ``"o-"``. + eng: If set to ``True``, formats the plot ticks with engineering notation. + If set to ``False``, uses the default ``matplotlib`` ticker formatting. + Defaults to ``False``. + prec: If ``eng=True``, sets the desired precision of the ticker formatting + (i.e. one plus this value is the desired number of digits). Defaults to + ``2``. + units: Unit system to display. Defaults to ``None``. kwargs: Passed to :func:`~concreteproperties.post.plotting_context` Returns: Matplotlib axes object """ + # assign default unit if no units provided + if units is None: + units = self.default_units + + # check moment unit + moment_unit = "-" if units is DEFAULT_UNITS else units.moment_unit[1:] + # scale moments - moments = np.array(self.m_xy) * m_scale + moments = np.array(self.m_xy) * units.moment_scale # create plot and setup the plot - with plotting_context(title="Moment-Curvature", **kwargs) as (fig, ax): + with plotting_context(title="Moment-Curvature", **kwargs) as (_, ax): if ax is None: msg = "Plot failed." raise RuntimeError(msg) ax.plot(self.kappa, moments, fmt) - plt.xlabel("Curvature") - plt.ylabel("Moment") + + if eng: + tick_formatter = FuncFormatter( + lambda x, _: string_formatter_plots(value=x, prec=prec) + ) + ax.xaxis.set_major_formatter(tick_formatter) + ax.yaxis.set_major_formatter(tick_formatter) + + plt.xlabel("Curvature [-]") + plt.ylabel(f"Bending Moment [{moment_unit}]") plt.grid(True) return ax @@ -516,8 +1155,10 @@ def plot_results( def plot_multiple_results( moment_curvature_results: list[MomentCurvatureResults], labels: list[str], - m_scale: float = 1e-6, fmt: str = "o-", + eng: bool = False, + prec: int = 2, + units: UnitDisplay | None = None, **kwargs, ) -> matplotlib.axes.Axes: """Plots multiple moment curvature results. @@ -525,15 +1166,28 @@ def plot_multiple_results( Args: moment_curvature_results: List of moment curvature results objects labels: List of labels for each moment curvature diagram - m_scale: Scaling factor to apply to bending moment. Defaults ``1e-6``. fmt: Plot format string. Defaults ``"o-"``. + eng: If set to ``True``, formats the plot ticks with engineering notation. + If set to ``False``, uses the default ``matplotlib`` ticker formatting. + Defaults to ``False``. + prec: If ``eng=True``, sets the desired precision of the ticker formatting + (i.e. one plus this value is the desired number of digits). Defaults to + ``2``. + units: Unit system to display. Defaults to ``None``. kwargs: Passed to :func:`~concreteproperties.post.plotting_context` Returns: Matplotlib axes object """ - # create plot and setup the plot - with plotting_context(title="Moment-Curvature", **kwargs) as (fig, ax): + # assign default unit if no units applied + if units is None: + units = DEFAULT_UNITS + moment_unit = "-" + else: + moment_unit = units.moment_unit[1:] + + # create plot and setup the plots + with plotting_context(title="Moment-Curvature", **kwargs) as (_, ax): if ax is None: msg = "Plot failed." raise RuntimeError(msg) @@ -544,12 +1198,19 @@ def plot_multiple_results( for idx, mk_result in enumerate(moment_curvature_results): # scale results kappas = np.array(mk_result.kappa) - moments = np.array(mk_result.m_xy) * m_scale + moments = np.array(mk_result.m_xy) * units.moment_scale ax.plot(kappas, moments, fmt, label=labels[idx]) - plt.xlabel("Curvature") - plt.ylabel("Moment") + if eng: + tick_formatter = FuncFormatter( + lambda x, _: string_formatter_plots(value=x, prec=prec) + ) + ax.xaxis.set_major_formatter(tick_formatter) + ax.yaxis.set_major_formatter(tick_formatter) + + plt.xlabel("Curvature [-]") + plt.ylabel(f"Bending Moment [{moment_unit}]") plt.grid(True) # if there is more than one curve show legend @@ -613,6 +1274,7 @@ class UltimateBendingResults: r"""Class for storing ultimate bending results. Args: + default_units: Default units to use for reporting theta: Angle (in radians) the neutral axis makes with the horizontal axis (:math:`-\pi \leq \theta \leq \pi`) d_n: Ultimate neutral axis depth @@ -624,6 +1286,9 @@ class UltimateBendingResults: label: Result label """ + # units + default_units: UnitDisplay + # bending angle theta: float @@ -642,29 +1307,77 @@ class UltimateBendingResults: def print_results( self, - fmt: str = "8.6e", + eng: bool = True, + prec: int = 3, + units: UnitDisplay | None = None, ) -> None: """Prints the ultimate bending results to the terminal. Args: - fmt: Number format. Defaults to ``"8.6e"``. + eng: If set to ``True``, formats with engineering notation. If set to + ``False``, formats with fixed notation. Defaults to ``True``. + prec: The desired precision (i.e. one plus this value is the desired number + of digits). Defaults to ``3``. + units: Unit system to display. Defaults to ``None``. """ + # setup table 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") + # assign default unit if no units provided + if units is None: + units = self.default_units + + # add table rows if self.label: - table.add_row("Label", self.label) + table.add_row("Label", self.label, end_section=True) - 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) + "Bending Angle - theta", + string_formatter( + value=self.theta, eng=eng, prec=prec, scale=units.angle_scale + ) + + units.angle_unit, + ) + table.add_row( + "Neutral Axis Depth - d_n", + string_formatter( + value=self.d_n, eng=eng, prec=prec, scale=units.length_scale + ) + + units.length_unit, + ) + table.add_row( + "Neutral Axis Parameter - k_u", + string_formatter(value=self.k_u, eng=eng, prec=prec), + end_section=True, + ) + table.add_row( + "Axial Force", + string_formatter(value=self.n, eng=eng, prec=prec, scale=units.force_scale) + + units.force_unit, + ) + table.add_row( + "Bending Capacity - m_x", + string_formatter( + value=self.m_x, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "Bending Capacity - m_y", + string_formatter( + value=self.m_y, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, + ) + table.add_row( + "Bending Capacity - m_xy", + string_formatter( + value=self.m_xy, eng=eng, prec=prec, scale=units.moment_scale + ) + + units.moment_unit, ) - 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_xy", "{:>{fmt}}".format(self.m_xy, fmt=fmt)) console = Console() console.print(table) @@ -675,9 +1388,13 @@ class MomentInteractionResults: """Class for storing moment interaction results. Args: + default_units: Default units to use for reporting results: List of ultimate bending result objects """ + # units + default_units: UnitDisplay + results: list[UltimateBendingResults] = field(default_factory=list) def sort_results(self) -> None: @@ -731,20 +1448,18 @@ def get_results_lists( def plot_diagram( self, - n_scale: float = 1e-3, - m_scale: float = 1e-6, moment: str = "m_x", fmt: str = "o-", labels: bool = False, label_offset: bool = False, + eng: bool = False, + prec: int = 2, + units: UnitDisplay | None = None, **kwargs, ) -> matplotlib.axes.Axes: """Plots a moment interaction diagram. Args: - n_scale: Scaling factor to apply to axial force. Defaults to ``1e-3``. - m_scale: Scaling factor to apply to the bending moment. Defaults to - ``1e-6``. moment: Which moment to plot, acceptable values are ``"m_x"``, ``"m_y"`` or ``"m_xy"``. Defaults to ``"m_x"``. fmt: Plot format string. Defaults to ``"o-"``. @@ -752,16 +1467,32 @@ def plot_diagram( ``False``. label_offset: If set to True, attempts to offset the label from the diagram. Defaults to ``False``. + eng: If set to ``True``, formats the plot ticks with engineering notation. + If set to ``False``, uses the default ``matplotlib`` ticker formatting. + Defaults to ``False``. + prec: If ``eng=True``, sets the desired precision of the ticker formatting + (i.e. one plus this value is the desired number of digits). Defaults to + ``2``. + units: Unit system to display. Defaults to ``None``. kwargs: Passed to :func:`~concreteproperties.post.plotting_context` Returns: Matplotlib axes object """ - # create plot and setup the plot - with plotting_context(title="Moment Interaction Diagram", **kwargs) as ( - fig, - ax, - ): + # assign default unit if no units provided + if units is None: + units = self.default_units + + # check moment/force unit + if units is DEFAULT_UNITS: + moment_unit = "-" + force_unit = "-" + else: + moment_unit = units.moment_unit[1:] + force_unit = units.force_unit[1:] + + # create plot and setup the plots + with plotting_context(title="Moment Interaction Diagram", **kwargs) as (_, ax): if ax is None: msg = "Plot failed." raise RuntimeError(msg) @@ -770,8 +1501,8 @@ def plot_diagram( n_list, m_list = self.get_results_lists(moment=moment) # scale results - forces = np.array(n_list) * n_scale - moments = np.array(m_list) * m_scale + forces = np.array(n_list) * units.force_scale + moments = np.array(m_list) * units.moment_scale # plot diagram ax.plot(moments, forces, fmt) @@ -788,8 +1519,8 @@ def plot_diagram( for idx, m in enumerate(m_list): if self.results[idx].label is not None: # get x,y position on plot - x = m * m_scale - y = n_list[idx] * n_scale + x = m * units.moment_scale + y = n_list[idx] * units.force_scale if label_offset: # calculate text offset @@ -820,8 +1551,15 @@ def plot_diagram( **annotate_dict, ) - plt.xlabel("Bending Moment") - plt.ylabel("Axial Force") + if eng: + tick_formatter = FuncFormatter( + lambda x, _: string_formatter_plots(value=x, prec=prec) + ) + ax.xaxis.set_major_formatter(tick_formatter) + ax.yaxis.set_major_formatter(tick_formatter) + + plt.xlabel(f"Bending Moment [{moment_unit}]") + plt.ylabel(f"Axial Force [{force_unit}]") plt.grid(True) return ax @@ -830,10 +1568,11 @@ def plot_diagram( def plot_multiple_diagrams( moment_interaction_results: list[MomentInteractionResults], labels: list[str], - n_scale: float = 1e-3, - m_scale: float = 1e-6, moment: str = "m_x", fmt: str = "o-", + eng: bool = False, + prec: int = 2, + units: UnitDisplay | None = None, **kwargs, ) -> matplotlib.axes.Axes: """Plots multiple moment interaction diagrams. @@ -841,21 +1580,32 @@ def plot_multiple_diagrams( Args: moment_interaction_results: List of moment interaction results objects labels: List of labels for each moment interaction diagram. - n_scale: Scaling factor to apply to axial force. Defaults to ``1e-3``. - m_scale: Scaling factor to apply to bending moment. Defaults to ``1e-6``. moment: Which moment to plot, acceptable values are ``"m_x"``, ``"m_y"`` or ``"m_xy"``. Defaults to ``"m_x"``. fmt: Plot format string. Defaults to ``"o-"``. + eng: If set to ``True``, formats the plot ticks with engineering notation. + If set to ``False``, uses the default ``matplotlib`` ticker formatting. + Defaults to ``False``. + prec: If ``eng=True``, sets the desired precision of the ticker formatting + (i.e. one plus this value is the desired number of digits). Defaults to + ``2``. + units: Unit system to display. Defaults to ``None``. kwargs: Passed to :func:`~concreteproperties.post.plotting_context` Returns: Matplotlib axes object """ - # create plot and setup the plot - with plotting_context(title="Moment Interaction Diagram", **kwargs) as ( - fig, - ax, - ): + # assign default unit if no units applied + if units is None: + units = DEFAULT_UNITS + force_unit = "-" + moment_unit = "-" + else: + force_unit = units.force_unit[1:] + moment_unit = units.moment_unit[1:] + + # create plot and setup the plots + with plotting_context(title="Moment Interaction Diagram", **kwargs) as (_, ax): if ax is None: msg = "Plot failed." raise RuntimeError(msg) @@ -867,13 +1617,23 @@ def plot_multiple_diagrams( n_list, m_list = mi_result.get_results_lists(moment=moment) # scale results - forces = np.array(n_list) * n_scale - moments = np.array(m_list) * m_scale + forces = np.array(n_list) * units.force_scale + moments = np.array(m_list) * units.moment_scale ax.plot(moments, forces, fmt, label=labels[idx]) - plt.xlabel("Bending Moment") - plt.ylabel("Axial Force") + if eng: + x_formatter = FuncFormatter( + lambda x, _: string_formatter_plots(value=x, prec=prec) + ) + y_formatter = FuncFormatter( + lambda y, _: string_formatter_plots(value=y, prec=prec) + ) + ax.xaxis.set_major_formatter(x_formatter) + ax.yaxis.set_major_formatter(y_formatter) + + plt.xlabel(f"Bending Moment [{moment_unit}]") + plt.ylabel(f"Axial Force [{force_unit}]") plt.grid(True) # if there is more than one curve show legend @@ -919,10 +1679,14 @@ class BiaxialBendingResults: """Class for storing biaxial bending results. Args: + default_units: Default units to use for reporting n: Net axial force results: List of ultimate bending result objects """ + # units + default_units: UnitDisplay + n: float results: list[UltimateBendingResults] = field(default_factory=list) @@ -946,38 +1710,72 @@ def get_results_lists( def plot_diagram( self, - m_scale: float = 1e-6, fmt: str = "o-", + eng: bool = False, + prec: int = 2, + units: UnitDisplay | None = None, **kwargs, ) -> matplotlib.axes.Axes: """Plots a biaxial bending diagram. Args: - m_scale: Scaling factor to apply to bending moment. Defaults to ``1e-6``. fmt: Plot format string. Defaults to ``"o-"``. + eng: If set to ``True``, formats the plot ticks with engineering notation. + If set to ``False``, uses the default ``matplotlib`` ticker formatting. + Defaults to ``False``. + prec: If ``eng=True``, sets the desired precision of the ticker formatting + (i.e. one plus this value is the desired number of digits). Defaults to + ``2``. + units: Unit system to display. Defaults to ``None``. kwargs: Passed to :func:`~concreteproperties.post.plotting_context` Returns: Matplotlib axes object """ + # assign default unit if no units provided + if units is None: + units = self.default_units + + # check moment/force unit + if units is DEFAULT_UNITS: + moment_unit = "-" + force_unit = "-" + else: + moment_unit = units.moment_unit[1:] + force_unit = units.force_unit[1:] + + # get biaxial results m_x_list, m_y_list = self.get_results_lists() - # create plot and setup the plot + # format axial force string + if eng: + n_str = string_formatter_plots(value=self.n * units.force_scale, prec=prec) + else: + n_str = f"{self.n * units.force_scale:.2e}" + + # create plot and setup the plots with plotting_context( - title=f"Biaxial Bending Diagram, $N = {self.n:.3e}$", **kwargs + title=f"Biaxial Bending Diagram, N = {n_str} {force_unit}", **kwargs ) as (fig, ax): if ax is None: msg = "Plot failed." raise RuntimeError(msg) # scale results - m_x = np.array(m_x_list) * m_scale - m_y = np.array(m_y_list) * m_scale + m_x = np.array(m_x_list) * units.moment_scale + m_y = np.array(m_y_list) * units.moment_scale ax.plot(m_x, m_y, fmt) - plt.xlabel("Bending Moment $M_x$") - plt.ylabel("Bending Moment $M_y$") + if eng: + tick_formatter = FuncFormatter( + lambda x, _: string_formatter_plots(value=x, prec=prec) + ) + ax.xaxis.set_major_formatter(tick_formatter) + ax.yaxis.set_major_formatter(tick_formatter) + + plt.xlabel("Bending Moment $M_x$" + f" [{moment_unit}]") + plt.ylabel("Bending Moment $M_y$" + f" [{moment_unit}]") plt.grid(True) return ax @@ -986,8 +1784,10 @@ def plot_diagram( def plot_multiple_diagrams_2d( biaxial_bending_results: list[BiaxialBendingResults], labels: list[str] | None = None, - m_scale: float = 1e-6, fmt: str = "o-", + eng: bool = False, + prec: int = 2, + units: UnitDisplay | None = None, **kwargs, ) -> matplotlib.axes.Axes: """Plots multiple biaxial bending diagrams in a 2D plot. @@ -996,15 +1796,32 @@ def plot_multiple_diagrams_2d( biaxial_bending_results: List of biaxial bending results objects labels: List of labels for each biaxial bending diagram, if not provided labels are axial forces. Defaults to ``None``. - m_scale: Scaling factor to apply to bending moment. Defaults to ``1e-6``. fmt: Plot format string. Defaults to ``"o-"``. + eng: If set to ``True``, formats the plot ticks with engineering notation. + If set to ``False``, uses the default ``matplotlib`` ticker formatting. + Defaults to ``False``. + prec: If ``eng=True``, sets the desired precision of the ticker formatting + (i.e. one plus this value is the desired number of digits). Defaults to + ``2``. + units: Unit system to display. Defaults to ``None``. kwargs: Passed to :func:`~concreteproperties.post.plotting_context` Returns: Matplotlib axes object """ - # create plot and setup the plot - with plotting_context(title="Biaxial Bending Diagram", **kwargs) as (fig, ax): + # assign default unit if no units applied + if units is None: + units = DEFAULT_UNITS + force_unit = "" + moment_unit = "-" + else: + force_unit = units.force_unit[1:] + moment_unit = units.moment_unit[1:] + + # create plot and setup the plots + with plotting_context( + title="Biaxial Bending Diagram", aspect=True, **kwargs + ) as (_, ax): if ax is None: msg = "Plot failed." raise RuntimeError(msg) @@ -1023,17 +1840,31 @@ def plot_multiple_diagrams_2d( m_x_list, m_y_list = bb_result.get_results_lists() # scale results - m_x_list = np.array(m_x_list) * m_scale - m_y_list = np.array(m_y_list) * m_scale + m_x_list = np.array(m_x_list) * units.moment_scale + m_y_list = np.array(m_y_list) * units.moment_scale # generate default labels if default_labels: - labels.append(f"N = {bb_result.n:.3e}") + if eng: + n_str = string_formatter_plots( + value=bb_result.n * units.force_scale, prec=prec + ) + else: + n_str = f"{bb_result.n * units.force_scale:.3e}" + + labels.append(f"N = {n_str} {force_unit}") ax.plot(m_x_list, m_y_list, fmt, label=labels[idx]) - plt.xlabel("Bending Moment $M_x$") - plt.ylabel("Bending Moment $M_y$") + if eng: + tick_formatter = FuncFormatter( + lambda x, _: string_formatter_plots(value=x, prec=prec) + ) + ax.xaxis.set_major_formatter(tick_formatter) + ax.yaxis.set_major_formatter(tick_formatter) + + plt.xlabel("Bending Moment $M_x$" + f" [{moment_unit}]") + plt.ylabel("Bending Moment $M_y$" + f" [{moment_unit}]") plt.grid(True) # if there is more than one curve show legend @@ -1045,22 +1876,37 @@ def plot_multiple_diagrams_2d( @staticmethod def plot_multiple_diagrams_3d( biaxial_bending_results: list[BiaxialBendingResults], - n_scale: float = 1e-3, - m_scale: float = 1e-6, fmt: str = "-", + eng: bool = False, + prec: int = 2, + units: UnitDisplay | None = None, ) -> matplotlib.axes.Axes: """Plots multiple biaxial bending diagrams in a 3D plot. Args: biaxial_bending_results: List of biaxial bending results objects - n_scale: Scaling factor to apply to axial force. Defaults to ``1e-3``. - m_scale: Scaling factor to apply to bending moment. Defaults to ``1e-6``. fmt: Plot format string. Defaults to ``"-"``. + eng: If set to ``True``, formats the plot ticks with engineering notation. + If set to ``False``, uses the default ``matplotlib`` ticker formatting. + Defaults to ``False``. + prec: If ``eng=True``, sets the desired precision of the ticker formatting + (i.e. one plus this value is the desired number of digits). Defaults to + ``2``. + units: Unit system to display. Defaults to ``None``. Returns: Matplotlib axes object """ - # make 3d plot + # assign default unit if no units applied + if units is None: + units = DEFAULT_UNITS + force_unit = "" + moment_unit = "-" + else: + force_unit = units.force_unit[1:] + moment_unit = units.moment_unit[1:] + + # make 3d plots plt.figure() ax = plt.axes(projection="3d") @@ -1069,15 +1915,23 @@ def plot_multiple_diagrams_3d( m_x_list, m_y_list = bb_result.get_results_lists() # scale results - n_list = bb_result.n * n_scale * np.ones(len(m_x_list)) - m_x_list = np.array(m_x_list) * m_scale - m_y_list = np.array(m_y_list) * m_scale + n_list = bb_result.n * units.force_scale * np.ones(len(m_x_list)) + m_x_list = np.array(m_x_list) * units.moment_scale + m_y_list = np.array(m_y_list) * units.moment_scale ax.plot3D(m_x_list, m_y_list, n_list, fmt) # pyright: ignore - ax.set_xlabel("Bending Moment $M_x$") - ax.set_ylabel("Bending Moment $M_y$") - ax.set_zlabel("Axial Force $N$") # pyright: ignore + if eng: + tick_formatter = FuncFormatter( + lambda x, _: string_formatter_plots(value=x, prec=prec) + ) + ax.xaxis.set_major_formatter(tick_formatter) + ax.yaxis.set_major_formatter(tick_formatter) + ax.zaxis.set_major_formatter(tick_formatter) # pyright: ignore + + plt.xlabel("Bending Moment $M_x$" + f" [{moment_unit}]") + plt.ylabel("Bending Moment $M_y$" + f" [{moment_unit}]") + ax.set_zlabel("Axial Force $N$" + f" [{force_unit}]") # pyright: ignore plt.show() return ax @@ -1111,6 +1965,7 @@ class StressResult: The lever arm is computed to the elastic centroid. Args: + default_units: Default units to use for reporting 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 @@ -1141,6 +1996,9 @@ class StressResult: (``force``, ``d_x``, ``d_y``) """ + # units + default_units: UnitDisplay + concrete_section: ConcreteSection concrete_analysis_sections: list[AnalysisSection] concrete_stresses: list[np.ndarray] @@ -1163,6 +2021,9 @@ def plot_stress( title: str = "Stress", conc_cmap: str = "RdGy", reinf_cmap: str = "bwr", + eng: bool = False, + prec: int = 2, + units: UnitDisplay | None = None, **kwargs, ) -> matplotlib.axes.Axes: """Plots concrete and steel stresses on a concrete section. @@ -1171,11 +2032,30 @@ def plot_stress( title: Plot title. Defaults to ``"Stress"``. conc_cmap: Colour map for the concrete stress. Defaults to ``"RdGy"``. reinf_cmap: Colour map for the reinforcement stress. Defaults to ``"bwr"``. + eng: If set to ``True``, formats with engineering notation. If set to + ``False``, formats with fixed notation. Defaults to ``False``. + prec: The desired precision (i.e. one plus this value is the desired number + of digits). Defaults to ``2``. + units: Unit system to display. Defaults to ``None``. kwargs: Passed to :func:`~concreteproperties.post.plotting_context` Returns: Matplotlib axes object """ + # assign default unit if no units provided + if units is None: + units = self.default_units + + # check stress unit + stress_unit = "-" if units is DEFAULT_UNITS else units.stress_unit[1:] + + # assign default unit if no units applied + if units is None: + units = DEFAULT_UNITS + stress_unit = "-" + else: + stress_unit = units.stress_unit[1:] + with plotting_context( title=title, aspect=True, @@ -1199,16 +2079,26 @@ def plot_stress( # 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 + conc_sig_min = ( + min([min(x) for x in self.concrete_stresses]) * units.stress_scale + - 1e-12 + ) + conc_sig_max = ( + max([max(x) for x in self.concrete_stresses]) * units.stress_scale + + 1e-12 + ) # if there is meshed reinforcement, calculate min and max if self.meshed_reinforcement_stresses: meshed_reinf_sig_min = ( - min([min(x) for x in self.meshed_reinforcement_stresses]) - 1e-12 + min([min(x) for x in self.meshed_reinforcement_stresses]) + * units.stress_scale + - 1e-12 ) meshed_reinf_sig_max = ( - max([max(x) for x in self.meshed_reinforcement_stresses]) + 1e-12 + max([max(x) for x in self.meshed_reinforcement_stresses]) + * units.stress_scale + + 1e-12 ) else: meshed_reinf_sig_min = None @@ -1216,11 +2106,13 @@ def plot_stress( # if there is lumped reinforcement, calculate min and max if self.lumped_reinforcement_stresses or self.strand_stresses: - lumped_reinf_sig_min = min( - self.lumped_reinforcement_stresses + self.strand_stresses + lumped_reinf_sig_min = ( + min(self.lumped_reinforcement_stresses + self.strand_stresses) + * units.stress_scale ) - lumped_reinf_sig_max = max( - self.lumped_reinforcement_stresses + self.strand_stresses + lumped_reinf_sig_max = ( + max(self.lumped_reinforcement_stresses + self.strand_stresses) + * units.stress_scale ) else: lumped_reinf_sig_min = None @@ -1228,17 +2120,17 @@ def plot_stress( # determine min and max reinforcement stresess if ( - meshed_reinf_sig_min - and meshed_reinf_sig_max - and lumped_reinf_sig_min - and lumped_reinf_sig_max + meshed_reinf_sig_min is not None + and meshed_reinf_sig_max is not None + and lumped_reinf_sig_min is not None + and lumped_reinf_sig_max is not None ): reinf_sig_min = min(meshed_reinf_sig_min, lumped_reinf_sig_min) reinf_sig_max = max(meshed_reinf_sig_max, lumped_reinf_sig_max) - elif meshed_reinf_sig_min and meshed_reinf_sig_max: + elif meshed_reinf_sig_min is not None and meshed_reinf_sig_max is not None: reinf_sig_min = meshed_reinf_sig_min reinf_sig_max = meshed_reinf_sig_max - elif lumped_reinf_sig_min and lumped_reinf_sig_max: + elif lumped_reinf_sig_min is not None and lumped_reinf_sig_max is not None: reinf_sig_min = lumped_reinf_sig_min reinf_sig_max = lumped_reinf_sig_max else: @@ -1273,6 +2165,9 @@ def plot_stress( self.concrete_analysis_sections[idx].mesh_elements[:, 0:3], # pyright: ignore ) + # scale stress + sig = sig * units.stress_scale + # plot the filled contour trictr_conc = fig.axes[0].tricontourf( triang_conc, sig, v_conc, cmap=cmap_conc, norm=CenteredNorm() @@ -1319,6 +2214,9 @@ def plot_stress( self.meshed_reinforcement_sections[idx].mesh_elements[:, 0:3], # pyright: ignore ) + # scale stress + sig = sig * units.stress_scale + # plot the filled contour trictr_reinf = fig.axes[0].tricontourf( triang_reinf, sig, v_reinf, cmap=cmap_reinf, norm=CenteredNorm() @@ -1357,6 +2255,9 @@ def plot_stress( colours = [] for idx, sig in enumerate(self.lumped_reinforcement_stresses): + # scale stress + sig = sig * units.stress_scale + lumped_geom = self.lumped_reinforcement_geometries[idx].geom lumped_reinf_patches.append( mpatches.Polygon(xy=list(lumped_geom.exterior.coords)) @@ -1364,6 +2265,9 @@ def plot_stress( colours.append(sig) for idx, sig in enumerate(self.strand_stresses): + # scale stress + sig = sig * units.stress_scale + lumped_reinf_patches.append( mpatches.Polygon( xy=list(self.strand_geometries[idx].geom.exterior.coords) @@ -1373,17 +2277,24 @@ def plot_stress( patch = PatchCollection(lumped_reinf_patches, cmap=cmap_reinf) patch.set_array(colours) + if reinf_tick_same: patch.set_clim(vmin=0.99 * v_reinf[0], vmax=1.01 * v_reinf[-1]) else: patch.set_clim(vmin=v_reinf[0], vmax=v_reinf[-1]) + fig.axes[0].add_collection(patch) + # set the tick formatter + tick_formatter = FuncFormatter( + lambda x, _: string_formatter_stress(value=x, eng=eng, prec=prec) + ) + # add the colour bars fig.colorbar( trictr_conc, # pyright: ignore - label="Concrete Stress", - format="%.2e", + label="Concrete Stress" + f" [{stress_unit}]", + format=tick_formatter, ticks=ticks_conc, cax=fig.axes[1], ) @@ -1392,8 +2303,8 @@ def plot_stress( fig.colorbar( mappable, - label="Reinforcement Stress", - format="%.2e", + label="Reinforcement Stress" + f" [{stress_unit}]", + format=tick_formatter, ticks=ticks_reinf, cax=fig.axes[2], ) diff --git a/src/concreteproperties/stress_strain_profile.py b/src/concreteproperties/stress_strain_profile.py index 588b06b5..287634b6 100644 --- a/src/concreteproperties/stress_strain_profile.py +++ b/src/concreteproperties/stress_strain_profile.py @@ -8,16 +8,23 @@ import matplotlib.pyplot as plt import numpy as np +from matplotlib.ticker import FuncFormatter from rich.console import Console from rich.table import Table from scipy.interpolate import interp1d from scipy.optimize import brentq -from concreteproperties.post import plotting_context +from concreteproperties.post import ( + DEFAULT_UNITS, + plotting_context, + string_formatter_plots, +) if TYPE_CHECKING: import matplotlib.axes + from concreteproperties.post import UnitDisplay + @dataclass class StressStrainProfile: @@ -214,6 +221,9 @@ def plot_stress_strain( self, title: str = "Stress-Strain Profile", fmt: str = "o-", + eng: bool = False, + prec: int = 2, + units: UnitDisplay | None = None, **kwargs, ) -> matplotlib.axes.Axes: """Plots the stress-strain profile. @@ -221,20 +231,45 @@ def plot_stress_strain( Args: title: Plot title. Defaults to ``"Stress-Strain Profile"``. fmt: Plot format string. Defaults to ``"o-"``. + eng: If set to ``True``, formats the plot ticks with engineering notation. + If set to ``False``, uses the default ``matplotlib`` ticker formatting. + Defaults to ``False``. + prec: If ``eng=True``, sets the desired precision of the ticker formatting + (i.e. one plus this value is the desired number of digits). Defaults to + ``2``. + units: Unit system to display. Defaults to ``None``. kwargs: Passed to :func:`~concreteproperties.post.plotting_context` Returns: Matplotlib axes object """ + # assign default unit if no units applied + if units is None: + units = DEFAULT_UNITS + stress_unit = "-" + else: + stress_unit = units.stress_unit[1:] + # create plot and setup the plot with plotting_context(title=title, **kwargs) as (_, ax): if ax is None: msg = "Plot failed." raise RuntimeError(msg) - ax.plot(self.strains, self.stresses, fmt) - plt.xlabel("Strain") - plt.ylabel("Stress") + # scale stresses + stresses = np.array(self.stresses) * units.stress_scale + + ax.plot(self.strains, stresses, fmt) + + if eng: + tick_formatter = FuncFormatter( + lambda x, _: string_formatter_plots(value=x, prec=prec) + ) + ax.xaxis.set_major_formatter(tick_formatter) + ax.yaxis.set_major_formatter(tick_formatter) + + plt.xlabel("Strain [-]") + plt.ylabel(f"Stress [{stress_unit}]") plt.grid(True) return ax diff --git a/tests/test_moment_interaction.py b/tests/test_moment_interaction.py index 38321122..dbf2d574 100644 --- a/tests/test_moment_interaction.py +++ b/tests/test_moment_interaction.py @@ -8,6 +8,7 @@ import concreteproperties.utils as utils from concreteproperties.concrete_section import ConcreteSection from concreteproperties.material import Concrete, SteelBar +from concreteproperties.post import DEFAULT_UNITS from concreteproperties.stress_strain_profile import ( ConcreteLinear, RectangularStressBlock, @@ -118,7 +119,9 @@ def test_limits(limits): limit_results = [ conc_sec.calculate_ultimate_section_actions( d_n=conc_sec.decode_d_n(theta=0, cp=lim, d_t=d_t), - ultimate_results=res.UltimateBendingResults(theta=theta), + ultimate_results=res.UltimateBendingResults( + default_units=DEFAULT_UNITS, theta=theta + ), ) for lim in limits ] diff --git a/tests/test_post.py b/tests/test_post.py new file mode 100644 index 00000000..72e74a9b --- /dev/null +++ b/tests/test_post.py @@ -0,0 +1,189 @@ +"""Tests the post methods.""" + +import math +import platform + +import pytest +from sectionproperties.pre.library import concrete_rectangular_section + +from concreteproperties import ( + Concrete, + ConcreteLinear, + ConcreteSection, + RectangularStressBlock, + SteelBar, + SteelElasticPlastic, +) +from concreteproperties.post import DEFAULT_UNITS, si_kn_m, si_n_mm, string_formatter + +linux_only = pytest.mark.skipif( + platform.system() != "Linux", + reason="Only test plotting on Linux", +) + + +@pytest.fixture +def concrete_section() -> ConcreteSection: + """Generates a simple ConcreteSection object. + + Returns: + Geometry + """ + concrete = Concrete( + name="32 MPa Concrete", + density=2.4e-6, + stress_strain_profile=ConcreteLinear(elastic_modulus=30.1e3), + ultimate_stress_strain_profile=RectangularStressBlock( + compressive_strength=32, + alpha=0.802, + gamma=0.89, + ultimate_strain=0.003, + ), + flexural_tensile_strength=3.4, + colour="lightgrey", + ) + steel = SteelBar( + name="500 MPa Steel", + density=7.85e-6, + stress_strain_profile=SteelElasticPlastic( + yield_strength=500, + elastic_modulus=200e3, + fracture_strain=0.05, + ), + colour="grey", + ) + geom = concrete_rectangular_section( + d=300, + b=300, + dia_top=20, + area_top=310, + n_top=2, + c_top=30, + dia_bot=24, + area_bot=450, + n_bot=2, + c_bot=30, + conc_mat=concrete, + steel_mat=steel, + ) + return ConcreteSection(geom) + + +def test_string_formatter(): + """Tests the string formatter.""" + assert string_formatter(value=2704.897111, eng=False, prec=3) == "2704.897" + assert string_formatter(value=2704.897111, eng=False, prec=0) == "2705" + assert string_formatter(value=2704.897111, eng=False, prec=10) == "2704.8971110000" + assert string_formatter(value=0, eng=True, prec=2) == "0.00" + assert string_formatter(value=2704.897111, eng=True, prec=4) == "2.7049 x 10^3" + assert string_formatter(value=0.0034563, eng=True, prec=2) == "3.46 x 10^-3" + assert string_formatter(value=0.034563, eng=True, prec=3) == "34.56 x 10^-3" + assert string_formatter(value=15, eng=True, prec=2) == "15.0" + assert string_formatter(value=14435.654, eng=True, prec=4) == "14.436 x 10^3" + assert ( + string_formatter(value=14435.654, eng=True, prec=4, scale=10) == "144.36 x 10^3" + ) + assert ( + string_formatter(value=14435.654, eng=True, prec=3, scale=1000) + == "14.44 x 10^6" + ) + assert string_formatter(value=14435.654, eng=True, prec=3, scale=1e-3) == "14.44" + + +def test_unit_display(): + """Tests the unit display class.""" + assert si_n_mm.length_unit == " mm" + assert si_kn_m.length_unit == " m" + assert si_n_mm.length_scale == pytest.approx(1) + assert si_kn_m.length_scale == pytest.approx(0.001) + assert si_n_mm.force_unit == " N" + assert si_kn_m.force_unit == " kN" + assert si_n_mm.force_scale == pytest.approx(1) + assert si_kn_m.force_scale == pytest.approx(0.001) + assert si_n_mm.mass_unit == " kg" + assert si_kn_m.mass_unit == " kg" + assert si_n_mm.mass_scale == pytest.approx(1) + assert si_kn_m.mass_scale == pytest.approx(1) + assert si_n_mm.angle_unit == " rads" + assert si_kn_m.angle_unit == " rads" + assert si_n_mm.angle_scale == pytest.approx(1) + assert si_kn_m.angle_scale == pytest.approx(1) + si_n_mm.radians = False + assert si_n_mm.angle_unit == " degs" + assert si_n_mm.angle_scale == pytest.approx(180 / math.pi) + assert si_n_mm.area_unit == " mm^2" + assert si_kn_m.area_unit == " m^2" + assert si_n_mm.area_scale == pytest.approx(1) + assert si_kn_m.area_scale == pytest.approx(1e-6) + assert si_n_mm.mass_per_length_unit == " kg/mm" + assert si_kn_m.mass_per_length_unit == " kg/m" + assert si_n_mm.mass_per_length_scale == pytest.approx(1) + assert si_kn_m.mass_per_length_scale == pytest.approx(1e3) + assert si_n_mm.moment_unit == " N.mm" + assert si_kn_m.moment_unit == " kN.m" + assert si_n_mm.moment_scale == pytest.approx(1) + assert si_kn_m.moment_scale == pytest.approx(1e-6) + assert si_n_mm.flex_rig_unit == " N.mm^2" + assert si_kn_m.flex_rig_unit == " kN.m^2" + assert si_n_mm.flex_rig_scale == pytest.approx(1) + assert si_kn_m.flex_rig_scale == pytest.approx(1e-9) + assert si_n_mm.stress_unit == " MPa" + assert si_kn_m.stress_unit == " kPa" + assert si_n_mm.stress_scale == pytest.approx(1) + assert si_kn_m.stress_scale == pytest.approx(1e3) + si_n_mm.length = "um" + assert si_n_mm.stress_unit == " N/um^2" + si_n_mm.length = "mm" + assert si_n_mm.length_3_unit == " mm^3" + assert si_kn_m.length_3_unit == " m^3" + assert si_n_mm.length_3_scale == pytest.approx(1) + assert si_kn_m.length_3_scale == pytest.approx(1e-9) + assert si_n_mm.length_4_unit == " mm^4" + assert si_kn_m.length_4_unit == " m^4" + assert si_n_mm.length_4_scale == pytest.approx(1) + assert si_kn_m.length_4_scale == pytest.approx(1e-12) + + +def test_print(concrete_section: ConcreteSection): + """Tests printing results to terminal.""" + conc_sec = concrete_section + gross_props = conc_sec.get_gross_properties() + gross_props.print_results() + gross_props.print_results(units=DEFAULT_UNITS) + gross_props.print_results(units=si_n_mm) + gross_props.print_results(units=si_kn_m) + transformed_props = conc_sec.get_transformed_gross_properties( + elastic_modulus=30.1e3 + ) + transformed_props.print_results() + cracked_res = conc_sec.calculate_cracked_properties() + cracked_res.print_results() + cracked_res.calculate_transformed_properties(elastic_modulus=32.8e3) + cracked_res.print_results() + ult_res = conc_sec.ultimate_bending_capacity() + ult_res.print_results() + si_n_mm.radians = False # display angles in degrees + ult_res.print_results(units=si_n_mm) + + +@linux_only +def test_plot(concrete_section: ConcreteSection): + """Tests plotting results.""" + conc_sec = concrete_section + + # mk + mk = conc_sec.moment_curvature_analysis(kappa_inc=1e-6, progress_bar=False) + mk.plot_results() + mk.plot_failure_geometry() + mk.plot_multiple_results([mk, mk], ["1", "2"]) + + # mi + mi = conc_sec.moment_interaction_diagram() + mi.plot_diagram() + mi.plot_multiple_diagrams([mi, mi], ["1", "2"]) + + # bbd + bbd = conc_sec.biaxial_bending_diagram(n=16) + bbd.plot_diagram() + bbd.plot_multiple_diagrams_2d([bbd, bbd]) + bbd.plot_multiple_diagrams_3d([bbd, bbd]) diff --git a/uv.lock b/uv.lock index 9fff1e4d..c7c2b19f 100644 --- a/uv.lock +++ b/uv.lock @@ -265,6 +265,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, ] +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -295,13 +307,14 @@ dependencies = [ { name = "matplotlib" }, { name = "more-itertools" }, { name = "numpy" }, + { name = "quantiphy" }, { name = "rich", extra = ["jupyter"] }, { name = "scipy" }, { name = "sectionproperties" }, { name = "shapely" }, ] -[package.dependency-groups] +[package.dev-dependencies] docs = [ { name = "furo" }, { name = "ipykernel" }, @@ -310,6 +323,7 @@ docs = [ { name = "nbsphinx" }, { name = "notebook" }, { name = "sphinx" }, + { name = "sphinx-autobuild" }, { name = "sphinx-copybutton" }, { name = "sphinxext-opengraph" }, ] @@ -330,13 +344,14 @@ requires-dist = [ { name = "matplotlib", specifier = "~=3.9.2" }, { name = "more-itertools", specifier = "~=10.5.0" }, { name = "numpy", specifier = "~=1.26.4" }, + { name = "quantiphy", specifier = "~=2.20" }, { name = "rich", extras = ["jupyter"], specifier = "~=13.9.3" }, { name = "scipy", specifier = "~=1.14.1" }, { name = "sectionproperties", specifier = "~=3.5.0" }, { name = "shapely", specifier = "~=2.0.6" }, ] -[package.metadata.dependency-groups] +[package.metadata.requires-dev] docs = [ { name = "furo", specifier = "==2024.8.6" }, { name = "ipykernel", specifier = "==6.29.5" }, @@ -345,6 +360,7 @@ docs = [ { name = "nbsphinx", specifier = "==0.9.5" }, { name = "notebook", specifier = "==7.2.2" }, { name = "sphinx", specifier = "==8.1.3" }, + { name = "sphinx-autobuild", specifier = "==2024.10.3" }, { name = "sphinx-copybutton", specifier = "==0.5.2" }, { name = "sphinxext-opengraph", specifier = "==0.9.1" }, ] @@ -1749,6 +1765,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/d2/3b2ab40f455a256cb6672186bea95cd97b459ce4594050132d71e76f0d6f/pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c", size = 550762 }, ] +[[package]] +name = "quantiphy" +version = "2.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/dc/dec6a98d5ef472812848c1141630f819bfaf2e469f3733083ded2f3b08aa/quantiphy-2.20.tar.gz", hash = "sha256:ba5375ac55c3b90077a793588dd5a88aaf81b2c3b0fc9c9359513ac39f6ed84d", size = 42073 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/91/42c59c137bf3d82a6525f3200f23ea6cad81f53828f96e187a519cdf34c3/quantiphy-2.20-py3-none-any.whl", hash = "sha256:afdf5f9d1cc87359bd7daf19dc1cb23808eb2e264d9395b36ca6527fb4d71b3a", size = 41254 }, +] + [[package]] name = "referencing" version = "0.35.1" @@ -2065,6 +2090,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125 }, ] +[[package]] +name = "sphinx-autobuild" +version = "2024.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "sphinx" }, + { name = "starlette" }, + { name = "uvicorn" }, + { name = "watchfiles" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/2c/155e1de2c1ba96a72e5dba152c509a8b41e047ee5c2def9e9f0d812f8be7/sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1", size = 14023 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/c0/eba125db38c84d3c74717008fd3cb5000b68cd7e2cbafd1349c6a38c3d3b/sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa", size = 11908 }, +] + [[package]] name = "sphinx-basic-ng" version = "1.0.0b2" @@ -2169,6 +2211,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, ] +[[package]] +name = "starlette" +version = "0.41.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/da/1fb4bdb72ae12b834becd7e1e7e47001d32f91ec0ce8d7bc1b618d9f0bd9/starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62", size = 2573867 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/43/f185bfd0ca1d213beb4293bed51d92254df23d8ceaf6c0e17146d508a776/starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d", size = 73259 }, +] + [[package]] name = "terminado" version = "0.18.1" @@ -2267,6 +2321,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, ] +[[package]] +name = "uvicorn" +version = "0.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/fc/1d785078eefd6945f3e5bab5c076e4230698046231eb0f3747bc5c8fa992/uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e", size = 77564 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/14/78bd0e95dd2444b6caacbca2b730671d4295ccb628ef58b81bee903629df/uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82", size = 63723 }, +] + [[package]] name = "virtualenv" version = "20.27.1" @@ -2281,6 +2349,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/92/78324ff89391e00c8f4cf6b8526c41c6ef36b4ea2d2c132250b1a6fc2b8d/virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4", size = 3117838 }, ] +[[package]] +name = "watchfiles" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/27/2ba23c8cc85796e2d41976439b08d52f691655fdb9401362099502d1f0cf/watchfiles-0.24.0.tar.gz", hash = "sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1", size = 37870 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/a1/631c12626378b9f1538664aa221feb5c60dfafbd7f60b451f8d0bdbcdedd/watchfiles-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:083dc77dbdeef09fa44bb0f4d1df571d2e12d8a8f985dccde71ac3ac9ac067a0", size = 375096 }, + { url = "https://files.pythonhosted.org/packages/f7/5c/f27c979c8a10aaa2822286c1bffdce3db731cd1aa4224b9f86623e94bbfe/watchfiles-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e94e98c7cb94cfa6e071d401ea3342767f28eb5a06a58fafdc0d2a4974f4f35c", size = 367425 }, + { url = "https://files.pythonhosted.org/packages/74/0d/1889e5649885484d29f6c792ef274454d0a26b20d6ed5fdba5409335ccb6/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82ae557a8c037c42a6ef26c494d0631cacca040934b101d001100ed93d43f361", size = 437705 }, + { url = "https://files.pythonhosted.org/packages/85/8a/01d9a22e839f0d1d547af11b1fcac6ba6f889513f1b2e6f221d9d60d9585/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acbfa31e315a8f14fe33e3542cbcafc55703b8f5dcbb7c1eecd30f141df50db3", size = 433636 }, + { url = "https://files.pythonhosted.org/packages/62/32/a93db78d340c7ef86cde469deb20e36c6b2a873edee81f610e94bbba4e06/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74fdffce9dfcf2dc296dec8743e5b0332d15df19ae464f0e249aa871fc1c571", size = 451069 }, + { url = "https://files.pythonhosted.org/packages/99/c2/e9e2754fae3c2721c9a7736f92dab73723f1968ed72535fff29e70776008/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:449f43f49c8ddca87c6b3980c9284cab6bd1f5c9d9a2b00012adaaccd5e7decd", size = 469306 }, + { url = "https://files.pythonhosted.org/packages/4c/45/f317d9e3affb06c3c27c478de99f7110143e87f0f001f0f72e18d0e1ddce/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abf4ad269856618f82dee296ac66b0cd1d71450fc3c98532d93798e73399b7a", size = 476187 }, + { url = "https://files.pythonhosted.org/packages/ac/d3/f1f37248abe0114916921e638f71c7d21fe77e3f2f61750e8057d0b68ef2/watchfiles-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f895d785eb6164678ff4bb5cc60c5996b3ee6df3edb28dcdeba86a13ea0465e", size = 425743 }, + { url = "https://files.pythonhosted.org/packages/2b/e8/c7037ea38d838fd81a59cd25761f106ee3ef2cfd3261787bee0c68908171/watchfiles-0.24.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ae3e208b31be8ce7f4c2c0034f33406dd24fbce3467f77223d10cd86778471c", size = 612327 }, + { url = "https://files.pythonhosted.org/packages/a0/c5/0e6e228aafe01a7995fbfd2a4edb221bb11a2744803b65a5663fb85e5063/watchfiles-0.24.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2efec17819b0046dde35d13fb8ac7a3ad877af41ae4640f4109d9154ed30a188", size = 595096 }, + { url = "https://files.pythonhosted.org/packages/63/d5/4780e8bf3de3b4b46e7428a29654f7dc041cad6b19fd86d083e4b6f64bbe/watchfiles-0.24.0-cp310-none-win32.whl", hash = "sha256:6bdcfa3cd6fdbdd1a068a52820f46a815401cbc2cb187dd006cb076675e7b735", size = 264149 }, + { url = "https://files.pythonhosted.org/packages/fe/1b/5148898ba55fc9c111a2a4a5fb67ad3fa7eb2b3d7f0618241ed88749313d/watchfiles-0.24.0-cp310-none-win_amd64.whl", hash = "sha256:54ca90a9ae6597ae6dc00e7ed0a040ef723f84ec517d3e7ce13e63e4bc82fa04", size = 277542 }, + { url = "https://files.pythonhosted.org/packages/85/02/366ae902cd81ca5befcd1854b5c7477b378f68861597cef854bd6dc69fbe/watchfiles-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:bdcd5538e27f188dd3c804b4a8d5f52a7fc7f87e7fd6b374b8e36a4ca03db428", size = 375579 }, + { url = "https://files.pythonhosted.org/packages/bc/67/d8c9d256791fe312fea118a8a051411337c948101a24586e2df237507976/watchfiles-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2dadf8a8014fde6addfd3c379e6ed1a981c8f0a48292d662e27cabfe4239c83c", size = 367726 }, + { url = "https://files.pythonhosted.org/packages/b1/dc/a8427b21ef46386adf824a9fec4be9d16a475b850616cfd98cf09a97a2ef/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6509ed3f467b79d95fc62a98229f79b1a60d1b93f101e1c61d10c95a46a84f43", size = 437735 }, + { url = "https://files.pythonhosted.org/packages/3a/21/0b20bef581a9fbfef290a822c8be645432ceb05fb0741bf3c032e0d90d9a/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8360f7314a070c30e4c976b183d1d8d1585a4a50c5cb603f431cebcbb4f66327", size = 433644 }, + { url = "https://files.pythonhosted.org/packages/1c/e8/d5e5f71cc443c85a72e70b24269a30e529227986096abe091040d6358ea9/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:316449aefacf40147a9efaf3bd7c9bdd35aaba9ac5d708bd1eb5763c9a02bef5", size = 450928 }, + { url = "https://files.pythonhosted.org/packages/61/ee/bf17f5a370c2fcff49e1fec987a6a43fd798d8427ea754ce45b38f9e117a/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73bde715f940bea845a95247ea3e5eb17769ba1010efdc938ffcb967c634fa61", size = 469072 }, + { url = "https://files.pythonhosted.org/packages/a3/34/03b66d425986de3fc6077e74a74c78da298f8cb598887f664a4485e55543/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3770e260b18e7f4e576edca4c0a639f704088602e0bc921c5c2e721e3acb8d15", size = 475517 }, + { url = "https://files.pythonhosted.org/packages/70/eb/82f089c4f44b3171ad87a1b433abb4696f18eb67292909630d886e073abe/watchfiles-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0fd7248cf533c259e59dc593a60973a73e881162b1a2f73360547132742823", size = 425480 }, + { url = "https://files.pythonhosted.org/packages/53/20/20509c8f5291e14e8a13104b1808cd7cf5c44acd5feaecb427a49d387774/watchfiles-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7a2e3b7f5703ffbd500dabdefcbc9eafeff4b9444bbdd5d83d79eedf8428fab", size = 612322 }, + { url = "https://files.pythonhosted.org/packages/df/2b/5f65014a8cecc0a120f5587722068a975a692cadbe9fe4ea56b3d8e43f14/watchfiles-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d831ee0a50946d24a53821819b2327d5751b0c938b12c0653ea5be7dea9c82ec", size = 595094 }, + { url = "https://files.pythonhosted.org/packages/18/98/006d8043a82c0a09d282d669c88e587b3a05cabdd7f4900e402250a249ac/watchfiles-0.24.0-cp311-none-win32.whl", hash = "sha256:49d617df841a63b4445790a254013aea2120357ccacbed00253f9c2b5dc24e2d", size = 264191 }, + { url = "https://files.pythonhosted.org/packages/8a/8b/badd9247d6ec25f5f634a9b3d0d92e39c045824ec7e8afcedca8ee52c1e2/watchfiles-0.24.0-cp311-none-win_amd64.whl", hash = "sha256:d3dcb774e3568477275cc76554b5a565024b8ba3a0322f77c246bc7111c5bb9c", size = 277527 }, + { url = "https://files.pythonhosted.org/packages/af/19/35c957c84ee69d904299a38bae3614f7cede45f07f174f6d5a2f4dbd6033/watchfiles-0.24.0-cp311-none-win_arm64.whl", hash = "sha256:9301c689051a4857d5b10777da23fafb8e8e921bcf3abe6448a058d27fb67633", size = 266253 }, + { url = "https://files.pythonhosted.org/packages/35/82/92a7bb6dc82d183e304a5f84ae5437b59ee72d48cee805a9adda2488b237/watchfiles-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7211b463695d1e995ca3feb38b69227e46dbd03947172585ecb0588f19b0d87a", size = 374137 }, + { url = "https://files.pythonhosted.org/packages/87/91/49e9a497ddaf4da5e3802d51ed67ff33024597c28f652b8ab1e7c0f5718b/watchfiles-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b8693502d1967b00f2fb82fc1e744df128ba22f530e15b763c8d82baee15370", size = 367733 }, + { url = "https://files.pythonhosted.org/packages/0d/d8/90eb950ab4998effea2df4cf3a705dc594f6bc501c5a353073aa990be965/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdab9555053399318b953a1fe1f586e945bc8d635ce9d05e617fd9fe3a4687d6", size = 437322 }, + { url = "https://files.pythonhosted.org/packages/6c/a2/300b22e7bc2a222dd91fce121cefa7b49aa0d26a627b2777e7bdfcf1110b/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34e19e56d68b0dad5cff62273107cf5d9fbaf9d75c46277aa5d803b3ef8a9e9b", size = 433409 }, + { url = "https://files.pythonhosted.org/packages/99/44/27d7708a43538ed6c26708bcccdde757da8b7efb93f4871d4cc39cffa1cc/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41face41f036fee09eba33a5b53a73e9a43d5cb2c53dad8e61fa6c9f91b5a51e", size = 452142 }, + { url = "https://files.pythonhosted.org/packages/b0/ec/c4e04f755be003129a2c5f3520d2c47026f00da5ecb9ef1e4f9449637571/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5148c2f1ea043db13ce9b0c28456e18ecc8f14f41325aa624314095b6aa2e9ea", size = 469414 }, + { url = "https://files.pythonhosted.org/packages/c5/4e/cdd7de3e7ac6432b0abf282ec4c1a1a2ec62dfe423cf269b86861667752d/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e4bd963a935aaf40b625c2499f3f4f6bbd0c3776f6d3bc7c853d04824ff1c9f", size = 472962 }, + { url = "https://files.pythonhosted.org/packages/27/69/e1da9d34da7fc59db358424f5d89a56aaafe09f6961b64e36457a80a7194/watchfiles-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c79d7719d027b7a42817c5d96461a99b6a49979c143839fc37aa5748c322f234", size = 425705 }, + { url = "https://files.pythonhosted.org/packages/e8/c1/24d0f7357be89be4a43e0a656259676ea3d7a074901f47022f32e2957798/watchfiles-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:32aa53a9a63b7f01ed32e316e354e81e9da0e6267435c7243bf8ae0f10b428ef", size = 612851 }, + { url = "https://files.pythonhosted.org/packages/c7/af/175ba9b268dec56f821639c9893b506c69fd999fe6a2e2c51de420eb2f01/watchfiles-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce72dba6a20e39a0c628258b5c308779b8697f7676c254a845715e2a1039b968", size = 594868 }, + { url = "https://files.pythonhosted.org/packages/44/81/1f701323a9f70805bc81c74c990137123344a80ea23ab9504a99492907f8/watchfiles-0.24.0-cp312-none-win32.whl", hash = "sha256:d9018153cf57fc302a2a34cb7564870b859ed9a732d16b41a9b5cb2ebed2d444", size = 264109 }, + { url = "https://files.pythonhosted.org/packages/b4/0b/32cde5bc2ebd9f351be326837c61bdeb05ad652b793f25c91cac0b48a60b/watchfiles-0.24.0-cp312-none-win_amd64.whl", hash = "sha256:551ec3ee2a3ac9cbcf48a4ec76e42c2ef938a7e905a35b42a1267fa4b1645896", size = 277055 }, + { url = "https://files.pythonhosted.org/packages/4b/81/daade76ce33d21dbec7a15afd7479de8db786e5f7b7d249263b4ea174e08/watchfiles-0.24.0-cp312-none-win_arm64.whl", hash = "sha256:b52a65e4ea43c6d149c5f8ddb0bef8d4a1e779b77591a458a893eb416624a418", size = 266169 }, + { url = "https://files.pythonhosted.org/packages/df/94/1ad200e937ec91b2a9d6b39ae1cf9c2b1a9cc88d5ceb43aa5c6962eb3c11/watchfiles-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:632676574429bee8c26be8af52af20e0c718cc7f5f67f3fb658c71928ccd4f7f", size = 376986 }, + { url = "https://files.pythonhosted.org/packages/ee/fd/d9e020d687ccf90fe95efc513fbb39a8049cf5a3ff51f53c59fcf4c47a5d/watchfiles-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a2a9891723a735d3e2540651184be6fd5b96880c08ffe1a98bae5017e65b544b", size = 369445 }, + { url = "https://files.pythonhosted.org/packages/43/cb/c0279b35053555d10ef03559c5aebfcb0c703d9c70a7b4e532df74b9b0e8/watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7fa2bc0efef3e209a8199fd111b8969fe9db9c711acc46636686331eda7dd4", size = 439383 }, + { url = "https://files.pythonhosted.org/packages/8b/c4/08b3c2cda45db5169148a981c2100c744a4a222fa7ae7644937c0c002069/watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01550ccf1d0aed6ea375ef259706af76ad009ef5b0203a3a4cce0f6024f9b68a", size = 426804 }, +] + [[package]] name = "wcwidth" version = "0.2.13" @@ -2317,6 +2438,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 }, ] +[[package]] +name = "websockets" +version = "13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/73/9223dbc7be3dcaf2a7bbf756c351ec8da04b1fa573edaf545b95f6b0c7fd/websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878", size = 158549 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/94/d15dbfc6a5eb636dbc754303fba18208f2e88cf97e733e1d64fb9cb5c89e/websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee", size = 157815 }, + { url = "https://files.pythonhosted.org/packages/30/02/c04af33f4663945a26f5e8cf561eb140c35452b50af47a83c3fbcfe62ae1/websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7", size = 155466 }, + { url = "https://files.pythonhosted.org/packages/35/e8/719f08d12303ea643655e52d9e9851b2dadbb1991d4926d9ce8862efa2f5/websockets-13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6", size = 155716 }, + { url = "https://files.pythonhosted.org/packages/91/e1/14963ae0252a8925f7434065d25dcd4701d5e281a0b4b460a3b5963d2594/websockets-13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b", size = 164806 }, + { url = "https://files.pythonhosted.org/packages/ec/fa/ab28441bae5e682a0f7ddf3d03440c0c352f930da419301f4a717f675ef3/websockets-13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa", size = 163810 }, + { url = "https://files.pythonhosted.org/packages/44/77/dea187bd9d16d4b91566a2832be31f99a40d0f5bfa55eeb638eb2c3bc33d/websockets-13.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700", size = 164125 }, + { url = "https://files.pythonhosted.org/packages/cf/d9/3af14544e83f1437eb684b399e6ba0fa769438e869bf5d83d74bc197fae8/websockets-13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c", size = 164532 }, + { url = "https://files.pythonhosted.org/packages/1c/8a/6d332eabe7d59dfefe4b8ba6f46c8c5fabb15b71c8a8bc3d2b65de19a7b6/websockets-13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0", size = 163948 }, + { url = "https://files.pythonhosted.org/packages/1a/91/a0aeadbaf3017467a1ee03f8fb67accdae233fe2d5ad4b038c0a84e357b0/websockets-13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f", size = 163898 }, + { url = "https://files.pythonhosted.org/packages/71/31/a90fb47c63e0ae605be914b0b969d7c6e6ffe2038cd744798e4b3fbce53b/websockets-13.1-cp310-cp310-win32.whl", hash = "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe", size = 158706 }, + { url = "https://files.pythonhosted.org/packages/93/ca/9540a9ba80da04dc7f36d790c30cae4252589dbd52ccdc92e75b0be22437/websockets-13.1-cp310-cp310-win_amd64.whl", hash = "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a", size = 159141 }, + { url = "https://files.pythonhosted.org/packages/b2/f0/cf0b8a30d86b49e267ac84addbebbc7a48a6e7bb7c19db80f62411452311/websockets-13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19", size = 157813 }, + { url = "https://files.pythonhosted.org/packages/bf/e7/22285852502e33071a8cf0ac814f8988480ec6db4754e067b8b9d0e92498/websockets-13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5", size = 155469 }, + { url = "https://files.pythonhosted.org/packages/68/d4/c8c7c1e5b40ee03c5cc235955b0fb1ec90e7e37685a5f69229ad4708dcde/websockets-13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd", size = 155717 }, + { url = "https://files.pythonhosted.org/packages/c9/e4/c50999b9b848b1332b07c7fd8886179ac395cb766fda62725d1539e7bc6c/websockets-13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02", size = 165379 }, + { url = "https://files.pythonhosted.org/packages/bc/49/4a4ad8c072f18fd79ab127650e47b160571aacfc30b110ee305ba25fffc9/websockets-13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7", size = 164376 }, + { url = "https://files.pythonhosted.org/packages/af/9b/8c06d425a1d5a74fd764dd793edd02be18cf6fc3b1ccd1f29244ba132dc0/websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096", size = 164753 }, + { url = "https://files.pythonhosted.org/packages/d5/5b/0acb5815095ff800b579ffc38b13ab1b915b317915023748812d24e0c1ac/websockets-13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084", size = 165051 }, + { url = "https://files.pythonhosted.org/packages/30/93/c3891c20114eacb1af09dedfcc620c65c397f4fd80a7009cd12d9457f7f5/websockets-13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3", size = 164489 }, + { url = "https://files.pythonhosted.org/packages/28/09/af9e19885539759efa2e2cd29b8b3f9eecef7ecefea40d46612f12138b36/websockets-13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9", size = 164438 }, + { url = "https://files.pythonhosted.org/packages/b6/08/6f38b8e625b3d93de731f1d248cc1493327f16cb45b9645b3e791782cff0/websockets-13.1-cp311-cp311-win32.whl", hash = "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f", size = 158710 }, + { url = "https://files.pythonhosted.org/packages/fb/39/ec8832ecb9bb04a8d318149005ed8cee0ba4e0205835da99e0aa497a091f/websockets-13.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557", size = 159137 }, + { url = "https://files.pythonhosted.org/packages/df/46/c426282f543b3c0296cf964aa5a7bb17e984f58dde23460c3d39b3148fcf/websockets-13.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc", size = 157821 }, + { url = "https://files.pythonhosted.org/packages/aa/85/22529867010baac258da7c45848f9415e6cf37fef00a43856627806ffd04/websockets-13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49", size = 155480 }, + { url = "https://files.pythonhosted.org/packages/29/2c/bdb339bfbde0119a6e84af43ebf6275278698a2241c2719afc0d8b0bdbf2/websockets-13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd", size = 155715 }, + { url = "https://files.pythonhosted.org/packages/9f/d0/8612029ea04c5c22bf7af2fd3d63876c4eaeef9b97e86c11972a43aa0e6c/websockets-13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0", size = 165647 }, + { url = "https://files.pythonhosted.org/packages/56/04/1681ed516fa19ca9083f26d3f3a302257e0911ba75009533ed60fbb7b8d1/websockets-13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6", size = 164592 }, + { url = "https://files.pythonhosted.org/packages/38/6f/a96417a49c0ed132bb6087e8e39a37db851c70974f5c724a4b2a70066996/websockets-13.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9", size = 165012 }, + { url = "https://files.pythonhosted.org/packages/40/8b/fccf294919a1b37d190e86042e1a907b8f66cff2b61e9befdbce03783e25/websockets-13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68", size = 165311 }, + { url = "https://files.pythonhosted.org/packages/c1/61/f8615cf7ce5fe538476ab6b4defff52beb7262ff8a73d5ef386322d9761d/websockets-13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14", size = 164692 }, + { url = "https://files.pythonhosted.org/packages/5c/f1/a29dd6046d3a722d26f182b783a7997d25298873a14028c4760347974ea3/websockets-13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf", size = 164686 }, + { url = "https://files.pythonhosted.org/packages/0f/99/ab1cdb282f7e595391226f03f9b498f52109d25a2ba03832e21614967dfa/websockets-13.1-cp312-cp312-win32.whl", hash = "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c", size = 158712 }, + { url = "https://files.pythonhosted.org/packages/46/93/e19160db48b5581feac8468330aa11b7292880a94a37d7030478596cc14e/websockets-13.1-cp312-cp312-win_amd64.whl", hash = "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3", size = 159145 }, + { url = "https://files.pythonhosted.org/packages/2d/75/6da22cb3ad5b8c606963f9a5f9f88656256fecc29d420b4b2bf9e0c7d56f/websockets-13.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238", size = 155499 }, + { url = "https://files.pythonhosted.org/packages/c0/ba/22833d58629088fcb2ccccedfae725ac0bbcd713319629e97125b52ac681/websockets-13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5", size = 155737 }, + { url = "https://files.pythonhosted.org/packages/95/54/61684fe22bdb831e9e1843d972adadf359cf04ab8613285282baea6a24bb/websockets-13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9", size = 157095 }, + { url = "https://files.pythonhosted.org/packages/fc/f5/6652fb82440813822022a9301a30afde85e5ff3fb2aebb77f34aabe2b4e8/websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6", size = 156701 }, + { url = "https://files.pythonhosted.org/packages/67/33/ae82a7b860fa8a08aba68818bdf7ff61f04598aa5ab96df4cd5a3e418ca4/websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a", size = 156654 }, + { url = "https://files.pythonhosted.org/packages/63/0b/a1b528d36934f833e20f6da1032b995bf093d55cb416b9f2266f229fb237/websockets-13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23", size = 159192 }, + { url = "https://files.pythonhosted.org/packages/56/27/96a5cd2626d11c8280656c6c71d8ab50fe006490ef9971ccd154e0c42cd2/websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f", size = 152134 }, +] + [[package]] name = "widgetsnbextension" version = "4.0.13"