Skip to content

571 feature request add basis for complex steel shapes #572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 69 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
44b2722
Add initial implementation of general steel plotter and default plott…
GerjanDorgelo Apr 21, 2025
622cb0f
Add tests for the steel plotter
GerjanDorgelo Apr 21, 2025
9dd4521
Add base implementation and tests for SteelCrossSection class
GerjanDorgelo Apr 21, 2025
55a7000
Add SteelElement class and corresponding tests for steel cross-sectio…
GerjanDorgelo Apr 21, 2025
e133a61
Add CHSSteelProfile class and corresponding tests for circular hollow…
GerjanDorgelo Apr 21, 2025
6fd18a6
Add StripSteelProfile class and corresponding tests for steel strip p…
GerjanDorgelo Apr 21, 2025
1330cbd
Add examples and documentation for visualizing steel profile shapes
GerjanDorgelo Apr 21, 2025
c343495
Refactor elastic section modulus calculations and remove plastic sect…
GerjanDorgelo Apr 21, 2025
a082479
Add fixtures for StripSteelProfile and CHSSteelProfile testing
GerjanDorgelo Apr 21, 2025
08b3647
Refactor SteelElement class: update imports, remove unused properties…
GerjanDorgelo Apr 21, 2025
17b9851
Fix yield_strength and ultimate_strength properties to raise ValueErr…
GerjanDorgelo Apr 21, 2025
c1f76a6
Fix geometry method calls in SteelElement class and update expected s…
GerjanDorgelo Apr 21, 2025
d300845
Add polygon property to SteelElement and update geometry handling in …
GerjanDorgelo Apr 21, 2025
5c9d9fc
Add tests for handling invalid yield and ultimate strength in SteelEl…
GerjanDorgelo Apr 21, 2025
a750428
Implement SteelCrossSection class with geometry and section properties
GerjanDorgelo Apr 21, 2025
5cf5339
Refactor import statements for SteelCrossSection to use the correct m…
GerjanDorgelo Apr 21, 2025
3a2e38b
Refactor SteelCrossSection and related profiles to unify area propert…
GerjanDorgelo Apr 21, 2025
2ebc2cf
Merge branch 'main' into 571-feature-request-add-basis-for-complex-st…
GerjanDorgelo Apr 21, 2025
a683067
Merge branch 'main' into 571-feature-request-add-basis-for-complex-st…
GerjanDorgelo Apr 22, 2025
198888d
Fix test_centroid to use correct expected value for QuarterCircularSp…
GerjanDorgelo Apr 22, 2025
badce9a
Add corrosion adjustment parameters to get_profile methods for CHS an…
GerjanDorgelo Apr 22, 2025
22dd2d8
Fix section method to call geometry() and update SteelElement instant…
GerjanDorgelo Apr 22, 2025
b9a5305
Add I-Profile steel section implementation and corresponding test suite
GerjanDorgelo Apr 22, 2025
50c5fed
Refactor LoadStandardCHS and LoadStandardStrip classes to clarify pro…
GerjanDorgelo Apr 22, 2025
a055ea7
Add support for HEB and custom I profiles in steel profile shapes exa…
GerjanDorgelo Apr 22, 2025
c44cdcc
Update steel profile examples to include corrosion parameters for CHS…
GerjanDorgelo Apr 22, 2025
4bd98bd
Add corrosion tests for CHS, HEB, and Strip profiles to validate erro…
GerjanDorgelo Apr 22, 2025
836d04e
Move mock fixtures for CrossSection and SteelMaterial to conftest.py …
GerjanDorgelo Apr 22, 2025
6c1eeac
Fix type hints for mock fixtures in conftest.py to improve clarity
GerjanDorgelo Apr 22, 2025
2f59eca
Add BaseGeometry import for type checking and improve polygon method …
GerjanDorgelo Apr 22, 2025
b2e67e3
Enhance polygon method to ensure valid geometry and consistent orient…
GerjanDorgelo Apr 22, 2025
1811fb3
Add mocker fixture for improved object mocking in tests
GerjanDorgelo Apr 22, 2025
15d0861
Ensure combined geometry is a valid Polygon with consistent orientation
GerjanDorgelo Apr 22, 2025
420cb59
Merge branch 'main' into 571-feature-request-add-basis-for-complex-st…
GerjanDorgelo Apr 28, 2025
b6c15cc
Update plastic section modulus properties to allow None return type
GerjanDorgelo Apr 28, 2025
3c081c5
Merge branch 'main' into 571-feature-request-add-basis-for-complex-st…
egarciamendez May 3, 2025
75fb1b3
Refactor steel cross-section computations using `polygon`.
egarciamendez May 4, 2025
b23d0b6
Refactor cross-section methods to remove abstraction.
egarciamendez May 7, 2025
2a4d9b4
Refactor docstring format in HEA profile initializer.
egarciamendez May 7, 2025
6c1d056
Update docstrings for HEB, HEM, and IPE profile classes
egarciamendez May 7, 2025
07db4cd
Update flange thickness for HEM280 profile
egarciamendez May 7, 2025
a0b8be0
Clarify documentation for nominal thickness in steel elements.
egarciamendez May 7, 2025
9f2ee36
Remove redundant properties from SteelElement class
egarciamendez May 7, 2025
0eac108
Clarify nominal thickness description in docstring.
egarciamendez May 7, 2025
4b6baab
Refactor steel cross-section class for dataclass support
egarciamendez May 7, 2025
1f5c86b
Add height and width properties to steel cross-section
egarciamendez May 7, 2025
480a459
Optimize steel section plotter for combined cross-sections
egarciamendez May 7, 2025
654446c
Refactor I-Profile steel section with dataclass and enhancements
egarciamendez May 7, 2025
8cdbcd2
Merge branch 'main' into 571-feature-request-add-basis-for-complex-st…
egarciamendez May 7, 2025
16c8388
Adjust text positioning offsets in steel plotter.
egarciamendez May 7, 2025
119d9df
Refine `plot` method type hints and add example usage.
egarciamendez May 7, 2025
1e7649b
Refactor CHSSteelProfile for immutability and enhanced flexibility
egarciamendez May 7, 2025
69eddb9
Set x-axis limits in steel profile plotter.
egarciamendez May 7, 2025
61e43a5
Reorder class docstring attributes for clarity
egarciamendez May 7, 2025
c53d9d7
Enhance docstring for ISteelProfile class.
egarciamendez May 7, 2025
3703ea1
Refactor `StripSteelProfile` and simplify standard profile loading.
egarciamendez May 7, 2025
7655596
Refactor steel profile fixtures to use `from_standard_profile`.
egarciamendez May 7, 2025
962bef8
Refactor strip profile tests for streamlined setup and logic
egarciamendez May 7, 2025
65f7e6b
Refactor strip profile tests for streamlined setup and logic
egarciamendez May 7, 2025
0294e05
Add icecream library to development dependencies
egarciamendez May 7, 2025
adb7de0
Refactor CHS profile tests for updated class interface
egarciamendez May 7, 2025
3a2d68a
Refactor test assertions to access cross_section directly
egarciamendez May 7, 2025
7843551
Fix incorrect property usage in ultimate strength calculation.
egarciamendez May 7, 2025
14ee661
Refine area property docstring for clarity and accuracy.
egarciamendez May 7, 2025
a97bb28
Remove unused properties and redundant imports from cross_section_rec…
egarciamendez May 7, 2025
2168b5d
Add new tests for strength and plastic section moduli
egarciamendez May 7, 2025
53cc3e9
Add tests for CombinedSteelCrossSection functionality
egarciamendez May 7, 2025
2e8878f
Merge remote-tracking branch 'origin/571-feature-request-add-basis-fo…
egarciamendez May 7, 2025
27583f7
Update types-shapely version to 2.1.0.20250512 in requirements_dev.txt
GerjanDorgelo Jun 3, 2025
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
23 changes: 17 additions & 6 deletions blueprints/structural_sections/_cross_section.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,25 @@ def polygon(self) -> Polygon:
"""Shapely Polygon representing the cross-section."""

@property
@abstractmethod
def area(self) -> MM2:
"""Area of the cross-section [mm²]."""
"""Area of the cross-section [mm²].

When using circular cross-sections, the area is an approximation of the area of the polygon.
The area is calculated using the `area` property of the Shapely Polygon.

In case you need an exact answer then you need to override this method in the derived class.
"""
return self.polygon.area

@property
@abstractmethod
def perimeter(self) -> MM:
"""Perimeter of the cross-section [mm]."""
return self.polygon.length

@property
@abstractmethod
def centroid(self) -> Point:
"""Centroid of the cross-section [mm]."""
return self.polygon.centroid

@property
@abstractmethod
Expand Down Expand Up @@ -78,16 +84,21 @@ def plastic_section_modulus_about_y(self) -> MM3 | None:
def plastic_section_modulus_about_z(self) -> MM3 | None:
"""Plastic section modulus about the z-axis [mm³]."""

@abstractmethod
def geometry(self, mesh_size: MM | None = None) -> Geometry:
"""Abstract method to be implemented by subclasses to return the geometry of the cross-section.
"""Geometry of the cross-section.

Properties
----------
mesh_size : MM
Maximum mesh element area to be used within
the Geometry-object finite-element mesh. If not provided, a default value will be used.
"""
if mesh_size is None:
mesh_size = 2.0

geom = Geometry(geom=self.polygon)
geom.create_mesh(mesh_sizes=mesh_size)
return geom

def section(self) -> Section:
"""Section object representing the cross-section."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ def polygon(self) -> Polygon:
"""
left_lower = (self.x, self.y)

# Approximate the quarter circle with 50 straight lines
# Approximate the quarter circle with 25 straight lines
quarter_circle_points = [
(self.x + self.radius - self.radius * math.cos(math.pi / 2 * i / 50), self.y + self.radius - self.radius * math.sin(math.pi / 2 * i / 50))
for i in range(51)
(self.x + self.radius - self.radius * math.cos(math.pi / 2 * i / 25), self.y + self.radius - self.radius * math.sin(math.pi / 2 * i / 25))
for i in range(26)
]
for i in range(51):
for i in range(26):
if self.mirrored_horizontally:
quarter_circle_points[i] = (2 * left_lower[0] - quarter_circle_points[i][0], quarter_circle_points[i][1])
if self.mirrored_vertically:
Expand Down
40 changes: 2 additions & 38 deletions blueprints/structural_sections/cross_section_rectangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from dataclasses import dataclass

from sectionproperties.pre import Geometry
from shapely import Point, Polygon
from shapely import Polygon

from blueprints.structural_sections._cross_section import CrossSection
from blueprints.type_alias import MM, MM2, MM3, MM4
from blueprints.type_alias import MM, MM3, MM4


@dataclass(frozen=True)
Expand Down Expand Up @@ -67,42 +67,6 @@ def polygon(self) -> Polygon:
]
)

@property
def area(self) -> MM2:
"""
Calculate the area of the rectangular cross-section.

Returns
-------
MM2
The area of the rectangle.
"""
return self.width * self.height

@property
def perimeter(self) -> MM:
"""
Calculate the perimeter of the rectangular cross-section.

Returns
-------
MM
The perimeter of the rectangle.
"""
return 2 * (self.width + self.height)

@property
def centroid(self) -> Point:
"""
Get the centroid of the rectangular cross-section.

Returns
-------
Point
The centroid of the rectangle.
"""
return Point(self.x, self.y)

@property
def moment_of_inertia_about_y(self) -> MM4:
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
"""Base class of all steel cross-sections."""

from abc import ABC
from collections.abc import Sequence
from dataclasses import dataclass, field

from shapely.geometry import Polygon
from shapely.geometry.base import BaseGeometry
from shapely.geometry.polygon import orient

from blueprints.structural_sections._cross_section import CrossSection
from blueprints.structural_sections.steel.steel_element import SteelElement
from blueprints.type_alias import KG_M, M3_M, MM, MM3, MPA
from blueprints.unit_conversion import MM3_TO_M3


@dataclass(kw_only=True)
class CombinedSteelCrossSection(CrossSection, ABC):
"""Base class of all steel cross-sections.

Parameters
----------
elements : Sequence[SteelElement], optional
A sequence of steel elements that make up the cross-section.
Default is an empty list.
name : str, optional
The name of the cross-section. Default is "Combined Steel Cross Section".
"""

elements: Sequence[SteelElement] = field(default_factory=list)
name: str = "Combined Steel Cross Section"

@property
def polygon(self) -> Polygon:
"""Return the polygon of the steel cross-section."""
# check if there are any elements
if not self.elements:
raise ValueError("No elements have been added to the cross-section.")

# return the polygon of the first element if there is only one
if len(self.elements) == 1:
return self.elements[0].cross_section.polygon

# Combine the polygons of all elements if there is multiple
combined_polygon: BaseGeometry = self.elements[0].cross_section.polygon
for element in self.elements[1:]:
combined_polygon = combined_polygon.union(element.cross_section.polygon)

# Ensure the result is a valid Polygon
if not isinstance(combined_polygon, Polygon):
raise TypeError("The combined geometry is not a valid Polygon.")

# Ensure consistent orientation
return orient(combined_polygon)

@property
def height(self) -> MM:
"""Height of the cross-section [mm]."""
return self.polygon.bounds[3] - self.polygon.bounds[1]

@property
def width(self) -> MM:
"""Width of the cross-section [mm]."""
return self.polygon.bounds[2] - self.polygon.bounds[0]

@property
def volume_per_meter(self) -> M3_M:
"""Total volume of the reinforced cross-section per meter length [m³/m]."""
length = 1000 # mm
return self.area * length * MM3_TO_M3

@property
def weight_per_meter(self) -> KG_M:
"""
Calculate the weight per meter of the steel element.

Returns
-------
KG_M
The weight per meter of the steel element.
"""
return sum(element.weight_per_meter for element in self.elements)

@property
def moment_of_inertia_about_y(self) -> KG_M:
"""Moment of inertia about the y-axis per meter length [mm⁴]."""
body_moments_of_inertia = sum(element.cross_section.moment_of_inertia_about_y for element in self.elements)
parallel_axis_theorem = sum(
element.cross_section.area * (element.cross_section.centroid.y - self.centroid.y) ** 2 for element in self.elements
)
return body_moments_of_inertia + parallel_axis_theorem

@property
def moment_of_inertia_about_z(self) -> KG_M:
"""Moment of inertia about the z-axis per meter length [mm⁴]."""
body_moments_of_inertia = sum(element.cross_section.moment_of_inertia_about_z for element in self.elements)
parallel_axis_theorem = sum(
element.cross_section.area * (element.cross_section.centroid.x - self.centroid.x) ** 2 for element in self.elements
)
return body_moments_of_inertia + parallel_axis_theorem

@property
def elastic_section_modulus_about_y_positive(self) -> KG_M:
"""Elastic section modulus about the y-axis on the positive z side [mm³]."""
distance_to_top = max(y for _, y in self.polygon.exterior.coords) - self.centroid.y
return self.moment_of_inertia_about_y / distance_to_top

@property
def elastic_section_modulus_about_y_negative(self) -> KG_M:
"""Elastic section modulus about the y-axis on the negative z side [mm³]."""
distance_to_bottom = self.centroid.y - min(y for _, y in self.polygon.exterior.coords)
return self.moment_of_inertia_about_y / distance_to_bottom

@property
def elastic_section_modulus_about_z_positive(self) -> KG_M:
"""Elastic section modulus about the z-axis on the positive y side [mm³]."""
distance_to_right = max(x for x, _ in self.polygon.exterior.coords) - self.centroid.x
return self.moment_of_inertia_about_z / distance_to_right

@property
def elastic_section_modulus_about_z_negative(self) -> KG_M:
"""Elastic section modulus about the z-axis on the negative y side [mm³]."""
distance_to_left = self.centroid.x - min(x for x, _ in self.polygon.exterior.coords)
return self.moment_of_inertia_about_z / distance_to_left

@property
def plastic_section_modulus_about_y(self) -> MM3 | None:
"""Plastic section modulus about the y-axis [mm³]."""
return self.section_properties().sxx

@property
def plastic_section_modulus_about_z(self) -> MM3 | None:
"""Plastic section modulus about the z-axis [mm³]."""
return self.section_properties().syy

@property
def yield_strength(self) -> MPA:
"""
Calculate the yield strength of the steel element.

This is the minimum yield strength of all elements in the cross-section.

Returns
-------
MPa
The yield strength of the steel element.
"""
# let's find the minimum yield strength of all elements
return min(element.yield_strength for element in self.elements)

@property
def ultimate_strength(self) -> MPA:
"""
Calculate the ultimate strength of the steel element.

This is the minimum ultimate strength of all elements in the cross-section.

Returns
-------
MPa
The ultimate strength of the steel element.
"""
# let's find the minimum ultimate strength of all elements
return min(element.ultimate_strength for element in self.elements)
Loading