Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
7a37a7c
feat: add PlasticShearStrengthIProfileCheck class for shear force res…
GerjanDorgelo Jan 31, 2026
ba28924
feat: update equation handling in Report class to support multline en…
GerjanDorgelo Jan 31, 2026
84bd641
feat: update Report class to support multline equations and enhance t…
GerjanDorgelo Jan 31, 2026
a7a61a5
feat: update report title for plastic shear strength check and modify…
GerjanDorgelo Jan 31, 2026
e9d6768
feat: modify Form6Dot18SubARolledIandHSection to split flange effects…
GerjanDorgelo Feb 1, 2026
b6485e5
fix: correct attribute names in latex output for Form6Dot18SubARolled…
GerjanDorgelo Feb 1, 2026
914fc0e
refactor: rename variables in test_latex method for consistency
GerjanDorgelo Feb 1, 2026
5ef3281
fix: correct latex equation formatting in Form6Dot18SubARolledIandHSe…
GerjanDorgelo Feb 1, 2026
baea411
feat: update PlasticShearStrengthIProfileCheck to use individual flan…
GerjanDorgelo Feb 1, 2026
79e6cc5
feat: update PlasticShearStrengthIProfileCheck to use consistent vari…
GerjanDorgelo Feb 1, 2026
f9b2d6e
feat: add evaluation test for non-symmetric profile in Form6Dot18SubA…
GerjanDorgelo Feb 1, 2026
b339990
feat: implement plastic shear strength checks for I profiles with com…
GerjanDorgelo Feb 1, 2026
f54f0e5
feat: add test for TypeError when checking non-I-profile in PlasticSh…
GerjanDorgelo Feb 1, 2026
c879793
feat: update shear force calculations in PlasticShearStrengthIProfile…
GerjanDorgelo Feb 1, 2026
88334ac
feat: enhance plastic shear strength calculations and add main execut…
GerjanDorgelo Feb 1, 2026
ffeb259
feat: remove main execution block from PlasticShearStrengthIProfileCh…
GerjanDorgelo Feb 1, 2026
9137a16
feat: update shear area formula in report generation for PlasticShear…
GerjanDorgelo Feb 1, 2026
72a68f9
fix: correct split index for shear area formula in report generation
GerjanDorgelo Feb 1, 2026
fca4809
feat: remove main execution block from PlasticShearStrengthIProfileCh…
GerjanDorgelo Feb 1, 2026
d0c6439
refactor: remove unused variables and print statement from formula_6_…
GerjanDorgelo Feb 1, 2026
ba970a7
fix: remove unnecessary blank line in test_shear_strength.py
GerjanDorgelo Feb 1, 2026
e2f536e
feat: add stress calculation method to Profile class
GerjanDorgelo Feb 5, 2026
5849cec
feat: add TorsionStrengthCheck module for torsional shear stress resi…
GerjanDorgelo Feb 5, 2026
4bd30b1
feat: enhance TorsionStrengthCheck with additional calculations and t…
GerjanDorgelo Feb 6, 2026
86cb351
feat: optimize torsion strength calculations and improve report clarity
GerjanDorgelo Feb 6, 2026
665e2be
feat: add TorsionWithShearStrengthIProfileCheck module and correspond…
GerjanDorgelo Feb 6, 2026
4617877
feat: rename TorsionStrengthCheck to StVenantTorsionStrengthCheck and…
GerjanDorgelo Feb 6, 2026
84fa85b
test: add report assertion to shear strength test cases
GerjanDorgelo Feb 7, 2026
ca29d2c
feat: optimize stress calculation in torsion checks and update return…
GerjanDorgelo Feb 7, 2026
17def6c
feat: simplify name of torsion strength check for clarity
GerjanDorgelo Feb 7, 2026
cdc5676
refactor: remove unused profile attribute in TorsionWithShearStrength…
GerjanDorgelo Feb 7, 2026
3dbacdf
refactor: remove unused profile attribute from PlasticShearStrengthIP…
GerjanDorgelo Feb 7, 2026
4834416
Update torsion_strength.py
GerjanDorgelo Feb 7, 2026
e6ecdbd
Update shear_strength.py
GerjanDorgelo Feb 7, 2026
19dbdaf
fix: use variable for equation type in LaTeX output
GerjanDorgelo Feb 7, 2026
14a9b4d
fix: update equation formatting in test_report.py to use equation env…
GerjanDorgelo Feb 7, 2026
9047dd4
fix: include additional equation count in report summary
GerjanDorgelo Feb 7, 2026
d99985f
fix: update equation handling to support both multline and equation e…
GerjanDorgelo Feb 7, 2026
40c581d
feat: update stress calculation to use correct unit conversions for f…
GerjanDorgelo Feb 7, 2026
c164736
Merge branch '966-feature-request-add-torsion-steel-check' of https:/…
GerjanDorgelo Feb 7, 2026
9e14b38
Merge remote-tracking branch 'origin/957-feature-request-production_m…
GerjanDorgelo Feb 8, 2026
e008b11
merge
GerjanDorgelo Feb 8, 2026
350d395
Add stress calculation method to Profile class for internal force ana…
GerjanDorgelo Feb 8, 2026
d147fc5
Add plastic shear strength check for steel cross-section class 3 and 4
GerjanDorgelo Feb 8, 2026
50a1510
Round maximum allowed shear stress calculation to specified decimal p…
GerjanDorgelo Feb 8, 2026
1b1f75b
Enhance calculation_formula method to support multiple return types a…
GerjanDorgelo Feb 8, 2026
8a7ffae
Merge branch '960-feature-request-add-plastic-shear-check-for-i-profi…
GerjanDorgelo Feb 8, 2026
242c5d0
Refactor CheckStrengthTorsionShearClass to simplify class name and up…
GerjanDorgelo Feb 8, 2026
9ab3649
Refactor torsional moment variable names to improve clarity and add C…
GerjanDorgelo Feb 8, 2026
aad589c
Update test_strength_torsion_shear.py
GerjanDorgelo Feb 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions blueprints/checks/eurocode/steel/strength_compression.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,18 @@ def report(self, n: int = 2) -> Report:
if self.n == 0:
report.add_paragraph("No compressive force was applied; therefore, no compression force check is necessary.")
return report

# Cache calculation formulas to avoid redundant recalculations
formulas = self.calculation_formula()

report.add_paragraph(
rf"Profile {self.steel_cross_section.profile.name} with steel quality {self.steel_cross_section.material.steel_class.name} "
rf"is loaded with a compressive force of {abs(self.n):.{n}f} kN. "
rf"The resistance is calculated as follows:"
)
report.add_formula(self.calculation_formula()["resistance"], n=n)
report.add_formula(formulas["resistance"], n=n)
report.add_paragraph("The unity check is calculated as follows:")
report.add_formula(self.calculation_formula()["check"], n=n)
report.add_formula(formulas["check"], n=n)
if self.result().is_ok:
report.add_paragraph("The check for compression force satisfies the requirements.")
else:
Expand Down
313 changes: 313 additions & 0 deletions blueprints/checks/eurocode/steel/strength_shear.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
"""Module for checking plastic shear force resistance of steel(Eurocode 3)."""

from dataclasses import dataclass
from typing import ClassVar, Literal

import numpy as np
from sectionproperties.post.post import SectionProperties

from blueprints.checks.check_result import CheckResult
from blueprints.codes.eurocode.en_1993_1_1_2005 import EN_1993_1_1_2005
from blueprints.codes.eurocode.en_1993_1_1_2005.chapter_6_ultimate_limit_state import formula_6_17, formula_6_18, formula_6_18_sub_av, formula_6_19
from blueprints.codes.formula import Formula
from blueprints.saf.results.result_internal_force_1d import ResultFor, ResultInternalForce1D, ResultOn
from blueprints.structural_sections.steel.profile_definitions.i_profile import IProfile
from blueprints.structural_sections.steel.steel_cross_section import SteelCrossSection
from blueprints.type_alias import DIMENSIONLESS, KN
from blueprints.unit_conversion import KN_TO_N
from blueprints.utils.report import Report


@dataclass(frozen=True)
class CheckStrengthShearClass12IProfile:
"""Class to perform plastic shear force resistance check for steel I-profiles of cross-section class 1 and 2 (Eurocode 3).

Coordinate System:

z (vertical, usually strong axis)
| x (longitudinal beam direction, into screen)
| ↗
| /
| /
| /
|/
←-----O
y (horizontal/side, usually weak axis)

Parameters
----------
steel_cross_section : SteelCrossSection
The steel cross-section, of type I-profile, to check.
v : KN
The applied shear force (in kN).
axis : Literal["Vz", "Vy"]
Axis along which the shear force is applied. "Vz" (default) for z (vertical), "Vy" for y (horizontal).
gamma_m0 : DIMENSIONLESS, optional
Partial safety factor for resistance of cross-sections, default is 1.0.
section_properties : SectionProperties | None, optional
Pre-calculated section properties. If None, they will be calculated internally.

Example
-------
from blueprints.checks.eurocode.steel.strength_shear import CheckStrengthShearClass12IProfile
from blueprints.materials.steel import SteelMaterial, SteelStrengthClass
from blueprints.structural_sections.steel.standard_profiles.heb import HEB

steel_material = SteelMaterial(steel_class=SteelStrengthClass.S355)
heb_300_profile = HEB.HEB300.with_corrosion(1.5)
v = 100 # Applied shear force in kN

heb_300_s355 = SteelCrossSection(profile=heb_300_profile, material=steel_material)
calc = CheckStrengthShearClass12IProfile(heb_300_s355, v, axis="Vz", gamma_m0=1.0)
calc.report().to_word("shear_strength.docx", language="nl")

"""

steel_cross_section: SteelCrossSection
v: KN = 0
axis: Literal["Vz", "Vy"] = "Vz"
gamma_m0: DIMENSIONLESS = 1.0
section_properties: SectionProperties | None = None
name: str = "Plastic shear strength check for steel I-profiles"
source_docs: ClassVar[list] = [EN_1993_1_1_2005]

def __post_init__(self) -> None:
"""Post-initialization to extract section properties and check profile type."""
if not isinstance(self.steel_cross_section.profile, IProfile):
raise TypeError("The provided profile is not an I-profile.")
if self.section_properties is None:
section_properties = self.steel_cross_section.profile.section_properties()
object.__setattr__(self, "section_properties", section_properties)

def calculation_formula(self) -> dict[str, Formula]:
"""Calculate plastic shear force resistance check.

Returns
-------
dict[str, Formula]
Calculation results keyed by formula number. Returns an empty dict if no shear force is applied.
"""
# Get parameters from profile, average top and bottom flange properties
a = float(self.section_properties.area) # type: ignore[attr-defined]
b1 = self.steel_cross_section.profile.top_flange_width # type: ignore[attr-defined]
b2 = self.steel_cross_section.profile.bottom_flange_width # type: ignore[attr-defined]
tf1 = self.steel_cross_section.profile.top_flange_thickness # type: ignore[attr-defined]
tf2 = self.steel_cross_section.profile.bottom_flange_thickness # type: ignore[attr-defined]
tw = self.steel_cross_section.profile.web_thickness # type: ignore[attr-defined]
hw = self.steel_cross_section.profile.total_height - ( # type: ignore[attr-defined]
self.steel_cross_section.profile.top_flange_thickness + self.steel_cross_section.profile.bottom_flange_thickness # type: ignore[attr-defined]
)
r1 = self.steel_cross_section.profile.top_radius # type: ignore[attr-defined]
r2 = self.steel_cross_section.profile.bottom_radius # type: ignore[attr-defined]

if self.axis == "Vz" and self.steel_cross_section.fabrication_method in ["hot-rolled", "cold-formed"]:
av = formula_6_18_sub_av.Form6Dot18SubARolledIandHSection(a=a, b1=b1, b2=b2, hw=hw, r1=r1, r2=r2, tf1=tf1, tf2=tf2, tw=tw, eta=1.0)
elif self.axis == "Vz" and self.steel_cross_section.fabrication_method == "welded":
av = formula_6_18_sub_av.Form6Dot18SubDWeldedIHandBoxSection(hw_list=[hw], tw_list=[tw], eta=1.0)
else: # axis == "Vy"
av = formula_6_18_sub_av.Form6Dot18SubEWeldedIHandBoxSection(a=a, hw_list=[hw], tw_list=[tw])

f_y = self.steel_cross_section.yield_strength
v_ed = abs(self.v) * KN_TO_N
v_pl_rd = formula_6_18.Form6Dot18DesignPlasticShearResistance(a_v=av, f_y=f_y, gamma_m0=self.gamma_m0)
check_shear = formula_6_17.Form6Dot17CheckShearForce(v_ed=v_ed, v_c_rd=v_pl_rd)
return {
"shear_area": av,
"resistance": v_pl_rd,
"check": check_shear,
}

def result(self) -> CheckResult:
"""Calculate result of plastic shear force resistance.

Returns
-------
CheckResult
True if the shear force check passes, False otherwise.
"""
steps = self.calculation_formula()
provided = abs(self.v) * KN_TO_N
required = steps["resistance"]
return CheckResult.from_comparison(provided=provided, required=required)

def report(self, n: int = 2) -> Report:
"""Returns the report for the plastic shear force check.

Parameters
----------
n : int, optional
Number of decimal places for numerical values in the report (default is 2).

Returns
-------
Report
Report of the plastic shear force check.
"""
report = Report("Check: shear force steel I-beam")
if self.v == 0:
report.add_paragraph("No shear force was applied; therefore, no shear force check is necessary.")
return report
axis_label = "(vertical) z" if self.axis == "Vz" else "(horizontal) y"
report.add_paragraph(
rf"Profile {self.steel_cross_section.profile.name} with steel quality {self.steel_cross_section.material.steel_class.name} "
rf"is loaded with a shear force of {abs(self.v):.{n}f} kN in the {axis_label}-direction. "
rf"The shear area $A_v$ is calculated as follows:"
)
formulas = self.calculation_formula()
report.add_formula(formulas["shear_area"], n=n, split_after=[(2, "="), (7, "+"), (3, "=")])
report.add_paragraph("The shear resistance is calculated as follows:")
report.add_formula(formulas["resistance"], n=n)
report.add_paragraph("The unity check is calculated as follows:")
report.add_formula(formulas["check"], n=n)
if self.result().is_ok:
report.add_paragraph("The check for plastic shear force satisfies the requirements.")
else:
report.add_paragraph("The check for plastic shear force does NOT satisfy the requirements.")
return report


@dataclass(frozen=True)
class CheckStrengthShearClass34:
"""Class to perform plastic shear force resistance check for steel cross-section class 3 and 4 (Eurocode 3).

Coordinate System:

z (vertical, usually strong axis)
| x (longitudinal beam direction, into screen)
| ↗
| /
| /
| /
|/
←-----O
y (horizontal/side, usually weak axis)

Parameters
----------
steel_cross_section : SteelCrossSection
The steel cross-section, of type I-profile, to check.
v : KN
The applied shear force (in kN).
axis : Literal["Vz", "Vy"]
Axis along which the shear force is applied. "Vz" (default) for z (vertical), "Vy" for y (horizontal).
gamma_m0 : DIMENSIONLESS, optional
Partial safety factor for resistance of cross-sections, default is 1.0.
section_properties : SectionProperties | None, optional
Pre-calculated section properties. If None, they will be calculated internally.

Example
-------
from blueprints.checks.eurocode.steel.strength_shear import CheckStrengthShearClass34IProfile
from blueprints.materials.steel import SteelMaterial, SteelStrengthClass
from blueprints.structural_sections.steel.standard_profiles.heb import HEB

steel_material = SteelMaterial(steel_class=SteelStrengthClass.S355)
heb_300_profile = HEB.HEB300.with_corrosion(1.5)
v = 100 # Applied shear force in kN

heb_300_s355 = SteelCrossSection(profile=heb_300_profile, material=steel_material)
calc = CheckStrengthShearClass34IProfile(heb_300_s355, v, axis="Vz", gamma_m0=1.0)
calc.report().to_word("shear_strength.docx", language="nl")

"""

steel_cross_section: SteelCrossSection
v: KN = 0
axis: Literal["Vz", "Vy"] = "Vz"
gamma_m0: DIMENSIONLESS = 1.0
section_properties: SectionProperties | None = None
name: str = "Elastic shear strength check"
source_docs: ClassVar[list] = [EN_1993_1_1_2005]

def __post_init__(self) -> None:
"""Post-initialization to extract section properties and check profile type."""
if self.section_properties is None:
section_properties = self.steel_cross_section.profile.section_properties()
object.__setattr__(self, "section_properties", section_properties)

def calculation_formula(self) -> dict[str, Formula | float | int]:
"""Calculate plastic shear force resistance check.

Returns
-------
dict[str, Formula]
Calculation results keyed by formula number. Returns an empty dict if no shear force is applied.
"""
rif1d = ResultInternalForce1D(
result_on=ResultOn.ON_BEAM,
member="N/A",
result_for=ResultFor.LOAD_CASE,
load_case="N/A",
vy=1 if self.axis == "Vy" else 0,
vz=1 if self.axis == "Vz" else 0,
)

unit_stress = self.steel_cross_section.profile.calculate_stress(rif1d)
sig_zxy_data = unit_stress.get_stress()[0]["sig_zxy"]
sig_zxy = float(np.max(np.abs(sig_zxy_data))) * abs(self.v)
resistance = float(self.steel_cross_section.yield_strength / np.sqrt(3) / self.gamma_m0 / sig_zxy * self.v * KN_TO_N)

check_shear = formula_6_19.Form6Dot19CheckDesignElasticShearResistance(
tau_ed=sig_zxy, f_y=self.steel_cross_section.yield_strength, gamma_m0=self.gamma_m0
)

return {
"shear_stress": sig_zxy,
"resistance": resistance,
"check": check_shear,
}

def result(self) -> CheckResult:
"""Calculate result of plastic shear force resistance.

Returns
-------
CheckResult
True if the shear force check passes, False otherwise.
"""
steps = self.calculation_formula()
provided = abs(self.v) * KN_TO_N
required = steps["resistance"]
return CheckResult.from_comparison(provided=provided, required=required)

def report(self, n: int = 2) -> Report:
"""Returns the report for the elastic shear force check.

Parameters
----------
n : int, optional
Number of decimal places for numerical values in the report (default is 2).

Returns
-------
Report
Report of the elastic shear force check.
"""
report = Report("Check: shear force steel I-beam (Class 3/4)")
if self.v == 0:
report.add_paragraph("No shear force was applied; therefore, no shear force check is necessary.")
return report
axis_label = "(vertical) z" if self.axis == "Vz" else "(horizontal) y"
report.add_paragraph(
rf"Profile {self.steel_cross_section.profile.name} with steel quality {self.steel_cross_section.material.steel_class.name} "
rf"is loaded with a shear force of {abs(self.v):.{n}f} kN in the {axis_label}-direction. "
rf"The shear stress $\tau_{{ed}}$ is calculated using elastic theory. "
)
formulas = self.calculation_formula()
report.add_paragraph(f"The maximum shear stress is: {formulas['shear_stress']:.{n}f} N/mm². ")

tau_max = round(self.steel_cross_section.yield_strength / (np.sqrt(3) * self.gamma_m0), n)
report.add_paragraph("The maximum allowed shear stress is calculated as follows:")
report.add_paragraph(rf"$f_y / (\sqrt{{3}} \cdot \gamma_{{M0}})$ = {tau_max} N/mm². ")

report.add_paragraph("The unity check is calculated as follows:")
check_formula = formulas["check"]
assert isinstance(check_formula, Formula), "Expected Formula for check"
report.add_formula(check_formula, n=n)
if self.result().is_ok:
report.add_paragraph("The check for elastic shear force satisfies the requirements.")
else:
report.add_paragraph("The check for elastic shear force does NOT satisfy the requirements.")
return report
8 changes: 6 additions & 2 deletions blueprints/checks/eurocode/steel/strength_tension.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,18 @@ def report(self, n: int = 2) -> Report:
if self.n == 0:
report.add_paragraph("No tensile force was applied; therefore, no tensile force check is necessary.")
return report

# Cache calculation formulas to avoid redundant recalculations
formulas = self.calculation_formula()

report.add_paragraph(
rf"Profile {self.steel_cross_section.profile.name} with steel quality {self.steel_cross_section.material.steel_class.name} "
rf"is loaded with a tensile force of {self.n:.{n}f} kN. "
rf"The resistance is calculated as follows:"
)
report.add_formula(self.calculation_formula()["resistance"], n=n)
report.add_formula(formulas["resistance"], n=n)
report.add_paragraph("The unity check is calculated as follows:")
report.add_formula(self.calculation_formula()["check"], n=n)
report.add_formula(formulas["check"], n=n)
if self.result().is_ok:
report.add_paragraph("The check for tensile force satisfies the requirements.")
else:
Expand Down
Loading
Loading